Project - Multisig Wallet

Here’s my crack at the Multisig Wallet after watching the first video. My implementation allows for any number of owners, but a limited number of paralell proposals. I found that in order to prevent the same owner approving the same transaction multiple times I have to keep track of the owners who already signed. This requires a mapping or a dynamic list. Neither can be assigned in dynamically (in a function), therefore I needed to create a constant (can be arbitrarily large if the deployer has the gas for it) number of mappings to keep track of the signatories to a proposal. I would appreciate any feedback from either @thecil or others.

pragma solidity 0.8.7;
pragma abicoder v2;

contract Pater{
    address internal creator; //Good idea to not make the public directly
    
    constructor(){
        // This way the contract deployer is always the owner
        creator = msg.sender;
    }
    modifier onlyCreator{
        require(msg.sender==creator, "Access Denied");
        _; // In practice: Run the function; Compiler code in practice it marks the point of editing
    }
}

contract Filii is Pater{
    mapping(address => bool) internal ownersMap;    // Stores if a given addess is an owner
    mapping(address => uint) internal ownersIdMap;  // Stores the position of an owner in the ownersList
    address[] internal ownersList;                  // The definitive list of owners
    uint internal ownersNum = 0;                    // Total number of owners
    uint internal reqNum = 0;
    
    modifier onlyOwner{
        require(ownersMap[msg.sender], "Only Owners can use this function.");
        _; // In practice: Run the function; Compiler code in practice it marks the point of editing
    }
    
    function addOwner(address _newOwner) public onlyCreator {
        require(!ownersMap[_newOwner],"Father, this one is already your son.");
        ownersMap[_newOwner] = true;
        ownersIdMap[_newOwner] = ownersList.length;
        ownersList.push(_newOwner);
        ownersNum += 1;
    }
    function removeOwner(address _fallenOwner) public onlyCreator {
        require(ownersMap[_fallenOwner],"Father, this one is no son of yours, You can't banish him.");
        ownersMap[_fallenOwner] = false;
        delete ownersList[ownersIdMap[_fallenOwner]];
        ownersNum -= 1;
    }
    function setRequired(uint _toSet) public onlyCreator {
        require(_toSet<=ownersNum,"Father, not even angels can agree more than unanimously. Do not require that which is impossible.");
        reqNum = _toSet;
    }
    function queryOwners() public view returns(address[] memory) {
        return(ownersList);
    }
}

contract Multisig is Filii{
    struct Request {
        address payable recipient;
        uint amount;
        uint numSupporter;
        uint RqId;
        bool completed;
        //address[] supporters;
    }
    uint constant maxParallelProposals = 30;                                             // Maximum number of paralel proposals with support in the contract
    mapping(address => bool)[maxParallelProposals] private RequestsSupporters;  // A predef. array of mappings. Each verify if an address supports the proposal
    //propSlotsInuse = 0; // This may not be needeed
    mapping(uint => uint) private RqId_to_PropSlot;                             // mapping of Request Ids to the supported proposal slots
    uint[] freeSlots;                                                           // Array populated with Ids of the currently free proposal slots
    Request[] private RequestsPending;                                          // An array of all requests (pending and completed)
    
    function generateSeries(uint _num) private {
        uint i=0;
        for(i = 0; i < _num; i++){
            freeSlots.push(i);
        }
    }
    constructor(){
        generateSeries(maxParallelProposals);
    }

    
    event depositDone(uint amount, address indexed AddedTo);
    
    function deposit() public payable returns(uint){
        emit depositDone(msg.value, msg.sender);
        return address(this).balance;
    }
    
    function payout(address payable _to, uint _amount) private {
        require(_amount <= address(this).balance,"The contract does not currently hold sufficient balance to fund this request. Repeat attempt when sufficient balance is available.");
        _to.transfer(_amount);
    }
    
    function assignPropSlot(uint _RqId) private {
        uint length=freeSlots.length;
        require(length>0,"No free slots left in the pending transactions queue. Try again once some of the pendings transactions were completed.");
        RqId_to_PropSlot[_RqId] = freeSlots[length-1];
        freeSlots.pop();
    }
    
    function createRequest(address payable _to, uint _amount) public onlyOwner{
        uint _RqId = RequestsPending.length;
        if (reqNum <= 1) {
            payout(_to, _amount);
            RequestsPending.push(Request(_to,_amount,1,_RqId,true));
        }
        else {
            RequestsPending.push(Request(_to,_amount,1,_RqId,false));
            assignPropSlot(_RqId);
            RequestsSupporters[RqId_to_PropSlot[_RqId]][msg.sender]=true;
        }
        
    }
    function clearPropSlot(uint _PropSlot) private{
        uint i;
        for (i=0; i< ownersList.length;i++){
            RequestsSupporters[_PropSlot][ownersList[i]] = false;
        }
    }
    
    function approveRequest(uint _RqId) public onlyOwner {
        uint PropSlot = RqId_to_PropSlot[_RqId];
        require(!RequestsSupporters[PropSlot][msg.sender],"You have already supported this proposal, no further action is required."); // For a successful approval, the owner cannot have laready supported the proposal
        require(!RequestsPending[_RqId].completed,"This request has been already completed. No further approvals are required or possible.");
        //if (RequestsSupporters[PropSlot][msg.sender] == false) {
        //   RequestsSupporters[PropSlot][msg.sender] = true;
        //    RequestsPending[_RqId].numSupporter+=1;
        //}
        RequestsSupporters[PropSlot][msg.sender] = true;
        RequestsPending[_RqId].numSupporter+=1;
        if (RequestsPending[_RqId].numSupporter >= reqNum) {
            payout(RequestsPending[_RqId].recipient, RequestsPending[_RqId].amount);
            RequestsPending[_RqId].completed = true;
            // popping free slot back in freePropslots
            clearPropSlot(PropSlot);
            freeSlots.push(PropSlot);
        }
    }
    
    function queryPendingRequests() public view returns(Request[] memory){
        return RequestsPending;
    }
}

