Project - Multisig Wallet

This certainly could use some improvements after having looked at some others’. However this should work as far as I understand

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.7.5;

import “./MultiOwner.sol”;

import “./Destroyable.sol”;

contract MultiSigWallet {

uint256 private balance = 0;

TransferToBeSigned [] transferRequests;

mapping(address => mapping(uint => bool)) signed;

mapping (address => bool) owners;

address [] ownerList;

uint signees;

struct TransferToBeSigned{

    uint amount;

    address payable recipient;

    bool executed;

}

modifier onlyOwners {

    require(owners[msg.sender] == true);

    _; //run the function

}

constructor(address [] memory o, uint _s){

    signees = _s;

    ownerList = o;

    for(uint i = 0; i < o.length; i++){

        owners[o[i]] = true;

    }

}

function deposit() public payable returns (uint)  {

    balance += msg.value;

    return balance;

}



function getBalance() public view returns (uint){

    return balance;

}

function transferRequest(address recipient, uint amount) public onlyOwners {

    TransferToBeSigned memory tr = TransferToBeSigned(amount, recipient, false);

    transferRequests.push(tr);

}



function sign(uint transaction_id) public onlyOwners{

    require(signed[msg.sender][transaction_id] != true)

    signed[msg.sender][transaction_id] = true;

}

function startTransfer(uint transaction_id) public onlyOwners{

    uint approvals = 0;

    for(uint o = 0; o < ownerList.length; o++){

        //address a = ownerList[o];

        if(signed[ownerList[o]][transaction_id]){

            approvals++;

        }

    }

    if(approvals >= signees && !(transferRequests[transaction_id].executed)){

        transfer(transferRequests[transaction_id].recipient, transferRequests[transaction_id].amount);

    }

}

function transfer(address recipient, uint amount) private {

    require(balance >= amount, "Balance not sufficient");

    require(msg.sender != recipient, "Don't transfer money to yourself");

   

    uint previousSenderBalance = balance;

    balance -= amount;

    assert(balance == previousSenderBalance - amount);

}

}

1 Like

Hi,

Is a work in progress my attempt at multisig wallet

Can anybody tell me why the complier tells me I have a undecleared identifier in the code where I try to push a Transfer object into my trasferrequests array?

pragma solidity 0.8.11;


contract Multisig {

    struct externalTransfer {
        uint amount;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }

    mapping(address => mapping(uint => bool)) approvals;

    event depositDone(uint amount, address depositedFrom);

    address payable Owner;
    address payable CoOwnerA;
    address payable CoOwnerB;

    externalTransfer[] transferRequests ;

    modifier onlyOwners(){
        require(msg.sender == Owner || msg.sender == CoOwnerA || msg.sender == CoOwnerB);
        _;
    }

    constructor () {
        Owner = payable(msg.sender);
        CoOwnerA = payable(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2);
        CoOwnerB = payable(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db);
        uint limit = 3; 
    }

    //Function that recives ETH into wallet
    function Deposit () public payable {
        
        emit depositDone(msg.value, msg.sender);     
    }

    //function to transfer ETH out of wallet to external adress
    function createExternalTransfer (uint _amount, address _recipient) public onlyOwners {
       require(address(this).balance >= _amount, "Insufficient funds");
       transferRequests.push(externalTransfer(_amount, _receiver, 0, false, transferRequests.length));
    }

    function Approve (uint _id) public onlyOwners {
        
    }

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

    function getTransferRequests () public view returns (uint) {
        return transferRequests[];
    }

}

Hi @Tomaage,

It’s because you are referencing _receiver (which is the name of the property in the struct with an underscore added) and not _recipient (which is the name of the parameter you want to assign to receiver) :wink:

Once you correct that, then you’ll get another error, because the receiver property in the struct is a payable address, and so the value you assign to it must also be payable.

After correcting that line of code, you’ll get another error for the return statement in this function …

You’re missing a reference to an index number between the square brackets, unless you want to return the whole array, in which case you only need to reference the name of the array:

return transferRequests;

Then you need to change the data type you are returning …

returns (uint)

… so that it corresponds with the actual value being returned:

  • an externalTransfer struct instance (object); or
  • an array of externalTransfer struct instances (objects).

You’ll then have another issue to resolve after that (relating to the final line in your constructor), but I’ll let you work that one out on your own :muscle:

