Project - Multisig Wallet

Thanks @thecil , very nice!

Are you saying that in order to bind a value to an array, the array needs to have a fixed size? and if size is dynamic you have to use push to add more elements?

Like: address payable public fixedArray[3]
fixedArray[0]=_owner1
fixedArray[1]=_owner2
fixedArray[2]=_owner3

with dynamic array/push you can do as you show

Hi, I managed to complete my very first project in Solidity. I have only watched the project introduction video and then went on completing this project. My code is written on a basic level however, it has all the functionalities described and caters to error handling. One major benefit of this contract is that the contract owner can input as many address owners and approvals as they wish. Meaning the number of signatures required to approve a transfer request is not restricted to 2 or 3.

Edit 1: Added the resetApproval() function
Edit 2: Fixed error ( VM error: invalid opcode. invalid opcode The execution might have thrown.) by replacing emit transferProcess before removing an element in array.


pragma solidity 0.7.5;
pragma abicoder v2;
import "./Ownable.sol";

contract wallet is Ownable{
    
    mapping(address => uint) balance;
    mapping(address => uint) referenceId;
    
    event deposited(uint amount, address indexed depositedTo);
    event transferProcess(address sender, address reciever, uint amount);
    
    address[] ownerAddreses;
    uint approvals;
    
    struct Transaction{
        uint amount;
        address to;
        address addressOfOwner;
    }
    
    Transaction[] transferRequest;
  
    function deposit() public payable returns (uint) {
       balance[msg.sender] += msg.value;
       emit deposited(msg.value, msg.sender);
       return balance[msg.sender];
   }
   
   function getBalance() public view returns (uint){
       return balance[msg.sender];
   }
   
   function setOwners(address _ownerAddress) public onlyOwner{
       ownerAddreses.push(address(_ownerAddress));
   }
   
   function getOwners() public view returns(address[] memory){
       return ownerAddreses;
   }
   
   function setApproval(uint _approvals) public onlyOwner{
       approvals = _approvals;
   }
   
   function checkApproval() public view returns(uint){
       return approvals;
   }
   
   function createTransfer(uint _amount, address _to) public{
       require(balance[msg.sender] >= _amount, "Insufficient balance!");
       require(msg.sender != _to, "Don't transfer money to yourself");
       checkIfOwner();
       transferRequest.push(Transaction(_amount,_to, msg.sender));
   }
   
   function checkIfOwner()private view{
       bool valid = false;
       for(uint i = 0; i < ownerAddreses.length; i++){
           if(msg.sender == ownerAddreses[i]){
               valid = true;
           }
       }
       require(valid, "Must be an address owner.");
   }
   
   function checkTransfer(uint _index) public view returns (uint, address, address){
       return (transferRequest[_index].amount, transferRequest[_index].to, transferRequest[_index].addressOfOwner);
   } 
   
   function approveTransfer(uint _index) public returns(uint){
       checkIfOwner();
       //check that the person who created the transfer cannot approveTransfer
       require(approvals > 0, "No approvals have been set.");
       require(transferRequest[_index].addressOfOwner != msg.sender, "You cannot sign your own transfer request.");
       require(referenceId[msg.sender] == 0, "You have already signed.");
       referenceId[msg.sender] = 1;
       if(approvals > 0){
           approvals -= 1;
       }
       sendTransfer(_index);
       return approvals;
       
   }
   function sendTransfer(uint _index) private{
       if(transferRequest.length > 0 && approvals == 0){
           _transfer(transferRequest[_index].addressOfOwner, transferRequest[_index].to, transferRequest[_index].amount);
           emit transferProcess(transferRequest[_index].addressOfOwner, transferRequest[_index].to, transferRequest[_index].amount);
           removeTransferElement(_index);
           resetApprovals();
           
       }
   }
   function removeTransferElement(uint _index) private{
       transferRequest[_index] = transferRequest[transferRequest.length - 1];
       transferRequest.pop();
   }
   function resetApprovals() private{
       for(uint i = 0; i < ownerAddreses.length; i++){
           referenceId[ownerAddreses[i]] = 0;
       }
   }
   function _transfer(address from, address to, uint amount) private {
       balance[from] -= amount;
       balance[to] += amount;
       
   }
   
       
}
1 Like

@thecil, again thanks for your help.

Im running into another problem.
I have a Transfer struct with approved that needs to be >=2 to approve transfer and a double mapping with the approvals saved. My idea is to initialize the double mapping with false when a transfer is requested and only allow it to be counted once when its false and turned into true and increase the counter only once by each owner.

Current code gives an undeclared identifier error:

How do I use the array in the double mapping.

address payable[] public owners;

struct Transfer{
address payable receiver;
uint amount;
uint approved;
}

Transfer[] transfers;

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

event transactionApproved(address approver, uint transID);