Hi! Nice solution. I’d like to give some feedback - I have just finished my version.

mapping(address => mapping(uint => bool)) approvals;
I like this a lot. I wasn’t able to figure it out and had to implement a fixed array of (mapping (address => bool))s for tracking approvals. Elegant solution.

modifier onlyOwners(){
        bool owner = false;
        for (uint i=0; i < owners.length; i++){
            if(owners[i] == msg.sender){
                owner=true;
            }
        }
        require(owner == true,"You are not an owner");
        _;
        
    }

I was able to a mapping like this to simplify tracking the owners:

mapping(address => bool) internal ownersMap;    // Stores if a given addess is an owner
...
 modifier onlyOwner{
        require(ownersMap[msg.sender], "Only Owners can use this function.");
        _;
    }

As far as I understood the contract act as some kind of fund aggregator. Anybody can donate, but only the assigned ‘owners’ can initiate a payout. Therefore, they should be able to pay out more than they put in (there needs to be no balance).

function getTransferRequests() public view returns (uint){
        return transferRequests.length;
    }

You can return the entire array of structs if you do it like this (and have the pragma abicoder v2; line at the top):

function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }

Hope You don’t mind me nosing in on you business. BTW I’m in on cryptorevolution idea. Drop me a note if you want to build something together.

pragma solidity 0.7.5;
pragma abicoder v2; //  able to return structs


contract Fund {
    uint balance;
    
    struct owner {
        address _address;
        uint _vote; // if vote == 1, confirm send, if vote==0 no confirm send
    }
    owner[] owners; 
    
    event depositDone(uint amount);   
    
    modifier onlyOwners {
        require((msg.sender == owners[0]._address) || (msg.sender == owners[1]._address) || (msg.sender == owners[2]._address));
        _;
    }
    
    modifier enough_votes {
        require(_get_total_votes()>1);
        _;
    }
    
    constructor(address _address_2, address _address_3, uint _initial_vote){
        require((_initial_vote==0) || (_initial_vote==1));
        owners.push(owner(msg.sender, _initial_vote));
        owners.push(owner(_address_2, _initial_vote));
        owners.push(owner(_address_3, _initial_vote));        
    }
    
    function get_owner(uint _index) public view returns (owner memory ) {
        return owners[_index];
    }

    function deposit() public payable onlyOwners returns (uint) {
        balance+=msg.value;
        emit depositDone(msg.value);
        return balance;
    }
    
    function getBalance() public view returns (uint) {
        return balance;
    }
    
    function vote(uint __vote) public onlyOwners returns (owner memory) {
        // after finding owner's index, put up vote
        require((__vote==0) || (__vote==1));
        uint index = _find_owner_index(msg.sender);
        owners[index]._vote=__vote;
        return owners[index];
    }
    
    function withdraw(uint _amount) public enough_votes onlyOwners returns (uint) {
        balance-=_amount;
        return balance;
    }
    
    function _find_owner_index(address __address) private view returns (uint) {
        if (owners[0]._address==__address) {
            return 0;
        }
        if (owners[1]._address==__address) {
            return 1;
        }
        else {
            return 2;
        }
        
    }
    
    function _get_total_votes() private view returns (uint) {
        uint total_votes;
        for (uint i=0;i<3;i++){
            total_votes+=owners[i]._vote;
        }
        return total_votes;
    }

}
1 Like
pragma solidity 0.7.5;
pragma abicoder v2;