Here is my multisig wallet. I had to get some help with the first assistance video, but after that I pretty much grasped the concept fully and was able to do the code almost completely by myself. Took me a while to figure out but I’m learning! Thanks for everything!

// MultiSig Wallet

pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet {

    // Input Owners Addresses (3 owners) and minimum number of approvals to make a transfer.
    address[] owners;
    uint approvalCountLimit;

    struct Transfer {
        address payable addressTo;
        uint amount;
        uint transferId;
        uint approvalCount;
        bool hasBeenSent;
    }

    Transfer[] transfers;

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

    // Constructor fills owners array and sets number of approvals required for a transfer to be sent.
    constructor(address[] memory _owners, uint _approvalCountLimit) {
        owners = _owners;
        approvalCountLimit = _approvalCountLimit;
    }

    // Modifier gives only owners access to a request.
    modifier onlyOwners() {
        bool owner = false;
        for(uint i=0; i<owners.length;i++){
            if(owners[i] == msg.sender){
                owner = true;
            }
        }
        require(owner == true);
        _;
    }

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

    // Create a transfer request, only owners can do this
    function createTransferRequest(address payable _addressTo, uint _amount) public onlyOwners {
        transfers.push(Transfer(_addressTo, _amount, transfers.length, 0, false));
    }
    // Approve transfer requests, only owners can do this

    function approveTransferRequest(uint _transferId) public onlyOwners {
        require(approvals[msg.sender][_transferId] == false);
        require(transfers[_transferId].hasBeenSent == false);

        approvals[msg.sender][_transferId] == true;
        transfers[_transferId].approvalCount++;

        if(transfers[_transferId].approvalCount >= approvalCountLimit) {
            sendTransfer(_transferId);
            transfers[_transferId].hasBeenSent = true;
        }
    }

    // sendTransfer() called in the approveTransferRequest() function. 
    function sendTransfer(uint _transferId) public {
        transfers[_transferId].addressTo.transfer(transfers[_transferId].amount);
    }

    function getTransfer() public view returns (Transfer[] memory) {
        return transfers;
    }

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

your Code is looking clean. I’m about to start my multisig wallet project

I think your approveTransferRequest needs another looksie. particularly at the if statement where you check if the current approval count is adequate to send. We need 2/3 (atleast) approved, not 3/3 or 4/3. I’m also thinking about how I’d do this currently, cheers.

1 Like

I’m looking at your mapping for approvals, particularly how the key(uint) isn’t the transferId that was created in the struct. I’m thinking that the transferID should be a state variable? Idk, the code probably runs well, but the key on the map and the transferId aren’t the same value.

I’m thinking that the send transfer should be private as well.

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

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

struct Transfer{
    uint amount;
    address payable receiver;
    uint approvals;
    bool hasBeenSent;
    uint id;
}

Transfer[] transferRequests;

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

//Should only allow people in the owners list to continue the execution.
modifier onlyOwners(){
bool isOwner = false;
for (uint i=0; i<owners.length; i++){
    if (owners[i] == msg.sender)
        isOwner = true;
}
	require(isOwner);
	_;
}
//Should initialize the owners list and the limit 
constructor(address[] memory _owners, uint _limit) {
	owners = _owners;
	limit = _limit;
}

//Empty function
function deposit() public payable {}

//Create an instance of the Transfer struct and add it to the transferRequests array
function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
	require(balance[msg.sender] >= _amount, "Balance not sufficient");
	require(msg.sender != _receiver, "Don't transfer money to yourself");
	transferRequests.push(Transfer(_amount, _receiver, 1, false, transferRequests.length));
}

//1-Set your approval for one of the transfer requests.
//2-Need to update the Transfer object.
//3-Need to update the mapping to record the approval for the msg.sender.
//4-When the amount of approvals for a transfer has reached the limit, this function should send the transfer to the recipient.
//5-An owner should not be able to vote twice.
//6-An owner should not be able to vote on a tranfer request that has already been sent.
function approve(uint _id) public onlyOwners {
	//6
	require(transferRequests[_id].hasBeenSent == false, "Transfer Request has already been sent.");
	//5
    require(approvals[msg.sender][_id] == false);
	//1, 2, 3
	//transferRequests[_id].approvers.push(_id);
	transferRequests[_id].approvals += 1;
	approvals[msg.sender][_id] = true;
	//4
	require(transferRequests[_id].approvals == limit, "Amount of approvals is below limit.");
	sendTranfer(_id);
}

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

