Project - Multisig Wallet

hello everyone!!
i was working on this project and I was trying to make an array of type address for approvers inside struct transaction request but i’m unable to push anything in this array .
my code is:-

pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet{
    
    address[3] private owners;
    uint private majority;
    uint public balance;
    enum Status {Pending,confirmed,Rejected}
    //events
    event amountDeposited(uint amount,address indexed depositedBy);
    constructor(address owner1,address owner2,address owner3,uint limit){
        owners[0]=owner1;
        owners[1]=owner2;
        owners[2]=owner3;
        majority=limit;
    }

    struct Transaction{
        uint txnID;
        address creator;
        address recipient;
        uint amount;
        address[] approvers;
        uint approvals ;
        Status status;
    }
    Transaction[] transactionPool;
    mapping(uint=>Transaction) lookUp;
    
    function deposit() public payable { 
        balance+=msg.value;
        emit amountDeposited(msg.value,msg.sender);
    }
    
    function transactionRequest(address _recipient,uint _amount) public returns(Transaction memory){
    //require onlyOwner
    Transaction memory newTransaction;
    newTransaction.txnID=transactionPool.length;
    newTransaction.creator=msg.sender;
    newTransaction.recipient=_recipient;
    newTransaction.amount=_amount;
    newTransaction.approvals=1;
    newTransaction.status=Status.Pending;
    newTransaction.approvers.push(msg.sender);
    transactionPool.push(newTransaction);
    lookUp[transactionPool.length-1]=newTransaction;
    return newTransaction;
    }

    function ConfirmTransaction(uint _txnID) public view {
    //require owner 
    //owner != creator for txnid
    Transaction memory txn=lookUp[_txnID];
    txn.approvals+=1;
    }
}

getting error at line -
newTransaction.approvers.push(msg.sender);

error- from solidity:
wallet.sol:45:5: TypeError: Member “push” is not available in address[] memory outside of storage.
newTransaction.approvers.push(msg.sender);
^---------------------------^

Below is my code for Multisignature Wallet with all the functionalities as required in introduction video.
It’s working all fine and
I request you all to have a look on this and provide your feedback and suggestions.

pragma solidity 0.7.5;
pragma abicoder v2;

contract MultiSigWallet  {
    uint  majority;
    address[] public owners;
    uint public Balance;
    enum Status {Pending,Confirmed,Rejected}

    //events
    event amountDeposited(uint amount,address indexed depositedBy);
    event TransactionConfirmed(uint indexed _txnID,address By,address to);    
    event TransactionRejected(uint indexed _txnID,address By,address to);    

    constructor(address owner1,address owner2,address owner3,uint limit){
        owners[0]=owner1;
        owners[1]=owner2;
        owners[2]=owner3;
        majority=limit;
    }


    struct Transaction{
        uint txnID;
        address creator;
        address recipient;
        uint amount;
        uint approvals ;
        uint rejections;
        Status status;
    }
    
    Transaction[] transactionPool ;

    mapping(address=>mapping(uint=>Status)) transactionRecord;

    modifier onlyOwner() {
        require(msg.sender==owners[0]
        || msg.sender==owners[1]
        ||msg.sender==owners[2], "YOU'RE NOT AUTHORISED!"
        );
        _;
    }
    modifier sufficientAmount(uint _amount){
        require(_amount<=Balance,"Insufficient Balance");
        _;
    }

    function transactionRequest(address _recipient,uint _amount) public onlyOwner sufficientAmount(_amount) returns(Transaction memory){
    Transaction memory newTransaction;
    newTransaction.txnID=transactionPool.length;
    newTransaction.creator=msg.sender;
    newTransaction.recipient=_recipient;
    newTransaction.amount=_amount;
    newTransaction.approvals=1;
    newTransaction.rejections=0;
    newTransaction.status=Status.Pending;
    transactionPool.push(newTransaction);
    //mapping record
    transactionRecord[msg.sender][transactionPool.length-1]=Status.Pending;
    return newTransaction;
    }

    function ConfirmTransaction(uint _txnID) public onlyOwner returns(Status){
        require(transactionRecord[msg.sender][_txnID]==Status.Pending,"YOU'VE ALREADY SIGNED THIS TRANSACTION.");
        transactionRecord[msg.sender][_txnID]=Status.Confirmed;
        transactionPool[_txnID].approvals+=1;
        if (transactionPool[_txnID].approvals==2){
           payable(transactionPool[_txnID].recipient).transfer(transactionPool[_txnID].amount);
            transactionPool[_txnID].status=Status.Confirmed;
            Balance-=transactionPool[_txnID].amount;
            emit TransactionConfirmed(_txnID,transactionPool[_txnID].creator,transactionPool[_txnID].recipient);    
        }
        return transactionPool[_txnID].status;
    }

    function RejectTransaction(uint _txnID) public onlyOwner returns(Status){
        require(transactionRecord[msg.sender][_txnID]==Status.Pending,"YOU'VE ALREADY SIGNED THIS TRANSACTION.");
        transactionRecord[msg.sender][_txnID]=Status.Rejected;
        transactionPool[_txnID].rejections+=1;
        if (transactionPool[_txnID].rejections==2){
            transactionPool[_txnID].status=Status.Rejected;
            emit TransactionRejected(_txnID,transactionPool[_txnID].creator,transactionPool[_txnID].recipient);    
        }
        return transactionPool[_txnID].status;

    }

    function GetTransactions() public view returns(Transaction[] memory){
        return transactionPool;
    }
     function deposit() public payable { 
        Balance+=msg.value;
        emit amountDeposited(msg.value,msg.sender);
    }
     
    function withdraw(uint _amount) public onlyOwner sufficientAmount(_amount){
        transactionRequest(msg.sender,_amount);
    }
}
1 Like

