mirror of
https://github.com/tornadocash/tornado-cli.git
synced 2025-04-06 21:03:40 -04:00
new feature - invoice
This commit is contained in:
parent
863474c203
commit
bbb41f1a8c
26
README.md
26
README.md
@ -57,6 +57,32 @@ Transaction mined in block 17036120
|
||||
Done
|
||||
```
|
||||
|
||||
### using invoice
|
||||

|
||||
|
||||
```bash
|
||||
node cli.js invoice ETH 0.1 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161
|
||||
Connecting to remote node
|
||||
Your remote IP address is xx.xx.xx.xx from xx.
|
||||
Your note: tornado-eth-0.1-5-0x1d9771a7b9f8b6c03d33116208ce8db1aa559d33e65d22dd2ff78375fc6b635f930536d2432b4bde0178c72cfc79d6b27023c5d9de60985f186b34c18c00
|
||||
Your invoice: tornadoInvoice-eth-0.1-5-0x1b680c7dda0c2dd1b85f0fe126d49b16ed594b3cd6d5114db5f4593877a6b84f
|
||||
Backed up deposit note as ./backup-tornado-eth-0.1-5-0x1d9771a7.txt
|
||||
Backed up invoice as ./backup-tornadoInvoice-eth-0.1-5-0x1b680c7d.txt
|
||||
```
|
||||
```bash
|
||||
node cli.js payInvoice tornadoInvoice-eth-0.1-5-0x1b680c7dda0c2dd1b85f0fe126d49b16ed594b3cd6d5114db5f4593877a6b84f --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161
|
||||
Connecting to remote node
|
||||
Your remote IP address is xx.xx.xx.xx from xx.
|
||||
Tornado contract balance is 823.6 ETH
|
||||
Sender account balance is 0.79544229 ETH
|
||||
Submitting deposit transaction
|
||||
Submitting transaction to the remote node
|
||||
View transaction on block explorer https://goerli.etherscan.io/tx/0x6ded443caed8d6f2666841149532c64bee149a9a8e1070ed4c91a12dd1837747
|
||||
Tornado contract balance is 823.7 ETH
|
||||
Sender account balance is 0.694488819 ETH
|
||||
```
|
||||
|
||||
|
||||
### List of public rpc & relayers for withdrawal
|
||||
|
||||
Infura API key fetched from https://rpc.info (Same one with Metamask)
|
||||
|
434
cli.js
434
cli.js
@ -43,7 +43,7 @@ function toHex(number, length = 32) {
|
||||
/** Display ETH account balance */
|
||||
async function printETHBalance({ address, name }) {
|
||||
const checkBalance = new BigNumber(await web3.eth.getBalance(address));
|
||||
console.log(`${name} balance is`, checkBalance.div(BigNumber(10).pow(18)).toString(),`${netSymbol}`)
|
||||
console.log(`${name} balance is`, checkBalance.div(BigNumber(10).pow(18)).toString(), `${netSymbol}`)
|
||||
}
|
||||
|
||||
/** Display ERC20 account balance */
|
||||
@ -54,18 +54,18 @@ async function printERC20Balance({ address, name, tokenAddress }) {
|
||||
const tokenDecimals = await erc20.methods.decimals().call()
|
||||
const tokenName = await erc20.methods.name().call()
|
||||
const tokenSymbol = await erc20.methods.symbol().call()
|
||||
console.log(`${name}`,tokenName,`Token Balance is`,tokenBalance.div(BigNumber(10).pow(tokenDecimals)).toString(),tokenSymbol)
|
||||
console.log(`${name}`, tokenName, `Token Balance is`, tokenBalance.div(BigNumber(10).pow(tokenDecimals)).toString(), tokenSymbol)
|
||||
}
|
||||
|
||||
async function submitTransaction(signedTX) {
|
||||
console.log("Submitting transaction to the remote node");
|
||||
await web3.eth.sendSignedTransaction(signedTX)
|
||||
.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)
|
||||
});
|
||||
.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)
|
||||
});
|
||||
}
|
||||
|
||||
async function generateTransaction(to, encodedData, value = 0) {
|
||||
@ -75,11 +75,11 @@ async function generateTransaction(to, encodedData, value = 0) {
|
||||
|
||||
async function estimateGas() {
|
||||
const fetchedGas = await web3.eth.estimateGas({
|
||||
from : senderAccount,
|
||||
to : to,
|
||||
value : value,
|
||||
nonce : nonce,
|
||||
data : encodedData
|
||||
from: senderAccount,
|
||||
to: to,
|
||||
value: value,
|
||||
nonce: nonce,
|
||||
data: encodedData
|
||||
})
|
||||
const bumped = Math.floor(fetchedGas * 1.3)
|
||||
return web3.utils.toHex(bumped)
|
||||
@ -94,32 +94,32 @@ async function generateTransaction(to, encodedData, value = 0) {
|
||||
// Generate EIP-1559 transaction
|
||||
if (netId == 1) {
|
||||
return {
|
||||
to : to,
|
||||
value : value,
|
||||
nonce : nonce,
|
||||
maxFeePerGas : gasPrice,
|
||||
maxPriorityFeePerGas : web3.utils.toHex(web3.utils.toWei('3', 'gwei')),
|
||||
gas : gasLimit,
|
||||
data : encodedData
|
||||
to: to,
|
||||
value: value,
|
||||
nonce: nonce,
|
||||
maxFeePerGas: gasPrice,
|
||||
maxPriorityFeePerGas: web3.utils.toHex(web3.utils.toWei('3', 'gwei')),
|
||||
gas: gasLimit,
|
||||
data: encodedData
|
||||
}
|
||||
} else if (netId == 5 || netId == 137 || netId == 43114) {
|
||||
return {
|
||||
to : to,
|
||||
value : value,
|
||||
nonce : nonce,
|
||||
maxFeePerGas : gasPrice,
|
||||
maxPriorityFeePerGas : gasPrice,
|
||||
gas : gasLimit,
|
||||
data : encodedData
|
||||
to: to,
|
||||
value: value,
|
||||
nonce: nonce,
|
||||
maxFeePerGas: gasPrice,
|
||||
maxPriorityFeePerGas: gasPrice,
|
||||
gas: gasLimit,
|
||||
data: encodedData
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
to : to,
|
||||
value : value,
|
||||
nonce : nonce,
|
||||
gasPrice : gasPrice,
|
||||
gas : gasLimit,
|
||||
data : encodedData
|
||||
to: to,
|
||||
value: value,
|
||||
nonce: nonce,
|
||||
gasPrice: gasPrice,
|
||||
gas: gasLimit,
|
||||
data: encodedData
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -128,10 +128,10 @@ async function generateTransaction(to, encodedData, value = 0) {
|
||||
if (!isLocalNode) {
|
||||
await submitTransaction(signed.rawTransaction);
|
||||
} else {
|
||||
console.log('\n=============Raw TX=================','\n')
|
||||
console.log(`Please submit this raw tx to https://${getExplorerLink()}/pushTx, or otherwise broadcast with node cli.js broadcast command.`,`\n`)
|
||||
console.log(signed.rawTransaction,`\n`)
|
||||
console.log('=====================================','\n')
|
||||
console.log('\n=============Raw TX=================', '\n')
|
||||
console.log(`Please submit this raw tx to https://${getExplorerLink()}/pushTx, or otherwise broadcast with node cli.js broadcast command.`, `\n`)
|
||||
console.log(signed.rawTransaction, `\n`)
|
||||
console.log('=====================================', '\n')
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,9 +151,20 @@ function createDeposit({ nullifier, secret }) {
|
||||
async function backupNote({ currency, amount, netId, note, noteString }) {
|
||||
try {
|
||||
await fs.writeFileSync(`./backup-tornado-${currency}-${amount}-${netId}-${note.slice(0, 10)}.txt`, noteString, 'utf8');
|
||||
console.log("Backed up deposit note as",`./backup-tornado-${currency}-${amount}-${netId}-${note.slice(0, 10)}.txt`)
|
||||
console.log("Backed up deposit note as", `./backup-tornado-${currency}-${amount}-${netId}-${note.slice(0, 10)}.txt`)
|
||||
} catch (e) {
|
||||
throw new Error('Writing backup note failed:',e)
|
||||
throw new Error('Writing backup note failed:', e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function backupInvoice({ currency, amount, netId, commitmentNote, invoiceString }) {
|
||||
try {
|
||||
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`)
|
||||
} catch (e) {
|
||||
throw new Error('Writing backup note failed:', e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,6 +218,75 @@ async function deposit({ currency, amount }) {
|
||||
return noteString
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* create a Invoice
|
||||
* @param currency Сurrency
|
||||
* @param amount Deposit amount
|
||||
*/
|
||||
async function createInvoice({ currency, amount }) {
|
||||
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}`)
|
||||
|
||||
const commitmentNote = toHex(deposit.commitment)
|
||||
const invoiceString = `tornadoInvoice-${currency}-${amount}-${netId}-${commitmentNote}`
|
||||
console.log(`Your invoice: ${invoiceString}`)
|
||||
|
||||
await backupNote({ currency, amount, netId, note, noteString })
|
||||
await backupInvoice({ currency, amount, netId, commitmentNote, invoiceString })
|
||||
|
||||
return (noteString, invoiceString)
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a payment
|
||||
* @param currency Сurrency
|
||||
* @param amount Deposit amount
|
||||
*/
|
||||
async function payInvoice({ currency, amount, netId, commitmentNote }) {
|
||||
assert(senderAccount != null, 'Error! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you deposit')
|
||||
|
||||
if (currency === netSymbol.toLowerCase()) {
|
||||
await printETHBalance({ address: tornadoContract._address, name: 'Tornado contract' })
|
||||
await printETHBalance({ address: senderAccount, name: 'Sender account' })
|
||||
const value = isTestRPC ? ETH_AMOUNT : fromDecimals({ amount, decimals: 18 })
|
||||
console.log('Submitting deposit transaction')
|
||||
await generateTransaction(contractAddress, tornado.methods.deposit(tornadoInstance, toHex(commitmentNote), []).encodeABI(), value)
|
||||
await printETHBalance({ address: tornadoContract._address, name: 'Tornado contract' })
|
||||
await printETHBalance({ address: senderAccount, name: 'Sender account' })
|
||||
} else {
|
||||
// a token
|
||||
await printERC20Balance({ address: tornadoContract._address, name: 'Tornado contract' })
|
||||
await printERC20Balance({ address: senderAccount, name: 'Sender account' })
|
||||
const decimals = isTestRPC ? 18 : config.deployments[`netId${netId}`][currency].decimals
|
||||
const tokenAmount = isTestRPC ? TOKEN_AMOUNT : fromDecimals({ amount, decimals })
|
||||
if (isTestRPC) {
|
||||
console.log('Minting some test tokens to deposit')
|
||||
await generateTransaction(erc20Address, erc20.methods.mint(senderAccount, tokenAmount).encodeABI())
|
||||
}
|
||||
|
||||
const allowance = await erc20.methods.allowance(senderAccount, tornado._address).call({ from: senderAccount })
|
||||
console.log('Current allowance is', fromWei(allowance))
|
||||
if (toBN(allowance).lt(toBN(tokenAmount))) {
|
||||
console.log('Approving tokens for deposit')
|
||||
await generateTransaction(erc20Address, erc20.methods.approve(tornado._address, tokenAmount).encodeABI())
|
||||
}
|
||||
|
||||
console.log('Submitting deposit transaction')
|
||||
await generateTransaction(contractAddress, tornado.methods.deposit(tornadoInstance, toHex(commitmentNote), []).encodeABI())
|
||||
await printERC20Balance({ address: tornadoContract._address, name: 'Tornado contract' })
|
||||
await printERC20Balance({ address: senderAccount, name: 'Sender account' })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generate merkle tree for a deposit.
|
||||
* Download deposit events from the tornado, reconstructs merkle tree, finds our deposit leaf
|
||||
@ -306,7 +386,7 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, torP
|
||||
throw new Error('ENS name resolving is not supported. Please provide DNS name of the relayer. See instuctions in README.md')
|
||||
}
|
||||
if (torPort) {
|
||||
options = { httpsAgent: new SocksProxyAgent('socks5h://127.0.0.1:'+torPort), headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0' } }
|
||||
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' } }
|
||||
}
|
||||
const relayerStatus = await axios.get(relayerURL + '/status', options)
|
||||
|
||||
@ -389,19 +469,19 @@ async function send({ address, amount, tokenAddress }) {
|
||||
const tokenSymbol = await erc20.methods.symbol().call()
|
||||
const toSend = new BigNumber(amount).times(BigNumber(10).pow(tokenDecimals))
|
||||
if (tokenBalance.lt(toSend)) {
|
||||
console.error("You have",tokenBalance.div(BigNumber(10).pow(tokenDecimals)).toString(),tokenSymbol,", you can't send more than you have")
|
||||
console.error("You have", tokenBalance.div(BigNumber(10).pow(tokenDecimals)).toString(), tokenSymbol, ", you can't send more than you have")
|
||||
process.exit(1);
|
||||
}
|
||||
const encodeTransfer = erc20.methods.transfer(address, toSend).encodeABI()
|
||||
await generateTransaction(tokenAddress, encodeTransfer)
|
||||
console.log('Sent',amount,tokenSymbol,'to',address);
|
||||
console.log('Sent', amount, tokenSymbol, 'to', address);
|
||||
} else {
|
||||
const balance = new BigNumber(await web3.eth.getBalance(senderAccount));
|
||||
assert(balance.toNumber() !== 0, "You have 0 balance, can't send transaction")
|
||||
if (amount) {
|
||||
toSend = new BigNumber(amount).times(BigNumber(10).pow(18))
|
||||
if (balance.lt(toSend)) {
|
||||
console.error("You have",balance.div(BigNumber(10).pow(18)),netSymbol+", you can't send more than you have.")
|
||||
console.error("You have", balance.div(BigNumber(10).pow(18)), netSymbol + ", you can't send more than you have.")
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
@ -416,7 +496,7 @@ async function send({ address, amount, tokenAddress }) {
|
||||
}
|
||||
}
|
||||
await generateTransaction(address, null, toSend)
|
||||
console.log('Sent',toSend.div(BigNumber(10).pow(18)).toString(),netSymbol,'to',address);
|
||||
console.log('Sent', toSend.div(BigNumber(10).pow(18)).toString(), netSymbol, 'to', address);
|
||||
}
|
||||
}
|
||||
|
||||
@ -689,18 +769,18 @@ function waitForTxReceipt({ txHash, attempts = 60, delay = 1000 }) {
|
||||
}
|
||||
|
||||
function initJson(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(file, 'utf8', (error, data) => {
|
||||
if (error) {
|
||||
resolve([]);
|
||||
}
|
||||
try {
|
||||
resolve(JSON.parse(data));
|
||||
} catch (error) {
|
||||
resolve([]);
|
||||
}
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(file, 'utf8', (error, data) => {
|
||||
if (error) {
|
||||
resolve([]);
|
||||
}
|
||||
try {
|
||||
resolve(JSON.parse(data));
|
||||
} catch (error) {
|
||||
resolve([]);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function loadCachedEvents({ type, currency, amount }) {
|
||||
@ -716,7 +796,7 @@ function loadCachedEvents({ type, currency, amount }) {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Error fetching cached files, syncing from block",deployedBlockNumber)
|
||||
console.log("Error fetching cached files, syncing from block", deployedBlockNumber)
|
||||
return {
|
||||
events: [],
|
||||
lastBlock: deployedBlockNumber,
|
||||
@ -724,102 +804,102 @@ function loadCachedEvents({ type, currency, amount }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchEvents({ type, currency, amount}) {
|
||||
if (type === "withdraw") {
|
||||
type = "withdrawal"
|
||||
}
|
||||
async function fetchEvents({ type, currency, amount }) {
|
||||
if (type === "withdraw") {
|
||||
type = "withdrawal"
|
||||
}
|
||||
|
||||
const cachedEvents = loadCachedEvents({ type, currency, amount })
|
||||
const startBlock = cachedEvents.lastBlock + 1
|
||||
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")
|
||||
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 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) {
|
||||
let j;
|
||||
if (i+chunks-1 > targetBlock) {
|
||||
j = targetBlock;
|
||||
} else {
|
||||
j = i+chunks-1;
|
||||
}
|
||||
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);
|
||||
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) {
|
||||
let j;
|
||||
if (i + chunks - 1 > targetBlock) {
|
||||
j = targetBlock;
|
||||
} else {
|
||||
j = i + chunks - 1;
|
||||
}
|
||||
|
||||
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();
|
||||
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);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error("Error while updating cache")
|
||||
process.exit(1)
|
||||
|
||||
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();
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error("Error while updating cache")
|
||||
process.exit(1)
|
||||
}
|
||||
await syncEvents();
|
||||
}
|
||||
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 ${type}s:`, updatedEvents.length)
|
||||
return updatedEvents;
|
||||
}
|
||||
const events = await loadUpdatedEvents();
|
||||
async function loadUpdatedEvents() {
|
||||
const fileName = `./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`
|
||||
const updatedEvents = await initJson(fileName);
|
||||
const updatedBlock = updatedEvents[updatedEvents.length - 1].blockNumber
|
||||
console.log("Cache updated for Tornado", type, amount, currency, "instance to block", updatedBlock, "successfully")
|
||||
console.log(`Total ${type}s:`, updatedEvents.length)
|
||||
return updatedEvents;
|
||||
}
|
||||
const events = await loadUpdatedEvents();
|
||||
|
||||
return events
|
||||
return events
|
||||
}
|
||||
|
||||
/**
|
||||
@ -847,6 +927,29 @@ function parseNote(noteString) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses Tornado.cash note
|
||||
* @param invoiceString the note
|
||||
*/
|
||||
function parseInvoice(noteString) {
|
||||
const noteRegex = /tornadoInvoice-(?<currency>\w+)-(?<amount>[\d.]+)-(?<netId>\d+)-0x(?<commitmentNote>[0-9a-fA-F]{64})/g
|
||||
const match = noteRegex.exec(noteString)
|
||||
if (!match) {
|
||||
throw new Error('The note has invalid format')
|
||||
}
|
||||
|
||||
const netId = Number(match.groups.netId)
|
||||
const buf = Buffer.from(match.groups.commitmentNote, 'hex')
|
||||
const commitmentNote = toHex(buf.slice(0, 32))
|
||||
|
||||
return {
|
||||
currency: match.groups.currency,
|
||||
amount: match.groups.amount,
|
||||
netId,
|
||||
commitmentNote
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDepositData({ amount, currency, deposit }) {
|
||||
try {
|
||||
const cachedEvents = await fetchEvents({ type: 'deposit', currency, amount })
|
||||
@ -924,16 +1027,16 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100', torPort,
|
||||
let ipOptions = {};
|
||||
if (torPort && rpc.includes("https")) {
|
||||
console.log("Using tor network")
|
||||
web3Options = { agent: { https: new SocksProxyAgent('socks5h://127.0.0.1:'+torPort) }, timeout: 60000 }
|
||||
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 })
|
||||
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' } }
|
||||
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 (torPort && rpc.includes("http")) {
|
||||
console.log("Using tor network")
|
||||
web3Options = { agent: { http: new SocksProxyAgent('socks5h://127.0.0.1:'+torPort) }, timeout: 60000 }
|
||||
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 })
|
||||
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' } }
|
||||
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")
|
||||
web3 = new Web3(new Web3.providers.IpcProvider(rpc, net), null, { transactionConfirmationBlocks: 1 })
|
||||
@ -948,7 +1051,7 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100', torPort,
|
||||
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+'.');
|
||||
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.');
|
||||
}
|
||||
@ -1054,6 +1157,29 @@ async function main() {
|
||||
await init({ rpc: program.rpc, currency, amount, torPort: program.tor, localMode: program.local })
|
||||
await deposit({ currency, amount })
|
||||
})
|
||||
|
||||
program
|
||||
.command('invoice <currency> <amount>')
|
||||
.description(
|
||||
'Create invoice of specified currency and amount from default eth account and return the invoice (to pay) and note (to claim). 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, torPort: program.tor, localMode: program.local })
|
||||
await createInvoice({ currency, amount })
|
||||
})
|
||||
|
||||
program
|
||||
.command('payInvoice <invoice>')
|
||||
.description(
|
||||
'pay invoice of specified currency and amount from default eth account. 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 (invoice) => {
|
||||
const { currency, amount, netId, commitmentNote } = parseInvoice(invoice)
|
||||
await init({ rpc: program.rpc, currency, amount, torPort: program.tor, localMode: program.local })
|
||||
await payInvoice({ currency, amount, netId, commitmentNote })
|
||||
})
|
||||
|
||||
program
|
||||
.command('withdraw <note> <recipient> [ETH_purchase]')
|
||||
.description(
|
||||
@ -1078,7 +1204,7 @@ async function main() {
|
||||
.action(async (address, tokenAddress) => {
|
||||
await init({ rpc: program.rpc, torPort: program.tor, balanceCheck: true })
|
||||
if (!address && senderAccount) {
|
||||
console.log("Using address",senderAccount,"from private key")
|
||||
console.log("Using address", senderAccount, "from private key")
|
||||
address = senderAccount;
|
||||
}
|
||||
await printETHBalance({ address, name: 'Account' })
|
||||
@ -1090,14 +1216,14 @@ async function main() {
|
||||
.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, localMode: program.local })
|
||||
await init({ rpc: program.rpc, torPort: program.tor, balanceCheck: true, localMode: program.local })
|
||||
await send({ address, amount, tokenAddress })
|
||||
})
|
||||
program
|
||||
.command('broadcast <signedTX>')
|
||||
.description('Submit signed TX to the remote node')
|
||||
.action(async (signedTX) => {
|
||||
await init({ rpc: program.rpc, torPort: program.tor, balanceCheck: true })
|
||||
await init({ rpc: program.rpc, torPort: program.tor, balanceCheck: true })
|
||||
await submitTransaction(signedTX)
|
||||
})
|
||||
program
|
||||
@ -1121,7 +1247,7 @@ async function main() {
|
||||
console.log('The note was not spent')
|
||||
return
|
||||
}
|
||||
console.log('=====================================','\n')
|
||||
console.log('=====================================', '\n')
|
||||
|
||||
const withdrawInfo = await loadWithdrawalData({ amount, currency, deposit })
|
||||
const withdrawalDate = new Date(withdrawInfo.timestamp * 1000)
|
||||
@ -1132,7 +1258,7 @@ async function main() {
|
||||
console.log('To :', `https://${getExplorerLink()}/address/${withdrawInfo.to}`)
|
||||
console.log('Transaction :', `https://${getExplorerLink()}/tx/${withdrawInfo.txHash}`)
|
||||
console.log('Nullifier :', withdrawInfo.nullifier)
|
||||
console.log('=====================================','\n')
|
||||
console.log('=====================================', '\n')
|
||||
})
|
||||
program
|
||||
.command('syncEvents <type> <currency> <amount>')
|
||||
@ -1144,7 +1270,7 @@ async function main() {
|
||||
currency = currency.toLowerCase()
|
||||
await init({ rpc: program.rpc, type, currency, amount, torPort: program.tor })
|
||||
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)
|
||||
})
|
||||
program
|
||||
.command('test')
|
||||
|
Loading…
x
Reference in New Issue
Block a user