2020-05-22 12:37:21 +03:00
#!/usr/bin/env node
2020-05-21 22:29:33 +03:00
2022-02-27 01:32:30 +03:00
require ( 'dotenv' ) . config ( ) ;
const fs = require ( 'fs' ) ;
const axios = require ( 'axios' ) ;
const assert = require ( 'assert' ) ;
2023-05-17 13:36:43 +03:00
const snarkjs = require ( '@tornado/snarkjs' ) ;
2022-02-27 01:32:30 +03:00
const crypto = require ( 'crypto' ) ;
2023-05-17 13:36:43 +03:00
const circomlib = require ( '@tornado/circomlib' ) ;
2022-02-27 01:32:30 +03:00
const bigInt = snarkjs . bigInt ;
2023-05-17 13:36:43 +03:00
const merkleTree = require ( '@tornado/fixed-merkle-tree' ) ;
2022-02-27 01:32:30 +03:00
const Web3 = require ( 'web3' ) ;
2023-05-17 13:36:43 +03:00
const Web3HttpProvider = require ( '@tornado/web3-providers-http' ) ;
const buildGroth16 = require ( '@tornado/websnark/src/groth16' ) ;
const websnarkUtils = require ( '@tornado/websnark/src/utils' ) ;
2022-02-27 01:32:30 +03:00
const { toWei , fromWei , toBN , BN } = require ( 'web3-utils' ) ;
2022-01-27 15:35:55 +03:00
const BigNumber = require ( 'bignumber.js' ) ;
2022-02-27 01:32:30 +03:00
const config = require ( './config' ) ;
const program = require ( 'commander' ) ;
2023-05-17 13:36:43 +03:00
const { GasPriceOracle } = require ( '@tornado/gas-price-oracle' ) ;
2022-12-01 14:54:18 +03:00
const { SocksProxyAgent } = require ( 'socks-proxy-agent' ) ;
2022-02-27 01:32:33 +03:00
const is _ip _private = require ( 'private-ip' ) ;
2023-03-26 12:20:32 +03:00
const readline = require ( 'readline' ) ;
2020-05-21 22:29:33 +03:00
2023-03-26 12:20:32 +03:00
const prompt = readline . createInterface ( { input : process . stdin , output : process . stdout } ) ;
const gasSpeedPreferences = [ 'instant' , 'fast' , 'standard' , 'low' ] ;
2023-04-14 20:02:05 +03:00
let web3 ,
torPort ,
tornado ,
tornadoContract ,
tornadoInstance ,
circuit ,
proving _key ,
groth16 ,
erc20 ,
senderAccount ,
netId ,
netName ,
netSymbol ,
multiCall ,
subgraph ;
2022-02-27 01:32:30 +03:00
let MERKLE _TREE _HEIGHT , ETH _AMOUNT , TOKEN _AMOUNT , PRIVATE _KEY ;
2020-05-21 22:29:33 +03:00
2023-03-26 12:20:32 +03:00
/** Command state parameters */
let preferenceSpeed = gasSpeedPreferences [ 0 ] ;
2023-04-14 20:02:05 +03:00
let isTestRPC ,
eipGasSupport = false ;
2023-03-26 12:20:32 +03:00
let shouldPromptConfirmation = true ;
let doNotSubmitTx , privateRpc ;
/** ----------------------------------------- **/
2020-05-21 22:29:33 +03:00
/** Generate random number of specified byte length */
2022-02-27 01:32:30 +03:00
const rbigint = ( nbytes ) => snarkjs . bigInt . leBuff2int ( crypto . randomBytes ( nbytes ) ) ;
2020-05-21 22:29:33 +03:00
/** Compute pedersen hash */
2022-02-27 01:32:30 +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 ) {
2022-02-27 01:32:30 +03: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 ) {
2023-04-14 20:02:05 +03:00
return new BigNumber ( bigNum )
. times ( BigNumber ( 10 ) . pow ( decimals ) )
. integerValue ( BigNumber . ROUND _DOWN )
. div ( BigNumber ( 10 ) . pow ( decimals ) )
. toNumber ( ) ;
2022-02-27 01:32:30 +03:00
}
/** 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 22:29:33 +03:00
}
/** Display ETH account balance */
2022-01-27 08:50:49 +03:00
async function printETHBalance ( { address , name } ) {
2022-02-27 01:32:30 +03: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 22:29:33 +03:00
}
/** Display ERC20 account balance */
async function printERC20Balance ( { address , name , tokenAddress } ) {
2022-02-27 01:32:30 +03:00
let tokenDecimals , tokenBalance , tokenName , tokenSymbol ;
const erc20ContractJson = require ( './build/contracts/ERC20Mock.json' ) ;
erc20 = tokenAddress ? new web3 . eth . Contract ( erc20ContractJson . abi , tokenAddress ) : erc20 ;
2022-02-27 01:32:39 +03:00
if ( ! isTestRPC && ! multiCall ) {
2023-04-14 20:02:05 +03:00
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 ( ) ]
] ) ;
2022-02-27 01:32:30 +03:00
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 19:57:18 +03:00
}
2023-04-22 19:16:28 +03:00
/ * *
* Compute merkle tree and its root from array of cached deposit events
* @ param { Array } depositEvents Array of deposit event objects
* @ returns { Object } treeData
* @ returns { String [ ] } treeData . leaves Commitment hashes converted to decimals
* @ returns { @ link MerkleTree } treeData . tree Builded merkle tree
* @ returns { String } treeData . root Merkle tree root
* /
function computeDepositEventsTree ( depositEvents ) {
const leaves = depositEvents
. sort ( ( a , b ) => a . leafIndex - b . leafIndex ) // Sort events in chronological order
. map ( ( e ) => toBN ( e . commitment ) . toString ( 10 ) ) ; // Leaf = commitment pedersen hash of deposit
console . log ( 'Computing deposit events merkle tree and its root' ) ;
const tree = new merkleTree ( MERKLE _TREE _HEIGHT , leaves ) ;
return { leaves , tree , root : tree . root ( ) } ;
}
/ * *
* Check validity of events merkle tree root via tornado contract
* @ async
* @ param { Array } depositEvents
* @ returns { boolean } True , if root is valid , else false
* @ throws { Error }
* /
async function isRootValid ( depositEvents ) {
const { root } = computeDepositEventsTree ( depositEvents ) ;
const isRootValid = await tornadoContract . methods . isKnownRoot ( toHex ( root ) ) . call ( ) ;
return isRootValid ;
}
2022-02-04 05:28:11 +03:00
async function submitTransaction ( signedTX ) {
2023-04-14 20:02:05 +03:00
console . log ( 'Submitting transaction to the remote node' ) ;
await web3 . eth
. sendSignedTransaction ( signedTX )
2022-02-27 01:32:30 +03: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-04 05:28:11 +03:00
}
2021-12-12 09:19:49 +03:00
async function generateTransaction ( to , encodedData , value = 0 ) {
2022-02-27 01:32:30 +03:00
const nonce = await web3 . eth . getTransactionCount ( senderAccount ) ;
let gasPrice = await fetchGasPrice ( ) ;
2021-12-12 09:19:49 +03:00
let gasLimit ;
async function estimateGas ( ) {
const fetchedGas = await web3 . eth . estimateGas ( {
2023-04-14 20:02:05 +03:00
from : senderAccount ,
to : to ,
value : value ,
nonce : nonce ,
data : encodedData
2022-02-27 01:32:30 +03:00
} ) ;
const bumped = Math . floor ( fetchedGas * 1.3 ) ;
return web3 . utils . toHex ( bumped ) ;
2021-12-12 09:19:49 +03:00
}
2023-03-26 12:20:32 +03:00
2022-01-23 15:03:46 +03:00
if ( encodedData ) {
2022-02-05 15:17:18 +03:00
gasLimit = await estimateGas ( ) ;
2022-01-23 15:03:46 +03:00
} else {
2023-03-26 12:20:32 +03:00
gasLimit = web3 . utils . toHex ( 23000 ) ;
2022-01-23 15:03:46 +03:00
}
2021-12-12 09:19:49 +03:00
2023-04-14 20:02:05 +03:00
const isNumRString = typeof value == 'string' || typeof value == 'number' ;
2023-03-26 12:20:32 +03:00
const valueCost = isNumRString ? toBN ( value ) : value ;
const gasCosts = toBN ( gasPrice ) . mul ( toBN ( gasLimit ) ) ;
const totalCosts = valueCost . add ( gasCosts ) ;
/** Transaction details */
console . log ( 'Gas price: ' , web3 . utils . hexToNumber ( gasPrice ) ) ;
console . log ( 'Gas limit: ' , web3 . utils . hexToNumber ( gasLimit ) ) ;
console . log ( 'Transaction fee: ' , rmDecimalBN ( fromWei ( gasCosts ) , 12 ) , ` ${ netSymbol } ` ) ;
console . log ( 'Transaction cost: ' , rmDecimalBN ( fromWei ( totalCosts ) , 12 ) , ` ${ netSymbol } ` ) ;
/** ----------------------------------------- **/
2022-02-27 01:32:30 +03:00
function txoptions ( ) {
2021-12-07 19:57:18 +03:00
// Generate EIP-1559 transaction
2022-02-05 14:08:17 +03:00
if ( netId == 1 ) {
2022-02-05 15:17:18 +03:00
return {
2023-04-14 20:02:05 +03:00
to : to ,
value : value ,
nonce : nonce ,
maxFeePerGas : gasPrice ,
maxPriorityFeePerGas : web3 . utils . toHex ( web3 . utils . toWei ( '3' , 'gwei' ) ) ,
gas : gasLimit ,
data : encodedData
} ;
2022-02-05 14:08:17 +03:00
} else if ( netId == 5 || netId == 137 || netId == 43114 ) {
2022-02-05 15:17:18 +03:00
return {
2023-04-14 20:02:05 +03:00
to : to ,
value : value ,
nonce : nonce ,
maxFeePerGas : gasPrice ,
maxPriorityFeePerGas : gasPrice ,
gas : gasLimit ,
data : encodedData
} ;
2021-12-07 19:57:18 +03:00
} else {
2022-02-05 15:17:18 +03:00
return {
2023-04-14 20:02:05 +03:00
to : to ,
value : value ,
nonce : nonce ,
gasPrice : gasPrice ,
gas : gasLimit ,
data : encodedData
} ;
2021-12-07 19:57:18 +03:00
}
}
2023-03-26 12:20:32 +03:00
if ( shouldPromptConfirmation ) {
await promptConfirmation ( ) ;
}
2022-02-27 01:32:30 +03:00
const tx = txoptions ( ) ;
2021-12-07 19:57:18 +03:00
const signed = await web3 . eth . accounts . signTransaction ( tx , PRIVATE _KEY ) ;
2023-03-26 12:20:32 +03:00
2022-02-27 01:32:33 +03:00
if ( ! doNotSubmitTx ) {
2022-02-05 15:17:18 +03:00
await submitTransaction ( signed . rawTransaction ) ;
2022-02-04 05:28:11 +03:00
} else {
2022-02-27 01:32:30 +03:00
console . log ( '\n=============Raw TX=================' , '\n' ) ;
2023-04-14 20:02:05 +03:00
console . log (
` Please submit this raw tx to https:// ${ getExplorerLink ( ) } /pushTx, or otherwise broadcast with node cli.js broadcast command. ` ,
` \n `
) ;
2022-02-27 01:32:30 +03:00
console . log ( signed . rawTransaction , ` \n ` ) ;
console . log ( '=====================================' , '\n' ) ;
2022-02-04 05:28:11 +03:00
}
2020-05-21 22:29:33 +03:00
}
/ * *
* Create deposit object from secret and nullifier
* /
function createDeposit ( { nullifier , secret } ) {
2022-02-27 01:32:30 +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' ) ;
2023-04-14 20:02:05 +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 ) {
2022-02-27 01:32:30 +03:00
throw new Error ( 'Writing backup note failed:' , e ) ;
2021-12-07 19:57:18 +03:00
}
}
2022-02-27 01:32:37 +03:00
async function backupInvoice ( { currency , amount , netId , commitmentNote , invoiceString } ) {
try {
2023-04-14 20:02:05 +03:00
await fs . writeFileSync (
` ./backup-tornadoInvoice- ${ currency } - ${ amount } - ${ netId } - ${ commitmentNote . slice ( 0 , 10 ) } .txt ` ,
invoiceString ,
'utf8'
) ;
console . log (
'Backed up invoice as' ,
` ./backup-tornadoInvoice- ${ currency } - ${ amount } - ${ netId } - ${ commitmentNote . slice ( 0 , 10 ) } .txt `
) ;
2022-02-27 01:32:37 +03:00
} catch ( e ) {
2023-04-14 20:02:05 +03:00
throw new Error ( 'Writing backup invoice failed:' , e ) ;
2022-02-27 01:32:37 +03:00
}
}
2020-05-21 22:29:33 +03:00
/ * *
2022-02-27 01:32:37 +03:00
* create a deposit invoice .
2020-05-21 22:29:33 +03:00
* @ param currency С urrency
* @ param amount Deposit amount
* /
2022-02-27 01:32:37 +03:00
async function createInvoice ( { currency , amount , chainId } ) {
2021-02-14 21:23:17 +03:00
const deposit = createDeposit ( {
nullifier : rbigint ( 31 ) ,
secret : rbigint ( 31 )
2022-02-27 01:32:30 +03:00
} ) ;
const note = toHex ( deposit . preimage , 62 ) ;
2022-02-27 01:32:37 +03:00
const noteString = ` tornado- ${ currency } - ${ amount } - ${ chainId } - ${ note } ` ;
2022-02-27 01:32:30 +03:00
console . log ( ` Your note: ${ noteString } ` ) ;
2022-02-27 01:32:37 +03:00
const commitmentNote = toHex ( deposit . commitment ) ;
const invoiceString = ` tornadoInvoice- ${ currency } - ${ amount } - ${ chainId } - ${ commitmentNote } ` ;
console . log ( ` Your invoice for deposit: ${ invoiceString } ` ) ;
await backupNote ( { currency , amount , netId : chainId , note , noteString } ) ;
await backupInvoice ( { currency , amount , netId : chainId , commitmentNote , invoiceString } ) ;
2023-04-14 20:02:05 +03:00
return noteString , invoiceString ;
2022-02-27 01:32:37 +03:00
}
/ * *
* Make a deposit
* @ param currency С urrency
* @ param amount Deposit amount
* /
async function deposit ( { currency , amount , commitmentNote } ) {
assert ( senderAccount != null , 'Error! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you deposit' ) ;
2022-02-27 01:32:39 +03:00
let commitment , noteString ;
2022-02-27 01:32:37 +03:00
if ( ! commitmentNote ) {
2023-04-14 20:02:05 +03:00
console . log ( 'Creating new random deposit note' ) ;
2022-02-27 01:32:37 +03:00
const deposit = createDeposit ( {
nullifier : rbigint ( 31 ) ,
secret : rbigint ( 31 )
} ) ;
const note = toHex ( deposit . preimage , 62 ) ;
2022-02-27 01:32:39 +03:00
noteString = ` tornado- ${ currency } - ${ amount } - ${ netId } - ${ note } ` ;
2022-02-27 01:32:37 +03:00
console . log ( ` Your note: ${ noteString } ` ) ;
await backupNote ( { currency , amount , netId , note , noteString } ) ;
commitment = toHex ( deposit . commitment ) ;
} else {
2023-04-14 20:02:05 +03:00
console . log ( 'Using supplied invoice for deposit' ) ;
2022-02-27 01:32:37 +03:00
commitment = toHex ( commitmentNote ) ;
}
2022-01-23 09:52:59 +03:00
if ( currency === netSymbol . toLowerCase ( ) ) {
2022-02-27 01:32:30 +03: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' ) ;
2022-02-27 01:32:37 +03:00
await generateTransaction ( contractAddress , tornado . methods . deposit ( tornadoInstance , commitment , [ ] ) . encodeABI ( ) , value ) ;
2022-02-27 01:32:30 +03:00
await printETHBalance ( { address : tornadoContract . _address , name : 'Tornado contract' } ) ;
await printETHBalance ( { address : senderAccount , name : 'Sender account' } ) ;
2021-02-14 21:23:17 +03:00
} else {
// a token
2022-02-27 01:32:30 +03:00
await printERC20Balance ( { address : tornadoContract . _address , name : 'Tornado contract' } ) ;
await printERC20Balance ( { address : senderAccount , name : 'Sender account' } ) ;
2023-05-14 00:08:18 +03:00
const decimals = isTestRPC ? 18 : config . deployments [ ` netId ${ netId } ` ] [ 'tokens' ] [ currency ] . decimals ;
2022-02-27 01:32:30 +03:00
const tokenAmount = isTestRPC ? TOKEN _AMOUNT : fromDecimals ( { amount , decimals } ) ;
2022-02-04 05:28:11 +03:00
if ( isTestRPC ) {
2022-02-27 01:32:30 +03:00
console . log ( 'Minting some test tokens to deposit' ) ;
await generateTransaction ( erc20Address , erc20 . methods . mint ( senderAccount , tokenAmount ) . encodeABI ( ) ) ;
2020-05-22 12:35:00 +03:00
}
2020-05-21 22:29:33 +03:00
2022-02-27 01:32:30 +03:00
const allowance = await erc20 . methods . allowance ( senderAccount , tornado . _address ) . call ( { from : senderAccount } ) ;
console . log ( 'Current allowance is' , fromWei ( allowance ) ) ;
2020-05-22 12:35:00 +03:00
if ( toBN ( allowance ) . lt ( toBN ( tokenAmount ) ) ) {
2022-02-27 01:32:30 +03:00
console . log ( 'Approving tokens for deposit' ) ;
await generateTransaction ( erc20Address , erc20 . methods . approve ( tornado . _address , tokenAmount ) . encodeABI ( ) ) ;
2020-05-21 22:29:33 +03:00
}
2022-02-27 01:32:30 +03:00
console . log ( 'Submitting deposit transaction' ) ;
2022-02-27 01:32:37 +03:00
await generateTransaction ( contractAddress , tornado . methods . deposit ( tornadoInstance , commitment , [ ] ) . encodeABI ( ) ) ;
2022-02-27 01:32:30 +03:00
await printERC20Balance ( { address : tornadoContract . _address , name : 'Tornado contract' } ) ;
await printERC20Balance ( { address : senderAccount , name : 'Sender account' } ) ;
2020-05-22 12:35:00 +03:00
}
2023-04-14 20:02:05 +03:00
if ( ! commitmentNote ) {
2022-02-27 01:32:37 +03:00
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-05-22 12:35:00 +03:00
// Get all deposit events from smart contract and assemble merkle tree from them
2022-02-27 01:32:30 +03:00
const cachedEvents = await fetchEvents ( { type : 'deposit' , currency , amount } ) ;
2023-05-09 19:21:49 +03:00
const { tree , leaves , root } = computeDepositEventsTree ( cachedEvents ) ;
2020-12-24 14:39:20 +03:00
2023-05-09 19:21:49 +03:00
// Validate that merkle tree is valid, deposit data is correct and note not spent.
const leafIndex = leaves . findIndex ( ( commitment ) => toBN ( deposit . commitmentHex ) . toString ( 10 ) === commitment ) ;
2022-02-27 01:32:30 +03:00
let isValidRoot , isSpent ;
2022-02-27 01:32:39 +03:00
if ( ! isTestRPC && ! multiCall ) {
2023-04-14 20:02:05 +03:00
const callContract = await useMultiCall ( [
[ tornadoContract . _address , tornadoContract . methods . isKnownRoot ( toHex ( root ) ) . encodeABI ( ) ] ,
[ tornadoContract . _address , tornadoContract . methods . isSpent ( toHex ( deposit . nullifierHash ) ) . encodeABI ( ) ]
] ) ;
2022-02-27 01:32:30 +03:00
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 12:35:00 +03:00
// Compute merkle proof of our commitment
2022-02-27 01:32:30 +03:00
const { pathElements , pathIndices } = tree . path ( leafIndex ) ;
return { root , pathElements , pathIndices } ;
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
2022-02-27 01:32:30 +03:00
const { root , pathElements , pathIndices } = 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 ,
2022-02-27 01:32:24 +03:00
pathElements : pathElements ,
pathIndices : pathIndices
2023-04-14 20:02:05 +03:00
} ;
2020-05-22 12:35:00 +03:00
2022-02-27 01:32:30 +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' ) ;
2020-05-22 12:35:00 +03: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-27 01:32:30 +03:00
] ;
2020-05-22 12:35:00 +03:00
2022-02-27 01:32:30 +03:00
return { proof , args } ;
2020-05-21 22:29:33 +03:00
}
/ * *
* Do an ETH withdrawal
* @ param noteString Note to withdraw
* @ param recipient Recipient address
* /
2022-02-27 01:32:35 +03:00
async function withdraw ( { deposit , currency , amount , recipient , relayerURL , refund = '0' } ) {
2021-12-07 19:57:18 +03:00
let options = { } ;
2022-01-23 09:52:59 +03:00
if ( currency === netSymbol . toLowerCase ( ) && refund !== '0' ) {
2022-02-27 01:32:30 +03:00
throw new Error ( 'The ETH purchase is supposted to be 0 for ETH withdrawals' ) ;
2020-05-22 12:35:00 +03:00
}
2023-05-16 18:00:35 +03:00
if ( ! web3 . utils . isAddress ( recipient ) ) {
throw new Error ( 'Recipient address is not valid' ) ;
}
2022-02-27 01:32:30 +03:00
refund = toWei ( refund ) ;
2020-05-22 12:35:00 +03:00
if ( relayerURL ) {
if ( relayerURL . endsWith ( '.eth' ) ) {
2023-04-14 20:02:05 +03: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 22:29:33 +03:00
}
2021-12-07 19:57:18 +03:00
if ( torPort ) {
2023-04-14 20:02:05 +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
}
2022-02-27 01:32:30 +03:00
const relayerStatus = await axios . get ( relayerURL + '/status' , options ) ;
2020-12-24 14:39:20 +03:00
2023-04-14 20:02:05 +03:00
const { rewardAccount , netId , ethPrices , tornadoServiceFee } = relayerStatus . data ;
2022-02-27 01:32:30 +03:00
assert ( netId === ( await web3 . eth . net . getId ( ) ) || netId === '*' , 'This relay is for different network' ) ;
console . log ( 'Relay address:' , rewardAccount ) ;
2020-12-24 14:39:20 +03:00
2022-02-27 01:32:30 +03:00
const gasPrice = await fetchGasPrice ( ) ;
2020-05-22 12:35:00 +03:00
2023-05-14 00:08:18 +03:00
const decimals = isTestRPC ? 18 : config . deployments [ ` netId ${ netId } ` ] [ 'tokens' ] [ currency ] . decimals ;
2021-02-14 21:23:17 +03:00
const fee = calculateFee ( {
currency ,
gasPrice ,
amount ,
refund ,
ethPrices ,
relayerServiceFee : tornadoServiceFee ,
decimals
2022-02-27 01:32:30 +03:00
} ) ;
2020-05-22 12:35:00 +03:00
if ( fee . gt ( fromDecimals ( { amount , decimals } ) ) ) {
2022-02-27 01:32:30 +03:00
throw new Error ( 'Too high refund' ) ;
2023-04-14 20:02:05 +03:00
}
2020-12-24 14:39:20 +03:00
2023-04-14 20:02:05 +03:00
const { proof , args } = await generateProof ( {
deposit ,
currency ,
amount ,
recipient ,
relayerAddress : rewardAccount ,
fee ,
refund
} ) ;
2020-05-21 22:29:33 +03:00
2022-02-27 01:32:30 +03:00
console . log ( 'Sending withdraw transaction through relay' ) ;
2023-03-26 12:20:32 +03:00
const gasCosts = toBN ( gasPrice ) . mul ( toBN ( 340000 ) ) ;
const totalCosts = fee . add ( gasCosts ) ;
/** Relayer fee details **/
console . log ( 'Transaction fee: ' , rmDecimalBN ( fromWei ( gasCosts ) , 12 ) , ` ${ netSymbol } ` ) ;
console . log ( 'Relayer fee: ' , rmDecimalBN ( fromWei ( fee ) , 12 ) , ` ${ netSymbol } ` ) ;
console . log ( 'Total fees: ' , rmDecimalBN ( fromWei ( totalCosts ) , 12 ) , ` ${ netSymbol } ` ) ;
/** -------------------- **/
if ( shouldPromptConfirmation ) {
await promptConfirmation ( ) ;
}
2020-05-22 12:35:00 +03:00
try {
2023-04-14 20:02:05 +03:00
const response = await axios . post (
relayerURL + '/v1/tornadoWithdraw' ,
{
contract : tornadoInstance ,
proof ,
args
} ,
options
) ;
2020-05-22 12:35:00 +03:00
2022-02-27 01:32:30 +03:00
const { id } = response . data ;
2020-12-24 14:39:20 +03:00
2022-02-27 01:32:30 +03:00
const result = await getStatus ( id , relayerURL , options ) ;
console . log ( 'STATUS' , result ) ;
2020-05-22 12:35:00 +03:00
} catch ( e ) {
if ( e . response ) {
2022-02-27 01:32:30 +03:00
console . error ( e . response . data . error ) ;
2020-05-22 12:35:00 +03:00
} else {
2022-02-27 01:32:30 +03:00
console . error ( e . message ) ;
2020-05-22 12:35:00 +03:00
}
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.
2023-04-14 20:02:05 +03:00
assert (
recipient . toLowerCase ( ) == senderAccount . toLowerCase ( ) ,
'Withdrawal recepient mismatches with the account of provided private key from environment file'
) ;
2022-02-27 01:32:30 +03:00
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 19:57:18 +03:00
2022-02-27 01:32:30 +03:00
const { proof , args } = await generateProof ( { deposit , currency , amount , recipient , refund } ) ;
2020-05-22 12:35:00 +03:00
2022-02-27 01:32:30 +03:00
console . log ( 'Submitting withdraw transaction' ) ;
await generateTransaction ( contractAddress , tornado . methods . withdraw ( tornadoInstance , proof , ... args ) . encodeABI ( ) ) ;
2020-05-22 12:35:00 +03:00
}
2022-01-26 16:24:58 +03:00
if ( currency === netSymbol . toLowerCase ( ) ) {
2022-02-27 01:32:30 +03:00
await printETHBalance ( { address : recipient , name : 'Recipient' } ) ;
2022-01-26 16:24:58 +03:00
} else {
2022-02-27 01:32:30 +03:00
await printERC20Balance ( { address : recipient , name : 'Recipient' } ) ;
2022-01-26 16:24:58 +03:00
}
2022-02-27 01:32:30 +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
2022-02-27 01:32:30 +03:00
assert ( senderAccount != null , 'Error! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you send' ) ;
2022-01-23 15:03:46 +03:00
if ( tokenAddress ) {
2022-02-27 01:32:30 +03:00
const erc20ContractJson = require ( './build/contracts/ERC20Mock.json' ) ;
erc20 = new web3 . eth . Contract ( erc20ContractJson . abi , tokenAddress ) ;
let tokenBalance , tokenDecimals , tokenSymbol ;
2022-02-27 01:32:39 +03:00
if ( ! isTestRPC && ! multiCall ) {
2023-04-14 20:02:05 +03:00
const callToken = await useMultiCall ( [
[ tokenAddress , erc20 . methods . balanceOf ( senderAccount ) . encodeABI ( ) ] ,
[ tokenAddress , erc20 . methods . decimals ( ) . encodeABI ( ) ] ,
[ tokenAddress , erc20 . methods . symbol ( ) . encodeABI ( ) ]
] ) ;
2022-02-27 01:32:30 +03:00
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 15:35:55 +03:00
if ( tokenBalance . lt ( toSend ) ) {
2023-04-14 20:02:05 +03:00
console . error (
'You have' ,
rmDecimalBN ( tokenBalance . div ( BigNumber ( 10 ) . pow ( tokenDecimals ) ) ) ,
tokenSymbol ,
", you can't send more than you have"
) ;
2022-01-23 15:03:46 +03:00
process . exit ( 1 ) ;
}
2022-02-27 01:32:30 +03:00
const encodeTransfer = erc20 . methods . transfer ( address , toSend ) . encodeABI ( ) ;
await generateTransaction ( tokenAddress , encodeTransfer ) ;
console . log ( 'Sent' , amount , tokenSymbol , 'to' , address ) ;
2022-01-23 15:03:46 +03:00
} else {
2022-01-27 15:35:55 +03:00
const balance = new BigNumber ( await web3 . eth . getBalance ( senderAccount ) ) ;
2022-02-27 01:32:30 +03:00
assert ( balance . toNumber ( ) !== 0 , "You have 0 balance, can't send transaction" ) ;
2022-01-26 16:24:58 +03:00
if ( amount ) {
2022-02-27 01:32:30 +03:00
toSend = new BigNumber ( amount ) . times ( BigNumber ( 10 ) . pow ( 18 ) ) ;
2022-01-27 15:35:55 +03:00
if ( balance . lt ( toSend ) ) {
2023-04-14 20:02:05 +03:00
console . error (
'You have' ,
rmDecimalBN ( balance . div ( BigNumber ( 10 ) . pow ( 18 ) ) ) ,
netSymbol + ", you can't send more than you have."
) ;
2022-01-26 16:24:58 +03:00
process . exit ( 1 ) ;
}
} else {
2022-02-27 01:32:30 +03:00
console . log ( 'Amount not defined, sending all available amounts' ) ;
2022-01-27 15:35:55 +03:00
const gasPrice = new BigNumber ( await fetchGasPrice ( ) ) ;
const gasLimit = new BigNumber ( 21000 ) ;
2022-02-27 01:32:30 +03:00
if ( netId == 1 ) {
2022-01-27 15:35:55 +03:00
const priorityFee = new BigNumber ( await gasPrices ( 3 ) ) ;
toSend = balance . minus ( gasLimit . times ( gasPrice . plus ( priorityFee ) ) ) ;
2022-01-23 15:03:46 +03:00
} else {
2022-01-27 15:35:55 +03:00
toSend = balance . minus ( gasLimit . times ( gasPrice ) ) ;
2022-01-23 15:03:46 +03:00
}
}
2022-02-27 01:32:30 +03:00
await generateTransaction ( address , null , toSend ) ;
console . log ( 'Sent' , rmDecimalBN ( toSend . div ( BigNumber ( 10 ) . pow ( 18 ) ) ) , netSymbol , 'to' , address ) ;
2022-01-23 15:03:46 +03:00
}
}
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 ( ) {
2022-02-27 01:32:30 +03:00
const responseStatus = await axios . get ( relayerURL + '/v1/jobs/' + id , options ) ;
2020-12-24 14:39:20 +03:00
if ( responseStatus . status === 200 ) {
2023-04-14 20:02:05 +03:00
const { txHash , status , confirmations , failedReason } = responseStatus . data ;
2020-12-24 14:39:20 +03:00
2022-02-27 01:32:30 +03:00
console . log ( ` Current job status ${ status } , confirmations: ${ confirmations } ` ) ;
2020-12-24 14:39:20 +03:00
if ( status === 'FAILED' ) {
2022-02-27 01:32:30 +03:00
throw new Error ( status + ' failed reason:' + failedReason ) ;
2020-12-24 14:39:20 +03:00
}
if ( status === 'CONFIRMED' ) {
2022-02-27 01:32:30 +03:00
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 } `
2022-02-27 01:32:30 +03:00
) ;
console . log ( 'Transaction mined in block' , receipt . blockNumber ) ;
resolve ( status ) ;
2020-12-24 14:39:20 +03:00
}
}
setTimeout ( ( ) => {
2022-02-27 01:32:30 +03:00
getRelayerStatus ( id , relayerURL ) ;
2023-04-14 20:02:05 +03:00
} , 3000 ) ;
2020-12-24 14:39:20 +03:00
}
2022-02-27 01:32:30 +03:00
getRelayerStatus ( ) ;
2023-04-14 20:02:05 +03:00
} ) ;
2020-12-24 14:39:20 +03:00
}
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 } ) {
2022-02-27 01:32:30 +03: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 12:35:00 +03:00
2022-02-27 01:32:30 +03:00
const negative = ether . substring ( 0 , 1 ) === '-' ;
2020-05-22 12:35:00 +03:00
if ( negative ) {
2022-02-27 01:32:30 +03:00
ether = ether . substring ( 1 ) ;
2020-05-22 12:35:00 +03:00
}
if ( ether === '.' ) {
2022-02-27 01:32:30 +03:00
throw new Error ( '[ethjs-unit] while converting number ' + amount + ' to wei, invalid value' ) ;
2020-05-22 12:35:00 +03:00
}
// Split it into a whole and fractional part
2022-02-27 01:32:30 +03:00
const comps = ether . split ( '.' ) ;
2020-05-22 12:35:00 +03:00
if ( comps . length > 2 ) {
2022-02-27 01:32:30 +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
}
2022-02-27 01:32:30 +03:00
let whole = comps [ 0 ] ;
let fraction = comps [ 1 ] ;
2020-05-22 12:35:00 +03:00
if ( ! whole ) {
2022-02-27 01:32:30 +03:00
whole = '0' ;
2020-05-22 12:35:00 +03:00
}
if ( ! fraction ) {
2022-02-27 01:32:30 +03:00
fraction = '0' ;
2020-05-22 12:35:00 +03:00
}
if ( fraction . length > baseLength ) {
2022-02-27 01:32:30 +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 ) {
2022-02-27 01:32:30 +03:00
fraction += '0' ;
2020-05-22 12:35:00 +03:00
}
2022-02-27 01:32:30 +03:00
whole = new BN ( whole ) ;
fraction = new BN ( fraction ) ;
let wei = whole . mul ( base ) . add ( fraction ) ;
2020-05-22 12:35:00 +03:00
if ( negative ) {
2022-02-27 01:32:30 +03:00
wei = wei . mul ( negative ) ;
2020-05-22 12:35:00 +03:00
}
2022-02-27 01:32:30 +03:00
return new BN ( wei . toString ( 10 ) , 10 ) ;
2020-05-21 22:29:33 +03:00
}
function toDecimals ( value , decimals , fixed ) {
2022-02-27 01:32:30 +03:00
const zero = new BN ( 0 ) ;
const negative1 = new BN ( - 1 ) ;
decimals = decimals || 18 ;
fixed = fixed || 7 ;
2020-05-22 12:35:00 +03:00
2022-02-27 01:32:30 +03: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 12:35:00 +03:00
if ( negative ) {
2022-02-27 01:32:30 +03:00
value = value . mul ( negative1 ) ;
2020-05-22 12:35:00 +03:00
}
2022-02-27 01:32:30 +03:00
let fraction = value . mod ( base ) . toString ( 10 ) ;
2020-05-22 12:35:00 +03:00
while ( fraction . length < baseLength ) {
2022-02-27 01:32:30 +03:00
fraction = ` 0 ${ fraction } ` ;
2020-05-22 12:35:00 +03:00
}
2022-02-27 01:32:30 +03:00
fraction = fraction . match ( /^([0-9]*[1-9]|0)(0*)/ ) [ 1 ] ;
2020-05-22 12:35:00 +03:00
2022-02-27 01:32:30 +03:00
const whole = value . div ( base ) . toString ( 10 ) ;
value = ` ${ whole } ${ fraction === '0' ? '' : ` . ${ fraction } ` } ` ;
2020-05-22 12:35:00 +03:00
if ( negative ) {
2022-02-27 01:32:30 +03:00
value = ` - ${ value } ` ;
2020-05-22 12:35:00 +03:00
}
if ( fixed ) {
2022-02-27 01:32:30 +03:00
value = value . slice ( 0 , fixed ) ;
2020-05-22 12:35:00 +03:00
}
2022-02-27 01:32:30 +03:00
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 :
2022-02-27 01:32:30 +03:00
return 'bscscan.com' ;
2021-12-07 19:57:18 +03:00
case 100 :
2022-02-27 01:32:30 +03:00
return 'blockscout.com/poa/xdai' ;
2021-12-07 19:57:18 +03:00
case 137 :
2022-02-27 01:32:30 +03:00
return 'polygonscan.com' ;
2021-12-07 19:57:18 +03:00
case 42161 :
2022-02-27 01:32:30 +03:00
return 'arbiscan.io' ;
2021-12-07 19:57:18 +03:00
case 43114 :
2022-02-27 01:32:30 +03:00
return 'snowtrace.io' ;
2021-12-07 19:57:18 +03:00
case 5 :
2022-02-27 01:32:30 +03:00
return 'goerli.etherscan.io' ;
2021-12-07 19:57:18 +03:00
case 42 :
2022-02-27 01:32:30 +03:00
return 'kovan.etherscan.io' ;
2022-01-23 12:42:08 +03:00
case 10 :
2022-02-27 01:32:30 +03:00
return 'optimistic.etherscan.io' ;
2021-12-07 19:57:18 +03:00
default :
2022-02-27 01:32:30 +03:00
return 'etherscan.io' ;
2021-12-07 19:57:18 +03:00
}
}
// 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 :
2022-02-27 01:32:30 +03:00
return 'Ethereum' ;
2021-12-07 19:57:18 +03:00
case 56 :
2022-02-27 01:32:30 +03:00
return 'BinanceSmartChain' ;
2021-12-07 19:57:18 +03:00
case 100 :
2022-02-27 01:32:30 +03:00
return 'GnosisChain' ;
2021-12-07 19:57:18 +03:00
case 137 :
2022-02-27 01:32:30 +03:00
return 'Polygon' ;
2021-12-07 19:57:18 +03:00
case 42161 :
2022-02-27 01:32:30 +03:00
return 'Arbitrum' ;
2021-12-07 19:57:18 +03:00
case 43114 :
2022-02-27 01:32:30 +03:00
return 'Avalanche' ;
2021-02-14 21:23:17 +03:00
case 5 :
2022-02-27 01:32:30 +03:00
return 'Goerli' ;
2021-02-14 21:23:17 +03:00
case 42 :
2022-02-27 01:32:30 +03:00
return 'Kovan' ;
2022-02-05 05:11:59 +03:00
case 10 :
2022-02-27 01:32:30 +03:00
return 'Optimism' ;
2021-12-07 19:57:18 +03:00
default :
2022-02-27 01:32:30 +03:00
return 'testRPC' ;
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 :
2022-02-27 01:32:30 +03:00
return 'BNB' ;
2021-12-07 19:57:18 +03:00
case 100 :
2022-02-27 01:32:30 +03:00
return 'xDAI' ;
2021-12-07 19:57:18 +03:00
case 137 :
2022-02-27 01:32:30 +03:00
return 'MATIC' ;
2021-12-07 19:57:18 +03:00
case 43114 :
2022-02-27 01:32:30 +03:00
return 'AVAX' ;
2021-12-07 19:57:18 +03:00
default :
2022-02-27 01:32:30 +03:00
return 'ETH' ;
2021-12-07 19:57:18 +03:00
}
}
function gasPricesETH ( value = 80 ) {
2022-02-27 01:32:30 +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' ) ) ;
2020-12-24 14:39:20 +03:00
}
2021-12-07 19:57:18 +03:00
function gasPrices ( value = 5 ) {
2022-02-27 01:32:30 +03:00
return toHex ( toWei ( value . toString ( ) , 'gwei' ) ) ;
2021-12-07 19:57:18 +03:00
}
2020-12-24 14:39:20 +03:00
async function fetchGasPrice ( ) {
try {
2023-03-26 12:20:32 +03:00
/** Gas preferences **/
console . log ( 'Gas speed preference: ' , preferenceSpeed ) ;
/** ----------------------------------------------- **/
2022-12-01 14:54:18 +03:00
try {
2023-04-14 20:02:05 +03:00
const isLegacy = ! eipGasSupport ;
2023-05-14 00:15:32 +03:00
const oracleOptions = { chainId : netId , defaultRpc : web3 . currentProvider . host } ;
const oracle = new GasPriceOracle ( oracleOptions ) ;
2022-12-02 11:16:35 +03:00
const gas = await oracle . gasPrices ( { isLegacy } ) ;
2023-04-14 20:02:05 +03:00
if ( netId === 1 ) {
2023-03-26 12:20:32 +03:00
return gasPricesETH ( gas [ preferenceSpeed ] ) ;
2022-12-01 14:54:18 +03:00
} else {
2023-04-14 20:02:05 +03:00
return gasPrices ( gas [ preferenceSpeed ] ) ;
2022-12-01 14:54:18 +03:00
}
2023-04-14 20:02:05 +03:00
} catch ( e ) {
2022-12-02 11:16:35 +03:00
const wei = await web3 . eth . getGasPrice ( ) ;
return wei / web3 . utils . unitMap . gwei ;
2021-12-07 19:57:18 +03:00
}
2020-12-24 14:39:20 +03:00
} catch ( err ) {
2022-02-27 01:32:30 +03:00
throw new Error ( ` Method fetchGasPrice has error ${ err . message } ` ) ;
2020-12-24 14:39:20 +03:00
}
}
function calculateFee ( { currency , gasPrice , amount , refund , ethPrices , relayerServiceFee , decimals } ) {
2021-02-14 21:23:17 +03:00
const decimalsPoint =
2022-02-27 01:32:30 +03: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 12:35:00 +03:00
switch ( currency ) {
2022-01-23 09:52:59 +03:00
case netSymbol . toLowerCase ( ) : {
2022-02-27 01:32:30 +03:00
desiredFee = expense . add ( feePercent ) ;
break ;
2021-12-07 19:57:18 +03:00
}
2021-02-14 21:23:17 +03:00
default : {
desiredFee = expense
. add ( toBN ( refund ) )
. mul ( toBN ( 10 * * decimals ) )
2022-02-27 01:32:30 +03:00
. div ( toBN ( ethPrices [ currency ] ) ) ;
desiredFee = desiredFee . add ( feePercent ) ;
break ;
2021-02-14 21:23:17 +03:00
}
2020-05-22 12:35:00 +03:00
}
2022-02-27 01:32:30 +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 ) => {
2022-02-27 01:32:30 +03:00
const result = await web3 . eth . getTransactionReceipt ( txHash ) ;
2020-05-22 12:35:00 +03:00
if ( ! result || ! result . blockNumber ) {
if ( retryAttempt <= attempts ) {
2022-02-27 01:32:30 +03:00
setTimeout ( ( ) => checkForTx ( txHash , retryAttempt + 1 ) , delay ) ;
2020-05-22 12:35:00 +03:00
} else {
2022-02-27 01:32:30 +03:00
reject ( new Error ( 'tx was not mined' ) ) ;
2020-05-21 22:29:33 +03:00
}
2020-05-22 12:35:00 +03:00
} else {
2022-02-27 01:32:30 +03:00
resolve ( result ) ;
2020-05-22 12:35:00 +03:00
}
2023-04-14 20:02:05 +03:00
} ;
2022-02-27 01:32:30 +03:00
checkForTx ( txHash ) ;
2023-04-14 20:02:05 +03:00
} ) ;
2020-05-21 22:29:33 +03:00
}
2022-01-22 07:49:45 +03:00
function initJson ( file ) {
2022-02-27 01:32:30 +03: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-22 07:49:45 +03:00
} ) ;
2022-02-27 01:32:30 +03:00
} ) ;
2023-04-14 20:02:05 +03:00
}
2022-01-22 07:49:45 +03:00
2023-04-14 23:07:02 +03:00
/ * *
* Erase all zero events from events tree array
* @ param events Events tree array
* /
function filterZeroEvents ( events ) {
return events . filter ( ( event ) => event . transactionHash !== null ) ;
}
2021-12-12 09:19:49 +03:00
function loadCachedEvents ( { type , currency , amount } ) {
2021-07-16 13:27:08 +03:00
try {
2022-02-27 01:32:30 +03:00
const module = require ( ` ./cache/ ${ netName . toLowerCase ( ) } / ${ type } s_ ${ currency } _ ${ amount } .json ` ) ;
2021-07-16 13:27:08 +03:00
if ( module ) {
2022-02-27 01:32:30 +03:00
const events = module ;
2021-07-16 13:27:08 +03:00
return {
2023-04-14 23:07:02 +03:00
events : filterZeroEvents ( events ) ,
2021-07-16 13:27:08 +03:00
lastBlock : events [ events . length - 1 ] . blockNumber
2023-04-14 20:02:05 +03:00
} ;
2021-07-16 13:27:08 +03:00
}
} catch ( err ) {
2023-04-14 20:02:05 +03:00
console . log ( 'Error fetching cached files, syncing from block' , deployedBlockNumber ) ;
2021-12-07 19:57:18 +03:00
return {
events : [ ] ,
2023-04-14 20:02:05 +03:00
lastBlock : deployedBlockNumber
} ;
2021-07-16 13:27:08 +03:00
}
}
2023-04-14 23:07:02 +03:00
async function fetchEvents ( { type , currency , amount , filterEvents } ) {
2023-04-14 20:02:05 +03:00
if ( type === 'withdraw' ) {
type = 'withdrawal' ;
2022-02-27 01:32:30 +03:00
}
2023-04-14 23:07:02 +03:00
if ( filterEvents === undefined ) filterEvents = true ;
2021-12-07 19:57:18 +03:00
2022-02-27 01:32:30 +03:00
const cachedEvents = loadCachedEvents ( { type , currency , amount } ) ;
const startBlock = cachedEvents . lastBlock + 1 ;
2021-12-07 19:57:18 +03:00
2023-04-14 20:02:05 +03: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 19:57:18 +03:00
2023-04-14 20:06:39 +03:00
async function updateCache ( fetchedEvents ) {
try {
const fileName = ` ./cache/ ${ netName . toLowerCase ( ) } / ${ type } s_ ${ currency } _ ${ amount } .json ` ;
const localEvents = await initJson ( fileName ) ;
2023-04-14 23:07:02 +03:00
const events = filterZeroEvents ( localEvents ) . concat ( fetchedEvents ) ;
2023-04-14 20:06:39 +03:00
await fs . writeFileSync ( fileName , JSON . stringify ( events , null , 2 ) , 'utf8' ) ;
} catch ( error ) {
throw new Error ( 'Writing cache file failed:' , error ) ;
}
}
2023-04-14 23:07:02 +03:00
/ * *
* Adds an zero ( empty ) event to the end of the events list
* If tornado transactions on the selected currency / amount are rare and the last one was much earlier than the current block ,
* it helps to quickly synchronize the events tree
* @ param blockNumber Latest block number on selected chain
* /
async function addZeroEvent ( blockNumber ) {
const zeroEvent = { blockNumber , transactionHash : null } ;
await updateCache ( [ zeroEvent ] ) ;
console . log ( 'Added' , amount , currency . toUpperCase ( ) , type , 'zero event to block:' , blockNumber ) ;
}
2022-02-27 01:32:30 +03:00
async function syncEvents ( ) {
try {
let targetBlock = await web3 . eth . getBlockNumber ( ) ;
let chunks = 1000 ;
2023-04-14 20:02:05 +03:00
console . log ( 'Querying latest events from RPC' ) ;
2022-02-27 01:32:30 +03: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
2023-04-14 20:02:05 +03:00
} ;
2022-02-27 01:32:30 +03:00
} ) ;
}
2022-01-22 07:49:45 +03:00
2022-02-27 01:32:30 +03:00
function mapWithdrawEvents ( ) {
fetchedEvents = fetchedEvents . map ( ( { blockNumber , transactionHash , returnValues } ) => {
const { nullifierHash , to , fee } = returnValues ;
return {
blockNumber ,
transactionHash ,
nullifierHash ,
to ,
fee
2023-04-14 20:02:05 +03:00
} ;
2022-02-27 01:32:30 +03:00
} ) ;
}
function mapLatestEvents ( ) {
2023-04-14 20:02:05 +03:00
if ( type === 'deposit' ) {
2022-02-27 01:32:30 +03:00
mapDepositEvents ( ) ;
} else {
mapWithdrawEvents ( ) ;
2022-01-22 07:49:45 +03:00
}
2022-02-27 01:32:30 +03:00
}
2022-01-22 07:49:45 +03:00
2022-02-27 01:32:35 +03:00
async function fetchWeb3Events ( i ) {
2022-02-27 01:32:30 +03:00
let j ;
if ( i + chunks - 1 > targetBlock ) {
j = targetBlock ;
} else {
j = i + chunks - 1 ;
2022-01-22 07:49:45 +03:00
}
2023-04-14 20:02:05 +03:00
await tornadoContract
. getPastEvents ( capitalizeFirstLetter ( type ) , {
fromBlock : i ,
toBlock : j
} )
. 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-27 01:32:30 +03:00
2023-04-14 23:09:12 +03:00
mapLatestEvents ( ) ;
2022-02-27 01:32:30 +03:00
}
2022-01-22 07:49:45 +03:00
2022-02-27 01:32:35 +03:00
await fetchWeb3Events ( i ) ;
2023-04-14 20:06:39 +03:00
await updateCache ( fetchedEvents ) ;
2021-12-07 19:57:18 +03:00
}
2023-04-14 23:07:02 +03:00
2023-04-14 23:19:33 +03:00
await addZeroEvent ( targetBlock ) ;
2022-02-27 01:32:30 +03:00
} catch ( error ) {
2023-04-14 20:02:05 +03:00
console . log ( error ) ;
throw new Error ( 'Error while updating cache' ) ;
2021-12-07 19:57:18 +03:00
}
2022-02-27 01:32:30 +03:00
}
2022-02-27 01:32:35 +03:00
async function syncGraphEvents ( ) {
let options = { } ;
if ( torPort ) {
2023-04-14 20:02:05 +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' }
} ;
2022-02-27 01:32:35 +03:00
}
async function queryLatestTimestamp ( ) {
try {
const variables = {
currency : currency . toString ( ) ,
amount : amount . toString ( )
2023-04-14 20:02:05 +03:00
} ;
if ( type === 'deposit' ) {
2022-02-27 01:32:35 +03:00
const query = {
query : `
query ( $currency : String , $amount : String ) {
deposits ( first : 1 , orderBy : timestamp , orderDirection : desc , where : { currency : $currency , amount : $amount } ) {
timestamp
}
}
` ,
variables
2023-04-14 20:02:05 +03:00
} ;
2022-02-27 01:32:35 +03:00
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
2023-04-14 20:02:05 +03:00
} ;
2022-02-27 01:32:35 +03:00
const querySubgraph = await axios . post ( subgraph , query , options ) ;
const queryResult = querySubgraph . data . data . withdrawals ;
const result = queryResult [ 0 ] . timestamp ;
return Number ( result ) ;
}
} catch ( error ) {
2023-04-14 20:02:05 +03:00
console . error ( 'Failed to fetch latest event from thegraph' ) ;
2022-02-27 01:32:35 +03:00
}
}
async function queryFromGraph ( timestamp ) {
try {
const variables = {
currency : currency . toString ( ) ,
amount : amount . toString ( ) ,
timestamp : timestamp
2023-04-14 20:02:05 +03:00
} ;
if ( type === 'deposit' ) {
2022-02-27 01:32:35 +03:00
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
2023-04-14 20:02:05 +03:00
} ;
2022-02-27 01:32:35 +03:00
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
2023-04-14 20:02:05 +03:00
} ;
2022-02-27 01:32:35 +03:00
} ) ;
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
2023-04-14 20:02:05 +03:00
} ;
2022-02-27 01:32:35 +03:00
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
2023-04-14 20:02:05 +03:00
} ;
2022-02-27 01:32:35 +03:00
} ) ;
return mapResult ;
}
} catch ( error ) {
console . error ( error ) ;
}
}
async function fetchGraphEvents ( ) {
2023-04-14 20:02:05 +03:00
console . log ( 'Querying latest events from TheGraph' ) ;
2022-02-27 01:32:35 +03:00
const latestTimestamp = await queryLatestTimestamp ( ) ;
if ( latestTimestamp ) {
const getCachedBlock = await web3 . eth . getBlock ( startBlock ) ;
const cachedTimestamp = getCachedBlock . timestamp ;
2023-04-14 20:02:05 +03:00
for ( let i = cachedTimestamp ; i < latestTimestamp ; ) {
2022-02-27 01:32:35 +03:00
const result = await queryFromGraph ( i ) ;
if ( Object . keys ( result ) . length === 0 ) {
i = latestTimestamp ;
} else {
2023-04-14 20:02:05 +03:00
if ( type === 'deposit' ) {
2022-02-27 01:32:39 +03:00
const resultBlock = result [ result . length - 1 ] . blockNumber ;
const resultTimestamp = result [ result . length - 1 ] . timestamp ;
await updateCache ( result ) ;
i = resultTimestamp ;
2023-04-14 20:02:05 +03:00
console . log ( 'Fetched' , amount , currency . toUpperCase ( ) , type , 'events to block:' , Number ( resultBlock ) ) ;
2022-02-27 01:32:39 +03:00
} else {
const resultBlock = result [ result . length - 1 ] . blockNumber ;
const getResultBlock = await web3 . eth . getBlock ( resultBlock ) ;
const resultTimestamp = getResultBlock . timestamp ;
await updateCache ( result ) ;
i = resultTimestamp ;
2023-04-14 20:02:05 +03:00
console . log ( 'Fetched' , amount , currency . toUpperCase ( ) , type , 'events to block:' , Number ( resultBlock ) ) ;
2022-02-27 01:32:39 +03:00
}
2022-02-27 01:32:35 +03:00
}
}
} else {
2023-04-14 20:02:05 +03:00
console . log ( 'Fallback to web3 events' ) ;
2022-02-27 01:32:35 +03:00
await syncEvents ( ) ;
}
}
await fetchGraphEvents ( ) ;
}
2022-02-27 01:32:39 +03:00
if ( ! privateRpc && ! subgraph && ! isTestRPC ) {
2022-02-27 01:32:35 +03:00
await syncGraphEvents ( ) ;
} else {
await syncEvents ( ) ;
}
2022-02-27 01:32:30 +03: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 ;
2023-04-14 20:02:05 +03:00
console . log ( 'Cache updated for Tornado' , type , amount , currency , 'instance to block' , updatedBlock , 'successfully' ) ;
2022-02-27 01:32:30 +03:00
console . log ( ` Total ${ type } s: ` , updatedEvents . length ) ;
return updatedEvents ;
}
const events = await loadUpdatedEvents ( ) ;
2023-04-14 23:07:02 +03:00
return filterEvents ? filterZeroEvents ( events ) : events ;
2021-12-07 19:57:18 +03:00
}
2020-05-21 22:29:33 +03:00
/ * *
* Parses Tornado . cash note
2023-04-22 19:20:44 +03:00
* @ param noteString the note
2020-05-21 22:29:33 +03:00
* /
function parseNote ( noteString ) {
2023-04-14 20:02:05 +03:00
const noteRegex = /tornado-(?<currency>\w+)-(?<amount>[\d.]+)-(?<netId>\d+)-0x(?<note>[0-9a-fA-F]{124})/g ;
2022-02-27 01:32:30 +03:00
const match = noteRegex . exec ( noteString ) ;
2020-05-22 12:35:00 +03:00
if ( ! match ) {
2022-02-27 01:32:30 +03:00
throw new Error ( 'The note has invalid format' ) ;
2020-05-22 12:35:00 +03:00
}
2022-02-27 01:32:30 +03: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 12:35:00 +03:00
2021-02-14 21:23:17 +03:00
return {
currency : match . groups . currency ,
amount : match . groups . amount ,
netId ,
deposit
2023-04-14 20:02:05 +03:00
} ;
2020-05-21 22:29:33 +03:00
}
2022-02-27 01:32:37 +03:00
/ * *
* Parses Tornado . cash deposit invoice
* @ param invoiceString the note
* /
function parseInvoice ( invoiceString ) {
2023-04-14 20:02:05 +03:00
const noteRegex = /tornadoInvoice-(?<currency>\w+)-(?<amount>[\d.]+)-(?<netId>\d+)-0x(?<commitmentNote>[0-9a-fA-F]{64})/g ;
const match = noteRegex . exec ( invoiceString ) ;
2022-02-27 01:32:37 +03:00
if ( ! match ) {
2023-04-14 20:02:05 +03:00
throw new Error ( 'The note has invalid format' ) ;
2022-02-27 01:32:37 +03:00
}
2023-04-14 20:02:05 +03:00
const netId = Number ( match . groups . netId ) ;
const buf = Buffer . from ( match . groups . commitmentNote , 'hex' ) ;
const commitmentNote = toHex ( buf . slice ( 0 , 32 ) ) ;
2022-02-27 01:32:37 +03:00
return {
currency : match . groups . currency ,
amount : match . groups . amount ,
netId ,
commitmentNote
2023-04-14 20:02:05 +03:00
} ;
2022-02-27 01:32:37 +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 {
2022-02-27 01:32:30 +03:00
const cachedEvents = await fetchEvents ( { type : 'deposit' , currency , amount } ) ;
2021-12-07 19:57:18 +03:00
const eventWhenHappened = await cachedEvents . filter ( function ( event ) {
return event . commitment === deposit . commitmentHex ;
2022-02-27 01:32:30 +03:00
} ) [ 0 ] ;
2021-12-07 19:57:18 +03:00
2020-05-22 12:35:00 +03:00
if ( eventWhenHappened . length === 0 ) {
2022-02-27 01:32:30 +03:00
throw new Error ( 'There is no related deposit, the note is invalid' ) ;
2020-05-22 12:35:00 +03:00
}
2020-05-21 22:29:33 +03:00
2022-02-27 01:32:30 +03: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 22:29:33 +03:00
2021-02-14 21:23:17 +03:00
return {
timestamp ,
txHash ,
isSpent ,
from : receipt . from ,
commitment : deposit . commitmentHex
2023-04-14 20:02:05 +03:00
} ;
2020-05-22 12:35:00 +03:00
} catch ( e ) {
2022-02-27 01:32:30 +03:00
console . error ( 'loadDepositData' , e ) ;
2020-05-22 12:35:00 +03:00
}
2023-04-14 20:02:05 +03:00
return { } ;
2020-05-21 22:29:33 +03:00
}
async function loadWithdrawalData ( { amount , currency , deposit } ) {
2020-05-22 12:35:00 +03:00
try {
2022-02-27 01:32:30 +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 ) => {
2023-04-14 20:02:05 +03:00
return event . nullifierHash === deposit . nullifierHex ;
2022-02-27 01:32:30 +03:00
} ) [ 0 ] ;
2020-05-22 12:35:00 +03:00
2022-02-27 01:32:30 +03:00
const fee = withdrawEvent . fee ;
2023-05-14 00:08:18 +03:00
const decimals = config . deployments [ ` netId ${ netId } ` ] [ 'tokens' ] [ currency ] . decimals ;
2022-02-27 01:32:30 +03:00
const withdrawalAmount = toBN ( fromDecimals ( { amount , decimals } ) ) . sub ( toBN ( fee ) ) ;
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 )
2023-04-14 20:02:05 +03:00
} ;
2020-05-22 12:35:00 +03:00
} catch ( e ) {
2022-02-27 01:32:30 +03:00
console . error ( 'loadWithdrawalData' , e ) ;
2020-05-22 12:35:00 +03:00
}
2020-05-21 22:29:33 +03:00
}
2023-03-26 12:20:32 +03:00
function statePreferences ( program ) {
const isPref = gasSpeedPreferences . includes ( program . gas _speed ) ;
if ( program . gas _speed && ! isPref ) {
2023-04-14 20:02:05 +03:00
throw new Error ( 'Invalid gas speed preference' ) ;
2023-03-26 12:20:32 +03:00
} else if ( program . gas _speed ) {
preferenceSpeed = program . gas _speed ;
2023-04-14 20:02:05 +03:00
}
2023-03-26 12:20:32 +03:00
2023-04-14 20:02:05 +03:00
if ( program . noconfirmation ) {
2023-03-26 12:20:32 +03:00
shouldPromptConfirmation = false ;
2023-04-14 20:02:05 +03:00
}
2023-05-11 12:04:42 +03:00
if ( program . onlyRpc ) {
2023-03-26 12:20:32 +03:00
privateRpc = true ;
2023-04-14 20:02:05 +03:00
}
if ( program . tor ) {
2023-03-26 12:20:32 +03:00
torPort = program . tor ;
2023-04-14 20:02:05 +03:00
}
2023-03-26 12:20:32 +03:00
}
async function promptConfirmation ( ) {
2023-04-14 20:02:05 +03:00
const query = 'Confirm the transaction [Y/n] ' ;
const confirmation = await new Promise ( ( resolve ) => prompt . question ( query , resolve ) ) ;
2023-03-26 12:20:32 +03:00
2023-04-14 20:02:05 +03:00
if ( confirmation . toUpperCase ( ) !== 'Y' ) {
throw new Error ( 'Transaction rejected' ) ;
2023-03-26 12:20:32 +03:00
}
}
2020-05-21 22:29:33 +03:00
/ * *
* Init web3 , contracts , and snark
* /
2023-05-10 18:49:59 +03:00
async function init ( { rpc , noteNetId , currency = 'dai' , amount = '100' , balanceCheck , localMode , privateKey } ) {
2022-02-27 01:32:33 +03:00
let contractJson , instanceJson , erc20ContractJson , erc20tornadoJson , tornadoAddress , tokenAddress ;
2023-03-23 06:47:37 +03:00
let ipOptions = { } ;
2023-05-15 14:35:17 +03:00
if ( noteNetId && ! rpc ) rpc = config . deployments [ ` netId ${ noteNetId } ` ] . defaultRpc ;
2023-05-16 11:10:03 +03:00
if ( torPort && rpc . startsWith ( 'https' ) ) {
2023-04-14 20:02:05 +03:00
console . log ( 'Using tor network' ) ;
2023-03-23 06:47:37 +03:00
web3Options = { agent : { https : new SocksProxyAgent ( 'socks5h://127.0.0.1:' + torPort ) } , timeout : 60000 } ;
// 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 } ) ;
2023-04-14 20:02:05 +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' }
} ;
2023-05-16 11:10:03 +03:00
} else if ( torPort && rpc . startsWith ( 'http' ) ) {
2023-04-14 20:02:05 +03:00
console . log ( 'Using tor network' ) ;
2023-03-23 06:47:37 +03:00
web3Options = { agent : { http : new SocksProxyAgent ( 'socks5h://127.0.0.1:' + torPort ) } , timeout : 60000 } ;
// 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 } ) ;
2023-04-14 20:02:05 +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' }
} ;
} else if ( rpc . includes ( 'ipc' ) ) {
console . log ( 'Using ipc connection' ) ;
2023-03-23 06:47:37 +03:00
web3 = new Web3 ( new Web3 . providers . IpcProvider ( rpc , net ) , null , { transactionConfirmationBlocks : 1 } ) ;
2023-05-16 11:10:03 +03:00
} else if ( rpc . startsWith ( 'ws' ) || rpc . startsWith ( 'wss' ) ) {
2023-04-14 20:02:05 +03: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 }
} ;
2023-03-23 06:47:37 +03:00
web3 = new Web3 ( new Web3 . providers . WebsocketProvider ( rpc , web3Options ) , net , { transactionConfirmationBlocks : 1 } ) ;
2020-05-22 12:35:00 +03:00
} else {
2023-04-14 20:02:05 +03:00
console . log ( 'Connecting to remote node' ) ;
2023-03-23 06:47:37 +03:00
web3 = new Web3 ( rpc , null , { transactionConfirmationBlocks : 1 } ) ;
}
2023-04-14 20:02:05 +03:00
2023-03-23 06:47:37 +03:00
const rpcHost = new URL ( rpc ) . hostname ;
const isIpPrivate = is _ip _private ( rpcHost ) ;
2023-04-14 20:02:05 +03:00
if ( ! isIpPrivate && ! rpc . includes ( 'localhost' ) && ! privateRpc ) {
2023-03-23 06:47:37 +03:00
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.' ) ;
2022-01-23 09:52:59 +03:00
}
2023-04-14 20:02:05 +03:00
} else if ( isIpPrivate || rpc . includes ( 'localhost' ) ) {
2023-03-23 06:47:37 +03:00
console . log ( 'Local RPC detected' ) ;
privateRpc = true ;
}
2023-04-14 20:02:05 +03:00
2023-03-23 06:47:37 +03: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 ;
2023-05-10 18:49:59 +03:00
const privKey = privateKey || process . env . PRIVATE _KEY ;
2023-04-14 20:02:05 +03:00
2023-03-23 06:47:37 +03:00
if ( privKey ) {
2023-05-10 18:49:59 +03:00
if ( privKey . startsWith ( '0x' ) ) {
PRIVATE _KEY = privKey . substring ( 2 ) ;
2023-03-23 06:47:37 +03:00
} else {
2023-05-10 18:49:59 +03:00
PRIVATE _KEY = privKey ;
2020-05-21 22:29:33 +03:00
}
2023-04-14 20:02:05 +03:00
}
if ( PRIVATE _KEY ) {
2023-03-23 06:47:37 +03: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-22 12:35:00 +03:00
}
2023-04-14 20:02:05 +03:00
2023-03-23 06:47:37 +03:00
erc20ContractJson = require ( './build/contracts/ERC20Mock.json' ) ;
erc20tornadoJson = require ( './build/contracts/ERC20Tornado.json' ) ;
2020-05-22 12:35:00 +03: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-27 01:32:30 +03:00
groth16 = await buildGroth16 ( ) ;
netId = await web3 . eth . net . getId ( ) ;
netName = getCurrentNetworkName ( ) ;
netSymbol = getCurrentNetworkSymbol ( ) ;
2023-03-23 06:47:37 +03:00
2020-05-22 12:35:00 +03:00
if ( noteNetId && Number ( noteNetId ) !== netId ) {
2022-02-27 01:32:30 +03:00
throw new Error ( 'This note is for a different network. Specify the --rpc option explicitly' ) ;
2020-05-22 12:35:00 +03:00
}
2023-04-14 20:02:05 +03:00
if ( netName === 'testRPC' ) {
2022-02-04 05:28:11 +03:00
isTestRPC = true ;
}
if ( localMode ) {
2023-04-14 20:02:05 +03:00
console . log ( 'Local mode detected: will not submit signed TX to remote node' ) ;
2022-02-27 01:32:33 +03:00
doNotSubmitTx = true ;
2021-12-07 19:57:18 +03:00
}
2020-05-22 12:35:00 +03:00
2022-02-04 05:28:11 +03:00
if ( isTestRPC ) {
2023-04-14 20:02:05 +03:00
tornadoAddress =
currency === netSymbol . toLowerCase ( ) ? contractJson . networks [ netId ] . address : erc20tornadoJson . networks [ netId ] . address ;
2022-02-27 01:32:30 +03:00
tokenAddress = currency !== netSymbol . toLowerCase ( ) ? erc20ContractJson . networks [ netId ] . address : null ;
deployedBlockNumber = 0 ;
senderAccount = ( await web3 . eth . getAccounts ( ) ) [ 0 ] ;
2020-05-22 12:35:00 +03:00
} else {
try {
2021-12-07 19:57:18 +03:00
if ( balanceCheck ) {
2022-02-27 01:32:30 +03:00
currency = netSymbol . toLowerCase ( ) ;
amount = Object . keys ( config . deployments [ ` netId ${ netId } ` ] [ currency ] . instanceAddress ) [ 0 ] ;
2021-12-07 19:57:18 +03:00
}
2022-02-27 01:32:30 +03:00
tornadoAddress = config . deployments [ ` netId ${ netId } ` ] . proxy ;
multiCall = config . deployments [ ` netId ${ netId } ` ] . multicall ;
2022-02-27 01:32:35 +03:00
subgraph = config . deployments [ ` netId ${ netId } ` ] . subgraph ;
2023-05-14 00:08:18 +03:00
tornadoInstance = config . deployments [ ` netId ${ netId } ` ] [ 'tokens' ] [ currency ] . instanceAddress [ amount ] ;
deployedBlockNumber = config . deployments [ ` netId ${ netId } ` ] [ 'tokens' ] [ currency ] . deployedBlockNumber [ amount ] ;
2020-12-24 14:39:20 +03:00
2020-05-22 12:35:00 +03:00
if ( ! tornadoAddress ) {
2022-02-27 01:32:30 +03:00
throw new Error ( ) ;
2020-05-22 12:35:00 +03:00
}
2023-05-14 00:08:18 +03:00
tokenAddress =
currency !== netSymbol . toLowerCase ( ) ? config . deployments [ ` netId ${ netId } ` ] [ 'tokens' ] [ currency ] . tokenAddress : null ;
2020-05-22 12:35:00 +03:00
} catch ( e ) {
2022-02-27 01:32:30 +03:00
console . error ( 'There is no such tornado instance, check the currency and amount you provide' , e ) ;
process . exit ( 1 ) ;
2020-05-21 22:29:33 +03:00
}
2020-05-22 12:35:00 +03:00
}
2022-02-27 01:32:30 +03: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 22:29:33 +03:00
}
async function main ( ) {
2023-03-23 06:47:37 +03:00
program
2023-05-15 14:35:17 +03:00
. option ( '-r, --rpc <URL>' , 'The RPC that CLI should interact with' )
2023-03-23 06:47:37 +03:00
. option ( '-R, --relayer <URL>' , 'Withdraw via relayer' )
. option ( '-T, --tor <PORT>' , 'Optional tor port' )
2023-05-10 18:49:59 +03:00
. option ( '-p, --private-key <KEY>' , "Wallet private key - If you didn't add it to .env file and it is needed for operation" )
2023-05-11 12:04:42 +03:00
. option ( '-S --gas-speed <SPEED>' , 'Gas speed preference [ instant, fast, standard, low ]' )
2023-04-14 20:02:05 +03:00
. option ( '-N --noconfirmation' , 'No confirmation mode - Does not query confirmation ' )
2023-03-26 12:20:32 +03:00
. option ( '-L, --local-rpc' , 'Local node mode - Does not submit signed transaction to the node' )
. option ( '-o, --only-rpc' , 'Only rpc mode - Does not enable thegraph api nor remote ip detection' ) ;
2023-03-23 06:47:37 +03:00
program
. command ( 'createNote <currency> <amount> <chainId>' )
. description (
'Create deposit note and invoice, allows generating private key like deposit notes from secure, offline environment. The currency is one of (ETH|DAI|cDAI|USDC|cUSDC|USDT). The amount depends on currency, see config.js file or visit https://tornado.cash.'
)
. action ( async ( currency , amount , chainId ) => {
2023-04-14 20:02:05 +03:00
currency = currency . toLowerCase ( ) ;
2023-03-23 06:47:37 +03:00
await createInvoice ( { currency , amount , chainId } ) ;
} ) ;
program
. command ( 'depositInvoice <invoice>' )
2023-04-14 20:02:05 +03:00
. description ( 'Submit a deposit of invoice from default eth account and return the resulting note.' )
2023-03-23 06:47:37 +03:00
. action ( async ( invoice ) => {
2023-04-14 20:02:05 +03:00
statePreferences ( program ) ;
2023-03-26 12:20:32 +03:00
2023-03-23 06:47:37 +03:00
const { currency , amount , netId , commitmentNote } = parseInvoice ( invoice ) ;
2023-05-11 12:04:42 +03:00
await init ( {
rpc : program . rpc ,
currency ,
amount ,
localMode : program . localRpc ,
privateKey : program . privateKey ,
noteNetId : netId
} ) ;
2023-04-14 20:02:05 +03:00
console . log ( 'Creating' , currency . toUpperCase ( ) , amount , 'deposit for' , netName , 'Tornado Cash Instance' ) ;
2023-03-23 06:47:37 +03:00
await deposit ( { currency , amount , commitmentNote } ) ;
} ) ;
program
. command ( 'deposit <currency> <amount>' )
. description (
'Submit a deposit of specified currency and amount from default eth account and return the resulting note. The currency is one of (ETH|DAI|cDAI|USDC|cUSDC|USDT). The amount depends on currency, see config.js file or visit https://tornado.cash.'
)
. action ( async ( currency , amount ) => {
currency = currency . toLowerCase ( ) ;
2023-03-26 12:20:32 +03:00
2023-04-14 20:02:05 +03:00
statePreferences ( program ) ;
2023-03-26 12:20:32 +03:00
2023-05-11 12:04:42 +03:00
await init ( { rpc : program . rpc , currency , amount , localMode : program . localRpc , privateKey : program . privateKey } ) ;
2023-03-23 06:47:37 +03:00
await deposit ( { currency , amount } ) ;
} ) ;
program
. command ( 'withdraw <note> <recipient> [ETH_purchase]' )
. description (
'Withdraw a note to a recipient account using relayer or specified private key. You can exchange some of your deposit`s tokens to ETH during the withdrawal by specifing ETH_purchase (e.g. 0.01) to pay for gas in future transactions. Also see the --relayer option.'
)
. action ( async ( noteString , recipient , refund ) => {
2023-04-14 20:02:05 +03:00
statePreferences ( program ) ;
2023-03-26 12:20:32 +03:00
2022-02-27 01:32:30 +03:00
const { currency , amount , netId , deposit } = parseNote ( noteString ) ;
2023-03-26 12:20:32 +03:00
2023-05-10 18:49:59 +03:00
await init ( {
rpc : program . rpc ,
noteNetId : netId ,
currency ,
amount ,
2023-05-11 12:04:42 +03:00
localMode : program . localRpc ,
2023-05-10 18:49:59 +03:00
privateKey : program . privateKey
} ) ;
2023-03-23 06:47:37 +03:00
await withdraw ( {
deposit ,
currency ,
amount ,
recipient ,
refund ,
relayerURL : program . relayer
2022-02-27 01:32:30 +03:00
} ) ;
2023-03-23 06:47:37 +03:00
} ) ;
program
. command ( 'balance [address] [token_address]' )
. description ( 'Check ETH and ERC20 balance' )
. action ( async ( address , tokenAddress ) => {
2023-04-14 20:02:05 +03:00
statePreferences ( program ) ;
2023-03-26 12:20:32 +03:00
2023-03-23 06:47:37 +03:00
await init ( { rpc : program . rpc , balanceCheck : true } ) ;
if ( ! address && senderAccount ) {
2023-04-14 20:02:05 +03:00
console . log ( 'Using address' , senderAccount , 'from private key' ) ;
2023-03-23 06:47:37 +03:00
address = senderAccount ;
}
await printETHBalance ( { address , name : 'Account' } ) ;
if ( tokenAddress ) {
await printERC20Balance ( { address , name : 'Account' , tokenAddress } ) ;
}
} ) ;
program
. command ( 'send <address> [amount] [token_address]' )
. description ( 'Send ETH or ERC to address' )
. action ( async ( address , amount , tokenAddress ) => {
2023-04-14 20:02:05 +03:00
statePreferences ( program ) ;
2023-03-26 12:20:32 +03:00
2023-05-11 12:04:42 +03:00
await init ( { rpc : program . rpc , balanceCheck : true , localMode : program . localRpc , privateKey : program . privateKey } ) ;
2023-03-23 06:47:37 +03:00
await send ( { address , amount , tokenAddress } ) ;
} ) ;
program
. command ( 'broadcast <signedTX>' )
. description ( 'Submit signed TX to the remote node' )
. action ( async ( signedTX ) => {
2023-04-14 20:02:05 +03:00
statePreferences ( program ) ;
2023-03-26 12:20:32 +03:00
2023-03-23 06:47:37 +03:00
await init ( { rpc : program . rpc , balanceCheck : true } ) ;
await submitTransaction ( signedTX ) ;
} ) ;
program
. command ( 'compliance <note>' )
. 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.'
)
. action ( async ( noteString ) => {
2023-04-14 20:02:05 +03:00
statePreferences ( program ) ;
2023-03-23 06:47:37 +03:00
const { currency , amount , netId , deposit } = parseNote ( noteString ) ;
2023-03-26 12:20:32 +03:00
2023-03-23 06:47:37 +03:00
await init ( { rpc : program . rpc , noteNetId : netId , currency , amount } ) ;
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 ) ;
if ( ! depositInfo . isSpent ) {
console . log ( 'The note was not spent' ) ;
return ;
}
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' ) ;
} ) ;
program
. command ( 'syncEvents <type> <currency> <amount>' )
2023-04-14 20:02:05 +03:00
. description ( 'Sync the local cache file of deposit / withdrawal events for specific currency.' )
2023-03-23 06:47:37 +03:00
. action ( async ( type , currency , amount ) => {
currency = currency . toLowerCase ( ) ;
2023-03-26 12:20:32 +03:00
2023-04-14 20:02:05 +03:00
statePreferences ( program ) ;
console . log ( 'Starting event sync command' ) ;
2023-03-26 12:20:32 +03:00
2023-03-23 06:47:37 +03:00
await init ( { rpc : program . rpc , type , currency , amount } ) ;
2023-04-14 23:07:02 +03:00
const cachedEvents = await fetchEvents ( { type , currency , amount , filterEvents : false } ) ;
2023-04-14 20:02:05 +03:00
console . log (
'Synced event for' ,
type ,
amount ,
currency . toUpperCase ( ) ,
netName ,
'Tornado instance to block' ,
cachedEvents [ cachedEvents . length - 1 ] . blockNumber
) ;
2023-03-23 06:47:37 +03:00
} ) ;
2023-04-22 19:16:28 +03:00
program
. command ( 'checkCacheValidity <currency> <amount>' )
. description ( 'Check cache file of deposit events for specific currency for validity of the root.' )
. action ( async ( currency , amount ) => {
statePreferences ( program ) ;
const type = 'deposit' ;
await init ( { rpc : program . rpc , type , currency : currency . toLowerCase ( ) , amount } ) ;
const depositCachedEvents = await fetchEvents ( { type , currency , amount } ) ;
const isValidRoot = await isRootValid ( depositCachedEvents ) ;
console . log (
'\nDeposit events tree for' ,
amount ,
currency . toUpperCase ( ) ,
'on' ,
netName ,
'chain' ,
isValidRoot ? 'has valid root' : 'is invalid, unknown root. You need to reset cache to zero array or to latest git state'
) ;
} ) ;
2023-04-14 20:02:05 +03:00
program . command ( 'parseNote <note>' ) . action ( async ( noteString ) => {
const parse = parseNote ( noteString ) ;
netId = parse . netId ;
netName = getCurrentNetworkName ( ) ;
console . log ( '\n=============Note=================' ) ;
console . log ( 'Network:' , netName ) ;
console . log ( 'Denomination:' , parse . amount , parse . currency . toUpperCase ( ) ) ;
console . log ( 'Commitment: ' , parse . deposit . commitmentHex ) ;
console . log ( 'Nullifier Hash: ' , parse . deposit . nullifierHex ) ;
console . log ( '=====================================' , '\n' ) ;
} ) ;
2023-03-23 06:47:37 +03:00
program
. command ( 'test' )
. description ( 'Perform an automated test. It deposits and withdraws one ETH and one ERC20 note. Uses ganache.' )
. action ( async ( ) => {
privateRpc = true ;
console . log ( 'Start performing ETH deposit-withdraw test' ) ;
let currency = 'eth' ;
let amount = '0.1' ;
2023-05-10 18:49:59 +03:00
await init ( { rpc : program . rpc , currency , amount , privateKey : program . privateKey } ) ;
2023-03-23 06:47:37 +03:00
let noteString = await deposit ( { currency , amount } ) ;
let parsedNote = parseNote ( noteString ) ;
await withdraw ( {
deposit : parsedNote . deposit ,
currency ,
amount ,
recipient : senderAccount ,
relayerURL : program . relayer
2022-02-27 01:32:30 +03:00
} ) ;
2023-03-23 06:47:37 +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 } ) ;
parsedNote = parseNote ( noteString ) ;
await withdraw ( {
deposit : parsedNote . deposit ,
currency ,
amount ,
recipient : senderAccount ,
refund : '0.02' ,
relayerURL : program . relayer
} ) ;
} ) ;
try {
2023-04-14 20:02:05 +03:00
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
}
2022-02-27 01:32:30 +03:00
main ( ) ;