Message signatures (Safe Protocol Kit)
Official Safe documentation explaining how to generate, sign, publish, and validate messages from a Safe account using the @safe-global/protocol-kit TypeScript SDK. It is the practical implementation companion to the abstract standards machinery — EIP-712, EIP-1271, and Safe’s CompatibilityFallbackHandler + SignMessageLib contracts — showing the exact method calls a developer uses to collect ECDSA signatures from EOA owners, contract signatures from nested Safe owners, publish the result either off-chain to the Safe Transaction Service or on-chain via a signMessage delegatecall, and verify the final signature through isValidSignature.
Core contribution
Where the EIP-712 and EIP-1271 specifications describe what signatures must look like, this guide describes how a developer actually produces them. It covers the four-stage flow — create, sign, publish, validate — and the specific method names, signing-method enums, and API Kit endpoints involved at each step.
Create: protocolKit.createMessage(message) returns an EthSafeMessage instance whose shape mirrors EthSafeTransaction — data: EIP712TypedData | string plus a signatures: Map<string, SafeSignature> that accumulates signatures as they are collected. A message can be either a plain string (signed via EIP-191 eth_sign) or an EIP-712 typed-data object.
Sign: protocolKit.signMessage(safeMessage, signingMethod) attaches a signature to the accumulator. The SigningMethod enum has three values that correspond to the three signing paths: ETH_SIGN (EIP-191 string signing with an EOA), ETH_SIGN_TYPED_DATA_V4 (EIP-712 typed-data signing with an EOA), and SAFE_SIGNATURE (another Safe account signing on behalf of itself as a parent Safe’s owner, which produces an EIP-1271 contract signature). The last case requires passing a parentSafe address so the nested Safe knows which domain the signature is for.
Build nested signatures: When a Safe is owned by other Safes (e.g. SAFE_3_4_ADDRESS owned by SAFE_1_1_ADDRESS and SAFE_2_3_ADDRESS), each child Safe must first collect signatures from its own owners, then call buildContractSignature(ownerSignatures, childSafeAddress) to produce a single contract signature that represents the child Safe’s consent. These contract signatures are then added to the parent Safe’s safeMessage.signatures map alongside any EOA signatures, and the parent’s checkSignatures resolves them via recursive EIP-1271 calls.
Publish off-chain: The @safe-global/api-kit exposes addMessage(safeAddress, { message, signature }) and addMessageSignature(safeMessageHash, signature). The first call proposes a new message with a single owner signature; subsequent calls add additional owner signatures until the Safe’s threshold is reached. The Safe Transaction Service stores the message and its signatures in a centralized-but-open-source database that Safe{Wallet} reads from. buildSignatureBytes([signature]) serializes a signature for transport over the API.
Publish on-chain: The SignMessageLib contract, fetched via getSignMessageLibContract({ safeVersion }), is a delegatecall target whose signMessage(messageHash) function writes into the Safe’s own signedMessages storage mapping. The guide shows the full transaction construction: compute hashSafeMessage(MESSAGE), encode the call as transaction data, build a SafeTransactionDataPartial with operation: OperationType.DelegateCall, create and execute the Safe transaction. Once the transaction lands, the message hash is permanently stored in the Safe contract.
Validate: protocolKit.isValidSignature(messageHash, signature) is the SDK wrapper around EIP-1271. For on-chain messages, the second argument is '0x' (empty bytes), triggering the pre-approved-hash lookup mode. For off-chain messages, the second argument is safeMessage.encodedSignatures(), which produces the concatenated owner-signature blob that Safe’s checkSignatures expects. Both routes delegate to the CompatibilityFallbackHandler.isValidSignature implementation on the Safe contract.
Key ideas
EthSafeMessageas a signature accumulator: A mutable object that collects owner signatures over time rather than a single monolithic signature. This is how Safe’s multisig model maps onto off-chain workflows — threshold satisfaction happens gradually across multiple wallets.SigningMethodas the selector between EOA and contract signing: The samesignMessageentry point routes through eithereth_sign,eth_signTypedData_v4, or a nested-Safe contract signature depending on the enum value. Developers do not have to manually construct the different signature formats.buildContractSignatureas the recursive step: Nested Safes produce contract signatures by collecting their own owners’ signatures first and then wrapping them. This is the recursive structure that lets Safes own Safes arbitrarily deep — each level’s signatures are consumable by the level above through EIP-1271.- Off-chain vs on-chain as two equally valid publication strategies: The guide treats them symmetrically. Off-chain via the API Kit is the default (cheap, fast, Safe{Wallet}-compatible). On-chain via
SignMessageLibis the fallback when the verifier cannot interact with the Safe Transaction Service or when the signature must survive without any centralized dependency. hashSafeMessageas the deterministic bridge: Both publication paths and the validation path all use the samehashSafeMessage(MESSAGE)function to compute the canonical Safe-wrapped message hash. This is the common coordinate that ties signatures produced by one path to signatures verified on another.
Connections to existing knowledge
This source directly implements the concepts documented in the existing wiki:
- EIP-712 is the hashing scheme used by
ETH_SIGN_TYPED_DATA_V4and byhashSafeMessage(which wraps the inner EIP-712 digest in aSafeMessage(bytes)struct under Safe’s own domain). - EIP-1271 is the interface that
protocolKit.isValidSignaturecalls — through theCompatibilityFallbackHandlercontract — to complete validation. - Smart Account Signatures describes the branching verifier logic; the Protocol Kit is the signer-side SDK that produces what those verifiers expect.
- Off-Chain Signatures is the broader pattern; the Safe Transaction Service + API Kit is Safe’s implementation of it.
- Creating a Farcaster Account by Hand performs the same on-chain
SignMessageLibpre-approval flow manually withcast. The Protocol Kit automates that same flow as a single function call:getSignMessageLibContract+encode('signMessage', [messageHash])+createTransaction+executeTransaction.
No contradictions. The source is a higher-level abstraction over the standards already documented — a developer-facing SDK that wraps every concept the wiki pages describe at the protocol level.
Ingestion manifest
- Safe Protocol Kit Message Signatures (source)
- Safe Protocol Kit (entity)
- EthSafeMessage (concept)
- Safe SigningMethod (concept)
- Safe Nested Contract Signatures (concept)
- SignMessageLib (concept)
- Safe Transaction Service (entity)
MOC updated: Digital Identity
https://docs.safe.global/sdk/protocol-kit/guides/signatures/messages