Skip to content

Bug fixes & Minor updates #20

Merged
merged 5 commits into from Jan 23, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 67 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,40 @@
# Warning!
# Tornado cli

Command line tool to interact with [Tornado Cash](https://tornadocash.eth.link).

### Warning!
Current cli version doesn't support [Anonymity Mining](https://tornado-cash.medium.com/tornado-cash-governance-proposal-a55c5c7d0703)

### Goerli, Mainnet, Binance Smart Chain
### How to install tornado cli
Download and install [node.js](https://nodejs.org/en/download/).

If you have git installed on your system, clone the master branch.

```bash
$ git clone https://github.com/tornadocash/tornado-cli
```

Or, download the archive file from github

https://github.com/tornadocash/tornado-cli/archive/refs/heads/master.zip

After downloading or cloning the repository, you must install necessary libraries using the following command.

```bash
$ cd tornado-cli
$ npm install
```

If you want to use Tor connection to conceal ip address, install [Tor Browser](https://www.torproject.org/download/) and add `--tor 9150` for `cli.js` if you connect tor with browser.
Note that you should reset your tor connection by restarting the browser every time when you deposit & withdraw otherwise you will have the same exit node used for connection.

### Goerli, Mainnet, Binance Smart Chain, Gnosis Chain, Polygon Network, Arbitrum, Avalanche
1. Add `PRIVATE_KEY` to `.env` file
2. `./cli.js --help`
2. `node cli.js --help`

Example:
```bash
$ ./cli.js deposit ETH 0.1 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --tor 9050
$ node cli.js deposit ETH 0.1 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --tor 9150

Your note: tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652
Tornado ETH balance is 8.9
@@ -18,7 +45,7 @@ Sender account ETH balance is 1004873.361652048361352542
```

```bash
$ ./cli.js withdraw tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --relayer https://goerli-frelay.duckdns.org --tor 9050
$ node cli.js withdraw tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --relayer https://goerli-frelay.duckdns.org --tor 9150

Relay address: 0x6A31736e7490AbE5D5676be059DFf064AB4aC754
Getting current state from tornado contract
@@ -520,6 +547,41 @@ Infura API key fetched from https://rpc.info (Same one with Metamask)
"cachedUrl":"https://goerli-v2.defidevotee.xyz"
}
}
},
"netId10":{
"rpcUrls":{
"Optimism":{
"name":"Optimism Public RPC",
"url":"https://mainnet.optimism.io"
}
},
"relayers":{
"optimism.t-relay.eth":{
"url":"optimism.t-relay.eth",
"name":"optimism.t-relay.eth",
"cachedUrl":"https://optimism.t-relay.online/"
},
"optimism.therelayer.eth":{
"url":"optimism.therelayer.eth",
"name":"optimism.therelayer.eth",
"cachedUrl":"https://optimism.therelayer.xyz/"
},
"optimism.relayer-service.eth":{
"url":"optimism.relayer-service.eth",
"name":"optimism.relayer-service.eth",
"cachedUrl":"https://optimism-relayer.hertz.zone/"
},
"optimism.torn.eth":{
"url":"optimism.torn.eth",
"name":"optimism.torn.eth",
"cachedUrl":"https://optimism.torn.cash/"
},
"optimism.relaymy.eth":{
"url":"optimism.relaymy.eth",
"name":"optimism.relaymy.eth",
"cachedUrl":"https://optimism.relaymy.xyz/"
}
}
}
}
```
262 changes: 173 additions & 89 deletions cli.js
Original file line number Diff line number Diff line change
@@ -71,7 +71,11 @@ async function generateTransaction(to, encodedData, value = 0) {
const bumped = Math.floor(fetchedGas * 1.3)
gasLimit = web3.utils.toHex(bumped)
}
await estimateGas();
if (encodedData) {
await estimateGas();
} else {
gasLimit = web3.utils.toHex(21000);
}

async function txoptions() {
// Generate EIP-1559 transaction
@@ -85,6 +89,16 @@ async function generateTransaction(to, encodedData, value = 0) {
gas : gasLimit,
data : encodedData
}
} else if (netId == 137 || netId == 43114) {
tx = {
to : to,
value : value,
nonce : nonce,
maxFeePerGas : gasPrice,
maxPriorityFeePerGas : gasPrice,
gas : gasLimit,
data : encodedData
}
} else {
tx = {
to : to,
@@ -144,7 +158,7 @@ async function deposit({ currency, amount }) {
const noteString = `tornado-${currency}-${amount}-${netId}-${note}`
console.log(`Your note: ${noteString}`)
await backupNote({ currency, amount, netId, note, noteString })
if (currency === 'eth' || currency === 'bnb' || currency === 'xdai' || currency === 'matic' || currency === 'avax') {
if (currency === netSymbol.toLowerCase()) {
await printETHBalance({ address: tornadoContract._address, name: 'Tornado contract', symbol: currency.toUpperCase() })
await printETHBalance({ address: senderAccount, name: 'Sender account', symbol: currency.toUpperCase() })
const value = isLocalRPC ? ETH_AMOUNT : fromDecimals({ amount, decimals: 18 })
@@ -269,7 +283,7 @@ async function generateProof({ deposit, currency, amount, recipient, relayerAddr
*/
async function withdraw({ deposit, currency, amount, recipient, relayerURL, torPort, refund = '0' }) {
let options = {};
if (currency === 'eth' && refund !== '0') {
if (currency === netSymbol.toLowerCase() && refund !== '0') {
throw new Error('The ETH purchase is supposted to be 0 for ETH withdrawals')
}
refund = toWei(refund)
@@ -328,7 +342,7 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, torP

// check if the address of recepient matches with the account of provided private key from environment to prevent accidental use of deposit address for withdrawal transaction.
const { address } = await web3.eth.accounts.privateKeyToAccount('0x' + PRIVATE_KEY)
assert(recipient.toLowerCase() == address.toLowerCase(), 'Withdrawal amount recepient',recipient,'mismatches with the account of provided private key from environment file',address)
assert(recipient.toLowerCase() == address.toLowerCase(), 'Withdrawal amount recepient mismatches with the account of provided private key from environment file')

const { proof, args } = await generateProof({ deposit, currency, amount, recipient, refund })

@@ -338,6 +352,53 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, torP
console.log('Done withdrawal from Tornado Cash')
}

/**
* Do an ETH / ERC20 send
* @param address Recepient address
* @param amount Amount to send
* @param tokenAddress ERC20 token address
*/
async function send({ address, amount, tokenAddress }) {
// using private key
assert(senderAccount != null, 'Error! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you send')
if (tokenAddress) {
const erc20ContractJson = require('./build/contracts/ERC20Mock.json')
erc20 = new web3.eth.Contract(erc20ContractJson.abi, tokenAddress)
const balance = await erc20.methods.balanceOf(senderAccount).call()
const decimals = await erc20.methods.decimals().call()
const toSend = amount * Math.pow(10, decimals)
if (balance < toSend) {
console.error("You have",toDecimals(balance, decimals, (balance.length + decimals)).toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ","),(await erc20.methods.symbol().call()),", you can't send more than you have")
process.exit(1);
}
await generateTransaction(tokenAddress, await erc20.methods.transfer(address, toBN(toSend)).encodeABI())
console.log('Sent',amount,(await erc20.methods.symbol().call()),'to',address);
} else {
const balance = await web3.eth.getBalance(senderAccount)
if (balance == 0) {
console.error("You have 0 balance, can't send")
process.exit(1);
}
if (!amount) {
console.log('Amount not defined, sending all available amounts')
const gasPrice = await fetchGasPrice()
const gasLimit = 21000;
if (netId == 1 || netId == 5) {
const priorityFee = await gasPrices(3)
amount = (balance - (gasLimit * (parseInt(gasPrice) + parseInt(priorityFee))))
} else {
amount = (balance - (gasLimit * parseInt(gasPrice)))
}
}
if (balance < amount) {
console.error("You have",web3.utils.fromWei(toHex(balance)),netSymbol,", you can't send more than you have.")
process.exit(1);
}
await generateTransaction(address, null, amount)
console.log('Sent',web3.utils.fromWei(toHex(amount)),netSymbol,'to',address);
}
}

function getStatus(id, relayerURL, options) {
return new Promise((resolve) => {
async function getRelayerStatus() {
@@ -476,6 +537,8 @@ function getExplorerLink() {
return 'goerli.etherscan.io'
case 42:
return 'kovan.etherscan.io'
case 10:
return 'optimistic.etherscan.io'
default:
return 'etherscan.io'
}
@@ -500,6 +563,8 @@ function getCurrentNetworkName() {
return 'Goerli'
case 42:
return 'Kovan'
case 137:
return 'Optimism'
default:
return 'localRPC'
}
@@ -562,23 +627,7 @@ function calculateFee({ currency, gasPrice, amount, refund, ethPrices, relayerSe
const expense = toBN(gasPrice).mul(toBN(5e5))
let desiredFee
switch (currency) {
case 'eth': {
desiredFee = expense.add(feePercent)
break
}
case 'bnb': {
desiredFee = expense.add(feePercent)
break
}
case 'xdai': {
desiredFee = expense.add(feePercent)
break
}
case 'matic': {
desiredFee = expense.add(feePercent)
break
}
case 'avax': {
case netSymbol.toLowerCase(): {
desiredFee = expense.add(feePercent)
break
}
@@ -618,6 +667,21 @@ function waitForTxReceipt({ txHash, attempts = 60, delay = 1000 }) {
})
}

function initJson(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, 'utf8', (error, data) => {
if (error) {
reject(error);
}
try {
resolve(JSON.parse(data));
} catch (error) {
resolve([]);
}
});
});
};

function loadCachedEvents({ type, currency, amount }) {
try {
const module = require(`./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`)
@@ -640,86 +704,93 @@ function loadCachedEvents({ type, currency, amount }) {
}

async function fetchEvents({ type, currency, amount}) {
let leafIndex = -1
let events = [];
let fetchedEvents, chunks, targetBlock;

if (type === "withdraw") {
type = "withdrawal"
}

const cachedEvents = loadCachedEvents({ type, currency, amount })
const startBlock = cachedEvents.lastBlock + 1

console.log("Loaded cached",amount,currency.toUpperCase(),type,"events for",startBlock,"block")
console.log("Fetching",amount,currency.toUpperCase(),type,"events for",netName,"network")

async function fetchLatestEvents() {
targetBlock = await web3.eth.getBlockNumber();
chunks = 1000;
fetchedEvents = [];
for (let i=startBlock; i < targetBlock; i+=chunks) {
await tornadoContract.getPastEvents(capitalizeFirstLetter(type), {
fromBlock: i,
toBlock: i+chunks-1,
}).then(r => { fetchedEvents = fetchedEvents.concat(r); console.log("Fetched",amount,currency.toUpperCase(),type,"events from block:", i) }, err => { console.error(i + " failed fetching",type,"events from node", err) }).catch(console.log);
}
}
await fetchLatestEvents()

async function mapDepositEvents() {
fetchedEvents = fetchedEvents.map(({ blockNumber, transactionHash, returnValues }) => {
const { commitment, leafIndex, timestamp } = returnValues
return {
blockNumber,
transactionHash,
commitment,
leafIndex: Number(leafIndex),
timestamp
}
})
}

async function mapWithdrawEvents() {
fetchedEvents = fetchedEvents.map(({ blockNumber, transactionHash, returnValues }) => {
const { nullifierHash, to, fee } = returnValues
return {
blockNumber,
transactionHash,
nullifierHash,
to,
fee
async function syncEvents() {
try {
let targetBlock = await web3.eth.getBlockNumber();
let chunks = 1000;
for (let i=startBlock; i < targetBlock; i+=chunks) {
let fetchedEvents = [];
async function fetchLatestEvents(i) {
await tornadoContract.getPastEvents(capitalizeFirstLetter(type), {
fromBlock: i,
toBlock: i+chunks-1,
}).then(r => { fetchedEvents = fetchedEvents.concat(r); console.log("Fetched",amount,currency.toUpperCase(),type,"events to block:", i) }, err => { console.error(i + " failed fetching",type,"events from node", err); process.exit(1); }).catch(console.log);
}

async function mapDepositEvents() {
fetchedEvents = fetchedEvents.map(({ blockNumber, transactionHash, returnValues }) => {
const { commitment, leafIndex, timestamp } = returnValues
return {
blockNumber,
transactionHash,
commitment,
leafIndex: Number(leafIndex),
timestamp
}
})
}

async function mapWithdrawEvents() {
fetchedEvents = fetchedEvents.map(({ blockNumber, transactionHash, returnValues }) => {
const { nullifierHash, to, fee } = returnValues
return {
blockNumber,
transactionHash,
nullifierHash,
to,
fee
}
})
}

async function mapLatestEvents() {
if (type === "deposit"){
await mapDepositEvents();
} else {
await mapWithdrawEvents();
}
}

async function updateCache() {
try {
const fileName = `./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`
const localEvents = await initJson(fileName);
const events = localEvents.concat(fetchedEvents);
await fs.writeFileSync(fileName, JSON.stringify(events, null, 2), 'utf8')
} catch (error) {
throw new Error('Writing cache file failed:',error)
}
}
await fetchLatestEvents(i);
await mapLatestEvents();
await updateCache();
}
})
}

async function mapLatestEvents() {
console.log("Mapping",amount,currency.toUpperCase(),type,"events, please wait")
if (type === "deposit"){
await mapDepositEvents();
} else {
await mapWithdrawEvents();
} catch (error) {
throw new Error("Error while updating cache")
process.exit(1)
}
}
await mapLatestEvents();

console.log("Gathering cached events + collected events from node")

async function concatEvents() {
events = cachedEvents.events.concat(fetchedEvents)
await syncEvents();

async function loadUpdatedEvents() {
const fileName = `./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`
const updatedEvents = await initJson(fileName);
const updatedBlock = updatedEvents[updatedEvents.length - 1].blockNumber
console.log("Cache updated for Tornado",type,amount,currency,"instance to block",updatedBlock,"successfully")
console.log('Total events:', updatedEvents.length)
return updatedEvents;
}
await concatEvents();

console.log('Total events:', events.length)

async function updateCache() {
try {
await fs.writeFileSync(`./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`, JSON.stringify(events, null, 2), 'utf8')
console.log("Cache updated for Tornado",type,amount,currency,"instance successfully")
} catch (e) {
throw new Error('Writing cache file failed:',e)
}
}
await updateCache();
const events = await loadUpdatedEvents();

return events
}
@@ -846,7 +917,12 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100', torPort,
MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT || 20
ETH_AMOUNT = process.env.ETH_AMOUNT
TOKEN_AMOUNT = process.env.TOKEN_AMOUNT
PRIVATE_KEY = process.env.PRIVATE_KEY
const privKey = process.env.PRIVATE_KEY
if (privKey.includes("0x")) {
PRIVATE_KEY = process.env.PRIVATE_KEY.substring(2)
} else {
PRIVATE_KEY = process.env.PRIVATE_KEY
}
if (PRIVATE_KEY) {
const account = web3.eth.accounts.privateKeyToAccount('0x' + PRIVATE_KEY)
web3.eth.accounts.wallet.add('0x' + PRIVATE_KEY)
@@ -893,6 +969,7 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100', torPort,
process.exit(1)
}
}
netSymbol = getCurrentNetworkSymbol()
tornado = new web3.eth.Contract(contractJson, tornadoAddress)
tornadoContract = new web3.eth.Contract(instanceJson, tornadoInstance)
contractAddress = tornadoAddress
@@ -958,6 +1035,13 @@ async function main() {
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) => {
await init({ rpc: program.rpc, torPort: program.tor, balanceCheck: true })
await send({ address, amount, tokenAddress })
})
program
.command('compliance <note>')
.description(
20 changes: 20 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
@@ -330,5 +330,25 @@ module.exports = {
},
proxy: '0x0D5550d52428E7e3175bfc9550207e4ad3859b17',
},
netId42161: {
'eth': {
'instanceAddress': {
'0.1': '0x84443CFd09A48AF6eF360C6976C5392aC5023a1F',
'1': '0xd47438C816c9E7f2E2888E060936a499Af9582b3',
'10': '0x330bdFADE01eE9bF63C209Ee33102DD334618e0a',
'100': '0x1E34A77868E19A6647b1f2F47B51ed72dEDE95DD'
},
'deployedBlockNumber': {
'0.1': 2243707,
'1': 2243709,
'10': 2243735,
'100': 2243749
},
'miningEnabled': false,
'symbol': 'ETH',
'decimals': 18
},
proxy: '0x0D5550d52428E7e3175bfc9550207e4ad3859b17',
},
}
}