new feature - invoice

This commit is contained in:
yoyoismee 2022-02-24 17:01:13 +07:00
parent 863474c203
commit bbb41f1a8c
2 changed files with 306 additions and 154 deletions

View file

@ -57,6 +57,32 @@ Transaction mined in block 17036120
Done
```
### using invoice
![Invoice user flow](img/invoice.png)
```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)

126
cli.js
View file

@ -157,6 +157,17 @@ async function backupNote({ currency, amount, netId, note, noteString }) {
}
}
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)
}
}
/**
* Make a deposit
* @param currency Сurrency
@ -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
@ -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 })
@ -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(