contract multiSig {
    
    mapping(address => uint) Balances;
    uint approval_limit;
    uint sumVotes;
    Owner[] owners;
    struct Owner {
        address ad;
        uint approval;  // 1 for yes ; 0 for no
    }
    constructor (address _ad1,address _ad2,address _ad3,uint _approval_limit){
        require(_approval_limit <= 3);
        owners.push(Owner(_ad1,0));     // set default to 0 which is no
        owners.push(Owner(_ad2,0));
        owners.push(Owner(_ad3,0));
        approval_limit = _approval_limit;
    }
    
    //anyone can deposit to the contract
    function deposit() public payable{
        Balances[msg.sender] += msg.value;
    }
    // check a user's balance
    function getBalance() public view returns(uint){
        return Balances[msg.sender];
    }
    
    //check if requester is in owners list;requester must be a owner
    function checkRequester(address requester) private view returns(bool){
        for(uint i=0;i<owners.length;i++){
            if(requester == owners[i].ad){
                return true;
            }
        }
        return false;
    }
    //check if total number of votes is larger than approval_limit
    function checkVote(uint v1,uint v2,uint v3)public returns(bool){
        sumVotes += v1; owners[0].approval = v1;
        sumVotes += v2; owners[1].approval = v2;
        sumVotes += v3; owners[2].approval = v3;
        
        if(sumVotes >= approval_limit){
            return true;
        }
        return false;
        
    }
    //request to transfer 
    function transfer(uint amount,address payable recipient) public{
        require(checkRequester(msg.sender));   // check if requester is one of owners before transferring
        require(checkVote(owners[0].approval,owners[1].approval,owners[2].approval));
        recipient.transfer(amount);
        Balances[msg.sender] -= amount;
        Balances[recipient] += amount;
        
    }
    
}
1 Like

pragma solidity 0.8.9;

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

}

1 Like

For some reason the list doesnt print anything but it works if you know the transaction id

pragma solidity 0.8.7;


contract multisig{
    struct transfer{
        uint id;
        address payable payTo;
        uint amount;
        address[] approvals;
    }
    
    mapping (address => bool) public owners;
    uint approvalLimit;
    transfer[] transfers;
    uint[] idList;
    modifier isOwner{
        require(owners[msg.sender]);
        _;
    }
    
    constructor(address[] memory _owners, uint _approvalLimit){
        
        for (uint i=0;i<_owners.length;i++){
            owners[_owners[i]]=true;
        }
       approvalLimit=_approvalLimit;
    }
    
    function requestTransfer(address payable _payTo,uint _amount)public isOwner{
        address[] memory emptyAddressList;
        transfers.push(transfer(transfers.length,_payTo,_amount,emptyAddressList));
    }
    
    function approveTransfer(uint _id) public isOwner {
        bool unapproved=true;
        for (uint i=0;i<transfers[_id].approvals.length;i++){
            if (transfers[_id].approvals[i]==msg.sender){
                unapproved=false;
            }
        }
        if (unapproved){
            transfers[_id].approvals.push(msg.sender);
        }
        if (transfers[_id].approvals.length>=approvalLimit){
            executeTransfer(transfers[_id].payTo,transfers[_id].amount);
        }
    }
    
    function executeTransfer(address payable payTo,uint amount) private {
        payTo.transfer(amount);
    }
    
    function listRequests() public isOwner returns(uint[] memory){
        delete idList;
        for (uint i=0;i<transfers.length;i++){
            idList.push(transfers[i].id);
        }
        return idList;
        
    }
    
    function deposit() public payable returns(uint){
        return(address(this).balance);
    }
    
    
}
1 Like

Hi everyone,

I have an error in the first line:
“parserError: expected identifier but got public address[ ] public owners”.

pragma solidity 0.7.5;
pragma abicoder v2;

address[] public owners;      //  ( Here's the error )
uint public required;
uint public transactionCount;

mapping (address => bool) public isOwner;
mapping (uint => Transaction) public transactions;
mapping (uint => mapping( address => bool)) public confirmations;

Can someone help me, please?

1 Like

you need to wrap your code into a contract. change your code to the example i provided below

pragma solidity 0.7.5;
pragma abicoder v2;


contract MultiSig {

    address[] public owners;      //  ( Here's the error )
    uint public required;
    uint public transactionCount;

    mapping (address => bool) public isOwner;
    mapping (uint => Transaction) public transactions;
    mapping (uint => mapping( address => bool)) public confirmations;
}