function sendTranfer(uint _id) private {
balance[msg.sender] -= transferRequests[_id].amount;
balance[transferRequests[_id].receiver] += transferRequests[_id].amount;
transferRequests[_id].hasBeenSent = true;
}

}

Hello everyone! So, I have completed my wallet and I like how it works. I haven’t seen the template, I will check it now. I only saw the first video where Fillip talked about double mapping but without explaining it too much. I played with it and saw how it works and used it in my code. It was a fundamental piece in the project.

pragma solidity 0.7.5;

contract Wallet {

address creator;

mapping(address=>bool)isOwner;

modifier OnlyOwner {
    require(isOwner[msg.sender] == true);
    _;
}

constructor(){
isOwner[msg.sender] = true;
creator = msg.sender;
}

function addOwner(address newOwner) public {
 require (msg.sender == creator, "Available only to contract creator");   
 isOwner[newOwner] = true;
}

function CheckIfOwner() public view returns(bool){
    if(isOwner[msg.sender] == true){
        return true;
    }
    else{return false;}
}



mapping (address => uint) balance;


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


uint limit;
function nrOfApprovals(uint _limit) public {
require (msg.sender == creator, "Available only to contract creator");
limit = _limit;
}


struct Transaction{
    address sender;
    address receiver;
    uint amount;
    uint txid;
}
Transaction[] pendingTx;

function CreateTransaction(address to, uint _amount) public OnlyOwner {
    require(balance[msg.sender] >= _amount, "Not enough balance.");
    Transaction memory current;
    current.sender = msg.sender;
    current.receiver = to;
    current.amount = _amount;
    current.txid = pendingTx.length;
    pendingTx.push(current);
    }

mapping (address => mapping(uint => bool)) approvals;
mapping (uint => uint) approved;

    function approve(uint _txid) public {
    require(isOwner[msg.sender] == true, "You are not owner.");
    require(approvals[msg.sender][_txid] == false, "You can only approve once!");
    approvals[msg.sender][_txid] = true;
    approved[_txid] += 1;
    }

function checkTxApprovals(uint Txid) public view returns(uint){
return approved[Txid];
}

function send(uint TxId) public{
    require (limit > 0, "Please input the required nr. of approvals.");
    require(approved[TxId] >= limit, "You need more approvals.");
    balance[pendingTx[TxId].sender] -= pendingTx[TxId].amount;
    balance[pendingTx[TxId].receiver] += pendingTx[TxId].amount;
}

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





}
1 Like

There’s something I don’t understand about L70 from the solution:

transferRequests[_id].receiver.transfer(transferRequests[_id].amount);

How are we checking that the contract has enough ETH to transfer to the receiver?

In line 61 we should add something like require(transferRequests[_id].amount <= contractBalance). However, the solution does not even have any variable to store the ETH balance of the contract, and the deposit() function is not implemented :face_with_raised_eyebrow:.

Just finished my MultiSig wallet and would love some feedback.

pragma solidity 0.8.7;
pragma abicoder v2;
contract MulitSig{

    address [] owners;
    uint256 approvalsNeeded;
    //Set Transaction to done when finished
    enum status{NOTDONE,DONE}

    struct Transaction{
        address to;
        uint256 amount;
        uint256 txId;
        status txStatus;
    }
    //Holds array of all transactions, array length will result in Transaction Id
    Transaction [] pendingTrans;
    mapping(address => mapping(uint256 => bool)) hasApproved;
    // TransactionId to Aprovalcount to keep track
    mapping(uint256 => uint256) aprovalConfirmed;
    //Read in number of approvals and list of owners
    constructor(uint256 _number, address [] memory _owners){
        require(_number <= _owners.length + 1,"Cant have more approvals than owners");
         approvalsNeeded = _number;
        owners = _owners;
        owners.push(msg.sender);
    }
    modifier onlyOwner{
        require(isOwner(msg.sender)==true,"You are not an owner");
        _;
    }

    //push transaction to array only owners can do this
    function startTransaction(address _to, uint256 _amount) public onlyOwner{
        require(address(this).balance >= _amount,"Not enough balance for this transaction");
            pendingTrans.push(Transaction(_to,_amount,pendingTrans.length,status.NOTDONE));
    }
    //approve a transaction
    function approve(uint256 _txId)public onlyOwner{
        require(pendingTrans.length != 0, "There are no transactions yet");
        require(_txId < pendingTrans.length, "This transaction does not exist");
        require(pendingTrans[_txId].txStatus == status.NOTDONE ,"The transaction has procced already");
        require(hasApproved[msg.sender][_txId] == false,"You have approved this already");
        //set user to approved and increase a approval
        hasApproved[msg.sender][_txId] = true;
        aprovalConfirmed[_txId]+=1;
        //if transactionid approval number is equal to amount of approvals set trasnsaction to done and transfer funds
         if(aprovalConfirmed[_txId] == approvalsNeeded){
        _transfer(pendingTrans[_txId].to,pendingTrans[_txId].amount);
        pendingTrans[_txId].txStatus = status.DONE;
        }
    }
    function viewTransaction(uint256 _txId)public view returns(Transaction memory){
        require(pendingTrans.length > _txId);
        return pendingTrans[_txId];
    }

    function _transfer(address _to, uint256 _amount)internal{
        payable(_to).transfer(_amount);
    }


    //Empty payable function
    function deposit() payable public {}

    //function to ensure an address is an owner
    function isOwner(address _addy) internal view returns(bool){
        for(uint i = 0; i < owners.length;i++){
            if(_addy == owners[i]){
                return true;
            }
        }
        return false;
            
            }

}

