- Key Components of a MultiSig Wallet
- Writing A MultiSig Wallet Contract
- Deploying MultiSig Wallets on Remix
MultiSig wallets have become increasingly popular due to their enhanced security features. In this article, we will delve into the key components of a MultiSig wallet, how to write a MultiSig wallet contract using Solidity, deploying the wallet, and handling deposits and withdrawals.
Note that the code provided here is for educational purposes only. If you want to use a multisig wallet to hold real funds you should use a production grade product that has been security audited like Gnosis Safe’s.
Key Components of a MultiSig Wallet
A MultiSig wallet is essentially a smart contract that requires multiple signatures before executing a transaction. The primary components of a MultiSig wallet include:
- Owners The individuals or entities who have the power to sign transactions. In most cases, a MultiSig wallet requires a minimum of two signatures from the owners to authorize a transaction.
- Transactions These are the funds transfer requests that require multiple signatures to be executed. Each transaction includes the recipient’s address and the amount to be transferred.
- Verification Once a transaction is submitted, the owners can either approve or reject the transaction. When enough owners have verified the transaction the last signer will be authorized to execute it.
Writing A MultiSig Wallet Contract
Here’s a basic template for a MultiSig wallet contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MultiSig {
address[] public owners;
uint public required;
struct Transaction {
address to;
uint value;
bool executed;
}
Transaction[] public transactions;
mapping(uint => mapping(address => bool)) public confirmations;
constructor(address[] memory _owners, uint _required) {
owners = _owners;
required = _required;
}
function submitTransaction(address _to, uint _value) public {
bool ownerFound = false;
for (uint i = 0; i < owners.length; i++) {
if (msg.sender == owners[i]) ownerFound = true;
}
require (ownerFound == true, "not an owner");
transactions.push(Transaction(_to, _value, false));
confirmations[transactions.length - 1][msg.sender] = true;
}
function confirmTransaction(uint _txIndex) public {
bool ownerFound = false;
uint confirms = 0;
for (uint i = 0; i < owners.length; i++) {
if (msg.sender == owners[i]) {
ownerFound = true;
confirmations[_txIndex][msg.sender] = true;
}
if (confirmations[_txIndex][owners[i]] == true) confirms ++;
}
require (ownerFound == true, "not an owner");
if (confirms >= required && transactions[_txIndex].executed == false) {
transactions[_txIndex].executed = true;
payable(transactions[_txIndex].to).transfer(transactions[_txIndex].value);
}
}
receive() external payable {}
}
The contract stores an array of owners and a required number of confirmations needed to approve a transaction. Transactions, once proposed by any owner, are stored in an array and can only be executed after the required number of confirmations is reached.
Key features include the ability for owners to submit transactions via the submitTransaction() function and confirm them through the confirmTransaction() function.
Each transaction includes a recipient address and the amount of Ether and an executed boolean to track whether it has been processed. The contract tracks each owner’s confirmation status for every transaction and executes it once the number of confirmations meets or exceeds the required threshold. The contract includes a receive() function to allow direct Ether deposits.
Note that there is no support for ERC20 tokens in this demo contract but you could add this by creating a tokenTransfer struct which includes a token address and then integrating an IERC20 interface.
Deploying MultiSig Wallets
You can deploy this contract on using hardhat, foundry and remix.
We will be doing it in Remix, visit the IDE at: https://remix.ethereum.org/
Paste in the code and compile the contract.
When deploying the contract you’ll need to pass in an array of owner addresses and a required variable which is the number of signatures required including the signer.
Once deployed you can send some testnet funds to the wallet by setting a value and calling “Low level interactions” transact. This will send some ETH from the deployers wallet to be held in the contract.
You can then call submitTransaction(_to, _value) to setup a new transaction. Finally switch account to one of the other owners and call confirmTransaction(0) and the funds will be sent from the contract to the _to address.