prettier + tidy

This commit is contained in:
Alexey 2020-10-02 13:26:05 +03:00
parent 17d48f9508
commit 9cd97feef0
21 changed files with 96 additions and 737 deletions

@ -22,7 +22,13 @@
}
],
"linebreak-style": ["error", "unix"],
"quotes": ["error", "single", { "avoidEscape": true }],
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"semi": ["error", "never"],
"object-curly-spacing": ["error", "always"],
"require-await": "error",

@ -2,8 +2,8 @@ name: build
on:
push:
branches: [ '*' ]
tags: [ 'v[0-9]+.[0-9]+.[0-9]+' ]
branches: ['*']
tags: ['v[0-9]+.[0-9]+.[0-9]+']
pull_request:
jobs:

1
.prettierignore Normal file

@ -0,0 +1 @@
keys/TreeUpdate.json

@ -1,6 +1,6 @@
{
"semi": false,
"arrowParens": "always",
"arrowParens": "avoid",
"singleQuote": true,
"printWidth": 110,
"trailingComma": "all"

@ -5,7 +5,10 @@
"scripts": {
"server": "node src/server.js",
"treeUpdater": "node src/treeWatcher",
"eslint": "npx eslint --ignore-path .gitignore .",
"eslint": "eslint --ext .js --ignore-path .gitignore .",
"prettier:check": "npx prettier --check . --config .prettierrc",
"prettier:fix": "npx prettier --write . --config .prettierrc",
"lint": "yarn eslint && yarn prettier:check",
"test": "mocha"
},
"author": "tornado.cash",
@ -34,6 +37,9 @@
"devDependencies": {
"chai": "^4.2.0",
"eslint": "^6.6.0",
"mocha": "^8.1.3"
"eslint-config-prettier": "^6.12.0",
"eslint-plugin-prettier": "^3.1.4",
"mocha": "^8.1.3",
"prettier": "^2.1.2"
}
}

@ -1,56 +0,0 @@
const Web3 = require('web3')
const { defaultGasPrice, oracleRpcUrl, oracleAddress } = require('../config')
const { getArgsForOracle } = require('./utils')
const { redisClient } = require('./redis')
const priceOracleABI = require('../abis/PriceOracle.abi.json')
class Fetcher {
constructor(web3) {
this.web3 = web3
this.oracleWeb3 = new Web3(oracleRpcUrl)
this.oracle = new this.oracleWeb3.eth.Contract(priceOracleABI, oracleAddress)
this.ethPrices = {
dai: '6700000000000000', // 0.0067
cdai: '157380000000000',
cusdc: '164630000000000',
usdc: '7878580000000000',
usdt: '7864940000000000'
}
this.tokenAddresses
this.oneUintAmount
this.currencyLookup
this.gasPrices = {
fast: defaultGasPrice
}
const { tokenAddresses, oneUintAmount, currencyLookup } = getArgsForOracle()
this.tokenAddresses = tokenAddresses
this.oneUintAmount = oneUintAmount
this.currencyLookup = currencyLookup
}
async fetchPrices() {
try {
let prices = await this.oracle.methods.getPricesInETH(this.tokenAddresses, this.oneUintAmount).call()
this.ethPrices = prices.reduce((acc, price, i) => {
acc[this.currencyLookup[this.tokenAddresses[i]]] = price
return acc
}, {})
setTimeout(() => this.fetchPrices(), 1000 * 30)
} catch (e) {
console.error('fetchPrices', e.message)
setTimeout(() => this.fetchPrices(), 1000 * 30)
}
}
async fetchNonce() {
try {
const nonce = await this.web3.eth.getTransactionCount(this.web3.eth.defaultAccount)
await redisClient.set('nonce', nonce)
console.log(`Current nonce: ${nonce}`)
} catch (e) {
console.error('fetchNonce failed', e.message)
setTimeout(this.fetchNonce, 3000)
}
}
}
module.exports = Fetcher

