Project - Multisig Wallet

The Bank contract is your first basic template, so its good to use it and improve it step by step :slight_smile:

Carlos Z

1 Like

Ok so this is my attempt without watching the videos. I’m experienced in java, so I’m pretty much writing it as though it was in java, so if there’s inefficiencies causing high gas fees etc I guess I’ll learn about it in the next videos. Let me know how it looks! Thanks.

pragma solidity 0.8.7;

contract Wallet{
    
    address[] owners;
    Request[] requests;
    uint approvalLimit;
    uint requestCosts;
    
    event depositDone(uint indexed amount, address indexed to);
    event payout(Request indexed request);
    
    modifier onlyOwner{
        require(isOwner(msg.sender));
        _;
    }
    
    struct Request{
        address to;
        uint amount;
        address[] approved;
    }
    
    constructor(address[] memory _owners, uint _approvalLimit){
        owners = _owners;
        approvalLimit = _approvalLimit;
        requestCosts = 0;
    }
    
    //deposit funds
    function deposit() public payable{
        emit depositDone(msg.value, msg.sender);
    }
    
    function getContractFunds() public view returns(uint){
        return address(this).balance;
    }
    function transferRequest(uint _amount, address _to) public onlyOwner{
        require(address(this).balance >= _amount + requestCosts);
        address[] memory current;
        requests.push(Request(_to,_amount,current));
        requests[requests.length-1].approved.push(msg.sender);
        requestCosts+= _amount;
    }
    //another owner approves the request. if its above approval limit then it runs
    function transferApprove(uint _request) public payable onlyOwner{
        ownerApprovedAdd(msg.sender, _request);
        if(requests[_request].approved.length >= approvalLimit){
            payable(requests[_request].to).transfer(requests[_request].amount);
            Request memory output = requests[_request];
            delete requests[_request];
            emit payout(output);
        }
    }

    //adds owner if they havent approved yet
    function ownerApprovedAdd(address _from, uint _request) internal{
        require(!checkOwnerApproved(_from, _request));
        requests[_request].approved.push(_from);
    }
    
    //checks to make sure owner doesnt approve twice
    function checkOwnerApproved(address _from, uint _request) internal view returns(bool){
        for(uint i =0; i< requests[_request].approved.length; i++){
            if(requests[_request].approved[i] == _from){
                return true;
            }
        }
        return false;
    } 
    
    //check if msg.sender is owner
    function isOwner(address _owner) internal view returns(bool){
        for(uint i =0; i<owners.length; i++){
            if(owners[i] == _owner){
                return true;
            }
        }
        return false;
    }
}
2 Likes

ok so after watching first video I have this now

pragma solidity 0.8.7;

contract Wallet{
    
    address[] owners;
    Request[] requests;
    uint approvalLimit;
    uint requestCosts;
    mapping(address => mapping(uint => bool)) approvals;
    
    event depositDone(uint indexed amount, address indexed to);
    event payout(Request indexed request);
    
    modifier onlyOwner{
        require(isOwner(msg.sender));
        _;
    }
    
    struct Request{
        address to;
        uint amount;
    }
    
    constructor(address[] memory _owners, uint _approvalLimit){
        owners = _owners;
        approvalLimit = _approvalLimit;
        requestCosts = 0;
    }
    
    //deposit funds
    function deposit() public payable{
        emit depositDone(msg.value, msg.sender);
    }
    
    //show how much money in contract
    function getContractFunds() public view returns(uint){
        return address(this).balance;
    }
    
    //return requests
    function getRequests() public view returns( Request[] memory){
        return requests;
    }
    //add to request array a new transfer request
    function transferRequest(uint _amount, address _to) public onlyOwner{
        require(address(this).balance >= _amount + requestCosts);
        requests.push(Request(_to,_amount));
        requestCosts+= _amount;
        ownerApprovedAdd(msg.sender, requests.length-1);
    }
    
    //another owner approves the request. if its above approval limit then it runs
    function transferApprove(uint _request) public payable onlyOwner{
        require(requests.length - 1 >= _request);
        ownerApprovedAdd(msg.sender, _request);
        if(howManyApproved(_request) >= approvalLimit){
            payable(requests[_request].to).transfer(requests[_request].amount);
            requestCosts -= requests[_request].amount;
            Request memory output = requests[_request];
            delete requests[_request];
            emit payout(output);
        }
    }
    
    //check amount of people who approved the request
    function howManyApproved(uint _request) internal view returns(uint){
        uint count = 0;
        for(uint i =0; i< owners.length; i++){
            if(approvals[owners[i]][_request] == true){
                count++;
            }
        }
        return count;
    }
    
    //adds owner if they havent approved yet
    function ownerApprovedAdd(address _from, uint _request) internal{
        require(approvals[_from][_request] == false);
        approvals[_from][_request] = true;
    }

    //check if msg.sender is owner
    function isOwner(address _owner) internal view returns(bool){
        for(uint i =0; i<owners.length; i++){
            if(owners[i] == _owner){
                return true;
            }
        }
        return false;
    }
    
}
1 Like