// deposit to contract by sending value

function deposit()public payable returns(uint){
    return msg.value;
}

// request a transfer

function requestTransfer(address payable _receiver, uint _amount)public returns (bool){
    transfers.push(Transfer(_receiver, _amount, 0));
   // need to create approvals boolean false -> 
   approvals[owners[0]][_transferID]=false;
    return true;
}

How can I fix this?

Multisig wallet: Version 2
This is an improved version with an additional capability such as being able to approve multiple transfers using a single address. I was able to implement this after learning the concept of double mapping from the videos: “Project Assistance” and “Double Mappings Explanation”.

Code:

pragma solidity 0.7.5;
pragma abicoder v2;
import "./Ownable.sol";

contract wallet is Ownable{
    
    mapping(address => uint) balance;
    mapping(address => mapping(uint => bool)) transactionApproval;
    
    event deposited(uint amount, address indexed depositedTo);
    event transferProcess(address sender, address reciever, uint amount);
    
    address[] ownerAddreses;
    uint approvalimit;
    
    struct Transaction{
        uint amount;
        address to;
        address addressOfOwner;
        uint approvals;
    }
    
    Transaction[] transferRequest;
  
    function deposit() public payable returns (uint) {
       balance[msg.sender] += msg.value;
       emit deposited(msg.value, msg.sender);
       return balance[msg.sender];
   }
   
   function getBalance() public view returns (uint){
       return balance[msg.sender];
   }
   
   function setOwners(address _ownerAddress) public onlyOwner{
       ownerAddreses.push(address(_ownerAddress));
   }
   
   function getOwners() public view returns(address[] memory){
       return ownerAddreses;
   }
   
   function setApproval(uint _approvals) public onlyOwner{
       approvalimit = _approvals;
   }
   
   function checkApprovalLimit() public view returns(uint){
       return approvalimit;
   }
   
   function createTransfer(uint _amount, address _to) public{
       require(balance[msg.sender] >= _amount, "Insufficient balance!");
       require(msg.sender != _to, "Don't transfer money to yourself");
       checkIfOwner();
       if(approvalimit > 0){
           transferRequest.push(Transaction(_amount,_to, msg.sender, approvalimit));
       }
       else{
           _transfer(msg.sender, _to, _amount);
       }
       
   }
   
   function checkIfOwner()private view{
       bool valid = false;
       for(uint i = 0; i < ownerAddreses.length; i++){
           if(msg.sender == ownerAddreses[i]){
               valid = true;
           }
       }
       require(valid, "Must be an address owner.");
   }
   
   function checkTransfer(uint _index) public view returns (uint, address, address, uint){
       return (transferRequest[_index].amount, transferRequest[_index].to, transferRequest[_index].addressOfOwner, transferRequest[_index].approvals);
   } 
   
   function approveTransfer(uint _index) public{
       checkIfOwner();
       require(transferRequest[_index].addressOfOwner != msg.sender, "You cannot sign your own transfer request.");
       require(transferRequest.length > 0, "No transfer request has been sent.");
       require(transactionApproval[msg.sender][_index] == false, "You have already signed.");
       transactionApproval[msg.sender][_index] = true;
       if(transferRequest[_index].approvals > 0){
           transferRequest[_index].approvals -= 1;
       }
       sendTransfer(_index);
   }
   
   function sendTransfer(uint _index) private{
       if(transferRequest.length > 0 && transferRequest[_index].approvals == 0){
           _transfer(transferRequest[_index].addressOfOwner, transferRequest[_index].to, transferRequest[_index].amount);
           emit transferProcess(transferRequest[_index].addressOfOwner, transferRequest[_index].to, transferRequest[_index].amount);
           transactionApproval[msg.sender][_index] = false;
           removeTransferElement(_index);
       }
   }
   
   function removeTransferElement(uint _index) private{
       transferRequest[_index] = transferRequest[transferRequest.length - 1];
       transferRequest.pop();
   }
   
   function _transfer(address from, address to, uint amount) private {
       balance[from] -= amount;
       balance[to] += amount;
   }
   
       
}
1 Like

Exactly, for dynamic arrays you need to use .push to insert new data on the array. While if you define a fixed array you can use array[index] to set values on that specific index.

I could suggest to at least finish the basic of the contract and them start improving some of the funcionalities, double mappings is a complex concept to manage, it might not be the best solution.

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

@thecil , thanks mate! My contract is kind of ready, all functionality is there. Just the approval function doesnt work properly, as the approvals mapping is not initialized automatically to false for a double mapping that has not been set, thus I try to fix it in the requestTransferfunction, by setting the state to false.

pragma solidity 0.7.5;
pragma abicoder v2;