Here is my finished project. I did end up watching all of the videos, but I also spent a ton of time studying other material to understand all of the concepts in this course. I also added some improvements to this wallet, which was hinted at in the last video.

Improvements:

  1. Added a nested loop to check for duplicate addresses entered in the constructor

  2. Converted wei to Ether.
    -The user now can input amounts in ether, not wei(1 instead of 1000000000000000000)

  3. There is now a balance function
    -The user can get the balance of the contract and the balance
    -The balance also updates after the funds have been withdrawn from the balance

  4. The user is not allowed to create a transfer that is bigger than the balance of the wallet

Remix link: https://remix.ethereum.org/#optimize=false&runs=200&evmVersion=null&version=soljson-v0.7.5+commit.eb77ed08.js

GitHub: https://github.com/sbelka-1703/Improved-MultiSig-Wallet

Code:

pragma solidity 0.7.5;
pragma abicoder v2;


//["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"]

contract Wallet {
    
     constructor(address[] memory _owners, uint _limit) {
         
        owners = _owners;
        limit = _limit;
        
       
       bool duplicate = false;
       //nested loop to check for duplicate addresses 
       for(uint i = 0; i < owners.length;i++) {
         for(uint j = 0; j < owners.length;j++) {
            if(i != j) {
            if(owners[i] == owners[j]){
                duplicate = true;
             break;
        }
            }
         }
         if(duplicate){
            break;
         }
        else{
         duplicate = false;
            }
      }
          
      
      require(duplicate == false, 'Duplicate addresses entered, every address has to be unique!');
      
         
    }
    
   
    address[] public owners;
    uint limit;
    
    uint totalContractBalance = 0;
    
    struct Transfer{
        uint amount;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }

    event TransferRequestCreated(uint _id, uint _amount, address _initiator, address _receiver);
    event ApprovalReceived(uint _id, uint _approvals, address _approver);
    event TransferApproved(uint _id);

    Transfer[] transferRequests;
    
    
    
    mapping(address => mapping(uint => bool)) approvals;
    mapping(address => uint) balance; 
    
    
    modifier onlyOwners(){
        bool owner = false;

        for(uint i = 0; i < owners.length; i++){
            if (owners[i] == msg.sender){
                owner = true;
            }
        }

        require(owner == true);
        _;
    }
   
   
    function getBalance() public view returns(uint){
        
        return totalContractBalance / 1e18;
    }

  
    function deposit() public payable  {
        totalContractBalance = totalContractBalance + msg.value;
    }
    
   
    function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
        require(_amount*1e18 <= totalContractBalance, "Insufficent balance, try lower amount");

        emit TransferRequestCreated(transferRequests.length, _amount, msg.sender, _receiver); 

        transferRequests.push(Transfer(_amount*1e18, _receiver, 0, false, transferRequests.length));

        
        
    }
    
    
    function approve(uint _id) public onlyOwners {
        require(approvals[msg.sender][_id] == false);
        require(transferRequests[_id].hasBeenSent == false);

        
 
        approvals[msg.sender][_id] = true;
        transferRequests[_id].approvals++;

        emit ApprovalReceived(_id, transferRequests[_id].approvals, msg.sender);

        

        if(limit <= transferRequests[_id].approvals){
            transferRequests[_id].hasBeenSent = true;
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
            totalContractBalance = totalContractBalance - transferRequests[_id].amount;
            emit TransferApproved(_id);
        }


    }
    
    function getTransferRequests() public view returns (Transfer[] memory){
       return transferRequests;

}

}
2 Likes

I can’t believe it !!! thank you very much :smile:

1 Like
pragma solidity 0.7.5;
pragma abicoder v2;

contract MultiSigWallet {
    address[] public owners;
    uint requiredSigs;

    struct Transfer{
        uint amount;
        address payable receiver;
        uint approvalCount;
        bool transferApproved;
        uint id;
    }
    
    Transfer[] transferList;

    mapping(address => mapping(uint => bool)) approvals;

    //Should only allow people in the owners list to continue the execution.
    modifier onlyOwners() {
        bool owner = false;
        for(uint i = 0; i < owners.length; i++) {
            if (owners[i] == msg.sender) {
                owner = true;
            }
        }
        
        require(owner == true);
        _;
    }

    //Should initialize the owners list and the limit
    constructor(address[] memory _owners, uint _requiredSigs) {
        owners = _owners;
        requiredSigs = _requiredSigs;
    }

    //Empty function
    function deposit() public payable {}

    //Create an instance of the Transfer struct and add it to the transferRequests array
    function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
        transferList.push(Transfer(_amount, _receiver, 0, false, transferList.length));
    }

    //Set your approval for one of the transfer requests.
    //Need to update the Transfer object.
    //Need to update the mapping to record the approval for the msg.sender.
    //When the amount of approvals for a transfer has reached the limit, this function should send the transfer to the recipient.
    //An owner should not be able to vote twice.
    //An owner should not be able to vote on a tranfer request that has already been sent.
    function approve(uint _id) public onlyOwners {
        require(approvals[msg.sender][_id] == false);
        require(transferList[_id].transferApproved == false);

        approvals[msg.sender][_id] = true;
        transferList[_id].approvalCount++;

        if (transferList[_id].approvalCount >= requiredSigs) {
            transferList[_id].transferApproved = true;
            transferList[_id].receiver.transfer(transferList[_id].amount);
        }

    }

    //Should return all transfer requests
    function getTransferRequests() public view returns (Transfer[] memory){
        return transferList;
    }


}

