Multi-sig contract suitable for use as a 2-of-3 multisig wallet.
The core functionality of the wallet is implemented in the WalletSimple contract. It is initialized with 3 signer addresses, two of which must participate in order to execute a transaction from the wallet. Auxillary contracts called Forwarders can be deployed with a WalletSimple contract initialized as its "parent". Any funds that the forwarder receives will be sent on back to the parent wallet. This enables omnibus-style wallets to create many addresses that are all controlled by the same wallet.
New forwarder contracts (contracts/ForwarderV4.sol) can be deployed and initialized with a parent address and fee address. Parent address will be the single sig base address of the wallet. Fee address will be the gas tank address of the wallet. Both parent address and fee address will be allowed to invoke methods of the contract, but any funds that the forwarder receives will be sent on back to the parent address. This enables omnibus-style wallets to create many addresses that are all controlled by the same wallet.
Features of the wallet contract:
- Functions as a 2-of-3 multisig wallet for sending transactions.
- Support for synchronous (single transaction) approvals containing multiple signatures through the use of ecrecover.
- ERC20 tokens and ether can be sent out from the main wallet through a multisig process.
- ‘Safe Mode’ can be set on a wallet contract that prevents ETH and ERC20 tokens from being sent anywhere other than to wallet signers.
- Transactions can be sent in batches through a batch function (sendMultiSigBatch) to save on fees if a user needs to perform multiple transactions.
- Slightly different implementations exist for non-eth chains, which require signatures to include a networkId to protect against cross-chain replay of signatures.
Features of the forwarder contract
- Deployed with a single, permanent parent address.
- Automatically flushes any ETH received to the parent address.
- Able to flush ERC20 tokens received to the parent address through a separate transaction (flushForwarderTokens).
Features of the updated forwarder contract
- Deployed with a permanent parent address and a fee address.
- Both parent address and fee address can invoke the methods of the contract.
- Automatically flushes any ETH received to the parent address.
- Able to flush ERC20 tokens received to the parent address through a separate transaction (flushTokens).
Note that this suite of contracts is an upgraded version of eth-multisig-v2. The main changes that were made are as follows:
- Wallets and forwarders are deployed as proxy instances to a single implementation, to save on deployment fees.
- Wallets and forwarders are deployed using CREATE2 to allow addresses to be generated on demand, but only deployed upon first use.
- Wallets include a batch function to save on fees when sending multiple transactions.
- SequenceId is now simply a strictly increasing nonce.
The Wallet contract and forwarder contracts can each be deployed independently of each other, using the provided ForwarderFactory and WalletFactory. These factories employ two features to minimize the cost associated with deploying a new contract:
- Minimal proxy - Each deployed contract is simply a tiny contract which proxies calls to use the logic of a single implementation contract.
- CREATE2 - Contracts are deployed with the CREATE2 opcode to allow users to distribute contract addresses and only deploy them upon first use.
Wallets To deploy wallets, follow these steps:
- Deploy a wallet contract (contracts/WalletSimple.sol) with any address. Take note of the wallet's address.
- Deploy a WalletFactory contract (contracts/WalletFactory.sol) with any address. Use the address of the contract deployed in step 1 as the
_implementationAddress
parameter. - Call the
createWallet
function on the factory deployed in step 2. Provide the list of signers on the wallet, and some "salt" which will be used to determine the wallet's address via CREATE2. - Check for the
WalletCreated
event from the above transaction. This will include your newly generated wallet address
Forwarders To deploy forwarders, follow these steps:
- Deploy a forwarder contract (contracts/Forwarder.sol) with any address. Take note of the contract's address.
- Deploy a ForwarderFactory contract (contracts/ForwarderFactory.sol) with any address. Use the address of the contract deployed in step 1 as the
_implementationAddress
parameter. - Call the
createForwarder
function on the factory deployed in step 2. Provide the parent address, and some "salt" which will be used to determine the forwarder's address via CREATE2. - Check for the
ForwarderCreated
event from the above transaction. This will include your newly generated forwarder address
ForwardersV4 To deploy forwarders, follow these steps:
- Deploy a forwarder contract (contracts/ForwarderV4.sol) with any address. Take note of the contract's address.
- Deploy a ForwarderFactory contract (contracts/ForwarderFactoryV4.sol) with any address. Use the address of the contract deployed in step 1 as the
_implementationAddress
parameter. - Call the
createForwarder
function on the factory deployed in step 2. Provide the parent address, fee address and some "salt" which will be used to determine the forwarder's address via CREATE2. - Check for the
ForwarderCreated
event from the above transaction. This will include your newly generated forwarder address
Brief descriptions of the various contracts provided in this repository.
The multi-sig wallet contract. Initializes with three signers and requires authorization from any two of them to send a transaction.
Forwarder function. Initializes with a parent to which it will forward any ETH that it receives. Also has a function to forward ERC20 tokens.
Factory to create wallets. Deploys a small proxy which utilizes the implementation of a single wallet contract.
Factory to create forwarder. Deploys a small proxy which utilizes the implementation of a single forwarder contract.
Transfer batcher. Takes a list of recipients and amounts, and distributes ETH to them in a single transaction.
Forwarder function. Initializes with a parent and a fee address. It will forward any ETH that it receives to the parent address. Also has a function to forward ERC20 tokens. This function can be invoked using the parent address or the fee address.
Factory to create updated forwarder (contracts/ForwarderV4.sol). Deploys a small proxy which utilizes the implementation of a single forwarder contract.
NodeJS 16 is required.
yarn
This installs hardhat.
Find it at contracts/WalletSimple.sol
A test suite is included through the use of the hardhat framework, providing coverage for methods in the wallet.
On first run:
- Create a file called
.env
in the root directory. - Add the following variable:
TESTNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT=<Your private key> MAINNET_PRIVATE_KEY_FOR_CONTRACT_DEPLOYMENT=<Your private key> PRIVATE_KEY_FOR_V4_CONTRACT_DEPLOYMENT=<Your private key>
Note: <your private key>
can be from a wallet like Metamask.
Once done, you can run the test with the following command:
npx hardhat test
You need to add the following to the extra mocha options
--require hardhat/register
- wallet creation salt should include [a hash of] the signers associated with the wallet.
- forwarder creation salt should include [a hash of] the parentAddress.
- new forwarderv4 creation salt should include [a hash of] the parentAddress and feeAddress.