Project - Multisig Wallet

Hi @M.K

Owner.length is the length of the owner array.
This is a basic concept that you should know very well at this point as it is fundamental to continue the courses.
I highly suggest to follow the javascript programming course or to watch it once again if you already did.
It is important to know the basic of programming before diving deep into Solidity:
https://academy.ivanontech.com/products/javascript-programming-for-blockchain-developers/categories

Happy learning,
Dani

1 Like

Hi @M.K

so we have the address array which stores all of the addresses of the wallet owner. Lets say we have 3 owners in this array like you have. So one of the conditions we want in our smart contract is that only a wallet owner can create a transaction for example. If we only had one owner this would be very simple. We would just use in the onlyOwners modifier

require(owner = msg.sender);

and then in our create transaction function we would include

function createTransaction(...arguments) public OnlyOwners {...

however since we have multipe wallet owners in this case this will not work. we need to check all of the members in the address owner array to see if the transaction is being created by one of them. if the transaction is not being created by one of the owners then we want our create transfer function to revert and not run. This is where the for loop comes in. Think of a for loop as just a way of checking all of the elements in a list. Every for loop takes in a loop variable whos value is equal to that of the current element of the list.

So say we have a list that consists of 5 numbers. Now you cant do this in solidity but say you want to print out this loops contents. One way to do it would be this (Code below is just pusdeo code style not language specific)(You cannot print like this in solidity FYI)

for (int i = 0; i < 5; i++)
{
      print( i)
{

when our program is running and it gets to this loop it runs the loop for as many times as specified by the conditions inside the braket. So in this case the loop will get run 6 times (6 times as i starts at 0 so 0 ,1 ,2 ,3, 4, 5). In the first iteration i = 0 and the print function is executed for i = 0; then since i < 5 the loop starts again. and because we specify i++ (which is just i = i + 1) i is no 1 and the loop runs with i as 1. Thus the print statment gets returned for i =1, this proccess continues non stop until i becomes 6. When i becomes 6 the loop condition is no longer satisfied because 6 is not smaller or equal than 5. thus the loop ends and the program proceeds to whatever code is after the loop.

So going back to this example. we want to check if any of the addresses in our address is in fact the owner. So we make a loop going from 0 to owners.length which is just the length of our array (we want to check the full thing of course so this is a way of doing it). Then the bool owner = false. Say the owner is the last person in the array. for the first iteration i = 0 and checks the first address which is not the owner thus i gets incremented and the loop runs again for i = 1, we check the middle element and it is also not the owner. i gets incremented and the loop runs for the final time where i = 2 and checks the 3rd address and this is the owner so bool owner = true and now the loop is complete and now that owner = true our require statement is met. If perchance the owner was at the first index in the array the loop would still run even though we found the owner because the loop statment specifies it to.

If you wanted to get more advanced to save computation you could check if the owner has been found and then include a break to prevent the checking of needless checks. However if your curious about this you could look up that yourself.

So lastly in regards to your code it will not work because i is just a placeholder uint variable. Meaning it has not been assigned to anything. So when you call

if(owners[i] == msg.sender){

you will get an error because i has no defined value yet it needs to be assigned to something. Also say you had of set i to 0. It still would not work because you would only be doing one check more specifically checking the 0th or first element of the array. there would still be two other addresses that could possibly be the owner. So that is why you need the loop so that these other checks can be done.

I hope that helps in some way.

EDIT***

If the whole modifiers thing is confusing you could just include the code in the modifier directly into the functions you want to use it in, namely the createTransfer function here. Then remove the onlyOwners part from the function statment.

3 Likes

Hi @M.K

If there are 3 owners addresses,
Does the value of owners.length in the for loop mean
1. 3 ?
or
2. the 3 owners addresses ? eg
0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 ,

Owner.length means 3 as in my example posted yesterday.

[“a”,“b”,“c”]
length =3
1 Like

Hi mcgrane5, Thanks for your reply

I think i got it now.

owners.length as explained by Daniele is the number of owners in the owners list. So it is 3, if there are 3 owners addresses.

As you explained, for loop will loop three times to see if any of the three owners addresses match msg.sender

As for owners[i]== msg.sender, it means compare the three owners addresses to see if it matches this msg.sender address.

Thank you very much, It definitely helped me in my understanding .
Appreciate it

1 Like

hi @M.K

No worries Im very much a learner too but anytime i can try explain something it reinforces my learning too. So glad we both benefited.

Regards,
Evan

1 Like

Hi Evan,

I looked through your code and
i have questions

function getUsers() public view returns(address[] memory)
{
return owners;
}

function getApprovalLimit() public view returns (uint)
{
return (limit);
}

I noticed sometimes we use parentheses when we return and sometimes we do not.
So that means, when we return the array , we dont need to () it ?

And if we return the limit, which is an uint, we () it ?

Can you let me understand the logic of this part ?

Cheers,

hi M.K

Hahaha no that just me being inconsistent its probably more conventional not use parentheses for the return statement im pretty sure but sometimes my typing ahead of my brain lol. It doesnt affect code execution though. I suppose its whatever you prefer. i probably should remove them though incase it confuses readers.

1 Like

HI Evan,

Just to check how to code if i want to just return the number of approvals recorded.

// my struct
struct Transfer {
uint amount;
address receiver;
uint NumberOfApprovals;
bool HasSent;
uint id;
}

// array with struct elements
Transfer[]transferRequest;

// create function to see pending transfer requests
function transferLists ( ) public view returns(Transfer [] memory ) {
return transferRequest;
}

// to return just the status of number of approvals
function SeeNumberOfApprovals () public view returns ( uint) {
return transferRequest[].NumberOfApprovals;
}

Is the last function correct ? If i want to single out one element in the struct to be returned, is this the right way to write the code ? is the [] needed or not ?

Cheers,

You should put in the square brackets the id, so its just .

return transferRequests[_id].NumberOfApprovals

It should work perfectly if each time the approve function is called that you increment transferRequests[_id].NumberOfApprovals. So in the approve function write

transferRequests[_id].NumberOfApprovals++

then your SeeNumberOfApprovals function should work.

1 Like

That means it should be

function SeeNumberOfApprovals ( uint _id) public view returns (uint{
return transferRequests[_id].NumberOfApprovals;

}

Right ?

And Thanks for the edit and pointing me in the right direction. I think i understand more after many sets of questions and answers, This forum is indded helpful. Thank Evan.

1 Like

Yeah the forums are soo good. there great fro learning. Yes that should work for getting the number of current approvals returned to the screen. However if you run into any errors try to read the error thrown in the terminal. You can click each output to get more information in remix sometimes reading them helps a lot.

2 Likes

I was getting really frustrated with Remix. I couldn’t figure out why my contract wouldn’t deploy. Then I watched the final video, and saw that you have to put the address array input with square brackets and double quotations.

Thanks for posting that Narnia. I was having similar frustrations yesterday (my contract was compiling ok in Remix but then throwing an error when I attempted to deploy it). I knew about an array argument needing to be given in square brackets but I wasn’t putting each address element in double-quotes. My constructor code (that received the array of addresses) was therefore erroring upon ‘Deploy’ and I was thinking I must have an issue with my Solidity code (and even braved the Remix debugger last night to prove it was erroring in the constructor). This morning I’ve been searching the forum to see what I was doing wrong! Thanks again.

2 Likes
pragma solidity 0.7.5;
pragma abicoder v2;


contract MultiSig{
    
     address[] public owners;
     uint numberOfOwners = owners.length;
     uint approvalsRequired;
    
    
    modifier onlyOwners {
        bool oneOfOwners;
        for (uint i = 0; i < numberOfOwners; i++){
            if (owners[i] == msg.sender){
                oneOfOwners=true;
            }
        }
        require(oneOfOwners==true, "You must be a verified owner to sign this transaction");
        _;
    }
    
    constructor(address[] memory _owners, uint _approvalsRequired){
        approvalsRequired = _approvalsRequired;
        owners = _owners;
        approvalsRequired = _approvalsRequired;
    }
    
    event balanceAddedToContract(address indexed from, uint amount);
    event transferred(address indexed from, address indexed to, uint amount);
    
    struct Transfer{
        uint txId;
        address payable transferTo;
        uint amount;
        uint approvalsCounted;
        bool txHasBeenSent;
    }
    
    Transfer[] transfers;
    
    mapping(address => mapping(uint => bool)) txApprovals;


    
    function deposit() public payable returns(uint){
        emit balanceAddedToContract(msg.sender, msg.value);
        return address(this).balance;
    }
    
    function createTransferRequest(address payable _transferTo, uint _amount) public onlyOwners{
        require(address(this).balance >= _amount);
        txApprovals[msg.sender][transfers.length] = true;

        transfers.push(Transfer(transfers.length, _transferTo, _amount, 1, false));  
    }
    
    
    function approveRequest(uint _txId) public onlyOwners {
        require(txApprovals[msg.sender][_txId] != true, "You have already approved this request");
        require(transfers[_txId].txHasBeenSent != true, "This transaction has already been sent");
        
        txApprovals[msg.sender][_txId] = true;
        transfers[_txId].approvalsCounted += 1;
        if (transfers[_txId].approvalsCounted == approvalsRequired) {
            transferFunds(_txId);
            transfers[_txId].txHasBeenSent = true;
        }
        
    }
    
    function transferFunds(uint _txId) public {
        require(address(this).balance >= transfers[_txId].amount);
        _transferFunds(_txId);
        emit transferred(address(this), transfers[_txId].transferTo, transfers[_txId].amount);
    }
    
    function _transferFunds(uint _txId) private {
        transfers[_txId].transferTo.transfer(transfers[_txId].amount);
    }
    
    function checkBalance() public view returns(uint){
        return address(this).balance;
    }
    
    function getTransferRequests() public view returns(Transfer[] memory _transfers) {
        return transfers;
    }
    
}
1 Like

Here is my MulitSigWallet project! this was a TON of fun to build I look forward to using this new knowledge to build more!

pragma solidity 0.7.5;
pragma abicoder v2;



contract multiSigWallet {
  
  uint private TotalBalance;
  
  //Owners
  address private Owner1 = address(0);
  address private Owner2 = address(0);
  address private Owner3 = address(0);
  
  //Transaction struct
  struct Transactions {
      address payable recipient;
      uint amount;
      address initiatior;
      bool signature1;
      address  countersigner;
      bool signature2;
      bool status; 
  }
  
  //array for logging the Transactions
  Transactions[] private TransactionLog;
  
  constructor() {
      Owner1 = msg.sender;
  }
  
  //modifier
  modifier OnlyAnOwner() {
      require(msg.sender == Owner1 || msg.sender == Owner2 || msg.sender == Owner3, "You dont own any of these wallets!!");
      _;
  }
  
  //one time function
  function SetNewOwners(address _Owner2, address _Owner3) public {
      require(msg.sender == Owner1, "You cannot access other wallets to define new owners");
      require(Owner2 == address(0) && Owner3 == address(0), "Wallets have already been defined.");
      Owner2 = _Owner2;
      Owner3 = _Owner3;
  }
  
  //events
  event depostiDone(address indexed depositedBy, uint ammount);
  event TransactionStarted(address indexed initiatior, address indexed recipient, uint ammount);
  event signatureDone(uint indexed transaction, address indexed Signatore, bool approval);
  event transactionDone(uint index, address indexed recipient, uint amount);
  
  
  //functions
  function getTransaction(uint index) public view OnlyAnOwner returns(Transactions memory) {
      return (TransactionLog[index]);
  }
  
  function SignTransaction(uint index, bool approval, bool send) public OnlyAnOwner {
      
      require(TransactionLog[index].status != true, "This transaction has already been executed.");
      require(msg.sender != TransactionLog[index].initiatior,"You have initiated the transaction now you need another signature.");
      
      TransactionLog[index].countersigner = msg.sender;
      TransactionLog[index].signature2 = approval;
      
      emit signatureDone(index, msg.sender, approval);
      
      if (send) {
          ExecuteTransaction(index);
      }
  }
  
  function InternalizeTransaction(address payable _recipient, uint _amount) public OnlyAnOwner {
      Transactions memory newTransaction;
      newTransaction.recipient = _recipient; //not sure why i have an error here?
      newTransaction.amount = _amount;
      newTransaction.initiatior = msg.sender;
      newTransaction.signature1 = true;
      newTransaction.countersigner = address(0);
      newTransaction.signature2 = false;
      newTransaction.status = false;
      
      insertTrasaction(newTransaction);
      emit TransactionStarted(msg.sender, _recipient, _amount);
  }
  
  function insertTrasaction (Transactions memory newTransaction) private {
      TransactionLog.push(newTransaction);
  }
  
  function ExecuteTransaction(uint index) public payable OnlyAnOwner{
      //checks
      require(TransactionLog[index].status != true, "This Transaction already exists!!");
      require(TransactionLog[index].signature1 == true && TransactionLog[index].signature2 == true, "Not enough signatures!!!");
      require(TotalBalance >= TransactionLog[index].amount, "Not enough funds!!!");
      
      //transfer and balance update
      TransactionLog[index].recipient.transfer(TransactionLog[index].amount);
      TotalBalance -= TransactionLog[index].amount;
      
      TransactionLog[index].status = true;
      
      emit transactionDone(index,TransactionLog[index].recipient, TransactionLog[index].amount);
  }
  
  function deposit() public payable returns(uint) {
      TotalBalance += msg.value;
      emit depostiDone(msg.sender, msg.value);
      return TotalBalance;
  }
  
  function getOwners() public view OnlyAnOwner returns(address, address, address){
      return (Owner1, Owner2, Owner3);
  }
  
  function getWalletBalance() public view returns(uint){
      return TotalBalance;
  }
  
  
}
1 Like

Here’s my solution (Github repo) - see the MultiSig Wallet Project folder: https://github.com/msuscens/Solidity7-101

This was a good challenge and fun to build. I have separated the wallet owners and the Tx approvers (using two contracts from which the multi-sig wallet contract inherits). This means that the approvers potentially don’t need to be the owners (the constructor in the code would need to receive a separate array of approvers as it is currently written to simply pass the owners in as approvers). My solution was developed without looking at the project assistance video and I have yet to review Fillip’s solution, so I may refine my solution after watching the project solution video and/or if anyone has any improvement comments on my solution! :slight_smile:

1 Like

After some help from Filip for aprove function, and a lot of headache, I think i did something!

pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet{
    mapping(address => mapping(uint => bool)) aprovals;
    mapping(address =>uint)balance;
    event desposinfo(uint amount,address indexed desposeto);
   event transfadded(address _reciver,uint _amount,uint id,address owner);
    address payable owner;
    uint limit;
    
    modifier aprovalsOnly{
   require(msg.sender==owner);
   _;
    }
   
    address []public owners;
    
    constructor(address payable _owner1,address payable _owner2,address payable _owner3,uint _limit){
   owners.push(_owner1);
   owners.push(_owner2);
   owners.push(_owner3);
   owner=msg.sender;
   limit=_limit;
         }
   
    struct Transfer{
          address payable reciver;
        uint aprovals;
        uint amount;
        uint id;
        bool alreadysend;
    }
    function desposit()public payable returns(uint){
        balance[msg.sender] += msg.value;
         emit desposinfo(msg.value,msg.sender);
        return balance[msg.sender];
    }
   
    function getbalance()public view returns(uint){
        return balance[msg.sender];
    }
    Transfer[] transrequests;
    
    function addtransfers(address payable _reciver,uint _amount)public aprovalsOnly{
        emit transfadded(_reciver,transrequests.length,_amount,msg.sender);
        transrequests.push(Transfer(_reciver,_amount,0,transrequests.length,false));
    } 
    function aprove(uint _id)public  aprovalsOnly{
        require(aprovals[msg.sender][_id] == false);
        require(transrequests[_id].alreadysend == false);
        aprovals[msg.sender][_id] = true;
        transrequests[_id].aprovals++;
        if(transrequests[_id].aprovals>=limit){
           transrequests[_id].alreadysend = true;
           transrequests[_id].reciver.transfer(transrequests[_id].amount);
        }
    }
    function gettransfers()public view returns(Transfer[]memory){
        return transrequests;
    }
     
}
2 Likes

So here is my shot at this.
Looking forward to get some creative feedback ;).

SharedOwnable.sol:


pragma solidity 0.7.5;

contract SharedOwnable {
    
    address[] public owners;
    
    modifier onlyOwner {
        bool isOwner = false;
        for(uint i=0; i<owners.length; i++) {
            if(msg.sender == owners[i]) {
                isOwner = true;
                break;
            }
        }
        require(isOwner, "You need to be one of the owners");
        _; // run the function
    }
    
    
    constructor(address[] memory _owners) {
        owners = _owners;
    }
}

MultiSig.sol:

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.7.5;

import "./SharedOwnable.sol";

// Assumptions:
//      - the creator of the contract is not required to be one of the approvers
//      - each transfer request is implicitly approved by the creator of the transfer requests (hence receives 1 implicit approve)

contract MultiSig is SharedOwnable {
    struct TransferRequest {
        address payable recipient;
        uint amount;
        uint numApproves;
        uint txid;
        bool isExecuted;
    }
    
    // We want to keep track of approvers. Will initialize to false when transaction
    // is created, and check whenever the approve function is called.
    mapping(address => bool[]) transferApprovals;
    
    // The list of transactions
    TransferRequest[] public transferRequests;
    
    // Indicates the number of approves required, including the creator of a request.
    uint public minApproves;       
    
    constructor(address[] memory _owners, uint _minApproves) SharedOwnable(_owners) {
        require(_minApproves > 1, "You need at least 1 approver for a transaction");
        minApproves = _minApproves;
    }
    
    function deposit() public payable returns(uint){
        return address(this).balance;
    }
    
    function createTransferRequest(address payable _recipient, uint _amount) public onlyOwner {
        uint txid = transferRequests.length;
        
        // We assume the creator of the transfer implicitly approves it
        // hence the 1 for the numApproves
        TransferRequest memory _transferRequest = TransferRequest(_recipient, _amount, 1, txid, false);
        transferRequests.push(_transferRequest);
        
        // Initialize the approvals for this transaction to false, except the initiator of 
        // the transfer. 
        // We assume the creator always approves the transfer.
        for (uint i=0; i<owners.length; i++) {
            transferApprovals[owners[i]].push(owners[i] == msg.sender);
        }
        
        // Verify if the minimum number of approvals has been met (it
        // could be set to 1, which according to the assumption means no
        // further approves necesarry)
        _checkExecuteTransfer(transferRequests[txid]);
    }
    
    function approveTransferRequest(uint _txid) public onlyOwner returns(uint){
        // Check that sender has not yet approved transaction - avoid double approves
        // Also we are not checking of the transfer is already executed or not, because I 
        // see no reason not to record any approver also after a transaction is executed.
        require(transferApprovals[msg.sender][_txid] == false, "You have already approved the transaction");

        transferApprovals[msg.sender][_txid] = true;
        TransferRequest storage _transferRequest = transferRequests[_txid];
        _transferRequest.numApproves++;
        return _checkExecuteTransfer(_transferRequest);
    }
    
    function _checkExecuteTransfer(TransferRequest storage _transferRequest) private returns(uint) {
        // Verify that the condition for executing the transfer are met. If so execute the transfer.
        if(_transferRequest.numApproves >= minApproves && !_transferRequest.isExecuted) {
            _transferRequest.recipient.transfer(_transferRequest.amount);
            _transferRequest.isExecuted = true;
        }
        // This return is more for debugging ...
        return _transferRequest.numApproves;
    }
    
    function getBalance() public view returns(uint){
        return address(this).balance;
    }
   
}
1 Like

Here is my implementation, it seems to work based on limited testing

pragma solidity 0.7.5;
pragma abicoder v2;

 //  ["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db"],2
// 2000000000000000000    

contract Wallet {
    
    address payable internal owner;
    address[] public owners;
    uint public limit;
    
    modifier onlyOwners {
        bool isOwner = false;
        for(uint i = 0; i<owners.length; i++){
            if(owners[i]==msg.sender){
                isOwner = true;
            }
        }
        require(isOwner, "Not a wallet owner");
        _; //run the function
    }
    
    struct Transfer{
        uint id;
        uint amount;
        uint approvals;
        address payable receiver;
        bool hasBeenSent;
    }
    
    Transfer[] transferRequests;
    
    mapping(address => mapping (uint => bool)) approvals;
    
    constructor(address[] memory _owners, uint _limit){
        owner = msg.sender;
        owners = _owners;
        limit = _limit;
    }
    
    function deposit() public payable {
        
    }
    
    function createTransfer(address payable _receiver, uint _amount) public onlyOwners {
        Transfer memory transferTemp = Transfer(transferRequests.length, _amount, 1, _receiver, false);
        approvals[msg.sender][transferRequests.length] = true;
        transferRequests.push(transferTemp);
    }
    
    //Set your approval for one of the transfer transferRequests
    //update the transfer object
    //update the mapping to record the approval for the msg.sender
    //when approvals reaches limits this funciton sends transfer
    //an owner can't vote twice
    //an owner cannot vote on a transfer that has been sent
    function approve(uint _id) public onlyOwners {
        require(transferRequests[_id].hasBeenSent == false, "This transaction has already been sent");
        require(approvals[msg.sender][_id] == false, "This address already approved the transaction");
        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;
    }
    
    function getTransfer(uint _id) public view returns(Transfer memory){
        return transferRequests[_id];
    }
}  
    
1 Like

Here is our code. Do we pass or do we need to finish watching all the videos?

pragma solidity 0.7.5;


contract multiSigWallet{
    
    //  Variables
    address[] public owners;    //  Array of owners that can submit, confirm, and execute transactions
    mapping(address => bool) public isOwner;    //  Tracks if the address is an account owner
    uint public numConfirmationsRequired;   //  Number of conformations to execute send
    
    mapping(uint => mapping(address => bool)) public isConfirmed;   //logs transaction id to which address is confirmed
    Transaction[] public transactions; // Array list of transactions
    
    //  Transaction struct
    struct Transaction {
        address to;
        uint value;
        bool executed;
        uint numConfirmations;
    }
    

    //  Modifiers
    
    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");
        _;
    }
    
    modifier onlyOwner() {
        require(isOwner[msg.sender], "You are not an owner.");
        _;
    }
    
    
    constructor(address[] memory _owners, uint _numConfirmationsRequired) {
        
        //  Basic validation
        require(_owners.length > 0, "owners required");
        require( _numConfirmationsRequired > 0 && _numConfirmationsRequired <= _owners.length, "invalid number of required confirmations");
    
        //  Add each owner to the owners array
        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);
        }
        
        //  store the number of confirmations to release funds
        numConfirmationsRequired = _numConfirmationsRequired;
    }
    
    //  anyone can pay
    function receive() payable public returns (uint){
         return address(this).balance;
        
    }

    //  Confirming the transaction (ONLY OWNERS CAN)
    function confirmTransaction(uint _txIndex) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) notConfirmed(_txIndex)
    {
        
        //  Increment confirmation count
        Transaction storage transaction = transactions[_txIndex];
        transaction.numConfirmations += 1;
        
        //  log which owner confirmed the transaction
        isConfirmed[_txIndex][msg.sender] = true;

    }

    //  Submit a transaction for confirmation
    function submitTransaction(address _to, uint _value) public onlyOwner returns(uint){
        
        //  Get transaction id
        uint txIndex = transactions.length;
        
        //  Add transaction to our array
        transactions.push(Transaction({
            to: _to,
            value: _value,
            executed: false,
            numConfirmations: 0
        }));
        
        //  return our transaction id
        return (txIndex);

    }
    
    //  Finally execute the transaction - checks for onlyOwner no double spend, only valid transactions
    function executeTransaction(uint _txIndex) public onlyOwner txExists(_txIndex) notExecuted(_txIndex){
        
        //  Get the transaction
        Transaction storage transaction = transactions[_txIndex];
        
        //  Enough confirmations?
        require( transaction.numConfirmations >= numConfirmationsRequired, "cannot execute tx, not enough comfirmations" );
        
        //  Set transaction executed prevent double spend
        transaction.executed = true;
        
        //Send the money
        (bool success, ) = transaction.to.call{value: transaction.value}("");
        require(success, "tx failed");

    }


}
2 Likes

hey @bfleming98, i would suggest watching the other videos now that youve completed the course. its always good to get insight into other peoples approaches, you may learn something also take a look at other peoples solutins in this forum there are some excellent and unique approaches by different people