- Truffle:
$ truffle version
Truffle v4.1.0 (core: 4.1.0)
Solidity v0.4.19 (solc-js)
- Geth:
$ geth version
Geth
Version: 1.8.1-stable
Git Commit: 1e67410e88d2685bc54611a7c9f75c327b553ccc
- Npm:
$ npm --version
5.6.0
- Node:
$ node --version
v9.5.0
As Möbius supports transfer of ethers and ERC20 compatible tokens, we denote by Deposit
and Withdraw
- in this tutorial - the action to do a deposit and a withdrawal to and from the Mixer. The user is free to use the appropriate suffix Ether
or ERC20Compatible
.
DepositERC20Compatible
function to run, the sender has to authorize the Mixer
to trigger a transfer of tokens on his behalf, FIRST. To do this, the sender has to run: token.approve([mixerAddress], [amountToApprove], {from: [senderAccount]});
(see: ERC20 interface for more details), before calling DepositERC20Compatible
.
DepositERC20Compatible
function is not payable, so the value
field of the transaction object should be omitted.
- The
Mixer
has aRING_SIZE
equal to 1 (see: https://github.com/clearmatics/mobius/blob/master/contracts/LinkableRing.sol#L80). If you want to follow the rest of the tutorial, please clone this repository and modify theLinkableRing
contract accordingly:
export $WORKDIR = ~/Path/To/Your/Working/Directory
git clone https://github.com/clearmatics/mobius.git $WORKDIR
cd $WORKDIR/mobius
[YourFavoriteTextEditor] contracts/LinkableRing.sol
[Change line 80: uint256 public constant RING_SIZE = 4; INTO uint256 public constant RING_SIZE = 1;]
[Save and exit your editor]
- We call Alice the sender of the payment and Bob the recipient.
- We assume that two accounts with some ethers are available in order to run this tutorial.
Orbital: https://github.com/clearmatics/orbital
- Run
orbital generate -n 1 > keys.json && cat keys.json
:
{
"pubkeys": [
{
"x": "0x26569781c3ab69ff42834ea67be539bb231fa48730afc3c89f2bba140b2045b2",
"y": "0xbf75913861d38b5a01b53654daa260856d5dd705af6a24e57622811d485e407"
}
],
"privkeys": [
"0x216e142880261d4b743386185c41ae9cf3609f648dbedc15bd0790332b23fb87"
]
}
- Make sure you have Ganache-cli installed
Ganache CLI v6.0.3 (ganache-core: 2.0.2)
- Start Ganache-cli:
yarn testrpc
The steps described in this section should only be executed if you decided not to use Ganache-cli
to run Mobius.
- Create a
genesis.json
file like this one:touch genesis.json
and copy paste the code below into yourgenesis.json
file:
{
"config": {
"chainId": 127,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0
},
"alloc" : {
"0x557ef97e60a4aab92c3b3e000a67fb5d26be04b4": {
"balance": "10000000000000000000000000000000000000000000000000000"
},
"0x8a7cf916f9b6e1bb77ea6282eb5c8b5eb5b779cc": {
"balance": "10000000000000"
},
},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0xFFFFFFF",
"gasPrice" : "0x1",
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00"
}
Note: Make sure to have the line "byzantiumBlock": 0
in the config
of your genesis.json
file in order to make sure that the Mobius contract will be able to execute precompiled contracts. (See: Byzantium)
2. Create the data directory for your local configuration of your ethereum network:
mkdir ~/ethdev
- Start your node with the genesis configuration:
geth --datadir ~/ethdev init genesis.json
- Start your mining node and open the geth console:
geth --datadir ~/ethdev --identity=NODE_ONE --networkid=15 --verbosity=1 --mine --minerthreads=1 --rpc --rpcport=8545 --nodiscover --maxpeers=1 console
All the steps above are gathered into a script below. Run: touch startCustomNode.sh && chmod +x startCustomNode.sh
and Copy/Paste the content below into the file, then run: ./startCustomNode.sh
#!/bin/bash
if [ ! -f "./geth" ]; then
echo "==> Fetching GETH 1.8.1 <==\n"
wget https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.1-1e67410e.tar.gz
echo "Unpacking GETH 1.8.1"
tar -xvf geth-linux-amd64-1.8.1-1e67410e.tar.gz
fi
echo "==> Creating ~/ethdev folder <== \n"
mkdir ~/ethdev
echo "==> Initializing the private blockchain with custom genesis file <==\n"
./geth --datadir ~/ethdev init genesis.json
echo "==> Starting miner node: Listening rpc on 8545 <==\n"
./geth --datadir ~/ethdev --identity=NODE_ONE --networkid=15 --verbosity=1 --mine --minerthreads=1 --rpc --rpcport=8545 --nodiscover --maxpeers=1 console
- Compile the contracts:
truffle compile
- Deploy the contracts to the
development
network
truffle deploy --network development
The logs of truffle deploy should output the address to which the Mixer has been deployed Mixer: [Address]
. If you can't see this log, run Mixer.deployed()
in the truffle console
and save the address somewhere (we'll need it in a few steps).
3. Get the Mixer Abi:
echo "var MixerAbi=`solc --optimize --combined-json abi contracts/Mixer.sol`" > mixerAbi.js
In the Geth console, run the following commands
- Load the file containing the ABI of the Mixer contract:
loadScript('mixerAbi.js')
- Store the ABI of the Mixer into a variable:
var mixerContractAbi = MixerAbi.contracts['contracts/Mixer.sol:Mixer'].abi;
- Run:
var mixerContract = eth.contract(JSON.parse(mixerContractAbi));
var mixerContractAddress = [Paste here the Mixe Address saved at step 2];
var mixer = mixerContract.at(mixerContractAddress);
After this step, we can use the mixerInstance
variable in order to interact with the contract from the geth console.
- Compile the Mixer contract:
echo "var mixerOutput=`solc --optimize --combined-json abi,bin,interface contracts/Mixer.sol`" > mixer.js
In the Geth console, run the following commands
- Load the
mixer.js
file:
loadScript('mixer.js')
- Store the ABI of the Mixer into a variable:
var mixerContractAbi = mixerOutput.contracts['contracts/Mixer.sol:Mixer'].abi
- Run:
var mixerContract = eth.contract(JSON.parse(mixerContractAbi))
var mixerBinCode = "0x" + mixerOutput.contracts['contracts/Mixer.sol:Mixer'].bin
personal.unlockAccount([AccountYouWantToUseToDeploy], [PasswordOfTheAccount], 0)
var deployTransationObject = { from: eth.accounts[0], data: mixerBinCode, gas: 1000000 };
var mixerInstance = mixerContract.new(deployTransationObject)
var mixerContractAdress = eth.getTransactionReceipt(mixerInstance.transactionHash).contractAddress
var mixer = mixerContract.at(mixerContractAddress);
- Define listeners to listen to the Mixer events:
var mixerDepositEvent = mixer.LogMixerDeposit();
mixerDepositEvent.watch(function(error, result){
if (error) { console.log(error); return; }
console.log("MixerDeposit event");
});
var mixerReadyEvent = mixer.LogMixerReady();
mixerReadyEvent.watch(function(error, result){
if (error) { console.log(error); return; }
console.log("MixerReady event");
console.log("Ring message: " + result.args.message);
console.log("Ring GUID: " + result.args.ring_id);
});
var mixerWithdrawEvent = mixer.LogMixerWithdraw();
mixerWithdrawEvent.watch(function(error, result){
if (error) { console.log(error); return; }
console.log("MixerWithdraw event");
});
var mixerDeadEvent = mixer.LogMixerDead();
mixerDeadEvent.watch(function(error, result){
if (error) { console.log(error); return; }
console.log("MixerDead event");
});
- Get the balance of the Mixer (should be equal to zero):
eth.getBalance("[mixerAddress]")
- Deposit funds to the Mixer (after generating the keys with
orbital generate -n 1 > keys.json
):
var pubX = [X component of pubKey in keys.json]
var pubY = [Y component of pubKey in keys.json]
var AliceAccount = eth.accounts[0];
var gasValue: [AmountOfGasAliceIsReadyToPay]
var yourDenomination = [AmountOfMoneyYouWantToTransfer]
// We trigger a deposit from Alice's account
mixer.depositEther(0, yourDenomination, pubX, pubY, {from: AliceAccount, value: yourDenomination, gas: gasValue})
- Verify that the deposit has successfully been done on the contract (the balance of the contract should be equal to the denomination)specified by the sender in the
Deposit
function
eth.getBalance("[mixerAddress]")
- Generate the ring signature using orbital:
orbital inputs -n 1 -f keys.json -m [RingMessage] > signature.json
- Withdraw funds from Bob's account:
var bobAccount = eth.accounts[1];
personal.unlockAccount(bobAccount); // Unlock Bob's account
var tauX = [tauX in signature.json];
var tauY = [tauY in signature.json];
var ctlist = [ctlistArray]
var ringGuid = [ring GUID returned by the mixerReadyEvent];
var gasValue: [AmountOfGasBobIsReadyToPay]
mixer.withdrawEther(ringGuid, tauX, tauY, ctlist, {from: bobAccount, gas: gasValue});
- At this stage of the process, the balance of the Mixer should be equal to 0 again. Verify it:
eth.getBalance("[mixerAddress]")
- Bob's account should have been credited. In order to make sure that everything worked well, Bob's balance after the withdrawal should respect the equality:
BalanceAfterWithdrawal = (BalanceBeforeWithdrawal - (gasUsed * gasPrice)) + denomination
Note: The gasUsed
and gasPrice
values can be accessed directly by running eth.getTransactionReceipt("[HashOfTheWithdrawalTX]")
.
- Bob has to have funds on his account in order to be able to pay for the Withdraw function to be executed. Thus, Mobius cannot be used to send funds to an address that doesn't have a minimum amount of funds.