Project - Multisig Wallet

Hi there, here is my Multisig Wallet. Any feedback is welcomed. Must say that this double mapping was quite confusing, kinda froze me. Had to watch all videos :cry:.
Just realized that many of you guys found some way around. Should have tried harder. Well done all!

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


contract MultisigWallet {
    
    
/* *** Storage ***
===================*/

    mapping(address => mapping(uint => bool)) approvals;
    address[] public owners;
    Transaction[] transactions;
    uint requiredApprovals;
    uint public balance = 0;
    
    struct Transaction {
        uint amount;
        address payable receiver;
        uint approvals;
        bool executed;
        uint txId;
    }
    
    
/* *** Events ***
==================*/

    event depositDone(uint amount, address indexed depositedTo);
    event TransactionRequestCreated(uint _txId, uint _amount, address _creator, address _receiver);
    event ApprovalReceived(uint _txId, uint _approvals, address _approver);
    event TransactionApproved(uint _txId);
    event OwnerRemoval(address indexed _owner);
    event OwnerAddition(address indexed _newOwner);

    
/* *** Constructor ***
=======================*/

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

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

    
/* *** Functions ***
=====================*/

    function replaceOwner(address _owner, address _newOwner) public onlyOwners {
        for (uint i=0; i<owners.length; i++)
            if (owners[i] == _owner) {
                owners[i] = _newOwner;
                break;
            }
        emit OwnerRemoval(_owner);
        emit OwnerAddition(_newOwner);
    }
    
    function deposit() public payable returns (uint) {
        balance += msg.value;
        emit depositDone(msg.value, msg.sender);
        return balance;
    }
    
    function createTransaction(uint _amount, address payable _receiver) public onlyOwners {
        require(balance >= _amount, "Balance insufficient");
        require(msg.sender != _receiver, "Can't send to yourself");
        
        emit TransactionRequestCreated(transactions.length, _amount, msg.sender, _receiver);
        transactions.push(Transaction(_amount, _receiver, 0, false, transactions.length));
        balance -= _amount;
    }
    
    function approve(uint _txId) public onlyOwners {
        require(approvals[msg.sender][_txId] == false, "Transaction already confirmed!");
        require(transactions[_txId].executed == false, "Transaction already executed!");
        
        approvals[msg.sender][_txId] = true;
        transactions[_txId].approvals++;
        emit ApprovalReceived(_txId, transactions[_txId].approvals, msg.sender);
        
        if (transactions[_txId].approvals >= requiredApprovals) {
            transactions[_txId].executed = true;
            transactions[_txId].receiver.transfer(transactions[_txId].amount);
            emit TransactionApproved(_txId);
        }
    }
    
    function getTransaction() public view returns (Transaction[] memory){
        return transactions;
    }
}
2 Likes

Hi Carlos

Sorry but I don’t understand. The _transfer() function takes a Tx argument which is a Struct that contains the recipient. You can see I reference the recipient in the body of the function with transaction.recipient.

In any case, the issue is when the requestTransfer) is called. This is the function that throws the error. This function doesn’t call _transfer()

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

// This is my second attempt at the Multi Sig Wallet without looking for the answer but using the web for ideas. It's got some flaws like I could not get it to prevent the same owner from signing twice but here it is.

// https://www.youtube.com/watch?v=OwavQTuHoM8&t=386s
// Learning Solidity : Tutorial 24 Multisig Wallet

