A Multi-Signature (MultiSig) wallet is a smart contract that requires multiple parties to agree on a transaction before it can be executed. Unlike a standard wallet controlled by a single private key (Single-of-One), a MultiSig adds a layer of security by requiring a
This is widely used by DAOs, project teams, and security-conscious individuals to prevent a single point of failure. If one signer's key is compromised, the funds remain safe.
Anyone can submit a transaction proposal to the contract. The transaction is stored in a PENDING state and assigned a unique IdCount.
function Submit(address _address, address _token, uint256 input, Tx_Type _type) public {
require(_type != Tx_Type.NONE, InvalidTransactionType());
if(_type == Tx_Type.ETH) {}Upon submission, if the sender is one of the authorized signers, the contract automatically records their approval via _tryAutoConfirm.
Authorized signers must call the confirm function for a specific Transaction ID.
- Signer Validation: Only addresses in the Signers array can confirm.
- Double-Voting Prevention: The hasConfirmed mapping ensures a signer cannot vote twice for the same transaction.
function confirm(uint256 _txId) public
checkSigner(msg.sender)
checkId(_txId) {}Once the number of approvals reaches the defined Threshold, the execute function can be called. This function performs the actual logic based on the Tx_Type:
- ETH: Transfers Ether from the contract to a recipient.
- ERC20: Transfers standard tokens using the
transferinterface. - ADD_SIGNER / REMOVE_SIGNER: Modifies the list of authorized controllers.
- THRESHOLD: Updates the minimum number of approvals required for future transactions.
function execute(uint256 _txId) public
checkId(_txId) {}The contract handles more than just moving money; it manages its own governance:
| Type | Action | Parameters Used |
|---|---|---|
| ETH | Send Native Currency | _address (Receiver), input (Amount) |
| ERC20 | Send Tokens | _token (Contract), _address (Receiver), input (Amount) |
| ADD_SIGNER | Add new admin | _address (New Signer) |
| REMOVE_SIGNER | Remove admin | _address (Target Signer) |
| THRESHOLD | Change logic | input (New Threshold value) |
At deployment, the contract enforces strict rules to prevent "bricking" (locking) the wallet:
- No Zero Addresses: Prevents burning ownership to the null address.
- Redundancy Check: Ensures the same address isn't added twice to inflate the signer count.
- Minimum Signers: Requires at least 2 signers to ensure it is actually a "multi" sig.
- Threshold Validation: The threshold must be at least 2 and cannot exceed the total number of signers.
Instead of gas-heavy strings, this contract uses Custom Errors (e.g., error ThresholdNotMet()). This makes transactions significantly cheaper for the signers because the EVM doesn't have to store and revert long string messages.
The contract uses two primary enums to manage logic flow:
- Tx_Type: Categorizes what the transaction intends to do (ETH, ERC20, etc.).
- States: Tracks if a transaction is
NONE,PENDING, orEXECUTED. OnceEXECUTED, a transaction cannot be run again, protecting against Reentrancy or Double-Spend attacks.
- ETH Transfers: Uses
.call{value: _amount}("")which is the modern standard for sending ETH, allowing for flexible gas stipends. - Array Management: When a signer is removed, the contract moves the last element of the array into the removed spot and pops the end (
Signers[i] = Signers[len-1];). This is a gas-efficient way to delete from an unordered array without shifting every element.
A contract TokenERC20 is deployed and used to test ERC20 Transactions.
The provided test suite ensures the contract behaves as expected under various conditions:
- Deployment: Validates that invalid thresholds or redundant signers cause a revert.
- Submission Logic: Checks that
ETHandERC20proposals require the correct inputs (e.g., you can't propose an ERC20 transfer without a token address). - Execution Flow: * Verifies funds actually move after execution.
- Ensures the Threshold is strictly enforced.
- Confirms that state updates to
EXECUTEDproperly.
- Edge Cases: Tests for insufficient ETH balance and unauthorized confirmation attempts.
-
Deploy: Provide a list of addresses and a threshold (e.g.,
[A, B, C], 2). - Fund: Send ETH or ERC20 tokens to the contract address.
-
Propose: Call
Submitwith the desired action. -
Approve: Other signers call
confirm(txId). -
Finalize: Once approvals
$\ge$ Threshold, anyone callsexecute(txId).