import { normalizeSuiAddress } from '@mysten/sui.js/utils'; import { sha256 } from '@noble/hashes/sha256'; import { bytesToHex } from '@noble/hashes/utils'; import sortKeys from 'sort-keys-recursive'; import { UpdateAddressBookEntry } from '@/types'; import { stringToBuffer } from '@/utils'; /** * MessageHelper defines messages to be signed by single signer wallet */ export class SigningMessageHelper { static createMSafeMessage(msafeAddress: string): string { return `Create MSafe Account: ${msafeAddress}`; } static loginMessageWithTimestamp(timestamp: string) { return `Welcome to MSafe. ${timestamp}`; } static loginMessage() { return SigningMessageHelper.loginMessageWithTimestamp(new Date().toUTCString()); } static decodeLoginMessageTimestamp(message: string) { const regex = /Welcome to MSafe. (.+)/; const matches = message.match(regex); return matches ? matches[1] : undefined; } static proposeIntentionMessage(input: { intention: any; sn: number; msafeAddress: string }) { const sortedInput = sortKeys(input); const message = JSON.stringify(sortedInput, (_, value) => typeof value === 'bigint' ? `${value.toString()}n` : value, ); const hash = sha256(message); const hashStr = bytesToHex(hash); return `Submit a new transaction proposal [${input.sn}]: ${hashStr}`; } static updateAddressBookMessage(updates: UpdateAddressBookEntry[]) { const sortedUpdates = updates .map((update) => ({ ...update, address: normalizeSuiAddress(update.address), })) .map((update) => SigningMessageHelper.sortObjectKeys(update)); const raw = JSON.stringify( sortedUpdates, (_, value) => (typeof value === 'bigint' ? `${value.toString()}n` : value), 2, ); const encoded = stringToBuffer(raw); if (encoded.length <= 1000) { // BCS signing can handle this amount of data, directly return return `Address book entry update: ${raw}`; } return `Bulk address book update: ${sha256(raw)}`; } private static sortObjectKeys(obj: Record): Record { return Object.keys(obj) .sort() .reduce((result, key) => ({ ...result, [key]: obj[key] }), {} as Record); } }