i send it again the correct way




pragma solidity 0.7.5;

pragma abicoder v2;


contract MultisignWallet{
    
    address[] public owners;
    uint limit;
    
    struct Transfer{
        
        uint amount;
        address payable receiver;
        uint id;
        uint Approvals;
        bool Approval = false
    }
    
    transfer[] TransferRequest;
    
    mapping ( address => (mapping uint = bool )) Approvals;
    
    
    function deposit() public payable{}
    
    bool Approval= false
    for( uint i=0; i>Approval.lenght;i++){
        if (Approval[i]>= 2){
        Approval = true 
    }
    }
    
    require (Approval==true);
    _;
    
    constructor (address[] memory _owners, uint _limit){
        
        owners=_owners;
        limit=_limit;
    }
    
    function TransferRequest (uint _amount, address payable receiver) public owners{
      
      TransferRequest.push(Transfer (_amount,_receiver,TransferRequest.lengh ));
  } 
   function Approve(uint _id, address ) public memory  {
       
       
   }
   
   function Approvalreceived(uint, address ) public returns(bool){
       
       return Approval = true;
       
       
   }
}

// I make as far as i can by myshelf- i know is wrong but explained further by you may help me understand it better 



Hey @Duende, hope you are ok.

You might wanna check your contract, there are too many syntax errors

The variable Approval is missing an ; at the end of it, also there is no need to assign its value to false, all boolean variables start at false by default, so doing bool Approval; is the same than yours, both will initialize as false.

You are missing a sign here, in the second mapping.
mapping ( address => mapping( uint => bool )) Approvals;

All that code is outside a function body.

I advice you to carefully check which are the errors that remix shows to you and correct them.

Carlos Z

Tweaked my project code a little

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;
    }
    
    mapping(uint =>bool)confirmMapping;
    
     uint confirmation = 0;
     uint transactionNumber = 1;
    
    function confirm(uint password, uint transactionID)public {
        require(admins[msg.sender] == true);
        confirmation=confirmation +1;
        require(password == passwordForConfirmation);
        require(confirmMapping[transactionID]=false);
        confirmMapping[transactionNumber]=true;
        
    }
    function transfer(address from,address to,uint amount) public {
        
        balance[from]-=amount;
        balance[to]+=amount;
        require(confirmation>=2);
        confirmation = 0;
        require(confirmMapping[transactionNumber]=true);
        transactionNumber+=1;
    }
    
    
    
    
    
    
}


I got things working but I still have a question. An earlier attempt used a mapping for the owners with address => bool which would always be true. My reasoning was that it would simplify the requirement as you could simply use require(owners[msg.sender]; _;);. This should return true if the sender’s address exists in the owners mapping. Though in my mind it should work, in practice it turned out differently. Any idea on why?

pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet {
    
    // Structs
    struct Transfer {
        uint amount;
        address payable recipient;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
    
    uint reqApprovals;
    address[] owners;
    Transfer[] transferRequests;
    
    // Mappings
    mapping(address => mapping(uint => bool)) approvals;
    
    // Events
    event balanceAdded(uint amount, address account);
    event transferCreated(uint amount, address account);
    event transferApproved(uint amount, address account);
    
    // Modifiers 
    modifier onlyOwners(){
        bool owner = false;
        for(uint i=0; i<owners.length;i++){
            if(owners[i] == msg.sender){
                owner = true;
            }
        }
        require(owner == true);
        _;
    }
    
    // Set owners and number of required approvals.
    constructor(address[] memory _owners, uint _reqApprovals) { 
        reqApprovals = _reqApprovals;
        owners = _owners;
        owners.push(msg.sender);
    }
    
    // Deposite to balance.
    function deposit() public payable {
        emit balanceAdded(msg.value, msg.sender);
    }
    
    // Create new transfer request.
    function createTransfer(uint _amount, address payable _recipient) public onlyOwners {
        // Create request.
        Transfer memory transfer = Transfer(_amount, _recipient, 0, false, transferRequests.length);
        transferRequests.push(transfer);
        
        // Approve for sender & send event.
        approve(transfer.id);
        emit transferCreated(_amount, _recipient);
    }
    
    // Increase approvals if needed.
    // Send transfer when minimum amount of approvals reached.
    function approve(uint _id) public onlyOwners {
        // If sender hasn't voted, and transfer isn't sent,
        // set vote & increase approvals.
        require(!approvals[msg.sender][_id], "You already voted!");
        require(!transferRequests[_id].hasBeenSent, "Transaction already sent!");
        transferRequests[_id].approvals += 1;
        approvals[msg.sender][_id] = true;
        
        // If minimum of approvals is reached, an request isn't sent,
        // send transfer to recipient.
        if (transferRequests[_id].approvals == reqApprovals && !transferRequests[_id].hasBeenSent) {
            transferRequests[_id].recipient.transfer(transferRequests[_id].amount);
            transferRequests[_id].hasBeenSent = true;
            emit transferApproved(transferRequests[_id].amount, transferRequests[_id].recipient);
        }
        
    }
    
    // Should return all transfer requests
    function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }
}
1 Like

hey @Lennart_Lucas, yeah you can use a mapping instead of a for loop. in all honesty the for loop will save you gas unless you want to have like like 7-10 ownees or something really high like that. If you use a maping like you have above and you add an owner it costs about twice as much gas to run the function but the gas cost will always remain the same so you really only save gas using the mapping if you plan on adding like 5 or 6+ owners. However using the maping will greatly reduce the cost of deployment of the contract itself. I do prefer the mapping myself because its much easier to read the code. The mapping will work if you do the following

mapping(address => bool) isOwner;

then dont bother hard coding you owners array in the constructor. Rather in the consteuctor append the address that deploys the contract. then create an add owner function which you can call at any time to dynamically add owners so like this

modifier onlyOwners() {
    require(isOwner[msg.sender] == true, "cannot call this function as a non wallet owner")
}

constructor() {
    owners.push(msg.sender);
    reqApprovals = owners.length - 1;
}

function addOwners(address owner) public onlyOwners {
    require(isOwner[owner] == false, "cannot add duplicate owners");
    owners.push(owner)
    reqApprovals = owners.length - 1;
    isOwner[owner] = true;
}
2 Likes

Hello everyone. For some reason I am able to vote twice / have multiple approvals on one transfer. Seems as if the is should give me an error code when voting for the same transfer. Any suggestions??

pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet {
    
        address [] public owners; //Stores owners of wallet
        uint limit;    //number of ownees who needs to sign off
        uint balance;
    
    
    struct Transfer{
        uint amount;
        address payable reciever;
        uint approvals;
        bool hasBeenSent;
        uint txid;
    }
    
    Transfer[] transferRequests; 
   //mapping[address][transferID]=> true/false
   
   mapping(address => mapping(uint => bool)) approvals; 
  
  
   modifier onlyOwner(){
        bool owner = false;
        for (uint i = 0; i < owners.length; i++){
            if(owners[i] == msg.sender){
            owner = true;
            }   
        }
  require(owner == true, "You don't have permission to execute onlyOwner.");
        _;
        
}

   
 constructor(address[] memory _owners, uint _limit) {
        
        owners = _owners;
       limit = _limit; 
     
 }
       
       
function getBanlance() public view returns (uint) {
    return balance;
}
       
function createTransfer(uint _amount, address payable _reciever) public onlyOwner {
    require(balance >= _amount, "Insufficient funds");
    transferRequests.push(
        Transfer(_amount, _reciever, 0, false,transferRequests.length));
   
    }
function getTransferRequests() public view returns (Transfer[]memory) {
    return transferRequests;
}

function deposite() public payable {
    balance += msg.value;
}

function approve(uint _txid) public onlyOwner {
    require(approvals[msg.sender][_txid] == false);
    require(transferRequests[_txid].hasBeenSent == false);
    
    approvals[msg.sender][_txid] == true;
    transferRequests[_txid].approvals++;
    
    if(transferRequests[_txid].approvals >= limit) {
        transferRequests[_txid].hasBeenSent == true;
        transferRequests[_txid].reciever.transfer(transferRequests[_txid].amount);
      
    }
}

}