contract MultiSigWallet {
    
    // owners
    uint public ownerCount = 0; // --------------------- my code
    
    address private _owner;
    address private _secondOwner;
    address private _thirdOwner;
    
    mapping(address => uint) private _owners;
    mapping(address => bool) signedOwners;
    
    mapping(address => uint) balances; // --------------------- my code -- adds all deposts
    
  
    // signing
    uint public confirmations = 0;
    mapping(address => bool) public confirmation;
    mapping(uint => SigCount) signatures;

    
    struct SigCount { // --------------------- my struct
        uint sigNum;
    }
    
    
    event ownerSet(address oldOwner, address newOwner);
    
    modifier isOwner() {
        require(msg.sender == _owner, "Caller is not owner");
        _;
    }
    
    modifier validOwner() {
        require(msg.sender == _owner || _owners[msg.sender] == 1);
        _;
    }
    
    event DepositFunds(address from, uint amount);
    event WithdrawFunds(address to, uint amount);
    event TransferFunds(address from, address to, uint amount);
    
    //-------------functions----------------------
    
    constructor() { // --------------------- my constructor
            _owner = msg.sender;
            ownerCount = 1;
            emit ownerSet(address(0), _owner);
        }
        
        
    function addOwner(address owner) public isOwner { // --------------------- my code
        _owners[owner] = 1;
        _owners[owner] = ownerCount ++;
       
        
    }
    
    function removeOwner(address owner) public isOwner {
        _owners[owner] = 0;
        ownerCount --;
    }
    
    function changeOwner(address newOwner) public isOwner {
        _owner = newOwner;
        emit ownerSet(_owner, newOwner);
        
    }
    
    function getOwner() public view returns(address) {
        return _owner;
    }
    
    function deposit() public payable {
        balances[msg.sender] += msg.value; // --------------------- my code
        emit DepositFunds(msg.sender, msg.value);
        
    }
    
    function getBalance() public view returns(uint) { // --------------------- my function
        return balances[msg.sender];
    }
    
    
    function withdraw(uint amount) public validOwner {
        require(msg.sender.balance >= amount);
        require(confirmations >= 1 && confirmations <= ownerCount, "Minimum of 2 signatures required");
        msg.sender.transfer(amount);
        emit WithdrawFunds(msg.sender, amount);
    }
    
    
    function transferTo(address recipient, uint amount) public validOwner { 
        require(msg.sender.balance >= amount, "Balance not sufficient");
        require(msg.sender != recipient, "You may not transfer to your own account");
        require(confirmations >= 1 && confirmations <= ownerCount, "Minimum of 2 signatures required");
        msg.sender.transfer(amount);
        uint previousSenderBalance = balances[msg.sender];
        
        _transfer(msg.sender, recipient, amount);
        
        emit TransferFunds(msg.sender, recipient, amount);
        
        assert(balances[msg.sender] == previousSenderBalance - amount);
        
    }
    
    function _transfer(address from, address to, uint amount) private {
        balances[from] -= amount;
        balances[to] += amount;
    }
    
    function ownerCountFunc(uint _ownerCount) internal pure returns(uint) { // --------------------- my function
        return _ownerCount;
         
    }
   
    
    function approve(uint _sigNum) public validOwner { // --------------------- my function
        confirmation[msg.sender] = true;
        incrementSigCountFunc();
        signatures[_sigNum] = SigCount(_sigNum);
        
    }
    
    function incrementSigCountFunc() internal { // --------------------- my function
        confirmations ++;
    }
    
    
}





1 Like

I did not look so I first tried two different ways and created two wallet exercises but now that I have watched the first video and realizing that I am missing some crucial functions…I have to do it again so we are all learning so…good job!

Good day! Is the double mapping essential to the code or is it just a way to track the approvals made by a certain address?

1 Like

Hey @Pedrojok01, hope you are well.

I have tested your code and it works perfectly, congratulations!

  • I deployed 3 addresses, signlimit of 2.
  • Deposit funds on the contract.
  • Created a transfer proposal.
  • Approve with 2 owners and the transaction goes valid right after approving it with the 2nd owner.
  • Check recipient balance, so your contract can deposit, approve and transfer funds.

(check this @Jose_Hernandez)
Now indeed double mappings are quite complex to understand, but once you get the idea of it, only practice can help you understand it better.

mapping(address => mapping(uint => bool)) approvals;
So basically a mapping from an address to a uint that should return a boolean value (true or false)

Then, you can assign multiple values for the same address on different variables.
approvals[msg.sender][_txId] = true;
Which means, for the mapping approvals, take this address msg.sender and this index (uint) _txid and set or get the value.

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

Carlos Z.

Thank you! I realized it was needed so that the creator of the transfer request cannot approve their own transfers multiple times.

1 Like

Note: My contract constructor expects 2 parameters - 1) An array of ā€œowner/approverā€ addresses, 2) A uint indicating the number of approvals required for transfer of funds.

Also, given that only an approver may request / initiate a transfer, I’ve assumed such a request should also represent an approval. As such, the moment a transfer is requested, it automatically receives the first of its required approvals.

-----Solution/Contract follows----------

pragma solidity 0.7.5;