Here is my contract, thanks for the lessons and the double mapping video! Feedback is appreciated, whether it’s logic based or just things I could’ve done to make code more DRY, etc.

contract MultisigWallet {

    address owner;
    address[] addresses;
    uint approvalMin;
    mapping(address => uint) balance;

    struct Transaction {
        uint amount;
        address sender;
        address recipient;
    }

    Transaction[] transactions;
    mapping(address => mapping(uint => bool)) transactionsToApprove;
    mapping(uint => uint) numberOfApprovals;

    event transferRequested(uint amount, address indexed from, address indexed to, uint transactionNumber);
    event depositDone(uint amount, address indexed depositedTo);
    event withdrawDone(uint amount, address indexed withdrawledFrom);
    event transferCompleted(uint amount, address indexed from, address indexed to);

    constructor(address[] memory _addresses, uint _approvalMin) {
        addresses = _addresses;
        approvalMin = _approvalMin;
        owner = msg.sender;
    }

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

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

    function transfer(address recipient, uint amount) public returns (Transaction memory) {
        require(balance[msg.sender] >= amount, "Insufficient funds");
        require(msg.sender != recipient, "Don't transfer money to yourself");
        Transaction memory newTransaction = Transaction(amount, msg.sender, recipient);
        transactions.push(newTransaction);
        uint transactionNumber = transactions.length - 1;
        numberOfApprovals[transactionNumber] = 0;
        emit transferRequested(amount, msg.sender, recipient, transactionNumber);
        return newTransaction;
    }

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

    function approveTransaction(uint transactionNumber) public {
        require(numberOfApprovals[transactionNumber] < approvalMin, "This transaction has already been processed"); 

        bool transactionStatus = transactionsToApprove[msg.sender][transactionNumber];
        require(transactionStatus != true, "You have already approved this transaction"); 
        transactionsToApprove[msg.sender][transactionNumber] = true;
        numberOfApprovals[transactionNumber] += 1;

        if (numberOfApprovals[transactionNumber] == approvalMin) {
            address from = transactions[transactionNumber].sender;
            address to = transactions[transactionNumber].recipient;
            uint amount = transactions[transactionNumber].amount;
            _transfer(from, to, amount);
        }
    }
}
1 Like

this is my code.

During first write code, i had one problem,

the check of confirm for Owner and check Owner every element in array Owner… i used first “for” and code in theory it worked, but this require MORE gas and EVM revert transaction at 3 “for”.

i looking for on google as manage this… and i seeing some solution and remember that the Blockchain is as an Array… ad i have use mapping for check I needed .

// SPDX-License-Identifier: Leluk911
pragma solidity ^0.8.7;

