Project - Multisig Wallet

Hey @Jordi_V, hope you are well.

The payable allows the function to be able to receive ether, in that sense, the amount of ether will be capture on msg.value.

Yes, this conditional check if msg.sender mapping value of id is equal to false.

Its on your approve function, where approvals[msg.sender][_id] = true;

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(transferRequests[_id].approvals >= limit){
        transferRequests[_id].hasBeenSent = true;
        transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
        emit TransferApproved(_id);
    }
}

Carlos Z

1 Like

Hey @Fernando_Ribas, hope you are well.

The problem is that the push() method is restricted to storage arrays, so instead you must fill the index of your memory array directly, here is an example:

    function getAllTxUnSent() public view returns(Transfer[] memory){
        
       Transfer[] memory unsent;
       for(uint i=0;i<=transfersRequest.length;i++){
           if(transfersRequest[i].approvals >= approvalsNeeded){
                if(transfersRequest[i].sent == false){
                    unsent[unsent.length] = transfersRequest[i];
                }
            }
        
        }
       return unsent;
    }

https://docs.soliditylang.org/en/latest/types.html?highlight=array#array-members

Carlos Z

1 Like

My version of a multisig.

pragma solidity 0.7.5;
//SPDX-License-Identifier: UNLICENSED
pragma experimental ABIEncoderV2;

contract MultisigWallet {

address Owner=0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
address  Sig1=0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
address  Sig2=0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;
address  Sig3=0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;
address  Null=0x0000000000000000000000000000000000000000;

struct approval {
    uint txnId;
    address approved_by_1;
    address approved_by_2;
    uint amount;
    address receiver;
    bool status;
}

approval[]  approvals;

modifier canApprove{
   require(msg.sender==Sig1 || msg.sender==Sig2 || msg.sender==Sig3, "Not a valid Approver" );
   _;
}


function getApproval(uint _txnId) private view returns (approval memory _approval){
     approval memory approved = approvals[_txnId];
     return (approved);
     //return (approved.txnId, approved.approved_by_1, approved.approved_by_2, approved.amount, approved.receiver);
}

function createApproval(address _approver1, address _approver2, address _receiver, uint _amount) private canApprove returns (uint _txnId) {
     uint txnId = approvals.length - 1;
     approval memory newApproval = approval(txnId, _approver1, _approver2 , _amount, _receiver, false );
     approvals.push(newApproval);
     return(txnId);
}


function approveTransfer (uint _txnId, address _receiver, uint _amount) public canApprove {

    uint txnId;
    
    if (_txnId==0) {
       txnId = createApproval(msg.sender, Null, _receiver, _amount);
    }
    else {
       approval memory oldApproval = getApproval(_txnId);
       
       // If different wallet approving,  amount the same, receiver the same
       if (oldApproval.approved_by_1 != msg.sender && _amount==oldApproval.amount && _receiver==oldApproval.receiver) {
                transfer(_receiver,_amount);
                approvals[_txnId].approved_by_2 = msg.sender;
                approvals[_txnId].status = true;
       }
       else {
            assert(1==2);
       }
    }

}

mapping(address => uint) balance;

mapping(address => uint) TransferApproved;

event depositDone(uint amount, address indexed depositedTo);

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

function withdraw(uint amount) public returns (uint){
    require(balance[msg.sender] >= amount);
    balance[msg.sender] -= amount;
    msg.sender.transfer(amount);
    return balance[msg.sender];
}

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


function transfer(address recipient, uint amount) public {
    require(balance[msg.sender] >= amount, "Balance not sufficient");
    require(msg.sender != recipient, "Don't transfer money to yourself");
    
    uint previousSenderBalance = balance[msg.sender];
    
    _transfer(msg.sender, recipient, amount);
            
    assert(balance[msg.sender] == previousSenderBalance - amount);
}

function _transfer(address from, address to, uint amount) private {
    balance[from] -= amount;
    balance[to] += amount;
}

}

1 Like

I looked at every template that I could search before moving to the next videos and I really like this one because I learned a lot about modifiers and require and the different things that you can do with them. I did not write this code.

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

