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.md
1---2name: safe-wallet-management3description: How to create and use a Safe multisig using the Safe transactions API4---56This file has instructions on how to create and use a Safe multisig.78## Quick Navigation910| Task | Section |11|------|---------|12| Check if a multisig already exists | [Check if the agent has already created a multisig](#check-if-the-agent-has-already-created-a-multisig) |13| Get the Safe address for an owner | [Get Safe address](#get-safe-address) |14| Create a new Safe multisig | [Creating a multisig](#creating-a-multisig) |15| Deploy smart contracts via Safe | [Deploying smart contracts using Safe multisig](#deploying-smart-contracts-using-safe-multisig) |16| Propose a transaction to Safe | [Propose transaction to Safe Transaction Service](#3-propose-transaction-to-safe-transaction-service) |17| Monitor and get contract address | [Monitor and Get Contract Address](#4-monitor-and-get-contract-address) |1819## Network Reference2021RPC URLs to be used for respective networks, make sure you know what network you are tasked to build on if you don't ask the user.2223### RPC URLs2425| Network | Chain ID | RPC |26|---------|----------|-----|27| Monad Testnet | 10143 | https://testnet-rpc.monad.xyz |28| Monad Mainnet | 143 | https://rpc.monad.xyz |2930Safe Transaction Base API URLs for respective networks, make sure you know what network you are tasked to build on.3132### Safe Transaction Service (base URLs)3334| Network | URL |35| --- | --- |36| Monad Testnet | https://api.safe.global/tx-service/monad-testnet/api/v1 |37| Monad Mainnet | https://api.safe.global/tx-service/monad/api/v1 |3839## Get Safe address4041To look up Safe multisig addresses owned by a specific wallet address, query the Safe Transaction Service API.4243### From local storage4445Check if the agent has previously stored multisig details:4647```bash48cat ~/.monskills/multisig.json49```5051The file has `testnet` and `mainnet` properties with Safe addresses and owners for each network.5253### From Safe Transaction Service API5455Query the API to get all Safes owned by a given address:5657**Monad testnet:**5859```bash60curl -s "https://api.safe.global/tx-service/monad-testnet/api/v1/owners/$OWNER_ADDRESS/safes/" | jq61```6263**Monad mainnet:**6465```bash66curl -s "https://api.safe.global/tx-service/monad/api/v1/owners/$OWNER_ADDRESS/safes/" | jq67```6869The response returns a `safes` array with all Safe addresses where the given address is an owner.7071### Verify a Safe on-chain7273To confirm a Safe exists and check its owners/threshold:7475```bash76# Get owners77cast call $SAFE_ADDRESS "getOwners()(address[])" --rpc-url [rpc-url-respective-to-network]7879# Get threshold80cast call $SAFE_ADDRESS "getThreshold()(uint256)" --rpc-url [rpc-url-respective-to-network]81```8283## Check if the agent has already created a multisig8485If the agent has created a multisig then there must be a file multisig.json in ~/.monskills/ folder. Based on the network (Monad mainnet or testnet) there might info for the same.8687If multisig details are not found then create a multisig.8889## Creating a multisig9091**Correct flow:**92931. Make sure the agent has a wallet (check for encrypted keystore in `~/.monskills/keystore/`).942. Make sure Foundry toolkit is installed (foundryup --version)953. Ask the user for 2 wallet address to be signers on the multisig.964. Deploy Safe with DeploySafeCREATE2.sol (check for the script in the same folder)97- DeploySafeCREATE2.sol can be used for both for Monad mainnet and Monad testnet.985. After the Safe is created make sure to store the multisig address with owners in multisig.json file in ~/.monskills/ folder, absolutely make sure that you have "testnet" and "mainnet" properties in the json file and are storing the multisig details respective to the network.99100### Commands to create Safe on Monad testnet (strictly for Monad testnet only)101102```bash103# Fund agent's wallet from faucet, since it is testnet, funds can be claimed from faucet.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# Wait for funds109while [ "$(cast balance $AGENT_WALLET_ADDRESS --rpc-url https://testnet-rpc.monad.xyz)" = "0" ]; do110sleep 2111done112113# Deploy Safe with CREATE2 (standard SafeProxyFactory)114# Decrypt private key on-the-fly from encrypted keystore.115# `cast wallet decrypt-keystore` prints "<uuid>'s private key is: 0x...";116# awk '{print $NF}' strips everything except the hex key.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### Commands to create Safe on Monad mainnet (strictly for Monad mainnet only)128129```bash130# Check if the wallet has balance on Monad mainnet, if no balance ask the user to fund the address on Monad mainnet.131cast balance $AGENT_WALLET_ADDRESS --rpc-url https://rpc.monad.xyz132133# Deploy Safe with CREATE2 (standard SafeProxyFactory)134# Decrypt private key on-the-fly from encrypted keystore (awk strips the135# "<uuid>'s private key is:" prefix that cast prints).136OWNER_1=$OWNER_1 OWNER_2=$OWNER_2 OWNER_3=$CLAUDE_ADDRESS \137forge script DeploySafeCREATE2.sol:DeploySafeCREATE2 \138--private-key $(cast wallet decrypt-keystore --keystore-dir ~/.monskills/keystore $KEYSTORE_FILENAME --unsafe-password "" | awk '{print $NF}') \139--rpc-url https://rpc.monad.xyz \140--broadcast141142echo "โ Safe deployed: $SAFE_ADDRESS"143echo "๐ https://app.safe.global/home?safe=monad:$SAFE_ADDRESS"144```145146Once Safe is created save the multisig address with owners in multisig.json file in ~/.monskills/ folder, absolutely make sure that you have "testnet" and "mainnet" properties in the multisig.json file and are storing the multisig details respective to the network.147148## Proposing transactions via Safe multisig149150A Safe multisig must be already deployed in order to deploy smart contracts or perform onchain actions.151152**IMPORTANT**: This workflow uses Safe multisig for ALL transactions โ deployments, contract calls, token transfers, withdrawals, etc. Direct transactions with --private-key or --broadcast are NOT allowed.153154**CRITICAL: Always use `propose.mjs`** โ NEVER write a new/custom script to propose Safe transactions. The `propose.mjs` file in the utils folder handles EIP-712 signing, the Transaction Service API, and QR code generation. After running `propose.mjs`, do NOT summarize, truncate, or reformat its output โ the script prints a QR code that the user needs to scan. Let the terminal output speak for itself. It supports two modes:155156| Mode | Env vars | Use case |157|------|----------|----------|158| Deploy | `DEPLOYMENT_BYTECODE` | Smart contract deployment via CreateCall delegatecall |159| Call | `TX_TO` + `TX_DATA` (+ optional `TX_VALUE`) | Any contract call: withdraw, swap, transfer, approve, etc. |160161Common env vars for both modes: `CHAIN_ID`, `SAFE_ADDRESS`, `PRIVATE_KEY`.162163Approach:164165โ Prepare calldata (deployment bytecode OR encoded function call)166โ Post to Transaction Service API with Agent's EIP-712 signature via `propose.mjs`167โ User sees transaction in Safe UI queue, signs (2/2), executes168โ QR code printed in terminal for mobile approval169170---171172### Deploying smart contracts173174**CRITICAL**: Safe wallets cannot directly CREATE contracts from a normal CALL. To deploy through a Safe, delegatecall into Safe's CreateCall helper smart contract so the CREATE happens in the Safe's context (Safe becomes the deployer).175176CreateCall: 0x9b35Af71d77eaf8d7e40252370304687390A1A52 (same address on both Monad mainnet and Monad testnet)177178Why it's needed:179180- Safe executes transactions via CALL/DELEGATECALL (not CREATE)181- Delegate calling CreateCall runs CREATE inside the Safe's context182- Safe becomes the deployer (no factory-ownership footgun)183- Matches Foundry simulations using --sender <SAFE_ADDRESS>184185```sol186interface ICreateCall {187function performCreate(uint256 value, bytes memory deploymentData) external returns (address);188function performCreate2(uint256 value, bytes memory deploymentData, bytes32 salt) external returns (address);189}190```191192#### 1. Prepare deployment bytecode193194Use forge script with --sender set to the Safe address:195196```bash197forge script script/Deploy.s.sol:DeployScript \198--rpc-url [rpc-url-respective-to-network] \199--sender <SAFE_ADDRESS>200```201202This simulates the deployment from the Safe wallet without broadcasting.203204#### 2. Extract Deployment Bytecode205206```bash207# Extract deployment bytecode208DEPLOYMENT_BYTECODE=$(jq -r '.transactions[0].transaction.input' \209broadcast/Deploy.s.sol/[chain-id-respective-to-network]/dry-run/run-latest.json)210211# Ensure Safe address is checksummed212SAFE_ADDRESS=$(cast to-check-sum-address "<SAFE_ADDRESS>")213```214215#### 3. Propose deployment to Safe Transaction Service216217Invoke the `propose.sh` wrapper alongside `propose.mjs` in this folder โ it218bootstraps a dependency cache at `~/.monskills/propose-deps/` the first time it219runs (one-time `npm install` of `viem` + `qrcode-terminal`), then executes the220proposer. Do NOT copy `propose.mjs` into the project dir or run221`npm install --no-save viem` โ Node will fail to resolve viem against the222script's own directory.223224```bash225# Run proposal โ set CHAIN_ID to 143 (mainnet) or 10143 (testnet).226# SCRIPT_DIR is the absolute path to this `utils/` folder inside the monskills227# plugin (the one containing propose.sh and propose.mjs).228# awk '{print $NF}' strips cast's "<uuid>'s private key is:" prefix.229CHAIN_ID=$CHAIN_ID \230SAFE_ADDRESS=$SAFE_ADDRESS \231PRIVATE_KEY=$(cast wallet decrypt-keystore --keystore-dir ~/.monskills/keystore $KEYSTORE_FILENAME --unsafe-password "" | awk '{print $NF}') \232DEPLOYMENT_BYTECODE=$(jq -r '.transactions[0].transaction.input' \233broadcast/Deploy.s.sol/$CHAIN_ID/dry-run/run-latest.json) \234bash "$SCRIPT_DIR/propose.sh"235```236237---238239### Calling contracts (withdraw, swap, transfer, approve, etc.)240241For any transaction that calls an existing contract function, encode the calldata and use `propose.mjs` in Call mode.242243#### 1. Encode calldata with `cast`244245```bash246# Example: withdraw(uint256 amount)247TX_DATA=$(cast calldata "withdraw(uint256)" 1000000000000000000)248249# Example: transfer(address to, uint256 amount)250TX_DATA=$(cast calldata "transfer(address,uint256)" 0xRecipient 1000000000000000000)251252# Example: approve(address spender, uint256 amount)253TX_DATA=$(cast calldata "approve(address,uint256)" 0xSpender 1000000000000000000)254```255256#### 2. Propose contract call to Safe Transaction Service257258Use `propose.sh` (same wrapper as above). `SCRIPT_DIR` is the absolute path to259this `utils/` folder.260261```bash262CHAIN_ID=$CHAIN_ID \263SAFE_ADDRESS=$SAFE_ADDRESS \264PRIVATE_KEY=$PRIVATE_KEY \265TX_TO=$TARGET_CONTRACT_ADDRESS \266TX_DATA=$TX_DATA \267TX_VALUE=0 \268bash "$SCRIPT_DIR/propose.sh"269```270271Set `TX_VALUE` to the amount of native token (in wei) to send with the call, or omit for 0.272273---274275### Example output (both modes):276277```278โ Agent's address: 0x937d...279โ Safe nonce: 0280โ๏ธ Signing with EIP-712...281โ Transaction hash: 0x0560...282โ Agent signed (1/2)283๐ค Posting to Transaction Service API...284โ Transaction proposed successfully!285286๐ Transaction appears in Safe UI queue!287288Scan QR code to approve on mobile:289290โโโโโโโโโโโโโโโ291โ โโโโโ โ QR โ292โ โ โ โ here โ293โ โโโโโ โ โ294โโโโโโโโโโโโโโโ295296User can now:2971. Open [safe url] (or scan QR code above)2982. See pending transaction (agent already signed 1/2)2993. Sign with their wallet (2/2)3004. Execute301```302303Ask the user to approve the transaction on the multisig page and ask for the transaction hash.304305### 4. Monitor and Get Contract Address306307After user executes the transaction in Safe UI:308309```bash310# User provides transaction hash after execution311cast receipt <TRANSACTION_HASH> --rpc-url https://testnet-rpc.monad.xyz312```313314**Do NOT use the `contractAddress` field โ it is always `null` for Safe deployments.** The Safe didn't directly `CREATE`; it delegatecalled into CreateCall, so the receipt's top-level `contractAddress` stays empty. The deployed address is in the `ContractCreation(address)` log emitted by CreateCall.315316Parse it from the logs:317318```bash319cast receipt <TRANSACTION_HASH> --rpc-url https://testnet-rpc.monad.xyz --json \320| jq -r '.logs[] | select(.address == "0x9b35Af71d77eaf8d7e40252370304687390A1A52") | "0x" + .data[26:66]'321```322323(`0x9b35Af71d77eaf8d7e40252370304687390A1A52` is the CreateCall address on Monad mainnet and testnet โ see `addresses/`.)324325### 5. Verify Smart Contract326327**After every smart contract deployment, you MUST verify the contract.** Do not skip this step. Refer to the **Verification (All Explorers)** section in `scaffold/SKILL.md` for full instructions. Use the verification API (`https://agents.devnads.com/v1/verify`) โ it verifies on all 3 explorers (MonadVision, Socialscan, Monadscan) with one call.