Repository navigation
Releases
v0.13.0
Smaller installs
The library and the CLI are still two crates, and each one now compiles only what it uses:
cargo install safe-rs-cli --locked # the `safe` CLI, with Ledger support
cargo install safe-rs-cli --locked --no-default-features # without Ledger (no libusb)[dependencies]
safe-rs = "0.13" # with fork simulation
safe-rs = { version = "0.13", default-features = false } # without revm
safe-rs = { version = "0.13", features = ["hd-wallet"] } # + keystores and RotatingSafe| Feature | Default | Adds |
|---|---|---|
simulation |
yes | Fork simulation with revm: simulate() on the builders, ForkSession, simulate_exec* |
hd-wallet |
no | Encrypted mnemonic keystores, HD keys and RotatingSafe (implies simulation) |
ledger |
no | Owner keys on a Ledger (implies hd-wallet) |
Neither crate enables alloy's full or tokio's full any more, which dropped the WebSocket and IPC transports, pubsub and the anvil/txpool/debug provider APIs. A library build without default features compiles 242 crates instead of 331, without revm, c-kzg, blst or secp256k1-sys. Without simulation, the builders still execute(), with gas from eth_estimateGas, so a reverting call still fails before anything is broadcast.
Multi-owner Safes
SignedSafeTxholds several owners' signatures, sorted. New functions pack, unpack and recover them, and buildapproveHashcalldata.safe send,safe send --rotateandsafe rotateread the Safe's owners, threshold and on-chainapprovedHashes. They then pick one of:- collecting signatures offchain in a bundle file (
--bundle,--sign-only,--merge-bundle) - approving the hash on chain (
--approve-hash) - executing
- collecting signatures offchain in a bundle file (
- New library functions:
read_approved_owners,simulate_exec_with_threshold,simulate_approve_hashandOuterTxParams::approve_hash.
One-time owner keys
Every transaction can swap the Safe's owner for a fresh key, so the key that signed is no longer an owner once its signature is public.
- CLI:
safe send --rotate --hd-wallet <name>appends the swap and then moves the old key's ETH to the new one.safe rotatedoes the swap on its own, finds the Safe when you don't name one, and moves an existing Safe onto the wallet.safe create --hd-walletdeploys from a funding key, so the owner key has never signed anything.safe wallet new|import|list|address|status|sync|sweepmanage the encrypted phrase and the record of used keys.
- Library: the
hd-walletfeature addsRotatingSafe,HdWallet, encrypted keystores, and rebuilding the rotation state from on-chain owner events.
Ledger
- CLI:
--ledgertakes the owner keys from a Ledger, wherever--hd-walletis accepted. It uses the same derivation path, so a Ledger seeded with a phrase shares that phrase's state. It's a default feature of the CLI. - Library: the
ledgerfeature addsLedgerKeys. Safe transactions are now signed as EIP-712 typed data, which the device can display. For a local key the signatures are unchanged.
Safe v1.5.0
safe createdeploys v1.5.0 Safes by default. Pass--safe-version 1.4.1for the old version.- Owner rotation works on v1.5.0.
- Batches go through the MultiSend contracts of the Safe's own version (
ChainAddresses::for_version,detect_addresses).
Breaking changes
- safe-rs no longer turns on alloy's
fullfeature. If your code relied on it for WebSocket, IPC or the extra provider APIs, enable those on your ownalloydependency. CallBuilder::simulate,simulation_resultandsimulation_successexist only with thesimulationfeature, which is on by default.safe infotakes the Safe address as a positional argument, defaults to the signer's own Safe, and no longer readsSAFE_ADDRESS.SignedSafeTxholds a map of signatures instead of a single signature.
Fixes
execute()estimates gas itself, so a send that fails estimation no longer leaves the provider a nonce ahead with the next transaction stuck.- A batch whose MultiSend target has no code is refused before signing. That applies to
SafeBuilderand toRotatingSafe. Before, it looked successful while none of the calls ran. - The fork backend runs on the caller's runtime, so
simulate()followed by another call on the same provider can no longer hang. WalletBuilder::connect_with_configkeeps the configured contract addresses.- Every sweep still owed from an earlier key stays on record across later rotations.
- Deployment checks no longer fail on a stale read from another RPC node.
Full changelog: v0.12.0...v0.13.0
v0.12.0
The CLI is on crates.io
The safe CLI is now published as its own crate, safe-rs-cli:
cargo install safe-rs-cli --lockedIt installs the same safe binary as before. The CLI used to live in an unpublished package, and the README's cargo install safe-rs failed because safe-rs is library-only.
Changes
- The
cli/package is renamed fromsafetosafe-rs-cli, which already has crates.io metadata. The binary is stillsafe. - The CLI's version now matches the library's: both crates are at
0.12.0. - The README install instructions are fixed, for both the CLI and the library.
The safe-rs library API is the same as in v0.11.0.
Full changelog: v0.11.0...v0.12.0
v0.11.0
Breaking changes
SafeBuilder::execute()now runs exactly the Safe transaction thatsimulate()ran. Before, the simulation usedsafeTxGas = 0, butexecute()signed a transaction withsafeTxGas = gas_used * 1.1. That meant a failing inner call could be mined asExecutionFailureand still consume the nonce.safeTxGasis now the value you set withwith_safe_tx_gas()/with_gas_limit(), or0.execute()fails withNonceMismatchif the Safe nonce changed aftersimulate(), and withInvalidConfigif calls were added after it. (77dd65b)execute()requires theExecutionSuccessevent.call_only()rejectsDelegateCallfor single calls.with_nonce()rejects a stale nonce.- Upgraded to alloy 2.5, revm 41, foundry-fork-db 0.27 and revm-inspectors 0.41.
is_saferead errors are nowIncompleteReadinstead ofFetch.
Features
- Frozen Safe transactions (
envelope):PreparedSafeTx/SignedSafeTxfix the nonce and use a fail-closed policy (safeTxGas = 0).SafeBuilder::prepare()returns a frozen transaction, so the same transaction can be reviewed, simulated, signed and broadcast. - Outer transactions (
outer): EIP-1559 outer transactions are signed locally, so the raw bytes and hash are available before broadcast. - Submission (
submit): raw broadcast that reports ambiguous outcomes. Receipt waits that time out still return the hash.ExecutionSuccess/ExecutionFailureare decoded by Safe address and Safe transaction hash. - Safe state inspection (
inspect): reads Safe state pinned to a block: owners, threshold, nonce, modules (paginated, with a completeness flag), guard, fallback handler and singleton. - Fork sessions (
simulation::session):ForkSessionis a stateful revm session pinned by block hash. It takes an explicit next-block environment, EVM spec and gas cap. TxCheckshas abalanceflag and aMEASUREpreset, which measure gas at the transaction gas cap without requiring the sender to afford the cap.- Halted frames (for example
OutOfGas) now appear by name in revert reasons.SafeExecutionOutcome::labelgives stable machine-readable names. - Helpers for the prepare → sign → simulate → broadcast flow:
OuterTxParams::exec_transaction,SimTx::from_outer,ForkSession::fork_next_block,ParentHeader::block_id,WalletBuilder::deploy_detailed,create2::fetch_proxy_creation_code, andWalletConfig::owners()/validate().safe_outcome_for_receipt,receipt_logsandis_gs013_revertare exported.
CLI
sendfreezes the Safe transaction and simulates the exact outerexecTransactionit will sign against the next block with strict checks. It then signs locally, broadcasts, and waits forExecutionSuccess. New flags:--nonce,--gas-limit,--max-fee-per-gas,--max-priority-fee-per-gas,--raw-out,--no-submit,--timeout,--trace,--block,--spec,--multi-send,--multi-send-call-only.inforeads Safe state pinned to a block (version, singleton, modules, guard, fallback handler) with--block.callruns in a fork session with--block,--specand--trace.createaccepts--singletonand--factoryoverrides.
Fixes
- EOA batches are simulated statefully, so call N sees the effects of the calls before it. For example,
approvefollowed bytransferFromnow simulates correctly. - A reverted simulation in the CLI used to print
Success: true. It now prints the inner revert reason and exits non-zero. - Bundle entries with
operation: 1are sent as DELEGATECALLs. They used to be sent as plain CALLs. - Call traces record logs, so traced simulations show emitted events.
send --skip-simulationexplains a GS013 revert when gas estimation fails.
Full changelog: v0.10.1...v0.11.0
v0.9.0
v0.6.1
v0.5.0
v0.3.0
What's Changed
Breaking Changes
- Simplified builder API by removing type-state pattern.
MulticallBuilderno longer usesNotSimulated/Simulatedtype states. The API is now a simple fluent builder with optional simulation.
New Features
- Added
state_difffield toSimulationResultshowing pre/post state for all touched accounts during simulation. Uses alloy'sDiffModetype from geth pre_state tracing.
API Changes
MulticallBuilder::execute_without_simulation()removed - useexecute()directly- Simulation result accessed via
builder.simulation_result()instead of methods on the builder - Re-exported
DiffModeandAccountStatetypes for working with state diffs