2020-05-22 05:37:21 -04:00
#!/usr/bin/env node
2020-05-21 15:29:33 -04:00
// Works both in browser and node.js
2022-02-26 17:32:30 -05:00
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 ( 'fixed-merkle-tree' ) ;
const Web3 = require ( 'web3' ) ;
2021-12-07 11:57:18 -05:00
const Web3HttpProvider = require ( 'web3-providers-http' ) ;
2022-02-26 17:32:30 -05:00
const buildGroth16 = require ( 'websnark/src/groth16' ) ;
const websnarkUtils = require ( 'websnark/src/utils' ) ;
const { toWei , fromWei , toBN , BN } = require ( 'web3-utils' ) ;
2022-01-27 07:35:55 -05:00
const BigNumber = require ( 'bignumber.js' ) ;
2022-02-26 17:32:30 -05:00
const config = require ( './config' ) ;
const program = require ( 'commander' ) ;
const { GasPriceOracle } = require ( 'gas-price-oracle' ) ;
const SocksProxyAgent = require ( 'socks-proxy-agent' ) ;
2022-02-26 17:32:33 -05:00
const is _ip _private = require ( 'private-ip' ) ;
2020-05-21 15:29:33 -04:00
2022-02-26 17:32:35 -05:00
let web3 , torPort , tornado , tornadoContract , tornadoInstance , circuit , proving _key , groth16 , erc20 , senderAccount , netId , netName , netSymbol , doNotSubmitTx , multiCall , privateRpc , subgraph ;
2022-02-26 17:32:30 -05:00
let MERKLE _TREE _HEIGHT , ETH _AMOUNT , TOKEN _AMOUNT , PRIVATE _KEY ;
2020-05-21 15:29:33 -04:00
/** Whether we are in a browser or node.js */
2022-02-26 17:32:30 -05:00
const inBrowser = typeof window !== 'undefined' ;
let isTestRPC = false ;
2020-05-21 15:29:33 -04:00
/** Generate random number of specified byte length */
2022-02-26 17:32:30 -05:00
const rbigint = ( nbytes ) => snarkjs . bigInt . leBuff2int ( crypto . randomBytes ( nbytes ) ) ;
2020-05-21 15:29:33 -04:00
/** Compute pedersen hash */
2022-02-26 17:32:30 -05:00
const pedersenHash = ( data ) => circomlib . babyJub . unpackPoint ( circomlib . pedersenHash . hash ( data ) ) [ 0 ] ;
2020-05-21 15:29:33 -04:00
/** BigNumber to hex string of specified length */
function toHex ( number , length = 32 ) {
2022-02-26 17:32:30 -05:00
const str = number instanceof Buffer ? number . toString ( 'hex' ) : bigInt ( number ) . toString ( 16 ) ;
return '0x' + str . padStart ( length * 2 , '0' ) ;
}
/** Remove Decimal without rounding with BigNumber */
function rmDecimalBN ( bigNum , decimals = 6 ) {
return new BigNumber ( bigNum ) . times ( BigNumber ( 10 ) . pow ( decimals ) ) . integerValue ( BigNumber . ROUND _DOWN ) . div ( BigNumber ( 10 ) . pow ( decimals ) ) . toNumber ( ) ;
}
/** Use MultiCall Contract */
async function useMultiCall ( queryArray ) {
const multiCallABI = require ( './build/contracts/Multicall.abi.json' ) ;
const multiCallContract = new web3 . eth . Contract ( multiCallABI , multiCall ) ;
const { returnData } = await multiCallContract . methods . aggregate ( queryArray ) . call ( ) ;
return returnData ;
2020-05-21 15:29:33 -04:00
}
/** Display ETH account balance */
2022-01-27 00:50:49 -05:00
async function printETHBalance ( { address , name } ) {
2022-02-26 17:32:30 -05:00
const checkBalance = new BigNumber ( await web3 . eth . getBalance ( address ) ) . div ( BigNumber ( 10 ) . pow ( 18 ) ) ;
console . log ( ` ${ name } balance is ` , rmDecimalBN ( checkBalance ) , ` ${ netSymbol } ` ) ;
2020-05-21 15:29:33 -04:00
}
/** Display ERC20 account balance */
async function printERC20Balance ( { address , name , tokenAddress } ) {
2022-02-26 17:32:30 -05:00
let tokenDecimals , tokenBalance , tokenName , tokenSymbol ;
const erc20ContractJson = require ( './build/contracts/ERC20Mock.json' ) ;
erc20 = tokenAddress ? new web3 . eth . Contract ( erc20ContractJson . abi , tokenAddress ) : erc20 ;
if ( ! isTestRPC || ! multiCall ) {
const tokenCall = await useMultiCall ( [ [ tokenAddress , erc20 . methods . balanceOf ( address ) . encodeABI ( ) ] , [ tokenAddress , erc20 . methods . decimals ( ) . encodeABI ( ) ] , [ tokenAddress , erc20 . methods . name ( ) . encodeABI ( ) ] , [ tokenAddress , erc20 . methods . symbol ( ) . encodeABI ( ) ] ] ) ;
tokenDecimals = parseInt ( tokenCall [ 1 ] ) ;
tokenBalance = new BigNumber ( tokenCall [ 0 ] ) . div ( BigNumber ( 10 ) . pow ( tokenDecimals ) ) ;
tokenName = web3 . eth . abi . decodeParameter ( 'string' , tokenCall [ 2 ] ) ;
tokenSymbol = web3 . eth . abi . decodeParameter ( 'string' , tokenCall [ 3 ] ) ;
} else {
tokenDecimals = await erc20 . methods . decimals ( ) . call ( ) ;
tokenBalance = new BigNumber ( await erc20 . methods . balanceOf ( address ) . call ( ) ) . div ( BigNumber ( 10 ) . pow ( tokenDecimals ) ) ;
tokenName = await erc20 . methods . name ( ) . call ( ) ;
tokenSymbol = await erc20 . methods . symbol ( ) . call ( ) ;
}
console . log ( ` ${ name } ` , tokenName , ` Balance is ` , rmDecimalBN ( tokenBalance ) , tokenSymbol ) ;
2021-12-07 11:57:18 -05:00
}
2022-02-03 21:28:11 -05:00
async function submitTransaction ( signedTX ) {
2022-02-05 07:17:18 -05:00
console . log ( "Submitting transaction to the remote node" ) ;
2022-02-03 21:28:11 -05:00
await web3 . eth . sendSignedTransaction ( signedTX )
2022-02-26 17:32:30 -05:00
. 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 ) ;
} ) ;
2022-02-03 21:28:11 -05:00
}
2021-12-12 01:19:49 -05:00
async function generateTransaction ( to , encodedData , value = 0 ) {
2022-02-26 17:32:30 -05:00
const nonce = await web3 . eth . getTransactionCount ( senderAccount ) ;
let gasPrice = await fetchGasPrice ( ) ;
2021-12-12 01:19:49 -05:00
let gasLimit ;
async function estimateGas ( ) {
const fetchedGas = await web3 . eth . estimateGas ( {
from : senderAccount ,
to : to ,
value : value ,
nonce : nonce ,
data : encodedData
2022-02-26 17:32:30 -05:00
} ) ;
const bumped = Math . floor ( fetchedGas * 1.3 ) ;
return web3 . utils . toHex ( bumped ) ;
2021-12-12 01:19:49 -05:00
}
2022-01-23 07:03:46 -05:00
if ( encodedData ) {
2022-02-05 07:17:18 -05:00
gasLimit = await estimateGas ( ) ;
2022-01-23 07:03:46 -05:00
} else {
gasLimit = web3 . utils . toHex ( 21000 ) ;
}
2021-12-12 01:19:49 -05:00
2022-02-26 17:32:30 -05:00
function txoptions ( ) {
2021-12-07 11:57:18 -05:00
// Generate EIP-1559 transaction
2022-02-05 06:08:17 -05:00
if ( netId == 1 ) {
2022-02-05 07:17:18 -05:00
return {
2021-12-12 01:19:49 -05: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 11:57:18 -05:00
}
2022-02-05 06:08:17 -05:00
} else if ( netId == 5 || netId == 137 || netId == 43114 ) {
2022-02-05 07:17:18 -05:00
return {
2022-01-23 02:26:24 -05:00
to : to ,
value : value ,
nonce : nonce ,
maxFeePerGas : gasPrice ,
maxPriorityFeePerGas : gasPrice ,
gas : gasLimit ,
data : encodedData
}
2021-12-07 11:57:18 -05:00
} else {
2022-02-05 07:17:18 -05:00
return {
2021-12-12 01:19:49 -05:00
to : to ,
2021-12-07 11:57:18 -05:00
value : value ,
2021-12-12 01:19:49 -05:00
nonce : nonce ,
2021-12-07 11:57:18 -05:00
gasPrice : gasPrice ,
gas : gasLimit ,
data : encodedData
}
}
}
2022-02-26 17:32:30 -05:00
const tx = txoptions ( ) ;
2021-12-07 11:57:18 -05:00
const signed = await web3 . eth . accounts . signTransaction ( tx , PRIVATE _KEY ) ;
2022-02-26 17:32:33 -05:00
if ( ! doNotSubmitTx ) {
2022-02-05 07:17:18 -05:00
await submitTransaction ( signed . rawTransaction ) ;
2022-02-03 21:28:11 -05:00
} else {
2022-02-26 17:32:30 -05:00
console . log ( '\n=============Raw TX=================' , '\n' ) ;
console . log ( ` Please submit this raw tx to https:// ${ getExplorerLink ( ) } /pushTx, or otherwise broadcast with node cli.js broadcast command. ` , ` \n ` ) ;
console . log ( signed . rawTransaction , ` \n ` ) ;
console . log ( '=====================================' , '\n' ) ;
2022-02-03 21:28:11 -05:00
}
2020-05-21 15:29:33 -04:00
}
/ * *
* Create deposit object from secret and nullifier
* /
function createDeposit ( { nullifier , secret } ) {
2022-02-26 17:32:30 -05: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 15:29:33 -04:00
}
2021-12-07 11:57:18 -05: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-02-26 17:32:30 -05:00
console . log ( "Backed up deposit note as" , ` ./backup-tornado- ${ currency } - ${ amount } - ${ netId } - ${ note . slice ( 0 , 10 ) } .txt ` ) ;
2021-12-07 11:57:18 -05:00
} catch ( e ) {
2022-02-26 17:32:30 -05:00
throw new Error ( 'Writing backup note failed:' , e ) ;
2021-12-07 11:57:18 -05:00
}
}
2020-05-21 15:29:33 -04:00
/ * *
* Make a deposit
* @ param currency С urrency
* @ param amount Deposit amount
* /
async function deposit ( { currency , amount } ) {
2022-02-26 17:32:30 -05:00
assert ( senderAccount != null , 'Error! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you deposit' ) ;
2021-02-14 13:23:17 -05:00
const deposit = createDeposit ( {
nullifier : rbigint ( 31 ) ,
secret : rbigint ( 31 )
2022-02-26 17:32:30 -05:00
} ) ;
const note = toHex ( deposit . preimage , 62 ) ;
const noteString = ` tornado- ${ currency } - ${ amount } - ${ netId } - ${ note } ` ;
console . log ( ` Your note: ${ noteString } ` ) ;
await backupNote ( { currency , amount , netId , note , noteString } ) ;
2022-01-23 01:52:59 -05:00
if ( currency === netSymbol . toLowerCase ( ) ) {
2022-02-26 17:32:30 -05:00
await printETHBalance ( { address : tornadoContract . _address , name : 'Tornado contract' } ) ;
await printETHBalance ( { address : senderAccount , name : 'Sender account' } ) ;
const value = isTestRPC ? ETH _AMOUNT : fromDecimals ( { amount , decimals : 18 } ) ;
console . log ( 'Submitting deposit transaction' ) ;
await generateTransaction ( contractAddress , tornado . methods . deposit ( tornadoInstance , toHex ( deposit . commitment ) , [ ] ) . encodeABI ( ) , value ) ;
await printETHBalance ( { address : tornadoContract . _address , name : 'Tornado contract' } ) ;
await printETHBalance ( { address : senderAccount , name : 'Sender account' } ) ;
2021-02-14 13:23:17 -05:00
} else {
// a token
2022-02-26 17:32:30 -05:00
await printERC20Balance ( { address : tornadoContract . _address , name : 'Tornado contract' } ) ;
await printERC20Balance ( { address : senderAccount , name : 'Sender account' } ) ;
const decimals = isTestRPC ? 18 : config . deployments [ ` netId ${ netId } ` ] [ currency ] . decimals ;
const tokenAmount = isTestRPC ? TOKEN _AMOUNT : fromDecimals ( { amount , decimals } ) ;
2022-02-03 21:28:11 -05:00
if ( isTestRPC ) {
2022-02-26 17:32:30 -05:00
console . log ( 'Minting some test tokens to deposit' ) ;
await generateTransaction ( erc20Address , erc20 . methods . mint ( senderAccount , tokenAmount ) . encodeABI ( ) ) ;
2020-05-22 05:35:00 -04:00
}
2020-05-21 15:29:33 -04:00
2022-02-26 17:32:30 -05:00
const allowance = await erc20 . methods . allowance ( senderAccount , tornado . _address ) . call ( { from : senderAccount } ) ;
console . log ( 'Current allowance is' , fromWei ( allowance ) ) ;
2020-05-22 05:35:00 -04:00
if ( toBN ( allowance ) . lt ( toBN ( tokenAmount ) ) ) {
2022-02-26 17:32:30 -05:00
console . log ( 'Approving tokens for deposit' ) ;
await generateTransaction ( erc20Address , erc20 . methods . approve ( tornado . _address , tokenAmount ) . encodeABI ( ) ) ;
2020-05-21 15:29:33 -04:00
}
2022-02-26 17:32:30 -05:00
console . log ( 'Submitting deposit transaction' ) ;
await generateTransaction ( contractAddress , tornado . methods . deposit ( tornadoInstance , toHex ( deposit . commitment ) , [ ] ) . encodeABI ( ) ) ;
await printERC20Balance ( { address : tornadoContract . _address , name : 'Tornado contract' } ) ;
await printERC20Balance ( { address : senderAccount , name : 'Sender account' } ) ;
2020-05-22 05:35:00 -04:00
}
2022-02-26 17:32:30 -05:00
return noteString ;
2020-05-21 15:29:33 -04: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 11:57:18 -05:00
async function generateMerkleProof ( deposit , currency , amount ) {
2022-02-26 17:32:30 -05:00
let leafIndex = - 1 ;
2020-05-22 05:35:00 -04:00
// Get all deposit events from smart contract and assemble merkle tree from them
2021-07-16 06:30:07 -04:00
2022-02-26 17:32:30 -05:00
const cachedEvents = await fetchEvents ( { type : 'deposit' , currency , amount } ) ;
2020-12-24 06:39:20 -05:00
2021-12-07 11:57:18 -05:00
const leaves = cachedEvents
2021-07-16 06:27:08 -04:00
. sort ( ( a , b ) => a . leafIndex - b . leafIndex ) // Sort events in chronological order
2020-12-24 06:39:20 -05:00
. map ( ( e ) => {
2022-02-26 17:32:30 -05:00
const index = toBN ( e . leafIndex ) . toNumber ( ) ;
2020-05-22 05:35:00 -04:00
2021-07-16 06:27:08 -04:00
if ( toBN ( e . commitment ) . eq ( toBN ( deposit . commitmentHex ) ) ) {
2022-02-26 17:32:30 -05:00
leafIndex = index ;
2020-12-24 06:39:20 -05:00
}
2022-02-26 17:32:30 -05:00
return toBN ( e . commitment ) . toString ( 10 ) ;
} ) ;
const tree = new merkleTree ( MERKLE _TREE _HEIGHT , leaves ) ;
2020-05-22 05:35:00 -04:00
// Validate that our data is correct
2022-02-26 17:32:30 -05:00
const root = tree . root ( ) ;
let isValidRoot , isSpent ;
if ( ! isTestRPC || ! multiCall ) {
const callContract = await useMultiCall ( [ [ tornadoContract . _address , tornadoContract . methods . isKnownRoot ( toHex ( root ) ) . encodeABI ( ) ] , [ tornadoContract . _address , tornadoContract . methods . isSpent ( toHex ( deposit . nullifierHash ) ) . encodeABI ( ) ] ] )
isValidRoot = web3 . eth . abi . decodeParameter ( 'bool' , callContract [ 0 ] ) ;
isSpent = web3 . eth . abi . decodeParameter ( 'bool' , callContract [ 1 ] ) ;
} else {
isValidRoot = await tornadoContract . methods . isKnownRoot ( toHex ( root ) ) . call ( ) ;
isSpent = await tornadoContract . 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' ) ;
2020-05-22 05:35:00 -04:00
// Compute merkle proof of our commitment
2022-02-26 17:32:30 -05:00
const { pathElements , pathIndices } = tree . path ( leafIndex ) ;
return { root , pathElements , pathIndices } ;
2020-05-21 15:29:33 -04: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 11:57:18 -05:00
async function generateProof ( { deposit , currency , amount , recipient , relayerAddress = 0 , fee = 0 , refund = 0 } ) {
2020-05-22 05:35:00 -04:00
// Compute merkle proof of our commitment
2022-02-26 17:32:30 -05:00
const { root , pathElements , pathIndices } = await generateMerkleProof ( deposit , currency , amount ) ;
2020-05-22 05:35:00 -04: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 ,
2022-02-26 17:32:24 -05:00
pathElements : pathElements ,
pathIndices : pathIndices
2020-05-22 05:35:00 -04:00
}
2022-02-26 17:32:30 -05: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' ) ;
2020-05-22 05:35:00 -04:00
const args = [
toHex ( input . root ) ,
toHex ( input . nullifierHash ) ,
toHex ( input . recipient , 20 ) ,
toHex ( input . relayer , 20 ) ,
toHex ( input . fee ) ,
toHex ( input . refund )
2022-02-26 17:32:30 -05:00
] ;
2020-05-22 05:35:00 -04:00
2022-02-26 17:32:30 -05:00
return { proof , args } ;
2020-05-21 15:29:33 -04:00
}
/ * *
* Do an ETH withdrawal
* @ param noteString Note to withdraw
* @ param recipient Recipient address
* /
2022-02-26 17:32:35 -05:00
async function withdraw ( { deposit , currency , amount , recipient , relayerURL , refund = '0' } ) {
2021-12-07 11:57:18 -05:00
let options = { } ;
2022-01-23 01:52:59 -05:00
if ( currency === netSymbol . toLowerCase ( ) && refund !== '0' ) {
2022-02-26 17:32:30 -05:00
throw new Error ( 'The ETH purchase is supposted to be 0 for ETH withdrawals' ) ;
2020-05-22 05:35:00 -04:00
}
2022-02-26 17:32:30 -05:00
refund = toWei ( refund ) ;
2020-05-22 05:35:00 -04:00
if ( relayerURL ) {
if ( relayerURL . endsWith ( '.eth' ) ) {
2022-02-26 17:32:30 -05:00
throw new Error ( 'ENS name resolving is not supported. Please provide DNS name of the relayer. See instuctions in README.md' ) ;
2020-05-21 15:29:33 -04:00
}
2021-12-07 11:57:18 -05:00
if ( torPort ) {
2022-02-26 17:32:30 -05: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 11:57:18 -05:00
}
2022-02-26 17:32:30 -05:00
const relayerStatus = await axios . get ( relayerURL + '/status' , options ) ;
2020-12-24 06:39:20 -05:00
const { rewardAccount , netId , ethPrices , tornadoServiceFee } = relayerStatus . data
2022-02-26 17:32:30 -05:00
assert ( netId === ( await web3 . eth . net . getId ( ) ) || netId === '*' , 'This relay is for different network' ) ;
console . log ( 'Relay address:' , rewardAccount ) ;
2020-12-24 06:39:20 -05:00
2022-02-26 17:32:30 -05:00
const gasPrice = await fetchGasPrice ( ) ;
2020-05-22 05:35:00 -04:00
2022-02-03 21:28:11 -05:00
const decimals = isTestRPC ? 18 : config . deployments [ ` netId ${ netId } ` ] [ currency ] . decimals
2021-02-14 13:23:17 -05:00
const fee = calculateFee ( {
currency ,
gasPrice ,
amount ,
refund ,
ethPrices ,
relayerServiceFee : tornadoServiceFee ,
decimals
2022-02-26 17:32:30 -05:00
} ) ;
2020-05-22 05:35:00 -04:00
if ( fee . gt ( fromDecimals ( { amount , decimals } ) ) ) {
2022-02-26 17:32:30 -05:00
throw new Error ( 'Too high refund' ) ;
} ;
2020-12-24 06:39:20 -05:00
2022-02-26 17:32:30 -05:00
const { proof , args } = await generateProof ( { deposit , currency , amount , recipient , relayerAddress : rewardAccount , fee , refund } ) ;
2020-05-21 15:29:33 -04:00
2022-02-26 17:32:30 -05:00
console . log ( 'Sending withdraw transaction through relay' ) ;
2020-05-22 05:35:00 -04:00
try {
2021-02-14 13:23:17 -05:00
const response = await axios . post ( relayerURL + '/v1/tornadoWithdraw' , {
contract : tornadoInstance ,
proof ,
args
2021-12-07 11:57:18 -05:00
} , options )
2020-05-22 05:35:00 -04:00
2022-02-26 17:32:30 -05:00
const { id } = response . data ;
2020-12-24 06:39:20 -05:00
2022-02-26 17:32:30 -05:00
const result = await getStatus ( id , relayerURL , options ) ;
console . log ( 'STATUS' , result ) ;
2020-05-22 05:35:00 -04:00
} catch ( e ) {
if ( e . response ) {
2022-02-26 17:32:30 -05:00
console . error ( e . response . data . error ) ;
2020-05-22 05:35:00 -04:00
} else {
2022-02-26 17:32:30 -05:00
console . error ( e . message ) ;
2020-05-22 05:35:00 -04:00
}
2020-05-21 15:29:33 -04:00
}
2021-02-14 13:23:17 -05:00
} else {
// using private key
2021-12-07 11:57:18 -05: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.
2022-02-26 17:32:30 -05:00
assert ( recipient . toLowerCase ( ) == senderAccount . toLowerCase ( ) , 'Withdrawal recepient mismatches with the account of provided private key from environment file' ) ;
const checkBalance = await web3 . eth . getBalance ( senderAccount ) ;
assert ( checkBalance !== 0 , 'You have 0 balance, make sure to fund account by withdrawing from tornado using relayer first' ) ;
2021-12-07 11:57:18 -05:00
2022-02-26 17:32:30 -05:00
const { proof , args } = await generateProof ( { deposit , currency , amount , recipient , refund } ) ;
2020-05-22 05:35:00 -04:00
2022-02-26 17:32:30 -05:00
console . log ( 'Submitting withdraw transaction' ) ;
await generateTransaction ( contractAddress , tornado . methods . withdraw ( tornadoInstance , proof , ... args ) . encodeABI ( ) ) ;
2020-05-22 05:35:00 -04:00
}
2022-01-26 08:24:58 -05:00
if ( currency === netSymbol . toLowerCase ( ) ) {
2022-02-26 17:32:30 -05:00
await printETHBalance ( { address : recipient , name : 'Recipient' } ) ;
2022-01-26 08:24:58 -05:00
} else {
2022-02-26 17:32:30 -05:00
await printERC20Balance ( { address : recipient , name : 'Recipient' } ) ;
2022-01-26 08:24:58 -05:00
}
2022-02-26 17:32:30 -05:00
console . log ( 'Done withdrawal from Tornado Cash' ) ;
2020-05-22 05:35:00 -04:00
}
2020-05-21 15:29:33 -04:00
2022-01-23 07:03:46 -05: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
2022-02-26 17:32:30 -05:00
assert ( senderAccount != null , 'Error! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you send' ) ;
2022-01-23 07:03:46 -05:00
if ( tokenAddress ) {
2022-02-26 17:32:30 -05:00
const erc20ContractJson = require ( './build/contracts/ERC20Mock.json' ) ;
erc20 = new web3 . eth . Contract ( erc20ContractJson . abi , tokenAddress ) ;
let tokenBalance , tokenDecimals , tokenSymbol ;
if ( ! isTestRPC || ! multiCall ) {
const callToken = await useMultiCall ( [ [ tokenAddress , erc20 . methods . balanceOf ( senderAccount ) . encodeABI ( ) ] , [ tokenAddress , erc20 . methods . decimals ( ) . encodeABI ( ) ] , [ tokenAddress , erc20 . methods . symbol ( ) . encodeABI ( ) ] ] ) ;
tokenBalance = new BigNumber ( callToken [ 0 ] ) ;
tokenDecimals = parseInt ( callToken [ 1 ] ) ;
tokenSymbol = web3 . eth . abi . decodeParameter ( 'string' , callToken [ 2 ] ) ;
} else {
tokenBalance = new BigNumber ( await erc20 . methods . balanceOf ( senderAccount ) . call ( ) ) ;
tokenDecimals = await erc20 . methods . decimals ( ) . call ( ) ;
tokenSymbol = await erc20 . methods . symbol ( ) . call ( ) ;
}
const toSend = new BigNumber ( amount ) . times ( BigNumber ( 10 ) . pow ( tokenDecimals ) ) ;
2022-01-27 07:35:55 -05:00
if ( tokenBalance . lt ( toSend ) ) {
2022-02-26 17:32:30 -05:00
console . error ( "You have" , rmDecimalBN ( tokenBalance . div ( BigNumber ( 10 ) . pow ( tokenDecimals ) ) ) , tokenSymbol , ", you can't send more than you have" ) ;
2022-01-23 07:03:46 -05:00
process . exit ( 1 ) ;
}
2022-02-26 17:32:30 -05:00
const encodeTransfer = erc20 . methods . transfer ( address , toSend ) . encodeABI ( ) ;
await generateTransaction ( tokenAddress , encodeTransfer ) ;
console . log ( 'Sent' , amount , tokenSymbol , 'to' , address ) ;
2022-01-23 07:03:46 -05:00
} else {
2022-01-27 07:35:55 -05:00
const balance = new BigNumber ( await web3 . eth . getBalance ( senderAccount ) ) ;
2022-02-26 17:32:30 -05:00
assert ( balance . toNumber ( ) !== 0 , "You have 0 balance, can't send transaction" ) ;
2022-01-26 08:24:58 -05:00
if ( amount ) {
2022-02-26 17:32:30 -05:00
toSend = new BigNumber ( amount ) . times ( BigNumber ( 10 ) . pow ( 18 ) ) ;
2022-01-27 07:35:55 -05:00
if ( balance . lt ( toSend ) ) {
2022-02-26 17:32:30 -05:00
console . error ( "You have" , rmDecimalBN ( balance . div ( BigNumber ( 10 ) . pow ( 18 ) ) ) , netSymbol + ", you can't send more than you have." ) ;
2022-01-26 08:24:58 -05:00
process . exit ( 1 ) ;
}
} else {
2022-02-26 17:32:30 -05:00
console . log ( 'Amount not defined, sending all available amounts' ) ;
2022-01-27 07:35:55 -05:00
const gasPrice = new BigNumber ( await fetchGasPrice ( ) ) ;
const gasLimit = new BigNumber ( 21000 ) ;
2022-02-26 17:32:30 -05:00
if ( netId == 1 ) {
2022-01-27 07:35:55 -05:00
const priorityFee = new BigNumber ( await gasPrices ( 3 ) ) ;
toSend = balance . minus ( gasLimit . times ( gasPrice . plus ( priorityFee ) ) ) ;
2022-01-23 07:03:46 -05:00
} else {
2022-01-27 07:35:55 -05:00
toSend = balance . minus ( gasLimit . times ( gasPrice ) ) ;
2022-01-23 07:03:46 -05:00
}
}
2022-02-26 17:32:30 -05:00
await generateTransaction ( address , null , toSend ) ;
console . log ( 'Sent' , rmDecimalBN ( toSend . div ( BigNumber ( 10 ) . pow ( 18 ) ) ) , netSymbol , 'to' , address ) ;
2022-01-23 07:03:46 -05:00
}
}
2021-12-07 11:57:18 -05:00
function getStatus ( id , relayerURL , options ) {
2020-12-24 06:39:20 -05:00
return new Promise ( ( resolve ) => {
async function getRelayerStatus ( ) {
2022-02-26 17:32:30 -05:00
const responseStatus = await axios . get ( relayerURL + '/v1/jobs/' + id , options ) ;
2020-12-24 06:39:20 -05:00
if ( responseStatus . status === 200 ) {
const { txHash , status , confirmations , failedReason } = responseStatus . data
2022-02-26 17:32:30 -05:00
console . log ( ` Current job status ${ status } , confirmations: ${ confirmations } ` ) ;
2020-12-24 06:39:20 -05:00
if ( status === 'FAILED' ) {
2022-02-26 17:32:30 -05:00
throw new Error ( status + ' failed reason:' + failedReason ) ;
2020-12-24 06:39:20 -05:00
}
if ( status === 'CONFIRMED' ) {
2022-02-26 17:32:30 -05:00
const receipt = await waitForTxReceipt ( { txHash } ) ;
2021-02-14 13:23:17 -05:00
console . log (
2021-12-07 11:57:18 -05:00
` Transaction submitted through the relay. View transaction on block explorer https:// ${ getExplorerLink ( ) } /tx/ ${ txHash } `
2022-02-26 17:32:30 -05:00
) ;
console . log ( 'Transaction mined in block' , receipt . blockNumber ) ;
resolve ( status ) ;
2020-12-24 06:39:20 -05:00
}
}
setTimeout ( ( ) => {
2022-02-26 17:32:30 -05:00
getRelayerStatus ( id , relayerURL ) ;
2020-12-24 06:39:20 -05:00
} , 3000 )
}
2022-02-26 17:32:30 -05:00
getRelayerStatus ( ) ;
2020-12-24 06:39:20 -05:00
} )
}
2021-12-07 11:57:18 -05:00
function capitalizeFirstLetter ( string ) {
return string . charAt ( 0 ) . toUpperCase ( ) + string . slice ( 1 ) ;
}
2020-05-22 05:35:00 -04:00
function fromDecimals ( { amount , decimals } ) {
2022-02-26 17:32:30 -05:00
amount = amount . toString ( ) ;
let ether = amount . toString ( ) ;
const base = new BN ( '10' ) . pow ( new BN ( decimals ) ) ;
const baseLength = base . toString ( 10 ) . length - 1 || 1 ;
2020-05-22 05:35:00 -04:00
2022-02-26 17:32:30 -05:00
const negative = ether . substring ( 0 , 1 ) === '-' ;
2020-05-22 05:35:00 -04:00
if ( negative ) {
2022-02-26 17:32:30 -05:00
ether = ether . substring ( 1 ) ;
2020-05-22 05:35:00 -04:00
}
if ( ether === '.' ) {
2022-02-26 17:32:30 -05:00
throw new Error ( '[ethjs-unit] while converting number ' + amount + ' to wei, invalid value' ) ;
2020-05-22 05:35:00 -04:00
}
// Split it into a whole and fractional part
2022-02-26 17:32:30 -05:00
const comps = ether . split ( '.' ) ;
2020-05-22 05:35:00 -04:00
if ( comps . length > 2 ) {
2022-02-26 17:32:30 -05:00
throw new Error ( '[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal points' ) ;
2020-05-22 05:35:00 -04:00
}
2022-02-26 17:32:30 -05:00
let whole = comps [ 0 ] ;
let fraction = comps [ 1 ] ;
2020-05-22 05:35:00 -04:00
if ( ! whole ) {
2022-02-26 17:32:30 -05:00
whole = '0' ;
2020-05-22 05:35:00 -04:00
}
if ( ! fraction ) {
2022-02-26 17:32:30 -05:00
fraction = '0' ;
2020-05-22 05:35:00 -04:00
}
if ( fraction . length > baseLength ) {
2022-02-26 17:32:30 -05:00
throw new Error ( '[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal places' ) ;
2020-05-22 05:35:00 -04:00
}
while ( fraction . length < baseLength ) {
2022-02-26 17:32:30 -05:00
fraction += '0' ;
2020-05-22 05:35:00 -04:00
}
2022-02-26 17:32:30 -05:00
whole = new BN ( whole ) ;
fraction = new BN ( fraction ) ;
let wei = whole . mul ( base ) . add ( fraction ) ;
2020-05-22 05:35:00 -04:00
if ( negative ) {
2022-02-26 17:32:30 -05:00
wei = wei . mul ( negative ) ;
2020-05-22 05:35:00 -04:00
}
2022-02-26 17:32:30 -05:00
return new BN ( wei . toString ( 10 ) , 10 ) ;
2020-05-21 15:29:33 -04:00
}
function toDecimals ( value , decimals , fixed ) {
2022-02-26 17:32:30 -05:00
const zero = new BN ( 0 ) ;
const negative1 = new BN ( - 1 ) ;
decimals = decimals || 18 ;
fixed = fixed || 7 ;
2020-05-22 05:35:00 -04:00
2022-02-26 17:32:30 -05:00
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 ;
2020-05-22 05:35:00 -04:00
if ( negative ) {
2022-02-26 17:32:30 -05:00
value = value . mul ( negative1 ) ;
2020-05-22 05:35:00 -04:00
}
2022-02-26 17:32:30 -05:00
let fraction = value . mod ( base ) . toString ( 10 ) ;
2020-05-22 05:35:00 -04:00
while ( fraction . length < baseLength ) {
2022-02-26 17:32:30 -05:00
fraction = ` 0 ${ fraction } ` ;
2020-05-22 05:35:00 -04:00
}
2022-02-26 17:32:30 -05:00
fraction = fraction . match ( /^([0-9]*[1-9]|0)(0*)/ ) [ 1 ] ;
2020-05-22 05:35:00 -04:00
2022-02-26 17:32:30 -05:00
const whole = value . div ( base ) . toString ( 10 ) ;
value = ` ${ whole } ${ fraction === '0' ? '' : ` . ${ fraction } ` } ` ;
2020-05-22 05:35:00 -04:00
if ( negative ) {
2022-02-26 17:32:30 -05:00
value = ` - ${ value } ` ;
2020-05-22 05:35:00 -04:00
}
if ( fixed ) {
2022-02-26 17:32:30 -05:00
value = value . slice ( 0 , fixed ) ;
2020-05-22 05:35:00 -04:00
}
2022-02-26 17:32:30 -05:00
return value ;
2020-05-21 15:29:33 -04:00
}
2021-12-07 11:57:18 -05:00
// List fetched from https://github.com/ethereum-lists/chains/blob/master/_data/chains
function getExplorerLink ( ) {
switch ( netId ) {
case 56 :
2022-02-26 17:32:30 -05:00
return 'bscscan.com' ;
2021-12-07 11:57:18 -05:00
case 100 :
2022-02-26 17:32:30 -05:00
return 'blockscout.com/poa/xdai' ;
2021-12-07 11:57:18 -05:00
case 137 :
2022-02-26 17:32:30 -05:00
return 'polygonscan.com' ;
2021-12-07 11:57:18 -05:00
case 42161 :
2022-02-26 17:32:30 -05:00
return 'arbiscan.io' ;
2021-12-07 11:57:18 -05:00
case 43114 :
2022-02-26 17:32:30 -05:00
return 'snowtrace.io' ;
2021-12-07 11:57:18 -05:00
case 5 :
2022-02-26 17:32:30 -05:00
return 'goerli.etherscan.io' ;
2021-12-07 11:57:18 -05:00
case 42 :
2022-02-26 17:32:30 -05:00
return 'kovan.etherscan.io' ;
2022-01-23 04:42:08 -05:00
case 10 :
2022-02-26 17:32:30 -05:00
return 'optimistic.etherscan.io' ;
2021-12-07 11:57:18 -05:00
default :
2022-02-26 17:32:30 -05:00
return 'etherscan.io' ;
2021-12-07 11:57:18 -05:00
}
}
// List fetched from https://github.com/trustwallet/assets/tree/master/blockchains
2020-05-21 15:29:33 -04:00
function getCurrentNetworkName ( ) {
2020-05-22 05:35:00 -04:00
switch ( netId ) {
2021-02-14 13:23:17 -05:00
case 1 :
2022-02-26 17:32:30 -05:00
return 'Ethereum' ;
2021-12-07 11:57:18 -05:00
case 56 :
2022-02-26 17:32:30 -05:00
return 'BinanceSmartChain' ;
2021-12-07 11:57:18 -05:00
case 100 :
2022-02-26 17:32:30 -05:00
return 'GnosisChain' ;
2021-12-07 11:57:18 -05:00
case 137 :
2022-02-26 17:32:30 -05:00
return 'Polygon' ;
2021-12-07 11:57:18 -05:00
case 42161 :
2022-02-26 17:32:30 -05:00
return 'Arbitrum' ;
2021-12-07 11:57:18 -05:00
case 43114 :
2022-02-26 17:32:30 -05:00
return 'Avalanche' ;
2021-02-14 13:23:17 -05:00
case 5 :
2022-02-26 17:32:30 -05:00
return 'Goerli' ;
2021-02-14 13:23:17 -05:00
case 42 :
2022-02-26 17:32:30 -05:00
return 'Kovan' ;
2022-02-04 21:11:59 -05:00
case 10 :
2022-02-26 17:32:30 -05:00
return 'Optimism' ;
2021-12-07 11:57:18 -05:00
default :
2022-02-26 17:32:30 -05:00
return 'testRPC' ;
2020-05-22 05:35:00 -04:00
}
2020-05-21 15:29:33 -04:00
}
2021-12-07 11:57:18 -05:00
function getCurrentNetworkSymbol ( ) {
switch ( netId ) {
case 56 :
2022-02-26 17:32:30 -05:00
return 'BNB' ;
2021-12-07 11:57:18 -05:00
case 100 :
2022-02-26 17:32:30 -05:00
return 'xDAI' ;
2021-12-07 11:57:18 -05:00
case 137 :
2022-02-26 17:32:30 -05:00
return 'MATIC' ;
2021-12-07 11:57:18 -05:00
case 43114 :
2022-02-26 17:32:30 -05:00
return 'AVAX' ;
2021-12-07 11:57:18 -05:00
default :
2022-02-26 17:32:30 -05:00
return 'ETH' ;
2021-12-07 11:57:18 -05:00
}
}
function gasPricesETH ( value = 80 ) {
2022-02-26 17:32:30 -05: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' ) ) ;
2020-12-24 06:39:20 -05:00
}
2021-12-07 11:57:18 -05:00
function gasPrices ( value = 5 ) {
2022-02-26 17:32:30 -05:00
return toHex ( toWei ( value . toString ( ) , 'gwei' ) ) ;
2021-12-07 11:57:18 -05:00
}
2020-12-24 06:39:20 -05:00
async function fetchGasPrice ( ) {
try {
2021-12-07 11:57:18 -05:00
const options = {
chainId : netId
}
// Bump fees for Ethereum network
if ( netId == 1 ) {
2022-02-26 17:32:30 -05:00
const oracle = new GasPriceOracle ( options ) ;
const gas = await oracle . gasPrices ( ) ;
return gasPricesETH ( gas . instant ) ;
2022-02-03 21:28:11 -05:00
} else if ( netId == 5 || isTestRPC ) {
2022-02-26 17:32:30 -05:00
const web3GasPrice = await web3 . eth . getGasPrice ( ) ;
return web3GasPrice ;
2021-12-07 11:57:18 -05:00
} else {
2022-02-26 17:32:30 -05:00
const oracle = new GasPriceOracle ( options ) ;
const gas = await oracle . gasPrices ( ) ;
return gasPrices ( gas . instant ) ;
2021-12-07 11:57:18 -05:00
}
2020-12-24 06:39:20 -05:00
} catch ( err ) {
2022-02-26 17:32:30 -05:00
throw new Error ( ` Method fetchGasPrice has error ${ err . message } ` ) ;
2020-12-24 06:39:20 -05:00
}
}
function calculateFee ( { currency , gasPrice , amount , refund , ethPrices , relayerServiceFee , decimals } ) {
2021-02-14 13:23:17 -05:00
const decimalsPoint =
2022-02-26 17:32:30 -05:00
Math . floor ( relayerServiceFee ) === Number ( relayerServiceFee ) ? 0 : relayerServiceFee . toString ( ) . split ( '.' ) [ 1 ] . length ;
const roundDecimal = 10 * * decimalsPoint ;
const total = toBN ( fromDecimals ( { amount , decimals } ) ) ;
const feePercent = total . mul ( toBN ( relayerServiceFee * roundDecimal ) ) . div ( toBN ( roundDecimal * 100 ) ) ;
const expense = toBN ( gasPrice ) . mul ( toBN ( 5e5 ) ) ;
let desiredFee ;
2020-05-22 05:35:00 -04:00
switch ( currency ) {
2022-01-23 01:52:59 -05:00
case netSymbol . toLowerCase ( ) : {
2022-02-26 17:32:30 -05:00
desiredFee = expense . add ( feePercent ) ;
break ;
2021-12-07 11:57:18 -05:00
}
2021-02-14 13:23:17 -05:00
default : {
desiredFee = expense
. add ( toBN ( refund ) )
. mul ( toBN ( 10 * * decimals ) )
2022-02-26 17:32:30 -05:00
. div ( toBN ( ethPrices [ currency ] ) ) ;
desiredFee = desiredFee . add ( feePercent ) ;
break ;
2021-02-14 13:23:17 -05:00
}
2020-05-22 05:35:00 -04:00
}
2022-02-26 17:32:30 -05:00
return desiredFee ;
2020-05-21 15:29:33 -04: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 05:35:00 -04:00
return new Promise ( ( resolve , reject ) => {
const checkForTx = async ( txHash , retryAttempt = 0 ) => {
2022-02-26 17:32:30 -05:00
const result = await web3 . eth . getTransactionReceipt ( txHash ) ;
2020-05-22 05:35:00 -04:00
if ( ! result || ! result . blockNumber ) {
if ( retryAttempt <= attempts ) {
2022-02-26 17:32:30 -05:00
setTimeout ( ( ) => checkForTx ( txHash , retryAttempt + 1 ) , delay ) ;
2020-05-22 05:35:00 -04:00
} else {
2022-02-26 17:32:30 -05:00
reject ( new Error ( 'tx was not mined' ) ) ;
2020-05-21 15:29:33 -04:00
}
2020-05-22 05:35:00 -04:00
} else {
2022-02-26 17:32:30 -05:00
resolve ( result ) ;
2020-05-22 05:35:00 -04:00
}
}
2022-02-26 17:32:30 -05:00
checkForTx ( txHash ) ;
2020-05-22 05:35:00 -04:00
} )
2020-05-21 15:29:33 -04:00
}
2022-01-21 23:49:45 -05:00
function initJson ( file ) {
2022-02-26 17:32:30 -05:00
return new Promise ( ( resolve , reject ) => {
fs . readFile ( file , 'utf8' , ( error , data ) => {
if ( error ) {
resolve ( [ ] ) ;
}
try {
resolve ( JSON . parse ( data ) ) ;
} catch ( error ) {
resolve ( [ ] ) ;
}
2022-01-21 23:49:45 -05:00
} ) ;
2022-02-26 17:32:30 -05:00
} ) ;
2022-01-21 23:49:45 -05:00
} ;
2021-12-12 01:19:49 -05:00
function loadCachedEvents ( { type , currency , amount } ) {
2021-07-16 06:27:08 -04:00
try {
2022-02-26 17:32:30 -05:00
const module = require ( ` ./cache/ ${ netName . toLowerCase ( ) } / ${ type } s_ ${ currency } _ ${ amount } .json ` ) ;
2021-07-16 06:27:08 -04:00
if ( module ) {
2022-02-26 17:32:30 -05:00
const events = module ;
2021-07-16 06:27:08 -04:00
return {
events ,
lastBlock : events [ events . length - 1 ] . blockNumber
}
}
} catch ( err ) {
2022-02-26 17:32:30 -05:00
console . log ( "Error fetching cached files, syncing from block" , deployedBlockNumber ) ;
2021-12-07 11:57:18 -05:00
return {
events : [ ] ,
lastBlock : deployedBlockNumber ,
}
2021-07-16 06:27:08 -04:00
}
}
2022-02-26 17:32:35 -05:00
async function fetchEvents ( { type , currency , amount } ) {
2022-02-26 17:32:30 -05:00
if ( type === "withdraw" ) {
type = "withdrawal" ;
}
2021-12-07 11:57:18 -05:00
2022-02-26 17:32:30 -05:00
const cachedEvents = loadCachedEvents ( { type , currency , amount } ) ;
const startBlock = cachedEvents . lastBlock + 1 ;
2021-12-07 11:57:18 -05:00
2022-02-26 17:32:30 -05:00
console . log ( "Loaded cached" , amount , currency . toUpperCase ( ) , type , "events for" , startBlock , "block" ) ;
console . log ( "Fetching" , amount , currency . toUpperCase ( ) , type , "events for" , netName , "network" ) ;
2021-12-07 11:57:18 -05:00
2022-02-26 17:32:30 -05:00
async function syncEvents ( ) {
try {
let targetBlock = await web3 . eth . getBlockNumber ( ) ;
let chunks = 1000 ;
2022-02-26 17:32:35 -05:00
console . log ( "Querying latest events from RPC" ) ;
2022-02-26 17:32:30 -05:00
for ( let i = startBlock ; i < targetBlock ; i += chunks ) {
let fetchedEvents = [ ] ;
function mapDepositEvents ( ) {
fetchedEvents = fetchedEvents . map ( ( { blockNumber , transactionHash , returnValues } ) => {
const { commitment , leafIndex , timestamp } = returnValues ;
return {
blockNumber ,
transactionHash ,
commitment ,
leafIndex : Number ( leafIndex ) ,
timestamp
2022-02-04 20:33:25 -05:00
}
2022-02-26 17:32:30 -05:00
} ) ;
}
2022-01-21 23:49:45 -05:00
2022-02-26 17:32:30 -05:00
function mapWithdrawEvents ( ) {
fetchedEvents = fetchedEvents . map ( ( { blockNumber , transactionHash , returnValues } ) => {
const { nullifierHash , to , fee } = returnValues ;
return {
blockNumber ,
transactionHash ,
nullifierHash ,
to ,
fee
}
} ) ;
}
function mapLatestEvents ( ) {
if ( type === "deposit" ) {
mapDepositEvents ( ) ;
} else {
mapWithdrawEvents ( ) ;
2022-01-21 23:49:45 -05:00
}
2022-02-26 17:32:30 -05:00
}
2022-01-21 23:49:45 -05:00
2022-02-26 17:32:35 -05:00
async function fetchWeb3Events ( i ) {
2022-02-26 17:32:30 -05:00
let j ;
if ( i + chunks - 1 > targetBlock ) {
j = targetBlock ;
} else {
j = i + chunks - 1 ;
2022-01-21 23:49:45 -05:00
}
2022-02-26 17:32:30 -05:00
await tornadoContract . getPastEvents ( capitalizeFirstLetter ( type ) , {
fromBlock : i ,
toBlock : j ,
2022-02-26 17:32:35 -05:00
} ) . then ( r => { fetchedEvents = fetchedEvents . concat ( r ) ; console . log ( "Fetched" , amount , currency . toUpperCase ( ) , type , "events to block:" , j ) } , err => { console . error ( i + " failed fetching" , type , "events from node" , err ) ; process . exit ( 1 ) ; } ) . catch ( console . log ) ;
2022-02-26 17:32:30 -05:00
if ( type === "deposit" ) {
mapDepositEvents ( ) ;
} else {
mapWithdrawEvents ( ) ;
2022-01-21 23:49:45 -05:00
}
2022-02-26 17:32:30 -05:00
}
2022-01-21 23:49:45 -05:00
2022-02-26 17:32:30 -05:00
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 ) ;
2022-01-21 23:49:45 -05:00
}
2021-12-07 11:57:18 -05:00
}
2022-02-26 17:32:35 -05:00
await fetchWeb3Events ( i ) ;
2022-02-26 17:32:30 -05:00
await updateCache ( ) ;
2021-12-07 11:57:18 -05:00
}
2022-02-26 17:32:30 -05:00
} catch ( error ) {
throw new Error ( "Error while updating cache" ) ;
process . exit ( 1 ) ;
2021-12-07 11:57:18 -05:00
}
2022-02-26 17:32:30 -05:00
}
2022-02-26 17:32:35 -05:00
async function syncGraphEvents ( ) {
let options = { } ;
if ( torPort ) {
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' } } ;
}
async function queryLatestTimestamp ( ) {
try {
const variables = {
currency : currency . toString ( ) ,
amount : amount . toString ( )
}
if ( type === "deposit" ) {
const query = {
query : `
query ( $currency : String , $amount : String ) {
deposits ( first : 1 , orderBy : timestamp , orderDirection : desc , where : { currency : $currency , amount : $amount } ) {
timestamp
}
}
` ,
variables
}
const querySubgraph = await axios . post ( subgraph , query , options ) ;
const queryResult = querySubgraph . data . data . deposits ;
const result = queryResult [ 0 ] . timestamp ;
return Number ( result ) ;
} else {
const query = {
query : `
query ( $currency : String , $amount : String ) {
withdrawals ( first : 1 , orderBy : timestamp , orderDirection : desc , where : { currency : $currency , amount : $amount } ) {
timestamp
}
}
` ,
variables
}
const querySubgraph = await axios . post ( subgraph , query , options ) ;
const queryResult = querySubgraph . data . data . withdrawals ;
const result = queryResult [ 0 ] . timestamp ;
return Number ( result ) ;
}
} catch ( error ) {
console . error ( "Failed to fetch latest event from thegraph" ) ;
}
}
async function queryFromGraph ( timestamp ) {
try {
const variables = {
currency : currency . toString ( ) ,
amount : amount . toString ( ) ,
timestamp : timestamp
}
if ( type === "deposit" ) {
const query = {
query : `
query ( $currency : String , $amount : String , $timestamp : Int ) {
deposits ( orderBy : timestamp , first : 1000 , where : { currency : $currency , amount : $amount , timestamp _gt : $timestamp } ) {
blockNumber
transactionHash
commitment
index
timestamp
}
}
` ,
variables
}
const querySubgraph = await axios . post ( subgraph , query , options ) ;
const queryResult = querySubgraph . data . data . deposits ;
const mapResult = queryResult . map ( ( { blockNumber , transactionHash , commitment , index , timestamp } ) => {
return {
blockNumber : Number ( blockNumber ) ,
transactionHash ,
commitment ,
leafIndex : Number ( index ) ,
timestamp
}
} ) ;
return mapResult ;
} else {
const query = {
query : `
query ( $currency : String , $amount : String , $timestamp : Int ) {
withdrawals ( orderBy : timestamp , first : 1000 , where : { currency : $currency , amount : $amount , timestamp _gt : $timestamp } ) {
blockNumber
transactionHash
nullifier
to
fee
}
}
` ,
variables
}
const querySubgraph = await axios . post ( subgraph , query , options ) ;
const queryResult = querySubgraph . data . data . withdrawals ;
const mapResult = queryResult . map ( ( { blockNumber , transactionHash , nullifier , to , fee } ) => {
return {
blockNumber : Number ( blockNumber ) ,
transactionHash ,
nullifierHash : nullifier ,
to ,
fee
}
} ) ;
return mapResult ;
}
} catch ( error ) {
console . error ( error ) ;
}
}
async function updateCache ( fetchedEvents ) {
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 ) ;
}
}
async function fetchGraphEvents ( ) {
console . log ( "Querying latest events from TheGraph" ) ;
const latestTimestamp = await queryLatestTimestamp ( ) ;
if ( latestTimestamp ) {
const getCachedBlock = await web3 . eth . getBlock ( startBlock ) ;
const cachedTimestamp = getCachedBlock . timestamp ;
for ( let i = cachedTimestamp ; i < latestTimestamp ; ) {
const result = await queryFromGraph ( i ) ;
if ( Object . keys ( result ) . length === 0 ) {
i = latestTimestamp ;
} else {
const resultBlock = result [ result . length - 1 ] . blockNumber ;
const getResultBlock = await web3 . eth . getBlock ( resultBlock ) ;
const resultTimestamp = getResultBlock . timestamp ;
await updateCache ( result ) ;
i = resultTimestamp ;
console . log ( "Fetched" , amount , currency . toUpperCase ( ) , type , "events to block:" , Number ( resultBlock ) ) ;
}
}
} else {
console . log ( "Fallback to web3 events" ) ;
await syncEvents ( ) ;
}
}
await fetchGraphEvents ( ) ;
}
if ( ! privateRpc || ! subgraph || ! isTestRPC ) {
await syncGraphEvents ( ) ;
} else {
await syncEvents ( ) ;
}
2022-02-26 17:32:30 -05:00
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 ${ type } s: ` , updatedEvents . length ) ;
return updatedEvents ;
}
const events = await loadUpdatedEvents ( ) ;
return events ;
2021-12-07 11:57:18 -05:00
}
2020-05-21 15:29:33 -04:00
/ * *
* Parses Tornado . cash note
* @ param noteString the note
* /
function parseNote ( noteString ) {
2020-05-22 05:35:00 -04:00
const noteRegex = /tornado-(?<currency>\w+)-(?<amount>[\d.]+)-(?<netId>\d+)-0x(?<note>[0-9a-fA-F]{124})/g
2022-02-26 17:32:30 -05:00
const match = noteRegex . exec ( noteString ) ;
2020-05-22 05:35:00 -04:00
if ( ! match ) {
2022-02-26 17:32:30 -05:00
throw new Error ( 'The note has invalid format' ) ;
2020-05-22 05:35:00 -04:00
}
2022-02-26 17:32:30 -05:00
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 ) ;
2020-05-22 05:35:00 -04:00
2021-02-14 13:23:17 -05:00
return {
currency : match . groups . currency ,
amount : match . groups . amount ,
netId ,
deposit
}
2020-05-21 15:29:33 -04:00
}
2021-12-07 11:57:18 -05:00
async function loadDepositData ( { amount , currency , deposit } ) {
2020-05-22 05:35:00 -04:00
try {
2022-02-26 17:32:30 -05:00
const cachedEvents = await fetchEvents ( { type : 'deposit' , currency , amount } ) ;
2021-12-07 11:57:18 -05:00
const eventWhenHappened = await cachedEvents . filter ( function ( event ) {
return event . commitment === deposit . commitmentHex ;
2022-02-26 17:32:30 -05:00
} ) [ 0 ] ;
2021-12-07 11:57:18 -05:00
2020-05-22 05:35:00 -04:00
if ( eventWhenHappened . length === 0 ) {
2022-02-26 17:32:30 -05:00
throw new Error ( 'There is no related deposit, the note is invalid' ) ;
2020-05-22 05:35:00 -04:00
}
2020-05-21 15:29:33 -04:00
2022-02-26 17:32:30 -05:00
const timestamp = eventWhenHappened . timestamp ;
const txHash = eventWhenHappened . transactionHash ;
const isSpent = await tornadoContract . methods . isSpent ( deposit . nullifierHex ) . call ( ) ;
const receipt = await web3 . eth . getTransactionReceipt ( txHash ) ;
2020-05-21 15:29:33 -04:00
2021-02-14 13:23:17 -05:00
return {
timestamp ,
txHash ,
isSpent ,
from : receipt . from ,
commitment : deposit . commitmentHex
}
2020-05-22 05:35:00 -04:00
} catch ( e ) {
2022-02-26 17:32:30 -05:00
console . error ( 'loadDepositData' , e ) ;
2020-05-22 05:35:00 -04:00
}
return { }
2020-05-21 15:29:33 -04:00
}
async function loadWithdrawalData ( { amount , currency , deposit } ) {
2020-05-22 05:35:00 -04:00
try {
2022-02-26 17:32:30 -05:00
const cachedEvents = await fetchEvents ( { type : 'withdrawal' , currency , amount } ) ;
2021-07-16 06:27:08 -04:00
2021-12-07 11:57:18 -05:00
const withdrawEvent = cachedEvents . filter ( ( event ) => {
2021-07-16 06:27:08 -04:00
return event . nullifierHash === deposit . nullifierHex
2022-02-26 17:32:30 -05:00
} ) [ 0 ] ;
2020-05-22 05:35:00 -04:00
2022-02-26 17:32:30 -05:00
const fee = withdrawEvent . fee ;
const decimals = config . deployments [ ` netId ${ netId } ` ] [ currency ] . decimals ;
const withdrawalAmount = toBN ( fromDecimals ( { amount , decimals } ) ) . sub ( toBN ( fee ) ) ;
const { timestamp } = await web3 . eth . getBlock ( withdrawEvent . blockNumber ) ;
2020-05-22 05:35:00 -04:00
return {
amount : toDecimals ( withdrawalAmount , decimals , 9 ) ,
txHash : withdrawEvent . transactionHash ,
2021-07-16 06:27:08 -04:00
to : withdrawEvent . to ,
2020-05-22 05:35:00 -04:00
timestamp ,
nullifier : deposit . nullifierHex ,
fee : toDecimals ( fee , decimals , 9 )
2020-05-21 15:29:33 -04:00
}
2020-05-22 05:35:00 -04:00
} catch ( e ) {
2022-02-26 17:32:30 -05:00
console . error ( 'loadWithdrawalData' , e ) ;
2020-05-22 05:35:00 -04:00
}
2020-05-21 15:29:33 -04:00
}
/ * *
* Init web3 , contracts , and snark
* /
2022-02-26 17:32:35 -05:00
async function init ( { rpc , noteNetId , currency = 'dai' , amount = '100' , balanceCheck , localMode } ) {
2022-02-26 17:32:33 -05:00
let contractJson , instanceJson , erc20ContractJson , erc20tornadoJson , tornadoAddress , tokenAddress ;
2020-05-22 05:35:00 -04: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 13:23:17 -05:00
web3 = new Web3 ( window . web3 . currentProvider , null , {
transactionConfirmationBlocks : 1
2022-02-26 17:32:30 -05:00
} ) ;
contractJson = await ( await fetch ( 'build/contracts/TornadoProxy.abi.json' ) ) . json ( ) ;
instanceJson = await ( await fetch ( 'build/contracts/Instance.abi.json' ) ) . json ( ) ;
circuit = await ( await fetch ( 'build/circuits/tornado.json' ) ) . json ( ) ;
proving _key = await ( await fetch ( 'build/circuits/tornadoProvingKey.bin' ) ) . arrayBuffer ( ) ;
MERKLE _TREE _HEIGHT = 20 ;
ETH _AMOUNT = 1e18 ;
TOKEN _AMOUNT = 1e19 ;
senderAccount = ( await web3 . eth . getAccounts ( ) ) [ 0 ] ;
2020-05-22 05:35:00 -04:00
} else {
2022-01-24 06:39:40 -05:00
let ipOptions = { } ;
2022-02-03 20:12:21 -05:00
if ( torPort && rpc . includes ( "https" ) ) {
2022-02-26 17:32:30 -05:00
console . log ( "Using tor network" ) ;
web3Options = { agent : { https : new SocksProxyAgent ( 'socks5h://127.0.0.1:' + torPort ) } , timeout : 60000 } ;
2021-12-07 11:57:18 -05:00
// Use forked web3-providers-http from local file to modify user-agent header value which improves privacy.
2022-02-26 17:32:30 -05:00
web3 = new Web3 ( new Web3HttpProvider ( rpc , web3Options ) , null , { transactionConfirmationBlocks : 1 } ) ;
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' } } ;
2022-02-03 20:12:21 -05:00
} else if ( torPort && rpc . includes ( "http" ) ) {
2022-02-26 17:32:30 -05:00
console . log ( "Using tor network" ) ;
web3Options = { agent : { http : new SocksProxyAgent ( 'socks5h://127.0.0.1:' + torPort ) } , timeout : 60000 } ;
2022-02-03 20:12:21 -05:00
// Use forked web3-providers-http from local file to modify user-agent header value which improves privacy.
2022-02-26 17:32:30 -05:00
web3 = new Web3 ( new Web3HttpProvider ( rpc , web3Options ) , null , { transactionConfirmationBlocks : 1 } ) ;
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 11:57:18 -05:00
} else if ( rpc . includes ( "ipc" ) ) {
2022-02-26 17:32:30 -05:00
console . log ( "Using ipc connection" ) ;
web3 = new Web3 ( new Web3 . providers . IpcProvider ( rpc , net ) , null , { transactionConfirmationBlocks : 1 } ) ;
2021-12-07 11:57:18 -05:00
} else if ( rpc . includes ( "ws" ) || rpc . includes ( "wss" ) ) {
2022-02-26 17:32:30 -05:00
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 } ) ;
2021-12-07 11:57:18 -05:00
} else {
2022-02-26 17:32:30 -05:00
console . log ( "Connecting to remote node" ) ;
web3 = new Web3 ( rpc , null , { transactionConfirmationBlocks : 1 } ) ;
2021-12-07 11:57:18 -05:00
}
2022-02-26 17:32:33 -05:00
const rpcHost = new URL ( rpc ) . hostname ;
const isIpPrivate = is _ip _private ( rpcHost ) ;
if ( ! isIpPrivate && ! rpc . includes ( "localhost" ) ) {
try {
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 + '.' ) ;
} catch ( error ) {
console . error ( 'Could not fetch remote IP from ip.tornado.cash, use VPN if the problem repeats.' ) ;
}
} else if ( isIpPrivate || rpc . includes ( "localhost" ) ) {
console . log ( 'Local RPC detected' ) ;
privateRpc = true ;
2022-02-05 17:47:57 -05:00
}
2022-02-26 17:32:30 -05:00
contractJson = require ( './build/contracts/TornadoProxy.abi.json' ) ;
instanceJson = require ( './build/contracts/Instance.abi.json' ) ;
circuit = require ( './build/circuits/tornado.json' ) ;
proving _key = fs . readFileSync ( 'build/circuits/tornadoProvingKey.bin' ) . buffer ;
MERKLE _TREE _HEIGHT = process . env . MERKLE _TREE _HEIGHT || 20 ;
ETH _AMOUNT = process . env . ETH _AMOUNT ;
TOKEN _AMOUNT = process . env . TOKEN _AMOUNT ;
const privKey = process . env . PRIVATE _KEY ;
2022-01-24 06:39:40 -05:00
if ( privKey ) {
if ( privKey . includes ( "0x" ) ) {
2022-02-26 17:32:30 -05:00
PRIVATE _KEY = process . env . PRIVATE _KEY . substring ( 2 ) ;
2022-01-24 06:39:40 -05:00
} else {
2022-02-26 17:32:30 -05:00
PRIVATE _KEY = process . env . PRIVATE _KEY ;
2022-01-24 06:39:40 -05:00
}
2022-01-23 01:52:59 -05:00
}
2020-05-22 05:35:00 -04:00
if ( PRIVATE _KEY ) {
2022-02-26 17:32:30 -05:00
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 15:29:33 -04:00
}
2022-02-26 17:32:30 -05:00
erc20ContractJson = require ( './build/contracts/ERC20Mock.json' ) ;
erc20tornadoJson = require ( './build/contracts/ERC20Tornado.json' ) ;
2020-05-22 05:35:00 -04:00
}
// groth16 initialises a lot of Promises that will never be resolved, that's why we need to use process.exit to terminate the CLI
2022-02-26 17:32:30 -05:00
groth16 = await buildGroth16 ( ) ;
netId = await web3 . eth . net . getId ( ) ;
netName = getCurrentNetworkName ( ) ;
netSymbol = getCurrentNetworkSymbol ( ) ;
2020-05-22 05:35:00 -04:00
if ( noteNetId && Number ( noteNetId ) !== netId ) {
2022-02-26 17:32:30 -05:00
throw new Error ( 'This note is for a different network. Specify the --rpc option explicitly' ) ;
2020-05-22 05:35:00 -04:00
}
2022-02-03 21:28:11 -05:00
if ( netName === "testRPC" ) {
isTestRPC = true ;
}
if ( localMode ) {
2022-02-26 17:32:30 -05:00
console . log ( "Local mode detected: will not submit signed TX to remote node" ) ;
2022-02-26 17:32:33 -05:00
doNotSubmitTx = true ;
2021-12-07 11:57:18 -05:00
}
2020-05-22 05:35:00 -04:00
2022-02-03 21:28:11 -05:00
if ( isTestRPC ) {
2022-02-26 17:32:30 -05:00
tornadoAddress = currency === netSymbol . toLowerCase ( ) ? contractJson . networks [ netId ] . address : erc20tornadoJson . networks [ netId ] . address ;
tokenAddress = currency !== netSymbol . toLowerCase ( ) ? erc20ContractJson . networks [ netId ] . address : null ;
deployedBlockNumber = 0 ;
senderAccount = ( await web3 . eth . getAccounts ( ) ) [ 0 ] ;
2020-05-22 05:35:00 -04:00
} else {
try {
2021-12-07 11:57:18 -05:00
if ( balanceCheck ) {
2022-02-26 17:32:30 -05:00
currency = netSymbol . toLowerCase ( ) ;
amount = Object . keys ( config . deployments [ ` netId ${ netId } ` ] [ currency ] . instanceAddress ) [ 0 ] ;
2021-12-07 11:57:18 -05:00
}
2022-02-26 17:32:30 -05:00
tornadoAddress = config . deployments [ ` netId ${ netId } ` ] . proxy ;
multiCall = config . deployments [ ` netId ${ netId } ` ] . multicall ;
2022-02-26 17:32:35 -05:00
subgraph = config . deployments [ ` netId ${ netId } ` ] . subgraph ;
2022-02-26 17:32:30 -05:00
tornadoInstance = config . deployments [ ` netId ${ netId } ` ] [ currency ] . instanceAddress [ amount ] ;
deployedBlockNumber = config . deployments [ ` netId ${ netId } ` ] [ currency ] . deployedBlockNumber [ amount ] ;
2020-12-24 06:39:20 -05:00
2020-05-22 05:35:00 -04:00
if ( ! tornadoAddress ) {
2022-02-26 17:32:30 -05:00
throw new Error ( ) ;
2020-05-22 05:35:00 -04:00
}
2022-02-26 17:32:30 -05:00
tokenAddress = currency !== netSymbol . toLowerCase ( ) ? config . deployments [ ` netId ${ netId } ` ] [ currency ] . tokenAddress : null ;
2020-05-22 05:35:00 -04:00
} catch ( e ) {
2022-02-26 17:32:30 -05:00
console . error ( 'There is no such tornado instance, check the currency and amount you provide' , e ) ;
process . exit ( 1 ) ;
2020-05-21 15:29:33 -04:00
}
2020-05-22 05:35:00 -04:00
}
2022-02-26 17:32:30 -05:00
tornado = new web3 . eth . Contract ( contractJson , tornadoAddress ) ;
tornadoContract = new web3 . eth . Contract ( instanceJson , tornadoInstance ) ;
contractAddress = tornadoAddress ;
erc20 = currency !== netSymbol . toLowerCase ( ) ? new web3 . eth . Contract ( erc20ContractJson . abi , tokenAddress ) : { } ;
erc20Address = tokenAddress ;
2020-05-21 15:29:33 -04:00
}
async function main ( ) {
2020-05-22 05:35:00 -04:00
if ( inBrowser ) {
2022-02-26 17:32:30 -05:00
const instance = { currency : 'eth' , amount : '0.1' } ;
await init ( instance ) ;
2020-05-22 05:35:00 -04:00
window . deposit = async ( ) => {
2022-02-26 17:32:30 -05:00
await deposit ( instance ) ;
2020-05-22 05:35:00 -04:00
}
window . withdraw = async ( ) => {
2022-02-26 17:32:30 -05:00
const noteString = prompt ( 'Enter the note to withdraw' ) ;
const recipient = ( await web3 . eth . getAccounts ( ) ) [ 0 ] ;
2020-05-21 15:29:33 -04:00
2022-02-26 17:32:30 -05:00
const { currency , amount , netId , deposit } = parseNote ( noteString ) ;
await init ( { noteNetId : netId , currency , amount } ) ;
await withdraw ( { deposit , currency , amount , recipient } ) ;
2020-05-22 05:35:00 -04:00
}
} else {
program
2021-12-07 11:57:18 -05:00
. option ( '-r, --rpc <URL>' , 'The RPC that CLI should interact with' , 'http://localhost:8545' )
2020-05-22 05:35:00 -04:00
. option ( '-R, --relayer <URL>' , 'Withdraw via relayer' )
2021-12-07 11:57:18 -05:00
. option ( '-T, --tor <PORT>' , 'Optional tor port' )
2022-02-26 17:32:30 -05:00
. option ( '-L, --local' , 'Local Node - Does not submit signed transaction to the node' ) ;
2020-05-22 05:35:00 -04:00
program
. command ( 'deposit <currency> <amount>' )
2021-02-14 13:23:17 -05: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 05:35:00 -04:00
. action ( async ( currency , amount ) => {
2022-02-26 17:32:30 -05:00
currency = currency . toLowerCase ( ) ;
2022-02-26 17:32:35 -05:00
torPort = program . tor ;
await init ( { rpc : program . rpc , currency , amount , localMode : program . local } ) ;
2022-02-26 17:32:30 -05:00
await deposit ( { currency , amount } ) ;
} ) ;
2020-05-22 05:35:00 -04:00
program
. command ( 'withdraw <note> <recipient> [ETH_purchase]' )
2021-02-14 13:23:17 -05: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 05:35:00 -04:00
. action ( async ( noteString , recipient , refund ) => {
2022-02-26 17:32:30 -05:00
const { currency , amount , netId , deposit } = parseNote ( noteString ) ;
2022-02-26 17:32:35 -05:00
torPort = program . tor ;
await init ( { rpc : program . rpc , noteNetId : netId , currency , amount , localMode : program . local } ) ;
2021-02-14 13:23:17 -05:00
await withdraw ( {
deposit ,
currency ,
amount ,
recipient ,
refund ,
2022-02-26 17:32:35 -05:00
relayerURL : program . relayer
2022-02-26 17:32:30 -05:00
} ) ;
} ) ;
2020-05-22 05:35:00 -04:00
program
2022-01-24 07:50:58 -05:00
. command ( 'balance [address] [token_address]' )
2020-05-22 05:35:00 -04:00
. description ( 'Check ETH and ERC20 balance' )
. action ( async ( address , tokenAddress ) => {
2022-02-26 17:32:35 -05:00
torPort = program . tor ;
await init ( { rpc : program . rpc , balanceCheck : true } ) ;
2022-01-24 07:50:58 -05:00
if ( ! address && senderAccount ) {
2022-02-26 17:32:30 -05:00
console . log ( "Using address" , senderAccount , "from private key" ) ;
2022-01-24 07:50:58 -05:00
address = senderAccount ;
}
2022-02-26 17:32:30 -05:00
await printETHBalance ( { address , name : 'Account' } ) ;
2020-05-22 05:35:00 -04:00
if ( tokenAddress ) {
2022-02-26 17:32:30 -05:00
await printERC20Balance ( { address , name : 'Account' , tokenAddress } ) ;
2020-05-21 15:29:33 -04:00
}
2022-02-26 17:32:30 -05:00
} ) ;
2022-01-23 07:03:46 -05:00
program
. command ( 'send <address> [amount] [token_address]' )
. description ( 'Send ETH or ERC to address' )
. action ( async ( address , amount , tokenAddress ) => {
2022-02-26 17:32:35 -05:00
torPort = program . tor ;
await init ( { rpc : program . rpc , balanceCheck : true , localMode : program . local } ) ;
2022-02-26 17:32:30 -05:00
await send ( { address , amount , tokenAddress } ) ;
} ) ;
2022-02-03 21:28:11 -05:00
program
. command ( 'broadcast <signedTX>' )
. description ( 'Submit signed TX to the remote node' )
. action ( async ( signedTX ) => {
2022-02-26 17:32:35 -05:00
torPort = program . tor ;
await init ( { rpc : program . rpc , balanceCheck : true } ) ;
2022-02-26 17:32:30 -05:00
await submitTransaction ( signedTX ) ;
} ) ;
2020-05-22 05:35:00 -04:00
program
. command ( 'compliance <note>' )
2021-02-14 13:23:17 -05: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 05:35:00 -04:00
. action ( async ( noteString ) => {
2022-02-26 17:32:30 -05:00
const { currency , amount , netId , deposit } = parseNote ( noteString ) ;
2022-02-26 17:32:35 -05:00
torPort = program . tor ;
await init ( { rpc : program . rpc , noteNetId : netId , currency , amount } ) ;
2022-02-26 17:32:30 -05:00
const depositInfo = await loadDepositData ( { amount , currency , deposit } ) ;
const depositDate = new Date ( depositInfo . timestamp * 1000 ) ;
console . log ( '\n=============Deposit=================' ) ;
console . log ( 'Deposit :' , amount , currency . toUpperCase ( ) ) ;
console . log ( 'Date :' , depositDate . toLocaleDateString ( ) , depositDate . toLocaleTimeString ( ) ) ;
console . log ( 'From :' , ` https:// ${ getExplorerLink ( ) } /address/ ${ depositInfo . from } ` ) ;
console . log ( 'Transaction :' , ` https:// ${ getExplorerLink ( ) } /tx/ ${ depositInfo . txHash } ` ) ;
console . log ( 'Commitment :' , depositInfo . commitment ) ;
console . log ( 'Spent :' , depositInfo . isSpent ) ;
2021-12-07 11:57:18 -05:00
if ( ! depositInfo . isSpent ) {
2022-02-26 17:32:30 -05:00
console . log ( 'The note was not spent' ) ;
return ;
2020-05-21 15:29:33 -04:00
}
2022-02-26 17:32:30 -05:00
console . log ( '=====================================' , '\n' ) ;
const withdrawInfo = await loadWithdrawalData ( { amount , currency , deposit } ) ;
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 ( ) ) ;
console . log ( 'To :' , ` https:// ${ getExplorerLink ( ) } /address/ ${ withdrawInfo . to } ` ) ;
console . log ( 'Transaction :' , ` https:// ${ getExplorerLink ( ) } /tx/ ${ withdrawInfo . txHash } ` ) ;
console . log ( 'Nullifier :' , withdrawInfo . nullifier ) ;
console . log ( '=====================================' , '\n' ) ;
} ) ;
2021-12-07 11:57:18 -05:00
program
. command ( 'syncEvents <type> <currency> <amount>' )
. description (
'Sync the local cache file of deposit / withdrawal events for specific currency.'
)
. action ( async ( type , currency , amount ) => {
2022-02-26 17:32:30 -05:00
console . log ( "Starting event sync command" ) ;
currency = currency . toLowerCase ( ) ;
2022-02-26 17:32:35 -05:00
torPort = program . tor ;
await init ( { rpc : program . rpc , type , currency , amount } ) ;
2022-02-26 17:32:30 -05:00
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 05:35:00 -04:00
program
. command ( 'test' )
. description ( 'Perform an automated test. It deposits and withdraws one ETH and one ERC20 note. Uses ganache.' )
. action ( async ( ) => {
2022-02-26 17:32:30 -05:00
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 13:23:17 -05:00
await withdraw ( {
deposit : parsedNote . deposit ,
currency ,
amount ,
recipient : senderAccount ,
relayerURL : program . relayer
2022-02-26 17:32:30 -05: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 } ) ;
parsedNote = parseNote ( noteString ) ;
2021-02-14 13:23:17 -05:00
await withdraw ( {
deposit : parsedNote . deposit ,
currency ,
amount ,
recipient : senderAccount ,
refund : '0.02' ,
relayerURL : program . relayer
2022-02-26 17:32:30 -05:00
} ) ;
} ) ;
2020-05-22 05:35:00 -04:00
try {
2022-02-26 17:32:30 -05:00
await program . parseAsync ( process . argv ) ;
process . exit ( 0 ) ;
2020-05-22 05:35:00 -04:00
} catch ( e ) {
2022-02-26 17:32:30 -05:00
console . log ( 'Error:' , e ) ;
process . exit ( 1 ) ;
2020-05-21 15:29:33 -04:00
}
2020-05-22 05:35:00 -04:00
}
2020-05-21 15:29:33 -04:00
}
2022-02-26 17:32:30 -05:00
main ( ) ;