contract MultiSigWallet {

//Contract creator
address owner;

//The parties which may participate in transfer approval
//address[] owningParties; 
mapping(address => uint) owningParties;

//Number of approvals required to complete transfer
uint requiredApprovalCount; 


mapping(address => uint) deposits;

uint transferRequestCount;


struct TransferRequest {
    
    //Transfer request id
    uint id;
    
    //Amount to transfer
    uint amount;
    
    //Address to which amount is to be transferred
    address payable to;
    
    //Number of affirmatives (ie, actual approvals as opposed to denials)
    uint approvalCount;
    
    //Mapping of "votes": (ie, approve or deny)
    //mapping(address => uint) approvedBy;
    
}

mapping(uint => TransferRequest) transferRequests;
mapping(uint => mapping(address => uint)) approvals;

modifier sufficientBalanceToRequest(uint transferAmount) {
    require(address(this).balance >= transferAmount, "Insufficient funds.");
    _;
}

modifier sufficientBalanceToApprove(uint transferRequestId) {
    require(address(this).balance >= transferRequests[transferRequestId].amount, "Contract funds insufficient funds insufficient to approve request.");
    _;
}

modifier unapproved(uint transferRequestId) {
    require (transferRequests[transferRequestId].approvalCount < requiredApprovalCount, "This transfer has already been completed.");
    _;
}


modifier approverOnly() {
    require(owningParties[msg.sender] == 1, "You may not request or approve transfers.");
    _;
}

modifier unapprovedByCaller(uint transferRequestId) {
    require(approvals[transferRequestId][msg.sender] != 1, "You have already approved this transfer request.");
    _;
}

constructor(address[] memory _owningParties, uint _requiredApprovalCount) {
    
    owner = msg.sender;
    
    //Capture the multiple owners (ie those who may approve a transfer)
    for(uint i=0; i < _owningParties.length; i++) {
        owningParties[_owningParties[i]] = 1;
    }
    
    requiredApprovalCount = _requiredApprovalCount;
}


function deposit() public payable {
    
    //Track deposits:
    deposits[msg.sender] += msg.value;
    
}

//Only allow "approvers" to request transfers.
//NOTE: When an approver requests a transfer, we will count their request as the 
//first of the (n) required approvals (otherwise, why submit the request?).
function requestTransfer(address payable to, uint amount) public approverOnly sufficientBalanceToRequest(amount) {
    
    transferRequestCount++;
    
    TransferRequest memory transferRequest = TransferRequest(transferRequestCount, amount, to, 0);

    transferRequests[transferRequestCount] = transferRequest;
    
    //An approver address is requesting a transfer, so we'll also submit the first approval:
    approveTransfer(transferRequestCount);
    
}

function getTransferRequest(uint transferRequestId) public view returns(uint, address, uint) {
    
    return (transferRequests[transferRequestId].amount, transferRequests[transferRequestId].to, transferRequests[transferRequestId].approvalCount);
    
}

function approveTransfer(uint transferRequestId) public approverOnly unapproved(transferRequestId) unapprovedByCaller(transferRequestId) sufficientBalanceToApprove(transferRequestId) {
    
    //Increment approval count for this transfer request:
    transferRequests[transferRequestId].approvalCount += 1;
    
    //Record approving address:
    approvals[transferRequestId][msg.sender] = 1;
    
    if(_isRequestApproved(transferRequestId)) {
        //Then this transfer has just been "fully" approved and 
        //should now be committed:
        
        _completeTransfer(transferRequestId);
    }
    
}

//Returns true, if specified TransferRequest has been fully approved; otherwise returns false
function _isRequestApproved(uint transferRequestId) private view returns(bool) {
    
    return (transferRequests[transferRequestId].approvalCount >= requiredApprovalCount);
    
}

//Perform actual transfer of value
function _completeTransfer(uint transferRequestId) private {
    
    (transferRequests[transferRequestId].to).transfer(transferRequests[transferRequestId].amount);
    
}

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

}

1 Like

Thanks for the nice feedback, also I don’t have much merit, it’s mostly Filip’s code haha!
Thanks too @bjamRez !

I’m wondering: Is it possible to use an external contract like uniswap while working on the testnet? Or how can I make sure that everything is working as intending before deploying on the mainnet?
I’ve a small project I would like to work on, not sure I should start or take the next ethereum course first.