contract MultiSigWallet {
    // list of Owner
    address[] public Owner;
    // confirm is owner
    mapping(address => bool) public isOwner;
    // mapping from tx index => owner => bool
    mapping(uint256 => mapping(address => bool)) public isConfirmed;
    // number of consents require
    uint256 public consents_need;

    struct Transaction {
        address payable to;
        uint256 value;
        bool executed;
        uint256 numConsents;
    }

    Transaction[] public transactions;

    // Variable contract Help
    uint256 public balanceETH;
    bool pause = false;
    ////---------------EVENT----------------/////

    event DepositEt(address indexed, uint256 indexed);

    event Tx_create(address indexed, uint256 indexed);

    event execute_Tx(address indexed, uint256 indexed);

    ////----------------constructor-----------//////

    constructor(address[] memory _Owner, uint256 _consents_need) payable {
        require(
            _Owner.length > 0 && _consents_need <= _Owner.length,
            "number of consens must less number Owner"
        );
        for (uint256 i = 0; i < _Owner.length; i++) {
            require(_Owner[i] != address(0), "address invalid");

            Owner.push(_Owner[i]);
            require(!isOwner[Owner[i]], "owner just exist");
            isOwner[Owner[i]] = true;
        }
        balanceETH = address(this).balance;
        consents_need = _consents_need;
        emit DepositEt(msg.sender, msg.value);
    }

    ////---------------MODIFIER----------------/////

    modifier StayPause() {
        require(!pause, "contract in pause");
        pause = true;
        _;
        pause = false;
    }

    modifier OnlyOwner() {
        require(isOwner[msg.sender], "not Owner");
        _;
    }

    modifier TxPassed(uint256 _indexTX) {
        require(!transactions[_indexTX].executed, "transaction already passed");
        _;
    }

    modifier IsntConfirm(uint256 _indexTX) {
        require(
            !isConfirmed[_indexTX][msg.sender],
            "transaction already Confirmed"
        );
        _;
    }

    ////---------------------------------------//////

    function depositETH() public payable StayPause {
        balanceETH = msg.value;
        emit DepositEt(msg.sender, msg.value);
    }

    function createNewTransaction(address payable _to, uint256 _value)
        public
        OnlyOwner
        StayPause
    {
        Transaction memory _transaction = Transaction(_to, _value, false, 0);
        transactions.push(_transaction);
        emit Tx_create(msg.sender, transactions.length);
    }

    function approveTransaction(uint256 _indexTX)
        public
        OnlyOwner
        TxPassed(_indexTX)
        IsntConfirm(_indexTX)
        StayPause
    {
        isConfirmed[_indexTX][msg.sender] = true;
        transactions[_indexTX].numConsents += 1;
    }

    function executeTransaction(uint256 _indexTX)
        public
        OnlyOwner
        TxPassed(_indexTX)
        StayPause
    {
        require(
            !transactions[_indexTX].executed,
            "This transaction just executed"
        );
        require(
            transactions[_indexTX].numConsents >= consents_need,
            "This transaction not Authorized"
        );
        (bool success, ) = transactions[_indexTX].to.call{
            value: transactions[_indexTX].value
        }("");
        require(success, "transaction faill");
        transactions[_indexTX].executed = true;
        balanceETH = transactions[_indexTX].value;
        emit execute_Tx(msg.sender, _indexTX);
    }
}
1 Like

This is my implementation - I used the same Ownable.sol from the lessons.

MultiSigWallet.sol:

pragma solidity 0.7.5;
pragma abicoder v2;

import "./Ownable.sol";

contract MultiSigWallet is Ownable {

    uint private balance;
    address[] private owners;
    uint private approvalNum;

    struct Transaction {
        address[] approvedBy;
        address to;
        uint amount;
        uint txId;
        bool fullyApproved;
    }

    Transaction[] private transactions;

    event depositDone(uint amount, address indexed depositedTo);
    event transactionRequested(uint amount, address indexed requester, address indexed to, uint indexed transactionIndex);
    event transactionSigned(address indexed approver, uint indexed transactionIndex);
    event transactionCompleted(uint amount, address indexed to, uint indexed transactionIndex);


    constructor(address[] memory _owners, uint _approvalNum){
        require(_approvalNum <= _owners.length);
               
        owners = _owners;
        approvalNum = _approvalNum;
    }


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


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


    function requestTransaction(address _to, uint _amount) public returns(Transaction memory){
        require(isOwner(msg.sender));

        uint transactionId = transactions.length;

        address[] memory approvedBy;
        Transaction memory transaction = Transaction(approvedBy, _to, _amount, transactionId, false);
        transactions.push(transaction);

        emit transactionRequested(_amount, msg.sender, _to, transactionId);

        _approveTransaction(msg.sender, transactionId);

        return transactions[transactionId];
    }


    function approveTransaction(uint _transactionId) public {
        _approveTransaction(msg.sender, _transactionId);
    }


    function _approveTransaction(address _approver, uint _transactionId) private {
        require(isOwner(_approver));
        
        bool alreadyApproved = false;
        for (uint i=0; i<transactions[_transactionId].approvedBy.length; i++){
            if (transactions[_transactionId].approvedBy[i] == _approver){
                alreadyApproved = true;
                break;
            }
        }
        require(alreadyApproved == false);
        
        transactions[_transactionId].approvedBy.push(_approver);
        emit transactionSigned(_approver, _transactionId);

        checkIfFullyApproved(_transactionId);
    }

    function checkIfFullyApproved(uint _transactionId) private {
        if (transactions[_transactionId].approvedBy.length >= approvalNum){
            transactions[_transactionId].fullyApproved = true;
            balance -= transactions[_transactionId].amount;
            emit transactionCompleted(transactions[_transactionId].amount, transactions[_transactionId].to, _transactionId);
        }
    }

    function isOwner(address _owner) private view returns(bool){
        bool addressFound = false;
        for (uint i=0; i<owners.length; i++){
            if (owners[i] == _owner){
                addressFound = true;
                break;
            }
        }
        return addressFound;
    }
}
1 Like
pragma solidity 0.7.5;