contract multisigWallet{

address payable public owner;

modifier onlyOwner{
    require(msg.sender == owner, "Only owner has access");
    _;
}

address payable[] public owners;

// Setting up owner accounts

constructor (address payable _owner1, address payable _owner2, address payable _owner3){
    owners.push(_owner1);
    owners.push(_owner2);
    owners.push(_owner3);
    
    owner = msg.sender;
    
    }

struct Transfer{
    address payable receiver;
    uint amount;
    uint approved;
}

Transfer[] transfers;

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

event transactionApproved(address approver, uint transID);


// deposit to contract by sending value

function deposit()public payable returns(uint){
    return msg.value;
}

// request a transfer

function requestTransfer(address payable _receiver, uint _amount)public returns (bool){
    transfers.push(Transfer(_receiver, _amount, 0));
   // need to create approvals boolean false -> 
   approvals[owners[0]][_transferID]=false;
    return true;
}

function showTransfer(uint txID) public view returns(address, uint){
    return(transfers[txID].receiver, transfers[txID].amount);
}

function showallTransfers() public view returns(address, uint){
    for(uint a=0; a<transfers.length; a++){
    return(transfers[a].receiver, transfers[a].amount);
    }
}

// approve transaction 

function approveTransaction(uint _transferID)public returns(bool){
    require(msg.sender == owners[0]  || msg.sender == owners[1]  || msg.sender == owners[2], "Only owners have voting power");
    require(_transferID != 0, "Use approveSelfDestruct function to approve contract selfdestruction");
    require(approvals[msg.sender][_transferID]=false, "Only 1 vote / address");
    approvals[msg.sender][_transferID]=true;
    transfers[_transferID].approved+=1;
    emit transactionApproved(msg.sender, _transferID);
    return true;
}

//withdraw if your transfer request has been approved by 2/3

function withdraw(uint txID)public returns(bool){
    require(transfers[txID].approved >= 2, "Need at least 2/3 votes to approved withdrawal");
    transfers[txID].receiver.transfer(transfers[txID].amount);
    return true;
    
}

// function to approve selfdestruct, transferID = 0, requires all three votes;

function approveSelfDestruct() public{
    approvals[msg.sender][0]=true;
    emit transactionApproved(msg.sender, 0);
    }
    
    // require all owners signature to selfdestruct

function destroyContract() public onlyOwner{
    require(approvals[owners[0]][0] && approvals[owners[1]][0] && approvals[owners[2]][0], "All 3 owners must approve selfdestruction");
    selfdestruct(owner);
}

}

1 Like

Here is my version of the Wallet contract:
I started trying it myself, watched Filip’s videos when I got stuck (which was a lot), then ended up making some modifications to my code to be closer to his.
I needed help on the Modifier and on the Approve function.
I originally had a Balance mapping that would track how much each owner deposited over time, but I removed it. Will continue to work on that in future on my own.
I didn’t add any Events after I was finished.

// addresses to use for owners for testing: [“0x5B38Da6a701c568545dCfcB03FcB875f56beddC4”, “0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2”, “0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db”]

pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet {
address[] public owners;
uint ownersNeeded;
uint balance;

struct Transfer {
    uint amount;
    address payable receiver;
    uint approvals;
    bool beenSent;
    uint id;
}

Transfer[] transferRequests;

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

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

// initializes the owners list and the number of owners needed to approve a transfer
constructor(address[] memory _owners, uint _ownersNeeded){
    owners = _owners;
    ownersNeeded = _ownersNeeded;
}

// creates a function to deposit funds into the contract; Filip has this function empty, no body code
function deposit() public payable {
    balance += msg.value; 
}

// creates a function to get the balance of funds in the contract; Filip does not have this function
function getBalance() public view returns(uint){
    return balance;
}

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

// creates a function to get the list of transfer requests
function getTransfer() public view onlyOwners returns(Transfer[] memory){
    return transferRequests;
}

// creates a function to approve a transfer request; once # of approvals needed reached, funds are transfered and contract balance is updated
function approve(uint _id) public onlyOwners {
    require(approved[msg.sender][_id] == false);
    require(transferRequests[_id].beenSent == false);
    
    approved[msg.sender][_id] = true;
    transferRequests[_id].approvals++;
    
    if(transferRequests[_id].approvals >= ownersNeeded){
        transferRequests[_id].beenSent = true;
        transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
        balance -= transferRequests[_id].amount;
    }
    
    
}

}

1 Like

Hi there, I’ve got a question regarding passing an array of addresses to a constructor. I get an error message.

Relevant part of my code:

pragma solidity 0.7.6;
pragma abicoder v2;

contract MultiSigWallet{
    
    uint approvalsLimit;
    address[] owners;
    
    constructor(uint _approvalsLimit, address[] memory _owners){
        approvalsLimit = _approvalsLimit;
        owners = _owners;
    }

Remix deploy input:

2, [0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2]

Remix error message when deploying:

creation of MultiSigWallet errored: Error encoding arguments: SyntaxError: JSON.parse: expected ‘,’ or ‘]’ after array element at line 1 column 3 of the JSON data