Tested in Remix - works great. Cheers. Will add events later and tweak to upgrade as well.

1 Like

// Doing the project before watching the next videos (for the hints, assistance)
pragma solidity 0.7.5;
pragma abicoder v2;

contract MultiSigWallet {

// just an internal tracking for anyone who deposited
mapping(address => uint) balance;

// make public for automatically created getters
// pass on an array of addresses enclosed in double quotes
address[] public owners;
uint public requiredApprovals;

// data structure for transfer request containing the address, amount to be sent, owners who approved the request, is the request processed (sent)
struct TransferRequest {
    address payable recipient;
    uint amount;
    address[] approvers;
    bool sent;
}

// container for all transfer request
TransferRequest[] transferRequests;

constructor (address[] memory _owners, uint _requiredApprovals) {
    owners = _owners;
    requiredApprovals = _requiredApprovals;
}

function deposit() public payable returns(uint) {
    balance[msg.sender] += msg.value;
    return balance[msg.sender];
}

function getDepositRecord() public view returns(uint) {
    return balance[msg.sender];
}

function getBalance() public view returns(uint) {
    return address(this).balance;
}

function getTransferRequest(uint _requestIndex) public view returns(TransferRequest memory) {
    return transferRequests[_requestIndex];
}

function getAllRequests() public view returns(TransferRequest[] memory) {
    return transferRequests;
} 

function transfer(address payable _sentTo, uint _amount) public returns(uint) {
    require(isOwner(msg.sender), "You are not an owner to make a transfer");
    require(getBalance() >= _amount, "Not enough funds.");

    // initialize empty array of address
    transferRequests.push(TransferRequest(_sentTo, _amount, new address[](0), false));
    return _amount;
}

function approveTransferRequest(uint _requestIndex) public returns(TransferRequest memory) {
    require(isOwner(msg.sender), "You are not an owner to make an approval");
    require(!transferRequests[_requestIndex].sent, "You cannot approve already sent transfer");
    require(!hasApproved(msg.sender, transferRequests[_requestIndex].approvers), "You already approved the transfer request.");

    transferRequests[_requestIndex].approvers.push(msg.sender);
    
    processTransferRequest(_requestIndex);

    return transferRequests[_requestIndex];
}

function processTransferRequest(uint _requestIndex) private {
    if (transferRequests[_requestIndex].approvers.length == requiredApprovals) {
        // with the required approvals, transfer is sent
        transferRequests[_requestIndex].sent = true;
        transferRequests[_requestIndex].recipient.transfer(transferRequests[_requestIndex].amount);
    }
}

// helper function to check if an address is an owner
function isOwner(address _address) private view returns(bool) {
    bool isAddressAnOwner = false;
    for (uint i = 0; i < owners.length; i++) {
        if (owners[i] == _address) {
            isAddressAnOwner = true;
        }
    }
    return isAddressAnOwner;
}

// helper function to check if an address (owner) has already approved the transfer request
// which is more optimal (1) a view function, passing the index then accessing the state, or (2) a pure function, passing a copy of the approvers array
function hasApproved(address _address, address[] memory _requestApprovers) private pure returns(bool) {
    bool approved = false;
    for (uint i = 0; i < _requestApprovers.length; i++) {
        if (_requestApprovers[i] == _address) {
            approved = true;
        }
    }
    return approved;
}

}

1 Like

This one was tough, but a good learning experience. Please let me know any thoughts or critique.

I didn’t implement this, but would there be an easy way to prevent the user from entering the same owner address twice when first creating the contract? Pretty much to only allow unique addresses entered?

pragma solidity 0.7.5;
pragma abicoder v2;