@Jose_Hernandez From what I understand, the double mapping offers some additional flexibility (e.g. msg.sender can define the number of owners and the number of required approvals), but many just hardcoded the 3 owner’s addresses without double mapping and it seems to be working just fine.

3 Likes

@filip

Please could you help? I’m tried running the academy solution and worryingly I get the same error message when invoking the createTransfer() function. I’m wondering if there is an environment problem with my remix IDE as both my and academy’s solution fail with same error?

Would it be possible for you to attempt to run my solution in your Remix and see if it throws the same error?

Steps to reproduce:

  1. using account 1 call constructor with arguments: (1, ["<account 2 address>"]). This will set up two owners (account 1 and account 2)
  2. using account 1 call deposit with 10 wei.
  3. using account 1 call requestTransfer with arguments: ("<account 3 address>", 5). This will request a transfer of 5 wei to account 3.

requestTransfer throws an error:

transact to MultiSigWallet.requestTransfer errored: VM error: revert. revert The transaction has been reverted to the initial state. Note: The called function should be payable if you send value and the value you send should be less than your current balance. Debug the transaction to get more information.

Much appreciated.

1 Like

Owenable.sol

pragma solidity 0.7.5;

contract Ownable {
    
    // Keep a list of owners
    address[] internal owners;
    
    modifier onlyOwner {
        require (isOwner(msg.sender), "Only an owner of the Multisig Wallet can call this function");
        _;
    }
    
    constructor (address[] memory _owners) {
        require(areAddressesUnique(_owners), "List of owners contained at least one duplicate address");
        owners = _owners;
    }
    
    function isOwner(address _owner) public view returns (bool) {
        return (countAddressOccurences(owners, _owner) == 1);
    }
    
    function areAddressesUnique(address[] memory _addresses) private pure returns (bool) {
        for (uint i = 0; i < _addresses.length; ++i) {
            if (countAddressOccurences(_addresses, _addresses[i]) > 1) {
                return false;
            }
        }
        return true;
    }

    function countAddressOccurences(address[] memory _addresses, address _address) internal pure returns (uint) {
        uint occurences = 0;
        for (uint i = 0; i < _addresses.length; ++i) {
            if (_address == _addresses[i]) {
                occurences++;
            }
        }
        return occurences;
    }
    
}

MultisigWallet.sol

...
pragma solidity 0.7.5;
pragma abicoder v2;

import "./Ownable.sol";

contract MultisigWallet is Ownable {
    
    // Used to keep track of funds that are locked up in pending transfers
    uint lockedFunds; 
    
    // Models a transfer request that an owner of the wallet initiates to use funds in the wallet
    struct TransferRequest {
       uint id;
       address payable recipient;
       uint amount;
       address[] approvals;
       bool complete;
    }
    
    // The number of approvals required in order to execute a requested transferRequestInitiated
    uint requiredApprovals;
    
    // Counter used to assign transfer ids
    uint nextTransferId;
    
    // Keeps track of the pending transfers
    TransferRequest[] pendingTransfers;
    
    // Events emitted by the contract
    event depositComplete(uint _amount, address _owner);
    event transferRequestInitiated(uint _amount, address _initiator, address _recipient);
    event transferRequestApproved(uint _amount, address _recipient);
    
    
    constructor(address[] memory _owners, uint _requiredApprovals) Ownable(_owners) {
       require(_requiredApprovals <= _owners.length, "Number of required approvals must be less than or equal to the number of owners");
       requiredApprovals = _requiredApprovals;
    }
    
    function deposit() public payable {
       emit depositComplete(msg.value, msg.sender);
    }
    
    function getAvailableFunds() public view onlyOwner returns (uint) {
        // consider the locked funds spent when reporting available funds
        return (address(this).balance - lockedFunds);
    }

    function lookupTransactionIndex(uint _id) private view returns (int) {
        for (uint i = 0; i < pendingTransfers.length; i++) {
            if (pendingTransfers[i].id == _id) {
                return int(i);
            }
        }
        return -1;
    }
    
    function initiateTransfer(address payable _recipient, uint _amount) public onlyOwner returns (uint) {
        require(_amount > 0, "Must transfer an amount greater than zero");
        require(_amount <= getAvailableFunds(), "Not enough funds available");
        
        // Add the amount requested in the trasfer to the locked funds
        lockedFunds += _amount;
        
        TransferRequest memory transfer;
        transfer.id = nextTransferId++;
        transfer.recipient = _recipient;
        transfer.amount = _amount;
        transfer.complete = false;

        // Add the request to the pending transfers
        pendingTransfers.push(transfer);

        emit transferRequestInitiated(transfer.amount, msg.sender, transfer.recipient);
        
        // return the transfer id of the new request
        return transfer.id;
    }
    
    function approveRequest(uint _id) public onlyOwner {
        require(lookupTransactionIndex(_id) >= 0, "Transfer Request doesn't exist");
        
        uint index = uint(lookupTransactionIndex(_id));
        TransferRequest storage request = pendingTransfers[index];
        
        require(request.complete == false, "This request is already complete");
        require(countAddressOccurences(request.approvals, msg.sender) == 0, "This request was already approved by you");
        
        // Add the sender to the list of approvals
        request.approvals.push(msg.sender);
        
        // Check to see there are enough approvals to send
        if (request.approvals.length >= requiredApprovals) {
            
            // transfer the funds
            request.recipient.transfer(request.amount);
            
            // reduce the loced funds by the transfer amount since it is now removed from the contract's balance
            lockedFunds -= request.amount;
            
            emit transferRequestApproved(request.amount, request.recipient);
  
            request.complete = true;            
        }
    }


    function getRequests() public view returns (TransferRequest[] memory) {
        return pendingTransfers;
    }     
}

