EthSafeMessage
EthSafeMessage is the Safe Protocol Kit class that represents a Safe message and its accumulating owner signatures. It is the message-signing counterpart to EthSafeTransaction and is returned by protocolKit.createMessage(message).
Shape
class EthSafeMessage implements SafeMessage {
data: EIP712TypedData | string
signatures: Map<string, SafeSignature> = new Map()
// Additional methods: getSignature, addSignature, encodedSignatures, ...
}
Two fields matter:
data— the message being signed. Either a plain string (for EIP-191eth_sign) or an EIP-712 typed-data object withtypes,primaryType,domain, andmessagefields. The type distinction is purely nominal; the SDK routes both throughhashSafeMessageto produce the canonical Safe-wrapped hash.signatures— a map from owner address toSafeSignature. It starts empty and is populated as each owner signs. The map is the heart of the class: it is the signature accumulator that lets a multi-step multisig signing workflow coexist with the synchronous-lookingsignMessageAPI.
Why a mutable accumulator
Multisig signing is fundamentally asynchronous and distributed. Four owners of a 3/4 Safe sit at four different wallets, possibly in four different processes, possibly across hours or days. No single process can “sign a message” in one shot — each process contributes one signature, hands the accumulator off (or stores it somewhere shared), and waits for the others.
EthSafeMessage models this directly. protocolKit.signMessage(safeMessage, method) does not return a new immutable object; it mutates (or returns an updated copy of) the same safeMessage, adding one entry to the signatures map. The caller holds a single object that grows until it carries enough signatures to meet the Safe’s threshold.
This is the same pattern Safe uses for transactions (EthSafeTransaction with its own signatures map). Messages and transactions are structurally unified as “a payload plus an accumulating set of owner signatures”.
Key methods
getSignature(address): SafeSignature | undefined— look up a specific owner’s signature. Used when you need to extract one signature for API submission, e.g.safeMessage.getSignature(OWNER_1_ADDRESS)to post it to the Safe Transaction Service viaapiKit.addMessage.addSignature(signature: SafeSignature)— manually insert a signature, used when a signature was produced outside the Protocol Kit (e.g. received from a counterparty over the network).encodedSignatures(): string— concatenate all signatures in the map into the canonical bytes blob that Safe’scheckSignaturesexpects. Used when callingprotocolKit.isValidSignature(messageHash, encodedSignatures)for off-chain validation.
Relationship to the signature bytes Safe expects
Safe’s on-chain checkSignatures function takes a single bytes argument containing all owner signatures concatenated in owner-address order. EthSafeMessage.encodedSignatures() produces exactly that blob from the accumulated map, sorting by address as Safe requires. The blob includes both ECDSA signatures (65 bytes each, v ∈ {27, 28}) and contract signatures (65 bytes of static data with v = 0, plus a dynamic tail containing the nested signer’s contract signature bytes).
See Safe Nested Contract Signatures for how contract signatures are produced before being added to the map, and Safe Protocol Kit Message Signatures for where in the flow encodedSignatures is called.
Connections
- Safe Protocol Kit: the SDK that creates and operates on
EthSafeMessageinstances - Safe Protocol Kit Message Signatures: the end-to-end signing flow
- Safe SigningMethod: the enum used to drive
signMessagecalls against anEthSafeMessage - Safe Nested Contract Signatures: how contract signatures enter the signature map
- EIP-712: Typed structured data hashing and signing: the type of data that can appear in the
datafield - Smart Account Signatures: the verifier-side pattern this accumulator eventually feeds