contract MultiSig {
    
    // Declare state vars
    
    struct depositLog {
        address depositFrom;
        address depositTo;
        uint depositAmt;
        uint depositTime;
    }
    
    
    struct transferLog {
        uint ID;
        address payable transferTo;
        uint transferAmt;
        uint transferTime;
        uint numApprovals;
        bool complete;
    }
    
    address creator; // contract creator
    address[] addressLog;  // List of valid owner addresses
    uint approvalLimit;  // number of approvals required for transfer
    
    depositLog[] depositLogArray;  // array of deposit transactions
    transferLog[] transferLogArray;  // array of transfer requests
    
    
    // Declare mapping
    mapping (address => uint) balance;  // an address will reference a non-negative amount. Call it balance
    mapping (address => bool) ownerAddress; // reference owner addresses
    mapping (address => mapping(uint => bool)) approvals; // reference approvals
    
    // Declare event
    event transferRcvd(string rcvMsg);
    
    
    // Constructor, called only once at first deploy
    constructor(address[] memory _addressLog, uint _approvalLimit){
        
        require(_approvalLimit > 0, "Your approval limit must be greater than 0");
        require(_addressLog.length >= _approvalLimit, "Not enough addresses to meet approval limit");
        addressLog = _addressLog;
        approvalLimit = _approvalLimit;
        
        for(uint i = 0; i < addressLog.length; i++) {
            ownerAddress[addressLog[i]] = true;
        }
        
        
        creator = msg.sender;
        
    }

 
 
 function deposit() public payable returns (uint) {
     balance[address(this)] += msg.value;
     depositLogArray.push(depositLog(msg.sender, address(this), msg.value, block.timestamp));
     return balance[address(this)];
 }
 
 function transferRequest(address payable recipient, uint amount) public {
     
     // Error handling
     require(balance[address(this)] >= amount, "Insufficient balance");  // Make sure we have the balance to transfer
     require(address(this) != recipient, "Recipient cannot be wallet address");  // Make sure we are not transferring to ourselves
     
     // If request originates from valid owner
     if(ownerAddress[msg.sender]) {
         transferLogArray.push(transferLog(transferLogArray.length, recipient, amount, block.timestamp, 0, false));
         emit transferRcvd("Your transfer has been made successfully and is awaiting approval");
     } else {
         revert("You do not have permission to submit a transfer request");
     }
         
} 
     
 
 function approveRequest(uint _ID) public payable {
     
     require(approvals[msg.sender] [_ID] == false, "This key has already been used as an approver");
     require(transferLogArray[_ID].complete == false, "This transaction request has already been approved");
     require(ownerAddress[msg.sender] == true, "Only valid keys can approve");
     
     approvals[msg.sender] [_ID] = true;
     transferLogArray[_ID].numApprovals++;
     
     if(transferLogArray[_ID].numApprovals == approvalLimit) {
         transferLogArray[_ID].complete = true;
         balance[address(this)] -= transferLogArray[_ID].transferAmt;
         transferLogArray[_ID].transferTo.transfer(transferLogArray[_ID].transferAmt);
         emit transferRcvd("Successfully approved and sent");
     } else {
         emit transferRcvd("Success, but you need more approvals");
     }
     
 }


 
 function showDepositAddress() public view returns (address) {
     return address(this);
 }
 
 function showDepositHistory() public view returns (depositLog[] memory) {
     return depositLogArray;
 }
 
 function showBalance() public view returns(uint) {
        return address(this).balance;  // returns the balance of this contracts address built-in function
    }
    
function showTransferHistory() public view returns (transferLog[] memory) {
     return transferLogArray;
 }
 
 function showAddressLog() public view returns (address[] memory) {
     return addressLog;
 }
 
 function showApprovalLimit() public view returns (uint) {
     return approvalLimit;
 }
 

 
}



Here is my code for the project. It took a lot of work and a lot of sweat but all the things he asked for works. I will still probably work on it because I still have to figure out how to make it so that when the admins confirm, it confirms only the transfer that need to be confirmed.

pragma solidity 0.7.5;


contract Multisigwallet{


  mapping(address => bool) admins;

  constructor(address _admin1,address _admin2,address _admin3){
     admins[_admin1]=true;
      admins[_admin2]=true;
      admins[_admin3]=true;  
  }
  
    uint passwordForConfirmation = 123;
    mapping(address => uint) balance;
    
    uint projectAmount = 0;
    
    function deposit()public payable {
  
    }
    
    function createAccount(uint _balance,address username)public {
        balance[username]=_balance;
    }
    
    function getBalance(address yourusername)public view returns(uint){
        return (balance[yourusername]);
    }
    
    function addBalance(address _yourusername, uint amountToAdd)public {
        balance[_yourusername]+=amountToAdd;
    }
    
     uint confirmation = 0;
     uint transactionNumber = 1;
    
    function confirm(uint password)public {
        require(admins[msg.sender] == true);
        confirmation=confirmation +1;
        require(password == passwordForConfirmation);
    }
    function transfer(address from,address to,uint amount) public {
        
        balance[from]-=amount;
        balance[to]+=amount;
        require(confirmation>=2);
        confirmation = 0;
    }
    
    
    
    
    
    
}