contract MultiSigWallet {
    event Deposit(address indexed sender, uint amount, uint balance);
    event SubmitTransaction(
        address indexed owner,
        uint indexed txIndex,
        address indexed to,
        uint value
        );
    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;
        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
    ) public onlyOwner {
        uint txIndex = transactions.length;

        transactions.push(
            Transaction({
                to: _to,
                value: _value,
               
                executed: false,
                numConfirmations: 0
            })
        );

        emit SubmitTransaction(msg.sender, txIndex, _to, _value);
    }

    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;

        

        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,
            
            bool executed,
            uint numConfirmations
        )
    {
        Transaction storage transaction = transactions[_txIndex];

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

1 Like

Ok here is some early version of the Multisig Wallet.
I followed the Template so the code is similar to the solution proposed with differences in variable or function names.

Added features:

  • Various prechecks
    like votes must be between 1 and number of owners, transfer request amount must be availalbe in balance etc
  • Bound funds
    Everytime a transfer request is created, the amount of funds that are to be transferred are bound till it is completed.
  • Option to reject a transfer by vote.
    If there are enough rejection votes such as the remaining votes are not enough for approval, the transfer is rejected and funds are released

Future Features

  • Dynamically add or remove owners
    either by the creator of the wallet or by voting
  • Adjust vote limit
    Each request may have different number of owners at its time of its creation, thus different limit of approval votes. Even further, each transfer has different set of owners from which it needs to get majority approval. Handle requests when the initiator of approval is removed, handle when another owner is removed.
pragma solidity 0.7.5;
pragma abicoder v2;

//VERSION UPDATES
// V03 check balance and bound funds before transfer request
// V04 cancel request
//["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"]
contract Wallet {
    address[] public owners;
    uint voteLimit;
    uint rejectLimit;
    uint public boundFunds;
    
    struct Transfer{
        uint amount;
        address payable receiver;
        uint approvals;
        uint rejections;
        bool hasBeenSent;
        bool hasBeenRejected;
        uint txId;
    }
    
    Transfer[] transferRequests;
    
    mapping(address => mapping(uint => bool)) approvalMap;
    mapping(address => mapping(uint => bool)) rejectionMap;
    
// EVENTS
    event depositDone(address _depositor,uint _amount);
    event transferRequestCreated(uint _txId, address _initiator, address _receiver, uint _amount);
    event approvalGiven (uint _txId, address _approver, uint _approvals);
    event rejectionGiven(uint _txId, address _approver, uint _rejections);
    event transferApproved(uint _txId);
    event transferRejected(uint _txId);
    
    //Should initialize the owners list and the limits
    constructor(address[] memory _owners, uint _voteLimit) {
        require(_voteLimit > 0 , "number of aprroval votes required must be at least one");
        require(_voteLimit <= _owners.length, "number of approval votes required can't exceed number of owners ");
        owners = _owners;
        voteLimit = _voteLimit ;
        rejectLimit =  owners.length - voteLimit + 1;
        boundFunds = 0;
    }
    
    //Should only allow people in the owners list to continue the execution.
    modifier onlyOwners(){
        bool isOwner = false;
        for (uint i=0; i<owners.length; i++) {
            if (msg.sender == owners[i]) {
                isOwner = true;
            }
        }
        require(isOwner == true , "Only addresses that belong to the owner list can make this request");
        _;
    }

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

    function deposit() public payable {
        emit depositDone(msg.sender, msg.value);
    }
    
    //Create an instance of the Transfer struct and add it to the transferRequests array
    //Creator of transfer request also approves the request
    function createTransfer(address payable _receiver, uint _amount) public onlyOwners {
        require(address(this).balance - boundFunds >= _amount, "Not enough free funds. Deposit more or unbind from transfer requests ");
        
        uint id = transferRequests.length;
        transferRequests.push(Transfer(_amount, _receiver, 1, 0, false, false, id));
        approvalMap[msg.sender][id]= true;
        boundFunds += _amount;
        emit transferRequestCreated(id ,  msg.sender, _receiver, _amount);
        emit approvalGiven(id, msg.sender, 1);
    }
    
    //Set your approval or rejection 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 receiver.
    //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 or rejected
    
    function approve(uint _txId) public onlyOwners {
        require(approvalMap[msg.sender][_txId] == false, "This address has already approved this transfer request");
        require(rejectionMap[msg.sender][_txId] == false, "This address has already rejected this transfer request");
        require(transferRequests[_txId].hasBeenSent == false, "This transfer request has already been approved");
        require(transferRequests[_txId].hasBeenRejected == false, "This transfer request has already been rejected");
        
        approvalMap[msg.sender][_txId] = true;
        transferRequests[_txId].approvals++;
        emit approvalGiven(_txId,msg.sender,transferRequests[_txId].approvals);
        
        if (transferRequests[_txId].approvals >= voteLimit) {
            transferRequests[_txId].hasBeenSent = true;
            uint _amount = transferRequests[_txId].amount;
            boundFunds -= _amount;
            transferRequests[_txId].receiver.transfer(_amount);
            emit transferApproved(_txId);
        }
    }
    
    function reject(uint _txId) public onlyOwners {
        require(approvalMap[msg.sender][_txId] == false, "This address has already approved this transfer request");
        require(rejectionMap[msg.sender][_txId] == false, "This address has already rejected this transfer request");
        require(transferRequests[_txId].hasBeenSent == false, "This transfer request has already been approved");
        require(transferRequests[_txId].hasBeenRejected == false, "This transfer request has already been rejected");
        
        rejectionMap[msg.sender][_txId] = true ;
        transferRequests[_txId].rejections++;
        emit rejectionGiven(_txId,msg.sender,transferRequests[_txId].rejections);
        
        if (transferRequests[_txId].rejections >= rejectLimit) {
            transferRequests[_txId].hasBeenRejected = true;
            uint _amount = transferRequests[_txId].amount;
            boundFunds -= _amount;
            emit transferRejected(_txId);
        }
    }
    //Should return all transfer requests
    function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }
    
}


// ISSUES FOR LATER VERSIONS

// variable number of owners
// votelimit bust be > owners/2 (at the the time of request creation)
// handle requests when owners change before executed

1 Like

Tried to do it without the template, ended up having to use it. I also had to use the last video to help me through a couple of sections. I added a way to get the balance of the wallet.

pragma solidity 0.7.5;
pragma abicoder v2;



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

contract Wallet {
    address[] public owners;
    uint limit;

    
    struct Transfer{
        uint amount;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
    
    event TransferRequestCreated(uint _id, uint _amount, address _initiator, address _receiver);
    event TransferApproved(uint _id);
    event ApprovalReceived(uint _id, uint _approvals, address _approver);
    
    Transfer[] transferRequests;
    
    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 _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 {
        emit TransferRequestCreated(transferRequests.length, _amount, msg.sender, _receiver);
        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] == false); 
        require(transferRequests[_id].hasBeenSent == false); 

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

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

        if (transferRequests[_id].approvals >= limit) { 
            transferRequests[_id].hasBeenSent = true;
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
            emit TransferApproved(_id);
        }
    }
    
    //Should return all transfer requests
    function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
        
    }
    
    
    //added function to get balance
    function getBalance () public view returns (uint) {
       uint contractBalance = address(this).balance;
       return contractBalance;
    }
}

1 Like

My attempt of a multi-sig wallet.

pragma solidity 0.7.5; 
pragma abicoder v2;

contract multisig {
    
    
    struct Transfer {
    uint id;
    uint approval;
    bool true; 
    address one;
    address two;
    } 
    
    Transfer [] transferRequests;
    
    mapping(address => mapping(uint => bool)) approval;                                            
    
    address owner;
    
    modifier onlyOwner(uint number) {
        require(msg.sender == owner);
        _; 
    }
    
    constructor() public {
        one = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
        two = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;
        approval == 2;
    }
    
    function deposit() public payable returns (uint) {
        balance[msg.sender] += msg.value;       //keep a track of which addresses deposited to the SC//
        emit depositDone (msg.value, msg.sender);
        return balance[msg.sender];
    }
    
    function getBalance() public view returns (uint) {
        return balance[msg.sender];
    }
        
    function approval(string memory address) public {
        require(msg.sender == one || msg.sender == two);
        require(approval[msg.sender] == false);
        approval[msg.sender] = true;
        mapping[msg.sender] [1] = true;
        transferRequest.push(transfer);
    }
    
    function _approval2() public view returns (string) {
        require(approval[one] && approval[two] == true);
        return [msg.sender] = true;
    }
    
    function transfer(address 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2, uint amount) public onlyOwner(1) returns (uint){
        require(balance[msg.sender] >= amount);
        msg.sender.transfer(amount);
        return balance[msg.sender];
        
    }
    
     function _transfer(address from, address to, uint amount) private {
        balance[from] -= amount;
        balance[to] += amount;
    }
    
}
    type or paste code here
