Skip to content
This repository has been archived by the owner on Sep 5, 2020. It is now read-only.

Wallet Contract

Dmitriy Khvatov edited this page Sep 24, 2016 · 4 revisions

Source Code

Wallet Contracts vs Accounts

When deployed with n owners, wallet contracts are multisignature vaults that require m of n owners to sign transactions that exceed a daily threshold.

Transacting from the Geth Interpreter

Creating a Contract Instance

To initiate a transaction from a wallet contract, first you must create an instance of the wallet contract. The example below is for an example v2 Wallet Contract deployed at the address "0x92fb112698b377023F3d076007B04608c03CF9d0":

myABI = [ { "constant": false, "inputs": [ { "name": "_owner", "type": "address" } ], "name": "removeOwner", "outputs": [], "type": "function" }, { "constant": false, "inputs": [ { "name": "_addr", "type": "address" } ], "name": "isOwner", "outputs": [ { "name": "", "type": "bool" } ], "type": "function" }, { "constant": true, "inputs": [], "name": "m_numOwners", "outputs": [ { "name": "", "type": "uint256", "value": "4" } ], "type": "function" }, { "constant": true, "inputs": [], "name": "m_lastDay", "outputs": [ { "name": "", "type": "uint256", "value": "17013" } ], "type": "function" }, { "constant": true, "inputs": [], "name": "version", "outputs": [ { "name": "", "type": "uint256", "value": "2" } ], "type": "function" }, { "constant": false, "inputs": [], "name": "resetSpentToday", "outputs": [], "type": "function" }, { "constant": true, "inputs": [], "name": "m_spentToday", "outputs": [ { "name": "", "type": "uint256", "value": "1000000000000000000" } ], "type": "function" }, { "constant": false, "inputs": [ { "name": "_owner", "type": "address" } ], "name": "addOwner", "outputs": [], "type": "function" }, { "constant": true, "inputs": [], "name": "m_required", "outputs": [ { "name": "", "type": "uint256", "value": "2" } ], "type": "function" }, { "constant": false, "inputs": [ { "name": "_h", "type": "bytes32" } ], "name": "confirm", "outputs": [ { "name": "", "type": "bool" } ], "type": "function" }, { "constant": false, "inputs": [ { "name": "_newLimit", "type": "uint256" } ], "name": "setDailyLimit", "outputs": [], "type": "function" }, { "constant": false, "inputs": [ { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" }, { "name": "_data", "type": "bytes" } ], "name": "execute", "outputs": [ { "name": "_r", "type": "bytes32" } ], "type": "function" }, { "constant": false, "inputs": [ { "name": "_operation", "type": "bytes32" } ], "name": "revoke", "outputs": [], "type": "function" }, { "constant": false, "inputs": [ { "name": "_newRequired", "type": "uint256" } ], "name": "changeRequirement", "outputs": [], "type": "function" }, { "constant": true, "inputs": [ { "name": "_operation", "type": "bytes32" }, { "name": "_owner", "type": "address" } ], "name": "hasConfirmed", "outputs": [ { "name": "", "type": "bool", "value": false } ], "type": "function" }, { "constant": false, "inputs": [ { "name": "_to", "type": "address" } ], "name": "kill", "outputs": [], "type": "function" }, { "constant": false, "inputs": [ { "name": "_from", "type": "address" }, { "name": "_to", "type": "address" } ], "name": "changeOwner", "outputs": [], "type": "function" }, { "constant": true, "inputs": [], "name": "m_dailyLimit", "outputs": [ { "name": "", "type": "uint256", "value": "1000000000000000000" } ], "type": "function" }, { "inputs": [ { "name": "_owners", "type": "address[]" }, { "name": "_required", "type": "uint256" }, { "name": "_daylimit", "type": "uint256" } ], "type": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "owner", "type": "address" }, { "indexed": false, "name": "operation", "type": "bytes32" } ], "name": "Confirmation", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "owner", "type": "address" }, { "indexed": false, "name": "operation", "type": "bytes32" } ], "name": "Revoke", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "oldOwner", "type": "address" }, { "indexed": false, "name": "newOwner", "type": "address" } ], "name": "OwnerChanged", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "newOwner", "type": "address" } ], "name": "OwnerAdded", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "oldOwner", "type": "address" } ], "name": "OwnerRemoved", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "newRequirement", "type": "uint256" } ], "name": "RequirementChanged", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "from", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Deposit", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "owner", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" }, { "indexed": false, "name": "to", "type": "address" }, { "indexed": false, "name": "data", "type": "bytes" } ], "name": "SingleTransact", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "owner", "type": "address" }, { "indexed": false, "name": "operation", "type": "bytes32" }, { "indexed": false, "name": "value", "type": "uint256" }, { "indexed": false, "name": "to", "type": "address" }, { "indexed": false, "name": "data", "type": "bytes" } ], "name": "MultiTransact", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "operation", "type": "bytes32" }, { "indexed": false, "name": "initiator", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" }, { "indexed": false, "name": "to", "type": "address" }, { "indexed": false, "name": "data", "type": "bytes" } ], "name": "ConfirmationNeeded", "type": "event" } ]

