mirror of
https://github.com/tornadocash/tornado-cli.git
synced 2024-11-21 17:27:07 +01:00
Merge branch 'master' into fixTorando
This commit is contained in:
commit
4dbdd42252
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": true,
|
||||
"semi": false,
|
||||
"printWidth": 130
|
||||
}
|
35
README.md
35
README.md
@ -7,24 +7,25 @@ Current cli version doesn't support [Anonymity Mining](https://tornado-cash.medi
|
||||
|
||||
Example:
|
||||
```bash
|
||||
./cli.js deposit ETH 0.1 --rpc https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448
|
||||
$ ./cli.js deposit ETH 0.1 --rpc https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448
|
||||
|
||||
Your note: tornado-eth-0.1-42-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652
|
||||
Tornado ETH balance is 8.9
|
||||
Sender account ETH balance is 1004873.470619891361352542
|
||||
Submitting deposit transaction
|
||||
Tornado ETH balance is 9
|
||||
Sender account ETH balance is 1004873.361652048361352542
|
||||
```
|
||||
> Your note: tornado-eth-0.1-42-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652
|
||||
> Tornado ETH balance is 8.9
|
||||
> Sender account ETH balance is 1004873.470619891361352542
|
||||
> Submitting deposit transaction
|
||||
> Tornado ETH balance is 9
|
||||
> Sender account ETH balance is 1004873.361652048361352542
|
||||
|
||||
```bash
|
||||
./cli.js withdraw tornado-eth-0.1-42-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448 --relayer https://kovan-frelay.duckdns.org
|
||||
```
|
||||
$ ./cli.js withdraw tornado-eth-0.1-42-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448 --relayer https://kovan-frelay.duckdns.org
|
||||
|
||||
> Relay address: 0x6A31736e7490AbE5D5676be059DFf064AB4aC754
|
||||
> Getting current state from tornado contract
|
||||
> Generating SNARK proof
|
||||
> Proof time: 9117.051ms
|
||||
> Sending withdraw transaction through relay
|
||||
> Transaction submitted through the relay. View transaction on etherscan https://kovan.etherscan.io/tx/0xcb21ae8cad723818c6bc7273e83e00c8393fcdbe74802ce5d562acad691a2a7b
|
||||
> Transaction mined in block 17036120
|
||||
> Done
|
||||
Relay address: 0x6A31736e7490AbE5D5676be059DFf064AB4aC754
|
||||
Getting current state from tornado contract
|
||||
Generating SNARK proof
|
||||
Proof time: 9117.051ms
|
||||
Sending withdraw transaction through relay
|
||||
Transaction submitted through the relay. View transaction on etherscan https://kovan.etherscan.io/tx/0xcb21ae8cad723818c6bc7273e83e00c8393fcdbe74802ce5d562acad691a2a7b
|
||||
Transaction mined in block 17036120
|
||||
Done
|
||||
```
|
||||
|
135
cli.js
135
cli.js
@ -23,14 +23,14 @@ let web3, tornado, mixerContract, tornadoInstance, circuit, proving_key, groth16
|
||||
let MERKLE_TREE_HEIGHT, ETH_AMOUNT, TOKEN_AMOUNT, PRIVATE_KEY
|
||||
|
||||
/** Whether we are in a browser or node.js */
|
||||
const inBrowser = (typeof window !== 'undefined')
|
||||
const inBrowser = typeof window !== 'undefined'
|
||||
let isLocalRPC = false
|
||||
|
||||
/** Generate random number of specified byte length */
|
||||
const rbigint = nbytes => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
||||
const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
||||
|
||||
/** Compute pedersen hash */
|
||||
const pedersenHash = data => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0]
|
||||
const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0]
|
||||
|
||||
/** BigNumber to hex string of specified length */
|
||||
function toHex(number, length = 32) {
|
||||
@ -69,7 +69,10 @@ function createDeposit({ nullifier, secret }) {
|
||||
* @param amount Deposit amount
|
||||
*/
|
||||
async function deposit({ currency, amount }) {
|
||||
const deposit = createDeposit({ nullifier: rbigint(31), secret: rbigint(31) })
|
||||
const deposit = createDeposit({
|
||||
nullifier: rbigint(31),
|
||||
secret: rbigint(31)
|
||||
})
|
||||
const note = toHex(deposit.preimage, 62)
|
||||
const noteString = `tornado-${currency}-${amount}-${netId}-${note}`
|
||||
console.log(`Your note: ${noteString}`)
|
||||
@ -81,7 +84,8 @@ async function deposit({ currency, amount }) {
|
||||
await tornado.methods.deposit(tornadoInstance, toHex(deposit.commitment), []).send({ value, from: senderAccount, gas: 2e6 })
|
||||
await printETHBalance({ address: tornado._address, name: 'Tornado' })
|
||||
await printETHBalance({ address: senderAccount, name: 'Sender account' })
|
||||
} else { // a token
|
||||
} else {
|
||||
// a token
|
||||
await printERC20Balance({ address: tornado._address, name: 'Tornado' })
|
||||
await printERC20Balance({ address: senderAccount, name: 'Sender account' })
|
||||
const decimals = isLocalRPC ? 18 : config.deployments[`netId${netId}`][currency].decimals
|
||||
@ -116,6 +120,7 @@ async function deposit({ currency, amount }) {
|
||||
async function generateMerkleProof(deposit, amount) {
|
||||
let leafIndex = -1
|
||||
// Get all deposit events from smart contract and assemble merkle tree from them
|
||||
|
||||
const cachedEvents = loadCachedEvents({ type: 'Deposit', amount })
|
||||
|
||||
const startBlock = cachedEvents.lastBlock
|
||||
@ -189,7 +194,7 @@ async function generateProof({ deposit, amount, recipient, relayerAddress = 0, f
|
||||
nullifier: deposit.nullifier,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathIndices: path_index
|
||||
}
|
||||
|
||||
console.log('Generating SNARK proof')
|
||||
@ -227,13 +232,21 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
|
||||
const relayerStatus = await axios.get(relayerURL + '/status')
|
||||
|
||||
const { rewardAccount, netId, ethPrices, tornadoServiceFee } = relayerStatus.data
|
||||
assert(netId === await web3.eth.net.getId() || netId === '*', 'This relay is for different network')
|
||||
assert(netId === (await web3.eth.net.getId()) || netId === '*', 'This relay is for different network')
|
||||
console.log('Relay address: ', rewardAccount)
|
||||
|
||||
const gasPrice = await fetchGasPrice()
|
||||
|
||||
const decimals = isLocalRPC ? 18 : config.deployments[`netId${netId}`][currency].decimals
|
||||
const fee = calculateFee({ currency, gasPrice, amount, refund, ethPrices, relayerServiceFee: tornadoServiceFee, decimals })
|
||||
const fee = calculateFee({
|
||||
currency,
|
||||
gasPrice,
|
||||
amount,
|
||||
refund,
|
||||
ethPrices,
|
||||
relayerServiceFee: tornadoServiceFee,
|
||||
decimals
|
||||
})
|
||||
if (fee.gt(fromDecimals({ amount, decimals }))) {
|
||||
throw new Error('Too high refund')
|
||||
}
|
||||
@ -242,7 +255,11 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
|
||||
|
||||
console.log('Sending withdraw transaction through relay')
|
||||
try {
|
||||
const response = await axios.post(relayerURL + '/v1/tornadoWithdraw', { contract: tornadoInstance, proof, args })
|
||||
const response = await axios.post(relayerURL + '/v1/tornadoWithdraw', {
|
||||
contract: tornadoInstance,
|
||||
proof,
|
||||
args
|
||||
})
|
||||
|
||||
const { id } = response.data
|
||||
|
||||
@ -255,18 +272,22 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
|
||||
console.error(e.message)
|
||||
}
|
||||
}
|
||||
} else { // using private key
|
||||
} else {
|
||||
// using private key
|
||||
const { proof, args } = await generateProof({ deposit, recipient, refund })
|
||||
|
||||
console.log('Submitting withdraw transaction')
|
||||
await tornado.methods.withdraw(tornadoInstance, proof, ...args).send({ from: senderAccount, value: refund.toString(), gas: 1e6 })
|
||||
await tornado.methods
|
||||
.withdraw(tornadoInstance, proof, ...args)
|
||||
.send({ from: senderAccount, value: refund.toString(), gas: 1e6 })
|
||||
.on('transactionHash', function (txHash) {
|
||||
if (netId === 1 || netId === 42) {
|
||||
console.log(`View transaction on etherscan https://${getCurrentNetworkName()}etherscan.io/tx/${txHash}`)
|
||||
} else {
|
||||
console.log(`The transaction hash is ${txHash}`)
|
||||
}
|
||||
}).on('error', function (e) {
|
||||
})
|
||||
.on('error', function (e) {
|
||||
console.error('on transactionHash error', e.message)
|
||||
})
|
||||
}
|
||||
@ -289,7 +310,9 @@ function getStatus (id, relayerURL) {
|
||||
|
||||
if (status === 'CONFIRMED') {
|
||||
const receipt = await waitForTxReceipt({ txHash })
|
||||
console.log(`Transaction submitted through the relay. View transaction on etherscan https://${getCurrentNetworkName()}etherscan.io/tx/${txHash}`)
|
||||
console.log(
|
||||
`Transaction submitted through the relay. View transaction on etherscan https://${getCurrentNetworkName()}etherscan.io/tx/${txHash}`
|
||||
)
|
||||
console.log('Transaction mined in block', receipt.blockNumber)
|
||||
resolve(status)
|
||||
}
|
||||
@ -322,9 +345,7 @@ function fromDecimals({ amount, decimals }) {
|
||||
// Split it into a whole and fractional part
|
||||
const comps = ether.split('.')
|
||||
if (comps.length > 2) {
|
||||
throw new Error(
|
||||
'[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal points'
|
||||
)
|
||||
throw new Error('[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal points')
|
||||
}
|
||||
|
||||
let whole = comps[0]
|
||||
@ -337,9 +358,7 @@ function fromDecimals({ amount, decimals }) {
|
||||
fraction = '0'
|
||||
}
|
||||
if (fraction.length > baseLength) {
|
||||
throw new Error(
|
||||
'[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal places'
|
||||
)
|
||||
throw new Error('[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal places')
|
||||
}
|
||||
|
||||
while (fraction.length < baseLength) {
|
||||
@ -401,7 +420,6 @@ function getCurrentNetworkName() {
|
||||
case 42:
|
||||
return 'kovan.'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function gasPrices(value = 80) {
|
||||
@ -423,9 +441,8 @@ async function fetchGasPrice() {
|
||||
}
|
||||
|
||||
function calculateFee({ currency, gasPrice, amount, refund, ethPrices, relayerServiceFee, decimals }) {
|
||||
const decimalsPoint = Math.floor(relayerServiceFee) === Number(relayerServiceFee) ?
|
||||
0 :
|
||||
relayerServiceFee.toString().split('.')[1].length
|
||||
const decimalsPoint =
|
||||
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))
|
||||
@ -437,7 +454,8 @@ function calculateFee({ currency, gasPrice, amount, refund, ethPrices, relayerSe
|
||||
break
|
||||
}
|
||||
default: {
|
||||
desiredFee = expense.add(toBN(refund))
|
||||
desiredFee = expense
|
||||
.add(toBN(refund))
|
||||
.mul(toBN(10 ** decimals))
|
||||
.div(toBN(ethPrices[currency]))
|
||||
desiredFee = desiredFee.add(feePercent)
|
||||
@ -512,7 +530,12 @@ function parseNote(noteString) {
|
||||
const deposit = createDeposit({ nullifier, secret })
|
||||
const netId = Number(match.groups.netId)
|
||||
|
||||
return { currency: match.groups.currency, amount: match.groups.amount, netId, deposit }
|
||||
return {
|
||||
currency: match.groups.currency,
|
||||
amount: match.groups.amount,
|
||||
netId,
|
||||
deposit
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDepositData({ deposit }) {
|
||||
@ -533,7 +556,13 @@ async function loadDepositData({ deposit }) {
|
||||
const isSpent = await tornado.methods.isSpent(deposit.nullifierHex).call()
|
||||
const receipt = await web3.eth.getTransactionReceipt(txHash)
|
||||
|
||||
return { timestamp, txHash, isSpent, from: receipt.from, commitment: deposit.commitmentHex }
|
||||
return {
|
||||
timestamp,
|
||||
txHash,
|
||||
isSpent,
|
||||
from: receipt.from,
|
||||
commitment: deposit.commitmentHex
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('loadDepositData', e)
|
||||
}
|
||||
@ -569,9 +598,7 @@ async function loadWithdrawalData({ amount, currency, deposit }) {
|
||||
|
||||
const fee = withdrawEvent.fee
|
||||
const decimals = config.deployments[`netId${netId}`][currency].decimals
|
||||
const withdrawalAmount = toBN(fromDecimals({ amount, decimals })).sub(
|
||||
toBN(fee)
|
||||
)
|
||||
const withdrawalAmount = toBN(fromDecimals({ amount, decimals })).sub(toBN(fee))
|
||||
const { timestamp } = await web3.eth.getBlock(withdrawEvent.blockHash)
|
||||
return {
|
||||
amount: toDecimals(withdrawalAmount, decimals, 9),
|
||||
@ -595,7 +622,9 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100' }) {
|
||||
if (inBrowser) {
|
||||
// Initialize using injected web3 (Metamask)
|
||||
// To assemble web version run `npm run browserify`
|
||||
web3 = new Web3(window.web3.currentProvider, null, { transactionConfirmationBlocks: 1 })
|
||||
web3 = new Web3(window.web3.currentProvider, null, {
|
||||
transactionConfirmationBlocks: 1
|
||||
})
|
||||
contractJson = await (await fetch('build/contracts/TornadoProxy.abi.json')).json()
|
||||
mixerJson = await (await fetch('build/contracts/Mixer.abi.json')).json()
|
||||
circuit = await (await fetch('build/circuits/tornado.json')).json()
|
||||
@ -678,7 +707,9 @@ async function main() {
|
||||
.option('-R, --relayer <URL>', 'Withdraw via relayer')
|
||||
program
|
||||
.command('deposit <currency> <amount>')
|
||||
.description('Submit a deposit of specified currency and amount from default eth account and return the resulting note. The currency is one of (ETH|DAI|cDAI|USDC|cUSDC|USDT). The amount depends on currency, see config.js file or visit https://tornado.cash.')
|
||||
.description(
|
||||
'Submit a deposit of specified currency and amount from default eth account and return the resulting note. The currency is one of (ETH|DAI|cDAI|USDC|cUSDC|USDT). The amount depends on currency, see config.js file or visit https://tornado.cash.'
|
||||
)
|
||||
.action(async (currency, amount) => {
|
||||
currency = currency.toLowerCase()
|
||||
await init({ rpc: program.rpc, currency, amount })
|
||||
@ -686,11 +717,20 @@ async function main() {
|
||||
})
|
||||
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.')
|
||||
.description(
|
||||
'Withdraw a note to a recipient account using relayer or specified private key. You can exchange some of your deposit`s tokens to ETH during the withdrawal by specifing ETH_purchase (e.g. 0.01) to pay for gas in future transactions. Also see the --relayer option.'
|
||||
)
|
||||
.action(async (noteString, recipient, refund) => {
|
||||
const { currency, amount, netId, deposit } = parseNote(noteString)
|
||||
await init({ rpc: program.rpc, noteNetId: netId, currency, amount })
|
||||
await withdraw({ deposit, currency, amount, recipient, refund, relayerURL: program.relayer })
|
||||
await withdraw({
|
||||
deposit,
|
||||
currency,
|
||||
amount,
|
||||
recipient,
|
||||
refund,
|
||||
relayerURL: program.relayer
|
||||
})
|
||||
})
|
||||
program
|
||||
.command('balance <address> [token_address]')
|
||||
@ -704,7 +744,9 @@ async function main() {
|
||||
})
|
||||
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.')
|
||||
.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) => {
|
||||
const { currency, amount, netId, deposit } = parseNote(noteString)
|
||||
await init({ rpc: program.rpc, noteNetId: netId, currency, amount })
|
||||
@ -720,7 +762,11 @@ async function main() {
|
||||
console.log('The note was not spent')
|
||||
}
|
||||
|
||||
const withdrawInfo = await loadWithdrawalData({ amount, currency, deposit })
|
||||
const withdrawInfo = await loadWithdrawalData({
|
||||
amount,
|
||||
currency,
|
||||
deposit
|
||||
})
|
||||
const withdrawalDate = new Date(withdrawInfo.timestamp * 1000)
|
||||
console.log('\n=============Withdrawal==============')
|
||||
console.log('Withdrawal :', withdrawInfo.amount, currency)
|
||||
@ -740,15 +786,28 @@ async function main() {
|
||||
await init({ rpc: program.rpc, currency, amount })
|
||||
let noteString = await deposit({ currency, amount })
|
||||
let parsedNote = parseNote(noteString)
|
||||
await withdraw({ deposit: parsedNote.deposit, currency, amount, recipient: senderAccount, relayerURL: program.relayer })
|
||||
await withdraw({
|
||||
deposit: parsedNote.deposit,
|
||||
currency,
|
||||
amount,
|
||||
recipient: senderAccount,
|
||||
relayerURL: program.relayer
|
||||
})
|
||||
|
||||
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 })
|
||||
parsedNote = parseNote(noteString)
|
||||
await withdraw({
|
||||
deposit: parsedNote.deposit,
|
||||
currency,
|
||||
amount,
|
||||
recipient: senderAccount,
|
||||
refund: '0.02',
|
||||
relayerURL: program.relayer
|
||||
})
|
||||
})
|
||||
try {
|
||||
await program.parseAsync(process.argv)
|
||||
|
Loading…
Reference in New Issue
Block a user