1 Like

I kinda went Rogue lol. Here’s my code. Going to watch the hints video now and see how it’s really done. Still proud of my creation hehe.

contract DAO {

mapping(uint => Owners) ownerList;

struct Owners {
string _name;
address _address;
}

Owners owner;

uint balance;

address[] ownerAddress;

function addOwner(string memory _name) public {
uint _index = 0;
owner = Owners(_name, msg.sender);
ownerList[_index] = owner;
Owners memory person = ownerList[_index];
ownerAddress.push(person._address);
_index += 1;
}

function getOwnerBalance() public view returns(uint) {
return msg.sender.balance;
}

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

address payable withdrawRequestAddress;
uint withdrawRequestAmount;

function withdrawRequest(address payable _to, uint _amount) public payable returns(address, uint) {
withdrawRequestAddress = _to;
withdrawRequestAmount = _amount;
}

function viewRequest() public view returns(address, uint) {
return (withdrawRequestAddress, withdrawRequestAmount);
}

function approveTx(address _address1, address _address2) public returns(string memory) {

  for (uint i = 0; i < ownerAddress.length; i++) {
      if (ownerAddress[i] == _address1) {
          for (uint i = 0; i < ownerAddress.length; i++) {
              if (ownerAddress[i] == _address2) {
                  withdrawRequestAddress.transfer(withdrawRequestAmount);
              } else {
                  return "You don't have access";
              }
          }
           
      } else {
          return "You don't have access";
      } 
  }
  
}

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

1 Like
pragma solidity 0.8.7;

contract MultiSigWallet {
    
    address[] owners;
    mapping(address => bool) ownersMap;
    uint public approvalsRequired;
    
    struct Transfer {
        uint amount;
        address payable recipient;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
    
    Transfer[] transfers;
    mapping(address => mapping(uint => bool)) approvals;
    
    constructor(address[] memory _owners, uint _approvalsRequired) {
        for (uint i = 0; i < _owners.length; i++) {
            address ownerAddress = _owners[i];
            ownersMap[ownerAddress] = true;
        }
        
        owners = _owners;
        approvalsRequired = _approvalsRequired;
    }
    
    modifier canApprove(address approver, uint index) {
        require(ownersMap[msg.sender], "You are not one of the owners of this contract");
        require(!approvals[approver][index], "You have already approved this transfer");
        _;
    }
    
    function getOwnerss() public view returns(address[] memory) {
        return owners;
    }
    
    function getOwnerByIndex(uint index) public view returns(address) {
        return owners[index];
    }
    
    function deposit() public payable {}
    
    function getBalance() public view returns(uint) {
        return address(this).balance;
    }
    
    function transferRequest(uint amount, address payable recipient) public {
        Transfer memory newTransfer = Transfer(amount, recipient, 0, false, transfers.length);
        transfers.push(newTransfer);
    }
    
    function getTransferRequests() public view returns(Transfer[] memory) {
        return transfers;
    }
    
    function approve(uint index) public canApprove(msg.sender, index) payable {
        if (!approvals[msg.sender][index]) {
            approvals[msg.sender][index] = true;
            transfers[index].approvals += 1;
        }
        if (transfers[index].approvals >= approvalsRequired && !transfers[index].hasBeenSent) {
            address payable recipient = transfers[index].recipient;
            uint amount = transfers[index].amount;
            recipient.transfer(amount);
            transfers[index].hasBeenSent = true;
        }
    }
    
}


2 Likes

I’ve been able to compile and test up to creating a transfer, but when adding modifiers to control signing and executing the transfer, I’m unable to compile and receive an error that says, ā€˜Member ā€œapprovalsā€ not found or not visible after argument-dependent lookup in struct, line 41.’

Any guidance you can give is appreciated.

Here’s all the code:

Ownable.sol

pragma solidity 0.8.6;

contract Ownable {
    address owner;
    
    modifier onlyOwner {
        require(msg.sender == owner);
        _; 
    }
    
    constructor() {
        owner = msg.sender;
    }
}

Authorizable.sol

pragma solidity 0.8.6;

import "./Ownable.sol";

contract Authorizable is Ownable {
 
    address[] public authorized;

    mapping(address => bool) public isAuthorized;
    
    event addedAuthorized(address indexed addAuthorized);
    
    event removedAuthorized(address indexed removeAuthorized);

    modifier onlyAuthorized() {
        require(isAuthorized[msg.sender] || owner == msg.sender, "Not authorized");
        _;
    }

    function addAuthorized(address _toAdd) onlyOwner public {
        isAuthorized[_toAdd] = true;
        emit addedAuthorized(_toAdd);
    }

    function removeAuthorized(address _toRemove) onlyOwner public {
        isAuthorized[_toRemove] = false;
        emit removedAuthorized(_toRemove);
    }
    
}

Wallet.sol

pragma solidity 0.8.6;
pragma abicoder v2;

import "./Authorizable.sol";

contract Wallet is Authorizable {
    
    // Individual deposits and deposit event log
    mapping(address => uint) balance;    
    event depositCompleted(address from, uint amount);    

    // Minimum # of signatures for approval
    uint public numSignaturesRequired;

    // Struct of transaction
    struct Transaction {
        address payable to;
        uint amount;
        uint numSignatures;
        bool executed;
        uint txIndex;
    }

    // Array of transfer requests
    Transaction[] public transactionRequests;
    

    // Storage of approvals
    mapping (address => mapping(uint => bool)) approvals;
    

    // Modifier to ensure transaction exists
    modifier txExists(uint _txIndex) {
        require(_txIndex < transactionRequests.length, "Transaction does not exist");
        _;
    }

    // Modifier to ensure transaction is not confirmed
       modifier notConfirmed(uint _txIndex) {
       require(!transactionRequests[_txIndex].approvals[msg.sender], "Transaction already confirmed");
        _;
    }

    // Modifier to ensure transaction is not executed
    modifier notExecuted(uint _txIndex) {
        require(!transactionRequests[_txIndex].executed, "Transaction already executed");
        _;
    }


    // Initalize authorized-owners list and limit
    constructor(address[] memory _authorized, uint _numSignaturesRequired){
        require(_authorized.length > 0, "Authorized signers required");

        for (uint i = 0; i < _authorized.length; i++) {
            address owner = _authorized[i];

            require(owner != address(0), "Not authorized");
            require(!isAuthorized[owner], "Signer not unique");

            isAuthorized[owner] = true;
            authorized.push(owner);
        }

        numSignaturesRequired = _numSignaturesRequired;
        
    }

    
    // Functions needed for transfer: create, sign and execute
    function createTransfer(address payable _to, uint _amount) onlyAuthorized public {

        transactionRequests.push(Transaction({
            to: _to,
            amount: _amount,
            numSignatures: 0,
            executed: false,
            txIndex: transactionRequests.length
        }));
    }
    
    
    
    function signTransfer(uint _txIndex) onlyAuthorized txExists(_txIndex) notConfirmed(_txIndex) notExecuted(_txIndex) public {
        Transaction storage transaction = transactionRequests[_txIndex];
        transaction.approvals[msg.sender] = true;
        transaction.numSignatures += 1;
    }



    function executeTransfer(uint _txIndex) onlyAuthorized txExists(_txIndex) notExecuted(_txIndex) public {
        Transaction storage transaction = transactionRequests[_txIndex];
        require(transaction.numSignatures >= numSignaturesRequired, "Cannot execute transaction");

        transaction.executed = true;

        (bool success, ) = transaction.to.call{value: transaction.amount};
        require(success, "Transaction failed");
    }


    function getAuthorized() public view returns (address[] memory) {
        return authorized;
    }
    
    function getTransactionCount() public view returns (uint) {
        return transactionRequests.length;
    }

    function getTransaction(uint _txIndex) public view returns (address to, uint amount, uint numSignatures, bool executed)
    {
        Transaction storage transaction = transactionRequests[_txIndex];

        return (
            transaction.to,
            transaction.amount,
            transaction.numSignatures,
            transaction.executed
        );
    }



    // Anyone can deposit
    function deposit() public payable returns (uint){
        balance[msg.sender] += msg.value;
        emit depositCompleted(msg.sender, msg.value);
        return balance[msg.sender];
    }
 
    //Return individual depositor balance   
    function getBalance() public view returns (uint){
        return balance[msg.sender];
    }    

    //Return contract balance    
    function getContractBalance() public view returns (uint){
        return address(this).balance;
    }    
    
}

Thank you.

1 Like
pragma solidity >=0.7.0 <0.9.0;

contract MultiSigWallet {
    
    address private _owner;
    
  
  mapping(address => uint8) private _owners;
    
    //minimum amnt of sigs to sign contract
    uint constant MIN_SIGNATURES = 2;
    
    uint private _transactiontx;
    
    struct Transaction {
        address from;
        address to;
        uint amount;
        uint8 signatureCount;
        mapping (address => uint8) signatures;
    }
    
    //The mapping of transaction ID to a transaction.  
    mapping (uint => Transaction) private _transactions;
    
    //dynamic array for the list of pending transactions that need processing
    uint[] private _pendingTransactions;
    
    modifier isOwner() {
        require(msg.sender == _owner);
        _;
    }
    
        require(msg.sender == _owner || _owners[msg.sender] == 1);
        _;
    }
    
    event DepositFunds(address from, uint amount);
    event WithdrawFunds(address from, uint amount);
    event TransferFunds(address from, address to, uint amount);
    event TransactionSigned(address by, uint transactionId);
    
    //contract creator = wallet owner
    function MultiSigWallet()
        public {
        _owner = msg.sender;
    }
    
    //Only isOwner can add addresses. 
    function addOwner(address owner) 
        isOwner 
        public {
        _owners[owner] = 1;
    }
    
    //remove an owner from the wallet.  0 = disabled.
    function removeOwner(address owner)
        isOwner
        public {
        _owners[owner] = 0;   
    }
    
    //anyone can send ether into the wallet
    function ()
        public
        payable {
        DepositFunds(msg.sender, msg.value);
    }
    
    function transferTo(address to, uint amount)
        validOwner
        public {
        require(address(this).balance >= amount);
        
        //creating a transactionId
  
        uint transactionId = _transactiontx++;

        Transaction memory transaction;
        transaction.from = msg.sender;
        transaction.to = to;
        transaction.amount = amount;
        transaction.signatureCount = 0;
        
   
        _transactions[transactionId] = transaction;
        _pendingTransactions.push(transactionId);
        //create an event that the transaction was created
        TransactionCreated(msg.sender, to, amount, transactionId);
    
    }
    
    //getting list + returning the array of pending transactions (must be owner)
    function getPendingTransactions()
        validOwner
        public
        returns (uint[]) {
        return _pendingTransactions;
    }
    
    //sign and execute if conditions are met
      function signTransaction(uint transactionId)
        validOwner
        public {
        
        Transaction storage transaction = _transactions[transactionId];
    
        //checking transaction exists to execute
        require(0x0 != transaction.from);
        //creator cannot sign contract
        require(msg.sender != transaction.from);
        //cannot sign more than once
        require(transaction.signatures[msg.sender] != 1);
        
        //sign the tranaction
        transaction.signatures[msg.sender] = 1;
        //increment the signatureCount by 1
        transaction.signatureCount++;
        //emit an event
        TransactionSigned(msg.sender, transactionId);
    
        //transaction must >= the minimum signatures to process + validation
        If (transaction.signatureCount >= MIN_SIGNATURES) {
      
            require(address(this).balance >= transaction.amount);
            transaction.to.transfer(transaction.amount);
            transactionCompleted(transaction.from, transaction.to, transaction.amount, transaction.Id);
            deleteTransaction(transactionId);
        }
        
    function deleteTransaction(uint transactionId)
        validOwner
        public {
        //deleting a dynamic array + reshuffling array
        uint8 replace = 0;
        for(uint i = 0; i < _pendingTransactions[i];
        _pendingTransactions[i-1] = _pendingTransactions[i];
        } elseif (transactionId == _pendingTransactions[i]) {
            replace = 1;
        }
    }
    //delete last element in array and then from the map
    delete _pendingTransactions[_pendingTransactions.length -1];
    //and decrement the array by 1
    _pendingTransactions.length--;
    delete _transactions[transactionId];
    }
    
    
    function walletBalance()
        constant
        public
        returns (uint) {
        return address(this).balance;
        }
}

Hey @WMXgTW6wh, hope you are well.

The problem is that you are trying to access to your struct property that does not exists on it.
image

You could do this instead, if what you want is just to verify if the transaction has been sent or not.

    // Modifier to ensure transaction is not confirmed
    modifier notConfirmed(uint _txIndex) {
       require(!transactionRequests[_txIndex].executed, "Transaction already confirmed");
        _;
    }

Or this in case you want to verify if msg.sender has signed or not the transaction

    // Modifier to ensure transaction is not confirmed
    modifier notConfirmed(uint _txIndex) {
       require(!approvals[msg.sender][_txIndex], "Transaction already confirmed");
        _;
    }

Carlos Z

Thanks! You helped me see this in a different way. I also saw that I had my value types out of order in the double mapping. Here is the final project that checks out in my testing, but I welcome feedback as always:

Ownable.sol

pragma solidity 0.8.6;

contract Ownable {
    address owner;
    
    modifier onlyOwner {
        require(msg.sender == owner);
        _; 
    }
    
    constructor() {
        owner = msg.sender;
    }
}

Authorizable.sol

pragma solidity 0.8.6;

import "./Ownable.sol";

contract Authorizable is Ownable {
 
    address[] public authorized;

    mapping(address => bool) public isAuthorized;
    
    event addedAuthorized(address indexed addAuthorized);
    
    event removedAuthorized(address indexed removeAuthorized);

    modifier onlyAuthorized() {
        require(isAuthorized[msg.sender] || owner == msg.sender, "Not authorized");
        _;
    }

    function addAuthorized(address _toAdd) onlyOwner public {
        isAuthorized[_toAdd] = true;
        emit addedAuthorized(_toAdd);
    }

    function removeAuthorized(address _toRemove) onlyOwner public {
        isAuthorized[_toRemove] = false;
        emit removedAuthorized(_toRemove);
    }
    
}

Wallet.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;
pragma abicoder v2;

import "./Authorizable.sol";

contract Wallet is Authorizable {
    
    // Individual deposits and deposit event log
    mapping(address => uint) balance;    
    event depositCompleted(address from, uint amount);    

    // Minimum # of signatures for approval
    uint public numSignaturesRequired;

    // Struct of transaction
    struct Transaction {
        address payable to;
        uint amount;
        uint numSignatures;
        bool executed;
        uint txIndex;
    }
    
    // Logs
    event Created(uint _txIndex, uint _amount, address _creator, address _to);
    event Signed(uint _txIndex, uint _numSignatures, address _signer);
    event Sent(uint _txIndex);
    
    

    // Array of transfer requests
    Transaction[] public transactionRequests;
    

    // Storage of approvals
    mapping(uint => mapping(address => bool)) public approvals;    


    // Modifier to ensure transaction exists
    modifier txExists(uint _txIndex) {
        require(_txIndex < transactionRequests.length, "Transaction does not exist");
        _;
    }
    

    // Modifier to ensure transaction not confirmed
    modifier notConfirmed(uint _txIndex) {
        require(!approvals[_txIndex][msg.sender], "Transaction already confirmed");
        _;
    }


    // Modifier to ensure transaction is not executed
    modifier notExecuted(uint _txIndex) {
        require(!transactionRequests[_txIndex].executed, "Transaction already executed");
        _;
    } 


    // Initalize authorized-owners list and limit
    constructor(address[] memory _authorized, uint _numSignaturesRequired){
        require(_authorized.length > 0, "Authorized signers required");

        for (uint i = 0; i < _authorized.length; i++) {
            address owner = _authorized[i];

            require(owner != address(0), "Not authorized");
            require(!isAuthorized[owner], "Signer not unique");

            isAuthorized[owner] = true;
            authorized.push(owner);
        }

        numSignaturesRequired = _numSignaturesRequired;
        
    }

    
    // Functions needed for transfer: create, sign and execute
    function createTransfer(address payable _to, uint _amount) onlyAuthorized public {
        
        emit Created(transactionRequests.length, _amount, msg.sender, _to);

        transactionRequests.push(Transaction({
            to: _to,
            amount: _amount,
            numSignatures: 0,
            executed: false,
            txIndex: transactionRequests.length
        }));
    }
    
    
    
    function signTransfer(uint _txIndex) onlyAuthorized txExists(_txIndex) notConfirmed(_txIndex) notExecuted(_txIndex) public {
        Transaction storage transaction = transactionRequests[_txIndex];
        transaction.numSignatures += 1;
        approvals[_txIndex][msg.sender] = true;
        
        emit Signed(_txIndex, transactionRequests[_txIndex].numSignatures, msg.sender);

    }



    function executeTransfer(uint _txIndex) onlyAuthorized txExists(_txIndex) notExecuted(_txIndex) public {
        
        emit Sent(_txIndex);
        
        Transaction storage transaction = transactionRequests[_txIndex];
        require(transaction.numSignatures >= numSignaturesRequired, "Cannot execute transaction");
        transaction.executed = true;
        transaction.to.transfer(transaction.amount);
    }


    function getAuthorized() public view returns (address[] memory) {
        return authorized;
    }
    
    function getTransactionCount() public view returns (uint) {
        return transactionRequests.length;
    }

    function getTransaction(uint _txIndex) public view returns (address to, uint amount, uint numSignatures, bool executed)
    {
        Transaction storage transaction = transactionRequests[_txIndex];

        return (
            transaction.to,
            transaction.amount,
            transaction.numSignatures,
            transaction.executed
        );
    }



    // Anyone can deposit
    function deposit() public payable returns (uint){
        balance[msg.sender] += msg.value;
        emit depositCompleted(msg.sender, msg.value);
        return balance[msg.sender];
    }
 
    //Return individual depositor balance   
    function getBalance() public view returns (uint){
        return balance[msg.sender];
    }    

    //Return contract balance    
    function getContractBalance() public view returns (uint){
        return address(this).balance;
    }    
    
}
1 Like

This is my contract for the multisig wallet.
I had a hard time with it and ended up watching all the videos by the end. I wanted to add the ability to add and remove owners which took forever.
For some reason it isn’t working when i try to run it so any help would be awesome. Thanks!

pragma solidity 0.8.0;

contract multisig {
    
    address [] public ownersArray;
    uint limit;
    
    struct ownershipStruct {
        uint listPointer;
    }
    
    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 => ownershipStruct) public ownershipStructs;
    
    mapping(address => mapping(uint => bool)) approvals;
    
    modifier onlyOwners(){
        bool owner = false;
        for(uint i=0; i<ownersArray.length;i++){
            if(ownersArray[i] == msg.sender){
                owner = true;
            }
        }
        require(owner == true);
        _;
    }
    
    constructor(address[] memory _owners) {
        ownersArray = _owners;
        limit = ownersArray.length - 1;
    }
     //I put in a way to add and remove owners
    function isOwner(address _address) public view returns(bool isIndeed) {
        if(ownersArray.length == 0) return false;
        return (ownersArray[ownershipStructs[_address].listPointer] == _address);
    }
    
    function getOwners () public view returns(address [] memory) {
        return ownersArray;
    }
    
    function newOwner (address _address) public onlyOwners returns(bool success){
        if(!isOwner(_address)) revert();
        ownersArray.push(_address);
        ownershipStructs[_address].listPointer = ownersArray.length -1;
        limit + 1;
        return true;
    }
    
    function deleteOwner(address _address) public onlyOwners returns(bool success){
        if(!isOwner(_address)) revert ();
        uint rowToDelete = ownershipStructs[_address].listPointer;
        address keyToMove = ownersArray[ownersArray.length-1];
        ownersArray[rowToDelete] = keyToMove;
        ownershipStructs[keyToMove].listPointer = rowToDelete;
        ownersArray.pop();
        delete ownershipStructs[_address];
        limit - 1;
        return true;
    }
    
    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].hasBeenSent == false);
        
        approvals[msg.sender][_id] = true;
        transferRequests[_id].approvals++;
        
        emit ApprovalReceived(_id, transferRequests[_id].approvals, msg.sender);
        
        if(transferRequests[_id].approvals >=limit){
            transferRequests[_id].hasBeenSent = true;
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
            emit TransferApproved(_id);
        }
    }
    
    function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }
    
}

