Dwarves
Memo
Type ESC to close search bar

Multisign wallet

Intro Multisig wallet

Usually, blockchain wallets are generally generated by a unique private key. All assets or transactions are controlled and signed by that private key. That key holder can do anything with that wallet. This is great for individuals, as it ensures privacy and security, as only the owner of the private key has full rights to those assets.

But with an organization, such as assets after IDO, private sale, or common company assets, it is different, if this asset is controlled by a single person, there will be a lot of risk. can happen like:

To solve the above problems, Multisig Wallet was born to minimize those risks.

What is multisig wallet?

In essence Multisig Wallet is a smart contract on the blockchain that allows certain logic to be processed when there are enough required signatures.

Multisig is short for Multi Signature.

A Multisig Wallet has the following properties:

How does it work?

As a simple analogy, we can picture a safe with two locks and two keys. One key is held by Alice and the other is held by Bob. The only way they can open the box is to provide both keys at the same time, so one cannot open the box without the other’s consent.

Basically, funds stored on a multi-signature address can only be accessed using 2 or more signatures. Thus, using a multisig wallet allows users to create an extra layer of security for their funds. But before going any further, it is important to understand the basics of standard Bitcoin addresses, which are based on a single key rather than multiple (single key addresses).

Pros and cons of Multisig Wallet

Everything has two sides, security and convenience are always two opposite sides of each other, so is Multisig Wallet.

Advantages

Defect

In this case, 2FA can be an effective solution to save the backup code for the account on the device, if we lose the device, we can still get the backup code back, which means getting the account back. .

Build Minimum Multisig Wallet

Mutisig smart contract:

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

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

Deployed Multisig Wallets

There is no universal standard for writing Multisig Wallet, but we can refer to the implementation from famous wallets being used in the world to be able to design or inherit our own implementation.

Referrence