Foundry cast safe 命令完全指南:用 CLI 全流程部署与运营 Safe 多签账户
【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry
cast safe是 Foundry 的 cast 命令套件中面向 Safe(原 Gnosis Safe)多签账户的完整操作子命令,覆盖 Safe 账户部署、交易服务委托管理、多签提案、签名、签名无关的模拟(signature-independent simulation)与链上执行全流程。本文以仓库 .changelog/cast-safe-transactions.md 记录的变更内容为骨架,结合 crates/cast/src/cmd/safe 下的全部源码实现,系统讲解每个子命令的用法、参数含义与底层原理,读完即可用 cast 命令行独立完成一个 Safe 多签账户从创建到执行交易的全生命周期管理。
一、cast safe 子命令全景
cast safe是 cast 下的一级命令组,在 crates/cast/src/cmd/safe/mod.rs 中定义为SafeSubcommand枚举,共包含 8 个子命令:
| 子命令 | 作用 | 实现文件 |
|---|---|---|
cast safe create | 部署一个 Safe 账户(多签钱包) | deploy.rs |
cast safe add-delegate | 为 Safe 所有者注册交易服务委托 | delegates.rs |
cast safe list-delegates | 列出 Safe 已注册的委托 | delegates.rs |
cast safe remove-delegate | 移除 Safe 所有者的委托 | delegates.rs |
cast safe propose | 创建、签名并提交 Safe 交易提案 | proposal.rs |
cast safe sign | 为已提案的 Safe 交易签名并提交确认 | proposal.rs |
cast safe simulate | 无需所有者签名即可模拟 Safe 交易 | simulate.rs |
cast safe execute | 链上执行已确认的 Safe 交易 | execute.rs |
此外 mod.rs 还定义了SafeOperation枚举(Call = 0/DelegateCall = 1),以及构建 RPC provider 的公共辅助函数rpc_provider。
二、cast safe create:部署 Safe 账户
cast safe create用于部署一个 Safe 多签账户,支持指定多个所有者与签名阈值。官方示例(见 mod.rs 中的文档注释):
# 单所有者、阈值 1,使用 ledger 签名 cast safe create $OWNER --threshold 1 --rpc-url $RPC --ledger # 三个所有者、阈值 2,使用 named account 签名 cast safe create $OWNER_1 $OWNER_2 $OWNER_3 --threshold 2 --rpc-url $RPC --account deployer2.1 核心参数说明
根据 deploy.rs 中CreateArgs的定义,完整参数如下:
| 参数 | 必填 | 默认值 | 说明 |
|---|---|---|---|
owners | 是(1 个以上) | — | 拥有 Safe 的地址列表,位置参数 |
--threshold | 否 | 全部所有者数量 | 所需签名数;必须大于 0 且不超过所有者数量 |
--salt-nonce | 否 | 链特定的默认 nonce | CREATE2 盐值 nonce,默认与 Safe Protocol Kit 的链特定 nonce 一致 |
--singleton | 否 | 规范的 v1.4.1 部署地址 | Safe singleton 合约地址,与--l1互斥 |
--l1 | 否 | false | 使用 L1 Safe singleton 而非 SafeL2 |
--factory | 否 | SAFE_PROXY_FACTORY_V1_4_1 | SafeProxyFactory 地址 |
--fallback-handler | 否 | COMPATIBILITY_FALLBACK_HANDLER_V1_4_1 | CompatibilityFallbackHandler 地址;传零地址可禁用它 |
--confirmations | 否 | 1 | 等待的确认数 |
--timeout | 否 | 配置值 | 部署确认超时(秒),可通过环境变量ETH_TIMEOUT设置 |
--poll-interval | 否 | 配置值 | 轮询部署回执的间隔(秒),可通过环境变量ETH_POLL_INTERVAL设置 |
2.2 底层部署流程
从源码看,部署流程分为四个步骤(deploy.rs):
- 校验所有者配置:调用
validate_owners,依次检查所有者列表非空、阈值大于 0 且不超过所有者数量、所有者不能为零地址或哨兵地址(SENTINEL_OWNER)、不能有重复所有者。 - 选择 singleton 并校验合约存在:若未显式指定
--singleton,则在主网(chain_id == 1)或指定--l1时使用 L1 Safe singleton,否则使用 SafeL2。随后通过ensure_contract检查 singleton、factory、fallback handler 地址上确实存在代码,否则报错提示通过对应 flag 指定该网络的部署地址。 - 编码并发送部署交易:构造
ISafe::setup的 initializer calldata(owners、threshold、fallbackHandler 等),再通过ISafeProxyFactory::createProxyWithNonce编码部署调用并发送。 - 解析 ProxyCreation 事件输出地址:从回执日志中过滤出 factory 发出的
ProxyCreation事件,打印新 Safe 地址。若回执未发出该事件则报错。
一个值得注意的细节是默认 salt nonce 的计算:default_salt_nonce(chain_id)使用keccak256("{PREDETERMINED_SALT_NONCE}{chain_id}")计算。该逻辑有专门的单元测试matches_protocol_kit_default_salt_nonce验证其与 Safe Protocol Kit 的一致性(测试断言 chain_id 1 的默认 nonce 为0x69b3...a1f6)。
2.3 交易发送与 Tempo 网络支持
部署交易的发送统一由 transaction.rs 中的SafeSendOpts完成。该结构体打包了 RPC、钱包与交易参数,其send方法会根据是否 Tempo 网络选择TempoNetwork或Ethereum网络发送。cast safe create与cast safe execute共用这套发送逻辑,并做了以下限制(当前版本):
- 外层交易 value 必须为零;
- 不支持 blob 交易与 EIP-7702 授权;
- Tempo sponsorship(赞助)、Tempo Accounts 会话尚不支持(会明确报错提示)。
这正对应变更日志中提到的"Tempo fee-token transaction options":在 Tempo 网络上发送时支持其特有的费用代币交易选项。
三、委托管理:add-delegate / list-delegates / remove-delegate
Safe Transaction Service 支持"委托(delegate)"机制:所有者可以委托其他地址代为提交交易提案,而无需转移所有权。三个子命令分别对应注册、查询、移除委托。
3.1 添加委托
cast safe add-delegate $SAFE $DELEGATE --label "my-bot" --rpc-url $RPC --account signer参数包括safe(Safe 地址)、delegate(允许提案的地址)和必填的--label(人类可读标签,源码中校验其非空)。实现上(delegates.rs)通过sign_delegate对 EIP-712 类型化数据签名后,向 Transaction Service 的v2/delegates/端点发送 POST 请求,请求体包含 delegate、label、safe、delegator 与签名。
3.2 列出委托
cast safe list-delegates $SAFE --rpc-url $RPC实现上通过v2/delegates/?safe=...端点 GET 查询,并自动处理分页:循环跟随next字段,且会校验下一页 URL 必须仍指向同一 Transaction Service 端点(防止分页跳转到外部地址),最终以 JSON 数组打印全部委托。
3.3 移除委托
cast safe remove-delegate $SAFE $DELEGATE --rpc-url $RPC --account signer对应v2/delegates/{delegate}/的 DELETE 请求,同样需要签名授权。
3.4 委托签名原理
委托相关的签名逻辑在 signing.rs 中:签名内容为 EIP-712 类型化数据,primaryType为Delegate,字段为delegateAddress与totp,域名为Safe Transaction Service(version 1.0,chainId 为当前链)。totp是时间窗口 TOTP(now / 3600,即按小时取整),起到防重放的时间约束作用。Trezor 钱包走safe_eth_sign路径(对 EIP-712 哈希做 eth_sign),其他钱包直接sign_dynamic_typed_data。
四、提案与签名:propose / sign
4.1 创建并提交提案
cast safe propose会在签名后向 Transaction Service 提交一条待确认的多签交易:
cast safe propose $SAFE $TO "transfer(address,uint256)" $RECIPIENT $AMOUNT --rpc-url $RPC --account owner1参数说明(proposal.rs 中ProposeArgs):
| 参数 | 说明 |
|---|---|
safe/to | Safe 地址与交易目标地址(位置参数) |
sig/args | 函数签名与参数;不提供时 calldata 为空 |
--data | 原始 calldata,与sig/args互斥(clap 强制冲突) |
--value | Safe 转出的原生代币数量,默认 0,支持以太单位解析 |
--operation | call(默认)或delegatecall |
--safe-tx-gas/--base-gas/--gas-price | Safe 交易 gas 相关字段,默认 0 |
--gas-token/--refund-receiver | gas 偿还代币与收款地址,默认零地址 |
--nonce | 交易 nonce,默认取 Transaction Service 下一个排队 nonce |
--origin | Safe 客户端展示的可选来源信息 |
nonce 的默认获取逻辑值得说明:先从链上读取 Safe 的当前 nonce,再调用next_nonce查询 Transaction Service 的v1/safes/{safe}/multisig-transactions/?executed=false&ordering=-nonce&limit=1,取两者较大者(服务端 nonce 加一)。这保证了排队中的交易不会与链上 nonce 冲突。
4.2 为提案签名确认
cast safe sign $SAFE $SAFE_TX_HASH --rpc-url $RPC --account owner2cast safe sign先从 Transaction Service(v1/multisig-transactions/{safe_tx_hash}/)拉取交易详情,然后做哈希校验:verify_hash会确认服务端返回的 Safe 地址与命令行一致、operation 合法,并通过链上getTransactionHash重算交易哈希,确保与服务端返回的safeTxHash一致,防止服务端数据被篡改。校验通过后打印交易摘要(Safe、To、Value、Operation、Nonce、Data 等字段),对哈希签名,再 POST 到v1/multisig-transactions/{hash}/confirmations/提交确认。
五、签名无关的模拟:cast safe simulate
cast safe simulate是变更日志中强调的"signature-independent simulation"——不需要任何所有者签名即可验证一条 Safe 交易能否成功执行:
cast safe simulate $SAFE $SAFE_TX_HASH --from $EXECUTOR --rpc-url $RPC5.1 原理与限制
模拟基于 Safe 的simulateAndRevert与SimulateTxAccessor合约实现(simulate.rs):
- 从 Transaction Service 拉取交易并校验哈希;
- 拒绝报销型交易:若
gasPrice > 0则报错,因为SimulateTxAccessor不强制执行safeTxGas(源码注释明确说明该限制); - 检查 SimulateTxAccessor 合约存在(默认
SIMULATE_TX_ACCESSOR_V1_4_1,可用--accessor覆盖); - 构造
simulateAndRevert(targetContract, calldataPayload)调用,其中 calldata 是 accessor 的simulate(to, value, data, operation); - 期望该调用必然 revert,从 revert data 中解码
(gasUsed, success, returnData)三元组; - 若内部模拟失败(success == false),报错并输出消耗的 gas 与返回数据;成功则以 JSON 输出
safeTxHash、success、gasUsed、returnData。
--from参数指定模拟的tx.origin(可通过环境变量ETH_FROM设置)。该模拟不校验Safe nonce、所有者签名、阈值或 guard 钩子,只关心内部 CALL/DELEGATECALL 在 Safe 上下文中能否成功,适合提案执行前的快速预检。
六、链上执行:cast safe execute
cast safe execute $SAFE $SAFE_TX_HASH --rpc-url $RPC --account executorcast safe execute将已收集足够确认的交易真正上链(execute.rs),执行前做如下检查:
- 交易未被执行过(服务端标记
is_executed为 false 且无transaction_hash); - 重算哈希与服务端一致;
- 交易 nonce 必须等于链上当前 nonce,否则拒绝执行(防止跳过排队交易)。
随后从服务端拉取全部确认签名,调用packed_signatures组装成 Safe 要求的签名打包格式,构造execTransaction调用并发送。签名打包逻辑在 service.rs 中,支持三类签名:
- EOA 签名(v >= 27):固定 65 字节,按所有者地址升序排列静态段;
- 合约签名(v = 0):携带动态数据,需校验长度字段与偏移量(偏移必须等于 65);
- P-256 签名(v = 2):固定 65 + 128 字节,同样校验偏移;
- 明确拒绝
approved-hash(v = 1)签名类型。
执行后从回执日志中查找 Safe 发出的ExecutionSuccess/ExecutionFailure事件(取最后一个匹配safeTxHash的事件),若内部交易失败则报错。该逻辑同样有单元测试uses_last_matching_safe_execution_event覆盖。
七、Safe Transaction Service 自动发现
所有需要与 Safe Transaction Service 交互的子命令都依赖 service.rs 中的SafeServiceOpts:
| 参数 | 环境变量 | 说明 |
|---|---|---|
--service-url | SAFE_TRANSACTION_SERVICE_URL | Transaction Service URL;省略时根据 RPC 链 ID 推断;/api后缀可写可不写(源码会自动补齐) |
--api-key | SAFE_API_KEY | Transaction Service API key,通过 Bearer 认证发送 |
当省略--service-url时,default_service_url会基于 chain_id 映射出官方服务地址(https://api.safe.global/tx-service/{short_name}),内置映射覆盖了以太坊(eth)、Arbitrum(arb1)、Base(base)、Optimism(oeth)、Polygon(pol)、BNB(bnb)、Gnosis(gno)、zkSync(zksync)、Linea(linea)、Scroll(scr)、Mantle(mantle)、Avalanche(avax)、Celo(celo)、Sepolia(sep)、**Tempo(chain 4217,short name "tempo")与 Tempo-Moderato(chain 42431)**等 30 余条链。未内置的链 ID 会提示"no known Safe Transaction Service for chain ID X; pass --service-url"。
值得注意的是list-delegates在显式提供--service-url时无需 RPC 连接(chain_id 传 0),因为服务端点已确定;其余命令仍需 RPC。该模块对 URL 规范化、非空/数字字符串反序列化、分页防循环等均有单元测试覆盖(见 service.rs)。
八、从一个提案到执行的完整工作流
综合以上各节,一个典型的多签交易生命周期如下:
# 1. 部署 Safe(多所有者、阈值 2) cast safe create $OWNER_1 $OWNER_2 $OWNER_3 --threshold 2 --rpc-url $RPC --account deployer # 2. 可选:注册委托,便于自动化工具代为提案 cast safe add-delegate $SAFE $BOT_ADDR --label "ops-bot" --rpc-url $RPC --account $OWNER_1 # 3. 所有者 1 创建并签名提案 cast safe propose $SAFE $TO "transfer(address,uint256)" $RECIPIENT $AMOUNT --rpc-url $RPC --account $OWNER_1 # 4. 执行前无签名模拟预检(任意执行者视角) cast safe simulate $SAFE $SAFE_TX_HASH --from $EXECUTOR --rpc-url $RPC # 5. 所有者 2 确认签名 cast safe sign $SAFE $SAFE_TX_HASH --rpc-url $RPC --account $OWNER_2 # 6. 达到阈值后链上执行 cast safe execute $SAFE $SAFE_TX_HASH --rpc-url $RPC --account $OWNER_1整个流程在 crates/cast/src/cmd/safe 目录下闭环实现:部署与执行走 transaction.rs 的统一发送通道,提案/签名/模拟/执行共享 service.rs 的 Transaction Service 客户端与哈希校验逻辑,所有链上接口定义集中在 contracts.rs。如果需要在自动化脚本中集成 Safe 多签管理,cast safe是除 Safe CLI / Web 界面之外的轻量命令行替代方案,且与 Foundry 现有的--account、--ledger等钱包体系无缝衔接。
【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考