1 Like

Hello Guys,
Is there a unshift method for arrays in Solidity ?

pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet {
    address[] public owners;
    uint limit;
    
    event transferRequest (address _emitter,uint _amount,address _receiver ,uint _id); //new transfer request
    event upDatedRequest (uint _id,uint _approvals,address _approver); // new approval on transfer request
    event transferHasBeenSent (uint _id,address _receiver,uint _amount);
    
    struct Transfer{ 
        uint amount;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
    
    Transfer[] transferRequests;
    
    mapping(address => mapping(uint => bool)) approvals; 
    
    //only allow people in the owners list to continue the execution.
    modifier onlyOwners(){
        bool testSender ;
        for (uint i=0 ; i<owners.length;i++){
            if (owners[i] == msg.sender){
            testSender=true;
            break;
            }
        }
        require (testSender == true, "You are not allowed to access this function");
        _;
    }
    
    //initialize the owners list and the limit 
    constructor(address[] memory _owners, uint _limit) {
        owners = _owners;
        owners.push(msg.sender); 
        limit = _limit;
    }
    
    //Empty function
    function deposit() public payable {}
    
    
    function getWalletBalance() public view onlyOwners returns (uint){
        return (address(this).balance);
    }
    
    //Create an instance of the Transfer struct and add it to the transferRequests array
    function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
        Transfer memory transferX = Transfer (_amount, _receiver, 0, false, transferRequests.length); 
        emit transferRequest (msg.sender,_amount,_receiver,transferRequests.length);
        transferRequests.push(transferX);
    }
    
   
    function approve(uint _id) public onlyOwners {
        //An owner should not be able to vote twice.
        require (approvals[msg.sender][_id] == false, 'You already approved this transfer request'); 
        //An owner should not be able to vote on a tranfer request that has already been sent.
        require (transferRequests[_id].hasBeenSent == false, 'This transfer has already been executed'); 
        
        //Set your approval for one of the transfer requests.
        //update the mapping to record the approval for the msg.sender.
        approvals[msg.sender][_id] = true; 
        
        //update the Transfer object.
        transferRequests[_id].approvals++;
        
        emit upDatedRequest (transferRequests[_id].id,transferRequests[_id].approvals,msg.sender);
        
        //When the amount of approvals for a transfer has reached the limit, this function should send the transfer to the recipient.
        if (transferRequests[_id].approvals>=limit){
            (transferRequests[_id].receiver).transfer(transferRequests[_id].amount);
            transferRequests[_id].hasBeenSent = true;
        }
        
        emit transferHasBeenSent (transferRequests[_id].id,transferRequests[_id].receiver,transferRequests[_id].amount);
    }
    
    //return all transfer requests
    function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }
}