var myAddress = "0x92fb112698b377023F3d076007B04608c03CF9d0";
var myContract = eth.contract(myABI);
var myInstance = myContract.at(myAddress);

Typing myInstance. and hitting tab displays the contract's available functions as described in the ABI. For instance, for the wallet contract above:

myInstance.m_dailyLimit();
myInstance.m_lastDay()
myInstance.m_numOwners()
myInstance.m_required()
myInstance.m_spentToday()

Transferring Into The Contract

The wallet contract will happily take all of the money that's sent to its address. No additional transaction metadata is required to make a deposit. To transfer 10 ether from your coinbase to the wallet contract described above:

eth.sendTransaction( { from: eth.coinbase, to: "0x92fb112698b377023F3d076007B04608c03CF9d0", value: web3.toWei(10, "ether") } )

Withdrawing From The Contract

To make a withdrawal, the execute() function can be called; a destination address, amount, and (optional) data are supplied, and the account that is submitting the withdrawal request (and paying the gas cost to do so) must be indicated in the transaction. For instance, for "0x7c468346f38261f26974575595b1ece2636d6c17" to initiate a withdrawal of 1 ether from the contract into "0x6c6212c1eea2f2b7d38b1062e408a73e35cc14b9":

toAddress = "0x6c6212c1eea2f2b7d38b1062e408a73e35cc14b9";
requestAddress = "0x7c468346f38261f26974575595b1ece2636d6c17";
valueWithdrawn = web3.toWei(1, "ether");
amtGas = 100000;
myInstance.execute(toAddress, valueWithdrawn, "", { from: requestAddress, gas: amtGas  } );

The transaction will take 50,000+ gas, so allocate accordingly.

Confirming a Transaction

If an owner request a withdrawal over the daily spend limit or executes another function that requires other contract owners to sign, the other owners must:

  • scan the blockchain for ConfirmationNeeded events associated with the request
  • parse the operation hash associated with the request
  • call the contract's confirm function from an owner account, referencing the request's operation hash

Here is a simple example that will get you the just the transaction hashes of transactions that contain events "ConfirmationNeeded" events from multisignature wallet contract transactions. The first step creates a filter; the second step uses 'watch' to scan the blockchain with that filter.

var myFilter = web3.eth.filter({fromBlock:0, toBlock: 'latest', address: myInstance.address, topics:myInstance.ConfirmationNeeded().options.topics}, function(error, result) {
if (!error)
    console.log(result.transactionHash);
});
myFilter.watch(function(error,result) {
    console.log(error);
    console.log(result);
})

In a single step, here is an example that will scan the blockchain and return the operation hashes associated with "ConfirmationNeeded" events created by multisignature wallet contract transactions. This will also find the operation hashes of operations that have already been confirmed, so you'll need to combine this with additional filtering!

var myFilter = web3.eth.filter({fromBlock:0, toBlock: 'latest', address: myInstance.address, topics:myInstance.ConfirmationNeeded().options.topics}, function(error, result) {
if (!error)
    tx = String(result.transactionHash);
    tx_r = eth.getTransactionReceipt(tx);
    console.log(tx_r.logs[1].data.substring(0,66));
});

Using the operation hash from above, one of other multisig addresses can now confirm the transaction request:

ohash = "0xc8df8e62cfc44eb4cd6db0d28026eddfb8f1920027a9549145b42362d9c9282f"
addSender = "0x6c6212c1eea2f2b7d38b1062e408a73e35cc14b9"
myInstance.confirm(ohash, { from:addSender, gas: 200000 } )

Once a sufficient number of owners have confirmed, the operation will proceed.