In the Full Project Code Video at 15:20 the transfer fails. Why can’t you create a transfer with a different approved owners address, besides the original creator of the smart contract? I thought that was the point of labeling them “owners”, to give them access. If anyone could shed more light on this in more detail . I’d greatly appreciate it.

Ok So I’m at the end of this course, assignment multisig wallet, I watched the video up to the double mapping explanation but haven’t watched the full code video yet. Also I have watched the basic videos many times and practice with Filip while watching the videos so i do understand the basic but I can’t do this assignment. Please advise what would be the best course of the action for me here before I watch the full code video. Also I wanted to know Is it ok for me to move on to the next solidity course?

i feel like my code is inefficient but it is the best i could come up with, would love some feedback on things i could have done differently.

i’ve only watched the project introduction video.

pragma solidity 0.7.5;


contract wallet {

    mapping (address => uint) balance;
    mapping (address => bool) owners;
    mapping (uint => uint) txApprovals;
    uint approvals;

    struct txRequest{
        address to;
        address from;
        uint amount;
    }
    txRequest[] public listOfRequests;

    constructor(address _owner1, address _owner2, uint _approvals) {
        owners[_owner1] = true;
        owners[_owner2] = true;
        owners[msg.sender] = true;
        approvals = _approvals;
    }

    modifier onlyOwners() {
        require(owners[msg.sender] == true);
        _;
    }

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

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

    function transferRequest(uint _amount, address _to) public onlyOwners{
        require(balance[msg.sender] >= _amount); 
        listOfRequests.push(txRequest(_to, msg.sender, _amount));
        txApprovals[listOfRequests.length - 1] = 0;
    }

    function approveTransfer(uint _index) public onlyOwners{
        txApprovals[_index] += 1;
        if (txApprovals[_index] == approvals) {
            transferAccepted(_index);
        }
    }

    function transferAccepted(uint _index) private {
        balance[listOfRequests[_index].from] -= listOfRequests[_index].amount;
        balance[listOfRequests[_index].to] += listOfRequests[_index].amount;
    }

}
1 Like
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

import "@openzeppelin/contracts/access/Ownable.sol";

error AlreadyApproved();
error AlreadySent();
error InvalidRecipient();
error NotEnoughApprovals();
error NotFound();
error Unauthorized();

contract MultiSigWallet is Ownable {
    mapping(address => uint256) public balances;
    mapping(uint256 => Transfer) public transfers;

    uint256 private nextTransferId = 1;
    address[] public admins;
    uint8 public approvalsRequired;

    event TransferCreated(
        uint256 transferId,
        address to,
        uint256 amount,
        address proposer
    );
    event TransferApproved(uint256 transferId, address approver);
    event TransferSent(uint256 transferId, address to, uint256 amount);

    struct Transfer {
        address payable to;
        uint256 amount;
        bool sent;
        address[] approvals;
    }

    constructor(address[] memory owners, uint8 numApprovals) {
        admins = owners;
        approvalsRequired = numApprovals;
    }

    modifier onlyAdmin() {
        if (!isAdmin(msg.sender)) revert Unauthorized();
        _;
    }

    function isAdmin(address user) internal view returns (bool) {
        for (uint8 i = 0; i < admins.length; i++) {
            if (user == admins[i]) return true;
        }
        return false;
    }

    function getTransfer(uint256 id)
        public
        view
        returns (
            address,
            uint256,
            bool,
            address[] memory
        )
    {
        Transfer memory transfer = transfers[id];
        return (
            transfer.to,
            transfer.amount,
            transfer.sent,
            transfer.approvals
        );
    }

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function createTransfer(address to, uint256 amount) public onlyAdmin {
        if (to == address(0)) revert InvalidRecipient();
        require(amount > 0, "Must be above 0");

        uint256 transferId = nextTransferId;
        nextTransferId++;

        address[] memory initialApprovals = new address[](1);
        initialApprovals[0] = msg.sender;

        transfers[transferId] = Transfer(
            payable(to),
            amount,
            false,
            initialApprovals
        );

        emit TransferCreated(transferId, to, amount, msg.sender);
        emit TransferApproved(transferId, msg.sender);
    }

    function approve(uint256 id) public onlyAdmin {
        Transfer memory transfer = transfers[id];

        if (transfer.amount == 0) revert NotFound();
        if (transfer.sent) revert AlreadySent();

        for (uint8 i; i < transfer.approvals.length; i++) {
            if (transfer.approvals[i] == msg.sender) revert AlreadyApproved();
        }

        transfers[id].approvals.push(msg.sender);

        emit TransferApproved(id, msg.sender);
    }

    function sendTransfer(uint256 id) public onlyAdmin {
        Transfer memory transfer = transfers[id];

        if (transfer.amount == 0) revert NotFound();
        if (transfer.approvals.length < approvalsRequired)
            revert NotEnoughApprovals();

        if (transfer.to == address(0)) revert InvalidRecipient();
        if (transfer.sent) revert AlreadySent();

        transfers[id].sent = true;

        (transfer.to).transfer(transfer.amount);

        emit TransferSent(id, transfer.to, transfer.amount);
    }
}
1 Like