Hello guys,
I finally made it to the end with Filip’s assistance. Since I am not the programmer, it was to hard to write the code just by myself. But at the end I tried and did it anyway with small hints when I stuck.
I understand the code so i commented on almost every line what means what. If there is something missing fell free to correct me:

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;
    
    //events to be logged to check (with emit command later) what is happening through executions 
    event transferRequestCreated(uint _id, uint _amount, address _initiator, address _receiver);
    event Approvalreceived(uint _id, uint _approvals, address _approver);
    event TransferApproved(uint _id);
    
    //for searching in database
    mapping(address => mapping(uint => bool)) approvals;
    
     //Should initialize the owners list and the number of approvals-at the deploy state
    constructor(address[] memory _owners, uint _limit) {
        owners=_owners;
        limit=_limit;//num of approvals
    }
    
    //Empty function-anyone can deposit
    function deposit() public payable {}
    
    //Should only allow people in the owners list to continue the execution.
    //It should be pasted like this:
    //different addresses, separated with comas and each in double quotations, all of them in arrays
    //["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"]
    modifier onlyOwners(){
        //necessarry to set to false at the beginning, can be anything:
        bool owner=false;
        //after that try to find if it is in the list of owners:
        for (uint i=0;i<owners.length;i++){
            //check if msg.sender is anyone's adress from the owners list
           if (owners[i]==msg.sender){
               owner=true;
           } 
        }
        //out of the loop we reqire the condition is true
        require(owner==true);
        _;//in oreder to continue to execution
    }
    
    //Create an instance of the Transfer struct and add it to the transferRequests array
    //the right condition is made by "modifier onlyOwners"
    function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
        emit transferRequestCreated(transferRequests.length, _amount, msg.sender, _receiver);
        //importing in transferRequests array (struct)
        //first 'transferRequests.length' is zero when you start and 1,2,3... later on which is id 
        transferRequests.push(
            Transfer(_amount,_receiver,0,false,transferRequests.length));
    }
    
    //input transfer id you want to approve-is saved in approvals in double mapping
    function approve(uint _id) public onlyOwners {
        //double mapping priciple: approvals:[address]->[mapping]==true/false
        //make sure msg.sender did not voted for this _id yet and not twice
        require(approvals[msg.sender][_id]==false);
        //transferRequests is boolean, number id means which number of existing booleans it is
        //An owner should not be able to vote on a transfer request that has already been sent.
        require(transferRequests[_id].hasBeenSent==false);
        //now we finally set, after requirements, that condition is true, msg.sender approved this transfer id
        approvals[msg.sender][_id]=true;
        //increasing approvals from 0 to 1
        transferRequests[_id].approvals++;
        
        emit Approvalreceived(_id, transferRequests[_id].approvals, msg.sender);
        //chech if we reach the number of approvals (defined in state variable "limit")
        if(transferRequests[_id].approvals>=limit){
            //set to true
            transferRequests[_id].hasBeenSent=true;
            //now make a transfer for the specified amount
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
            emit TransferApproved(_id);
        }
    }
    
    //Should return all transfer requests
    function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }
    
    //current contract balance
    function getContractBalance() public view returns (uint){
        return address(this).balance;
    }
}
1 Like