Thank you all for your help everyone.

1 Like

This is my first post on the forum so I hope I’m replying in the correct section.

When I read the problem I thought it was meant to handle multiple Multisig Wallets with the creator managing the wallets. After reviewing some other people’s submissions I think I misunderstood, but I’m curious to see what advice I’d get on my work.

Also can anyone recommend a good place to learn more about payable I don’t understand how it’s working nearly well enough.

Thanks for the help / advice.

pragma solidity 0.7.5;

contract Wallet {

/*
– Anyone should be able to deposit ether into the smart contract
– The contract creator should be able to input (1): the addresses of the owners and (2): the numbers of approvals required for a transfer, in the constructor.
For example, input 3 addresses and set the approval limit to 2.
– Anyone of the owners should be able to create a transfer request. The creator of the transfer request will specify what amount and to what address the transfer will be made.
– Owners should be able to approve transfer requests.
– When a transfer request has the required approvals, the transfer should be sent.

0xdD870fA1b7C4700F2BD7f44238821C26f7392148,
0x5B38Da6a701c568545dCfcB03FcB875f56beddC4,
0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,
0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c,

0x617F2E2fD72FD9D5503197092aC168c91465E7f2
*/
mapping (address => uint) balance;

struct Wallet {
    address owner1;
    address owner2;
    address owner3;
    uint reqApprovals;
    uint transferAmount;
    address destinationAddress;
    uint approvalCount;
}

mapping(address => Wallet) msWallets;

address creator;

modifier onlyCreator {
    require(msg.sender == creator);
    _; //run the function
}


//constructor (address _walletAddress, address _owner1, address _owner2, address _owner3, uint _reqApprovals, uint _balance){
constructor (){
    creator = msg.sender;
    //setupMsWallet(_walletAddress, _owner1, _owner2, _owner3, _reqApprovals, _balance);
    setupMsWallet(0xdD870fA1b7C4700F2BD7f44238821C26f7392148, 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2, 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c,2,100);
}

function setTransfer(address _walletAddress, uint _amount, address _destinationAddress) public {
    //0xdD870fA1b7C4700F2BD7f44238821C26f7392148,44444444444,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
    if (msWallets[_walletAddress].owner1 == msg.sender || msWallets[_walletAddress].owner2 == msg.sender || msWallets[_walletAddress].owner3 == msg.sender){
        if (balance[_walletAddress] >= _amount){
            msWallets[_walletAddress].destinationAddress = _destinationAddress;
            msWallets[_walletAddress].transferAmount = _amount;
            approve(_walletAddress);
        }
    }   
}

function sendTransfer(address _walletAddress) public payable {
    if (msWallets[_walletAddress].approvalCount >= msWallets[_walletAddress].reqApprovals) {
        if (msWallets[_walletAddress].transferAmount <= balance[_walletAddress]) {
            balance[_walletAddress] -= msWallets[_walletAddress].transferAmount;
            balance[msWallets[_walletAddress].destinationAddress] += msWallets[_walletAddress].transferAmount;
            
            msWallets[_walletAddress].transferAmount = 0;
            msWallets[_walletAddress].destinationAddress = _walletAddress;
            msWallets[_walletAddress].approvalCount = 0;

        }    
    }
}

function setupMsWallet(address _walletAddress, address _owner1, address _owner2, address _owner3, uint _reqApprovals, uint _balance) public onlyCreator {
    Wallet memory _walletInst = Wallet(_owner1, _owner2, _owner3,_reqApprovals,0,_walletAddress,0);
    msWallets[_walletAddress] = _walletInst;
    balance[_walletAddress] = _balance;
}

function showMsWallet(address _walletAddress) public view returns(address, address, address, uint, uint,uint,uint){
    return (msWallets[_walletAddress].owner1,msWallets[_walletAddress].owner2,msWallets[_walletAddress].owner3, 
            msWallets[_walletAddress].reqApprovals,
            msWallets[_walletAddress].approvalCount,
            msWallets[_walletAddress].transferAmount,
            balance[_walletAddress]);
}

function approve(address _walletAddress) public{
    if (msWallets[_walletAddress].transferAmount > 0 && msg.sender != address(0)) {
     
        if (msWallets[_walletAddress].owner1 == msg.sender){
            msWallets[_walletAddress].approvalCount += 1;
            msWallets[_walletAddress].owner1 = address(0);
        } else if (msWallets[_walletAddress].owner2 == msg.sender){
            msWallets[_walletAddress].approvalCount += 1;
            msWallets[_walletAddress].owner2 = address(0);
        } else if (msWallets[_walletAddress].owner3 == msg.sender){
            msWallets[_walletAddress].approvalCount += 1;
            msWallets[_walletAddress].owner3 = address(0);
        }
     
        if (msWallets[_walletAddress].approvalCount >= msWallets[_walletAddress].reqApprovals){
            sendTransfer(_walletAddress);
        }
    }
}

function showApprovals(address _walletAddress) public view returns(uint){
    return msWallets[_walletAddress].approvalCount;
}



event depositDone(uint amount, address indexed depositedTo);

function deposit(address _walletAddress) public payable { 
    balance[_walletAddress] += msg.value;
    emit depositDone(msg.value, msg.sender);
    return balance[_walletAddress];
}

}

