Transaction Workflows
This guide explains the lifecycle of a Safe transaction and how to manage each stage of the process.
Transaction Lifecycle
Safe transactions follow a specific workflow:
Creation: A transaction is created and signed by the first signer (or a delegate)
Proposal: The transaction is proposed to the Safe Transaction Gateway Service
Confirmation: Other signers then add their signatures (confirmations) to the transaction
Execution: When enough signatures are collected, the transaction can be executed on-chain
Creating and Proposing Transactions
You can create transactions very easily using your safe account like any other Ape account type:
Via Python API
# Create a simple transfer
# NOTE: This will fail to execute due to `submit=False`,
# but will successfully submit to the Safe Gateway
safe.transfer("0xRecipientAddress", "1 ether", submit=False)
You can also do more complex contract interactions:
# Load a contract
token = Contract("0xTokenAddress")
# Call a contract method
# NOTE: This will fail to execute due to `submit=False`,
# but will successfully submit to the Safe Gateway
token.transfer(
"0xRecipientAddress",
amount,
sender=safe,
submit=False,
)
Via CLI
# Propose a simple ETH transfer
ape safe pending propose --to 0xRecipientAddress --value "1 ether"
# Propose a contract interaction
ape safe pending propose --to 0xTokenAddress --data 0x123abc...
Listing Pending Transactions
Once a transaction has been signed and proposed to the Safe Gateway, you can fetch them from the service
Via Python API
# Get all pending transactions in the Safe Gateway's queue
for tx, confirmations in safe.pending_transactions():
print(f"Nonce: {tx.nonce}, To: {tx.to}, Data: {tx.data}, Value: {tx.value}")
print(f"Confirmations: {len(confirmations)}/{safe.confirmations_required}")
Via CLI
# List pending transactions
ape safe pending list
# Show more details
ape safe pending list --verbosoe
# Show confirmations for a transaction
ape safe pending show-confs 0x123...abc
Adding Confirmations to Transactions
You can also add more confirmations (signatures) to proposed transactions in the Safe Gateway queue.
Via Python API
# Find transaction by nonce
for tx, _ in safe.pending_transactions():
if tx.nonce == 42: # The nonce you're looking for
# Add signatures from local signers
signatures = safe.add_signatures(tx)
# Check if we have enough signatures to execute
if len(signatures) >= safe.confirmations_required:
print("Transaction ready to execute")
Via CLI
# Approve by transaction hash
ape safe pending approve 0x123abc...
# Or approve by nonce (if only one transaction with that nonce)
ape safe pending approve 42
Executing Transactions
When a transaction has enough signatures, it can then be executed on-chain.
Via Python API
from ape import accounts
# Get a regular account to submit the transaction
submitter = accounts.load("my-account")
# Find and execute a transaction that has enough signatures
for tx, confirmations in safe.pending_transactions():
if tx.nonce == 42 and len(confirmations) >= safe.confirmations_required:
receipt = safe.submit_safe_tx(tx, submitter=submitter)
print(f"Transaction executed: {receipt.txn_hash}")
Via CLI
# Execute a transaction with a specific submitter
ape safe pending execute 42 --submitter my-account
# Approve and also execute if the threshold is met
ape safe pending approve 42 --execute my-account
Rejecting Transactions
Rejecting a transaction replaces it with a 0 ETH self-send transaction at the same nonce, effectively cancelling it. This is useful if you have mistakenly placed a bad transaction in your queue that you need to avoid executing.
Via Python API
# Create a rejection transaction for nonce 42
safe.transfer(safe, 0, nonce=42)
Via CLI
# Reject a transaction
ape safe pending reject 42
# Reject and execute immediately
ape safe pending reject 42 --execute my-account
Transaction Nonce Management
Safe uses nonces to ensure transactions are executed in a specific order:
Only one transaction can be executed per nonce
Transactions must be executed in nonce order
A rejected transaction still consumes its nonce
To check the next available nonce:
# Next on-chain nonce
print(f"Next nonce: {safe.next_nonce}")
# Next available nonce (including pending)
print(f"New nonce: {safe.new_nonce}")
Note
By default, all transactions use the next “pending” nonce e.g. .new_nonce
when no nonce=
is specified.
Transaction Signatures
Safe uses EIP-712 signatures for off-chain approval:
from ape_safe.utils import get_safe_tx_hash
# Get the EIP-712 hash for a transaction
tx_hash = get_safe_tx_hash(tx)
# Collect signatures from local signers
signatures = safe.add_signatures(tx)