SDK
Guides
Execute transactions

Execute transactions

In this guide, you will learn how to create Safe transactions, sign them, collect the signatures from the different owners, and execute them.

See the Protocol Kit reference to find more details and configuration options.

Prerequisites

Install dependencies

First, you need to install some dependencies.


_10
pnpm add @safe-global/api-kit \
_10
@safe-global/protocol-kit \
_10
@safe-global/types-kit

Steps

Imports

Here are all the necessary imports for this guide.


_10
import SafeApiKit from '@safe-global/api-kit'
_10
import Safe from '@safe-global/protocol-kit'
_10
import {
_10
MetaTransactionData,
_10
OperationType
_10
} from '@safe-global/types-types'

Setup

You need a Safe account setup with two or more signers and threshold two, so at least multiple signatures have to be collected when executing a transaction.

This example uses private keys, but any EIP-1193 compatible signers can be used.


_10
const SAFE_ADDRESS = // ...
_10
_10
const OWNER_1_ADDRESS = // ...
_10
const OWNER_1_PRIVATE_KEY = // ...
_10
_10
const OWNER_2_PRIVATE_KEY = // ...
_10
_10
const RPC_URL = 'https://eth-sepolia.public.blastapi.io'

This guide uses Sepolia, but you can use any chain from the Safe Transaction Service supported networks.

Initialize the Protocol Kit

To handle transactions and signatures, you need to create an instance of the Protocol Kit with the provider, signer and safeAddress.

Optionally, you can track your Safe transactions on-chain by using the onchainAnalytics property.


_10
const protocolKitOwner1 = await Safe.init({
_10
provider: RPC_URL,
_10
signer: OWNER_1_PRIVATE_KEY,
_10
safeAddress: SAFE_ADDRESS,
_10
onchainAnalytics // Optional
_10
})

Create a transaction

Create a safeTransactionData object with the properties of the transaction, add it to an array of transactions you want to execute, and pass it to the createTransaction method.


_10
const safeTransactionData: MetaTransactionData = {
_10
to: '0x',
_10
value: '1', // 1 wei
_10
data: '0x',
_10
operation: OperationType.Call
_10
}
_10
_10
const safeTransaction = await protocolKitOwner1.createTransaction({
_10
transactions: [safeTransactionData]
_10
})

For more details on what to include in a transaction, see the createTransaction method in the reference.

Propose the transaction

Before a transaction can be executed, the signer who creates it needs to send it to the Safe Transaction Service so that it is accessible by the other owners, who can then give their approval and sign the transaction.

Firstly, you need to create an instance of the API Kit. In chains where the Safe Transaction Service is supported, it's enough to specify the chainId property.


_10
const apiKit = new SafeApiKit({
_10
chainId: 11155111n
_10
})

You need to calculate the Safe transaction hash, sign the transaction hash, and call the proposeTransaction method from the API Kit instance to propose a transaction.

For a full list and description of the properties see proposeTransaction in the API Kit reference.


_13
// Deterministic hash based on transaction parameters
_13
const safeTxHash = await protocolKitOwner1.getTransactionHash(safeTransaction)
_13
_13
// Sign transaction to verify that the transaction is coming from owner 1
_13
const senderSignature = await protocolKitOwner1.signHash(safeTxHash)
_13
_13
await apiKit.proposeTransaction({
_13
safeAddress,
_13
safeTransactionData: safeTransaction.data,
_13
safeTxHash,
_13
senderAddress: OWNER_1_ADDRESS,
_13
senderSignature: senderSignature.data
_13
})

Retrieve the pending transactions

The other signers need to retrieve the pending transactions from the Safe Transaction Service. Depending on the situation, different methods in the API Kit are available.

Call the getPendingTransactions method to retrieve all the pending transactions of a Safe account.


_10
const pendingTransactions = (await apiKit.getPendingTransactions(safeAddress)).results

Confirm the transaction

Once a signer has the pending transaction, they need to sign it with the Protocol Kit and submit the signature to the service using the confirmTransaction method.


_14
const protocolKitOwner2 = await Safe.init({
_14
provider: RPC_URL,
_14
signer: OWNER_2_PRIVATE_KEY,
_14
safeAddress: SAFE_ADDRESS
_14
})
_14
_14
const safeTxHash = transaction.transactionHash
_14
const signature = await protocolKitOwner2.signHash(safeTxHash)
_14
_14
// Confirm the Safe transaction
_14
const signatureResponse = await apiKit.confirmTransaction(
_14
safeTxHash,
_14
signature.data
_14
)

Execute the transaction

The Safe transaction is now ready to be executed. This can be done using the Safe{Wallet} (opens in a new tab) web interface, the Protocol Kit, the Safe CLI or any other tool that's available.

In this guide, the first signer will get the transaction from the service by calling the getTransaction method and execute it by passing the transaction with all the signatures to the executeTransaction method.


_10
const safeTransaction = await apiKit.getTransaction(safeTxHash)
_10
const executeTxResponse = await protocolKitOwner1.executeTransaction(safeTransaction)

Recap and further reading

After following this guide, you are able to create, sign, and execute Safe transactions with the Protocol Kit and share the signatures with the different signers using the API Kit.

Was this page helpful?