Safe Transaction Service
The Safe Transaction Service is Safe’s open-source off-chain backend for storing proposed and partially-signed Safe transactions and messages. It is the database layer that makes the multisig signing workflow asynchronous and distributed: owners can sign at different times, from different wallets, without ever touching the blockchain until the threshold is met.
Why it exists
Safe’s on-chain contract cannot track partially-signed transactions — a transaction is either on-chain (executed) or off-chain (not yet executed), with nothing in between. But multisig signing is inherently multi-step: owner A signs today, owner B signs tomorrow, owner C is offline until next week. The partial signatures have to live somewhere until enough accumulate to trigger an on-chain execution.
Without a shared storage layer, each owner would have to email or otherwise ferry their signature blob to the others, and the final executor would have to collect and concatenate them all manually. The Safe Transaction Service centralizes this: owners submit their signatures to a shared API, the service stores them, and any party can fetch the current state to see what’s missing or to execute when the threshold is met.
The same logic applies to Safe messages. A message is not on-chain by default; it needs somewhere to accumulate its signatures so the set can be retrieved and verified later.
API surface for messages
The @safe-global/api-kit package is the official TypeScript client. Two methods matter for message signing:
addMessage(safeAddress, { message, signature })— proposes a new message. Themessagefield is the EIP-712 typed-data object (or string); thesignaturefield is the bytes-encoded first owner signature produced bybuildSignatureBytes([signature]). The service computes thesafeMessageHashfrom the message and records the proposal under that key.addMessageSignature(safeMessageHash, signature)— adds an additional owner signature to an existing proposal. Subsequent owners call this instead ofaddMessagebecause the message is already stored; only the new signature needs to be sent.getMessage(safeMessageHash)— retrieves the current state: the message, the list of signatures collected so far, and a derived status field.
The corresponding endpoints accept transaction proposals (proposeTransaction, confirmTransaction) with parallel semantics.
Authentication and deployment
The hosted service at safe.global requires an API key, which is documented at docs.safe.global/core-api/how-to-use-api-keys. Without a key, only read endpoints work. Write endpoints (addMessage, addMessageSignature, proposeTransaction, confirmTransaction) all require authentication.
The service is open-source. Any team can deploy its own instance against any chain, using the reference implementation at github.com/safe-global/safe-transaction-service. The Protocol Kit and API Kit can be configured to point at a custom deployment instead of the hosted service.
Not a consensus layer
The Safe Transaction Service stores signatures; it does not validate them (beyond basic format checks), and it does not enforce Safe-specific rules like thresholds. It is a dumb database. All the cryptographic and governance logic lives on-chain in the Safe contract.
This matters because the service is centralized (or federated, depending on deployment), and a compromised service could in principle drop signatures, refuse to serve them, or misorder them. But it cannot forge a valid multisig signature — the final verification happens on-chain via checkSignatures and ecrecover / isValidSignature, and the service has no private key material. The worst a malicious service can do is censor; it cannot forge.
Safe{Wallet} integration
Safe{Wallet} is the official Safe UI. It reads from and writes to the Safe Transaction Service by default, which means any signature submitted through the SDK to the service immediately appears in Safe{Wallet}‘s UI for the corresponding Safe. Messages appear at https://app.safe.global/transactions/messages?safe=<NETWORK>:<SAFE_ADDRESS>.
This is how the Protocol Kit + Safe{Wallet} workflow composes: a developer’s script produces the first signature and calls addMessage, the remaining owners log into Safe{Wallet}, see the pending message, click “Confirm”, and Safe{Wallet} internally calls addMessageSignature with their signatures. Once the threshold is met, any party can fetch the complete signature set from the service and pass it to a verifier.
Connections
- Safe Protocol Kit: the SDK that produces signatures for the service to store
- Safe Protocol Kit Message Signatures: the flow that calls
addMessageandaddMessageSignature - EthSafeMessage: the client-side accumulator whose entries get posted to the service
- Off-Chain Signatures: the broader pattern the service supports
- Smart Account Signatures: what the stored signatures ultimately validate through