Reference attached picture.
I am working with the last module of the “Ethereum Smart Contract Programming 101” course and have imported the “project final code” into Remix (the assignment code made availeble for the Multisig Wallet).
My issue is that when I want to deploy the contract I just get this fault message: “creation of Wallet errored: Error encoding arguments: Error: expected array value (argument=null, value=”", code=INVALID_ARGUMENT, version=abi/5.5.0)" ?
Can somebody please guide me forward?
Thanks

1 Like

You need to fill the arguments needed on the constructor to initialize the contract, at the right of the “deploy” button, you have some input fields to fill with the arguments.

Carlos Z

1 Like

Thanks, this helped.

1 Like

after finishing this video series, I would love to actually make the wallet to production / deployment level. but I’m still unsure how. are there any tutorials, walkthrough, documents of sorts that teaches me how to do so?
so I could show case that I’ve made a wallet, without just cloning it.

1 Like

@thecil is there by chance that moralis has a course on teaching how to create an entire wallet, from the frontend to the backend?

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.

Hi @Khudeja,

Is that an error that you received?
The note says that you have tried to send a value to a function that is not a payable function.
Can you provide more details on what caused the error?

function Withdraw (address payable _from, address payable _to, uint amt) public returns(uint, uint ){
require(balance[_from]>=amt);
require(_from== msg.sender);

 balance[_from] -= amt;
 balance[_to] += amt;

When calling the withdraw function make sure to keep the transaction value ( or message value) as 0.

That should fix the error.

1 Like

I wrote a solution with double mapping because I was influenced by the video title. But I think we can also do it without:

Simply map a transaction id to an array of addresses. Each time we approve a transaction:

  • we add the address in the address map (checking unicity)
  • If the count is higher than the limit we send the transaction
pragma solidity 0.7.5;
pragma abicoder v2;

import "../Ownable.sol";

contract Multisig is Ownable {
    address[] public owners;
    uint limit;
    
    mapping(uint => address[]) private approvals;
    
    struct Transfer {
        uint id;
        address payable receiver;
        uint amount;
    }
    Transfer[] transferRequests;

    modifier OnlyOwners() {
        require(addressInArray(msg.sender, owners));
        _;
    }

    constructor(address[] memory _owners, uint _limit) {
        require(_owners.length >= _limit);
        owners = _owners;
        limit = _limit;
    }

    function createTransfer(address payable _receiver, uint _amount) public {
        transferRequests.push(Transfer(transferRequests.length, _receiver, _amount));
    }

    function getTransfer(uint _idx) public view returns(uint) {
        require(_idx < transferRequests.length);
        return transferRequests[_idx].id;
    }

    function approve(uint txId) public OnlyOwners {
        require(approvals[txId].length < limit, "transaction already approved");
        require(!addressInArray(msg.sender, approvals[txId]), "sender already approved"); 

        approvals[txId].push(msg.sender);

        if(approvals[txId].length >= limit) {
            transferRequests[txId].receiver.transfer(transferRequests[txId].amount);
        }
    }

    function addressInArray(address _testAddress, address[] memory _array) public pure returns(bool) {
        // check if duplicates
        bool canBeAdded = true;
        for(uint i=0; i < _array.length; i++) {
            if(_array[i] == _testAddress) {
                canBeAdded = false;
                return true;
            }
        }

        return false;
    }
}

Any reason why we’re not doing it? I may be missing something.

1 Like

Hi @2mx, That’s good though to simplify the code.
Sometimes we have to take gas into consideration. Storing an array or addresses might cost more gas than storing a uint or bool.
As long as you are able to make it work with less gas, it is the best solution.

I am trying to understand the problem in real life situation. What I dont undertand is that, in the project, for approving the transaction we change the address from the react options on the left, but how does it work in real situation. Will the approver get some kind of notification to sign?

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 approvers);
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 isowner=false;
      for (uint i=0; i< owners.length; i++){
          if (owners[i]==msg.sender){
              isowner=true;
          }
        }
    require(isowner==true);
    _;
}
//Should initialize the owners list and the limit 
constructor(address[] memory _owners, uint _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)
    );
    
}
//["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2"]
//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(transferRequests[_id].hasBeenSent ==false);
    require(approvals[msg.sender][_id]==false);
    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);
        TransferApproved(_id);
    }


    
}

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

}