API
Guides
Messages

Messages with off-chain signatures

This guide shows how to interact with the Safe Transaction Service API to create and sign messages with a Safe account.

The different steps are implemented using Curl (opens in a new tab) requests, the Safe{Core} SDK (opens in a new tab) TypeScript library and the safe-eth-py (opens in a new tab) Python library.

Prerequisites

  1. Node.js and npm (opens in a new tab) when using the Safe{Core} SDK.
  2. Python (opens in a new tab) >= 3.9 when using safe-eth-py.
  3. Have a Safe account configured with a threshold of 2, where two signatures are needed.

Steps

Install dependencies


_10
yarn add ethers @safe-global/api-kit @safe-global/protocol-kit @safe-global/safe-core-sdk-types

Imports


_10
import { ethers } from 'ethers'
_10
import SafeApiKit, { AddMessageProps } from '@safe-global/api-kit'
_10
import Safe, { EthersAdapter, hashSafeMessage } from '@safe-global/protocol-kit'

Create a Safe message


_19
const ethProvider = new ethers.JsonRpcProvider(config.RPC_URL)
_19
_19
// Instantiate an EthAdapter with Owner A
_19
const ownerA = new ethers.Wallet(config.OWNER_A_PRIVATE_KEY, ethProvider)
_19
const ethAdapterOwnerA = new EthersAdapter({
_19
ethers,
_19
signerOrProvider: ownerA
_19
})
_19
_19
// Initialize the Protocol Kit with Owner A
_19
const protocolKitOwnerA = await Safe.create({
_19
ethAdapter: ethAdapterOwnerA,
_19
safeAddress: config.SAFE_ADDRESS
_19
})
_19
_19
const rawMessage: string = 'A Safe Message - ' + Date.now()
_19
_19
// Create a Safe message
_19
const safeMessage = protocolKitOwnerA.createMessage(rawMessage)

Sign the message


_10
// Sign the message with Owner A
_10
const signedMessageOwnerA = await protocolKitOwnerA.signMessage(safeMessage)

Send the message to the service


_12
// Initialize the API Kit
_12
const apiKit = new SafeApiKit({
_12
chainId: 11155111n
_12
})
_12
_12
const messageProps: AddMessageProps = {
_12
message: rawMessage,
_12
signature: signedMessageOwnerA.encodedSignatures()
_12
}
_12
_12
// Send the message to the Transaction Service with the signature from Owner A
_12
apiKit.addMessage(config.SAFE_ADDRESS, messageProps)

Collect the missing signatures

Get the pending message


_20
// Instantiate an EthAdapter with Owner B
_20
const ownerB = new ethers.Wallet(config.OWNER_B_PRIVATE_KEY, ethProvider)
_20
const ethAdapterOwnerB = new EthersAdapter({
_20
ethers,
_20
signerOrProvider: ownerB
_20
})
_20
_20
// Initialize the Protocol Kit with Owner B
_20
const protocolKitOwnerB = await Safe.create({
_20
ethAdapter: ethAdapterOwnerB,
_20
safeAddress: config.SAFE_ADDRESS
_20
})
_20
_20
// Get the Safe message hash
_20
const safeMessageHash = await protocolKitOwnerB.getSafeMessageHash(
_20
hashSafeMessage(rawMessage)
_20
)
_20
_20
// Get the Safe message
_20
const safeServiceMessage = await apiKit.getMessage(safeMessageHash)

Add missing signatures


_10
// Sign the message with Owner B
_10
const signedMessageOwnerB = await protocolKitOwnerB.signMessage(safeServiceMessage)
_10
_10
const ownerBAddress = (await ethAdapterOwnerB.getSignerAddress()) || '0x'
_10
_10
// Send the message to the Transaction Service with the signature from Owner B
_10
await apiKit.addMessageSignature(
_10
safeMessageHash,
_10
signedMessageOwnerB.getSignature(ownerBAddress)?.data || '0x'
_10
)

Was this page helpful?