What am I doing wrong here? Thanks for helping :slight_smile:

pragma solidity 0.7.5;
pragma abicoder v2;

contract MultisigWallet {

address[] public owners;
uint limit;

struct Transfer{
    uint amount;
    address payable receiver;
    uint approvals;
    bool sentalready;
    uint id;
}

event TransferRequestCreated (uint _id,uint amount, address _initiator, address _receiver);
event ApprovalGotten (uint _id, uint _approvals, address _approver);
event TransferApproved (uint _id);

Transfer[] transferRequests;

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

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

constructor(address[] memory _owners,uint _limit){
    owners=_owners;
    limit=_limit;
    
}
function deposit() public payable {}

function createTransfer(uint _amount, address payable _receiver) public onlyOwners{
    emit TransferRequestCreated(transferRequests.length, _amount,msg.sender,_receiver);
    transferRequests.push(Transfer(_amount,_receiver,0,false,transferRequests.length));
    
    
}
function approve(uint _id) public onlyOwners{
    require(approvals[msg.sender][_id]==false);
    require(transferRequests[_id].sentalready==false);
    
    approvals[msg.sender][_id]=true;
    transferRequests[_id].approvals++;
    
    emit ApprovalGotten(_id,transferRequests[_id].approvals,msg.sender);
    
    if(transferRequests[_id].approvals>=limit){
        transferRequests[_id].sentalready=true;
        transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
        emit TransferApproved(_id);
        
    }
    
}

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

}

DeclarationError: Undeclared identifier. approvals[owners[0]][_transferID]=false; ^---------^

You have an undeclared parameter, _transferID you should send the id of the tx to the mapping in order to set the value.

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

I found the solution to my problem: Addresses have to be passed with double quotation marks!

2, ["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2"]

Below you find my solution to the multi-sig wallet project. Does anyone have suggestions for improvements? Thank you :slight_smile:

pragma solidity 0.7.6;
pragma abicoder v2;

// Example initialization using Remix front-end: 
// 3, ["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db", "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"]

contract MultiSigWallet{
   
   // Owners of multi-sig wallet
   address[] owners;
   // Number of approvals necessary to execute a transfer request
   uint limit;
   
   constructor(uint _limit, address[] memory _owners){
       // Check that owners are unique
       for(uint i=0; i<_owners.length; i++){
           uint foundCount = 0; 
           for(uint j=0; j<_owners.length; j++){
               if(_owners[i] == _owners[j]){
                   foundCount++;
               }
           }

           require(foundCount == 1, "Owner addresses aren't unique.");
       }

       owners = _owners;
       limit = _limit;
   }
   
   struct TransferRequest{
       uint id;
       address payable to;
       uint amount;
       uint approvalsCount;
       bool executed;
   }
   TransferRequest[] requests;
   
   // Owner address --> transfer request id --> approval status
   mapping(address => mapping(uint => bool)) approvals;
   
   modifier onlyOwners{
       // Check that sender is one of the owners
       bool isOwner = false;
       for (uint i=0; i<owners.length; i++){
           if(msg.sender == owners[i]){
               isOwner = true;
               break;
           }
       }
       
       require(isOwner, "You aren't an owner of this wallet.");
       _;
   }
   
   event transferRequested(uint index, address initiator, address to, uint amount);
   event transferApproved(uint index, uint approvalsCount, address approver);
   event transferExecuted(uint index);
   
   // Deposits to this multi-sig wallet
   function deposit() public payable {}
   
   // Creates transfer request and approves it by its creator
   // Returns the id of the created transfer request, which equals its index value in "requests"
   function request(address payable _to, uint _amount) public onlyOwners returns(uint){
       require(_amount <= address(this).balance, "Request exceeds balance of contract.");
       
       uint index = requests.length;
       approvals[msg.sender][index] = true;
       requests.push(TransferRequest(index, _to, _amount, 1, false));
       emit transferRequested(index, msg.sender, _to, _amount);
       
       return index;
   }
   
   // Approves transfer request and executes it if approval limit is reached
   // Returns number of missing approvals in order to execute the request
   function approve(uint _requestIndex) public onlyOwners returns(int){
       require(_requestIndex < requests.length, "Request doesn't exist.");
       require(!requests[_requestIndex].executed, "Request has already been executed.");
       require(!approvals[msg.sender][_requestIndex], "Request has already been approved by you.");
       
       // Approve
       approvals[msg.sender][_requestIndex] = true;
       requests[_requestIndex].approvalsCount++;
       emit transferApproved(_requestIndex, requests[_requestIndex].approvalsCount, msg.sender);
       
       // Execute if ...
       int missingApprovals = int(limit - requests[_requestIndex].approvalsCount);
       if (missingApprovals <= 0){
           _execute(_requestIndex);
       }
       
       return missingApprovals;
   }
   
   // Returns all transfer requests
   function getRequests() public view returns(TransferRequest[] memory){
       return requests;
   }
   
   // Returns multi-sig wallet balance
   function getBalance() public view returns(uint){
       return address(this).balance;
   }
   
   // Executes transfer request
   function _execute(uint _requestIndex) private{
       TransferRequest storage r = requests[_requestIndex];
       require(r.amount <= address(this).balance, "Request exceeds balance of contract");
       
       r.executed = true;
       r.to.transfer(r.amount);
       emit transferExecuted(_requestIndex);
   }
}
3 Likes

