gas speed param, fee printing & confirmation prompts

This commit is contained in:
gozzy 2023-03-26 09:20:32 +00:00
parent 4b4364e2fe
commit 2f82f4b3da

152
cli.js
View File

@ -20,11 +20,20 @@ const program = require('commander');
const { GasPriceOracle } = require('gas-price-oracle'); const { GasPriceOracle } = require('gas-price-oracle');
const { SocksProxyAgent } = require('socks-proxy-agent'); const { SocksProxyAgent } = require('socks-proxy-agent');
const is_ip_private = require('private-ip'); const is_ip_private = require('private-ip');
const readline = require('readline');
let web3, torPort, tornado, tornadoContract, tornadoInstance, circuit, proving_key, groth16, erc20, senderAccount, netId, netName, netSymbol, doNotSubmitTx, multiCall, privateRpc, subgraph; const prompt = readline.createInterface({ input: process.stdin, output: process.stdout });
const gasSpeedPreferences = ['instant', 'fast', 'standard', 'low'];
let web3, torPort, tornado, tornadoContract, tornadoInstance, circuit, proving_key, groth16, erc20, senderAccount, netId, netName, netSymbol, multiCall, subgraph;
let MERKLE_TREE_HEIGHT, ETH_AMOUNT, TOKEN_AMOUNT, PRIVATE_KEY; let MERKLE_TREE_HEIGHT, ETH_AMOUNT, TOKEN_AMOUNT, PRIVATE_KEY;
let isTestRPC = false; /** Command state parameters */
let preferenceSpeed = gasSpeedPreferences[0];
let isTestRPC, eipGasSupport = false;
let shouldPromptConfirmation = true;
let doNotSubmitTx, privateRpc;
/** ----------------------------------------- **/
/** Generate random number of specified byte length */ /** 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));
@ -104,12 +113,25 @@ async function generateTransaction(to, encodedData, value = 0) {
const bumped = Math.floor(fetchedGas * 1.3); const bumped = Math.floor(fetchedGas * 1.3);
return web3.utils.toHex(bumped); return web3.utils.toHex(bumped);
} }
if (encodedData) { if (encodedData) {
gasLimit = await estimateGas(); gasLimit = await estimateGas();
} else { } else {
gasLimit = web3.utils.toHex(21000); gasLimit = web3.utils.toHex(23000);
} }
const isNumRString = typeof value == 'string' || typeof value == 'number'
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}`);
/** ----------------------------------------- **/
function txoptions() { function txoptions() {
// Generate EIP-1559 transaction // Generate EIP-1559 transaction
if (netId == 1) { if (netId == 1) {
@ -143,8 +165,14 @@ async function generateTransaction(to, encodedData, value = 0) {
} }
} }
} }
if (shouldPromptConfirmation) {
await promptConfirmation();
}
const tx = txoptions(); const tx = txoptions();
const signed = await web3.eth.accounts.signTransaction(tx, PRIVATE_KEY); const signed = await web3.eth.accounts.signTransaction(tx, PRIVATE_KEY);
if (!doNotSubmitTx) { if (!doNotSubmitTx) {
await submitTransaction(signed.rawTransaction); await submitTransaction(signed.rawTransaction);
} else { } else {
@ -404,6 +432,20 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
const { proof, args } = await generateProof({ deposit, currency, amount, recipient, relayerAddress: rewardAccount, fee, refund }); const { proof, args } = await generateProof({ deposit, currency, amount, recipient, relayerAddress: rewardAccount, fee, refund });
console.log('Sending withdraw transaction through relay'); console.log('Sending withdraw transaction through relay');
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();
}
try { try {
const response = await axios.post(relayerURL + '/v1/tornadoWithdraw', { const response = await axios.post(relayerURL + '/v1/tornadoWithdraw', {
contract: tornadoInstance, contract: tornadoInstance,
@ -698,19 +740,20 @@ function gasPrices(value = 5) {
async function fetchGasPrice() { async function fetchGasPrice() {
try { try {
const options = { /** Gas preferences **/
chainId: netId console.log('Gas speed preference: ', preferenceSpeed);
} /** ----------------------------------------------- **/
// Bump fees for Ethereum network const options = { chainId: netId }
try { try {
const isLegacy = true const isLegacy = !eipGasSupport
const oracle = new GasPriceOracle(options); const oracle = new GasPriceOracle(options);
const gas = await oracle.gasPrices({ isLegacy }); const gas = await oracle.gasPrices({ isLegacy });
if (netId === 1) { if (netId === 1) {
return gasPricesETH(gas.instant); return gasPricesETH(gas[preferenceSpeed]);
} else { } else {
return gasPrices(gas.instant) return gasPrices(gas[preferenceSpeed])
} }
} catch(e) { } catch(e) {
const wei = await web3.eth.getGasPrice(); const wei = await web3.eth.getGasPrice();
@ -1178,6 +1221,33 @@ async function loadWithdrawalData({ amount, currency, deposit }) {
} }
} }
function statePreferences(program) {
const isPref = gasSpeedPreferences.includes(program.gas_speed);
if (program.gas_speed && !isPref) {
throw new Error("Invalid gas speed preference");
} else if (program.gas_speed) {
preferenceSpeed = program.gas_speed;
}
if(program.noconfirmation) {
shouldPromptConfirmation = false;
} if(program.onlyrpc) {
privateRpc = true;
} if(program.tor) {
torPort = program.tor;
}
}
async function promptConfirmation() {
const query = "Confirm the transaction [Y/n] ";
const confirmation = await new Promise(resolve => prompt.question(query, resolve));
if (confirmation.toUpperCase() !== "Y") {
throw new Error("Transaction rejected");
}
}
/** /**
* Init web3, contracts, and snark * Init web3, contracts, and snark
*/ */
@ -1304,15 +1374,17 @@ async function main() {
.option('-r, --rpc <URL>', 'The RPC that CLI should interact with', 'http://localhost:8545') .option('-r, --rpc <URL>', 'The RPC that CLI should interact with', 'http://localhost:8545')
.option('-R, --relayer <URL>', 'Withdraw via relayer') .option('-R, --relayer <URL>', 'Withdraw via relayer')
.option('-T, --tor <PORT>', 'Optional tor port') .option('-T, --tor <PORT>', 'Optional tor port')
.option('-L, --local', 'Local Node - Does not submit signed transaction to the node') .option('-S --gas_speed <SPEED>', 'Gas speed preference [ instant, fast, standard, low ]')
.option('-o, --onlyrpc', 'Only rpc mode - Does not enable thegraph api nor remote ip detection'); .option('-N --noconfirmation', 'No confirmation mode - Does not query confirmation ')
.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');
program program
.command('createNote <currency> <amount> <chainId>') .command('createNote <currency> <amount> <chainId>')
.description( .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.' '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) => { .action(async (currency, amount, chainId) => {
currency = currency.toLowerCase(); currency = currency.toLowerCase();
await createInvoice({ currency, amount, chainId }); await createInvoice({ currency, amount, chainId });
}); });
program program
@ -1321,10 +1393,8 @@ async function main() {
'Submit a deposit of invoice from default eth account and return the resulting note.' 'Submit a deposit of invoice from default eth account and return the resulting note.'
) )
.action(async (invoice) => { .action(async (invoice) => {
if (program.onlyrpc) { statePreferences(program)
privateRpc = true;
}
torPort = program.tor;
const { currency, amount, netId, commitmentNote } = parseInvoice(invoice); const { currency, amount, netId, commitmentNote } = parseInvoice(invoice);
await init({ rpc: program.rpc, currency, amount, localMode: program.local }); await init({ rpc: program.rpc, currency, amount, localMode: program.local });
console.log("Creating", currency.toUpperCase(), amount, "deposit for", netName, "Tornado Cash Instance"); console.log("Creating", currency.toUpperCase(), amount, "deposit for", netName, "Tornado Cash Instance");
@ -1336,11 +1406,10 @@ async function main() {
'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.' '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) => { .action(async (currency, amount) => {
if (program.onlyrpc) {
privateRpc = true;
}
currency = currency.toLowerCase(); currency = currency.toLowerCase();
torPort = program.tor;
statePreferences(program)
await init({ rpc: program.rpc, currency, amount, localMode: program.local }); await init({ rpc: program.rpc, currency, amount, localMode: program.local });
await deposit({ currency, amount }); await deposit({ currency, amount });
}); });
@ -1350,11 +1419,10 @@ async function main() {
'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.' '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) => { .action(async (noteString, recipient, refund) => {
if (program.onlyrpc) { statePreferences(program)
privateRpc = true;
}
const { currency, amount, netId, deposit } = parseNote(noteString); const { currency, amount, netId, deposit } = parseNote(noteString);
torPort = program.tor;
await init({ rpc: program.rpc, noteNetId: netId, currency, amount, localMode: program.local }); await init({ rpc: program.rpc, noteNetId: netId, currency, amount, localMode: program.local });
await withdraw({ await withdraw({
deposit, deposit,
@ -1369,10 +1437,8 @@ async function main() {
.command('balance [address] [token_address]') .command('balance [address] [token_address]')
.description('Check ETH and ERC20 balance') .description('Check ETH and ERC20 balance')
.action(async (address, tokenAddress) => { .action(async (address, tokenAddress) => {
if (program.onlyrpc) { statePreferences(program)
privateRpc = true;
}
torPort = program.tor;
await init({ rpc: program.rpc, balanceCheck: true }); await init({ rpc: program.rpc, balanceCheck: true });
if (!address && senderAccount) { if (!address && senderAccount) {
console.log("Using address", senderAccount, "from private key"); console.log("Using address", senderAccount, "from private key");
@ -1387,10 +1453,8 @@ async function main() {
.command('send <address> [amount] [token_address]') .command('send <address> [amount] [token_address]')
.description('Send ETH or ERC to address') .description('Send ETH or ERC to address')
.action(async (address, amount, tokenAddress) => { .action(async (address, amount, tokenAddress) => {
if (program.onlyrpc) { statePreferences(program)
privateRpc = true;
}
torPort = program.tor;
await init({ rpc: program.rpc, balanceCheck: true, localMode: program.local }); await init({ rpc: program.rpc, balanceCheck: true, localMode: program.local });
await send({ address, amount, tokenAddress }); await send({ address, amount, tokenAddress });
}); });
@ -1398,10 +1462,8 @@ async function main() {
.command('broadcast <signedTX>') .command('broadcast <signedTX>')
.description('Submit signed TX to the remote node') .description('Submit signed TX to the remote node')
.action(async (signedTX) => { .action(async (signedTX) => {
if (program.onlyrpc) { statePreferences(program)
privateRpc = true;
}
torPort = program.tor;
await init({ rpc: program.rpc, balanceCheck: true }); await init({ rpc: program.rpc, balanceCheck: true });
await submitTransaction(signedTX); await submitTransaction(signedTX);
}); });
@ -1411,11 +1473,10 @@ async function main() {
'Shows the deposit and withdrawal of the provided note. This might be necessary to show the origin of assets held in your withdrawal address.' '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) => { .action(async (noteString) => {
if (program.onlyrpc) { statePreferences(program)
privateRpc = true;
}
const { currency, amount, netId, deposit } = parseNote(noteString); const { currency, amount, netId, deposit } = parseNote(noteString);
torPort = program.tor;
await init({ rpc: program.rpc, noteNetId: netId, currency, amount }); await init({ rpc: program.rpc, noteNetId: netId, currency, amount });
const depositInfo = await loadDepositData({ amount, currency, deposit }); const depositInfo = await loadDepositData({ amount, currency, deposit });
const depositDate = new Date(depositInfo.timestamp * 1000); const depositDate = new Date(depositInfo.timestamp * 1000);
@ -1448,12 +1509,11 @@ async function main() {
'Sync the local cache file of deposit / withdrawal events for specific currency.' 'Sync the local cache file of deposit / withdrawal events for specific currency.'
) )
.action(async (type, currency, amount) => { .action(async (type, currency, amount) => {
if (program.onlyrpc) {
privateRpc = true;
}
console.log("Starting event sync command");
currency = currency.toLowerCase(); currency = currency.toLowerCase();
torPort = program.tor;
statePreferences(program)
console.log("Starting event sync command");
await init({ rpc: program.rpc, type, currency, amount }); await init({ rpc: program.rpc, type, currency, amount });
const cachedEvents = await fetchEvents({ type, currency, amount }); const cachedEvents = await fetchEvents({ type, currency, amount });
console.log("Synced event for", type, amount, currency.toUpperCase(), netName, "Tornado instance to block", cachedEvents[cachedEvents.length - 1].blockNumber); console.log("Synced event for", type, amount, currency.toUpperCase(), netName, "Tornado instance to block", cachedEvents[cachedEvents.length - 1].blockNumber);