…

1 Like

It is completely valid to use external contracts like uniswap on testnet, is basically the same process but the contract address is different than the mainnet. I could suggest your to go into the Ethereum Smart Contract Programming 201 course to get a better understanding on solidity development, also you will learn how to deploy your contracts in the testnet.

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

Carlos Z.

1 Like

Hey @gcradduck, hope you are ok.

your contract looks very well, when you create a transfer request, the requester automatically approve the transfer, then it will only have to wait for another owner to approve and then funds are transfered to the recipient automatically. Nice job.

I could suggest you to add some events that can help you track which operations has been made through your contract (like when requesting a transfer or approving it)

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

Carlos Z.

Hi there,
I almost finished my contract. The only problem which I can’t solve is that one related to the ā€œmakeTransactionā€ function. When function is running the following error popup:

transact to WalletMultiSig.transferSign errored: VM error: revert. revert The transaction has been reverted to the initial state. Note: The called function should be payable if you send value and the value you send should be less than your current balance. Debug the transaction to get more information.

Any advices?

My contract code:

pragma solidity 0.8.1; //assign the compiler version
pragma abicoder v2; // if need to return struct from function


contract Events {
    event depositComplete(address indexed depositFrom, uint amount);
    event addOwnerComplete(address indexed added, address indexed addedBy);
    event selfDestructed(address contractAddress);
    event numberSigUpdated(uint, address indexed addedBy);
    event duplicatedUserError(address indexed added, address indexed addedBy);
    event transactionRequestCreated(address indexed addedBy, address indexed receiver, uint amount);
    event transctionSigned(address indexed signedBy, uint transactionId);
    event transactionComplete(address indexed reciver, uint amount, uint contractBalance);
}


contract Privileges {
    address contractCreator;
    address[] owners;

    modifier onlyCreator {
        require(msg.sender == contractCreator, "Only for contract creator. Access Denied!");
        _;
    }
    
    modifier onlyOwner{
        bool ownerFound = false;
        for(uint i=0 ; i<owners.length ; i++){
            if(msg.sender == owners[i]){
                ownerFound = true;
            }
        }
        require(ownerFound == true, "Only for wallet owners. Access Denied!");
        _;
    }
    

}

contract Destroyable is Privileges, Events{

    function close() internal onlyCreator {
        emit selfDestructed(contractCreator);
        selfdestruct(payable(contractCreator));
    }
}


