Building a multi-sig wallet in Solidity, step by step
Threshold approvals, replay protection, and deploying to Polygon testnet. A complete walkthrough.
What is a multi-sig wallet
A multi-signature wallet requires M-of-N owners to approve a transaction before it executes. For example, a 2-of-3 wallet means any 2 of 3 designated owners must sign. This is the standard pattern for shared treasury management on-chain.
The contract structure
The core state: a list of owners, the threshold M, and a mapping of proposed transactions. Each transaction has a value, a target address, calldata, and a set of confirmations from owners.
contract MultiSigWallet {
address[] public owners;
uint public required; // threshold
struct Transaction {
address to;
uint value;
bytes data;
bool executed;
uint confirmations;
}
Transaction[] public transactions;
mapping(uint => mapping(address => bool)) public confirmed;
}Submitting and confirming
Any owner can submit a transaction. Once M owners confirm it, anyone can trigger execution. The separation of confirmation from execution is intentional — execution is a separate step so the submitter does not have special power to force execution.
function confirmTransaction(uint txIndex) external onlyOwner {
require(!confirmed[txIndex][msg.sender], "already confirmed");
confirmed[txIndex][msg.sender] = true;
transactions[txIndex].confirmations++;
}
function executeTransaction(uint txIndex) external onlyOwner {
Transaction storage txn = transactions[txIndex];
require(!txn.executed, "already executed");
require(txn.confirmations >= required, "not enough confirmations");
txn.executed = true;
(bool success, ) = txn.to.call{value: txn.value}(txn.data);
require(success, "execution failed");
}Replay protection
Without replay protection, a confirmed transaction could be re-executed. The executed flag on the Transaction struct prevents this — once set, the transaction cannot be re-executed regardless of confirmation count.
Deploying to Polygon testnet
Deployment used Hardhat with the Polygon Mumbai testnet. The key advantage of Polygon over Ethereum testnet: lower gas costs make iterative testing practical.
// hardhat.config.ts
polygon_mumbai: {
url: process.env.POLYGON_RPC_URL,
accounts: [process.env.PRIVATE_KEY!],
chainId: 80001
}Security considerations
The main risks in multi-sig contracts: owner key compromise (mitigated by the threshold), and griefing by non-cooperative owners (no mitigation — this is a social contract). We also added a timelock on execution for high-value transactions, giving owners a window to revoke confirmations if something looks wrong.