pragma abicoder v2;

contract MultiSigWallet {

    //Transaction struct

    struct Transactions{

        address payable recipiant;

        uint amount;

        uint txId;

        uint approvals;

        bool transferApproved;

    }

   

    // An array to keeptrack of transaction

    Transactions[] transactionLog;

    address public creator;

    address[] public owners;

    uint approvalLimit;

    uint balance;

    mapping(address => mapping(uint => bool)) transferApprovals;

   

    modifier onlyOwner() {

        bool owner = false;

        for(uint i=0; i<owners.length;i++){

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

                owner = true;

            }

        }

        require(owner == true);

        _;

    }

    modifier txExists(uint _txId){

        require(_txId < transactionLog.length);

        _;

    }

    modifier notApproved(uint _txId){

        require(!transferApprovals[msg.sender][_txId]);

        _;

    }

    modifier notExecuted(uint _txId){

        require(!transactionLog[_txId].transferApproved);

        _;

    }

    modifier onlyCreator{

        require(msg.sender == creator);

        _;

    }

   

    constructor (address[] memory _owners, uint _approvalLimit){

        creator = msg.sender;

        owners = _owners;

        approvalLimit = _approvalLimit;

    }

    function deposit() public payable onlyOwner returns(uint){

        require(msg.value > 0);

        balance += msg.value;

        return balance;

    }

   

     function createTransaction(uint _amount, address payable _recipiant) public onlyOwner {

        transactionLog.push(Transactions(_recipiant, _amount, transactionLog.length, 0, false));

    }

    function approve(uint _txId) public onlyOwner txExists(_txId) notApproved(_txId) notExecuted(_txId) {

        require(transferApprovals[msg.sender][_txId] == false);

        require(transactionLog[_txId].transferApproved == false);

        require(_getApprovalCount(_txId) >= approvalLimit);

         

        transferApprovals[msg.sender][_txId] = true;

        transactionLog[_txId].approvals++;

        if(transactionLog[_txId].approvals >= approvalLimit){

            transactionLog[_txId].transferApproved = false;

            transactionLog[_txId].recipiant.transfer(transactionLog[_txId].amount);

        }

    }

    function _getApprovalCount(uint _txId) public view returns(uint){

        uint count;

        for(uint i = 0; i < owners.length; i++){

            if(transferApprovals[owners[i]][_txId]){

                count++;

            }

        }

        return count;

    }

    function cancelApproval(uint _txId) public onlyOwner txExists(_txId) notExecuted(_txId){

        require (transferApprovals[msg.sender][_txId]);

        transferApprovals[msg.sender][_txId] = false;

    }

    function getBalance() public view returns(uint){

        return balance;

    }

    function addNewOwner(address _newOwner) public onlyCreator{

        owners.push(_newOwner);

    }

     function getTransactionLog() public view returns (Transactions[] memory){

         return transactionLog;

    }

}

my previous solution had a bug. got it fixed after watching the last video. in my previous submission i created a separate function for executing the transaction but after the second owner calls the approve function it doesnt transfer the ether.

Hi , I haven’t watched the next video was trying to implement multisig wallet with two owner first. But in transfer function , require statement should give the error when the condition is false but i am not able to see the error. please give suggestion

pragma solidity ^0.8.0;