I really enjoyed the multisig wallet project - it was designed in a very good way to have to use a lot of the lessons in the first videos and readings.

I have some previous experience coding and was able to mostly complete this without watching the videos, although, I had to peak at the next video titles to discover the concept of double mapping.

I was trying to solve for some of the same things using arrays but was running into trouble getting the array I created inside a function to be returned back outside the function into my TransactionRequest construct.

Another issue that tripped me for a little was that I wanted to save my list of owner accounts that I was adding in as an array to the constructor the syntax looks like this for 3 owners,
[“0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2”,“0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db”, “0x5B38Da6a701c568545dCfcB03FcB875f56beddC4”]

I pasted this after constructing it into a text editor that changed the " characters to a curly version. Remix did not like that at all! Teaches me to be using a text editor that has its own formatting in mind.

pragma solidity 0.7.5;


contract multiSigWallet {
    
    mapping(address => uint) contributions;
    mapping(address => mapping(uint => bool)) approvals;

    uint balance;
    uint TransactionRequestId;
    uint reqApprovals;
    address[] owners;
    struct TransactionRequest {
        uint amount;
        address payable recipientAddress;
        uint approvalCount;
        uint status; //1 is sent, 0 is waiting additional approval
    }
    
    TransactionRequest[] TransactionRequests;
    
    constructor(address[] memory _addresses, uint _reqApprovals){
        reqApprovals = _reqApprovals;
        owners = _addresses;
        TransactionRequestId = 0;
    }
    
    function deposit() public payable returns (uint){
        contributions[msg.sender] += msg.value;
        balance += msg.value;
        return contributions[msg.sender];
    }
    
    function showMyContributions() public view returns (uint){
        return contributions[msg.sender];
    }
    
    function showMultiSigBalance() public view returns (uint){
        return balance;
    }
    
    function getOwners() public view returns (address[] memory){
       return owners;
    }
    
    function verifyAuthorization() private view {
        bool approved = false;
        for (uint i=0; i<owners.length; i++) {
            if(owners[i] == msg.sender){
                approved = true;
            } else {
            }
        }
        require(approved == true, "You are not authorized to do this.");
    }
    
    function addTransactionRequest(uint _amount, address payable _address) public{
        verifyAuthorization();
        
        /* Add the new request */
        uint _oneapproval = 1; //increment approvals by 1 - the transaction requester automatically gets approved
        uint _status = 0; //0 status means transaction has not been sent
        TransactionRequest memory newTransactionRequest = TransactionRequest(_amount, _address, _oneapproval, _status);
        TransactionRequests.push(newTransactionRequest);
        
        /* Set all the approvals to false for this new transaction request, except the requester who is true */
        for (uint i=0; i<owners.length; i++) {
            approvals[owners[i]][TransactionRequestId] = false;
            }
        approvals[msg.sender][TransactionRequestId] = true;
        
        TransactionRequestId++;
    }
    
    function getTransactionRequests(uint _index) public view returns (uint, address, uint, uint) {
        TransactionRequest memory transactionToReturn = TransactionRequests[_index];
        return (transactionToReturn.amount, transactionToReturn.recipientAddress, transactionToReturn.approvalCount, transactionToReturn.status);
    }
    
    function transfer(uint _index) public payable {
        require(TransactionRequests[_index].approvalCount >= reqApprovals, "To make an approval, first request an approval as an owner, then get enough approvals from other owners");
        require(TransactionRequests[_index].status == 0, "This has already been transferred");
        TransactionRequests[_index].recipientAddress.transfer(TransactionRequests[_index].amount);
        balance -= TransactionRequests[_index].amount;
        TransactionRequests[_index].status = 1;
        }
    
    function Approve(uint _index) public {
        verifyAuthorization();
        
        /* check that the approver hasn't already approved, then mark them as already approved */
        require(approvals[msg.sender][_index] == false, "You have already approved this.");
        approvals[msg.sender][_index] = true;
        
        TransactionRequests[_index].approvalCount += 1;
        
        transfer(_index);
    }
1 Like

Thanks for sharing yours! I realized a number of things I can improve on for my own after looking at yours :grinning: nice job!

Ownable contract

pragma solidity 0.7.5;

contract Ownable {
    
    address[3] owners; //Owners of the wallet
    
    constructor(address owner1, address owner2, address owner3) {
        owners[0] = owner1;
        owners[1] = owner2;
        owners[2] = owner3;
    }
    
    modifier onlyOwner {
        require(checkIfOwner(msg.sender));
        _;
    }
    
    //Check if an address is one of the owner in the smart contract.
    function checkIfOwner(address _sender) private view returns (bool) {
        for(uint i = 0; i < owners.length; i++) {
            if(owners[i] == _sender) {
                return true;
            }
        }
        return false;
    }
}

MultisigWallet contract

pragma solidity 0.7.5;
pragma abicoder v2;
import "./Ownable.sol";

contract MultisigWallet is Ownable {
    uint numOfSig; //Number of signatures that is needed for a transaction request
    uint balance; //Total balance of the wallet.
    
    //Transaction request
    struct TxRequest {
        uint txId;
        uint amount;
        address payable receiver;
        address[] acceptedTxReq;
    }
    
    //List of transaction requests
    TxRequest[] txRequests;
    
    constructor(address owner1, address owner2, address owner3, uint _numOfSig) Ownable(owner1, owner2, owner3) {
        numOfSig = _numOfSig;
    }
    
    //Deposit ether into the contract
    function deposit() public payable {
        balance += msg.value;
    }
    
    //Get the balance of the contract
    function getBalance() public view returns(uint) {
        return balance;
    }
    
    //Create one transaction request
    function txRequest(uint _amount, address payable _receiver) public onlyOwner {
        require(_amount <= balance);
        TxRequest memory _txReq;
        _txReq.amount = _amount;
        _txReq.txId = txRequests.length;
        _txReq.receiver = _receiver;
        txRequests.push(_txReq);
        txRequests[txRequests.length - 1].acceptedTxReq.push(msg.sender);
        assert(txRequests[txRequests.length - 1].amount == _amount);
        assert(txRequests[txRequests.length - 1].receiver == _receiver);
    }
    
    //Get all transaction requests in the contract
    function getTxRequests() public onlyOwner view returns (TxRequest[] memory) {
        return txRequests;
    }
    
    //Function that lets owner of the contract accepts different transfer requests made by other owners
    function acceptTxRequest(uint _txId) public onlyOwner {
        require(checkIfOwnerAccepted(msg.sender, _txId));
        require(countAcceptedSig(_txId) != true); //Enough owners has already accepted the transfer.
        require(txRequests[_txId].amount <= balance);
        txRequests[_txId].acceptedTxReq.push(msg.sender);
        if(countAcceptedSig(_txId)) {
            transfer(txRequests[_txId].receiver, txRequests[_txId].amount);
            balance -= txRequests[_txId].amount;
            delete txRequests[_txId];
        }
        assert(balance >= 0);
    }
    
    //Functions that transfer ether to a specific address
    function transfer(address payable _receiver, uint _amount) private {
        require(_amount <= balance);
        _receiver.transfer(_amount);
        assert(balance >= 0);
    }
    
    //Control if owner already accepted the transaction request
    function checkIfOwnerAccepted(address _owner, uint _txId) private view returns (bool) {
        for(uint i = 0; i < txRequests[_txId].acceptedTxReq.length; i++) {
            if(txRequests[_txId].acceptedTxReq[i] == _owner) {
                return false;
            }
        }
        return true;
    }
    
    //Check if enough owners has accepted the transaction request to let it through
    function countAcceptedSig(uint _txId) private view returns (bool) {
        uint _count = 0;
        for(uint i = 0; i < txRequests[_txId].acceptedTxReq.length; i++) {
            _count++;
            if(_count >= numOfSig) {
                return true;
            }
        }
        return false;
    }
}

This is my solution I got before watching any of Filip´s videos. After watching his solution on the MultisigWallet, I reflected about some stuff I could better my solution with.

One thing was that my solution only let the contract have exactly 3 owners, before watching Filip´s solution I didn´t know a constructor could take an array as an argument which open up the possibility of the contract creator to choose how many owners the contract should have and who they should be.

Another thing was the events which I forgot completely and didn´t implement at all in my contract.

Feel free to criticize my solution :grinning:

1 Like

Finally done, a working product. I was doing a horseload of debugging to find the problem, and in the end it was a require statement with = instead of == comparison (cry emoji).

Heres the code: You can choose 3 owners/voters at deployment, transfers require 2/3 votes, contract destruction 3/3 votes. And it works! Going to be interesting to see Filips solution / program after this, maybe it is something totally different.

pragma solidity 0.7.5;
pragma abicoder v2;

contract multisigWallet{

address payable public owner;

modifier onlyOwner{
    require(msg.sender == owner, "Only owner has access");
    _;
}

address payable[] public owners;

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

struct Transfer{
    address payable receiver;
    uint amount;
    uint approved;
    bool sent;
}

Transfer[] transfers;

// Setting up owner accounts

constructor (address payable _owner1, address payable _owner2, address payable _owner3){
    owners.push(_owner1);
    owners.push(_owner2);
    owners.push(_owner3);
    
    owner = msg.sender;
    
    // Initialize selfdestruct approvals false, occupy first element in transfers;
    
    usedApprovals[owners[0]][0]=false;
    usedApprovals[owners[1]][0]=false;
    usedApprovals[owners[2]][0]=false;
    
    // Reserve 0-element in transfers struct for seldestruc approval

    transfers.push(Transfer(owner, 0, 0, false));

    
    }


event transactionApproved(address approver, uint transID);


// deposit to contract by sending value

function deposit()public payable returns(uint){
    return msg.value;
}

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

// request a transfer

function requestTransfer(address payable _receiver, uint _amount)public returns (bool){
    transfers.push(Transfer(_receiver, _amount, 0, false));
   // need to create approvals boolean false -> 
   usedApprovals[owners[0]][transfers.length]=false;
   usedApprovals[owners[1]][transfers.length]=false;
   usedApprovals[owners[2]][transfers.length]=false;
    return true;
}

function showTransfer(uint txID) public view returns(address, uint){
    return(transfers[txID].receiver, transfers[txID].amount);
}

/* doesnt work, stops after first loop / transfers[0]

function showallTransfers() public view returns(address, uint){
    for(uint a=0; a<transfers.length; a++){
    return(transfers[a].receiver, transfers[a].amount);
    }
}

*/

// approve transaction 

function approveTransaction(uint _transferID)public returns(bool){
    require(transfers.length >= _transferID + 1, "Cannot approve a transaction that does not exist");
    require(msg.sender == owners[0]  || msg.sender == owners[1]  || msg.sender == owners[2], "Only owners have voting power");
    require(usedApprovals[msg.sender][_transferID]==false, "Only 1 vote / address");
    usedApprovals[msg.sender][_transferID]=true;
    transfers[_transferID].approved+=1;
    emit transactionApproved(msg.sender, _transferID);
    return true;
}

//withdraw if your transfer request has been approved by 2/3

function withdraw(uint txID)public returns(bool){
    require(transfers[txID].approved >= 2, "Need at least 2/3 votes to approved withdrawal");
    require(transfers[txID].sent == false, "Can only withdraw a transaction once");
    require(transfers[txID].amount <= address(this).balance, " Can't withdraw more than current balance");
    transfers[txID].sent = true;
    transfers[txID].receiver.transfer(transfers[txID].amount);
    return true;
    
}

// function to approve selfdestruct, transferID = 0, requires all three votes;

function approveSelfDestruct() public{
    usedApprovals[msg.sender][0]=true;
    emit transactionApproved(msg.sender, 0);
    }
    
    // require all owners signature to selfdestruct

function destroyContract() public onlyOwner{
    require(usedApprovals[owners[0]][0] && usedApprovals[owners[1]][0] && usedApprovals[owners[2]][0], "All 3 owners must approve selfdestruction");
    selfdestruct(owner);
}

}

So i made two contracts: creator.sol and multisigwallet.sol

creator.sol lets you define the creator as the deployer, and the only one who can set who the owners are:

pragma solidity >=0.7.0 <0.8.0;

contract ownable3{
    
    address creator;
    
    constructor(){
        creator = msg.sender;
    }
    function isCreator() public view returns(bool){
        return msg.sender==creator;
    }
    modifier CreatorOnly{
        require(isCreator());
        _;
    }
  
    function getCreator() public view returns(address){
        return creator;
    }
}

Then the multisigWallet.sol handles the wallet logic:

pragma solidity >=0.7.0 <0.8.0;
import "./owners.sol";

contract MultiSig is ownable3 {
    
    //Variables declaration
    
    struct  owner{
        string name;
        address addr;
    }
    
    owner[3] dudes;
    uint8 count=0;
    // mapping(address => uint256) balanceOf;      // keeps track of historic deposits
    mapping(address => bool) Signed;            // approval signature for owner's addresses
    
    // @We initialise the sign in false.
    constructor(){
        for(uint8 p=0; p<3; p++){
            Signed[dudes[p].addr]= false;
        }
    }
    
    // @Here we set up the modifier and an error handling function.
    
     function ErrorMsg() public pure returns(string memory){
        string memory k= "you are not an owner";
        return k;
    }
    
    
    modifier OnlyOwners() {
        if(msg.sender == dudes[0].addr || msg.sender == dudes[1].addr || msg.sender == dudes[2].addr){
            _;
        } else{
            ErrorMsg;
        }
    }
    
    // @Events 
    
    event NewDeposit(address _from, uint _amount);
    event OwnerSign(address _owner);
    event withdrawBy(address _withdrawer);
                                            ////////////////////////
                                            
                                            
    // @The creator/deployer sets who the owners are
    function SetOwners(string memory _name, address _addr, uint _index) external CreatorOnly{
            dudes[_index].name = _name;
            dudes[_index].addr = _addr;
    }
    
    // @Get who the owners are atm
    function getOwners() public view returns(string memory,string memory,string memory){
                    return (dudes[0].name, dudes[1].name, dudes[2].name);
        
    }
    
                                           /////////////////////////
                                            // @MAIN BODY //
    
    function deposit() payable public {
       // balanceOf[msg.sender] += msg.value;
       emit NewDeposit(msg.sender, msg.value);
    }
    
    function getBalance() public view returns(uint){
       return address(this).balance;
    }
    
    // @Owner agrees on withdrawal by signing
    
    function sign() public OnlyOwners {
        require(Signed[msg.sender] == false);
        Signed[msg.sender] = true;
        emit OwnerSign(msg.sender);
    }
    
    // @Warns that less than 2 owners agree on withdrawal
    
    function ErrorM() private pure returns(string memory){
        string memory p= "2 signatures are required to withdraw";
        return p;
    }
    
    function withdraw(uint _amount) public OnlyOwners {
        for(uint8 i=0; i<3; i++){                                       // counts the amount of approvals in "count"
                if(dudes[i].addr != msg.sender && Signed[dudes[i].addr]){
                count++;
                }
        }
        if(count >= 2 && _amount<= address(this).balance){                                                 // if we have more than 2 approvals then we do the transfer to the sender.
            msg.sender.transfer(_amount);
            emit withdrawBy(msg.sender);
            for(uint8 j=0; j<3; j++){                                   // here we set the permission back to not Signed because the Tx was already sent.
                Signed[dudes[j].addr] = false;
                
                }
            count=0;                              // we return the contract's balance 
            getBalance();
            } else{
            ErrorM;
        }
    }
}

Please take a minute and share some feedback and tips !

Hi @FredrikLarsson, hope you are ok.

Now you have an error in your constructor of the multisig wallet contract, you have arguments that are not being use in the constructor body, so your contract cant run properly.

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

Hi @thecil.
That´s strange I don´t get that compile error. Are you sure you are using my “Ownable” contract that I made, cause that contract have a constructor with 3 arguments of the type addresses. I am confused if that is the case, cause the error states that it expects 0 arguments to the Ownable contract but in reality it should be expecting 3 arguments.

If I haven´t done something in the solidity syntax that isn´t allowed it should work, I get it to work in my remix without any compilation error.

Hi guys,

Here’s my code for the the Multisig Wallet project. Cheers!

pragma solidity 0.7.5; 

pragma abicoder v2;

contract Wallet {
    
    mapping(uint => address) owners;
    uint limit;
    
    constructor (address _owner1, address _owner2, address _owner3, uint _limit) {
        owners[1]=_owner1;
        owners[2]=_owner2;
        owners[3]=_owner3;
        limit=_limit;
        require(_limit<=3,"Limit needs to be less or equal to the number of owners!");
    }
    modifier onlyOwners {
        require(msg.sender==owners[1] || msg.sender==owners[2] || msg.sender==owners[3], "Only contract owners can approve transfers!");
        _;
    }
    
    function deposit() public payable{}
    function contractBalance() public view returns(uint) {
        return address(this).balance;
    }
     
    struct Mempool{
        address payable receiver;
        uint amount;
        uint approvals;
    } 
    Mempool[] unapprovedTransactions;
    
    function initiateTransfer(address payable _receiver, uint _amount) public onlyOwners {
        Mempool memory unapproved = Mempool(_receiver, _amount,0);
        unapprovedTransactions.push(unapproved);
    }
    function viewMempool() public view returns (Mempool[] memory){
        return unapprovedTransactions;
    }
    
    mapping(uint => mapping(address => bool)) appovalsList;
    
    function approveTransaction(uint _index) public onlyOwners {
        
        require(appovalsList[_index][msg.sender]!=true);
        
        if (unapprovedTransactions[_index].approvals < limit && appovalsList[_index][msg.sender]!=true) {
            
            appovalsList[_index][msg.sender] = true;
            unapprovedTransactions[_index].approvals++;
            
        }
        
        if(unapprovedTransactions[_index].approvals==limit) {
            
            unapprovedTransactions[_index].receiver.transfer(unapprovedTransactions[_index].amount);
            delete unapprovedTransactions[_index];
            
        }
        
    }
     
}   
1 Like