Overview

A multi-signature wallet requires multiple signatures to authorize a transaction. This enhances security by ensuring that no single person can control the funds. In this guide, we will create a simple multi-signature wallet smart contract, deploy it, and interact with it using Ethers.js.

1. Writing the Multi-Signature Wallet Smart Contract

First, we need to write a smart contract for the multi-signature wallet. Below is a simple implementation in Solidity:

        
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MultiSigWallet {
address[] public owners;
mapping(address => bool) public isOwner;
uint public required;

struct Transaction {
address to;
uint value;
bool executed;
uint confirmations;
mapping(address => bool) isConfirmed;
}

Transaction[] public transactions;

modifier onlyOwner() {
require(isOwner[msg.sender], "Not an owner");
_;
}

constructor(address[] memory _owners, uint _required) {
require(_owners.length > 0, "Owners required");
require(_required > 0 && _required <= _owners.length, "Invalid required number of owners");

for (uint i = 0; i < _owners.length; i++) {
address owner = _owners[i];
require(owner != address(0), "Invalid owner");
require(!isOwner[owner], "Owner not unique");

isOwner[owner] = true;
owners.push(owner);
}
required = _required;
}

function submitTransaction(address _to, uint _value) public onlyOwner {
transactions.push(Transaction({
to: _to,
value: _value,
executed: false,
confirmations: 0
}));
}

function confirmTransaction(uint _txIndex) public onlyOwner {
Transaction storage transaction = transactions[_txIndex];
require(!transaction.isConfirmed[msg.sender], "Transaction already confirmed");
transaction.isConfirmed[msg.sender] = true;
transaction.confirmations++;
}

function executeTransaction(uint _txIndex) public onlyOwner {
Transaction storage transaction = transactions[_txIndex];
require(transaction.confirmations >= required, "Not enough confirmations");
require(!transaction.executed, "Transaction already executed");

transaction.executed = true;
(bool success, ) = transaction.to.call{value: transaction.value}("");
require(success, "Transaction failed");
}

receive() external payable {}
}

2. Deploying the Smart Contract

To deploy the smart contract, you can use Ethers.js. Below is an example of how to deploy the contract:

        
// deploy.js
const { ethers } = require("ethers");

async function main() {
const provider = new ethers.providers.JsonRpcProvider("https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID");
const wallet = new ethers.Wallet("YOUR_PRIVATE_KEY", provider);

const MultiSigWallet = await ethers.getContractFactory("MultiSigWallet", wallet);
const owners = ["0xOwnerAddress1", "0xOwnerAddress2", "0xOwnerAddress3"];
const required = 2;

const multiSigWallet = await MultiSigWallet.deploy(owners, required);
console.log("MultiSigWallet deployed to:", multiSigWallet.address);
}

main().catch((error) => {
console.error(error);
process.exit (1);
});

3. Interacting with the Multi-Signature Wallet

Once the contract is deployed, you can interact with it using Ethers.js. Below is an example of how to submit, confirm, and execute a transaction:

        
// interact.js
const { ethers } = require("ethers");

async function main() {
const provider = new ethers.providers.JsonRpcProvider("https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID");
const wallet = new ethers.Wallet("YOUR_PRIVATE_KEY", provider);
const multiSigWalletAddress = "DEPLOYED_CONTRACT_ADDRESS";
const multiSigWallet = new ethers.Contract(multiSigWalletAddress, [
"function submitTransaction(address _to, uint _value)",
"function confirmTransaction(uint _txIndex)",
"function executeTransaction(uint _txIndex)"
], wallet);

// Submit a transaction
const txTo = "0xRecipientAddress";
const txValue = ethers.utils.parseEther("0.1");
await multiSigWallet.submitTransaction(txTo, txValue);
console.log("Transaction submitted");

// Confirm the transaction (repeat this for each owner)
await multiSigWallet.confirmTransaction(0);
console.log("Transaction confirmed");

// Execute the transaction after enough confirmations
await multiSigWallet.executeTransaction(0);
console.log("Transaction executed");
}

main().catch((error) => {
console.error(error);
process.exit(1);
});

Conclusion

In this guide, we created a simple multi-signature wallet using Ethers.js. We wrote a smart contract that requires multiple confirmations to execute transactions, deployed it to the Ethereum blockchain, and interacted with it using Ethers.js. This approach enhances security for managing funds, making it a valuable tool for groups or organizations that require shared control over assets.