Hey! So I took approach of keeping track of requests with mapping(uint => struct Transfer) requests as it helps with debugging. However this way you can’t remove an already approved request. It might not be production ready MultiSig Wallet - you can only approve or don’t vote, so it doesn’t keep track of not approved transfers. But it’s working.

MultiSigWallet.sol:

// SPDX-License-Identifier: UNLICENCED

pragma solidity 0.8.9;

import "./Usable.sol";

contract MultiSigWallet is Usable {
    
    uint private balance;
    uint private minApprovals;
    mapping(uint => Transfer) requests;
    uint private requestCount;
    
    struct Transfer {
        address to;
        uint amount;
        uint id;
        address[] approvers;
        bool approved;
    }
    
    constructor (address[] memory _owners, uint _minApprovals) {
        owners = _owners;
        minApprovals = _minApprovals;
    }
    
    function deposit() public payable mOwnerOnly {
        balance += msg.value;
    }
    
    function requestTransfer(address _to, uint _amount) public mOwnerOnly {
        require(_amount <= balance, "Insufficient amount");
        
        address[] memory emptyArray;
        requests[requestCount] = Transfer(_to, _amount, requestCount, emptyArray, false);
        requestCount++;
    }
   
    
    function approveTransferRequest(uint _id) public payable mOwnerOnly {
        address[] memory currentApprovers = requests[_id].approvers;
        bool addApproval = true;
        for (uint i=0; i<currentApprovers.length; i++) {
            if (currentApprovers[i] == msg.sender) {
                addApproval = false;
                break;
            }
        }
        
        require(addApproval, "You aleady approved this request");
        requests[_id].approvers.push(msg.sender);
        
        if (requests[_id].approvers.length >= minApprovals) {
            transfer(_id);          
        }
    }
    
    function transfer(uint _id) private {
        require(requests[_id].approvers.length >= minApprovals, "Not enough approvals");
        require(!requests[_id].approved, "Transaction was already approved");
        requests[_id].approved = true;
        payable(requests[_id].to).transfer(requests[_id].amount);
    }
}

Usable.sol:

// SPDX-License-Identifier: UNLICENCED

pragma solidity 0.8.9;
 
 contract Usable {
     
    address[] public owners;
     
    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
    
    modifier mOwnerOnly {
        bool isUser;
        for (uint i=0; i<owners.length; i++) {
            if (owners[i] == msg.sender) {
                isUser = true;
            }
        }
        
        require(isUser);
        _;
    }
 }
1 Like

hey @JJ_FX. you actually are able to remove approved transfer request whilst using yout TransferRequests mapping. just type

delete(requests[_id]);

inside your if statement in the approve transfer function

1 Like

also @jtb you can approve the tranfer as many times as you have set your approval limit to be.

@mcgrane5 Oh wow, that’s super useful! Thanks! :grin:

Oh ok. For some reason I set the limit to 2, but yet I can approve a transfer multiple times with the same address. I would think that the require statement in the approve function would prevent a user from voting multiple times considering that the limit is set to 2 when deploying the contract.

1 Like

in your approvals function you need cto change

approvals[msg.sender][id] == true

to

approvals[msg.sender][id] = true

with one equals signs as its an assignemtn not an assertion

pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet {
    address[] public owners;
    uint limit;
    
    struct Transfer{
        uint amount;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
    
    Transfer[] transferRequests;
    
    mapping(address => mapping(uint => bool)) approvals;
    
    //Should only allow people in the owners list to continue the execution.
    modifier onlyOwners(){
        bool is_owner = false;
        for (uint i=0; i<owners.length; i++){
            if (msg.sender==owners[i]){
                is_owner = true;
            }
        }
        require(is_owner);
        _;
    }
    //Should initialize the owners list and the limit 
    constructor(address[] memory _owners, uint _limit) {
        owners = _owners;
        limit = _limit;
    }
    
    //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 {
        transferRequests.push(Transfer(_amount, _receiver, 0, false, transferRequests.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]);
        require(!transferRequests[_id].hasBeenSent);
        
        approvals[msg.sender][_id] = true;
        transferRequests[_id].approvals += 1;
        
        if (transferRequests[_id].approvals >= limit){
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
            transferRequests[_id].hasBeenSent = true;
        }
        
    }
    
    //Should return all transfer requests
    function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }
    
    
}