Finally done, made sure to take time with this project.
It was hard, as the first line of code I ever wrote was one month ago in the javascript course.
But I took a couple of days with only google and youtube, didn’t quite get all the parts together but was surely a learning experience!

After a few days, I took the help of the template and Filip’s assistance.
Added some extra functionality like keeping track of funds which required me to add another ā€œelementā€(?) to the struct and seems to work well! And a show balance function :slight_smile:

Im excited and looking forward to trying building something small with what I’ve learnt before I jump into the 201 courses, any tips/ideas on what to build? There are tons of youtube videos on this subject so I’m sure I’ll find something to work on.

See you in the next course!

If anyone else is reading this and is all-in on this blockchain programming journey and want to connect, feel free to drop me a DM on IG @imsamm8.

pragma solidity 0.7.5;
pragma abicoder v2;
// ["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"]
contract Wallet {
    address[] public owners;
    
    uint limit;
    
    struct Transfer{
        uint amount;
        address payable receiver;
        address _theSender;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
    
    Transfer[] transferRequests;
    
    mapping(address => mapping(uint => bool)) approvals;
    mapping(address => uint) balance;
    
    //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,"You are not an owner");
        _;
        
    }
    //Should initialize the owners list and the limit 
    constructor(address[] memory _owners, uint _limit) {
        owners = _owners;
        limit = _limit;
    }
    event TransferRequestCreated (address _from, address _to, uint _amount);
     
    //Empty function
    //Add balance count
    function deposit() public payable {
        balance[msg.sender] += msg.value;
    }
    
    //show balance
    function showBalance() view public returns(uint) {
        return balance[msg.sender];
    }
    
   
    //Create an instance of the Transfer struct and add it to the transferRequests array
    function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
        require(balance[msg.sender] >= _amount, "Insufficent funds");
        
        transferRequests.push(
            Transfer(_amount, _receiver,msg.sender, 0, false, transferRequests.length)
            );
        emit TransferRequestCreated(msg.sender,_receiver,_amount);
    }
    
    function approve(uint _id) public onlyOwners {
        
        require(transferRequests[_id].hasBeenSent == false);
        require(approvals[msg.sender][_id] == false);
        
        approvals[msg.sender][_id] = true;
        transferRequests[_id].approvals++;
       
        if (transferRequests[_id].approvals >= limit){
            transferRequests[_id].hasBeenSent = true;
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
            balance[transferRequests[_id]._theSender] -= transferRequests[_id].amount;
        }
        
    }
    
    //Should return all transfer requests
    function getTransferRequests() public view returns (uint){
        return transferRequests.length;
    }
    
    
}
1 Like