contract WalletMultiSig is Privileges, Events{
    
    uint requiredSig;
    uint contractBalance;
    uint transactionId;
    
    constructor(){
        requiredSig = 2;
        contractBalance = 0;
        transactionId = 0;
        contractCreator = msg.sender;
    }
    
    struct Transaction {
        address receiver;
        uint amount;
        uint numberSigns;
        bool exist;
    }

    mapping(uint => Transaction) transaction;

    function deposit() public payable{
        uint oldBalance = contractBalance;
        contractBalance += msg.value;
        emit depositComplete(msg.sender, msg.value);
        
        assert(contractBalance - msg.value == oldBalance);
    }
    
    function requiredSigUpdate(uint _requiredSig) public onlyCreator {
        requiredSig = _requiredSig;
        emit numberSigUpdated(_requiredSig, msg.sender);
        
        assert(requiredSig == _requiredSig);
    }
    
    function addOwner(address _ownerAddress) public onlyCreator {
        for(uint i=0 ; i<owners.length ; i++){
            if(_ownerAddress == owners[i]){
                emit duplicatedUserError(_ownerAddress, msg.sender);
                require(_ownerAddress != owners[i], "Owner has been already added. Operation failed!");
            }
        }
        uint oldOwnersNumber = owners.length;
        owners.push(_ownerAddress);
        emit addOwnerComplete(_ownerAddress, msg.sender);
        
        assert(owners.length - 1 == oldOwnersNumber);
    }
    
    function transferRequest(address _receiver, uint _amount) public onlyOwner returns(uint){

        transaction[transactionId] = Transaction(_receiver, _amount, 0, true);
        emit transactionRequestCreated(msg.sender, _receiver, _amount);
        transactionId++;
        return transactionId - 1;
        
    }
    
    function transferSign(uint _transactionId) public onlyOwner {
        require(transaction[_transactionId].exist == true, "Provided transaction ID does not exist!");

        uint oldSignNumber = transaction[_transactionId].numberSigns;
        transaction[_transactionId].numberSigns += 1;
        emit transctionSigned(msg.sender, _transactionId);
        
        assert(oldSignNumber == transaction[_transactionId].numberSigns - 1);
        
        if(transaction[_transactionId].numberSigns >= requiredSig){
            makeTransaction(_transactionId);
        }
    }
    
    function makeTransaction(uint _transactionId) public payable onlyOwner returns(uint){
        payable(transaction[_transactionId].receiver).transfer(transaction[_transactionId].amount);
        require(transaction[_transactionId].exist == true, "Provided transaction ID does not exist!");
        require(transaction[_transactionId].numberSigns >= requiredSig, "The transaction needs to be signed!");
        require(contractBalance >= transaction[_transactionId].amount, "Balance not sufficient!");
        
        uint oldBalance = contractBalance;
        contractBalance -= transaction[_transactionId].amount;
        emit transactionComplete(transaction[_transactionId].receiver, transaction[_transactionId].amount, contractBalance);
        
        assert(oldBalance == contractBalance - transaction[_transactionId].amount);
        
        return contractBalance;
    }
    
    function getBalance() public onlyOwner returns(uint){
        return contractBalance;
    }

}
1 Like

OK, I’ll jump into Ethereum 201 then. Thank you for your advise :slight_smile:

1 Like

Eyy, finished mine https://github.com/dominikclemente/learn-solidity/blob/master/smart-contract-101-iot/final-project/MultisigWallet.sol

1 Like

I’ve found the source of the issue.

It’s a problem with creating a transaction and adding it to the txs array. I have a property of type mapping in my Struct Transaction called approvers and want to initialise it to be empty.

Is this possible?

1 Like

Hey @D0tmask, hope you are good.

You should check if you are adding correctly the transaction the your contract array, i research a bit an apparently this issue appears when you have not added properly the data to a mapping or array.

    function transferRequest(address payable _receiver, uint _amount) public onlyOwner returns(uint){

        transaction[transactionId] = Transaction(_receiver, _amount, 0, true);
        emit transactionRequestCreated(msg.sender, _receiver, _amount);
        transactionId++;
        return transactionId - 1;
        
    }

I suggest you to check the contract solution if you have any doubt because is very common to skip or miss simple syntax.

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

Carlos Z.

Many thanks, Sir. Immediately following my post, it struck me I should have added some events / logging. I’ll circle back and take another stab at it.

Appreciate the feedback.

Garrett.

1 Like

Hey @jonp, hope you are well.

I check your contract again, you have a mapping inside the struct which I’m sure we do not teach that, also is not a good practice for it.

I’m not completely sure if you are adding the transfer data correctly in the array, because i dont see any place on the contract that you are increasing the index of that array.

I advice you check again the arraysn lesson and mapping lessons, you could also ask me to give you more examples for the subject :nerd_face:

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

Carlos Z.