contract multisigWallet{

enum Status{

 Pending,

 Approved,

 Rejected

}

struct OWNER{

 address addr;

 Status status;

}

modifier onlyOwner(){

 require(msg.sender==owner[0].addr||msg.sender==owner[1].addr,"You are not a owner");

 _;

}

OWNER[2] owner;

constructor(address add1,address add2){

 owner[0].addr=add1;

 owner[1].addr=add2;

}

function getStatus() public view onlyOwner returns(string memory){

if(msg.sender==owner[0].addr){

     if(owner[0].status==Status.Pending){

         return "Status is Pending";

     }

     else if(owner[0].status==Status.Approved){

         return "Status is Approved ";

     }

     else{

         return "Status is Rejected";

     }

    }

else {

    if(owner[1].status==Status.Pending){

         return "Status is Pending";

     }

     else if(owner[1].status==Status.Approved){

         return "Status is Approved ";

     }

     else{

         return "Status is Rejected";

     }

}

}

function setStatus(address addr,Status sta)public onlyOwner{

if(addr==owner[0].addr){

    owner[0].status=sta;

}

else{

    owner[1].status=sta;

}

}

function depositFund() payable public{

}

function fundBalance() public view returns(uint){

return address(this).balance;

}

function transfertoAccount(address payable _to,uint amount) payable onlyOwner public{

require(owner[0].status==Status.Approved && owner[1].status==Status.Approved, "Please get the approval of owner");

_to.transfer(amount);

}

}

// SPDX-License-Identifier:MIT

pragma solidity ^0.8.0;

contract Wallet{

event submit(uint txnid,address receiver,uint amount);

event executed(uint txnid,address receiver,uint amount);

address[]  owners;

mapping(address=>bool) isOwner;

uint public limit;

struct Transaction{

    uint txnid;

    address payable receiver;

    uint amount;

    bool executed;

   

}

Transaction[]  transactions;

mapping(uint=>mapping(address=>bool))  Approved;

modifier onlyOwner(){

    require(isOwner[msg.sender]==true,"You are not a owner");

    _;

}

modifier notApproved(uint _txnid){

    require(Approved[_txnid][msg.sender]==false,"You have approved this transaction");

    _;

}

modifier notExecuted(uint _txnid){

    require(transactions[_txnid].executed==false,"Transaction is already executed");

    _;

}

constructor(address[] memory _owners,uint _limit){

    require(_owners.length>0 , "Owners required");

    require(_limit>0 && _limit<=_owners.length,"Not a valid limit");

   

    for(uint i;i<_owners.length;i++){

       address owner = _owners[i];

       require(isOwner[owner]==false,"Owner is not unique");

       isOwner[owner]=true;

       owners.push(owner);

    }

    limit=_limit;

}

function depositFund() payable public{

}

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

   

    transactions.push(Transaction(transactions.length,_receiver,_amount,false));

    emit submit(transactions.length,_receiver,_amount);

}

function approve(uint _txnid) public onlyOwner notApproved(_txnid) notExecuted(_txnid){

    Approved[_txnid][msg.sender]=true;

}

function getApprovalCount(uint _txnid) public view returns(uint count){

    for(uint i=0 ; i<owners.length ; i++){

   

        if(Approved[_txnid][owners[i]]==true){

            count=count+1;

        }

       

    }

    return count;

}

function getTransaction(uint _id) public view returns(Transaction memory){

    return transactions[_id];

}

function execute(uint _txnid) public onlyOwner notExecuted(_txnid){

    require(getApprovalCount(_txnid)<=limit,"More approval needed");

    transactions[_txnid].receiver.transfer(transactions[_txnid].amount);

    transactions[_txnid].executed=true;

    emit executed(_txnid,transactions[_txnid].receiver,transactions[_txnid].amount);

}

function CheckBalanceofContract() public view returns(uint){

    return address(this).balance;

}

}

1 Like

Here is my implementation, any feedback will be welcome.

pragma solidity 0.7.5; 
pragma abicoder v2;


