safe-rs
A Rust library and CLI for interacting with Safe smart accounts. Built for single-owner (1/1) Safes with a focus on simplicity, safety, and developer experience.
Philosophy
Opinionated by design. safe-rs optimizes for an opinionated usecase: single-owner Safes where you want to execute transactions with confidence. Rather than supporting every Safe configuration, it provides a streamlined experience with compile-time guarantees and optional forking simulation.
Minimal surface area. One way to do things, done well. No configuration sprawl, no optional safety features that can be accidentally disabled.
Features
- Fluent builder pattern — Simple API with optional simulation before execution
- Fork simulation — Test transactions against live blockchain state using revm
- Automatic multicall batching — Single calls execute directly; multiple calls batch via MultiSend
- Type-safe contract calls — First-class support for alloy's
sol!macro - Multi-chain support — Pre-configured for Ethereum, Arbitrum, Optimism, Base, Polygon, and more
- Deterministic deployment — Deploy new Safes with predictable addresses via CREATE2
- Gas estimation — Automatic safeTxGas calculation with safety buffer
- Revert decoding — Human-readable error messages from failed simulations
- EOA fallback mode — Same builder API for executing as individual transactions from an EOA
Installation
CLI
Library
[]
= "0.1"
Quick Start
CLI
Execute an ERC20 transfer through your Safe:
Simulate without executing:
Library
use ;
use ;
let safe = connect.await?;
safe.verify_single_owner.await?;
let result = safe
.multicall
.add_typed
.simulate.await?
.execute.await?;
println!;
CLI Reference
safe send
Execute transactions through a Safe. Always simulates first, then prompts for confirmation.
Single call:
Multiple calls:
))
From bundle file:
Options:
| Flag | Description |
|---|---|
--simulate-only |
Simulate without executing |
--call-only |
Use MultiSendCallOnly (no delegatecall) |
--no-confirm |
Skip confirmation prompt |
--json |
Output as JSON |
-i, --interactive |
Prompt for private key |
safe call
Simulate a transaction without executing. Useful for testing and gas estimation.
safe info
Query Safe state.
Output:
Safe: 0xYourSafe
Nonce: 42
Threshold: 1
Owners:
- 0xOwner1
safe create
Deploy a new Safe with deterministic addressing.
Options:
| Flag | Description |
|---|---|
--owner <address> |
Additional owner (repeatable) |
--threshold <n> |
Required signatures (default: 1) |
--salt-nonce <n> |
Salt for deterministic address |
--compute-only |
Show address without deploying |
Wallet Options
All commands that require signing support:
| Flag | Description |
|---|---|
--private-key <key> |
Private key (hex) |
-i, --interactive |
Prompt for private key securely |
PRIVATE_KEY env var |
Environment variable |
Library API
Connecting to a Safe
use Safe;
// Auto-detect chain configuration
let safe = connect.await?;
// Verify single-owner requirement
safe.verify_single_owner.await?;
Building Transactions
The MulticallBuilder provides a fluent API for constructing transactions:
// Raw call
let builder = safe.multicall
.add;
// Typed call (recommended)
let builder = safe.multicall
.add_typed;
// Multiple calls batch automatically
let builder = safe.multicall
.add_typed
.add_typed
.call_only; // Use MultiSendCallOnly for safety
Simulation
Simulation runs the transaction against a fork of the current blockchain state:
let builder = builder.simulate.await?;
// Access simulation result
if let Some = builder.simulation_result
Execution
After simulation, you can execute:
let result = simulated.execute.await?;
println!;
Simulation-Only Mode
For read-only operations or testing, you don't need to be an owner:
use PrivateKeySigner;
// Use any signer for simulation
let dummy = random;
let safe = new;
let builder = safe.multicall
.add_typed
.simulate.await?;
// Inspect results without executing
if let Some = builder.simulation_result
Querying Safe State
let nonce = safe.nonce.await?;
let threshold = safe.threshold.await?;
let owners = safe.owners.await?;
EOA Fallback Mode
The Eoa client provides the same builder API as Safe multicall, but executes each call as a separate transaction. This is useful when you don't have a Safe but want the same batching workflow:
use Eoa;
let eoa = connect.await?;
let result = eoa.batch
.add_typed
.add_typed
.simulate.await?
.execute.await?;
println!;
for tx in &result.results
Key differences from Safe mode:
| Aspect | Safe Mode | EOA Mode |
|---|---|---|
| Execution | Single atomic tx via MultiSend | Multiple independent txs |
| Failure | All-or-nothing | Can partially succeed |
| Result | Single TxHash |
Vec<TxHash> |
| DelegateCall | Supported | Not supported |
Partial failure handling:
By default, EOA batch execution stops on the first failure. Use continue_on_failure() to execute all transactions regardless:
let result = eoa.batch
.add_typed
.add_typed
.continue_on_failure // Don't stop on first failure
.simulate.await?
.execute.await?;
if let Some = result.first_failure
Bundle Format
The --bundle option accepts JSON files compatible with the Safe Transaction Bundler format:
Fields:
to— Target address (required)value— Wei to send (optional, default: "0")data— Calldata hex (optional, default: "0x")operation— 0 for Call, 1 for DelegateCall (optional, default: 0)
Supported Chains
safe-rs includes pre-configured addresses for Safe v1.4.1 contracts:
| Chain | Chain ID |
|---|---|
| Ethereum | 1 |
| Sepolia | 11155111 |
| Arbitrum | 42161 |
| Optimism | 10 |
| Base | 8453 |
| Polygon | 137 |
| BSC | 56 |
| Avalanche | 43114 |
| Gnosis | 100 |
All chains use the same contract addresses (deployed via CREATE2):
| Contract | Address |
|---|---|
| Safe Singleton | 0x41675C099F32341bf84BFc5382aF534df5C7461a |
| MultiSend | 0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526 |
| MultiSendCallOnly | 0x9641d764fc13c8B624c04430C7356C1C7C8102e2 |
| Proxy Factory | 0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67 |
| Fallback Handler | 0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99 |
Environment Variables
| Variable | Description |
|---|---|
ETH_RPC_URL |
RPC endpoint URL |
SAFE_ADDRESS |
Default Safe address |
PRIVATE_KEY |
Signer private key |
Examples
See the examples/ directory:
simple_transfer.rs— Single ERC20 transfermulticall_erc20.rs— Batch multiple operationssimulation_only.rs— Simulation without execution
Run examples:
Why safe-rs?
vs Safe Transaction Service API: safe-rs executes transactions directly on-chain without relying on Safe's infrastructure. No API keys, no rate limits, no external dependencies.
vs ethers/alloy directly: safe-rs handles the complexity of Safe transaction encoding, EIP-712 signing, gas estimation, and multicall batching. You focus on what you want to do, not how Safe works internally.
vs multi-owner Safes: If you need multiple signers, use the Safe web interface or Transaction Service. safe-rs is intentionally limited to 1/1 Safes for simplicity and reliability.
License
MIT