Project - Multisig Wallet

Hi everyone, here is my code. It’s completely different than Filip’s but it works. I did not use double mapping even though double mapping seems useful.

The only thing that i can’t figure out is it seems i cannot call some of the view functions from other functions. For example, every time the transaction is approved, I want the “pending transactions” list to refresh automatically without having to click on the button “getPendingTx” each time.

Does anyone know how to do that? It seemed simple, but for some reason it doesn’t work the way i wanted it.

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

contract myWallet {
    address[] owners;
    address transactionSender;
    mapping(address=>uint) ownersContribution;
    uint txCount;
    
    struct Transaction{
        address from;
        address payable to;
        uint amount;
        uint txId;
        uint txApproved;
        address approver1;
        address approver2;
    }

    Transaction[] pendingTx;
    Transaction[] completedTx;

    mapping(uint=>Transaction) TxId;

    modifier onlyOwner{
        require(msg.sender==owners[0] || msg.sender==owners[1] || msg.sender==owners[2]);
        _;
    }
    constructor(){
        owners.push(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
        owners.push(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2);
        owners.push(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db);
        txCount=0;
    }
    function getOwners() public view returns(address[] memory){
        return(owners);
    }
    function Deposit() public payable {
        ownersContribution[msg.sender]+=msg.value;
    }
    function getWalletBalance() public view returns(uint){
        return(address(this).balance);
    }
    function getOwnersContribution() public view returns(uint, uint, uint){
        return(ownersContribution[0x5B38Da6a701c568545dCfcB03FcB875f56beddC4], ownersContribution[0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2], ownersContribution[0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db]);
    }
    function ApproveTransfer(uint _txId)public onlyOwner returns(Transaction[] memory){
        require(TxId[_txId].txApproved<2, "transaction already approved");
        require(msg.sender!=TxId[_txId].from, "you cannot approve your own transaction");
        require((msg.sender!=TxId[_txId].approver1) && (msg.sender!=TxId[_txId].approver2), "you already approved this transaction");
        if(TxId[_txId].txApproved==0){
            TxId[_txId].approver1=msg.sender;
        }
        else{
            TxId[_txId].approver2=msg.sender;
        }
        TxId[_txId].txApproved+=1;

        if(TxId[_txId].txApproved==2){
            sendFunds(TxId[_txId].to, TxId[_txId].amount);
            completedTx.push(TxId[_txId]);
            remove(getIndex(TxId[_txId].txId));
            getCompletedTx();
            getPendingTransaction();
        }
    return(pendingTx);
    }
    function Transfer(address payable recepient, uint amount) public onlyOwner {
        pendingTx.push(Transaction(msg.sender, recepient, amount, txCount, 0,address(0), address(0) ));
        TxId[txCount]=Transaction(msg.sender, recepient, amount, txCount, 0,address(0), address(0));
        txCount++; 
        getCompletedTx();
        getPendingTransaction();
    }
    function getPendingTransaction() public view returns(Transaction[] memory){
        return(pendingTx);
    }
    function sendFunds(address payable _to, uint _amount) private {
        _to.transfer(_amount);
    }
    function getCompletedTx() public view returns(Transaction[] memory){
        return(completedTx);
    }
    function getIndex(uint _txId)public view returns(uint){
        uint index;
        for (uint i=0; i<pendingTx.length; i++){
            if(pendingTx[i].txId==_txId){
                index=i;
            }
        }
        return(index);
    }
    function remove(uint _index) private {
        for (uint i = _index; i<pendingTx.length-1; i++){
            pendingTx[i] = pendingTx[i+1];
        }
        delete pendingTx[pendingTx.length-1];
        pendingTx.pop();
    }
}

@mcgrane5
:rofl: :rofl: :rofl:
I am a bit new to programming (even after the Javascript course). Here is my wallet, and it probably looks funny to more experienced programmers!

contract MultiSigWallet {

    address owner1 = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;

    address owner2 = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;

    address owner3 = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;

   

    string approveTx1[1];

    string approveTx2[1];

    string approveTx3[1];

    mapping(address => uint) balance;

   

    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 approve(string _yesOrNo) private returns(string) {

        require(msg.sender = owner1);

        approveTx1.push(_yesOrNo);

    }

     function approve(string _yesOrNo) private returns(string) {

        require(msg.sender = owner2);

        approveTx2.push(_yesOrNo);

    }

   

     function approve(string _yesOrNo) private returns(string) {

        require(msg.sender = owner3);

        approveTx3.push(_yesOrNo);

    }

       

    function withdraw(uint amount) public onlyOwner returns (uint){

        require(balance[msg.sender] >= amount);

        require(

        approveTx1[yes] && approveTx2[yes]

        || approveTx1[yes] && approveTx3[yes]

        ||  approveTx2[yes] && approveTx3[yes]

        );

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

       

        govermentInstance.addTransaction(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;

    }

   

}
pragma solidity 0.7.5;
pragma abicoder v2;
// 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2, 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db

contract MultiSig {
    struct Transaction {
        uint id;
        address multisig0;
        address multisig1;
        address recipient;
        uint amount;
        uint confs;
    }
    
    Transaction[] private transactions;
    
    uint private balance; 
    uint private pendingBalance; 
    
    address[3] private multisig;
    
    event newTransaction(uint id, address signatory, address to, uint amount);
    event newTransactionConf(uint id, address signatory);
    event paymentTransfer(uint id, address to, uint amount);
    
    constructor(address _multisig0, address _multisig1, address _multisig2) {
        
        require(_multisig0 != _multisig1 && _multisig0 != _multisig2 && _multisig1 != _multisig2, "Must have 3 different signatory addresses.");
        
        multisig = [_multisig0, _multisig1, _multisig2];
        balance = 0;
        pendingBalance = 0;
    }
    
    modifier onlyOwner() {
        require(msg.sender == multisig[0] || msg.sender == multisig[1] || msg.sender == multisig[2], "Function not initiated by contract owner.");
        _; 
    }

    function setMultisig(uint _index, address _newMultisig) public onlyOwner {
        multisig[_index] = _newMultisig;
    }
    
    function getMultisig() public view returns(address[3] memory)
    {
        return multisig;
    }
    
    function getBalance() public view returns(uint) {
        return balance;
    }
    
    function getPendingBalance() public view returns(uint) {
        return pendingBalance;
    }
    
    function receiveFunds() public payable{
        balance += msg.value;
        pendingBalance += msg.value;
    }
    
    function createTransaction(address _recipient, uint _amount) public onlyOwner {
        
        Transaction memory _newTransaction;
        
        require(msg.sender != _recipient, "Cannot send to signatories.");
        require(balance >= _amount && pendingBalance >= _amount, "Not enough funds to transact.");
        
        _newTransaction.id = transactions.length;
        _newTransaction.multisig0 = msg.sender;
        _newTransaction.multisig1 = address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
        _newTransaction.recipient = _recipient;
        _newTransaction.amount = _amount;
        _newTransaction.confs = 1;
        
        
        transactions.push(_newTransaction);
        
        pendingBalance -= _amount;
        
        emit newTransaction(_newTransaction.id, msg.sender, _newTransaction.recipient, _newTransaction.amount);
    }
    
    function confirmTransaction(uint _id) public onlyOwner {
        
        require(transactions[_id].confs < 2, "Transaction already confirmed.");
        
        
        
        require(transactions[_id].multisig0 != address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4), "!!Error: MultiSig0 is a 0 address!!");
        
        
        require(transactions[_id].multisig1 == address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4), "!!Error: MultiSig1 is not a 0 address!!");
        
        require(msg.sender != transactions[_id].multisig0, "Transaction already signed by this signatory.");
        
        transactions[_id].multisig1 = msg.sender;
        transactions[_id].confs = 2;
        
        emit newTransactionConf(_id, msg.sender);
        
        processTransaction(transactions[_id].id, transactions[_id].recipient, transactions[_id].amount);
    }
    
    function getTransaction(uint _id) public view returns(uint id, address multisig0, address multisig1, address recipient, uint amount, uint confs) {
        return(transactions[_id].id, transactions[_id].multisig0, transactions[_id].multisig1, transactions[_id].recipient, transactions[_id].amount, transactions[_id].confs);
    }
    
    function processTransaction(uint _id, address _recipient, uint _amount) public payable onlyOwner {
        address payable sendTransfer = address(uint160(_recipient));
        balance -= _amount;
        
        sendTransfer.transfer(_amount);
        
        emit paymentTransfer(_id, _recipient, _amount);
    }
    
    function toWei(uint _ether) public pure returns(uint) {
        uint _wei = _ether * 1e18;
        
        return _wei;
    }
}
1 Like

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