@ -1,98 +0,0 @@
const express = require('express')
const {
netId,
port,
relayerServiceFee,
gasBumpPercentage,
pendingTxTimeout,
watherInterval,
maxGasPrice
} = require('../config')
const relayController = require('./relayController')
const { fetcher, web3, gasPriceOracle } = require('./instances')
const { getMixers } = require('./utils')
const mixers = getMixers()
const { redisClient } = require('./redis')
const { version } = require('../package.json')
const app = express()
app.use(express.json())
app.use((err, req, res, next) => {
if (err) {
console.log('Invalid Request data')
res.send('Invalid Request data')
} else {
next()
}
})
app.use(function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
next()
})
app.get('/', function (req, res) {
// just for testing purposes
res.send(
'This is <a href=https://tornado.cash>tornado.cash</a> Relayer service. Check the <a href=/status>/status</a> for settings'
)
})
app.get('/status', async function (req, res) {
let nonce = await redisClient.get('nonce')
let latestBlock = null
try {
latestBlock = await web3.eth.getBlockNumber()
} catch (e) {
console.error('Problem with RPC', e)
}
const { ethPrices } = fetcher
res.json({
relayerAddress: web3.eth.defaultAccount,
mixers,
gasPrices: await gasPriceOracle.gasPrices(),
netId,
ethPrices,
relayerServiceFee,
nonce,
version,
latestBlock
})
})
app.post('/relay', relayController)
console.log('Version:', version)
let server = app.listen(port || 8000)
server.setTimeout(600000)
console.log('Gas price oracle started.')
fetcher.fetchPrices()
fetcher.fetchNonce()
console.log('Relayer started on port', port || 8000)
console.log(`relayerAddress: ${web3.eth.defaultAccount}`)
console.log(`mixers: ${JSON.stringify(mixers)}`)
console.log(`netId: ${netId}`)
console.log(`ethPrices: ${JSON.stringify(fetcher.ethPrices)}`)
const {
GAS_PRICE_BUMP_PERCENTAGE,
ALLOWABLE_PENDING_TX_TIMEOUT,
NONCE_WATCHER_INTERVAL,
MAX_GAS_PRICE
} = process.env
if (!NONCE_WATCHER_INTERVAL) {
console.log(`NONCE_WATCHER_INTERVAL is not set. Using default value ${watherInterval / 1000} sec`)
}
if (!GAS_PRICE_BUMP_PERCENTAGE) {
console.log(`GAS_PRICE_BUMP_PERCENTAGE is not set. Using default value ${gasBumpPercentage}%`)
}
if (!ALLOWABLE_PENDING_TX_TIMEOUT) {
console.log(`ALLOWABLE_PENDING_TX_TIMEOUT is not set. Using default value ${pendingTxTimeout / 1000} sec`)
}
if (!MAX_GAS_PRICE) {
console.log(`ALLOWABLE_PENDING_TX_TIMEOUT is not set. Using default value ${maxGasPrice} Gwei`)
}

@ -1,15 +0,0 @@
const { rpcUrl } = require('../config')
const Fetcher = require('./Fetcher')
const Sender = require('./sender')
const { GasPriceOracle } = require('gas-price-oracle')
const web3 = require('./setupWeb3')
const fetcher = new Fetcher(web3)
const sender = new Sender(web3)
const gasPriceOracle = new GasPriceOracle({ defaultRpc: rpcUrl })
module.exports = {
fetcher,
web3,
sender,
gasPriceOracle
}

@ -1,19 +0,0 @@
const { redisUrl } = require('../config')
const Redis = require('ioredis')
const redisClient = new Redis(redisUrl)
const subscriber = new Redis(redisUrl)
const redisOpts = {
createClient: function (type) {
switch (type) {
case 'client':
return redisClient
case 'subscriber':
return subscriber
default:
return new Redis(redisUrl)
}
}
}
module.exports = { redisOpts, redisClient }

