Hardhat

What is the role of Hardhat in a multisignature wallet project


Hardhat is a powerful development environment for Ethereum that facilitates the creation, testing, and deployment of smart contracts. In a multi-signature wallet project, Hardhat plays a crucial role in managing the development lifecycle, including writing, testing, and deploying the multi-signature wallet smart contract. This guide will explain the role of Hardhat in such a project and provide sample code to demonstrate its usage.

What is a Multi-Signature Wallet?

A multi-signature wallet (or multi-sig wallet) requires multiple signatures (or approvals) to execute transactions. This enhances security by ensuring that no single individual has control over the wallet's funds. Multi-sig wallets are commonly used by organizations and DAOs (Decentralized Autonomous Organizations) to manage funds collectively.

Setting Up Your Hardhat Project

To start, you need to set up a Hardhat project. If you haven't done this yet, follow these steps:

mkdir multi-sig-wallet
cd multi-sig-wallet
npm init --yes
npm install --save-dev hardhat
npx hardhat

When prompted, select `Create a basic sample project` and follow the instructions to set up the project.

Creating the Multi-Signature Wallet Contract

Create a new file in the contracts directory named MultiSigWallet.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MultiSigWallet {
    event Deposit(address indexed sender, uint amount);
    event Submit(uint indexed txIndex, address indexed owner, address indexed to, uint amount, bytes data);
    event Confirm(uint indexed txIndex, address indexed owner);
    event Revoke(uint indexed txIndex, address indexed owner);
    event Execute(uint indexed txIndex);
    address[] public owners;
    mapping(address => bool) public isOwner;
    struct Transaction {
        address to;
        uint amount;
        bytes data;
        bool executed;
        uint confirmations;
    }
    Transaction[] public transactions;
    mapping(uint => mapping(address => bool)) public isConfirmed;
    modifier onlyOwner() {
        require(isOwner[msg.sender], `Not an owner`);
        _;
    }
    constructor(address[] memory _owners) {
        require(_owners.length > 0, `No owners`);
        for (uint i = 0; i < _owners.length; i++) {
            require(!isOwner[_owners[i]], `Owner is not unique`);
            isOwner[_owners[i]] = true;
            owners.push(_owners[i]);
        }
    }
    receive() external payable {
        emit Deposit(msg.sender, msg.value);
    }
    function submitTransaction(address _to, uint _amount, bytes memory _data) public onlyOwner {
        uint txIndex = transactions.length;
        transactions.push(Transaction({
            to: _to,
            amount: _amount,
            data: _data,
            executed: false,
            confirmations: 0
        }));
        emit Submit(txIndex, msg.sender, _to, _amount, _data);
    }
    function confirmTransaction(uint _txIndex) public onlyOwner {
        require(!isConfirmed[_txIndex][msg.sender], `Transaction already confirmed`);
        isConfirmed[_txIndex][msg.sender] = true;
        transactions[_txIndex].confirmations += 1;
        emit Confirm(_txIndex, msg.sender);
    }
    function executeTransaction(uint _txIndex) public onlyOwner {
        Transaction storage transaction = transactions[_txIndex];
        require(transaction.confirmations > 1, `Not enough confirmations`);
        require(!transaction.executed, `Transaction already executed`);
        transaction.executed = true;
        (bool success, ) = transaction.to.call{value: transaction.amount}(transaction.data);
        require(success, `Transaction failed`);
        emit Execute(_txIndex);
    }
}

Writing Tests for the Multi-Signature Wallet

Hardhat provides a testing framework to ensure that your smart contracts behave as expected. Create a new test file in the test directory named MultiSigWallet.test.js:

const { expect } = require(`chai`);
describe(`MultiSigWallet`, function () {
    let MultiSigWallet;
    let multiSigWallet;
    let owner1, owner2, owner3, addr1;
    beforeEach(async function () {
        [owner1, owner2, owner3, addr1] = await ethers.getSigners();
        const owners = [owner1.address, owner2.address, owner3.address];
        MultiSigWallet = await ethers.getContractFactory(`MultiSigWallet`);
 multiSigWallet = await MultiSigWallet.deploy(owners);
        await multiSigWallet.deployed();
    });
    it(`should allow owners to submit transactions`, async function () {
        await multiSigWallet.submitTransaction(addr1.address, ethers.utils.parseEther(`1`), `0x`);
        const transaction = await multiSigWallet.transactions(0);
        expect(transaction.to).to.equal(addr1.address);
        expect(transaction.amount).to.equal(ethers.utils.parseEther(`1`));
    });
    it(`should allow owners to confirm transactions`, async function () {
        await multiSigWallet.submitTransaction(addr1.address, ethers.utils.parseEther(`1`), `0x`);
        await multiSigWallet.connect(owner1).confirmTransaction(0);
        const transaction = await multiSigWallet.transactions(0);
        expect(transaction.confirmations).to.equal(1);
    });
    it(`should execute transaction with enough confirmations`, async function () {
        await multiSigWallet.submitTransaction(addr1.address, ethers.utils.parseEther(`1`), `0x`);
        await multiSigWallet.connect(owner1).confirmTransaction(0);
        await multiSigWallet.connect(owner2).confirmTransaction(0);
        await multiSigWallet.executeTransaction(0);
        const balance = await ethers.provider.getBalance(addr1.address);
        expect(balance).to.equal(ethers.utils.parseEther(`1`));
    });
});

Deploying the Multi-Signature Wallet

To deploy the multi-signature wallet, create a new deployment script in the scripts directory named deploy.js:

async function main() {
    const [owner1, owner2, owner3] = await ethers.getSigners();
    const owners = [owner1.address, owner2.address, owner3.address];
    const MultiSigWallet = await ethers.getContractFactory(`MultiSigWallet`);
    const multiSigWallet = await MultiSigWallet.deploy(owners);
    await multiSigWallet.deployed();
    console.log(`MultiSigWallet deployed to:`, multiSigWallet.address);
}
main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error);
        process.exit(1);
    });

Conclusion

In this guide, we explored the role of Hardhat in developing a multi-signature wallet project. We set up a Hardhat project, created a multi-signature wallet contract, wrote tests to ensure its functionality, and deployed the contract. Hardhat's powerful features streamline the development process, making it easier to build secure and reliable smart contracts.

Written by Surfside Media

Senior Full Stack Developer specializing in Web Technologies.