I was getting stuck my original attempt without using the template or watching the video (multisigWallet.sol). But i took a peak at the answer forumn after over a week of trying to figure this out
but i realized i was trying to add features that werent neccesarly required which resulted in me starting over and working from the template

ProjectTemplate.sol
pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet {
    
    address[] internal owners;
    uint limit;
    
    
    struct Transfer{
        uint amount;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }
    
    Transfer[] transferRequests;
    
     mapping(address => mapping(uint => bool) ) approvals; // mapping[address][txId] => true/false 
    
    //Should only allow people in the owners list to continue the execution.
    modifier onlyOwners() {
       bool isOwner;
       for(uint i=0; i < owners.length - 1; i++){
           if( owners[i] == msg.sender ){
               isOwner = true;
           }
       }
       require(isOwner, "You do not own this account");
       _;
      }
      
    constructor(address[] memory _owners, uint _limit){
     require(_owners.length > 1, "More than one address required");
     require( _limit > 1, "More than one approval required");
     require( _limit <= _owners.length, "number of approvals exceeds number of addresses");
     
     bool _duplicate;
     for(uint i = 0; i <= _owners.length - 1; i++) {
        for(uint j = i + 1; j < _owners.length; j++) {
            if(_owners[i] == _owners[j]){
                _duplicate = true;
                break;
            }
        }   
            if(_duplicate == false){
                owners.push(_owners[i]);
            }
     }
     require(_duplicate == false, "Matching owner addresses not allowed");
     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 {
        require(_amount > 0, "amount must be greater than 0");
        require(address(this).balance >= _amount, "Balance is too low");
        
        transferRequests.push(Transfer(_amount, _receiver, 0, false, transferRequests.length));
        
        approvals[msg.sender][transferRequests.length] = true; //Creator of transfer automaticly signs off
        
    }
    
    //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, "You already approved this transfer.");
        require(transferRequests[_id].hasBeenSent ==false, "This transfer has been sent");
        
        approvals[msg.sender][_id] = true;
        transferRequests[_id].approvals++;
        
        if(transferRequests[_id].approvals >= limit){
            transferRequests[_id].hasBeenSent = true;
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
        }
   }
   
   //Should return all transfer requests
    function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }
   ///

    }