@ -1,164 +0,0 @@
const Queue = require('bull')
const { numberToHex, toWei, toHex, toBN, toChecksumAddress } = require('web3-utils')
const mixerABI = require('../abis/mixerABI.json')
const { isValidProof, isValidArgs, isKnownContract, isEnoughFee } = require('./utils')
const config = require('../config')
const { redisClient, redisOpts } = require('./redis')
const { GasPriceOracle } = require('gas-price-oracle')
const gasPriceOracle = new GasPriceOracle({ defaultRpc: rpcUrl })
const { web3, fetcher, sender, gasPriceOracle } = require('./instances')
const withdrawQueue = new Queue('withdraw', redisOpts)
const reponseCbs = {}
let respLambda = (job, { msg, status }) => {
const resp = reponseCbs[job.id]
resp.status(status).json(msg)
delete reponseCbs[job.id]
}
withdrawQueue.on('completed', respLambda)
async function relayController(req, resp) {
let requestJob
const { proof, args, contract } = req.body
let { valid, reason } = isValidProof(proof)
if (!valid) {
console.log('Proof is invalid:', reason)
return resp.status(400).json({ error: 'Proof format is invalid' })
}
// eslint-disable-next-line no-extra-semi
;({ valid, reason } = isValidArgs(args))
if (!valid) {
console.log('Args are invalid:', reason)
return resp.status(400).json({ error: 'Withdraw arguments are invalid' })
}
let currency, amount
;({ valid, currency, amount } = isKnownContract(contract))
if (!valid) {
console.log('Contract does not exist:', contract)
return resp.status(400).json({ error: 'This relayer does not support the token' })
}
const [root, nullifierHash, recipient, relayer, fee, refund] = [
args[0],
args[1],
toChecksumAddress(args[2]),
toChecksumAddress(args[3]),
toBN(args[4]),
toBN(args[5])
]
console.log('fee, refund', fee.toString(), refund.toString(), recipient)
if (currency === 'eth' && !refund.isZero()) {
return resp.status(400).json({ error: 'Cannot send refund for eth currency.' })
}
if (relayer !== web3.eth.defaultAccount) {
console.log('This proof is for different relayer:', relayer)
return resp.status(400).json({ error: 'Relayer address is invalid' })
}
requestJob = await withdrawQueue.add(
{
contract,
nullifierHash,
root,
proof,
args,
currency,
amount,
fee: fee.toString(),
refund: refund.toString()
},
{ removeOnComplete: true }
)
reponseCbs[requestJob.id] = resp
}
withdrawQueue.process(async function (job, done) {
console.log(Date.now(), ' withdraw started', job.id)
const gasPrices = await gasPriceOracle.gasPrices()
const { contract, nullifierHash, root, proof, args, refund, currency, amount, fee } = job.data
console.log(JSON.stringify(job.data))
// job.data contains the custom data passed when the job was created
// job.id contains id of this job.
try {
const mixer = new web3.eth.Contract(mixerABI, contract)
const isSpent = await mixer.methods.isSpent(nullifierHash).call()
if (isSpent) {
done(null, {
status: 400,
msg: {
error: 'The note has been spent.'
}
})
return
}
const isKnownRoot = await mixer.methods.isKnownRoot(root).call()
if (!isKnownRoot) {
done(null, {
status: 400,
msg: {
error: 'The merkle root is too old or invalid.'
}
})
return
}
let gas = await mixer.methods.withdraw(proof, ...args).estimateGas({
from: web3.eth.defaultAccount,
value: refund
})
gas += 50000
const ethPrices = fetcher.ethPrices
const { isEnough, reason } = isEnoughFee({
gas,
gasPrices,
currency,
amount,
refund: toBN(refund),
ethPrices,
fee: toBN(fee)
})
if (!isEnough) {
console.log(`Wrong fee: ${reason}`)
done(null, {
status: 400,
msg: { error: reason }
})
return
}
const data = mixer.methods.withdraw(proof, ...args).encodeABI()
let nonce = Number(await redisClient.get('nonce'))
console.log('nonce', nonce)
const tx = {
from: web3.eth.defaultAccount,
value: numberToHex(refund),
gas: numberToHex(gas),
gasPrice: toHex(toWei(gasPrices.fast.toString(), 'gwei')),
// you can use this gasPrice to test watcher
// gasPrice: numberToHex(100000000),
to: mixer._address,
netId: config.netId,
data,
nonce
}
tx.date = Date.now()
await redisClient.set('tx:' + nonce, JSON.stringify(tx))
nonce += 1
await redisClient.set('nonce', nonce)
sender.sendTx(tx, done)
} catch (e) {
console.error(e, 'estimate gas failed')
done(null, {
status: 400,
msg: { error: 'Internal Relayer Error. Please use a different relayer service' }
})
}
})
module.exports = relayController

