> For the complete documentation index, see [llms.txt](https://docs.xft.finance/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.xft.finance/learn/multisig/technical-implementation.md).

# Technical Implementation

nonce: A simple counter which increments for each new transaction. Helps prevent replay attacks\
threshold: The number of required confirmations to execute a transaction\
isOwner: A mapping which is used to set and find if an address is an owner.\
ownersArr: An array listing the owner’s addresses

{% code overflow="wrap" expandable="true" %}

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

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

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

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

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

    Transaction[] public transactions;

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

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

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

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

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

        for (uint256 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, uint256 _value, bytes memory _data)
        public
        onlyOwner
    {
        uint256 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(uint256 _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(uint256 _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(uint256 _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 (uint256) {
        return transactions.length;
    }

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

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

{% endcode %}

{% code overflow="wrap" expandable="true" %}

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract TestContract {
    uint256 public i;

    function callMe(uint256 j) public {
        i += j;
    }

    function getData() public pure returns (bytes memory) {
        return abi.encodeWithSignature("callMe(uint256)", 123);
    }
}
```

{% endcode %}

<https://www.cyfrin.io/glossary/multisig-wallet-solidity-code-example>

Submit a transaction\
Approve and revoke approval of pending transactions\
Execute a transaction after enough owners have approved it.

Even without any extensions, the Bitcoin protocol actually does facilitate a weak version of a concept of "*smart contracts*". UTXO in Bitcoin can be owned

<https://bitcoinwiki.org/wiki/Multisignature>

<https://electrum.readthedocs.io/en/latest/multisig.html>

* **nonce**: A simple counter which increments for each new transaction. Helps prevent replay attacks
* **threshold**: The number of required confirmations to execute a transaction
* **isOwner**: A mapping which is used to set and find if an address is an owner.
* **ownersArr**: An array listing the owner’s addresses

### What Multisignature cannot do <a href="#what_multisignature_cannot_do" id="what_multisignature_cannot_do"></a>

You can't set a spending limit; anything on the account can be withdrawn by a single, properly signed transaction.

* It has no time limit; once a transaction is signed, it allows you to withdraw all funds immediately.
* It has no daily limit on the number of transactions: you can create thousands of transactions per minute.
* It has no notifications; you don't get an SMS or email when the account status has changed[<sup>\[6\]</sup>](https://bitcoinwiki.org/wiki/Multisignature#cite_note-6).

[Is it possible to deploy a smart contract using multi-sig? i.e. contract will deploy only when multiple parties approve the tx?](https://ethereum.stackexchange.com/questions/155043/is-it-possible-to-deploy-a-smart-contract-using-multi-sig-i-e-contract-will-de)

It's possible, but note that on **PUBLIC** blockchain, everyone can deploy a SmartContract (if they can afford it). So you can't stop people from deploying code that you don't approve.

For your use-case: You can have your own smart-contract that deploys other contracts. However, inside of this "deployer contract" you can implement mutli-sig functionality/checks. Making sure only whitelisted/group of address is allowed to do that.

#### **Typical Payment Flow**

1. Funds are deposited into a contract-controlled “treasury” (often stablecoins for predictable accounting).
2. Payment conditions are defined in code (milestones, time locks, approvals, delivery confirmations, revenue events).
3. A trigger confirms the condition is satisfied.
4. The contract executes a transfer (single payout, batch payouts, or split payouts).
5. Every step is recorded on-chain for auditability and dispute reduction.

<figure><img src="https://960155864-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVw4YaRvQtCLcdva1K37V%2Fuploads%2FByrGIQwst8yGKejBHa0o%2Fimage.png?alt=media&amp;token=e796839e-b3e1-4a65-96f6-dd94c2b7f5ae" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" %}

```
2 of 3 Multisig Wallet
1. The wallet is created with 3 public addresses listed as signers
2. A transaction is proposed by one signer
3. Other signers review the transaction details
4. Once 2 out of 3 signers approve the transaction executes on chain. If only 1 signer approves nothing happens.
```

{% endcode %}

{% code overflow="wrap" %}

```
MULTISIG ESCROW 
A 3 of 3 multisig is created between signers 1, 2, and 3
The multisig owns two outputs A and B, both size 1 XMR
Signer 1: "I want to withdraw 1 XMR to my address X". 2 and 3 agree
Signer 2 is selected to begin signing the transaction. 2 constructs the transaction sending output A -> X
Signer 3 signs, the transaction now has 2/3 signatures
Signer 1 signs, the transaction has 3/3 signatures and is valid and can be broadcasted to the network
Signer 1 does not broadcast the A -> X transaction. 1 lies and says he could not sign. He claims he got the error This signature was made with stale data.
Signer 1 says "We should do it again, it didn't work"
Signer 1 constructs the transaction sending B -> X
Signer 2 and 3 sign, 3 broadcasts B -> X
Signer 1 broadcasts the withheld A -> X transaction
Signer 1 has drained all the funds!
```

{% endcode %}

{% code overflow="wrap" %}

```
STATE VARIABLES
owners: Array of addresses representing the wallet owners.
isOwner: Mapping to check if an address is an owner.
numConfirmationsRequired: Number of confirmations required to execute a transaction.
transactions: Array of Transaction structs representing the submitted transactions.
isConfirmed: Mapping to track which owners have confirmed a transaction.

EVENTS
Deposit: Logs when ether is deposited into the wallet.
SubmitTransaction: Logs when a new transaction is submitted.
ConfirmTransaction: Logs when an owner confirms a transaction.
RevokeTransaction: Logs when an owner revokes their confirmation of a transaction.
ExecuteTransaction: Logs when a transaction is executed.

MODIFIERS
onlyOwner: Ensures that only an owner can call certain functions.
txExists: Ensures the transaction exists.
notExecuted: Ensures the transaction has not been executed.
notConfirmed: Ensures the transaction has not been confirmed by the caller.

STRUCTS
Transaction: Represents a transaction with details such as recipient address, value, data, execution status, and number of confirmations.

Constructor
Initializes the contract with the provided owners and the required number of confirmations.
submitTransaction(address _to, uint256 _value, bytes memory _data): Allows an owner to submit a new transaction.
confirmTransaction(uint256 _txIndex): Allows an owner to confirm a submitted transaction.
revokeConfirmation(uint256 _txIndex): Allows an owner to revoke their confirmation of a transaction.
executeTransaction(uint256 _txIndex): Executes a transaction if the required number of confirmations is met.
depositETH(): Allows anyone to deposit ether into the wallet.
receive(): Fallback function to receive ether.
getOwners(): Returns the list of wallet owners.
getTransactionCount(): Returns the total number of transactions.
getTransaction(uint256 _txIndex): Returns the details of a specific transaction.
getBalance(): Returns the current balance of the wallet.
```

{% endcode %}

<figure><img src="https://960155864-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVw4YaRvQtCLcdva1K37V%2Fuploads%2FO6KQDoSVUWtTGRfggicc%2Fimage.png?alt=media&amp;token=d4b04adf-327e-4d0c-842a-3d6bd167c3d4" alt=""><figcaption></figcaption></figure>

* ***Hord protocol*** implementation of a simple multisig: HordCongress.sol contract <https://github.com/hord/smart-contracts/blob/master/contracts/governance/HordCongress.sol>
* ***Compound protocol*** implementation of governance contract: GovernorAlpha.sol contract <https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/GovernorAlpha.sol>

\---

RESOURCES

[Is it possible to deploy a smart contract using multi-sig? i.e. contract will deploy only when multiple parties approve the tx?](https://ethereum.stackexchange.com/questions/155043/is-it-possible-to-deploy-a-smart-contract-using-multi-sig-i-e-contract-will-de)

It's possible, but note that on **PUBLIC** blockchain, everyone can deploy a SmartContract (if they can afford it). So you can't stop people from deploying code that you don't approve.

For your use-case: You can have your own smart-contract that deploys other contracts. However, inside of this "deployer contract" you can implement mutli-sig functionality/checks. Making sure only whitelisted/group of address is allowed to do that.

\
[\
https://dev.to/sumana10l/building-a-secure-multisig-wallet-on-ethereum-38ne\
https://www.coinbase.com/learn/wallet/what-is-a-multi-signature-multi-sig-wallet\
https://safe.global/\
https://www.bitgo.com/resources/blog/multisig-wallet-transactions/\
https://www.deltadeltaandmoredeltas.com/escrow-transactions-using-multisig/\
https://coesecurity.com/darknet-escrow-exit-scam-risk/\
https://github.com/near/core-contracts/tree/master/multisig\
https://docs.getmonero.org/multisignature/#spending-concerns\
https://docs.getmonero.org/multisignature/\
https://ethereum.org/whitepaper/\
https://www.bitgo.com/resources/blog/multi-sig-wallets-for-institutions/\
https://blog.tetrapolar.com/tutorial-how-to-complete-a-bitcoin-escrow-deal-buyers-guide/\
https://www.paxos.com/blog/simple-multisig-how-it-works-and-why-its-awesome\
https://www.paxos.com/blog/simple-multisig-how-it-works-and-why-its-awesome\
https://ethereum.stackexchange.com/questions/9627/deploying-a-contract-from-a-multisig-wallet\
https://mavaliseecolo.com/safe-wallet-dispute-resolution-using-multisig-timelock-as-escrow-for-p2p-transactions/\
https://gwern.net/doc/bitcoin/nashx/2014-zimbeck.pdf <br>](<&#xD;&#xA;https://dev.to/sumana10l/building-a-secure-multisig-wallet-on-ethereum-38ne&#xD;&#xA;https://www.coinbase.com/learn/wallet/what-is-a-multi-signature-multi-sig-wallet&#xD;&#xA;https://safe.global/&#xD;&#xA;https://www.bitgo.com/resources/blog/multisig-wallet-transactions/&#xD;&#xA;https://www.deltadeltaandmoredeltas.com/escrow-transactions-using-multisig/&#xD;&#xA;https://coesecurity.com/darknet-escrow-exit-scam-risk/&#xD;&#xA;https://github.com/near/core-contracts/tree/master/multisig&#xD;&#xA;https://docs.getmonero.org/multisignature/#spending-concerns&#xD;&#xA;https://docs.getmonero.org/multisignature/&#xD;&#xA;https://ethereum.org/whitepaper/&#xD;&#xA;https://www.bitgo.com/resources/blog/multi-sig-wallets-for-institutions/&#xD;&#xA;https://blog.tetrapolar.com/tutorial-how-to-complete-a-bitcoin-escrow-deal-buyers-guide/&#xD;&#xA;https://www.paxos.com/blog/simple-multisig-how-it-works-and-why-its-awesome&#xD;&#xA;https://www.paxos.com/blog/simple-multisig-how-it-works-and-why-its-awesome&#xD;&#xA;https://ethereum.stackexchange.com/questions/9627/deploying-a-contract-from-a-multisig-wallet&#xD;&#xA;https://mavaliseecolo.com/safe-wallet-dispute-resolution-using-multisig-timelock-as-escrow-for-p2p-transactions/&#xD;&#xA;https://gwern.net/doc/bitcoin/nashx/2014-zimbeck.pdf&#xD;&#xA;>)

<https://gwern.net/doc/bitcoin/nashx/2014-zimbeck.pdf>

Is it possible to deploy a smart contract using multi-sig? i.e. contract will deploy only when multiple parties approve the tx?

<https://medium.com/@krayon_digital/multisig-wallets-explained-bef603c2bd42>

<https://github.com/hyperliquid-dex/hyperliquid-python-sdk/tree/master>

<https://bitcoinwiki.org/wiki/Multisignature>