MultisigWallet.sol
pragma solidity 0.7.5;
pragma abicoder v2;
import "./destruct.sol";


contract Wallet {
    
    address[3] owners;
     
    uint limit;  //# of approvals needed
    
    struct Transfers {
        address from;
        address to;
        uint amount;
        uint signatures;  //approvals
        uint txId;  //false, until signed by one other address
        bool approved;
    }
    
    Transfers[] transfers; //potential transfers saved here and referenced by a function that takes the transfer by index and checks for 2 approvals.
    
      modifier onlyOwners() {
        require(msg.sender == owners[0] || msg.sender == owners[1] || msg.sender == owners[2], "Function not initiated by contract owner.");
        _; // If true, continue execution
      }
   
     
     constructor(address _owner0, address _owner1, address _owner2){
     // owners[0] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; // insert owners here 
     // owners[1] = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
    //  owners[2] = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;
      
      owners[0] = _owner0; // insert owners here 
      owners[1] = _owner1;
      owners[2] = _owner2;
    }

    mapping(address => uint) balance;
    mapping(address => mapping(uint => bool) ) approvals; // mapping[address][txId] => true/false 
    
   function Owners(address _owner) internal view returns(bool)
    {
        for(uint i = 0; i < owners.length; i++) {
            if (owners[i] == _owner) {
                return true;
            }
        }

        return false;
    }
    
    
    function transfer(address _to, uint _amount) public onlyOwners payable returns(string memory) {
        transfers.push( Transfers ( msg.sender, _to, _amount, 1, transfers.length, false) );
        approvals[msg.sender][transfers.length] = true;
        return  ("Transfer requested");
    } 
     
    function getTransfer(uint _txId) public view returns(address from, address to, uint amount, uint signatures, uint txId) {
        return (transfers[_txId].from, transfers[_txId].to, transfers[_txId].amount, transfers[_txId].signatures, transfers[_txId].txId );        
    }

   function deposit() public onlyOwners payable {
     }
     
   function getBalance() public view  returns (uint256) {
         return address(this).balance;
     }
     
   function sign(address _name, uint _txId) public onlyOwners returns (string memory) {
      // require(transfers[_txId].from != msg.sender, "You already signed this transfer.");
      // require( _name == msg.sender, "This is not your address");
       require(approvals[_name][_txId] == false, "you have already approved this transfer");
       
       approvals[_name][_txId] = true;
       
       if(approvals[_name][_txId] = true) {
           transfers[_txId].signatures += 1;
       } else {
           return "approval failed";
       }
       //assert( );
       return "You have approved the function";
   }
   
   function send( uint _txId) public onlyOwners payable returns (string memory) {
       //check number of approvals >= 2.
       if(transfers[_txId].signatures < 2) {
           return "Transfer not approved";
       } else {
            //transfer the funds
       _transfer(transfers[_txId].from, transfers[_txId].to, transfers[_txId].amount);
       
       }
       return "Transfer sent";
   }
   
   //Make sure funds are tranfering from the contracts address, (not one of the owners address), to whoever is recieving.
   function _transfer(address from, address to, uint amount) private onlyOwners  {
        balance[from] -= amount;
        balance[to] += amount;
        //emit balanceTransfered(amount, msg.sender, to );
    }
   
     
   /* function withdraw(uint _txId ) public onlyOwners payable {
        require (address(this).balance >= transfers[_txId].amount);
        msg.value -= transfers[_txId].amount;
        transfers[_txId].to += msg.value;
    }   
    
    function destroy() public onlyOwners payable {
        selfdestruct(address(this));
    }
    */
}
1 Like

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

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;

//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 _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 {
    emit TransferRequestCreated(transferRequests.length, _amount, msg.sender, _receiver);
    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] == false);
    require(transferRequests[_id].hasBeenSent == false);
    
    approvals[msg.sender][_id] = true;
    transferRequests[_id].approvals++;
    
    emit ApprovalReceived(_id, transferRequests[_id].approvals, msg.sender);
    
    if(transferRequests[_id].approvals >= limit){
        transferRequests[_id].hasBeenSent = true;
        transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
        emit TransferApproved(_id);
    }
}

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

}

1 Like

Assignment project:

pragma solidity 0.8.7
contract multisigWallet {
address private owners
mapping(address => uint)
modifier isOwner () {
require(msg.sender == owner);
}
function wallet() {
owner = msg.sender;
}
constructor{
uint address owner1;
uint address owner2;
uint address owner3;
}
struct Transfer{
address from
address to
uint Id
uint amount
bool sig1;
bool sig2;
bool sig3;
}
mapping(address => mapping(uint => bool));
event transfer(address, uint, amount)
event deposit(address, uint, amount)
event signTransaction(address, uint, amount)
event withdraw(address, uint, amount)
function addOwner {
owner.push (owner1)
owner.push (owner2)
owner.push (owner3)
function transfer(address recipient uint amount){
require (balance[address] >= amount)
msg.sender.transfer(amount);
transaction.numOfSig = 0;
transaction.from = msg.sender;
transfer.to = to;
transfer.amount = amount;
balance[transfer.to] += transfer.amount;
balance[transfer.from] -= transfer.amount
}
function signTransaction (msg.sender, transactionId) {
public onlyOwner
require (msg.sender == owner)
require (transaction.sig[msg.sender] != 1)
if (transaction.sig == true) {
transaction.numOfSig ++;
}
if (transaction.numOfSig >= 2) {
transaction.to.transfer(amount);
}
}
function deposit (uint transactionId) {
public payable
deposit(msg.sender, msg.value);
}
function withdraw() {
public payable
for (i = 0, i < transaction.length, i++) {
if (transactionId == transaction [i]) {
replace = 1;
delete transaction[transactionId];
}
}
msg.sender.transfer(amount);
}
public view returns
return balance[owner1]
return balance[owner2]
return balance[owner3]
}
}

1 Like