@ -1,81 +0,0 @@
const { redisClient } = require('./redis')
const config = require('../config')
const { toBN, toHex, toWei, BN, fromWei } = require('web3-utils')
class Sender {
constructor(web3) {
this.web3 = web3
this.watherInterval = config.watherInterval
this.pendingTxTimeout = config.pendingTxTimeout
this.gasBumpPercentage = 100 + Number(config.gasBumpPercentage)
this.watcher()
}
async watcher() {
try {
const networkNonce = await this.web3.eth.getTransactionCount(this.web3.eth.defaultAccount)
let tx = await redisClient.get('tx:' + networkNonce)
if (tx) {
tx = JSON.parse(tx)
if (Date.now() - tx.date > this.pendingTxTimeout) {
const newGasPrice = toBN(tx.gasPrice).mul(toBN(this.gasBumpPercentage)).div(toBN(100))
const maxGasPrice = toBN(toWei(config.maxGasPrice.toString(), 'Gwei'))
tx.gasPrice = toHex(BN.min(newGasPrice, maxGasPrice))
tx.date = Date.now()
await redisClient.set('tx:' + tx.nonce, JSON.stringify(tx))
console.log('resubmitting with gas price', fromWei(tx.gasPrice.toString(), 'gwei'), ' gwei')
this.sendTx(tx, null, 9999)
}
}
} catch (e) {
console.error('watcher error:', e)
} finally {
setTimeout(() => this.watcher(), this.watherInterval)
}
}
async sendTx(tx, done, retryAttempt = 1) {
let signedTx = await this.web3.eth.accounts.signTransaction(tx, config.privateKey)
let result = this.web3.eth.sendSignedTransaction(signedTx.rawTransaction)
result
.once('transactionHash', (txHash) => {
console.log(`A new successfully sent tx ${txHash}`)
if (done) {
done(null, {
status: 200,
msg: { txHash }
})
}
})
.on('error', async (e) => {
console.log(`Error for tx with nonce ${tx.nonce}\n${e.message}`)
if (
e.message ===
'Returned error: Transaction gas price supplied is too low. There is another transaction with same nonce in the queue. Try increasing the gas price or incrementing the nonce.' ||
e.message === 'Returned error: Transaction nonce is too low. Try incrementing the nonce.' ||
e.message === 'Returned error: nonce too low' ||
e.message === 'Returned error: replacement transaction underpriced'
) {
console.log('nonce too low, retrying')
if (retryAttempt <= 10) {
retryAttempt++
const newNonce = tx.nonce + 1
tx.nonce = newNonce
await redisClient.set('nonce', newNonce)
await redisClient.set('tx:' + newNonce, JSON.stringify(tx))
this.sendTx(tx, done, retryAttempt)
return
}
}
if (done) {
done(null, {
status: 400,
msg: { error: 'Internal Relayer Error. Please use a different relayer service' }
})
}
})
}
}
module.exports = Sender

@ -1,16 +0,0 @@
const Web3 = require('web3')
const { rpcUrl, privateKey } = require('../config')
function setup() {
try {
const web3 = new Web3(rpcUrl, null, { transactionConfirmationBlocks: 1 })
const account = web3.eth.accounts.privateKeyToAccount('0x' + privateKey)
web3.eth.accounts.wallet.add('0x' + privateKey)
web3.eth.defaultAccount = account.address
return web3
} catch (e) {
console.error('web3 failed')
}
}
const web3 = setup()
module.exports = web3

@ -1,19 +0,0 @@
const fs = require('fs')
const { Controller } = require('tornado-cash-anonymity-mining')
const { web3 } = require('./instances')
const { farmingAddress, farmingMerkleTreeHeight } = require('../config')
const contract = web3.eth.contract(require('../abis/mining.abi.json'), farmingAddress)
const provingKeys = {
treeUpdateCircuit: require('.../keys/TreeUpdate.json'),
treeUpdateProvingKey: fs.readFileSync('../keys/TreeUpdate_proving_key.bin').buffer,
}
const controller = new Controller({
contract,
provingKeys,
merkleTreeHeight: farmingMerkleTreeHeight,
})
// await controller.init()
// await controller.treeUpdate(commitment)

