How to Create a MultiSig Wallet in Solidity

Posted By : Ashish

Oct 30, 2023

Creating a Multi-Sig Wallet in Solidity

 

A multi-signature (multi-sig) wallet is like a safe that needs multiple keys to open. It's a smart contract that holds cryptocurrency and requires more than one person's approval to make transactions. Today, we'll delve into creating a multi-sig wallet using Hardhat, a popular Ethereum development environment. Creating a multi-sig wallet requires significant expertise in smart contract development

 

Prerequisites

 

  1. A basic understanding of Solidity and Ethereum.
  2. Node.js is installed on your machine.
  3. Hardhat is installed globally using the command: npm install -g hardhat.

 

Setting Up Hardhat

 

  1. Creating a New Project: Run npx hardhat in your terminal and follow the prompts to create a new project.
  2. Installing Dependencies: Inside your project directory, install the necessary npm packages with:
    bash

 

npm install @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers

 

Writing the Multi-Sig Wallet Smart Contract

 

  1. Create a new file called MultiSigWallet.sol in the contracts folder.
  2. Paste the below code

 

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

contract MultiSigWallet {
   event Deposit(address indexed sender, uint amount, uint balance);
   event SubmitTransaction(
       address indexed owner,
       uint indexed txIndex,
       address indexed to,
       uint value,
       bytes data
   );
   event ConfirmTransaction(address indexed owner, uint indexed txIndex);
   event RevokeConfirmation(address indexed owner, uint indexed txIndex);
   event ExecuteTransaction(address indexed owner, uint indexed txIndex);

   address[] public owners;
   mapping(address => bool) public isOwner;
   uint public numConfirmationsRequired;

   struct Transaction {
       address to;
       uint value;
       bytes data;
       bool executed;
       uint numConfirmations;
   }

   // mapping from tx index => owner => bool
   mapping(uint => mapping(address => bool)) public isConfirmed;

   Transaction[] public transactions;

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

   modifier txExists(uint _txIndex) {
       require(_txIndex < transactions.length, "tx does not exist");
       _;
   }

   modifier notExecuted(uint _txIndex) {
       require(!transactions[_txIndex].executed, "tx already executed");
       _;
   }

   modifier notConfirmed(uint _txIndex) {
       require(!isConfirmed[_txIndex][msg.sender], "tx already confirmed");
       _;
   }

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

       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);
       }

       numConfirmationsRequired = _numConfirmationsRequired;
   }

   receive() external payable {
       emit Deposit(msg.sender, msg.value, address(this).balance);
   }

   function submitTransaction(
       address _to,
       uint _value,
       bytes memory _data
   ) public onlyOwner {
       uint txIndex = transactions.length;

       transactions.push(
           Transaction({
               to: _to,
               value: _value,
               data: _data,
               executed: false,
               numConfirmations: 0
           })
       );

       emit SubmitTransaction(msg.sender, txIndex, _to, _value, _data);
   }

   function confirmTransaction(
       uint _txIndex
   ) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) notConfirmed(_txIndex) {
       Transaction storage transaction = transactions[_txIndex];
       transaction.numConfirmations += 1;
       isConfirmed[_txIndex][msg.sender] = true;

       emit ConfirmTransaction(msg.sender, _txIndex);
   }

   function executeTransaction(
       uint _txIndex
   ) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) {
       Transaction storage transaction = transactions[_txIndex];

       require(
           transaction.numConfirmations >= numConfirmationsRequired,
           "cannot execute tx"
       );

       transaction.executed = true;

       (bool success, ) = transaction.to.call{value: transaction.value}(
           transaction.data
       );
       require(success, "tx failed");

       emit ExecuteTransaction(msg.sender, _txIndex);
   }

   function revokeConfirmation(
       uint _txIndex
   ) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) {
       Transaction storage transaction = transactions[_txIndex];

       require(isConfirmed[_txIndex][msg.sender], "tx not confirmed");

       transaction.numConfirmations -= 1;
       isConfirmed[_txIndex][msg.sender] = false;

       emit RevokeConfirmation(msg.sender, _txIndex);
   }

   function getOwners() public view returns (address[] memory) {
       return owners;
   }

   function getTransactionCount() public view returns (uint) {
       return transactions.length;
   }

   function getTransaction(
       uint _txIndex
   )
       public
       view
       returns (
           address to,
           uint value,
           bytes memory data,
           bool executed,
           uint numConfirmations
       )
   {
       Transaction storage transaction = transactions[_txIndex];

       return (
           transaction.to,
           transaction.value,
           transaction.data,
           transaction.executed,
           transaction.numConfirmations
       );
   }
}

 

Compiling Your Smart Contract:

 

  • In your terminal, run npx hardhat compile.

 

Contract Details

 

Contract Setup

 

  • Defined using pragma solidity ^0.8.20; to specify the Solidity compiler version.
  • contract SecureMultiWallet { ... } initiates the contract definition.

 

Event Definitions

 

  • Events such as FundsDeposited, TransactionSubmitted, TransactionConfirmed, ConfirmationRevoked, and TransactionExecuted are defined to emit logs for significant actions within the contract.

 

State Variables

 

  • authorizedUsers is an array to track wallet signatories.
  • isAuthorized is a mapping to quickly verify if an address is authorized.
  • requiredApprovals specifies the number of approvals needed to execute a transaction.
  • pendingTransactions is an array to store all proposed transactions.
  • hasConfirmed is a nested mapping to keep track of approvals per transaction.

 

Struct Definition

 

  • PendingTransaction struct is defined to hold information about each proposed transaction.

 

Modifiers

 

  • onlyAuthorized ensures the function is called by an authorized user.
  • transactionExists checks if the transaction ID exists.
  • notYetExecuted checks if the transaction has not been executed yet.
  • notYetConfirmed checks if the transaction has not already been approved by the caller.

 

Constructor

 

  • The constructor initializes the contract with a list of authorized users and the required number of approvals.

 

Fallback Function

 

  • The receive function allows the contract to accept ether and emits a FundsDeposited event.

 

Transaction Management Functions

 

  • addTransaction: Allows an authorized user to propose a new transaction.
  • approveTransaction: Allows an authorized user to approve a proposed transaction.
  • runTransaction: Allows an authorized user to execute a transaction once the required number of approvals have been met.
  • retractApproval: Allows an authorized user to retract their approval from a proposed transaction.

 

View Functions

 

  • listUsers: Allows anyone to query the list of authorized users.
  • countTransactions: Allows anyone to query the total number of proposed transactions.
  • fetchTransaction: Allows anyone to query details of a specific transaction by its ID.

 

Github: https://github.com/AshishG2/MultiSigSolidity

 

If you are looking for smart contract development or crypto wallet development, connect with our skilled smart contract developers to get started. 

Leave a

Comment

Name is required

Invalid Name

Comment is required

Recaptcha is required.

blog-detail

September 8, 2024 at 02:31 am

Your comment is awaiting moderation.

By using this site, you allow our use of cookies. For more information on the cookies we use and how to delete or block them, please read our cookie notice.

Chat with Us
Telegram Button
Youtube Button
Contact Us

Oodles | Blockchain Development Company

Name is required

Please enter a valid Name

Please enter a valid Phone Number

Please remove URL from text