@ -1,6 +1,8 @@
#!/usr/bin/env node
#!/usr/bin/env NODE_OPTIONS=--no-warnings node
// Temporary demo client
// Works both in browser and node.js
require ( 'dotenv' ) . config ( )
const fs = require ( 'fs' )
const axios = require ( 'axios' )
const assert = require ( 'assert' )
@ -12,13 +14,17 @@ const merkleTree = require('./lib/MerkleTree')
const Web3 = require ( 'web3' )
const buildGroth16 = require ( 'websnark/src/groth16' )
const websnarkUtils = require ( 'websnark/src/utils' )
const { toWei , fromWei } = require ( 'web3-utils' )
const { toWei , fromWei , toBN , BN } = require ( 'web3-utils' )
const config = require ( './config' )
const program = require ( 'commander' )
let web3 , tornado , erc20tornado , circuit , proving _key , groth16 , erc20 , senderAccount
let MERKLE _TREE _HEIGHT , ETH _AMOUNT , TOKEN _AMOUNT , ERC20_TOKEN
let web3 , tornado , circuit, proving _key , groth16 , erc20 , senderAccount , netId
let MERKLE _TREE _HEIGHT , ETH _AMOUNT , TOKEN _AMOUNT , PRIVATE_KEY
/** Whether we are in a browser or node.js */
const inBrowser = ( typeof window !== 'undefined' )
let isLocalRPC = false
const networks = { '1' : 'mainnet' , '42' : 'kovan' }
/** Generate random number of specified byte length */
const rbigint = nbytes => snarkjs . bigInt . leBuff2int ( crypto . randomBytes ( nbytes ) )
@ -28,21 +34,27 @@ const pedersenHash = data => circomlib.babyJub.unpackPoint(circomlib.pedersenHas
/** BigNumber to hex string of specified length */
function toHex ( number , length = 32 ) {
le t str = number instanceof Buffer ? number . toString ( 'hex' ) : bigInt ( number ) . toString ( 16 )
cons t str = number instanceof Buffer ? number . toString ( 'hex' ) : bigInt ( number ) . toString ( 16 )
return '0x' + str . padStart ( length * 2 , '0' )
}
/** Display account balance */
async function printBalance ( account , name ) {
console . log ( ` ${ name } ETH balance is ` , web3 . utils . fromWei ( await web3 . eth . getBalance ( account ) ) )
console . log ( ` ${ name } Token Balance is ` , web3 . utils . fromWei ( await erc20 . methods . balanceOf ( account ) . call ( ) ) )
/** Display ETH account balance */
async function printETHBalance ( { address , name } ) {
console . log ( ` ${ name } ETH balance is ` , web3 . utils . fromWei ( await web3 . eth . getBalance ( address ) ) )
}
/** Display ERC20 account balance */
async function printERC20Balance ( { address , name , tokenAddress } ) {
const erc20ContractJson = require ( './build/contracts/ERC20Mock.json' )
erc20 = tokenAddress ? new web3 . eth . Contract ( erc20ContractJson . abi , tokenAddress ) : erc20
console . log ( ` ${ name } Token Balance is ` , web3 . utils . fromWei ( await erc20 . methods . balanceOf ( address ) . call ( ) ) )
}
/ * *
* Create deposit object from secret and nullifier
* /
function createDeposit ( nullifier , secret ) {
le t deposit = { nullifier , secret }
function createDeposit ( { nullifier , secret } ) {
cons t deposit = { nullifier , secret }
deposit . preimage = Buffer . concat ( [ deposit . nullifier . leInt2Buff ( 31 ) , deposit . secret . leInt2Buff ( 31 ) ] )
deposit . commitment = pedersenHash ( deposit . preimage )
deposit . nullifierHash = pedersenHash ( deposit . nullifier . leInt2Buff ( 31 ) )
@ -50,88 +62,90 @@ function createDeposit(nullifier, secret) {
}
/ * *
* Make an ETH deposit
* Make a deposit
* @ param currency С urrency
* @ param amount Deposit amount
* /
async function deposit ( ) {
const deposit = createDeposit ( rbigint ( 31 ) , rbigint ( 31 ) )
console . log ( 'Submitting deposit transaction' )
await tornado . methods . deposit ( toHex ( deposit . commitment ) ) . send ( { value : ETH _AMOUNT , from : senderAccount , gas : 2e6 } )
async function deposit ( { currency , amount } ) {
const deposit = createDeposit ( { nullifier : rbigint ( 31 ) , secret : rbigint ( 31 ) } )
const note = toHex ( deposit . preimage , 62 )
console . log ( 'Your note:' , note )
return note
}
const noteString = ` tornado- ${ currency } - ${ amount } - ${ netId } - ${ note } `
console . log ( ` Your note: ${ noteString } ` )
if ( currency === 'eth' ) {
await printETHBalance ( { address : tornado . _address , name : 'Tornado' } )
await printETHBalance ( { address : senderAccount , name : 'Sender account' } )
const value = isLocalRPC ? ETH _AMOUNT : fromDecimals ( { amount , decimals : 18 } )
console . log ( 'Submitting deposit transaction' )
await tornado . methods . deposit ( toHex ( deposit . commitment ) ) . send ( { value , from : senderAccount , gas : 2e6 } )
await printETHBalance ( { address : tornado . _address , name : 'Tornado' } )
await printETHBalance ( { address : senderAccount , name : 'Sender account' } )
} else { // a token
await printERC20Balance ( { address : tornado . _address , name : 'Tornado' } )
await printERC20Balance ( { address : senderAccount , name : 'Sender account' } )
const decimals = isLocalRPC ? 18 : config . deployments [ ` netId ${ netId } ` ] [ currency ] . decimals
const tokenAmount = isLocalRPC ? TOKEN _AMOUNT : fromDecimals ( { amount , decimals } )
if ( isLocalRPC ) {
console . log ( 'Minting some test tokens to deposit' )
await erc20 . methods . mint ( senderAccount , tokenAmount ) . send ( { from : senderAccount , gas : 2e6 } )
}
/ * *
* Make an ERC20 deposit
* /
async function depositErc20 ( ) {
const deposit = createDeposit ( rbigint ( 31 ) , rbigint ( 31 ) )
const allowance = await erc20 . methods . allowance ( senderAccount , tornado . _address ) . call ( { from : senderAccount } )
console . log ( 'Current allowance is' , fromWei ( allowance ) )
if ( toBN ( allowance ) . lt ( toBN ( tokenAmount ) ) ) {
console . log ( 'Approving tokens for deposit' )
await erc20 . methods . approve ( tornado . _address , tokenAmount ) . send ( { from : senderAccount , gas : 1e6 } )
}
if ( ERC20 _TOKEN === '' ) {
console . log ( 'Minting some test tokens to deposit' )
await erc20 . methods . mint ( senderAccount , TOKEN _AMOUNT ) . send ( { from : senderAccount , gas : 2e6 } )
console . log ( 'Submitting deposit transaction' )
await tornado . methods . deposit ( toHex ( deposit . commitment ) ) . send ( { from : senderAccount , gas : 2e6 } )
await printERC20Balance ( { address : tornado . _address , name : 'Tornado' } )
await printERC20Balance ( { address : senderAccount , name : 'Sender account' } )
}
console . log ( 'Approving tokens for deposit' )
await erc20 . methods . approve ( erc20tornado . _address , TOKEN _AMOUNT ) . send ( { from : senderAccount , gas : 1e6 } )
console . log ( 'Submitting deposit transaction' )
await erc20tornado . methods . deposit ( toHex ( deposit . commitment ) ) . send ( { from : senderAccount , gas : 2e6 } )
const note = toHex ( deposit . preimage , 62 )
console . log ( 'Your note:' , note )
return note
return noteString
}
/ * *
* Generate merkle tree for a deposit .
* Download deposit events from the contract , reconstructs merkle tree , finds our deposit leaf
* Download deposit events from the tornado , reconstructs merkle tree , finds our deposit leaf
* in it and generates merkle proof
* @ param contract Tornado contract address
* @ param deposit Deposit object
* /
async function generateMerkleProof ( contract, deposit) {
async function generateMerkleProof ( deposit) {
// Get all deposit events from smart contract and assemble merkle tree from them
console . log ( 'Getting current state from tornado contract' )
const events = await contract . getPastEvents ( 'Deposit' , { fromBlock : contract . deployedBlock , toBlock : 'latest' } )
const events = await tornado . getPastEvents ( 'Deposit' , { fromBlock : 0 , toBlock : 'latest' } )
const leaves = events
. sort ( ( a , b ) => a . returnValues . leafIndex - b . returnValues . leafIndex ) // Sort events in chronological order
. map ( e => e . returnValues . commitment )
const tree = new merkleTree ( MERKLE _TREE _HEIGHT , leaves )
// Find current commitment in the tree
le t depositEvent = events . find ( e => e . returnValues . commitment === toHex ( deposit . commitment ) )
le t leafIndex = depositEvent ? depositEvent . returnValues . leafIndex : - 1
cons t depositEvent = events . find ( e => e . returnValues . commitment === toHex ( deposit . commitment ) )
cons t leafIndex = depositEvent ? depositEvent . returnValues . leafIndex : - 1
// Validate that our data is correct
const isValidRoot = await contract . methods . isKnownRoot ( toHex ( await tree . root ( ) ) ) . call ( )
const isSpent = await contract . methods . isSpent ( toHex ( deposit . nullifierHash ) ) . call ( )
const isValidRoot = await tornado . methods . isKnownRoot ( toHex ( await tree . root ( ) ) ) . call ( )
const isSpent = await tornado . methods . isSpent ( toHex ( deposit . nullifierHash ) ) . call ( )
assert ( isValidRoot === true , 'Merkle tree is corrupted' )
assert ( isSpent === false , 'The note is already spent' )
assert ( leafIndex >= 0 , 'The deposit is not found in the tree' )
// Compute merkle proof of our commitment
return await tree . path ( leafIndex )
return tree . path ( leafIndex )
}
/ * *
* Generate SNARK proof for withdrawal
* @ param contract Tornado contract address
* @ param note Note string
* @ param deposit Deposit object
* @ param recipient Funds recipient
* @ param relayer Relayer address
* @ param fee Relayer fee
* @ param refund Receive ether for exchanged tokens
* /
async function generateProof ( contract , note , recipient , relayer = 0 , fee = 0 , refund = 0 ) {
// Decode hex string and restore the deposit object
let buf = Buffer . from ( note . slice ( 2 ) , 'hex' )
let deposit = createDeposit ( bigInt . leBuff2int ( buf . slice ( 0 , 31 ) ) , bigInt . leBuff2int ( buf . slice ( 31 , 62 ) ) )
async function generateProof ( { deposit , recipient , relayerAddress = 0 , fee = 0 , refund = 0 } ) {
// Compute merkle proof of our commitment
const { root , path _elements , path _index } = await generateMerkleProof ( contract, deposit)
const { root , path _elements , path _index } = await generateMerkleProof ( deposit )
// Prepare circuit input
const input = {
@ -139,7 +153,7 @@ async function generateProof(contract, note, recipient, relayer = 0, fee = 0, re
root : root ,
nullifierHash : deposit . nullifierHash ,
recipient : bigInt ( recipient ) ,
relayer : bigInt ( relayer ) ,
relayer : bigInt ( relayer Address ) ,
fee : bigInt ( fee ) ,
refund : bigInt ( refund ) ,
@ -170,77 +184,135 @@ async function generateProof(contract, note, recipient, relayer = 0, fee = 0, re
/ * *
* Do an ETH withdrawal
* @ param note Note to withdraw
* @ param note String Note to withdraw
* @ param recipient Recipient address
* /
async function withdraw ( note , recipient ) {
const { proof , args } = await generateProof ( tornado , note , recipient )
async function withdraw ( { deposit , currency , amount , recipient , relayerURL , refund = '0' } ) {
if ( currency === 'eth' && refund !== '0' ) {
throw new Error ( 'The ETH purchase is supposted to be 0 for ETH withdrawals' )
}
refund = toWei ( refund )
if ( relayerURL ) {
const relayerStatus = await axios . get ( relayerURL + '/status' )
const { relayerAddress , netId , gasPrices , ethPrices , relayerServiceFee } = relayerStatus . data
assert ( netId === await web3 . eth . net . getId ( ) || netId === '*' , 'This relay is for different network' )
console . log ( 'Relay address: ' , relayerAddress )
console . log ( 'Submitting withdraw transaction' )
await tornado . methods . withdraw ( proof , ... args ) . send ( { from : senderAccount , gas : 1e6 } )
const decimals = isLocalRPC ? 18 : config . deployments [ ` netId ${ netId } ` ] [ currency ] . decimals
const fee = calculateFee ( { gasPrices , currency , amount , refund , ethPrices , relayerServiceFee , decimals } )
if ( fee . gt ( fromDecimals ( { amount , decimals } ) ) ) {
throw new Error ( 'Too high refund' )
}
const { proof , args } = await generateProof ( { deposit , recipient , relayerAddress , fee , refund } )
console . log ( 'Sending withdraw transaction through relay' )
try {
const relay = await axios . post ( relayerURL + '/relay' , { contract : tornado . _address , proof , args } )
if ( netId === 1 || netId === 42 ) {
console . log ( ` Transaction submitted through the relay. View transaction on etherscan https:// ${ networks [ netId ] } .etherscan.io/tx/ ${ relay . data . txHash } ` )
} else {
console . log ( ` Transaction submitted through the relay. The transaction hash is ${ relay . data . txHash } ` )
}
const receipt = await waitForTxReceipt ( { txHash : relay . data . txHash } )
console . log ( 'Transaction mined in block' , receipt . blockNumber )
} catch ( e ) {
if ( e . response ) {
console . error ( e . response . data . error )
} else {
console . error ( e . message )
}
}
} else { // using private key
const { proof , args } = await generateProof ( { deposit , recipient , refund } )
console . log ( 'Submitting withdraw transaction' )
await tornado . methods . withdraw ( proof , ... args ) . send ( { from : senderAccount , value : refund . toString ( ) , gas : 1e6 } )
. on ( 'transactionHash' , function ( txHash ) {
if ( netId === 1 || netId === 42 ) {
console . log ( ` View transaction on etherscan https:// ${ networks [ netId ] } .etherscan.io/tx/ ${ txHash } ` )
} else {
console . log ( ` The transaction hash is ${ txHash } ` )
}
} ) . on ( 'error' , function ( e ) {
console . error ( 'on transactionHash error' , e . message )
} )
}
console . log ( 'Done' )
}
/ * *
* Do a ERC20 withdrawal
* @ param note Note to withdraw
* @ param recipient Recipient address
* /
async function withdrawErc20 ( note , recipient ) {
const { proof , args } = await generateProof ( erc20tornado , note , recipient )
function fromDecimals ( { amount , decimals } ) {
amount = amount . toString ( )
let ether = amount . toString ( )
const base = new BN ( '10' ) . pow ( new BN ( decimals ) )
const baseLength = base . toString ( 10 ) . length - 1 || 1
console . log ( 'Submitting withdraw transaction' )
await erc20tornado . methods . withdraw ( proof , ... args ) . send ( { from : senderAccount , gas : 1e6 } )
console . log ( 'Done' )
const negative = ether . substring ( 0 , 1 ) === '-'
if ( negative ) {
ether = ether . substring ( 1 )
}
if ( ether === '.' ) {
throw new Error ( '[ethjs-unit] while converting number ' + amount + ' to wei, invalid value' )
}
// Split it into a whole and fractional part
const comps = ether . split ( '.' )
if ( comps . length > 2 ) {
throw new Error (
'[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal points'
)
}
let whole = comps [ 0 ]
let fraction = comps [ 1 ]
if ( ! whole ) {
whole = '0'
}
if ( ! fraction ) {
fraction = '0'
}
if ( fraction . length > baseLength ) {
throw new Error (
'[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal places'
)
}
while ( fraction . length < baseLength ) {
fraction += '0'
}
whole = new BN ( whole )
fraction = new BN ( fraction )
let wei = whole . mul ( base ) . add ( fraction )
if ( negative ) {
wei = wei . mul ( negative )
}
return new BN ( wei . toString ( 10 ) , 10 )
}
/ * *
* Do an ETH withdrawal through relay
* @ param note Note to withdraw
* @ param recipient Recipient address
* @ param relayUrl Relay url address
* /
async function withdrawRelay ( note , recipient , relayUrl ) {
const resp = await axios . get ( relayUrl + '/status' )
const { relayerAddress , netId , gasPrices } = resp . data
assert ( netId === await web3 . eth . net . getId ( ) || netId === '*' , 'This relay is for different network' )
console . log ( 'Relay address: ' , relayerAddress )
const fee = bigInt ( toWei ( gasPrices . fast . toString ( ) , 'gwei' ) ) . mul ( bigInt ( 1e6 ) )
const { proof , args } = await generateProof ( tornado , note , recipient , relayerAddress , fee )
console . log ( 'Sending withdraw transaction through relay' )
const resp2 = await axios . post ( relayUrl + '/relay' , { contract : tornado . _address , proof : { proof , publicSignals : args } } )
console . log ( ` Transaction submitted through relay, tx hash: ${ resp2 . data . txHash } ` )
let receipt = await waitForTxReceipt ( resp2 . data . txHash )
console . log ( 'Transaction mined in block' , receipt . blockNumber )
console . log ( 'Done' )
}
/ * *
* Do a ERC20 withdrawal through relay
* @ param note Note to withdraw
* @ param recipient Recipient address
* @ param relayUrl Relay url address
* /
async function withdrawRelayErc20 ( note , recipient , relayUrl ) {
const resp = await axios . get ( relayUrl + '/status' )
const { relayerAddress , netId , gasPrices , ethPriceInDai } = resp . data
assert ( netId === await web3 . eth . net . getId ( ) || netId === '*' , 'This relay is for different network' )
console . log ( 'Relay address: ' , relayerAddress )
const refund = bigInt ( toWei ( '0.001' ) )
const fee = bigInt ( toWei ( gasPrices . fast . toString ( ) , 'gwei' ) ) . mul ( bigInt ( 1e6 ) ) . add ( refund ) . mul ( bigInt ( fromWei ( ethPriceInDai . toString ( ) ) ) )
const { proof , args } = await generateProof ( erc20tornado , note , recipient , relayerAddress , fee , refund )
console . log ( 'Sending withdraw transaction through relay' )
const resp2 = await axios . post ( relayUrl + '/relay' , { contract : erc20tornado . _address , proof : { proof , publicSignals : args } } )
console . log ( ` Transaction submitted through relay, tx hash: ${ resp2 . data . txHash } ` )
let receipt = await waitForTxReceipt ( resp2 . data . txHash )
console . log ( 'Transaction mined in block' , receipt . blockNumber )
console . log ( 'Done' )
function calculateFee ( { gasPrices , currency , amount , refund , ethPrices , relayerServiceFee , decimals } ) {
const feePercent = toBN ( fromDecimals ( { amount , decimals } ) ) . mul ( toBN ( relayerServiceFee * 10 ) ) . div ( toBN ( '1000' ) )
const expense = toBN ( toWei ( gasPrices . fast . toString ( ) , 'gwei' ) ) . mul ( toBN ( 5e5 ) )
let desiredFee
switch ( currency ) {
case 'eth' : {
desiredFee = expense . add ( feePercent )
break
}
default : {
desiredFee =
expense . add ( toBN ( refund ) )
. mul ( toBN ( 10 * * decimals ) )
. div ( toBN ( ethPrices [ currency ] ) )
desiredFee = desiredFee . add ( feePercent )
break
}
}
return desiredFee
}
/ * *
@ -249,7 +321,7 @@ async function withdrawRelayErc20(note, recipient, relayUrl) {
* @ param attempts
* @ param delay
* /
function waitForTxReceipt ( txHash , attempts = 60 , delay = 1000 ) {
function waitForTxReceipt ( { txHash , attempts = 60 , delay = 1000 } ) {
return new Promise ( ( resolve , reject ) => {
const checkForTx = async ( txHash , retryAttempt = 0 ) => {
const result = await web3 . eth . getTransactionReceipt ( txHash )
@ -267,11 +339,32 @@ function waitForTxReceipt(txHash, attempts = 60, delay = 1000) {
} )
}
/ * *
* Parses Tornado . cash note
* @ param noteString the note
* /
function parseNote ( noteString ) {
const noteRegex = /tornado-(?<currency>\w+)-(?<amount>[\d.]+)-(?<netId>\d+)-0x(?<note>[0-9a-fA-F]{124})/g
const match = noteRegex . exec ( noteString )
if ( ! match ) {
throw new Error ( 'The note has invalid format' )
}
const buf = Buffer . from ( match . groups . note , 'hex' )
const nullifier = bigInt . leBuff2int ( buf . slice ( 0 , 31 ) )
const secret = bigInt . leBuff2int ( buf . slice ( 31 , 62 ) )
const deposit = createDeposit ( { nullifier , secret } )
const netId = Number ( match . groups . netId )
return { currency : match . groups . currency , amount : match . groups . amount , netId , deposit }
}
/ * *
* Init web3 , contracts , and snark
* /
async function init ( ) {
let contractJson , erc20ContractJson , erc20tornadoJson
async function init ( { rpc , noteNetId , currency = 'dai' , amount = '100' } ) {
let contractJson , erc20ContractJson , erc20tornadoJson , tornadoAddress , tokenAddress
// TODO do we need this? should it work in browser really?
if ( inBrowser ) {
// Initialize using injected web3 (Metamask)
// To assemble web version run `npm run browserify`
@ -279,187 +372,128 @@ async function init() {
contractJson = await ( await fetch ( 'build/contracts/ETHTornado.json' ) ) . json ( )
circuit = await ( await fetch ( 'build/circuits/withdraw.json' ) ) . json ( )
proving _key = await ( await fetch ( 'build/circuits/withdraw_proving_key.bin' ) ) . arrayBuffer ( )
MERKLE _TREE _HEIGHT = 16
MERKLE _TREE _HEIGHT = 20
ETH _AMOUNT = 1e18
TOKEN _AMOUNT = 1e19
senderAccount = ( await web3 . eth . getAccounts ( ) ) [ 0 ]
} else {
// Initialize from local node
web3 = new Web3 ( 'http://localhost:8545' , null , { transactionConfirmationBlocks : 1 } )
web3 = new Web3 ( rpc , null , { transactionConfirmationBlocks : 1 } )
contractJson = require ( './build/contracts/ETHTornado.json' )
circuit = require ( './build/circuits/withdraw.json' )
proving _key = fs . readFileSync ( 'build/circuits/withdraw_proving_key.bin' ) . buffer
require ( 'dotenv' ) . config ( )
MERKLE _TREE _HEIGHT = process . env . MERKLE _TREE _HEIGHT
ETH _AMOUNT = process . env . ETH _AMOUNT
TOKEN _AMOUNT = process . env . TOKEN _AMOUNT
ERC20_TOKEN = process . env . ERC20 _TOKEN
PRIVATE_KEY = process . env . PRIVATE _KEY
erc20ContractJson = require ( './build/contracts/ERC20Mock.json' )
erc20tornadoJson = require ( './build/contracts/ERC20Tornado.json' )
const account = web3 . eth . accounts . privateKeyToAccount ( '0x' + PRIVATE _KEY )
web3 . eth . accounts . wallet . add ( '0x' + PRIVATE _KEY )
web3 . eth . defaultAccount = account . address
senderAccount = account . address
}
// groth16 initialises a lot of Promises that will never be resolved, that's why we need to use process.exit to terminate the CLI
groth16 = await buildGroth16 ( )
let netId = await web3 . eth . net . getId ( )
if ( contractJson . networks [ netId ] ) {
const tx = await web3 . eth . getTransaction ( contractJson . networks [ netId ] . transactionHash )
tornado = new web3 . eth . Contract ( contractJson . abi , contractJson . networks [ netId ] . address )
tornado . deployedBlock = tx . blockNumber
netId = await web3 . eth . net . getId ( )
if ( noteNetId && Number ( noteNetId ) !== netId ) {
throw new Error ( 'This note is for a different network. Specify the --rpc option explicitly' )
}
isLocalRPC = netId > 42
const tx3 = await web3 . eth . getTransaction ( erc20tornadoJson . networks [ netId ] . transactionHash )
erc20tornado = new web3 . eth . Contract ( erc20tornadoJson . abi , erc20tornadoJson . networks [ netId ] . address )
erc20tornado . deployedBlock = tx3 . blockNumber
if ( ERC20 _TOKEN === '' ) {
erc20 = new web3 . eth . Contract ( erc20ContractJson . abi , erc20ContractJson . networks [ netId ] . address )
const tx2 = await web3 . eth . getTransaction ( erc20ContractJson . networks [ netId ] . transactionHash )
erc20 . deployedBlock = tx2 . blockNumber
}
senderAccount = ( await web3 . eth . getAccounts ( ) ) [ 0 ]
console . log ( 'Loaded' )
}
// ========== CLI related stuff below ==============
/** Print command line help */
function printHelp ( code = 0 ) {
console . log ( ` Usage:
Submit a deposit from default eth account and return the resulting note
$ . / cli . js deposit
$ . / cli . js depositErc20
Withdraw a note to 'recipient' account
$ . / cli . js withdraw < note > < recipient > [ relayUrl ]
$ . / cli . js withdrawErc20 < note > < recipient > [ relayUrl ]
Check address balance
$ . / cli . js balance < address >
Perform an automated test
$ . / cli . js test
$ . / cli . js testRelay
Example :
$ . / cli . js deposit
...
Your note : 0x1941fa999e2b4bfeec3ce53c2440c3bc991b1b84c9bb650ea19f8331baf621001e696487e2a2ee54541fa12f49498d71e24d00b1731a8ccd4f5f5126f3d9f400
$ . / cli . js withdraw 0x1941fa999e2b4bfeec3ce53c2440c3bc991b1b84c9bb650ea19f8331baf621001e696487e2a2ee54541fa12f49498d71e24d00b1731a8ccd4f5f5126f3d9f400 0xee6249BA80596A4890D1BD84dbf5E4322eA4E7f0
` )
process . exit ( code )
}
/** Process command line args and run */
async function runConsole ( args ) {
if ( args . length === 0 ) {
printHelp ( )
if ( isLocalRPC ) {
tornadoAddress = currency === 'eth' ? contractJson . networks [ netId ] . address : erc20tornadoJson . networks [ netId ] . address
tokenAddress = currency !== 'eth' ? erc20ContractJson . networks [ netId ] . address : null
senderAccount = ( await web3 . eth . getAccounts ( ) ) [ 0 ]
} else {
switch ( args [ 0 ] ) {
case 'deposit' :
if ( args . length === 1 ) {
await init ( )
await printBalance ( tornado . _address , 'Tornado' )
await printBalance ( senderAccount , 'Sender account' )
await deposit ( )
await printBalance ( tornado . _address , 'Tornado' )
await printBalance ( senderAccount , 'Sender account' )
} else {
printHelp ( 1 )
try {
tornadoAddress = config . deployments [ ` netId ${ netId } ` ] [ currency ] . instanceAddress [ amount ]
if ( ! tornadoAddress ) {
throw new Error ( )
}
break
case 'depositErc20' :
if ( args . length === 1 ) {
await init ( )
await printBalance ( erc20tornado . _address , 'Tornado' )
await printBalance ( senderAccount , 'Sender account' )
await depositErc20 ( )
await printBalance ( erc20tornado . _address , 'Tornado' )
await printBalance ( senderAccount , 'Sender account' )
} else {
printHelp ( 1 )
}
break
case 'balance' :
if ( args . length === 2 && /^0x[0-9a-fA-F]{40}$/ . test ( args [ 1 ] ) ) {
await init ( )
await printBalance ( args [ 1 ] )
} else {
printHelp ( 1 )
}
break
case 'withdraw' :
if ( args . length >= 3 && args . length <= 4 && /^0x[0-9a-fA-F]{124}$/ . test ( args [ 1 ] ) && /^0x[0-9a-fA-F]{40}$/ . test ( args [ 2 ] ) ) {
await init ( )
await printBalance ( tornado . _address , 'Tornado' )
await printBalance ( args [ 2 ] , 'Recipient account' )
if ( args [ 3 ] ) {
await withdrawRelay ( args [ 1 ] , args [ 2 ] , args [ 3 ] )
} else {
await withdraw ( args [ 1 ] , args [ 2 ] )
tokenAddress = config . deployments [ ` netId ${ netId } ` ] [ currency ] . tokenAddress
} catch ( e ) {
console . error ( 'There is no such tornado instance, check the currency and amount you provide' )
process . exit ( 1 )
}
}
tornado = new web3 . eth . Contract ( contractJson . abi , tornadoAddress )
erc20 = currency !== 'eth' ? new web3 . eth . Contract ( erc20ContractJson . abi , tokenAddress ) : { }
}
async function main ( ) {
if ( inBrowser ) {
const instance = { currency : 'eth' , amount : '0.1' }
await init ( instance )
window . deposit = async ( ) => {
await deposit ( instance )
}
window . withdraw = async ( ) => {
const noteString = prompt ( 'Enter the note to withdraw' )
const recipient = ( await web3 . eth . getAccounts ( ) ) [ 0 ]
const { currency , amount , netId , deposit } = parseNote ( noteString )
await init ( { noteNetId : netId , currency , amount } )
await withdraw ( { deposit , currency , amount , recipient } )
}
} else {
program
. option ( '-r, --rpc <URL>' , 'The RPC, CLI should interact with' , 'http://localhost:8545' )
. option ( '-R, --relayer <URL>' , 'Withdraw via relayer' )
program
. command ( 'deposit <currency> <amount>' )
. description ( 'Submit a deposit of specified currency and amount from default eth account and return the resulting note. The currency is one of (ETH|DAI|cDAI|USDC|cUSDC|USDT). The amount depends on currency, see config.js file or visit https://tornado.cash.' )
. action ( async ( currency , amount ) => {
currency = currency . toLowerCase ( )
await init ( { rpc : program . rpc , currency , amount } )
await deposit ( { currency , amount } )
} )
program
. command ( 'withdraw <note> <recipient> [ETH_purchase]' )
. description ( 'Withdraw a note to a recipient account using relayer or specified private key. You can exchange some of your deposit`s tokens to ETH during the withdrawal by specifing ETH_purchase (e.g. 0.01) to pay for gas in future transactions. Also see the --relayer option.' )
. action ( async ( noteString , recipient , refund ) => {
const { currency , amount , netId , deposit } = parseNote ( noteString )
await init ( { rpc : program . rpc , noteNetId : netId , currency , amount } )
await withdraw ( { deposit , currency , amount , recipient , refund , relayerURL : program . relayer } )
} )
program
. command ( 'balance <address> [token_address]' )
. description ( 'Check ETH and ERC20 balance' )
. action ( async ( address , tokenAddress ) => {
await init ( { rpc : program . rpc } )
await printETHBalance ( { address , name : '' } )
if ( tokenAddress ) {
await printERC20Balance ( { address , name : '' , tokenAddress } )
}
await printBalance ( tornado . _address , 'Tornado' )
await printBalance ( args [ 2 ] , 'Recipient account' )
} else {
printHelp ( 1 )
}
break
case 'withdrawErc20' :
if ( args . length >= 3 && args . length <= 4 && /^0x[0-9a-fA-F]{124}$/ . test ( args [ 1 ] ) && /^0x[0-9a-fA-F]{40}$/ . test ( args [ 2 ] ) ) {
await init ( )
await printBalance ( erc20tornado . _address , 'Tornado' )
await printBalance ( args [ 2 ] , 'Recipient account' )
if ( args [ 3 ] ) {
await withdrawRelayErc20 ( args [ 1 ] , args [ 2 ] , args [ 3 ] )
} else {
await withdrawErc20 ( args [ 1 ] , args [ 2 ] )
}
await printBalance ( erc20tornado . _address , 'Tornado' )
await printBalance ( args [ 2 ] , 'Recipient account' )
} else {
printHelp ( 1 )
}
break
case 'test' :
if ( args . length === 1 ) {
await init ( )
const note1 = await deposit ( )
await withdraw ( note1 , senderAccount )
} )
program
. command ( 'test' )
. description ( 'Perform an automated test. It deposits and withdraws one ETH and one ERC20 note. Uses ganache.' )
. action ( async ( ) => {
console . log ( 'Start performing ETH deposit-withdraw test' )
let currency = 'eth'
let amount = '0.1'
await init ( { rpc : program . rpc , currency , amount } )
let noteString = await deposit ( { currency , amount } )
let parsedNote = parseNote ( noteString )
await withdraw ( { deposit : parsedNote . deposit , currency , amount , recipient : senderAccount , relayerURL : program . relayer } )
const note2 = await depositErc20 ( )
await withdrawErc20 ( note2 , senderAccount )
} else {
printHelp ( 1 )
}
break
case 'testRelay' :
if ( args . length === 1 ) {
await init ( )
const note1 = await deposit ( )
await withdrawRelay ( note1 , senderAccount , 'http://localhost:8000' )
const note2 = await depositErc20 ( )
await withdrawRelayErc20 ( note2 , senderAccount , 'http://localhost:8000' )
} else {
printHelp ( 1 )
}
break
default :
printHelp ( 1 )
console . log ( '\nStart performing DAI deposit-withdraw test' )
currency = 'dai'
amount = '100'
await init ( { rpc : program . rpc , currency , amount } )
noteString = await deposit ( { currency , amount } )
; ( parsedNote = parseNote ( noteString ) )
await withdraw ( { deposit : parsedNote . deposit , currency , amount , recipient : senderAccount , refund : '0.02' , relayerURL : program . relayer } )
} )
try {
await program . parseAsync ( process . argv )
process . exit ( 0 )
} catch ( e ) {
console . log ( 'Error:' , e )
process . exit ( 1 )
}
}
}
if ( inBrowser ) {
window . deposit = deposit
window . depositErc20 = depositErc20
window . withdraw = async ( ) => {
const note = prompt ( 'Enter the note to withdraw' )
const recipient = ( await web3 . eth . getAccounts ( ) ) [ 0 ]
await withdraw ( note , recipient )
}
init ( )
} else {
runConsole ( process . argv . slice ( 2 ) )
. then ( ( ) => process . exit ( 0 ) )
. catch ( err => { console . log ( err ) ; process . exit ( 1 ) } )
}
main ( )