name: safe-multisig description: Guide the user through Safe multisig operations -- managing owners, preparing arbitrary transactions, collecting signatures, and executing on-chain. Supports Ledger hardware wallets and Timelock interactions. user-invocable: true allowed-tools: Read, Edit, Write, Bash, Grep, Glob, AskUserQuestion argument-hint: "[manage-owners | prepare-tx | sign | execute]"
Safe Multisig Operations
You are helping the user interact with a Gnosis Safe multisig wallet. The toolkit lives in tools/safeMultisig/.
The workflow always follows: Prepare -> Sign -> Execute.
Step 1: Determine the operation
Ask the user (or determine from $ARGUMENTS) which operation they need:
Option A: Manage Owners (manageOwners.ts)
- When: Add/remove Safe owners or change the signing threshold
- Script:
tools/safeMultisig/manageOwners.ts
Option B: Prepare Arbitrary Transaction (prepareTransaction.ts)
- When: Execute any contract call through the Safe (e.g., Timelock schedule/execute, contract upgrades, token transfers)
- Script:
tools/safeMultisig/prepareTransaction.ts
Option C: Sign a Prepared Transaction (signSafeTransaction.ts)
- When: A transaction is already prepared in
transactions.jsonand needs more signatures - Script:
tools/safeMultisig/signSafeTransaction.ts
Option D: Execute a Signed Transaction (executeSafeTransaction.ts)
- When: Enough signatures have been collected and the transaction is ready to go on-chain
- Script:
tools/safeMultisig/executeSafeTransaction.ts
If the user says something generic like "I want to use the multisig", ask them which operation they need.
Step 2: Gather required parameters
For ALL operations:
- Network -- which network to use (e.g.,
mainnet,sepolia,custom). Ask: "Which network should we use?" - Environment -- remind the user to ensure
.envhas:CUSTOM_PROVIDER(RPC URL, if using--network custom)LEDGER_ACCOUNT(their Ledger address, if using Ledger)- Or
MNEMONICfor software wallet signing
For Option A (Manage Owners):
Ask the user for:
- safeAddress -- the Safe multisig address
- ownersToAdd -- addresses to add as owners (optional)
- ownersToRemove -- addresses to remove (optional)
- newThreshold -- new signing threshold (optional, keeps current if omitted)
At least one of ownersToAdd, ownersToRemove, or newThreshold must be provided.
For Option B (Prepare Arbitrary Transaction):
Ask the user for:
- safeAddress -- the Safe multisig address
- to -- target contract address
- data -- encoded calldata (hex string starting with
0x) - value -- ETH value (default: 0). Supports:
0,"1.5 ether","10 gwei", or wei as string - operation -- 0 for Call, 1 for DelegateCall (default: 0)
- description -- human-readable description (optional but recommended)
Common use case: Timelock schedule/execute
If the user wants to schedule or execute a Timelock operation, help them encode the calldata:
Schedule:
function schedule(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt, uint256 delay)
Execute:
function execute(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt)
The Timelock address for mainnet is: 0xEf1462451C30Ea7aD8555386226059Fe837CA4EF
For Option C (Sign):
- TX_INDEX -- which transaction to sign (default: latest)
- SIGNER_INDEX -- index of signer in hardhat config (default: 0). In practice each owner signs from their own machine with their own Ledger, so this stays at 0. Only relevant if a single machine has multiple signers configured.
For Option D (Execute):
- EXECUTOR_INDEX -- index of executor account (default: 0)
- TX_INDEX -- which transaction to execute (default: latest)
- FORCE_EXECUTE -- set to
"true"to bypass nonce mismatch (only if user confirms)
Step 3: Write the parameters file
For Options A and B, write the configuration to tools/safeMultisig/parameters.json.
Template for Manage Owners:
{
"safeAddress": "<safe-address>",
"ownersToAdd": ["<address1>", "<address2>"],
"ownersToRemove": ["<address>"],
"newThreshold": 3
}
Only include the fields that are relevant. Omit ownersToAdd if not adding, omit ownersToRemove if not removing.
Template for Arbitrary Transaction:
{
"safeAddress": "<safe-address>",
"to": "<target-contract>",
"data": "<encoded-calldata>",
"value": 0,
"description": "<human-readable-description>"
}
Step 4: Run the appropriate script
All commands run from the repository root.
Prepare (Option A -- Manage Owners):
npx hardhat run tools/safeMultisig/manageOwners.ts --network <network>
Prepare (Option B -- Arbitrary Transaction):
npx hardhat run tools/safeMultisig/prepareTransaction.ts --network <network>
Sign (Option C):
SIGNER_INDEX=<index> npx hardhat run tools/safeMultisig/signSafeTransaction.ts --network <network>
Execute (Option D):
EXECUTOR_INDEX=<index> npx hardhat run tools/safeMultisig/executeSafeTransaction.ts --network <network>
Or with force execute:
FORCE_EXECUTE=true EXECUTOR_INDEX=<index> npx hardhat run tools/safeMultisig/executeSafeTransaction.ts --network <network>
Step 5: Guide through the full flow
After preparing, remind the user of the remaining steps:
- Collect signatures -- each Safe owner runs
signSafeTransaction.tsfrom their own machine with their own Ledger (SIGNER_INDEX stays at 0 for everyone). After each owner signs, they share the updatedtransactions.jsonwith the next owner until threshold is met. - Execute -- once threshold signatures are collected, anyone runs
executeSafeTransaction.ts.
If using Ledger, remind the user:
- Ledger must be connected and unlocked
- Ethereum app must be open
- Blind signing must be enabled (Settings -> Blind signing -> Enabled)
Troubleshooting
If the user hits an error, check these common issues:
| Error | Cause | Fix |
|---|---|---|
| "Nonce mismatch" | A previous tx was executed or prepared tx is stale | Re-run the preparation script |
| "Insufficient signatures" | Not enough owners have signed | More owners need to run signSafeTransaction.ts |
| "Not an owner" / "Signer is not a Safe owner" | The signer address is not a Safe owner | Check SIGNER_INDEX or LEDGER_ACCOUNT |
| "Already signed" | This signer already signed | Another owner needs to sign |
| "Gas estimation failed" | Transaction would revert | Check calldata, permissions; try FORCE_EXECUTE=true |
| Ledger not detected | USB or app issue | Reconnect, unlock, open Ethereum app |
| Transaction rejected on Ledger | Blind signing disabled | Enable blind signing in Ledger Ethereum app settings |
Important notes
- NEVER ask the user for private keys or mnemonics. Only remind them to set these in
.env. - Always confirm the network and addresses with the user before executing.
- For mainnet operations, recommend Ledger hardware wallets.
- The
transactions.jsonfile is auto-generated -- do not manually create it.