Safe multisig setup for personal ops
A second signature is useful when I want a transaction checked on another device before it can move funds. A Safe lets me make that a requirement in the wallet contract.
Here I use a 2-of-3 setup: a daily signer, a hardware signer, and an offline recovery signer. The first funding would be a small test transfer; buying and maintaining several devices can be a significant cost for a wallet under $1,000.
Owners, threshold, and execution
A Safe is a smart contract deployed to a chain, with a list of owners and a signature threshold. Any owner can propose a transaction, but the Safe will only execute once the configured threshold is met. That threshold and owner list live on-chain in the Safe contract, and transaction execution is a contract call with the Safe’s nonce, signature set, and calldata (Safe smart-account contracts).
I relied on the official Safe docs and smart-account contracts to verify the model: owners are stored on-chain, the threshold is enforced inside the Safe contract, and the Safe itself is the account that holds assets and executes transactions. The Safe Transaction Service is optional, but it makes multi-device signing sane because it stores proposed transactions for other owners to pick up and confirm (Safe Transaction Service).
For this setup, the keys have three roles:
- Hot signer (daily ops). A software wallet on a hardened machine.
- Primary signer (secure). A hardware wallet that stays offline unless I’m funding or approving.
- Recovery signer (cold). A separate hardware wallet or seed stored offline that exists only for loss recovery.
That setup is a 2-of-3 Safe: daily ops require the hot key plus one hardware key, while recovery still works if the hot key is compromised or the primary device fails. Safe supports arbitrary M-of-N thresholds, which makes this split possible without custom code (Safe docs).
Code: read Safe owners and threshold
Before funding, I verify the Safe owners and threshold directly from the contract. This is a cheap eth_call, so there’s no gas cost unless you submit a transaction. I used a minimal ABI to keep the script clear.
import { ethers } from "ethers";
const rpcUrl = process.env.RPC_URL; // e.g., https://arb1.arbitrum.io/rpc
const safeAddress = process.env.SAFE_ADDRESS;
const abi = [
"function getOwners() view returns (address[])",
"function getThreshold() view returns (uint256)",
];
if (!rpcUrl || !safeAddress) {
throw new Error("Set RPC_URL and SAFE_ADDRESS env vars");
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const safe = new ethers.Contract(safeAddress, abi, provider);
const owners = await safe.getOwners();
const threshold = await safe.getThreshold();
console.log("Owners:", owners);
console.log("Threshold:", threshold.toString());
If the owners or threshold don’t match what I expect, I do not fund the Safe. A stale test owner is exactly the kind of setup error this check should catch.
Risk analysis: multisig still has sharp edges
- Technical risk. Safe is a smart contract. A bug in the contract or a malicious module could still lead to loss. Stick to verified Safe deployments and avoid installing extra modules unless you understand the code and audits.
- Economic risk. Multisigs reduce key risk but don’t protect you from bad trades or contracts. If you approve a malicious token or sign a bad swap, the Safe will execute it once the threshold is met.
- Operational risk. Loss of two signers bricks a 2-of-3 Safe. I keep written recovery steps and verify backups at least once before adding funds. Make sure the remaining two keys can sign if one device is lost.
Test signing and recovery before funding
- Test the complete signing flow with a small transfer. Include the recovery key in a rehearsal before relying on it.
- Choose a threshold you can maintain. A 2-of-3 split tolerates one unavailable signer, but two lost keys can lock you out.
- Verify the Safe on-chain before every new funding wave. The read-only script above is the fastest sanity check.
- Keep backups offline and test them. Recovery only works if you can actually sign with the cold key when you need it.
Source notes
- Safe documentation on owners, threshold, and transaction flow: https://docs.safe.global/
- Safe smart-account contracts (owner storage, threshold logic): https://github.com/safe-global/safe-smart-account
- Safe Transaction Service (proposed transaction storage): https://github.com/safe-global/safe-transaction-service