Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Generate Privy wallet security policy rules from natural language and submit them for on-chain user approval.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
utils/SAFE_WALLET_MANAGEMENT.zh.md
1---2name: safe-wallet-management3description: 如何使用 Safe 交易 API 创建和使用 Safe 多签4---56本文件包含如何创建和使用 Safe 多签的说明。78## 快速导航910| 任务 | 章节 |11|------|------|12| 检查是否已存在多签 | [检查 agent 是否已创建多签](#检查-agent-是否已创建多签) |13| 获取所有者的 Safe 地址 | [获取 Safe 地址](#获取-safe-地址) |14| 创建新的 Safe 多签 | [创建多签](#创建多签) |15| 通过 Safe 部署智能合约 | [使用 Safe 多签部署智能合约](#使用-safe-多签部署智能合约) |16| 向 Safe 提交交易 | [向 Safe 交易服务提交交易](#3-向-safe-交易服务提交交易) |17| 监控并获取合约地址 | [监控并获取合约地址](#4-监控并获取合约地址) |1819## 网络参考2021用于各网络的 RPC URL,请确保你知道需要在哪个网络上构建,如果不确定请询问用户。2223### RPC URL2425| 网络 | Chain ID | RPC |26|------|----------|-----|27| Monad 测试网 | 10143 | https://testnet-rpc.monad.xyz |28| Monad 主网 | 143 | https://rpc.monad.xyz |2930各网络的 Safe 交易基础 API URL,请确保你知道需要在哪个网络上构建。3132### Safe 交易服务(基础 URL)3334| 网络 | URL |35| --- | --- |36| Monad 测试网 | https://api.safe.global/tx-service/monad-testnet/api/v1 |37| Monad 主网 | https://api.safe.global/tx-service/monad/api/v1 |3839## 获取 Safe 地址4041要查找特定钱包地址拥有的 Safe 多签地址,请查询 Safe 交易服务 API。4243### 从本地存储4445检查 agent 是否之前存储了多签详情:4647```bash48cat ~/.monskills/multisig.json49```5051该文件包含 `testnet` 和 `mainnet` 属性,分别存储每个网络的 Safe 地址和所有者。5253### 从 Safe 交易服务 API5455查询 API 以获取指定地址拥有的所有 Safe:5657**Monad 测试网:**5859```bash60curl -s "https://api.safe.global/tx-service/monad-testnet/api/v1/owners/$OWNER_ADDRESS/safes/" | jq61```6263**Monad 主网:**6465```bash66curl -s "https://api.safe.global/tx-service/monad/api/v1/owners/$OWNER_ADDRESS/safes/" | jq67```6869响应会返回一个 `safes` 数组,包含指定地址作为所有者的所有 Safe 地址。7071### 在链上验证 Safe7273要确认 Safe 存在并检查其所有者/阈值:7475```bash76# 获取所有者77cast call $SAFE_ADDRESS "getOwners()(address[])" --rpc-url [rpc-url-respective-to-network]7879# 获取阈值80cast call $SAFE_ADDRESS "getThreshold()(uint256)" --rpc-url [rpc-url-respective-to-network]81```8283## 检查 agent 是否已创建多签8485如果 agent 已创建多签,则 ~/.monskills/ 文件夹中应该存在 multisig.json 文件。根据网络(Monad 主网或测试网),该文件中可能包含相应信息。8687如果未找到多签详情,则创建一个多签。8889## 创建多签9091**正确流程:**92931. 确保 agent 有钱包(检查 `~/.monskills/keystore/` 中是否存在加密密钥存储)。942. 确保已安装 Foundry 工具包(foundryup --version)953. 向用户索要 2 个钱包地址作为多签的签名者。964. 使用 DeploySafeCREATE2.sol 部署 Safe(检查同一文件夹中的脚本)97- DeploySafeCREATE2.sol 可同时用于 Monad 主网和 Monad 测试网。985. Safe 创建后,务必将多签地址及所有者存储在 ~/.monskills/ 文件夹的 multisig.json 文件中,绝对确保 json 文件中有 "testnet" 和 "mainnet" 属性,并将多签详情存储到对应的网络属性中。99100### 在 Monad 测试网上创建 Safe 的命令(仅限 Monad 测试网)101102```bash103# 从水龙头为 agent 钱包充值,因为是测试网,可以从水龙头领取资金。104FAUCET_RESPONSE=$(curl -s -X POST https://agents.devnads.com/v1/faucet \105-H "Content-Type: application/json" \106-d "{\"chainId\": 10143, \"address\": \"$AGENT_WALLET_ADDRESS\"}")107108# 等待资金到账109while [ "$(cast balance $AGENT_WALLET_ADDRESS --rpc-url https://testnet-rpc.monad.xyz)" = "0" ]; do110sleep 2111done112113# 使用 CREATE2 部署 Safe(标准 SafeProxyFactory)114# 从加密密钥存储中即时解密私钥。115# `cast wallet decrypt-keystore` 会输出 "<uuid>'s private key is: 0x...",116# 用 awk '{print $NF}' 只保留最后的十六进制私钥。117OWNER_1=$OWNER_1 OWNER_2=$OWNER_2 OWNER_3=$CLAUDE_ADDRESS \118forge script DeploySafeCREATE2.sol:DeploySafeCREATE2 \119--private-key $(cast wallet decrypt-keystore --keystore-dir ~/.monskills/keystore $KEYSTORE_FILENAME --unsafe-password "" | awk '{print $NF}') \120--rpc-url https://testnet-rpc.monad.xyz \121--broadcast122123echo "✅ Safe deployed: $SAFE_ADDRESS"124echo "🌐 https://app.safe.global/home?safe=monad-testnet:$SAFE_ADDRESS"125```126127### 在 Monad 主网上创建 Safe 的命令(仅限 Monad 主网)128129```bash130# 检查钱包在 Monad 主网上是否有余额,如果没有余额,请用户在 Monad 主网上为地址充值。131cast balance $AGENT_WALLET_ADDRESS --rpc-url https://rpc.monad.xyz132133# 使用 CREATE2 部署 Safe(标准 SafeProxyFactory)134# 从加密密钥存储中即时解密私钥(awk 去掉 "<uuid>'s private key is:" 前缀)。135OWNER_1=$OWNER_1 OWNER_2=$OWNER_2 OWNER_3=$CLAUDE_ADDRESS \136forge script DeploySafeCREATE2.sol:DeploySafeCREATE2 \137--private-key $(cast wallet decrypt-keystore --keystore-dir ~/.monskills/keystore $KEYSTORE_FILENAME --unsafe-password "" | awk '{print $NF}') \138--rpc-url https://rpc.monad.xyz \139--broadcast140141echo "✅ Safe deployed: $SAFE_ADDRESS"142echo "🌐 https://app.safe.global/home?safe=monad:$SAFE_ADDRESS"143```144145Safe 创建后,将多签地址及所有者存储在 ~/.monskills/ 文件夹的 multisig.json 文件中,绝对确保 multisig.json 文件中有 "testnet" 和 "mainnet" 属性,并将多签详情存储到对应的网络属性中。146147## 通过 Safe 多签提交交易148149必须已经部署了 Safe 多签,才能部署智能合约或执行链上操作。150151**重要提示**:此工作流程使用 Safe 多签处理所有交易 — 部署、合约调用、代币转账、提款等。不允许使用 --private-key 或 --broadcast 进行直接交易。152153**关键:始终使用 `propose.mjs`** — 切勿编写新的/自定义脚本来提交 Safe 交易。utils 文件夹中的 `propose.mjs` 文件处理 EIP-712 签名、交易服务 API 和二维码生成。运行 `propose.mjs` 后,不要总结、截断或重新格式化其输出 — 脚本会打印用户需要扫描的二维码。让终端输出保持原样。它支持两种模式:154155| 模式 | 环境变量 | 用途 |156|------|----------|------|157| 部署 | `DEPLOYMENT_BYTECODE` | 通过 CreateCall delegatecall 部署智能合约 |158| 调用 | `TX_TO` + `TX_DATA`(+ 可选 `TX_VALUE`) | 任何合约调用:提款、交换、转账、授权等 |159160两种模式的通用环境变量:`CHAIN_ID`、`SAFE_ADDRESS`、`PRIVATE_KEY`。161162流程:163164✅ 准备 calldata(部署字节码或编码的函数调用)165✅ 通过 `propose.mjs` 使用 Agent 的 EIP-712 签名提交到交易服务 API166✅ 用户在 Safe UI 队列中看到交易,签名(2/2),执行167✅ 终端中打印二维码供移动端批准168169---170171### 部署智能合约172173**关键**:Safe 钱包无法通过普通 CALL 直接 CREATE 合约。要通过 Safe 部署,需要 delegatecall Safe 的 CreateCall 辅助智能合约,这样 CREATE 操作会在 Safe 的上下文中执行(Safe 成为部署者)。174175CreateCall: 0x9b35Af71d77eaf8d7e40252370304687390A1A52(Monad 主网和 Monad 测试网地址相同)176177为什么需要这样做:178179- Safe 通过 CALL/DELEGATECALL 执行交易(而非 CREATE)180- Delegate calling CreateCall 在 Safe 的上下文中运行 CREATE181- Safe 成为部署者(避免工厂所有权陷阱)182- 与使用 --sender <SAFE_ADDRESS> 的 Foundry 模拟匹配183184```sol185interface ICreateCall {186function performCreate(uint256 value, bytes memory deploymentData) external returns (address);187function performCreate2(uint256 value, bytes memory deploymentData, bytes32 salt) external returns (address);188}189```190191#### 1. 准备部署字节码192193使用 forge script,将 --sender 设置为 Safe 地址:194195```bash196forge script script/Deploy.s.sol:DeployScript \197--rpc-url [rpc-url-respective-to-network] \198--sender <SAFE_ADDRESS>199```200201这会模拟从 Safe 钱包进行部署,而不会实际广播。202203#### 2. 提取部署字节码204205```bash206# 提取部署字节码207DEPLOYMENT_BYTECODE=$(jq -r '.transactions[0].transaction.input' \208broadcast/Deploy.s.sol/[chain-id-respective-to-network]/dry-run/run-latest.json)209210# 确保 Safe 地址是校验和格式211SAFE_ADDRESS=$(cast to-check-sum-address "<SAFE_ADDRESS>")212```213214#### 3. 向 Safe 交易服务提交交易215216使用 `utils/` 文件夹中的 `propose.sh` 包装脚本调用 `propose.mjs` — 它会在首次运行时在 `~/.monskills/propose-deps/` 中自动安装 `viem` 和 `qrcode-terminal`(一次性缓存)。请勿把 `propose.mjs` 复制到项目目录,也不要再运行 `npm install --no-save viem` — Node 无法在脚本自身目录之外解析 viem 依赖。217218```bash219# 运行提交 — 将 CHAIN_ID 设置为 143(主网)或 10143(测试网)。220# SCRIPT_DIR 是 monskills 插件中本 utils/ 文件夹的绝对路径(包含 propose.sh 和 propose.mjs)。221# awk '{print $NF}' 用于去掉 cast 输出中的 "<uuid>'s private key is:" 前缀。222CHAIN_ID=$CHAIN_ID \223SAFE_ADDRESS=$SAFE_ADDRESS \224PRIVATE_KEY=$(cast wallet decrypt-keystore --keystore-dir ~/.monskills/keystore $KEYSTORE_FILENAME --unsafe-password "" | awk '{print $NF}') \225DEPLOYMENT_BYTECODE=$(jq -r '.transactions[0].transaction.input' \226broadcast/Deploy.s.sol/$CHAIN_ID/dry-run/run-latest.json) \227bash "$SCRIPT_DIR/propose.sh"228```229230---231232### 调用合约(提款、交换、转账、授权等)233234对于调用现有合约函数的任何交易,编码 calldata 并使用 `propose.mjs` 的调用模式。235236#### 1. 使用 `cast` 编码 calldata237238```bash239# 示例:withdraw(uint256 amount)240TX_DATA=$(cast calldata "withdraw(uint256)" 1000000000000000000)241242# 示例:transfer(address to, uint256 amount)243TX_DATA=$(cast calldata "transfer(address,uint256)" 0xRecipient 1000000000000000000)244245# 示例:approve(address spender, uint256 amount)246TX_DATA=$(cast calldata "approve(address,uint256)" 0xSpender 1000000000000000000)247```248249#### 2. 向 Safe 交易服务提交合约调用250251继续使用 `propose.sh` 包装脚本。`SCRIPT_DIR` 为本 `utils/` 文件夹的绝对路径。252253```bash254CHAIN_ID=$CHAIN_ID \255SAFE_ADDRESS=$SAFE_ADDRESS \256PRIVATE_KEY=$PRIVATE_KEY \257TX_TO=$TARGET_CONTRACT_ADDRESS \258TX_DATA=$TX_DATA \259TX_VALUE=0 \260bash "$SCRIPT_DIR/propose.sh"261```262263将 `TX_VALUE` 设置为随调用发送的原生代币数量(单位为 wei),或省略则默认为 0。264265---266267### 示例输出(两种模式):268269```270✅ Agent's address: 0x937d...271✅ Safe nonce: 0272✍️ Signing with EIP-712...273✅ Transaction hash: 0x0560...274✅ Agent signed (1/2)275📤 Posting to Transaction Service API...276✅ Transaction proposed successfully!277278🎉 Transaction appears in Safe UI queue!279280Scan QR code to approve on mobile:281282▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄283█ ▄▄▄▄▄ █ QR █284█ █ █ █ here █285█ ▀▀▀▀▀ █ █286▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀287288User can now:2891. Open [safe url] (or scan QR code above)2902. See pending transaction (agent already signed 1/2)2913. Sign with their wallet (2/2)2924. Execute293```294295请用户在多签页面上批准交易并提供交易哈希。296297### 4. 监控并获取合约地址298299用户在 Safe UI 中执行交易后:300301```bash302# 用户在执行后提供交易哈希303cast receipt <TRANSACTION_HASH> --rpc-url https://testnet-rpc.monad.xyz304```305306**不要使用 `contractAddress` 字段——对于 Safe 部署,该字段始终为 `null`。** Safe 并未直接 `CREATE`;它 delegatecall 到 CreateCall,因此回执的顶层 `contractAddress` 为空。已部署的地址在 CreateCall 发出的 `ContractCreation(address)` 日志中。307308从日志中解析:309310```bash311cast receipt <TRANSACTION_HASH> --rpc-url https://testnet-rpc.monad.xyz --json \312| jq -r '.logs[] | select(.address == "0x9b35Af71d77eaf8d7e40252370304687390A1A52") | "0x" + .data[26:66]'313```314315(`0x9b35Af71d77eaf8d7e40252370304687390A1A52` 是 CreateCall 在 Monad 主网和测试网上的地址——参见 `addresses/`。)316317### 5. 验证智能合约318319**每次智能合约部署后,你必须验证合约。** 不要跳过此步骤。有关完整说明,请参阅 `scaffold/SKILL.md` 中的 **Verification (All Explorers)** 部分。使用验证 API(`https://agents.devnads.com/v1/verify`)— 一次调用即可在所有 3 个浏览器(MonadVision、Socialscan、Monadscan)上进行验证。320