diff --git a/cli.js b/cli.js index a93b6a7..63a9235 100755 --- a/cli.js +++ b/cli.js @@ -2,6 +2,7 @@ // Temporary demo client // Works both in browser and node.js const fs = require('fs') +const axios = require('axios') const assert = require('assert') const snarkjs = require('snarkjs') const crypto = require('crypto') @@ -11,16 +12,31 @@ const merkleTree = require('./lib/MerkleTree') const Web3 = require('web3') const buildGroth16 = require('websnark/src/groth16') const websnarkUtils = require('websnark/src/utils') +const { toWei, fromWei } = require('web3-utils') -let web3, mixer, erc20mixer, circuit, proving_key, groth16, erc20 -let MERKLE_TREE_HEIGHT, ETH_AMOUNT, ERC20_TOKEN +let web3, mixer, erc20mixer, circuit, proving_key, groth16, erc20, senderAccount +let MERKLE_TREE_HEIGHT, ETH_AMOUNT, TOKEN_AMOUNT, ERC20_TOKEN + +/** Whether we are in a browser or node.js */ const inBrowser = (typeof window !== 'undefined') /** 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) { + let str = number instanceof Buffer ? number.toString('hex') : bigInt(number).toString(16) + return '0x' + str.padStart(length * 2, '0') +} + +/** Display account balance */ +async function printBalance(account, name) { + console.log(`${name} ETH balance is`, web3.utils.fromWei(await web3.eth.getBalance(account))) + console.log(`${name} Token Balance is`, web3.utils.fromWei(await erc20.methods.balanceOf(account).call())) +} /** * Create deposit object from secret and nullifier @@ -29,169 +45,103 @@ function createDeposit(nullifier, secret) { let deposit = { nullifier, secret } deposit.preimage = Buffer.concat([deposit.nullifier.leInt2Buff(31), deposit.secret.leInt2Buff(31)]) deposit.commitment = pedersenHash(deposit.preimage) + deposit.nullifierHash = pedersenHash(deposit.nullifier.leInt2Buff(31)) return deposit } /** - * Make a deposit - * @returns {Promise} + * Make an ETH deposit */ async function deposit() { const deposit = createDeposit(rbigint(31), rbigint(31)) console.log('Submitting deposit transaction') - await mixer.methods.deposit('0x' + deposit.commitment.toString(16)).send({ value: ETH_AMOUNT, from: (await web3.eth.getAccounts())[0], gas:1e6 }) + await mixer.methods.deposit(toHex(deposit.commitment)).send({ value: ETH_AMOUNT, from: senderAccount, gas:1e6 }) - const note = '0x' + deposit.preimage.toString('hex') + const note = toHex(deposit.preimage, 62) console.log('Your note:', note) return note } +/** + * Make an ERC20 deposit + */ async function depositErc20() { - const account = (await web3.eth.getAccounts())[0] - const tokenAmount = process.env.TOKEN_AMOUNT - await erc20.methods.mint(account, tokenAmount).send({ from: account, gas:1e6 }) - - await erc20.methods.approve(erc20mixer.address, tokenAmount).send({ from: account, gas:1e6 }) - const allowance = await erc20.methods.allowance(account, erc20mixer.address).call() - console.log('erc20mixer allowance', allowance.toString(10)) - const deposit = createDeposit(rbigint(31), rbigint(31)) - await erc20mixer.methods.deposit('0x' + deposit.commitment.toString(16)).send({ value: ETH_AMOUNT, from: account, gas:1e6 }) - const balance = await erc20.methods.balanceOf(erc20mixer.address).call() - console.log('erc20mixer balance', balance.toString(10)) - const note = '0x' + deposit.preimage.toString('hex') - console.log('Your note:', note) - return note -} - -async function withdrawErc20(note, recipient, relayer) { - let buf = Buffer.from(note.slice(2), 'hex') - let deposit = createDeposit(bigInt.leBuff2int(buf.slice(0, 31)), bigInt.leBuff2int(buf.slice(31, 62))) - - console.log('Getting current state from mixer contract') - const events = await erc20mixer.getPastEvents('Deposit', { fromBlock: erc20mixer.deployedBlock, toBlock: 'latest' }) - let leafIndex - - const commitment = deposit.commitment.toString(16).padStart('66', '0x000000') - const leaves = events - .sort((a, b) => a.returnValues.leafIndex.sub(b.returnValues.leafIndex)) - .map(e => { - if (e.returnValues.commitment.eq(commitment)) { - leafIndex = e.returnValues.leafIndex.toNumber() - } - return e.returnValues.commitment - }) - const tree = new merkleTree(MERKLE_TREE_HEIGHT, leaves) - const validRoot = await erc20mixer.methods.isKnownRoot(await tree.root()).call() - const nullifierHash = pedersenHash(deposit.nullifier.leInt2Buff(31)) - const nullifierHashToCheck = nullifierHash.toString(16).padStart('66', '0x000000') - const isSpent = await erc20mixer.methods.isSpent(nullifierHashToCheck).call() - assert(validRoot === true) - assert(isSpent === false) - - assert(leafIndex >= 0) - const { root, path_elements, path_index } = await tree.path(leafIndex) - // Circuit input - const input = { - // public - root: root, - nullifierHash, - recipient: bigInt(recipient), - relayer: bigInt(relayer), - fee: bigInt(web3.utils.toWei('0.01')), - refund: bigInt(0), - - // private - nullifier: deposit.nullifier, - secret: deposit.secret, - pathElements: path_elements, - pathIndices: path_index, + if(ERC20_TOKEN === '') { + console.log('Minting some test tokens to deposit') + await erc20.methods.mint(senderAccount, TOKEN_AMOUNT).send({ from: senderAccount, gas: 1e6 }) } - console.log('Generating SNARK proof') - console.time('Proof time') - const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key) - const { proof } = websnarkUtils.toSolidityInput(proofData) - console.timeEnd('Proof time') + console.log('Approving tokens for deposit') + await erc20.methods.approve(erc20mixer._address, TOKEN_AMOUNT).send({ from: senderAccount, gas:1e6 }) - console.log('Submitting withdraw transaction') - const args = [ - toHex(input.root), - toHex(input.nullifierHash), - toHex(input.recipient, 20), - toHex(input.relayer, 20), - toHex(input.fee), - toHex(input.refund) - ] - await erc20mixer.methods.withdraw(proof, ...args).send({ from: (await web3.eth.getAccounts())[0], gas: 1e6 }) - console.log('Done') + console.log('Submitting deposit transaction') + await erc20mixer.methods.deposit(toHex(deposit.commitment)).send({ from: senderAccount, gas:1e6 }) + + const note = toHex(deposit.preimage, 62) + console.log('Your note:', note) + return note } -async function getBalance(recipient) { - const balance = await web3.eth.getBalance(recipient) - console.log('Balance is ', web3.utils.fromWei(balance)) -} - -async function getBalanceErc20(recipient, relayer) { - const balanceRecipient = await web3.eth.getBalance(recipient) - const balanceRelayer = await web3.eth.getBalance(relayer) - const tokenBalanceRecipient = await erc20.methods.balanceOf(recipient).call() - const tokenBalanceRelayer = await erc20.methods.balanceOf(relayer).call() - console.log('Recipient eth Balance is ', web3.utils.fromWei(balanceRecipient)) - console.log('Relayer eth Balance is ', web3.utils.fromWei(balanceRelayer)) - - console.log('Recipient token Balance is ', web3.utils.fromWei(tokenBalanceRecipient.toString())) - console.log('Relayer token Balance is ', web3.utils.fromWei(tokenBalanceRelayer.toString())) -} - -function toHex(number, length = 32) { - let str = bigInt(number).toString(16) - while (str.length < length * 2) str = '0' + str - str = '0x' + str - return str -} - -async function withdraw(note, recipient) { - // Decode hex string and restore the deposit object - let buf = Buffer.from(note.slice(2), 'hex') - let deposit = createDeposit(bigInt.leBuff2int(buf.slice(0, 31)), bigInt.leBuff2int(buf.slice(31, 62))) - const nullifierHash = pedersenHash(deposit.nullifier.leInt2Buff(31)) - const paddedNullifierHash = nullifierHash.toString(16).padStart('66', '0x000000') - const paddedCommitment = deposit.commitment.toString(16).padStart('66', '0x000000') - +/** + * Generate merkle tree for a deposit. + * Download deposit events from the contract, reconstructs merkle tree, finds our deposit leaf + * in it and generates merkle proof + * @param contract Mixer contract address + * @param deposit Deposit object + */ +async function generateMerkleProof(contract, deposit) { // Get all deposit events from smart contract and assemble merkle tree from them console.log('Getting current state from mixer contract') - const events = await mixer.getPastEvents('Deposit', { fromBlock: mixer.deployedBlock, toBlock: 'latest' }) + const events = await contract.getPastEvents('Deposit', { fromBlock: contract.deployedBlock, toBlock: 'latest' }) const leaves = events .sort((a, b) => a.returnValues.leafIndex - b.returnValues.leafIndex) // Sort events in chronological order .map(e => e.returnValues.commitment) const tree = new merkleTree(MERKLE_TREE_HEIGHT, leaves) // Find current commitment in the tree - let depositEvent = events.find(e => e.returnValues.commitment === paddedCommitment) + let depositEvent = events.find(e => e.returnValues.commitment === toHex(deposit.commitment)) let leafIndex = depositEvent ? depositEvent.returnValues.leafIndex : -1 // Validate that our data is correct - const isValidRoot = await mixer.methods.isKnownRoot(toHex(await tree.root())).call() - const isSpent = await mixer.methods.isSpent(paddedNullifierHash).call() - assert(isValidRoot === true) // Merkle tree assembled correctly - assert(isSpent === false) // The note is not spent - assert(leafIndex >= 0) // Our deposit is present in the tree + const isValidRoot = await contract.methods.isKnownRoot(toHex(await tree.root())).call() + const isSpent = await contract.methods.isSpent(toHex(deposit.nullifierHash)).call() + assert(isValidRoot === true, 'Merkle tree is corrupted') + assert(isSpent === false, 'The note is already spent') + assert(leafIndex >= 0, 'The deposit is not found in the tree') // Compute merkle proof of our commitment - const { root, path_elements, path_index } = await tree.path(leafIndex) + return await tree.path(leafIndex) +} + +/** + * Generate SNARK proof for withdrawal + * @param contract Mixer contract address + * @param note Note string + * @param recipient Funds recipient + * @param relayer Relayer address + * @param fee Relayer fee + * @param refund Receive ether for exchanged tokens + */ +async function generateProof(contract, note, recipient, relayer = 0, fee = 0, refund = 0) { + // Decode hex string and restore the deposit object + let buf = Buffer.from(note.slice(2), 'hex') + let deposit = createDeposit(bigInt.leBuff2int(buf.slice(0, 31)), bigInt.leBuff2int(buf.slice(31, 62))) + + // Compute merkle proof of our commitment + const { root, path_elements, path_index } = await generateMerkleProof(contract, deposit) // Prepare circuit input const input = { // Public snark inputs root: root, - nullifierHash, + nullifierHash: deposit.nullifierHash, recipient: bigInt(recipient), - relayer: bigInt(0), - fee: bigInt(0), - refund: bigInt(0), + relayer: bigInt(relayer), + fee: bigInt(fee), + refund: bigInt(refund), // Private snark inputs nullifier: deposit.nullifier, @@ -206,7 +156,6 @@ async function withdraw(note, recipient) { const { proof } = websnarkUtils.toSolidityInput(proofData) console.timeEnd('Proof time') - console.log('Submitting withdraw transaction') const args = [ toHex(input.root), toHex(input.nullifierHash), @@ -215,10 +164,109 @@ async function withdraw(note, recipient) { toHex(input.fee), toHex(input.refund) ] - await mixer.methods.withdraw(proof, ...args).send({ from: (await web3.eth.getAccounts())[0], gas: 1e6 }) + + return { proof, args } +} + +/** + * Do an ETH withdrawal + * @param note Note to withdraw + * @param recipient Recipient address + */ +async function withdraw(note, recipient) { + const { proof, args } = await generateProof(mixer, note, recipient) + + console.log('Submitting withdraw transaction') + await mixer.methods.withdraw(proof, ...args).send({ from: senderAccount, gas: 1e6 }) console.log('Done') } +/** + * Do a ERC20 withdrawal + * @param note Note to withdraw + * @param recipient Recipient address + */ +async function withdrawErc20(note, recipient) { + const { proof, args } = await generateProof(erc20mixer, note, recipient) + + console.log('Submitting withdraw transaction') + await erc20mixer.methods.withdraw(proof, ...args).send({ from: senderAccount, gas: 1e6 }) + console.log('Done') +} + +/** + * Do an ETH withdrawal through relay + * @param note Note to withdraw + * @param recipient Recipient address + * @param relayUrl Relay url address + */ +async function withdrawRelay(note, recipient, relayUrl) { + const resp = await axios.get(relayUrl + '/status') + const { relayerAddress, netId, gasPrices } = resp.data + assert(netId === await web3.eth.net.getId() || netId === '*', 'This relay is for different network') + console.log('Relay address: ', relayerAddress) + + const fee = bigInt(toWei(gasPrices.fast.toString(), 'gwei')).mul(bigInt(1e6)) + const { proof, args } = await generateProof(mixer, note, recipient, relayerAddress, fee) + + console.log('Sending withdraw transaction through relay') + const resp2 = await axios.post(relayUrl + '/relay', { contract: mixer._address, proof: { proof, publicSignals: args } }) + console.log(`Transaction submitted through relay, tx hash: ${resp2.data.txHash}`) + + let receipt = await waitForTxReceipt(resp2.data.txHash) + console.log('Transaction mined in block', receipt.blockNumber) + console.log('Done') +} + +/** + * Do a ERC20 withdrawal through relay + * @param note Note to withdraw + * @param recipient Recipient address + * @param relayUrl Relay url address + */ +async function withdrawRelayErc20(note, recipient, relayUrl) { + const resp = await axios.get(relayUrl + '/status') + const { relayerAddress, netId, gasPrices, ethPriceInDai } = resp.data + assert(netId === await web3.eth.net.getId() || netId === '*', 'This relay is for different network') + console.log('Relay address: ', relayerAddress) + + const refund = bigInt(toWei('0.001')) + const fee = bigInt(toWei(gasPrices.fast.toString(), 'gwei')).mul(bigInt(1e6)).add(refund).mul(bigInt(fromWei(ethPriceInDai.toString()))) + const { proof, args } = await generateProof(erc20mixer, note, recipient, relayerAddress, fee, refund) + + console.log('Sending withdraw transaction through relay') + const resp2 = await axios.post(relayUrl + '/relay', { contract: erc20mixer._address, proof: { proof, publicSignals: args } }) + console.log(`Transaction submitted through relay, tx hash: ${resp2.data.txHash}`) + + let receipt = await waitForTxReceipt(resp2.data.txHash) + console.log('Transaction mined in block', receipt.blockNumber) + console.log('Done') +} + +/** + * Waits for transaction to be mined + * @param txHash Hash of transaction + * @param attempts + * @param delay + */ +function waitForTxReceipt(txHash, attempts = 60, delay = 1000) { + return new Promise((resolve, reject) => { + const checkForTx = async (txHash, retryAttempt = 0) => { + const result = await web3.eth.getTransactionReceipt(txHash) + if (!result || !result.blockNumber) { + if (retryAttempt <= attempts) { + setTimeout(() => checkForTx(txHash, retryAttempt + 1), delay) + } else { + reject(new Error('tx was not mined')) + } + } else { + resolve(result) + } + } + checkForTx(txHash) + }) +} + /** * Init web3, contracts, and snark */ @@ -233,6 +281,7 @@ async function init() { proving_key = await (await fetch('build/circuits/withdraw_proving_key.bin')).arrayBuffer() MERKLE_TREE_HEIGHT = 16 ETH_AMOUNT = 1e18 + TOKEN_AMOUNT = 1e19 } else { // Initialize from local node web3 = new Web3('http://localhost:8545', null, { transactionConfirmationBlocks: 1 }) @@ -242,6 +291,7 @@ async function init() { require('dotenv').config() MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT ETH_AMOUNT = process.env.ETH_AMOUNT + TOKEN_AMOUNT = process.env.TOKEN_AMOUNT ERC20_TOKEN = process.env.ERC20_TOKEN erc20ContractJson = require('./build/contracts/ERC20Mock.json') erc20mixerJson = require('./build/contracts/ERC20Mixer.json') @@ -263,21 +313,30 @@ async function init() { const tx2 = await web3.eth.getTransaction(erc20ContractJson.networks[netId].transactionHash) erc20.deployedBlock = tx2.blockNumber } + + senderAccount = (await web3.eth.getAccounts())[0] console.log('Loaded') } // ========== CLI related stuff below ============== +/** Print command line help */ function printHelp(code = 0) { console.log(`Usage: Submit a deposit from default eth account and return the resulting note $ ./cli.js deposit + $ ./cli.js depositErc20 Withdraw a note to 'recipient' account - $ ./cli.js withdraw + $ ./cli.js withdraw [relayUrl] + $ ./cli.js withdrawErc20 [relayUrl] Check address balance $ ./cli.js balance
+ + Perform an automated test + $ ./cli.js test + $ ./cli.js testRelay Example: $ ./cli.js deposit @@ -289,75 +348,99 @@ Example: process.exit(code) } -if (inBrowser) { - window.deposit = deposit - window.withdraw = async () => { - const note = prompt('Enter the note to withdraw') - const recipient = (await web3.eth.getAccounts())[0] - await withdraw(note, recipient) - } - init() -} else { - const args = process.argv.slice(2) +/** Process command line args and run */ +async function runConsole(args) { if (args.length === 0) { printHelp() } else { switch (args[0]) { case 'deposit': if (args.length === 1) { - init().then(() => deposit()).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)}) - } - else + await init() + await printBalance(mixer._address, 'Mixer') + await printBalance(senderAccount, 'Sender account') + await deposit() + await printBalance(mixer._address, 'Mixer') + await printBalance(senderAccount, 'Sender account') + } else { printHelp(1) + } break case 'depositErc20': if (args.length === 1) { - init().then(() => depositErc20()).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)}) - } - else + await init() + await printBalance(erc20mixer._address, 'Mixer') + await printBalance(senderAccount, 'Sender account') + await depositErc20() + await printBalance(erc20mixer._address, 'Mixer') + await printBalance(senderAccount, 'Sender account') + } else { printHelp(1) + } break case 'balance': if (args.length === 2 && /^0x[0-9a-fA-F]{40}$/.test(args[1])) { - init().then(() => getBalance(args[1])).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)}) - } else - printHelp(1) - break - case 'balanceErc20': - if (args.length === 3 && /^0x[0-9a-fA-F]{40}$/.test(args[1]) && /^0x[0-9a-fA-F]{40}$/.test(args[2])) { - init().then(() => getBalanceErc20(args[1], args[2])).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)}) - } else + await init() + await printBalance(args[1]) + } else { printHelp(1) + } break case 'withdraw': - if (args.length === 3 && /^0x[0-9a-fA-F]{124}$/.test(args[1]) && /^0x[0-9a-fA-F]{40}$/.test(args[2])) { - init().then(() => withdraw(args[1], args[2])).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)}) - } - else + if (args.length >= 3 && args.length <= 4 && /^0x[0-9a-fA-F]{124}$/.test(args[1]) && /^0x[0-9a-fA-F]{40}$/.test(args[2])) { + await init() + await printBalance(mixer._address, 'Mixer') + await printBalance(args[2], 'Recipient account') + if (args[3]) { + await withdrawRelay(args[1], args[2], args[3]) + } else { + await withdraw(args[1], args[2]) + } + await printBalance(mixer._address, 'Mixer') + await printBalance(args[2], 'Recipient account') + } else { printHelp(1) + } break case 'withdrawErc20': - if (args.length === 4 && /^0x[0-9a-fA-F]{124}$/.test(args[1]) && /^0x[0-9a-fA-F]{40}$/.test(args[2]) && /^0x[0-9a-fA-F]{40}$/.test(args[3])) { - init().then(() => withdrawErc20(args[1], args[2], args[3])).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)}) - } - else + if (args.length >= 3 && args.length <= 4 && /^0x[0-9a-fA-F]{124}$/.test(args[1]) && /^0x[0-9a-fA-F]{40}$/.test(args[2])) { + await init() + await printBalance(erc20mixer._address, 'Mixer') + await printBalance(args[2], 'Recipient account') + if (args[3]) { + await withdrawRelayErc20(args[1], args[2], args[3]) + } else { + await withdrawErc20(args[1], args[2]) + } + await printBalance(erc20mixer._address, 'Mixer') + await printBalance(args[2], 'Recipient account') + } else { printHelp(1) + } break case 'test': if (args.length === 1) { - (async () => { - await init() - const account = (await web3.eth.getAccounts())[0] - const note = await deposit() - await withdraw(note, account) + await init() + const note1 = await deposit() + await withdraw(note1, senderAccount) - const note2 = await deposit() - await withdraw(note2, account, account) - process.exit(0) - })() - } - else + const note2 = await depositErc20() + await withdrawErc20(note2, senderAccount) + } else { printHelp(1) + } + break + case 'testRelay': + if (args.length === 1) { + await init() + const note1 = await deposit() + await withdrawRelay(note1, senderAccount, 'http://localhost:8000') + + const note2 = await depositErc20() + await withdrawRelayErc20(note2, senderAccount, 'http://localhost:8000') + } else { + printHelp(1) + } break default: @@ -365,3 +448,18 @@ if (inBrowser) { } } } + +if (inBrowser) { + window.deposit = deposit + window.depositErc20 = depositErc20 + window.withdraw = async () => { + const note = prompt('Enter the note to withdraw') + const recipient = (await web3.eth.getAccounts())[0] + await withdraw(note, recipient) + } + init() +} else { + runConsole(process.argv.slice(2)) + .then(() => process.exit(0)) + .catch(err => { console.log(err); process.exit(1) }) +} diff --git a/lib/MerkleTree.js b/lib/MerkleTree.js index 05b97ff..958b809 100644 --- a/lib/MerkleTree.js +++ b/lib/MerkleTree.js @@ -1,6 +1,5 @@ const jsStorage = require('./Storage') const hasherImpl = require('./MiMC') -const { bigInt } = require('snarkjs') class MerkleTree { @@ -12,7 +11,7 @@ class MerkleTree { this.zero_values = [] this.totalElements = 0 - let current_zero_value = bigInt('5702960885942360421128284892092891246826997279710054143430547229469817701242') + let current_zero_value = '5702960885942360421128284892092891246826997279710054143430547229469817701242' this.zero_values.push(current_zero_value) for (let i = 0; i < n_levels; i++) { current_zero_value = this.hasher.hash(i, current_zero_value, current_zero_value) @@ -77,6 +76,7 @@ class MerkleTree { this.path_index.push(element_index % 2) } } + index = Number(index) let traverser = new PathTraverser(this.prefix, this.storage, this.zero_values) const root = await this.storage.get_or_element( MerkleTree.index_to_key(this.prefix, this.n_levels, 0), diff --git a/package-lock.json b/package-lock.json index 84dbc06..3f5bf11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -488,6 +488,22 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, + "axios": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", + "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + } + } + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -3611,6 +3627,29 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==" }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", diff --git a/package.json b/package.json index d093a91..bc7ea0c 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@truffle/artifactor": "^4.0.38", "@truffle/contract": "^4.0.39", "@truffle/hdwallet-provider": "^1.0.24", + "axios": "^0.19.0", "bn-chai": "^1.0.1", "browserify": "^16.5.0", "chai": "^4.2.0",