1 Like

Hi, I have a question.
TransferRequests[] transferRequests;
why am I getting: DeclarationError: Identifier not found or not unique

1 Like

Nevermind solved it. Thank you

1 Like

I make it work all my own, but then watched the suggested methods, but I wanted to make sure I really understood so I did the entire contract again to make sure I have a decent understand…

Any tips would be greatly appreciated.

Here’s my code:

pragma solidity 0.7.5;
pragma abicoder v2; //allows for a function to return a Struct

// 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
// 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
// 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c

/* short cuts for easier testing... 'cause I suck and have to test a lot... haha
constructor
    ["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"],3
setTransaction
    0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,10000000000000000
    0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c,10000000000000000
*/


contract Wallet {
    
    address[] owners;
    uint approvalsReq;
    
    mapping(address => mapping(uint=>bool)) approvals;
    
    struct Transactions {
        address payable recipient;
        uint amount;
        uint approvalCount;
        uint txID;
        bool isSent;
    }
    

    Transactions[] transactionList;
    
    modifier onlyOwners(){
        bool isOwner = false;
        for (uint i = 0; i<owners.length; i++){
            if (msg.sender == owners[i]){
                isOwner = true;
            }
        }
        require(isOwner == true);
        _;
    }
    
    event Deposit(address sender, uint amount, uint balance);
    event CreateTransaction(address recipient, uint amount, uint txID);
    event SendTransaction(uint txID, address recipient, uint amount);
    
    //NTD:
    //address[] _owners = [0x5B38Da6a701c568545dCfcB03FcB875f56beddC4,0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c];
    //uint _approvalsReq = 3;
    constructor (address[] memory _owners, uint _approvalsReq){
    //constructor (){
        owners = _owners;
        approvalsReq = _approvalsReq;
       
    }
    
    function showWalletConfig () public view returns (address[] memory, uint, uint){
        return (owners,approvalsReq,address(this).balance);
    }

    function deposit() public payable{
        emit Deposit(msg.sender,msg.value,address(this).balance);
    }

    function setTransaction(address payable _recipient, uint _amount) public onlyOwners {

        for (uint i = 0; i<owners.length;i++){
            approvals[owners[i]][transactionList.length]=false;
        }
        emit CreateTransaction(_recipient,_amount,transactionList.length);
        Transactions memory _transactionEntry = Transactions(_recipient,_amount,0,transactionList.length,false);
        transactionList.push(_transactionEntry);
    }
    
    function getTransaction(uint _txID) public view returns(Transactions memory){
        return transactionList[_txID];
    }
    
    function approveTransaction(uint _txID) public onlyOwners {
        require(transactionList[_txID].isSent == false);
        require(approvals[msg.sender][_txID] == false);
        
        transactionList[_txID].approvalCount ++;
        approvals[msg.sender][_txID] = true;
        
        if (transactionList[_txID].approvalCount >= approvalsReq){
            sendTransaction(_txID);
        }
    }
    
    function sendTransaction(uint _txID) public payable {
        require(address(this).balance >= transactionList[_txID].amount);
        
        emit SendTransaction(_txID,transactionList[_txID].recipient,transactionList[_txID].amount);
        transactionList[_txID].isSent = true;
        transactionList[_txID].recipient.transfer(transactionList[_txID].amount);
        
    }
}

1 Like

Question: Would you recommend using the Bank contract and make the changes or should we start from scratch? Many thanks!

Glad to know you solve it, sorry for the delay, next time would be also great if you share the code, that way we can have a better picture about the issue :nerd_face:

Carlos Z