2020-05-22 12:37:21 +03:00
#!/usr/bin/env node
2020-05-21 22:29:33 +03:00
// 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' )
const snarkjs = require ( 'snarkjs' )
const crypto = require ( 'crypto' )
const circomlib = require ( 'circomlib' )
const bigInt = snarkjs . bigInt
const merkleTree = require ( './lib/MerkleTree' )
const Web3 = require ( 'web3' )
2021-12-07 19:57:18 +03:00
const Web3HttpProvider = require ( 'web3-providers-http' ) ;
2020-05-21 22:29:33 +03:00
const buildGroth16 = require ( 'websnark/src/groth16' )
const websnarkUtils = require ( 'websnark/src/utils' )
const { toWei , fromWei , toBN , BN } = require ( 'web3-utils' )
const config = require ( './config' )
const program = require ( 'commander' )
2020-12-24 14:39:20 +03:00
const { GasPriceOracle } = require ( 'gas-price-oracle' )
2021-12-07 19:57:18 +03:00
const SocksProxyAgent = require ( 'socks-proxy-agent' )
2020-05-21 22:29:33 +03:00
2021-12-07 19:57:18 +03:00
let web3 , tornado , tornadoContract , tornadoInstance , circuit , proving _key , groth16 , erc20 , senderAccount , netId , netName , netSymbol
2020-05-21 22:29:33 +03:00
let MERKLE _TREE _HEIGHT , ETH _AMOUNT , TOKEN _AMOUNT , PRIVATE _KEY
/** Whether we are in a browser or node.js */
2021-02-14 21:23:17 +03:00
const inBrowser = typeof window !== 'undefined'
2020-05-21 22:29:33 +03:00
let isLocalRPC = false
/** Generate random number of specified byte length */
2021-02-14 21:23:17 +03:00
const rbigint = ( nbytes ) => snarkjs . bigInt . leBuff2int ( crypto . randomBytes ( nbytes ) )
2020-05-21 22:29:33 +03:00
/** Compute pedersen hash */
2021-02-14 21:23:17 +03:00
const pedersenHash = ( data ) => circomlib . babyJub . unpackPoint ( circomlib . pedersenHash . hash ( data ) ) [ 0 ]
2020-05-21 22:29:33 +03:00
/** BigNumber to hex string of specified length */
function toHex ( number , length = 32 ) {
2020-05-22 12:35:00 +03:00
const str = number instanceof Buffer ? number . toString ( 'hex' ) : bigInt ( number ) . toString ( 16 )
return '0x' + str . padStart ( length * 2 , '0' )
2020-05-21 22:29:33 +03:00
}
/** Display ETH account balance */
2021-12-07 19:57:18 +03:00
async function printETHBalance ( { address , name , symbol } ) {
console . log ( ` ${ name } balance is ` , web3 . utils . fromWei ( await web3 . eth . getBalance ( address ) ) , ` ${ symbol } ` )
2020-05-21 22:29:33 +03:00
}
/** Display ERC20 account balance */
async function printERC20Balance ( { address , name , tokenAddress } ) {
2020-05-22 12:35:00 +03:00
const erc20ContractJson = require ( './build/contracts/ERC20Mock.json' )
erc20 = tokenAddress ? new web3 . eth . Contract ( erc20ContractJson . abi , tokenAddress ) : erc20
2021-12-07 19:57:18 +03:00
balance = await erc20 . methods . balanceOf ( address ) . call ( )
decimals = await erc20 . methods . decimals ( ) . call ( )
console . log ( ` ${ name } ` , ( await erc20 . methods . name ( ) . call ( ) ) , ` Token Balance is ` , toDecimals ( balance , decimals , ( balance . length + decimals ) ) . toString ( ) . replace ( /\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g , "," ) , ( await erc20 . methods . symbol ( ) . call ( ) ) )
}
2021-12-12 09:19:49 +03:00
async function generateTransaction ( to , encodedData , value = 0 ) {
const nonce = await web3 . eth . getTransactionCount ( senderAccount )
2021-12-07 19:57:18 +03:00
const gasPrice = await fetchGasPrice ( )
2021-12-12 09:19:49 +03:00
let gasLimit ;
2021-12-07 19:57:18 +03:00
let tx = { }
2021-12-12 09:19:49 +03:00
async function estimateGas ( ) {
const fetchedGas = await web3 . eth . estimateGas ( {
from : senderAccount ,
to : to ,
value : value ,
nonce : nonce ,
data : encodedData
} )
const bumped = Math . floor ( fetchedGas * 1.3 )
gasLimit = web3 . utils . toHex ( bumped )
}
2022-01-23 15:03:46 +03:00
if ( encodedData ) {
await estimateGas ( ) ;
} else {
gasLimit = web3 . utils . toHex ( 21000 ) ;
}
2021-12-12 09:19:49 +03:00
2021-12-07 19:57:18 +03:00
async function txoptions ( ) {
// Generate EIP-1559 transaction
if ( netId == 1 || netId == 5 ) {
tx = {
2021-12-12 09:19:49 +03:00
to : to ,
value : value ,
nonce : nonce ,
maxFeePerGas : gasPrice ,
maxPriorityFeePerGas : web3 . utils . toHex ( web3 . utils . toWei ( '3' , 'gwei' ) ) ,
gas : gasLimit ,
data : encodedData
2021-12-07 19:57:18 +03:00
}
2022-01-23 10:26:24 +03:00
} else if ( netId == 137 || netId == 43114 ) {
tx = {
to : to ,
value : value ,
nonce : nonce ,
maxFeePerGas : gasPrice ,
maxPriorityFeePerGas : gasPrice ,
gas : gasLimit ,
data : encodedData
}
2021-12-07 19:57:18 +03:00
} else {
tx = {
2021-12-12 09:19:49 +03:00
to : to ,
2021-12-07 19:57:18 +03:00
value : value ,
2021-12-12 09:19:49 +03:00
nonce : nonce ,
2021-12-07 19:57:18 +03:00
gasPrice : gasPrice ,
gas : gasLimit ,
data : encodedData
}
}
}
await txoptions ( ) ;
const signed = await web3 . eth . accounts . signTransaction ( tx , PRIVATE _KEY ) ;
await web3 . eth . sendSignedTransaction ( signed . rawTransaction )
. on ( 'transactionHash' , function ( txHash ) {
console . log ( ` View transaction on block explorer https:// ${ getExplorerLink ( ) } /tx/ ${ txHash } ` )
} )
. on ( 'error' , function ( e ) {
console . error ( 'on transactionHash error' , e . message )
} ) ;
2020-05-21 22:29:33 +03:00
}
/ * *
* Create deposit object from secret and nullifier
* /
function createDeposit ( { nullifier , secret } ) {
2020-05-22 12:35:00 +03:00
const deposit = { nullifier , secret }
deposit . preimage = Buffer . concat ( [ deposit . nullifier . leInt2Buff ( 31 ) , deposit . secret . leInt2Buff ( 31 ) ] )
deposit . commitment = pedersenHash ( deposit . preimage )
deposit . commitmentHex = toHex ( deposit . commitment )
deposit . nullifierHash = pedersenHash ( deposit . nullifier . leInt2Buff ( 31 ) )
deposit . nullifierHex = toHex ( deposit . nullifierHash )
return deposit
2020-05-21 22:29:33 +03:00
}
2021-12-07 19:57:18 +03:00
async function backupNote ( { currency , amount , netId , note , noteString } ) {
try {
await fs . writeFileSync ( ` ./backup-tornado- ${ currency } - ${ amount } - ${ netId } - ${ note . slice ( 0 , 10 ) } .txt ` , noteString , 'utf8' ) ;
2022-01-14 16:16:10 +03:00
console . log ( "Backed up deposit note as" , ` ./backup-tornado- ${ currency } - ${ amount } - ${ netId } - ${ note . slice ( 0 , 10 ) } .txt ` )
2021-12-07 19:57:18 +03:00
} catch ( e ) {
throw new Error ( 'Writing backup note failed:' , e )
}
}
2020-05-21 22:29:33 +03:00
/ * *
* Make a deposit
* @ param currency С urrency
* @ param amount Deposit amount
* /
async function deposit ( { currency , amount } ) {
2021-12-07 19:57:18 +03:00
assert ( senderAccount != null , 'Error! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you deposit' )
2021-02-14 21:23:17 +03:00
const deposit = createDeposit ( {
nullifier : rbigint ( 31 ) ,
secret : rbigint ( 31 )
} )
2020-05-22 12:35:00 +03:00
const note = toHex ( deposit . preimage , 62 )
const noteString = ` tornado- ${ currency } - ${ amount } - ${ netId } - ${ note } `
console . log ( ` Your note: ${ noteString } ` )
2021-12-07 19:57:18 +03:00
await backupNote ( { currency , amount , netId , note , noteString } )
2022-01-23 09:52:59 +03:00
if ( currency === netSymbol . toLowerCase ( ) ) {
2021-12-07 19:57:18 +03:00
await printETHBalance ( { address : tornadoContract . _address , name : 'Tornado contract' , symbol : currency . toUpperCase ( ) } )
await printETHBalance ( { address : senderAccount , name : 'Sender account' , symbol : currency . toUpperCase ( ) } )
2020-05-22 12:35:00 +03:00
const value = isLocalRPC ? ETH _AMOUNT : fromDecimals ( { amount , decimals : 18 } )
console . log ( 'Submitting deposit transaction' )
2021-12-12 09:19:49 +03:00
await generateTransaction ( contractAddress , await tornado . methods . deposit ( tornadoInstance , toHex ( deposit . commitment ) , [ ] ) . encodeABI ( ) , value )
2021-12-07 19:57:18 +03:00
await printETHBalance ( { address : tornadoContract . _address , name : 'Tornado contract' , symbol : currency . toUpperCase ( ) } )
await printETHBalance ( { address : senderAccount , name : 'Sender account' , symbol : currency . toUpperCase ( ) } )
2021-02-14 21:23:17 +03:00
} else {
// a token
2021-12-07 19:57:18 +03:00
await printERC20Balance ( { address : tornadoContract . _address , name : 'Tornado contract' } )
2020-05-22 12:35:00 +03:00
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' )
2021-12-12 09:19:49 +03:00
await generateTransaction ( erc20Address , await erc20 . methods . mint ( senderAccount , tokenAmount ) . encodeABI ( ) )
2020-05-22 12:35:00 +03:00
}
2020-05-21 22:29:33 +03:00
2020-05-22 12:35:00 +03:00
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' )
2021-12-12 09:19:49 +03:00
await generateTransaction ( erc20Address , await erc20 . methods . approve ( tornado . _address , tokenAmount ) . encodeABI ( ) )
2020-05-21 22:29:33 +03:00
}
2020-05-22 12:35:00 +03:00
console . log ( 'Submitting deposit transaction' )
2021-12-12 09:19:49 +03:00
await generateTransaction ( contractAddress , await tornado . methods . deposit ( toHex ( deposit . commitment ) ) . encodeABI ( ) )
2021-12-07 19:57:18 +03:00
await printERC20Balance ( { address : tornadoContract . _address , name : 'Tornado contract' } )
2020-05-22 12:35:00 +03:00
await printERC20Balance ( { address : senderAccount , name : 'Sender account' } )
}
return noteString
2020-05-21 22:29:33 +03:00
}
/ * *
* Generate merkle tree for a deposit .
* Download deposit events from the tornado , reconstructs merkle tree , finds our deposit leaf
* in it and generates merkle proof
* @ param deposit Deposit object
* /
2021-12-07 19:57:18 +03:00
async function generateMerkleProof ( deposit , currency , amount ) {
2020-12-24 14:39:20 +03:00
let leafIndex = - 1
2020-05-22 12:35:00 +03:00
// Get all deposit events from smart contract and assemble merkle tree from them
2021-07-16 13:30:07 +03:00
2021-12-07 19:57:18 +03:00
const cachedEvents = await fetchEvents ( { type : 'deposit' , currency , amount } )
2020-12-24 14:39:20 +03:00
2021-12-07 19:57:18 +03:00
const leaves = cachedEvents
2021-07-16 13:27:08 +03:00
. sort ( ( a , b ) => a . leafIndex - b . leafIndex ) // Sort events in chronological order
2020-12-24 14:39:20 +03:00
. map ( ( e ) => {
2021-07-16 13:27:08 +03:00
const index = toBN ( e . leafIndex ) . toNumber ( )
2020-05-22 12:35:00 +03:00
2021-07-16 13:27:08 +03:00
if ( toBN ( e . commitment ) . eq ( toBN ( deposit . commitmentHex ) ) ) {
2020-12-24 14:39:20 +03:00
leafIndex = index
}
2021-07-16 13:27:08 +03:00
return toBN ( e . commitment ) . toString ( 10 )
2020-12-24 14:39:20 +03:00
} )
const tree = new merkleTree ( MERKLE _TREE _HEIGHT , leaves )
2020-05-22 12:35:00 +03:00
// Validate that our data is correct
const root = await tree . root ( )
2021-07-17 00:54:20 +03:00
const isValidRoot = await tornadoContract . methods . isKnownRoot ( toHex ( root ) ) . call ( )
const isSpent = await tornadoContract . methods . isSpent ( toHex ( deposit . nullifierHash ) ) . call ( )
2020-05-22 12:35:00 +03:00
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 tree . path ( leafIndex )
2020-05-21 22:29:33 +03:00
}
/ * *
* Generate SNARK proof for withdrawal
* @ param deposit Deposit object
* @ param recipient Funds recipient
* @ param relayer Relayer address
* @ param fee Relayer fee
* @ param refund Receive ether for exchanged tokens
* /
2021-12-07 19:57:18 +03:00
async function generateProof ( { deposit , currency , amount , recipient , relayerAddress = 0 , fee = 0 , refund = 0 } ) {
2020-05-22 12:35:00 +03:00
// Compute merkle proof of our commitment
2021-12-07 19:57:18 +03:00
const { root , path _elements , path _index } = await generateMerkleProof ( deposit , currency , amount )
2020-05-22 12:35:00 +03:00
// Prepare circuit input
const input = {
// Public snark inputs
root : root ,
nullifierHash : deposit . nullifierHash ,
recipient : bigInt ( recipient ) ,
relayer : bigInt ( relayerAddress ) ,
fee : bigInt ( fee ) ,
refund : bigInt ( refund ) ,
// Private snark inputs
nullifier : deposit . nullifier ,
secret : deposit . secret ,
pathElements : path _elements ,
2021-02-14 21:23:17 +03:00
pathIndices : path _index
2020-05-22 12:35:00 +03:00
}
console . log ( 'Generating SNARK proof' )
console . time ( 'Proof time' )
const proofData = await websnarkUtils . genWitnessAndProve ( groth16 , input , circuit , proving _key )
const { proof } = websnarkUtils . toSolidityInput ( proofData )
console . timeEnd ( 'Proof time' )
const args = [
toHex ( input . root ) ,
toHex ( input . nullifierHash ) ,
toHex ( input . recipient , 20 ) ,
toHex ( input . relayer , 20 ) ,
toHex ( input . fee ) ,
toHex ( input . refund )
]
return { proof , args }
2020-05-21 22:29:33 +03:00
}
/ * *
* Do an ETH withdrawal
* @ param noteString Note to withdraw
* @ param recipient Recipient address
* /
2021-12-07 19:57:18 +03:00
async function withdraw ( { deposit , currency , amount , recipient , relayerURL , torPort , refund = '0' } ) {
let options = { } ;
2022-01-23 09:52:59 +03:00
if ( currency === netSymbol . toLowerCase ( ) && refund !== '0' ) {
2020-05-22 12:35:00 +03:00
throw new Error ( 'The ETH purchase is supposted to be 0 for ETH withdrawals' )
}
refund = toWei ( refund )
if ( relayerURL ) {
if ( relayerURL . endsWith ( '.eth' ) ) {
throw new Error ( 'ENS name resolving is not supported. Please provide DNS name of the relayer. See instuctions in README.md' )
2020-05-21 22:29:33 +03:00
}
2021-12-07 19:57:18 +03:00
if ( torPort ) {
2021-12-12 09:19:49 +03:00
options = { httpsAgent : new SocksProxyAgent ( 'socks5h://127.0.0.1:' + torPort ) , headers : { 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0' } }
2021-12-07 19:57:18 +03:00
}
const relayerStatus = await axios . get ( relayerURL + '/status' , options )
2020-12-24 14:39:20 +03:00
const { rewardAccount , netId , ethPrices , tornadoServiceFee } = relayerStatus . data
2021-02-14 21:23:17 +03:00
assert ( netId === ( await web3 . eth . net . getId ( ) ) || netId === '*' , 'This relay is for different network' )
2021-12-07 19:57:18 +03:00
console . log ( 'Relay address:' , rewardAccount )
2020-12-24 14:39:20 +03:00
const gasPrice = await fetchGasPrice ( )
2020-05-22 12:35:00 +03:00
const decimals = isLocalRPC ? 18 : config . deployments [ ` netId ${ netId } ` ] [ currency ] . decimals
2021-02-14 21:23:17 +03:00
const fee = calculateFee ( {
currency ,
gasPrice ,
amount ,
refund ,
ethPrices ,
relayerServiceFee : tornadoServiceFee ,
decimals
} )
2020-05-22 12:35:00 +03:00
if ( fee . gt ( fromDecimals ( { amount , decimals } ) ) ) {
throw new Error ( 'Too high refund' )
2020-05-21 22:29:33 +03:00
}
2020-12-24 14:39:20 +03:00
2021-12-07 19:57:18 +03:00
const { proof , args } = await generateProof ( { deposit , currency , amount , recipient , relayerAddress : rewardAccount , fee , refund } )
2020-05-21 22:29:33 +03:00
2020-05-22 12:35:00 +03:00
console . log ( 'Sending withdraw transaction through relay' )
try {
2021-02-14 21:23:17 +03:00
const response = await axios . post ( relayerURL + '/v1/tornadoWithdraw' , {
contract : tornadoInstance ,
proof ,
args
2021-12-07 19:57:18 +03:00
} , options )
2020-05-22 12:35:00 +03:00
2020-12-24 14:39:20 +03:00
const { id } = response . data
2021-12-07 19:57:18 +03:00
const result = await getStatus ( id , relayerURL , options )
2020-12-24 14:39:20 +03:00
console . log ( 'STATUS' , result )
2020-05-22 12:35:00 +03:00
} catch ( e ) {
if ( e . response ) {
console . error ( e . response . data . error )
} else {
console . error ( e . message )
}
2020-05-21 22:29:33 +03:00
}
2021-02-14 21:23:17 +03:00
} else {
// using private key
2021-12-07 19:57:18 +03:00
// check if the address of recepient matches with the account of provided private key from environment to prevent accidental use of deposit address for withdrawal transaction.
const { address } = await web3 . eth . accounts . privateKeyToAccount ( '0x' + PRIVATE _KEY )
2022-01-23 09:52:59 +03:00
assert ( recipient . toLowerCase ( ) == address . toLowerCase ( ) , 'Withdrawal amount recepient mismatches with the account of provided private key from environment file' )
2021-12-07 19:57:18 +03:00
const { proof , args } = await generateProof ( { deposit , currency , amount , recipient , refund } )
2020-05-22 12:35:00 +03:00
console . log ( 'Submitting withdraw transaction' )
2021-12-12 09:19:49 +03:00
await generateTransaction ( contractAddress , await tornado . methods . withdraw ( tornadoInstance , proof , ... args ) . encodeABI ( ) )
2020-05-22 12:35:00 +03:00
}
2021-12-07 19:57:18 +03:00
console . log ( 'Done withdrawal from Tornado Cash' )
2020-05-22 12:35:00 +03:00
}
2020-05-21 22:29:33 +03:00
2022-01-23 15:03:46 +03:00
/ * *
* Do an ETH / ERC20 send
* @ param address Recepient address
* @ param amount Amount to send
* @ param tokenAddress ERC20 token address
* /
async function send ( { address , amount , tokenAddress } ) {
// using private key
assert ( senderAccount != null , 'Error! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you send' )
if ( tokenAddress ) {
const erc20ContractJson = require ( './build/contracts/ERC20Mock.json' )
erc20 = new web3 . eth . Contract ( erc20ContractJson . abi , tokenAddress )
const balance = await erc20 . methods . balanceOf ( senderAccount ) . call ( )
const decimals = await erc20 . methods . decimals ( ) . call ( )
const toSend = amount * Math . pow ( 10 , decimals )
if ( balance < toSend ) {
console . error ( "You have" , toDecimals ( balance , decimals , ( balance . length + decimals ) ) . toString ( ) . replace ( /\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g , "," ) , ( await erc20 . methods . symbol ( ) . call ( ) ) , ", you can't send more than you have" )
process . exit ( 1 ) ;
}
await generateTransaction ( tokenAddress , await erc20 . methods . transfer ( address , toBN ( toSend ) ) . encodeABI ( ) )
console . log ( 'Sent' , amount , ( await erc20 . methods . symbol ( ) . call ( ) ) , 'to' , address ) ;
} else {
const balance = await web3 . eth . getBalance ( senderAccount )
if ( balance == 0 ) {
console . error ( "You have 0 balance, can't send" )
process . exit ( 1 ) ;
}
if ( ! amount ) {
console . log ( 'Amount not defined, sending all available amounts' )
const gasPrice = await fetchGasPrice ( )
const gasLimit = 21000 ;
if ( netId == 1 || netId == 5 ) {
const priorityFee = await gasPrices ( 3 )
amount = ( balance - ( gasLimit * ( parseInt ( gasPrice ) + parseInt ( priorityFee ) ) ) )
} else {
amount = ( balance - ( gasLimit * parseInt ( gasPrice ) ) )
}
}
if ( balance < amount ) {
console . error ( "You have" , web3 . utils . fromWei ( toHex ( balance ) ) , netSymbol , ", you can't send more than you have." )
process . exit ( 1 ) ;
}
await generateTransaction ( address , null , amount )
console . log ( 'Sent' , web3 . utils . fromWei ( toHex ( amount ) ) , netSymbol , 'to' , address ) ;
}
}
2021-12-07 19:57:18 +03:00
function getStatus ( id , relayerURL , options ) {
2020-12-24 14:39:20 +03:00
return new Promise ( ( resolve ) => {
async function getRelayerStatus ( ) {
2021-12-07 19:57:18 +03:00
const responseStatus = await axios . get ( relayerURL + '/v1/jobs/' + id , options )
2020-12-24 14:39:20 +03:00
if ( responseStatus . status === 200 ) {
const { txHash , status , confirmations , failedReason } = responseStatus . data
console . log ( ` Current job status ${ status } , confirmations: ${ confirmations } ` )
if ( status === 'FAILED' ) {
throw new Error ( status + ' failed reason:' + failedReason )
}
if ( status === 'CONFIRMED' ) {
const receipt = await waitForTxReceipt ( { txHash } )
2021-02-14 21:23:17 +03:00
console . log (
2021-12-07 19:57:18 +03:00
` Transaction submitted through the relay. View transaction on block explorer https:// ${ getExplorerLink ( ) } /tx/ ${ txHash } `
2021-02-14 21:23:17 +03:00
)
2020-12-24 14:39:20 +03:00
console . log ( 'Transaction mined in block' , receipt . blockNumber )
resolve ( status )
}
}
setTimeout ( ( ) => {
getRelayerStatus ( id , relayerURL )
} , 3000 )
}
getRelayerStatus ( )
} )
}
2021-12-07 19:57:18 +03:00
function capitalizeFirstLetter ( string ) {
return string . charAt ( 0 ) . toUpperCase ( ) + string . slice ( 1 ) ;
}
2020-05-22 12:35:00 +03:00
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
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 ) {
2021-02-14 21:23:17 +03:00
throw new Error ( '[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal points' )
2020-05-22 12:35:00 +03:00
}
let whole = comps [ 0 ]
let fraction = comps [ 1 ]
if ( ! whole ) {
whole = '0'
}
if ( ! fraction ) {
fraction = '0'
}
if ( fraction . length > baseLength ) {
2021-02-14 21:23:17 +03:00
throw new Error ( '[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal places' )
2020-05-22 12:35:00 +03:00
}
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 )
2020-05-21 22:29:33 +03:00
}
function toDecimals ( value , decimals , fixed ) {
2020-05-22 12:35:00 +03:00
const zero = new BN ( 0 )
const negative1 = new BN ( - 1 )
decimals = decimals || 18
fixed = fixed || 7
value = new BN ( value )
const negative = value . lt ( zero )
const base = new BN ( '10' ) . pow ( new BN ( decimals ) )
const baseLength = base . toString ( 10 ) . length - 1 || 1
if ( negative ) {
value = value . mul ( negative1 )
}
let fraction = value . mod ( base ) . toString ( 10 )
while ( fraction . length < baseLength ) {
fraction = ` 0 ${ fraction } `
}
fraction = fraction . match ( /^([0-9]*[1-9]|0)(0*)/ ) [ 1 ]
const whole = value . div ( base ) . toString ( 10 )
value = ` ${ whole } ${ fraction === '0' ? '' : ` . ${ fraction } ` } `
if ( negative ) {
value = ` - ${ value } `
}
if ( fixed ) {
value = value . slice ( 0 , fixed )
}
return value
2020-05-21 22:29:33 +03:00
}
2021-12-07 19:57:18 +03:00
// List fetched from https://github.com/ethereum-lists/chains/blob/master/_data/chains
function getExplorerLink ( ) {
switch ( netId ) {
case 56 :
return 'bscscan.com'
case 100 :
return 'blockscout.com/poa/xdai'
case 137 :
return 'polygonscan.com'
case 42161 :
return 'arbiscan.io'
case 43114 :
return 'snowtrace.io'
case 5 :
return 'goerli.etherscan.io'
case 42 :
return 'kovan.etherscan.io'
2022-01-23 12:42:08 +03:00
case 10 :
return 'optimistic.etherscan.io'
2021-12-07 19:57:18 +03:00
default :
return 'etherscan.io'
}
}
// List fetched from https://github.com/trustwallet/assets/tree/master/blockchains
2020-05-21 22:29:33 +03:00
function getCurrentNetworkName ( ) {
2020-05-22 12:35:00 +03:00
switch ( netId ) {
2021-02-14 21:23:17 +03:00
case 1 :
2021-12-07 19:57:18 +03:00
return 'Ethereum'
case 56 :
return 'BinanceSmartChain'
case 100 :
return 'xDai'
case 137 :
return 'Polygon'
case 42161 :
return 'Arbitrum'
case 43114 :
return 'Avalanche'
2021-02-14 21:23:17 +03:00
case 5 :
2021-12-07 19:57:18 +03:00
return 'Goerli'
2021-02-14 21:23:17 +03:00
case 42 :
2021-12-07 19:57:18 +03:00
return 'Kovan'
2022-01-23 12:42:08 +03:00
case 137 :
return 'Optimism'
2021-12-07 19:57:18 +03:00
default :
return 'localRPC'
2020-05-22 12:35:00 +03:00
}
2020-05-21 22:29:33 +03:00
}
2021-12-07 19:57:18 +03:00
function getCurrentNetworkSymbol ( ) {
switch ( netId ) {
case 56 :
return 'BNB'
case 100 :
2021-12-12 09:19:49 +03:00
return 'xDAI'
2021-12-07 19:57:18 +03:00
case 137 :
return 'MATIC'
case 43114 :
return 'AVAX'
default :
return 'ETH'
}
}
function gasPricesETH ( value = 80 ) {
2020-12-24 14:39:20 +03:00
const tenPercent = ( Number ( value ) * 5 ) / 100
const max = Math . max ( tenPercent , 3 )
const bumped = Math . floor ( Number ( value ) + max )
return toHex ( toWei ( bumped . toString ( ) , 'gwei' ) )
}
2021-12-07 19:57:18 +03:00
function gasPrices ( value = 5 ) {
return toHex ( toWei ( value . toString ( ) , 'gwei' ) )
}
2020-12-24 14:39:20 +03:00
async function fetchGasPrice ( ) {
try {
2021-12-07 19:57:18 +03:00
const options = {
chainId : netId
}
// Bump fees for Ethereum network
if ( netId == 1 ) {
const oracle = new GasPriceOracle ( options )
const gas = await oracle . gasPrices ( )
return gasPricesETH ( gas . instant )
2021-12-12 09:19:49 +03:00
} else if ( netId == 5 || isLocalRPC ) {
2021-12-07 19:57:18 +03:00
return gasPrices ( 1 )
} else {
const oracle = new GasPriceOracle ( options )
const gas = await oracle . gasPrices ( )
return gasPrices ( gas . instant )
}
2020-12-24 14:39:20 +03:00
} catch ( err ) {
throw new Error ( ` Method fetchGasPrice has error ${ err . message } ` )
}
}
function calculateFee ( { currency , gasPrice , amount , refund , ethPrices , relayerServiceFee , decimals } ) {
2021-02-14 21:23:17 +03:00
const decimalsPoint =
Math . floor ( relayerServiceFee ) === Number ( relayerServiceFee ) ? 0 : relayerServiceFee . toString ( ) . split ( '.' ) [ 1 ] . length
2020-05-22 12:35:00 +03:00
const roundDecimal = 10 * * decimalsPoint
2020-05-22 12:37:21 +03:00
const total = toBN ( fromDecimals ( { amount , decimals } ) )
const feePercent = total . mul ( toBN ( relayerServiceFee * roundDecimal ) ) . div ( toBN ( roundDecimal * 100 ) )
2020-12-24 14:39:20 +03:00
const expense = toBN ( gasPrice ) . mul ( toBN ( 5e5 ) )
2020-05-22 12:37:21 +03:00
let desiredFee
2020-05-22 12:35:00 +03:00
switch ( currency ) {
2022-01-23 09:52:59 +03:00
case netSymbol . toLowerCase ( ) : {
2021-12-07 19:57:18 +03:00
desiredFee = expense . add ( feePercent )
break
}
2021-02-14 21:23:17 +03:00
default : {
desiredFee = expense
. add ( toBN ( refund ) )
. mul ( toBN ( 10 * * decimals ) )
. div ( toBN ( ethPrices [ currency ] ) )
desiredFee = desiredFee . add ( feePercent )
break
}
2020-05-22 12:35:00 +03:00
}
2020-05-22 12:37:21 +03:00
return desiredFee
2020-05-21 22:29:33 +03:00
}
/ * *
* Waits for transaction to be mined
* @ param txHash Hash of transaction
* @ param attempts
* @ param delay
* /
function waitForTxReceipt ( { txHash , attempts = 60 , delay = 1000 } ) {
2020-05-22 12:35:00 +03:00
return new Promise ( ( resolve , reject ) => {
const checkForTx = async ( txHash , retryAttempt = 0 ) => {
const result = await web3 . eth . getTransactionReceipt ( txHash )
if ( ! result || ! result . blockNumber ) {
if ( retryAttempt <= attempts ) {
setTimeout ( ( ) => checkForTx ( txHash , retryAttempt + 1 ) , delay )
} else {
reject ( new Error ( 'tx was not mined' ) )
2020-05-21 22:29:33 +03:00
}
2020-05-22 12:35:00 +03:00
} else {
resolve ( result )
}
}
checkForTx ( txHash )
} )
2020-05-21 22:29:33 +03:00
}
2022-01-22 07:49:45 +03:00
function initJson ( file ) {
return new Promise ( ( resolve , reject ) => {
fs . readFile ( file , 'utf8' , ( error , data ) => {
if ( error ) {
reject ( error ) ;
}
try {
resolve ( JSON . parse ( data ) ) ;
} catch ( error ) {
resolve ( [ ] ) ;
}
} ) ;
} ) ;
} ;
2021-12-12 09:19:49 +03:00
function loadCachedEvents ( { type , currency , amount } ) {
2021-07-16 13:27:08 +03:00
try {
2021-12-07 19:57:18 +03:00
const module = require ( ` ./cache/ ${ netName . toLowerCase ( ) } / ${ type } s_ ${ currency } _ ${ amount } .json ` )
2021-07-16 13:27:08 +03:00
if ( module ) {
const events = module
return {
events ,
lastBlock : events [ events . length - 1 ] . blockNumber
}
}
} catch ( err ) {
2021-12-07 19:57:18 +03:00
console . log ( "Error fetching cached files, syncing from block" , deployedBlockNumber )
return {
events : [ ] ,
lastBlock : deployedBlockNumber ,
}
2021-07-16 13:27:08 +03:00
}
}
2021-12-12 09:19:49 +03:00
async function fetchEvents ( { type , currency , amount } ) {
2021-12-07 19:57:18 +03:00
if ( type === "withdraw" ) {
type = "withdrawal"
}
const cachedEvents = loadCachedEvents ( { type , currency , amount } )
const startBlock = cachedEvents . lastBlock + 1
2022-01-22 07:49:45 +03:00
console . log ( "Loaded cached" , amount , currency . toUpperCase ( ) , type , "events for" , startBlock , "block" )
2021-12-07 19:57:18 +03:00
console . log ( "Fetching" , amount , currency . toUpperCase ( ) , type , "events for" , netName , "network" )
2022-01-22 07:49:45 +03:00
async function syncEvents ( ) {
try {
let targetBlock = await web3 . eth . getBlockNumber ( ) ;
let chunks = 1000 ;
for ( let i = startBlock ; i < targetBlock ; i += chunks ) {
let fetchedEvents = [ ] ;
async function fetchLatestEvents ( i ) {
await tornadoContract . getPastEvents ( capitalizeFirstLetter ( type ) , {
fromBlock : i ,
toBlock : i + chunks - 1 ,
2022-01-23 09:52:59 +03:00
} ) . then ( r => { fetchedEvents = fetchedEvents . concat ( r ) ; console . log ( "Fetched" , amount , currency . toUpperCase ( ) , type , "events to block:" , i ) } , err => { console . error ( i + " failed fetching" , type , "events from node" , err ) ; process . exit ( 1 ) ; } ) . catch ( console . log ) ;
2022-01-22 07:49:45 +03:00
}
async function mapDepositEvents ( ) {
fetchedEvents = fetchedEvents . map ( ( { blockNumber , transactionHash , returnValues } ) => {
const { commitment , leafIndex , timestamp } = returnValues
return {
blockNumber ,
transactionHash ,
commitment ,
leafIndex : Number ( leafIndex ) ,
timestamp
}
} )
}
async function mapWithdrawEvents ( ) {
fetchedEvents = fetchedEvents . map ( ( { blockNumber , transactionHash , returnValues } ) => {
const { nullifierHash , to , fee } = returnValues
return {
blockNumber ,
transactionHash ,
nullifierHash ,
to ,
fee
}
} )
}
async function mapLatestEvents ( ) {
if ( type === "deposit" ) {
await mapDepositEvents ( ) ;
} else {
await mapWithdrawEvents ( ) ;
}
}
async function updateCache ( ) {
try {
const fileName = ` ./cache/ ${ netName . toLowerCase ( ) } / ${ type } s_ ${ currency } _ ${ amount } .json `
const localEvents = await initJson ( fileName ) ;
const events = localEvents . concat ( fetchedEvents ) ;
await fs . writeFileSync ( fileName , JSON . stringify ( events , null , 2 ) , 'utf8' )
} catch ( error ) {
throw new Error ( 'Writing cache file failed:' , error )
}
}
await fetchLatestEvents ( i ) ;
await mapLatestEvents ( ) ;
await updateCache ( ) ;
2021-12-07 19:57:18 +03:00
}
2022-01-22 07:49:45 +03:00
} catch ( error ) {
throw new Error ( "Error while updating cache" )
process . exit ( 1 )
2021-12-07 19:57:18 +03:00
}
}
2022-01-22 07:49:45 +03:00
await syncEvents ( ) ;
async function loadUpdatedEvents ( ) {
const fileName = ` ./cache/ ${ netName . toLowerCase ( ) } / ${ type } s_ ${ currency } _ ${ amount } .json `
const updatedEvents = await initJson ( fileName ) ;
const updatedBlock = updatedEvents [ updatedEvents . length - 1 ] . blockNumber
console . log ( "Cache updated for Tornado" , type , amount , currency , "instance to block" , updatedBlock , "successfully" )
console . log ( 'Total events:' , updatedEvents . length )
return updatedEvents ;
2021-12-07 19:57:18 +03:00
}
2022-01-22 07:49:45 +03:00
const events = await loadUpdatedEvents ( ) ;
2021-12-07 19:57:18 +03:00
return events
}
2020-05-21 22:29:33 +03:00
/ * *
* Parses Tornado . cash note
* @ param noteString the note
* /
function parseNote ( noteString ) {
2020-05-22 12:35:00 +03:00
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 )
2021-02-14 21:23:17 +03:00
return {
currency : match . groups . currency ,
amount : match . groups . amount ,
netId ,
deposit
}
2020-05-21 22:29:33 +03:00
}
2021-12-07 19:57:18 +03:00
async function loadDepositData ( { amount , currency , deposit } ) {
2020-05-22 12:35:00 +03:00
try {
2021-12-07 19:57:18 +03:00
const cachedEvents = await fetchEvents ( { type : 'deposit' , currency , amount } )
const eventWhenHappened = await cachedEvents . filter ( function ( event ) {
return event . commitment === deposit . commitmentHex ;
} ) [ 0 ]
2020-05-22 12:35:00 +03:00
if ( eventWhenHappened . length === 0 ) {
throw new Error ( 'There is no related deposit, the note is invalid' )
}
2020-05-21 22:29:33 +03:00
2021-12-07 19:57:18 +03:00
const timestamp = eventWhenHappened . timestamp
const txHash = eventWhenHappened . transactionHash
2021-07-17 00:54:20 +03:00
const isSpent = await tornadoContract . methods . isSpent ( deposit . nullifierHex ) . call ( )
2020-05-22 12:35:00 +03:00
const receipt = await web3 . eth . getTransactionReceipt ( txHash )
2020-05-21 22:29:33 +03:00
2021-02-14 21:23:17 +03:00
return {
timestamp ,
txHash ,
isSpent ,
from : receipt . from ,
commitment : deposit . commitmentHex
}
2020-05-22 12:35:00 +03:00
} catch ( e ) {
console . error ( 'loadDepositData' , e )
}
return { }
2020-05-21 22:29:33 +03:00
}
async function loadWithdrawalData ( { amount , currency , deposit } ) {
2020-05-22 12:35:00 +03:00
try {
2021-12-07 19:57:18 +03:00
const cachedEvents = await fetchEvents ( { type : 'withdrawal' , currency , amount } )
2021-07-16 13:27:08 +03:00
2021-12-07 19:57:18 +03:00
const withdrawEvent = cachedEvents . filter ( ( event ) => {
2021-07-16 13:27:08 +03:00
return event . nullifierHash === deposit . nullifierHex
2020-05-22 12:35:00 +03:00
} ) [ 0 ]
2021-07-16 13:27:08 +03:00
const fee = withdrawEvent . fee
2020-05-22 12:35:00 +03:00
const decimals = config . deployments [ ` netId ${ netId } ` ] [ currency ] . decimals
2021-02-14 21:23:17 +03:00
const withdrawalAmount = toBN ( fromDecimals ( { amount , decimals } ) ) . sub ( toBN ( fee ) )
2021-07-29 18:11:53 +03:00
const { timestamp } = await web3 . eth . getBlock ( withdrawEvent . blockNumber )
2020-05-22 12:35:00 +03:00
return {
amount : toDecimals ( withdrawalAmount , decimals , 9 ) ,
txHash : withdrawEvent . transactionHash ,
2021-07-16 13:27:08 +03:00
to : withdrawEvent . to ,
2020-05-22 12:35:00 +03:00
timestamp ,
nullifier : deposit . nullifierHex ,
fee : toDecimals ( fee , decimals , 9 )
2020-05-21 22:29:33 +03:00
}
2020-05-22 12:35:00 +03:00
} catch ( e ) {
console . error ( 'loadWithdrawalData' , e )
}
2020-05-21 22:29:33 +03:00
}
/ * *
* Init web3 , contracts , and snark
* /
2021-12-07 19:57:18 +03:00
async function init ( { rpc , noteNetId , currency = 'dai' , amount = '100' , torPort , balanceCheck } ) {
2021-07-17 00:54:20 +03:00
let contractJson , instanceJson , erc20ContractJson , erc20tornadoJson , tornadoAddress , tokenAddress
2020-05-22 12:35:00 +03:00
// 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`
2021-02-14 21:23:17 +03:00
web3 = new Web3 ( window . web3 . currentProvider , null , {
transactionConfirmationBlocks : 1
} )
2020-12-24 14:39:20 +03:00
contractJson = await ( await fetch ( 'build/contracts/TornadoProxy.abi.json' ) ) . json ( )
2021-07-17 00:54:20 +03:00
instanceJson = await ( await fetch ( 'build/contracts/Instance.abi.json' ) ) . json ( )
2020-12-24 14:39:20 +03:00
circuit = await ( await fetch ( 'build/circuits/tornado.json' ) ) . json ( )
proving _key = await ( await fetch ( 'build/circuits/tornadoProvingKey.bin' ) ) . arrayBuffer ( )
2020-05-22 12:35:00 +03:00
MERKLE _TREE _HEIGHT = 20
ETH _AMOUNT = 1e18
TOKEN _AMOUNT = 1e19
senderAccount = ( await web3 . eth . getAccounts ( ) ) [ 0 ]
} else {
2022-01-24 14:39:40 +03:00
let ipOptions = { } ;
2021-12-07 19:57:18 +03:00
if ( torPort ) {
console . log ( "Using tor network" )
2021-12-12 09:19:49 +03:00
web3Options = { agent : { https : new SocksProxyAgent ( 'socks5h://127.0.0.1:' + torPort ) } , timeout : 60000 }
2021-12-07 19:57:18 +03:00
// Use forked web3-providers-http from local file to modify user-agent header value which improves privacy.
web3 = new Web3 ( new Web3HttpProvider ( rpc , web3Options ) , null , { transactionConfirmationBlocks : 1 } )
2022-01-24 14:39:40 +03:00
ipOptions = { httpsAgent : new SocksProxyAgent ( 'socks5h://127.0.0.1:' + torPort ) , headers : { 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0' } }
2021-12-07 19:57:18 +03:00
} else if ( rpc . includes ( "ipc" ) ) {
console . log ( "Using ipc connection" )
web3 = new Web3 ( new Web3 . providers . IpcProvider ( rpc , net ) , null , { transactionConfirmationBlocks : 1 } )
} else if ( rpc . includes ( "ws" ) || rpc . includes ( "wss" ) ) {
console . log ( "Using websocket connection (Note: Tor is not supported for Websocket providers)" )
web3Options = { clientConfig : { keepalive : true , keepaliveInterval : - 1 } , reconnect : { auto : true , delay : 1000 , maxAttempts : 10 , onTimeout : false } }
web3 = new Web3 ( new Web3 . providers . WebsocketProvider ( rpc , web3Options ) , net , { transactionConfirmationBlocks : 1 } )
} else {
console . log ( "Connecting to remote node" )
web3 = new Web3 ( rpc , null , { transactionConfirmationBlocks : 1 } )
}
2022-01-24 14:39:40 +03:00
const fetchRemoteIP = await axios . get ( 'https://ip.tornado.cash' , ipOptions )
const { country , ip } = fetchRemoteIP . data
console . log ( 'Your remote IP address is' , ip , 'from' , country + '.' ) ;
2020-12-24 14:39:20 +03:00
contractJson = require ( './build/contracts/TornadoProxy.abi.json' )
2021-07-17 00:54:20 +03:00
instanceJson = require ( './build/contracts/Instance.abi.json' )
2020-12-24 14:39:20 +03:00
circuit = require ( './build/circuits/tornado.json' )
proving _key = fs . readFileSync ( 'build/circuits/tornadoProvingKey.bin' ) . buffer
2020-05-22 12:35:00 +03:00
MERKLE _TREE _HEIGHT = process . env . MERKLE _TREE _HEIGHT || 20
ETH _AMOUNT = process . env . ETH _AMOUNT
TOKEN _AMOUNT = process . env . TOKEN _AMOUNT
2022-01-23 09:52:59 +03:00
const privKey = process . env . PRIVATE _KEY
2022-01-24 14:39:40 +03:00
if ( privKey ) {
if ( privKey . includes ( "0x" ) ) {
PRIVATE _KEY = process . env . PRIVATE _KEY . substring ( 2 )
} else {
PRIVATE _KEY = process . env . PRIVATE _KEY
}
2022-01-23 09:52:59 +03:00
}
2020-05-22 12:35:00 +03:00
if ( PRIVATE _KEY ) {
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
2020-05-21 22:29:33 +03:00
}
2020-05-22 12:35:00 +03:00
erc20ContractJson = require ( './build/contracts/ERC20Mock.json' )
erc20tornadoJson = require ( './build/contracts/ERC20Tornado.json' )
}
// 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 ( )
netId = await web3 . eth . net . getId ( )
2021-12-07 19:57:18 +03:00
netName = getCurrentNetworkName ( )
2020-05-22 12:35:00 +03:00
if ( noteNetId && Number ( noteNetId ) !== netId ) {
throw new Error ( 'This note is for a different network. Specify the --rpc option explicitly' )
}
2021-12-12 09:19:49 +03:00
if ( netName === "localRPC" ) {
2021-12-07 19:57:18 +03:00
isLocalRPC = true ;
}
2020-05-22 12:35:00 +03:00
if ( isLocalRPC ) {
tornadoAddress = currency === 'eth' ? contractJson . networks [ netId ] . address : erc20tornadoJson . networks [ netId ] . address
tokenAddress = currency !== 'eth' ? erc20ContractJson . networks [ netId ] . address : null
2021-12-07 19:57:18 +03:00
netSymbol = getCurrentNetworkSymbol ( )
deployedBlockNumber = 0
2020-05-22 12:35:00 +03:00
senderAccount = ( await web3 . eth . getAccounts ( ) ) [ 0 ]
} else {
try {
2021-12-07 19:57:18 +03:00
if ( balanceCheck ) {
netSymbol = getCurrentNetworkSymbol ( )
currency = netSymbol . toLowerCase ( )
amount = Object . keys ( config . deployments [ ` netId ${ netId } ` ] [ currency ] . instanceAddress ) [ 0 ]
}
2020-12-24 14:39:20 +03:00
tornadoAddress = config . deployments [ ` netId ${ netId } ` ] . proxy
2021-07-17 00:54:20 +03:00
tornadoInstance = config . deployments [ ` netId ${ netId } ` ] [ currency ] . instanceAddress [ amount ]
2021-12-07 19:57:18 +03:00
deployedBlockNumber = config . deployments [ ` netId ${ netId } ` ] [ currency ] . deployedBlockNumber [ amount ]
2020-12-24 14:39:20 +03:00
2020-05-22 12:35:00 +03:00
if ( ! tornadoAddress ) {
throw new Error ( )
}
tokenAddress = config . deployments [ ` netId ${ netId } ` ] [ currency ] . tokenAddress
} catch ( e ) {
2021-12-07 19:57:18 +03:00
console . error ( 'There is no such tornado instance, check the currency and amount you provide' , e )
2020-05-22 12:35:00 +03:00
process . exit ( 1 )
2020-05-21 22:29:33 +03:00
}
2020-05-22 12:35:00 +03:00
}
2022-01-23 09:52:59 +03:00
netSymbol = getCurrentNetworkSymbol ( )
2020-12-24 14:39:20 +03:00
tornado = new web3 . eth . Contract ( contractJson , tornadoAddress )
2021-07-17 00:54:20 +03:00
tornadoContract = new web3 . eth . Contract ( instanceJson , tornadoInstance )
2021-12-07 19:57:18 +03:00
contractAddress = tornadoAddress
2020-05-22 12:35:00 +03:00
erc20 = currency !== 'eth' ? new web3 . eth . Contract ( erc20ContractJson . abi , tokenAddress ) : { }
2021-12-07 19:57:18 +03:00
erc20Address = tokenAddress
2020-05-21 22:29:33 +03:00
}
async function main ( ) {
2020-05-22 12:35:00 +03:00
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 ]
2020-05-21 22:29:33 +03:00
2020-05-22 12:35:00 +03:00
const { currency , amount , netId , deposit } = parseNote ( noteString )
await init ( { noteNetId : netId , currency , amount } )
await withdraw ( { deposit , currency , amount , recipient } )
}
} else {
program
2021-12-07 19:57:18 +03:00
. option ( '-r, --rpc <URL>' , 'The RPC that CLI should interact with' , 'http://localhost:8545' )
2020-05-22 12:35:00 +03:00
. option ( '-R, --relayer <URL>' , 'Withdraw via relayer' )
2021-12-07 19:57:18 +03:00
. option ( '-T, --tor <PORT>' , 'Optional tor port' )
2020-05-22 12:35:00 +03:00
program
. command ( 'deposit <currency> <amount>' )
2021-02-14 21:23:17 +03:00
. 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.'
)
2020-05-22 12:35:00 +03:00
. action ( async ( currency , amount ) => {
currency = currency . toLowerCase ( )
2021-12-07 19:57:18 +03:00
await init ( { rpc : program . rpc , currency , amount , torPort : program . tor } )
2020-05-22 12:35:00 +03:00
await deposit ( { currency , amount } )
} )
program
. command ( 'withdraw <note> <recipient> [ETH_purchase]' )
2021-02-14 21:23:17 +03:00
. 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.'
)
2020-05-22 12:35:00 +03:00
. action ( async ( noteString , recipient , refund ) => {
const { currency , amount , netId , deposit } = parseNote ( noteString )
2021-12-07 19:57:18 +03:00
await init ( { rpc : program . rpc , noteNetId : netId , currency , amount , torPort : program . tor } )
2021-02-14 21:23:17 +03:00
await withdraw ( {
deposit ,
currency ,
amount ,
recipient ,
refund ,
2021-12-07 19:57:18 +03:00
relayerURL : program . relayer ,
torPort : program . tor
2021-02-14 21:23:17 +03:00
} )
2020-05-22 12:35:00 +03:00
} )
program
2022-01-24 15:50:58 +03:00
. command ( 'balance [address] [token_address]' )
2020-05-22 12:35:00 +03:00
. description ( 'Check ETH and ERC20 balance' )
. action ( async ( address , tokenAddress ) => {
2021-12-07 19:57:18 +03:00
await init ( { rpc : program . rpc , torPort : program . tor , balanceCheck : true } )
2022-01-24 15:50:58 +03:00
if ( ! address && senderAccount ) {
console . log ( "Using address" , senderAccount , "from private key" )
address = senderAccount ;
}
2021-12-07 19:57:18 +03:00
await printETHBalance ( { address , name : 'Account' , symbol : netSymbol } )
2020-05-22 12:35:00 +03:00
if ( tokenAddress ) {
2021-12-07 19:57:18 +03:00
await printERC20Balance ( { address , name : 'Account' , tokenAddress } )
2020-05-21 22:29:33 +03:00
}
2020-05-22 12:35:00 +03:00
} )
2022-01-23 15:03:46 +03:00
program
. command ( 'send <address> [amount] [token_address]' )
. description ( 'Send ETH or ERC to address' )
. action ( async ( address , amount , tokenAddress ) => {
await init ( { rpc : program . rpc , torPort : program . tor , balanceCheck : true } )
await send ( { address , amount , tokenAddress } )
} )
2020-05-22 12:35:00 +03:00
program
. command ( 'compliance <note>' )
2021-02-14 21:23:17 +03:00
. description (
'Shows the deposit and withdrawal of the provided note. This might be necessary to show the origin of assets held in your withdrawal address.'
)
2020-05-22 12:35:00 +03:00
. action ( async ( noteString ) => {
const { currency , amount , netId , deposit } = parseNote ( noteString )
2021-12-07 19:57:18 +03:00
await init ( { rpc : program . rpc , noteNetId : netId , currency , amount , torPort : program . tor } )
const depositInfo = await loadDepositData ( { amount , currency , deposit } )
2020-05-22 12:35:00 +03:00
const depositDate = new Date ( depositInfo . timestamp * 1000 )
console . log ( '\n=============Deposit=================' )
2021-12-07 19:57:18 +03:00
console . log ( 'Deposit :' , amount , currency . toUpperCase ( ) )
2020-05-22 12:35:00 +03:00
console . log ( 'Date :' , depositDate . toLocaleDateString ( ) , depositDate . toLocaleTimeString ( ) )
2021-12-07 19:57:18 +03:00
console . log ( 'From :' , ` https:// ${ getExplorerLink ( ) } /address/ ${ depositInfo . from } ` )
console . log ( 'Transaction :' , ` https:// ${ getExplorerLink ( ) } /tx/ ${ depositInfo . txHash } ` )
2020-05-22 12:35:00 +03:00
console . log ( 'Commitment :' , depositInfo . commitment )
2021-12-07 19:57:18 +03:00
console . log ( 'Spent :' , depositInfo . isSpent )
if ( ! depositInfo . isSpent ) {
2020-05-22 12:35:00 +03:00
console . log ( 'The note was not spent' )
2021-07-29 18:17:35 +03:00
return
2020-05-21 22:29:33 +03:00
}
2021-12-07 19:57:18 +03:00
console . log ( '=====================================' , '\n' )
2020-05-22 12:35:00 +03:00
2021-12-07 19:57:18 +03:00
const withdrawInfo = await loadWithdrawalData ( { amount , currency , deposit } )
2020-05-22 12:35:00 +03:00
const withdrawalDate = new Date ( withdrawInfo . timestamp * 1000 )
console . log ( '\n=============Withdrawal==============' )
console . log ( 'Withdrawal :' , withdrawInfo . amount , currency )
console . log ( 'Relayer Fee :' , withdrawInfo . fee , currency )
console . log ( 'Date :' , withdrawalDate . toLocaleDateString ( ) , withdrawalDate . toLocaleTimeString ( ) )
2021-12-07 19:57:18 +03:00
console . log ( 'To :' , ` https:// ${ getExplorerLink ( ) } /address/ ${ withdrawInfo . to } ` )
console . log ( 'Transaction :' , ` https:// ${ getExplorerLink ( ) } /tx/ ${ withdrawInfo . txHash } ` )
2020-05-22 12:35:00 +03:00
console . log ( 'Nullifier :' , withdrawInfo . nullifier )
2021-12-07 19:57:18 +03:00
console . log ( '=====================================' , '\n' )
} )
program
. command ( 'syncEvents <type> <currency> <amount>' )
. description (
'Sync the local cache file of deposit / withdrawal events for specific currency.'
)
. action ( async ( type , currency , amount ) => {
console . log ( "Starting event sync command" )
currency = currency . toLowerCase ( )
await init ( { rpc : program . rpc , type , currency , amount , torPort : program . tor } )
const cachedEvents = await fetchEvents ( { type , currency , amount } )
console . log ( "Synced event for" , type , amount , currency . toUpperCase ( ) , netName , "Tornado instance to block" , cachedEvents [ cachedEvents . length - 1 ] . blockNumber )
2020-05-22 12:35:00 +03:00
} )
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 )
2021-02-14 21:23:17 +03:00
await withdraw ( {
deposit : parsedNote . deposit ,
currency ,
amount ,
recipient : senderAccount ,
relayerURL : program . relayer
} )
2020-05-22 12:35:00 +03:00
console . log ( '\nStart performing DAI deposit-withdraw test' )
currency = 'dai'
amount = '100'
await init ( { rpc : program . rpc , currency , amount } )
noteString = await deposit ( { currency , amount } )
2021-02-14 21:23:17 +03:00
parsedNote = parseNote ( noteString )
await withdraw ( {
deposit : parsedNote . deposit ,
currency ,
amount ,
recipient : senderAccount ,
refund : '0.02' ,
relayerURL : program . relayer
} )
2020-05-22 12:35:00 +03:00
} )
try {
await program . parseAsync ( process . argv )
process . exit ( 0 )
} catch ( e ) {
console . log ( 'Error:' , e )
process . exit ( 1 )
2020-05-21 22:29:33 +03:00
}
2020-05-22 12:35:00 +03:00
}
2020-05-21 22:29:33 +03:00
}
main ( )