@ -1,178 +0,0 @@
const { isHexStrict, toBN, toWei, BN } = require('web3-utils')
const { netId, mixers, relayerServiceFee } = require('../config')
function isValidProof(proof) {
// validator expects `websnarkUtils.toSolidityInput(proof)` output
if (!proof) {
return { valid: false, reason: 'The proof is empty.' }
}
if (!isHexStrict(proof) || proof.length !== 2 + 2 * 8 * 32) {
return { valid: false, reason: 'Corrupted proof' }
}
return { valid: true }
}
function isValidArgs(args) {
if (!args) {
return { valid: false, reason: 'Args are empty' }
}
if (args.length !== 6) {
return { valid: false, reason: 'Length of args is lower than 6' }
}
for (let signal of args) {
if (!isHexStrict(signal)) {
return { valid: false, reason: `Corrupted signal ${signal}` }
}
}
if (
args[0].length !== 66 ||
args[1].length !== 66 ||
args[2].length !== 42 ||
args[3].length !== 42 ||
args[4].length !== 66 ||
args[5].length !== 66
) {
return { valid: false, reason: 'The length one of the signals is incorrect' }
}
return { valid: true }
}
function isKnownContract(contract) {
const mixers = getMixers()
for (let currency of Object.keys(mixers)) {
for (let amount of Object.keys(mixers[currency].mixerAddress)) {
if (mixers[currency].mixerAddress[amount] === contract) {
return { valid: true, currency, amount }
}
}
}
return { valid: false }
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
function fromDecimals(value, decimals) {
value = value.toString()
let ether = value.toString()
const base = new BN('10').pow(new BN(decimals))
const baseLength = base.toString(10).length - 1 || 1
const negative = ether.substring(0, 1) === '-'
if (negative) {
ether = ether.substring(1)
}
if (ether === '.') {
throw new Error('[ethjs-unit] while converting number ' + value + ' to wei, invalid value')
}
// Split it into a whole and fractional part
const comps = ether.split('.')
if (comps.length > 2) {
throw new Error('[ethjs-unit] while converting number ' + value + ' to wei, too many decimal points')
}
let whole = comps[0]
let fraction = comps[1]
if (!whole) {
whole = '0'
}
if (!fraction) {
fraction = '0'
}
if (fraction.length > baseLength) {
throw new Error('[ethjs-unit] while converting number ' + value + ' to wei, too many decimal places')
}
while (fraction.length < baseLength) {
fraction += '0'
}
whole = new BN(whole)
fraction = new BN(fraction)
let wei = whole.mul(base).add(fraction)
if (negative) {
wei = wei.mul(negative)
}
return new BN(wei.toString(10), 10)
}
function isEnoughFee({ gas, gasPrices, currency, amount, refund, ethPrices, fee }) {
const { decimals } = mixers[`netId${netId}`][currency]
const decimalsPoint =
Math.floor(relayerServiceFee) === relayerServiceFee
? 0
: relayerServiceFee.toString().split('.')[1].length
const roundDecimal = 10 ** decimalsPoint
const feePercent = toBN(fromDecimals(amount, decimals))
.mul(toBN(relayerServiceFee * roundDecimal))
.div(toBN(roundDecimal * 100))
const expense = toBN(toWei(gasPrices.fast.toString(), 'gwei')).mul(toBN(gas))
let desiredFee
switch (currency) {
case 'eth': {
desiredFee = expense.add(feePercent)
break
}
default: {
desiredFee = expense
.add(refund)
.mul(toBN(10 ** decimals))
.div(toBN(ethPrices[currency]))
desiredFee = desiredFee.add(feePercent)
break
}
}
console.log(
'sent fee, desired fee, feePercent',
fee.toString(),
desiredFee.toString(),
feePercent.toString()
)
if (fee.lt(desiredFee)) {
return { isEnough: false, reason: 'Not enough fee' }
}
return { isEnough: true }
}
function getArgsForOracle() {
const tokens = mixers['netId1']
const tokenAddresses = []
const oneUintAmount = []
const currencyLookup = {}
Object.entries(tokens).map(([currency, data]) => {
if (currency !== 'eth') {
tokenAddresses.push(data.tokenAddress)
oneUintAmount.push(toBN('10').pow(toBN(data.decimals.toString())).toString())
currencyLookup[data.tokenAddress] = currency
}
})
return { tokenAddresses, oneUintAmount, currencyLookup }
}
function getMixers() {
return mixers[`netId${netId}`]
}
module.exports = {
isValidProof,
isValidArgs,
sleep,
isKnownContract,
isEnoughFee,
getMixers,
getArgsForOracle
}

@ -1,4 +1,8 @@
const { getTornadoWithdrawInputError, getMiningRewardInputError, getMiningWithdrawInputError } = require('./validator')
const {
getTornadoWithdrawInputError,
getMiningRewardInputError,
getMiningWithdrawInputError,
} = require('./validator')
const { postJob } = require('./queue')
async function tornadoWithdraw(req, res) {

@ -18,7 +18,7 @@ async function fetchEvents(from = 0, to = 'latest') {
})
return events
.sort((a, b) => a.returnValues.index - b.returnValues.index)
.map((e) => toBN(e.returnValues.commitment))
.map(e => toBN(e.returnValues.commitment))
}
async function processNewEvent(err, event) {
@ -91,7 +91,7 @@ async function init() {
init()
process.on('unhandledRejection', (error) => {
process.on('unhandledRejection', error => {
console.error('Unhandled promise rejection', error)
process.exit(1)
})

@ -2,7 +2,7 @@ const { instances, netId } = require('../config')
const { poseidon } = require('circomlib')
const { toBN, toChecksumAddress } = require('web3-utils')
const sleep = (ms) => new Promise((res) => setTimeout(res, ms))
const sleep = ms => new Promise(res => setTimeout(res, ms))
function getInstance(address) {
address = toChecksumAddress(address)
@ -27,7 +27,7 @@ function getInstance(address) {
// }
// }
const poseidonHash = (items) => toBN(poseidon(items).toString())
const poseidonHash = items => toBN(poseidon(items).toString())
const poseidonHash2 = (a, b) => poseidonHash([a, b])
function setSafeInterval(func, interval) {
@ -44,10 +44,10 @@ function setSafeInterval(func, interval) {
function when(source, event) {
return new Promise((resolve, reject) => {
source
.once(event, (payload) => {
.once(event, payload => {
resolve(payload)
})
.on('error', (error) => {
.on('error', error => {
reject(error)
})
})

@ -115,7 +115,7 @@ async function processTornadoWithdraw(job) {
await currentTx
.send()
.on('transactionHash', updateTxHash)
.on('mined', (receipt) => {
.on('mined', receipt => {
console.log('Mined in block', receipt.blockNumber)
})
.on('confirmations', updateConfirmations)
@ -141,7 +141,7 @@ async function processMiningReward(job) {
await currentTx
.send()
.on('transactionHash', updateTxHash)
.on('mined', (receipt) => {
.on('mined', receipt => {
console.log('Mined in block', receipt.blockNumber)
})
.on('confirmations', updateConfirmations)
@ -167,7 +167,7 @@ async function processMiningWithdraw(job) {
await currentTx
.send()
.on('transactionHash', updateTxHash)
.on('mined', (receipt) => {
.on('mined', receipt => {
console.log('Mined in block', receipt.blockNumber)
})
.on('confirmations', updateConfirmations)

@ -1,48 +0,0 @@
const { toHex, toWei } = require('web3-utils')
const TxManager = require('./src/TxManager')
const { rpcUrl, privateKey } = require('./config')
const logHandles = require('why-is-node-running')
const manager = new TxManager({
privateKey,
rpcUrl,
config: {
CONFIRMATIONS: 1,
GAS_BUMP_INTERVAL: 1000 * 15,
},
})
const tx1 = {
value: 1,
gasPrice: toHex(toWei('1', 'gwei')),
to: '0xA43Ce8Cc89Eff3AA5593c742fC56A30Ef2427CB0',
}
const tx2 = {
value: 2,
// gasPrice: toHex(toWei('1', 'gwei')),
to: '0x0039F22efB07A647557C7C5d17854CFD6D489eF3',
}
async function main() {
const tx = manager.createTx(tx1)
setTimeout(() => tx.cancel(), 1000)
// setTimeout(() => tx.replace(tx2), 1000)
const receipt = await tx.send()
.on('transactionHash', (hash) => {
console.log('hash', hash)
})
.on('mined', (receipt) => {
console.log('Mined in block', receipt.blockNumber)
})
.on('confirmations', (confirmations) => {
console.log('confirmations', confirmations)
})
console.log('receipt', receipt)
// setTimeout(logHandles, 100)
}
main()

@ -1262,6 +1262,20 @@ escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
eslint-config-prettier@^6.12.0:
version "6.12.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.12.0.tgz#9eb2bccff727db1c52104f0b49e87ea46605a0d2"
integrity sha512-9jWPlFlgNwRUYVoujvWTQ1aMO8o6648r+K7qU7K5Jmkbyqav1fuEZC0COYpGBxyiAJb65Ra9hrmFx19xRGwXWw==
dependencies:
get-stdin "^6.0.0"
eslint-plugin-prettier@^3.1.4:
version "3.1.4"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz#168ab43154e2ea57db992a2cd097c828171f75c2"
integrity sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg==
dependencies:
prettier-linter-helpers "^1.0.0"
eslint-scope@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
@ -1673,6 +1687,11 @@ fast-deep-equal@^3.1.1:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-diff@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
@ -1876,6 +1895,11 @@ get-port@^5.1.1:
resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==
get-stdin@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
@ -3176,6 +3200,18 @@ prepend-http@^2.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
prettier-linter-helpers@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
dependencies:
fast-diff "^1.1.2"
prettier@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5"
integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==
process@~0.5.1:
version "0.5.2"
resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf"