MultiSigWallet Solidity Contract Structure

Overview

The MultiSigWallet Solidity contract is designed to manage a complex system of accounts and signers with various levels of security and recovery options. The contract is structured to ensure secure transactions, daily spending limits, and multiple recovery methods, including social and hardware-based recovery.

Contract Components

1. Signers

The contract supports multiple types of signers, each with different roles and responsibilities:

2. Sub-Accounts

The contract manages funds through various sub-accounts:

3. 2+ Factor Authentication

The contract enforces two or more factor authentication for large transactions. For transactions exceeding a certain threshold, at least two types of signers (e.g., passkey + hardware) are required to authorize the transaction.

4. Main Account

The main account is the root of trust within the contract. It has the authority to manage all sub-accounts, add or remove signers, and initiate recovery procedures.

5. Recovery Mechanisms

The contract includes several recovery mechanisms to ensure the security and accessibility of the main account:

Solidity Contract Code

Below is the complete Solidity contract that implements the structure described above:


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

contract MultiSigWallet {

    struct Signer {
        bool passkey; // Accessible via app and backed up to iCloud
        bool hardware; // Yubikey or other hardware key
        bool other; // Any other type of signer
    }

    struct SubAccount {
        string name;
        uint256 balance;
        bool isBusiness;
    }

    address public mainAccount;
    mapping(address => Signer) public signers;
    mapping(string => SubAccount) public subAccounts;
    address public recoveryAddress;
    bool public recoveryInitiated;
    uint256 public recoveryTimestamp;
    uint256 public constant RECOVERY_WAIT_TIME = 7 days;

    enum RecoveryMethod { None, Coinbase, Social, Backup }
    RecoveryMethod public recoveryMethod;
    address[] public socialRecoverySigners;
    uint256 public requiredSocialSigners = 2;

    uint256 public dailySpendingLimit = 1 ether;
    mapping(address => uint256) public dailySpending;

    string constant public DAILY_SPENDING = "DailySpending";
    string constant public SAVING = "Saving";
    string constant public OTHER = "Other";

    event TransactionExecuted(address indexed signer, string subAccountName, uint256 amount, address to);
    event RecoveryInitiated(address indexed initiator);
    event RecoveryExecuted(address indexed newOwner);
    event SubAccountAdded(string name, bool isBusiness);
    event FundsDeposited(string subAccountName, uint256 amount);
    event FundsWithdrawn(string subAccountName, uint256 amount, address to);
    event DailySpendingUpdated(address indexed signer, uint256 newSpendingAmount);

    modifier onlyMainAccount() {
        require(msg.sender == mainAccount, "Only main account can call this function");
        _;
    }

    modifier onlyPasskey() {
        require(signers[msg.sender].passkey, "Only passkey signer can call this function");
        _;
    }

    modifier onlyHardware() {
        require(signers[msg.sender].hardware, "Only hardware signer can call this function");
        _;
    }

    modifier onlySigner() {
        require(signers[msg.sender].passkey || signers[msg.sender].hardware || signers[msg.sender].other, "Only signers can call this function");
        _;
    }

    modifier onlyDuringRecovery() {
        require(recoveryInitiated && block.timestamp >= recoveryTimestamp + RECOVERY_WAIT_TIME, "Recovery not yet finalized");
        _;
    }

    constructor(
        address _mainAccount,
        address _recoveryAddress,
        address[] memory _signers,
        bool[] memory _passkey,
        bool[] memory _hardware,
        bool[] memory _other,
        RecoveryMethod _recoveryMethod
    ) {
        mainAccount = _mainAccount;
        recoveryAddress = _recoveryAddress;
        recoveryMethod = _recoveryMethod;
        for (uint i = 0; i < _signers.length; i++) {
            signers[_signers[i]] = Signer(_passkey[i], _hardware[i], _other[i]);
        }

        subAccounts[DAILY_SPENDING] = SubAccount(DAILY_SPENDING, 0, false);
        subAccounts[SAVING] = SubAccount(SAVING, 0, false);
        subAccounts[OTHER] = SubAccount(OTHER, 0, true);
    }

    function addSubAccount(string memory name, bool isBusiness) external onlyMainAccount {
        subAccounts[name] = SubAccount(name, 0, isBusiness);
        emit SubAccountAdded(name, isBusiness);
    }

    function depositFunds(string memory subAccountName) external payable {
        require(bytes(subAccountName).length > 0, "SubAccount name is required");
        subAccounts[subAccountName].balance += msg.value;
        emit FundsDeposited(subAccountName, msg.value);
    }

    function withdrawFunds(string memory subAccountName, uint256 amount, address to) external onlySigner {
        require(subAccounts[subAccountName].balance >= amount, "Insufficient balance in sub-account");
        subAccounts[subAccountName].balance -= amount;
        payable(to).transfer(amount);
        emit FundsWithdrawn(subAccountName, amount, to);
    }

    function executeTransaction(string memory subAccountName, uint256 amount, address to) external onlySigner {
        require(subAccounts[subAccountName].balance >= amount, "Insufficient balance in sub-account");

        if (keccak256(bytes(subAccountName)) == keccak256(bytes(DAILY_SPENDING))) {
            require(dailySpending[msg.sender] + amount <= dailySpendingLimit, "Exceeds daily spending limit");
            dailySpending[msg.sender] += amount;
            emit DailySpendingUpdated(msg.sender, dailySpending[msg.sender]);
        } else if (amount > dailySpendingLimit) {
            require(signers[msg.sender].passkey && (signers[msg.sender].hardware || signers[msg.sender].other), "Large transactions require 2+ factor authentication");
        }

        subAccounts[subAccountName].balance -= amount;
        payable(to).transfer(amount);
        emit TransactionExecuted(msg.sender, subAccountName, amount, to);
    }

    function initiateRecovery() external {
        require(msg.sender == recoveryAddress, "Only recovery address can initiate recovery");
        recoveryInitiated = true;
        recoveryTimestamp = block.timestamp;
        emit RecoveryInitiated(msg.sender);
    }

    function executeRecovery() external onlyDuringRecovery {
        require(msg.sender == recoveryAddress, "Only recovery address can execute recovery");

        if (recoveryMethod == RecoveryMethod.Coinbase) {
            // Implement Coinbase-specific recovery steps (mock logic here)
        } else if (recoveryMethod == RecoveryMethod.Social) {
            require(verifySocialRecovery(), "Social recovery not fulfilled");
        } else if (recoveryMethod == RecoveryMethod.Backup) {
            // Implement Backup-specific recovery steps (mock logic here)
        }

        mainAccount = recoveryAddress;
        recoveryInitiated = false;
        emit RecoveryExecuted(mainAccount);
    }

    function verifySocialRecovery() internal view returns (bool) {
        uint256 confirmations = 0;
        for (uint256 i = 0; i < socialRecoverySigners.length; i++) {
            if (signers[socialRecoverySigners[i]].passkey || signers[socialRecoverySigners[i]].hardware || signers[socialRecoverySigners[i]].other) {
                confirmations++;
            }
            if (confirmations >= requiredSocialSigners) {
                return true;
            }
        }
        return false;
    }

    function addSigner(address newSigner, bool passkey, bool hardware, bool other) external onlyMainAccount {
        signers[newSigner] = Signer(passkey, hardware, other);
    }

    function removeSigner(address signer) external onlyMainAccount {
        delete signers[signer];
    }

    function setRecoveryMethod(RecoveryMethod method) external onlyMainAccount {
        recoveryMethod = method;
    }

    function setDailySpendingLimit(uint256 limit) external onlyMainAccount {
        dailySpendingLimit = limit;
    }

    function addSocialRecoverySigner(address signer) external onlyMainAccount {
        socialRecoverySigners.push(signer);
    }

    function removeSocialRecoverySigner(address signer) external onlyMainAccount {
        for (uint256 i = 0; i < socialRecoverySigners.length; i++) {
            if (socialRecoverySigners[i] == signer) {
                socialRecoverySigners[i] = socialRecoverySigners[socialRecoverySigners.length - 1];
                socialRecoverySigners.pop();
                break;
            }
        }
    }
}
            

This Solidity contract is a comprehensive implementation of the MultiSigWallet structure, covering all aspects of the diagram, including signers, sub-accounts, multi-factor authentication, and recovery methods. Copy and paste the above code into your Solidity development environment to use or modify the contract.