Wallet.sol

/*
Arrays of accounts that are owners = ["0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db"]
Sending ether to this account = "0x617F2E2fD72FD9D5503197092aC168c91465E7f2"
amount = 1000000000000000000
*/

pragma solidity 0.7.5;
pragma abicoder v2;

import "./Ownable.sol";

contract MultisigWallet is Ownable {
    
    /*
     *
     Events
     *
     */    
    event depositDone(address indexed sender, uint amount, uint balance);
    event ownersDone(address indexed sender);
    event submitTransactionDone(address indexed sender, uint indexed id, address indexed to, uint amount);
    event approved(address indexed sender, uint indexed txId);
    event executeTransferDone(address indexed sender, uint amount);
    
    uint limit;
    mapping(address => mapping(uint => bool)) approvals;

    struct Transfer {
        uint amount;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
    
    Transfer[] transfer;
    
    constructor(address[] memory _owners, uint _limit) {
        owners = _owners;
        limit = _limit;
    }
    
    /*
     *
     Modifiers
     *
     */
    
    modifier txExist(uint _id) {
        require(_id < transfer.length, "Transaction does not exist");
        _;
    }
    
    modifier notExecuted(uint _id) {
        require(!transfer[_id].hasBeenSent, "Transaction already confirmed");
        _;
    }
    
    /*
     *
     Functions
     *
     */
    
    function deposit() payable external {
        emit depositDone(msg.sender, msg.value, address(this).balance);
    }
    
    function getBalance() public returns(uint) {
        return (address(this).balance);
    }
    
    function submitTransaction(address payable _to, uint _amount) public onlyOwners {
        transfer.push(Transfer(_amount, _to, 0, false, transfer.length));
        emit submitTransactionDone(msg.sender, transfer.length, _to, _amount);
    }
    
    function approve(uint _id) public onlyOwners txExist(_id) notExecuted(_id) {
        require(approvals[msg.sender][_id] == false);
        require(transfer[_id].hasBeenSent == false);
        approvals[msg.sender][_id] = true;
        transfer[_id].approvals++;
        emit approved(msg.sender, _id);
    }
    
    function executeTransfer(uint _id ) public onlyOwners txExist(_id) notExecuted(_id) {
        require(transfer[_id].approvals >= limit);
        if(transfer[_id].approvals >= limit) {
            transfer[_id].hasBeenSent = true;
            transfer[_id].receiver.transfer(transfer[_id].amount);
        }
        emit executeTransferDone(msg.sender, _id);
    }
    
    function getTransfer() public view returns(Transfer[] memory) {
        return transfer;
    }  
}
Ownable
pragma solidity 0.7.5;

contract Ownable {
    
    address internal owner;
    address[] public owners;

    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner of the contract interact!");
        _;
    }
    
    modifier onlyOwners() {
        bool owner = false;
        for(uint i = 0; i < owners.length; i++) {
            if(owners[i] == msg.sender) {
                owner = true;
            }
        }
        require(owner = true);
        _;
    }
    
    constructor() {

Took a crack at it, haven’t watched beyond the intro video. Code works perfectly but I feel sloppy. If anyone wants to give it a once over or even run it that would be fantastic. On a side note after reading some of these solutions I don’t know why I did half my contract the way I wrote it, it feels so inefficient, oh well live you learn.

pragma solidity 0.7.5;

contract ownerPerms {

address payable internal owner;

constructor () {
owner = msg.sender;
}

modifier onlyOwner {
require(msg.sender == owner);
_;
}

struct walletMembers { // struct containing the contract owners
address payable ownerA;
address payable ownerB;
address payable ownerC;
}

}

contract MultiSigWallet is ownerPerms {

modifier onlyOwners { // ensures only walletMembers have access to functions
   require(msg.sender == owners[0].ownerA || msg.sender == owners[0].ownerB || msg.sender == owners[0].ownerC); //ensures only 3 owners of contract get access to functions
   _;
}

mapping(string => uint) balanceContract; // ether gets deposited here
mapping(address => uint) balancePeople; // balance of each of the owners
string permName; // name of wallet eth is deposited to

uint counter;
uint amount;
address withdrawRequest; //address requesting withdraw

mapping(address => bool) ownerSign; 

walletMembers[] owners; 
   
event depositDone(uint amount, address indexed sender); // event broadcasts address and amount of deposit
   
function createWallet(string memory walletName, address payable _ownerB, address payable _ownerC) public onlyOwner {
    owners.push(walletMembers(owner, _ownerB, _ownerC)); //uses owners array and pushes to allOwners struct
    balanceContract[walletName];
    permName = walletName;
    // creates wallet instance with names of 3 owners
}

function getOwners(uint _index) public view returns(address, address, address) {
    return (owners[_index].ownerA, owners[_index].ownerB, owners[_index].ownerC);
    // returns owners of indexed wallet instance
}

function depositEth() public payable {
    balanceContract[permName] += msg.value;
    emit depositDone(msg.value, msg.sender);
    // deposits eth into wallet instance
}
   
function getContractBalance() public view returns(uint){
    return balanceContract[permName];
    // returns balance of the wallet
}

function getBalance() public view returns(uint) {
    return balancePeople[msg.sender];
    // returns balance of address
}

function beginWithdraw(uint _amount) public {
    ownerSign[msg.sender] = true;
    withdrawRequest = msg.sender;
    amount = _amount;
    counter += 1;
    // starts transfer as well as writes first signature from the owner
}

function signWithdraw(bool sign) public onlyOwners {
    require(msg.sender != withdrawRequest);
    ownerSign[msg.sender] = sign;
    counter += 1;
    // sign from address not requesting withdraw
}

function executeWithdraw() public onlyOwners{
    require(msg.sender == withdrawRequest);
    require(counter >= 2);
    _withdraw(amount);
    counter = 0;
    // executes withdraw using _withdraw function
}

function _withdraw(uint _amount) private {
    require(balanceContract[permName] >= _amount);
    balanceContract[permName] -= _amount;
    balancePeople[msg.sender] += _amount;
}

}

1 Like

hey @Adavy561 great that you attempted this yourself without video and arrived at your own solution. I can give you a few tips. The first is to do wit you owners system. You should not have this as a strcut. By all means you can but youy should only use a struct whenever you have a data structure that has more than one attribute. For example taking the case of a transfer we can have a receiver address, sender address, amount, id etc that are all part of a single transfer. Here in your wallet owner struct you are defining the attributes of the struct as individual owners which is not really the nest way to go about it. For example if you did want to do it this way a better way to go would to be make a struct like so

struct walletOwner {
    addresss owner;
    address ownerId;
}

//define owneer array
walletOwner[] ownersArray;

This way is better because alls structs are are ā€œuser definedā€ data strutures. So in this format each instance of this struct will be an owner and each owner has an address and an ID. Then we type cast an array of type walletOwner below which will store the walletr ownerrs. You could create a function to then add owners to the wallet. This function would initialise a wallet owneer through the struct and append them to the wallet owneerrs array. consider the following

function addOwner(address _owner) public OnlyOwners {
    for (uint i = 0; i < ownersArray.length; i++) {
        if (ownersArray[i].owner == owner) {
            revert("cannot add duplicate owners");
        }
    }

    ownersArray.push(walletOwner(_owner, ownerId));
    ownerId++;
}

this function would allow you to add owners to you array where each owner has an address and an id. Note that you would set the ownerId variable globally and initilaise it to 0. Now i am only doing things this way because it is similar to the method your doing with the struct. However this way is innefficent because of gas. You better off declaring a regular array of type address globally like address[] ownerArray and modifying the above function to

function addOwner(address _owner) public OnlyOwners {
   for (uint i = 0; i < ownersArray.length; i++) {
       if (ownersArray[i] == owner) {
           revert("cannot add duplicate owners");
       }
   }

   ownersArray.push(_owner);
   ownerId[_owner] = ownerId
   ownerId++;
}

then if you wanted each owner to have and id you would set declare a mapping like mapping(address => uint).

But great jon there is other things you can do like acually create a transfer function instead of doing approvals for the withdrawals like you have done above.

However if you want to see a robust multisig code see my repo link below and have a look at this smart contract. https://github.com/mcgraneder/Etherem-MultiSigWallet-Dapp/blob/main/contracts/Multisig.sol