contract Multisign {

// The contract creator should be able to input (1): 
                //-the addresses of the owners and (2): 
                // the numbers of approvals required for a transfer, in the constructor. For example, input 3 addresses and set the approval limit to 2. 
    mapping (address => bool) owners; 
    uint approvalsigns;

    constructor (address [] memory initialOwners, uint approvalLimit){
        
        for (uint i = 0; i < initialOwners.length; i++){
            //including each owner from the input to the mapping, and set the value to true
            owners[initialOwners[i]] = true;
        }
       
        
        approvalsigns = approvalLimit;

    }

    //modifier to identify the owners within the functions

    modifier isOwner{ 
        require(owners[msg.sender] == true, " only owners can performance this operation");
        _;
    }

    //Transfer structure 
    struct Transfer{
        uint _id;
        address payable _beneficiary;
        string  _concept; 
        uint _amount;
        uint _signs;
        address [] _approvals; //to store who signed the transfers
        
    }
    uint id=0; // Setting the Trnasfer Id to 0, it allways grows 

    Transfer [] public pendingTransfers; // to store the not approved yet, I made it public then anyone can check what are the pending transfers

    mapping(address =>mapping (uint=> bool)) approvals; //to check who have signed 

    event depositDone(uint amount, address indexed depositedFrom); //event to keep track of the deposits
    event transferSent(Transfer transfer); //Event to keep track of the transfers already done 
   
   
   
   // Anyone should be able to deposit ether into the smart contract

    function deposit() public payable {
        //function to deposit in the samrt contract, anyone is allowed 
       
          emit depositDone(msg.value, msg.sender);
       
    }



// Anyone of the owners should be able to create a transfer request. The creator of the transfer request will specify what amount and 
//to what address the transfer will be made.
    function createPayment(address payable beneficiary, string memory concept, uint amount) public isOwner returns(Transfer memory) {
     
        Transfer memory newTransfer = Transfer (id, beneficiary, concept, amount, 0, new address[](0));
        pendingTransfers.push(newTransfer); 
        id++;
        return newTransfer;

    }

   // – Owners should be able to approve transfer requests.
  //– When a transfer request has the required approvals, the transfer should be sent. 
    
  //The next function approve, allows the owners to approve and immediately send the transfer if we reach the approval limit 


    
    function approve(uint id) public isOwner returns (string memory) {

             for (uint i = 0; i < pendingTransfers.length; i++){ 
                 if (pendingTransfers[i]._id==id){
                     if (approvals[msg.sender][id]==true){ //check that they are not signing twice the same transfer
                         return "you have already approved this transfer";
                     }
                     else{
                        pendingTransfers[i]._approvals.push(msg.sender);
                        pendingTransfers[i]._signs++;
                       
                        if (pendingTransfers[i]._signs ==approvalsigns){ //check if we got the approvals needed
                            pendingTransfers[i]._beneficiary.transfer(pendingTransfers[i]._amount);// if so, Make the transfer
                            emit transferSent(pendingTransfers[i]);
                            approvals[msg.sender][id]=true; // Set the approvals mapping to true to avoid to collect 2 times the same signature
                            removeTransfer(i); //we remove the transfer from pending transfers
                            return "Transaction approved and payed";
                        }
                        return "You approved the transaction, Waiting for other approval";
                     }
                 }     
            }

         
    }

    function removeTransfer(uint index) internal { 
        //function to remove a selected transfer from the array of transfers, internal because in this case we only remove a transfer when we fully approve them
            pendingTransfers[index]= pendingTransfers[pendingTransfers.length-1];
            pendingTransfers.pop();
    }

}
2 Likes

My solution is rather simple, could be buggy, I haven’t used help but after seeing some of the solutions above maybe I should have :grin:
This is my attempt.


pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet {

    mapping(address => uint) balance;

    mapping(uint => address) owners;

    uint countOfApprovals;

    uint approvalLimit;

    mapping(address => mapping(uint => bool)) approvals;

    modifier ownersOnly {
        require(msg.sender == owners[0] || msg.sender == owners[1] || msg.sender == owners[2],
        "Only owners can approve or transfer");
        _;
    }

    constructor() {
        owners[0] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
        owners[1] = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
        owners[2] = 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db;
        approvalLimit = 2;
    }

    function approve() public ownersOnly {
        countOfApprovals++;
        approvals[msg.sender][countOfApprovals] = true;
    }

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

    function transferToSomeone(address recipient, uint amount) public ownersOnly {
        require(balance[msg.sender] >= amount, "Balance not sufficient");
        require(msg.sender != recipient, "Don't transfer money to yourself");
        // check if 2 approvals are signed
        require(countOfApprovals >= approvalLimit);

        _transfer(msg.sender, recipient, amount);
    }

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

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

    // get balance of smart contract
    function getBalanceOfSC() public view returns (uint256) {
        return address(this).balance;
    }

}

Edit: fixed it but I will leave it here for honest attempt

1 Like