diff --git a/.eslintrc.js b/.eslintrc.js index 23389a3..ca44f30 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,65 +1,44 @@ module.exports = { - "env": { - "es2021": true, - "node": true - }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:import/recommended", - "plugin:import/typescript", - "prettier", - "plugin:prettier/recommended", - ], - "overrides": [ - { - "env": { - "node": true - }, - "files": [ - ".eslintrc.{js,cjs}" - ], - "parserOptions": { - "sourceType": "script" - } - } - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "plugins": [ - "@typescript-eslint", - "prettier" - ], - "rules": { - "prettier/prettier": [ - "error", - { - singleQuote: true, - printWidth: 120 - } + env: { + es2021: true, + node: true, + }, + extends: [ + 'prettier', + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:import/recommended', + 'plugin:import/typescript', + 'plugin:prettier/recommended', ], - "import/order": ["error"], - /** - "indent": [ - "error", - 2 + overrides: [ + { + env: { + node: true, + }, + files: ['.eslintrc.{js,cjs}'], + parserOptions: { + sourceType: 'script', + }, + }, ], - **/ - "linebreak-style": [ - "error", - "unix" - ], - "quotes": [ - "error", - "single" - ], - "semi": [ - "error", - "always" - ], - "@typescript-eslint/no-unused-vars": ["warn"] - } -} + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + plugins: ['@typescript-eslint', 'prettier'], + rules: { + 'prettier/prettier': [ + 'error', + { + tabWidth: 4, + printWidth: 120, + singleQuote: true, + }, + ], + 'import/order': ['error'], + '@typescript-eslint/no-unused-vars': ['warn'], + '@typescript-eslint/no-unused-expressions': ['off'], + }, +}; diff --git a/dist/cli.js b/dist/cli.js index b1840e6..3ee6ba0 100644 --- a/dist/cli.js +++ b/dist/cli.js @@ -181669,9 +181669,7 @@ BigInt.prototype.toJSON = function() { }; const isNode = !process.browser && typeof globalThis.window === "undefined"; const dist_crypto = isNode ? external_crypto_.webcrypto : globalThis.crypto; -const chunk = (arr, size) => [...Array(Math.ceil(arr.length / size))].map( - (_, i) => arr.slice(size * i, size + size * i) -); +const chunk = (arr, size) => [...Array(Math.ceil(arr.length / size))].map((_, i) => arr.slice(size * i, size + size * i)); function dist_sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } @@ -181699,9 +181697,7 @@ function bufferToBytes(b) { return new Uint8Array(b.buffer); } function bytesToBase64(bytes) { - return btoa( - bytes.reduce((data, byte) => data + String.fromCharCode(byte), "") - ); + return btoa(bytes.reduce((data, byte) => data + String.fromCharCode(byte), "")); } function base64ToBytes(base64) { return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); @@ -181716,11 +181712,7 @@ function dist_hexToBytes(hexString) { if (hexString.length % 2 !== 0) { hexString = "0" + hexString; } - return Uint8Array.from( - hexString.match(/.{1,2}/g).map( - (byte) => parseInt(byte, 16) - ) - ); + return Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))); } function bytesToBN(bytes) { return BigInt(dist_bytesToHex(bytes)); @@ -181733,11 +181725,7 @@ function bnToBytes(bigint) { if (hexString.length % 2 !== 0) { hexString = "0" + hexString; } - return Uint8Array.from( - hexString.match(/.{1,2}/g).map( - (byte) => parseInt(byte, 16) - ) - ); + return Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))); } function leBuff2Int(bytes) { return new bn(bytes, 16, "le"); @@ -181800,9 +181788,7 @@ function getHttpAgent({ const { HttpsProxyAgent } = require("https-proxy-agent"); const { SocksProxyAgent } = require("socks-proxy-agent"); if (torPort) { - return new SocksProxyAgent( - `socks5h://tor${retry}@127.0.0.1:${torPort}` - ); + return new SocksProxyAgent(`socks5h://tor${retry}@127.0.0.1:${torPort}`); } if (!proxyUrl) { return; @@ -182007,9 +181993,7 @@ const populateTransaction = async (signer, tx) => { tx.gasLimit = gasLimit === BigInt(21e3) ? gasLimit : gasLimit * (BigInt(1e4) + BigInt(signer.gasLimitBump)) / BigInt(1e4); } catch (error) { if (signer.gasFailover) { - console.log( - "populateTransaction: warning gas estimation failed falling back to 3M gas" - ); + console.log("populateTransaction: warning gas estimation failed falling back to 3M gas"); tx.gasLimit = BigInt("3000000"); } else { throw error; @@ -182024,12 +182008,7 @@ class TornadoWallet extends Wallet { gasLimitBump; gasFailover; bumpNonce; - constructor(key, provider, { - gasPriceBump, - gasLimitBump, - gasFailover, - bumpNonce - } = {}) { + constructor(key, provider, { gasPriceBump, gasLimitBump, gasFailover, bumpNonce } = {}) { super(key, provider); this.gasPriceBump = gasPriceBump ?? 0; this.gasLimitBump = gasLimitBump ?? 3e3; @@ -182038,16 +182017,8 @@ class TornadoWallet extends Wallet { } static fromMnemonic(mneomnic, provider, index = 0, options) { const defaultPath = `m/44'/60'/0'/0/${index}`; - const { privateKey } = HDNodeWallet.fromPhrase( - mneomnic, - void 0, - defaultPath - ); - return new TornadoWallet( - privateKey, - provider, - options - ); + const { privateKey } = HDNodeWallet.fromPhrase(mneomnic, void 0, defaultPath); + return new TornadoWallet(privateKey, provider, options); } async populateTransaction(tx) { const txObject = await populateTransaction(this, tx); @@ -182061,12 +182032,7 @@ class TornadoVoidSigner extends VoidSigner { gasLimitBump; gasFailover; bumpNonce; - constructor(address, provider, { - gasPriceBump, - gasLimitBump, - gasFailover, - bumpNonce - } = {}) { + constructor(address, provider, { gasPriceBump, gasLimitBump, gasFailover, bumpNonce } = {}) { super(address, provider); this.gasPriceBump = gasPriceBump ?? 0; this.gasLimitBump = gasLimitBump ?? 3e3; @@ -182085,12 +182051,7 @@ class TornadoRpcSigner extends JsonRpcSigner { gasLimitBump; gasFailover; bumpNonce; - constructor(provider, address, { - gasPriceBump, - gasLimitBump, - gasFailover, - bumpNonce - } = {}) { + constructor(provider, address, { gasPriceBump, gasLimitBump, gasFailover, bumpNonce } = {}) { super(provider, address); this.gasPriceBump = gasPriceBump ?? 0; this.gasLimitBump = gasLimitBump ?? 3e3; @@ -182098,9 +182059,7 @@ class TornadoRpcSigner extends JsonRpcSigner { this.bumpNonce = bumpNonce ?? false; } async sendUncheckedTransaction(tx) { - return super.sendUncheckedTransaction( - await populateTransaction(this, tx) - ); + return super.sendUncheckedTransaction(await populateTransaction(this, tx)); } } class TornadoBrowserProvider extends BrowserProvider { @@ -182115,22 +182074,13 @@ class TornadoBrowserProvider extends BrowserProvider { await this.options.connectWallet(this.options?.netId); } if (this.options?.handleNetworkChanges) { - window?.ethereum?.on( - "chainChanged", - this.options.handleNetworkChanges - ); + window?.ethereum?.on("chainChanged", this.options.handleNetworkChanges); } if (this.options?.handleAccountChanges) { - window?.ethereum?.on( - "accountsChanged", - this.options.handleAccountChanges - ); + window?.ethereum?.on("accountsChanged", this.options.handleAccountChanges); } if (this.options?.handleAccountDisconnect) { - window?.ethereum?.on( - "disconnect", - this.options.handleAccountDisconnect - ); + window?.ethereum?.on("disconnect", this.options.handleAccountDisconnect); } return new TornadoRpcSigner(this, signerAddress, this.options); } @@ -182399,11 +182349,7 @@ async function getStatistic({ }; } } -async function getMeta({ - graphApi, - subgraphName, - fetchDataOptions: fetchDataOptions2 -}) { +async function getMeta({ graphApi, subgraphName, fetchDataOptions: fetchDataOptions2 }) { try { const { _meta: { @@ -182487,9 +182433,7 @@ async function getAllRegisters({ events.push(...result2); break; } - result2 = result2.filter( - ({ blockRegistration }) => blockRegistration !== lastEvent.blockRegistration - ); + result2 = result2.filter(({ blockRegistration }) => blockRegistration !== lastEvent.blockRegistration); fromBlock = Number(lastEvent.blockRegistration); events.push(...result2); } @@ -182499,18 +182443,16 @@ async function getAllRegisters({ lastSyncBlock }; } - const result = events.map( - ({ id, address, ensName, blockRegistration }) => { - const [transactionHash, logIndex] = id.split("-"); - return { - blockNumber: Number(blockRegistration), - logIndex: Number(logIndex), - transactionHash, - ensName, - relayerAddress: address_getAddress(address) - }; - } - ); + const result = events.map(({ id, address, ensName, blockRegistration }) => { + const [transactionHash, logIndex] = id.split("-"); + return { + blockNumber: Number(blockRegistration), + logIndex: Number(logIndex), + transactionHash, + ensName, + relayerAddress: address_getAddress(address) + }; + }); return { events: result, lastSyncBlock @@ -182587,9 +182529,7 @@ async function getAllDeposits({ events.push(...result2); break; } - result2 = result2.filter( - ({ blockNumber }) => blockNumber !== lastEvent2.blockNumber - ); + result2 = result2.filter(({ blockNumber }) => blockNumber !== lastEvent2.blockNumber); fromBlock = Number(lastEvent2.blockNumber); events.push(...result2); } @@ -182599,20 +182539,18 @@ async function getAllDeposits({ lastSyncBlock }; } - const result = events.map( - ({ id, blockNumber, commitment, index, timestamp, from }) => { - const [transactionHash, logIndex] = id.split("-"); - return { - blockNumber: Number(blockNumber), - logIndex: Number(logIndex), - transactionHash, - commitment, - leafIndex: Number(index), - timestamp: Number(timestamp), - from: address_getAddress(from) - }; - } - ); + const result = events.map(({ id, blockNumber, commitment, index, timestamp, from }) => { + const [transactionHash, logIndex] = id.split("-"); + return { + blockNumber: Number(blockNumber), + logIndex: Number(logIndex), + transactionHash, + commitment, + leafIndex: Number(index), + timestamp: Number(timestamp), + from: address_getAddress(from) + }; + }); const [lastEvent] = result.slice(-1); return { events: result, @@ -182693,9 +182631,7 @@ async function getAllWithdrawals({ events.push(...result2); break; } - result2 = result2.filter( - ({ blockNumber }) => blockNumber !== lastEvent2.blockNumber - ); + result2 = result2.filter(({ blockNumber }) => blockNumber !== lastEvent2.blockNumber); fromBlock = Number(lastEvent2.blockNumber); events.push(...result2); } @@ -182705,20 +182641,18 @@ async function getAllWithdrawals({ lastSyncBlock }; } - const result = events.map( - ({ id, blockNumber, nullifier, to, fee, timestamp }) => { - const [transactionHash, logIndex] = id.split("-"); - return { - blockNumber: Number(blockNumber), - logIndex: Number(logIndex), - transactionHash, - nullifierHash: nullifier, - to: address_getAddress(to), - fee, - timestamp: Number(timestamp) - }; - } - ); + const result = events.map(({ id, blockNumber, nullifier, to, fee, timestamp }) => { + const [transactionHash, logIndex] = id.split("-"); + return { + blockNumber: Number(blockNumber), + logIndex: Number(logIndex), + transactionHash, + nullifierHash: nullifier, + to: address_getAddress(to), + fee, + timestamp: Number(timestamp) + }; + }); const [lastEvent] = result.slice(-1); return { events: result, @@ -182825,9 +182759,7 @@ async function getAllGraphEchoEvents({ events.push(...result2); break; } - result2 = result2.filter( - ({ blockNumber }) => blockNumber !== lastEvent2.blockNumber - ); + result2 = result2.filter(({ blockNumber }) => blockNumber !== lastEvent2.blockNumber); fromBlock = Number(lastEvent2.blockNumber); events.push(...result2); } @@ -182919,9 +182851,7 @@ async function getAllEncryptedNotes({ events.push(...result2); break; } - result2 = result2.filter( - ({ blockNumber }) => blockNumber !== lastEvent2.blockNumber - ); + result2 = result2.filter(({ blockNumber }) => blockNumber !== lastEvent2.blockNumber); fromBlock = Number(lastEvent2.blockNumber); events.push(...result2); } @@ -183025,17 +182955,7 @@ async function getAllGovernanceEvents({ } ); const formattedVotes = votes.map( - ({ - blockNumber, - logIndex, - transactionHash, - proposalId, - voter, - support, - votes: votes2, - from, - input - }) => { + ({ blockNumber, logIndex, transactionHash, proposalId, voter, support, votes: votes2, from, input }) => { if (!input || input.length > 2048) { input = ""; } @@ -183054,13 +182974,7 @@ async function getAllGovernanceEvents({ } ); const formattedDelegates = delegates.map( - ({ - blockNumber, - logIndex, - transactionHash, - account, - delegateTo - }) => { + ({ blockNumber, logIndex, transactionHash, account, delegateTo }) => { return { blockNumber: Number(blockNumber), logIndex: Number(logIndex), @@ -183072,13 +182986,7 @@ async function getAllGovernanceEvents({ } ); const formattedUndelegates = undelegates.map( - ({ - blockNumber, - logIndex, - transactionHash, - account, - delegateFrom - }) => { + ({ blockNumber, logIndex, transactionHash, account, delegateFrom }) => { return { blockNumber: Number(blockNumber), logIndex: Number(logIndex), @@ -183114,9 +183022,7 @@ async function getAllGovernanceEvents({ count: eventsLength }); } - formattedEvents = formattedEvents.filter( - ({ blockNumber }) => blockNumber !== lastEvent2.blockNumber - ); + formattedEvents = formattedEvents.filter(({ blockNumber }) => blockNumber !== lastEvent2.blockNumber); fromBlock = Number(lastEvent2.blockNumber); result.push(...formattedEvents); } @@ -183206,9 +183112,7 @@ class BatchBlockService { let err; while (!this.shouldRetry && retries === 0 || this.shouldRetry && retries < this.retryMax) { try { - return await Promise.all( - blocks.map((b) => this.getBlock(b)) - ); + return await Promise.all(blocks.map((b) => this.getBlock(b))); } catch (e) { retries++; err = e; @@ -183222,13 +183126,8 @@ class BatchBlockService { async getBatchBlocks(blocks) { let blockCount = 0; const results = []; - for (const chunks of chunk( - blocks, - this.concurrencySize * this.batchSize - )) { - const chunksResult = (await Promise.all( - this.createBatchRequest(chunk(chunks, this.batchSize)) - )).flat(); + for (const chunks of chunk(blocks, this.concurrencySize * this.batchSize)) { + const chunksResult = (await Promise.all(this.createBatchRequest(chunk(chunks, this.batchSize)))).flat(); results.push(...chunksResult); blockCount += chunks.length; if (typeof this.onProgress === "function") { @@ -183283,9 +183182,7 @@ class BatchTransactionService { let err; while (!this.shouldRetry && retries === 0 || this.shouldRetry && retries < this.retryMax) { try { - return await Promise.all( - txs.map((tx) => this.getTransaction(tx)) - ); + return await Promise.all(txs.map((tx) => this.getTransaction(tx))); } catch (e) { retries++; err = e; @@ -183299,13 +183196,8 @@ class BatchTransactionService { async getBatchTransactions(txs) { let txCount = 0; const results = []; - for (const chunks of chunk( - txs, - this.concurrencySize * this.batchSize - )) { - const chunksResult = (await Promise.all( - this.createBatchRequest(chunk(chunks, this.batchSize)) - )).flat(); + for (const chunks of chunk(txs, this.concurrencySize * this.batchSize)) { + const chunksResult = (await Promise.all(this.createBatchRequest(chunk(chunks, this.batchSize)))).flat(); results.push(...chunksResult); txCount += chunks.length; if (typeof this.onProgress === "function") { @@ -183347,27 +183239,17 @@ class BatchEventsService { this.retryMax = retryMax; this.retryOn = retryOn; } - async getPastEvents({ - fromBlock, - toBlock, - type - }) { + async getPastEvents({ fromBlock, toBlock, type }) { let err; let retries = 0; while (!this.shouldRetry && retries === 0 || this.shouldRetry && retries < this.retryMax) { try { - return await this.contract.queryFilter( - type, - fromBlock, - toBlock - ); + return await this.contract.queryFilter(type, fromBlock, toBlock); } catch (e) { err = e; retries++; if (e.message.includes("after last accepted block")) { - const acceptedBlock = parseInt( - e.message.split("after last accepted block ")[1] - ); + const acceptedBlock = parseInt(e.message.split("after last accepted block ")[1]); toBlock = acceptedBlock; } await dist_sleep(this.retryOn); @@ -183381,11 +183263,7 @@ class BatchEventsService { return this.getPastEvents(event); }); } - async getBatchEvents({ - fromBlock, - toBlock, - type = "*" - }) { + async getBatchEvents({ fromBlock, toBlock, type = "*" }) { if (!toBlock) { toBlock = await this.provider.getBlockNumber(); } @@ -183966,9 +183844,7 @@ const defaultConfig = { } } }; -const enabledChains = Object.values(NetId).filter( - (n) => typeof n === "number" -); +const enabledChains = Object.values(NetId).filter((n) => typeof n === "number"); let customConfig = {}; function addNetwork(newConfig) { enabledChains.push( @@ -184087,9 +183963,7 @@ const baseEventsSchemaProperty = { }, transactionHash: bytes32SchemaType }; -const baseEventsSchemaRequired = Object.keys( - baseEventsSchemaProperty -); +const baseEventsSchemaRequired = Object.keys(baseEventsSchemaProperty); const governanceEventsSchema = { type: "array", items: { @@ -184150,11 +184024,7 @@ const governanceEventsSchema = { account: addressSchemaType, delegateTo: addressSchemaType }, - required: [ - ...baseEventsSchemaRequired, - "account", - "delegateTo" - ], + required: [...baseEventsSchemaRequired, "account", "delegateTo"], additionalProperties: false }, { @@ -184165,11 +184035,7 @@ const governanceEventsSchema = { account: addressSchemaType, delegateFrom: addressSchemaType }, - required: [ - ...baseEventsSchemaRequired, - "account", - "delegateFrom" - ], + required: [...baseEventsSchemaRequired, "account", "delegateFrom"], additionalProperties: false } ] @@ -184199,13 +184065,7 @@ const depositsEventsSchema = { timestamp: { type: "number" }, from: addressSchemaType }, - required: [ - ...baseEventsSchemaRequired, - "commitment", - "leafIndex", - "timestamp", - "from" - ], + required: [...baseEventsSchemaRequired, "commitment", "leafIndex", "timestamp", "from"], additionalProperties: false } }; @@ -184220,13 +184080,7 @@ const withdrawalsEventsSchema = { fee: bnSchemaType, timestamp: { type: "number" } }, - required: [ - ...baseEventsSchemaRequired, - "nullifierHash", - "to", - "fee", - "timestamp" - ], + required: [...baseEventsSchemaRequired, "nullifierHash", "to", "fee", "timestamp"], additionalProperties: false } }; @@ -184314,28 +184168,14 @@ const statusSchema = { onSyncEvents: { type: "boolean" }, currentQueue: { type: "number" } }, - required: [ - "rewardAccount", - "instances", - "netId", - "tornadoServiceFee", - "version", - "health", - "currentQueue" - ] + required: ["rewardAccount", "instances", "netId", "tornadoServiceFee", "version", "health", "currentQueue"] }; function getStatusSchema(netId, config, tovarish) { const { tokens, optionalTokens, disabledTokens, nativeCurrency } = config; const schema = JSON.parse(JSON.stringify(statusSchema)); const instances = Object.keys(tokens).reduce( (acc, token) => { - const { - instanceAddress, - tokenAddress, - symbol, - decimals, - optionalInstances = [] - } = tokens[token]; + const { instanceAddress, tokenAddress, symbol, decimals, optionalInstances = [] } = tokens[token]; const amounts = Object.keys(instanceAddress); const instanceProperties = { type: "object", @@ -184349,9 +184189,7 @@ function getStatusSchema(netId, config, tovarish) { }, {} ), - required: amounts.filter( - (amount) => !optionalInstances.includes(amount) - ) + required: amounts.filter((amount) => !optionalInstances.includes(amount)) }, decimals: { enum: [decimals] } }, @@ -184388,26 +184226,17 @@ function getStatusSchema(netId, config, tovarish) { if (_tokens.length) { const ethPrices = { type: "object", - properties: _tokens.reduce( - (acc, token) => { - acc[token] = bnSchemaType; - return acc; - }, - {} - ), + properties: _tokens.reduce((acc, token) => { + acc[token] = bnSchemaType; + return acc; + }, {}), required: _tokens }; schema.properties.ethPrices = ethPrices; schema.required.push("ethPrices"); } if (tovarish) { - schema.required.push( - "gasPrices", - "latestBlock", - "latestBalance", - "syncStatus", - "onSyncEvents" - ); + schema.required.push("gasPrices", "latestBlock", "latestBalance", "syncStatus", "onSyncEvents"); } return schema; } @@ -184439,10 +184268,7 @@ const jobRequestSchema = { const MIN_FEE = 0.1; const MAX_FEE = 0.9; const MIN_STAKE_BALANCE = parseEther("500"); -function calculateScore({ - stakeBalance, - tornadoServiceFee -}) { +function calculateScore({ stakeBalance, tornadoServiceFee }) { if (tornadoServiceFee < MIN_FEE) { tornadoServiceFee = MIN_FEE; } else if (tornadoServiceFee >= MAX_FEE) { @@ -184451,9 +184277,7 @@ function calculateScore({ const serviceFeeCoefficient = (tornadoServiceFee - MIN_FEE) ** 2; const feeDiffCoefficient = 1 / (MAX_FEE - MIN_FEE) ** 2; const coefficientsMultiplier = 1 - feeDiffCoefficient * serviceFeeCoefficient; - return BigInt( - Math.floor(Number(stakeBalance || "0") * coefficientsMultiplier) - ); + return BigInt(Math.floor(Number(stakeBalance || "0") * coefficientsMultiplier)); } function getWeightRandom(weightsScores, random) { for (let i = 0; i < weightsScores.length; i++) { @@ -184511,9 +184335,7 @@ class RelayerClient { timeout: 3e4, maxRetry: this.fetchDataOptions?.torPort ? 2 : 0 }); - const statusValidator = dist_ajv.compile( - getStatusSchema(this.netId, this.config, this.tovarish) - ); + const statusValidator = dist_ajv.compile(getStatusSchema(this.netId, this.config, this.tovarish)); if (!statusValidator(rawStatus)) { throw new Error("Invalid status schema"); } @@ -184528,9 +184350,7 @@ class RelayerClient { throw new Error("This relayer serves a different network"); } if (relayerAddress && this.netId === NetId.MAINNET && status.rewardAccount !== relayerAddress) { - throw new Error( - "The Relayer reward address must match registered address" - ); + throw new Error("The Relayer reward address must match registered address"); } return status; } @@ -184570,18 +184390,18 @@ class RelayerClient { } async getValidRelayers(relayers) { const invalidRelayers = []; - const validRelayers = (await Promise.all( - relayers.map((relayer) => this.filterRelayer(relayer)) - )).filter((r) => { - if (!r) { - return false; + const validRelayers = (await Promise.all(relayers.map((relayer) => this.filterRelayer(relayer)))).filter( + (r) => { + if (!r) { + return false; + } + if (r.hasError) { + invalidRelayers.push(r); + return false; + } + return true; } - if (r.hasError) { - invalidRelayers.push(r); - return false; - } - return true; - }); + ); return { validRelayers, invalidRelayers @@ -184642,20 +184462,14 @@ class RelayerClient { const errMsg = `Job ${status}: ${jobUrl} failed reason: ${failedReason}`; throw new Error(errMsg); } else if (status === "SENT") { - console.log( - `Job ${status}: ${jobUrl}, txhash: ${txHash} -` - ); + console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash} +`); } else if (status === "MINED") { - console.log( - `Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations} -` - ); + console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations} +`); } else if (status === "CONFIRMED") { - console.log( - `Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations} -` - ); + console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations} +`); } else { console.log(`Job ${status}: ${jobUrl} `); @@ -184730,38 +184544,17 @@ class BaseEventsService { }; } /* eslint-disable @typescript-eslint/no-unused-vars */ - updateEventProgress({ - percentage, - type, - fromBlock, - toBlock, - count - }) { + updateEventProgress({ percentage, type, fromBlock, toBlock, count }) { } - updateBlockProgress({ - percentage, - currentIndex, - totalIndex - }) { + updateBlockProgress({ percentage, currentIndex, totalIndex }) { } - updateTransactionProgress({ - percentage, - currentIndex, - totalIndex - }) { + updateTransactionProgress({ percentage, currentIndex, totalIndex }) { } - updateGraphProgress({ - type, - fromBlock, - toBlock, - count - }) { + updateGraphProgress({ type, fromBlock, toBlock, count }) { } /* eslint-enable @typescript-eslint/no-unused-vars */ async formatEvents(events) { - return await new Promise( - (resolve) => resolve(events) - ); + return await new Promise((resolve) => resolve(events)); } /** * Get saved or cached events @@ -184845,9 +184638,7 @@ class BaseEventsService { }; } } - async getLatestEvents({ - fromBlock - }) { + async getLatestEvents({ fromBlock }) { if (this.tovarishClient?.selectedRelayer && ![DEPOSIT, WITHDRAWAL].includes(this.type.toLowerCase())) { const { events, lastSyncBlock: lastBlock } = await this.tovarishClient.getEvents({ type: this.getTovarishType(), @@ -184894,10 +184685,7 @@ class BaseEventsService { } const newEvents = await this.getLatestEvents({ fromBlock }); const eventSet = /* @__PURE__ */ new Set(); - const allEvents = [ - ...savedEvents.events, - ...newEvents.events - ].sort((a, b) => { + const allEvents = [...savedEvents.events, ...newEvents.events].sort((a, b) => { if (a.blockNumber === b.blockNumber) { return a.logIndex - b.logIndex; } @@ -184932,14 +184720,7 @@ class BaseTornadoService extends BaseEventsService { batchTransactionService; batchBlockService; constructor(serviceConstructor) { - const { - Tornado: contract, - amount, - currency, - provider, - optionalTree, - merkleTreeService - } = serviceConstructor; + const { Tornado: contract, amount, currency, provider, optionalTree, merkleTreeService } = serviceConstructor; super({ ...serviceConstructor, contract @@ -184976,60 +184757,44 @@ class BaseTornadoService extends BaseEventsService { async formatEvents(events) { const type = this.getType().toLowerCase(); if (type === DEPOSIT) { - const formattedEvents = events.map( - ({ blockNumber, index: logIndex, transactionHash, args }) => { - const { commitment, leafIndex, timestamp } = args; - return { - blockNumber, - logIndex, - transactionHash, - commitment, - leafIndex: Number(leafIndex), - timestamp: Number(timestamp) - }; - } - ); - const txs = await this.batchTransactionService.getBatchTransactions( - [ - ...new Set( - formattedEvents.map( - ({ transactionHash }) => transactionHash - ) - ) - ] - ); + const formattedEvents = events.map(({ blockNumber, index: logIndex, transactionHash, args }) => { + const { commitment, leafIndex, timestamp } = args; + return { + blockNumber, + logIndex, + transactionHash, + commitment, + leafIndex: Number(leafIndex), + timestamp: Number(timestamp) + }; + }); + const txs = await this.batchTransactionService.getBatchTransactions([ + ...new Set(formattedEvents.map(({ transactionHash }) => transactionHash)) + ]); return formattedEvents.map((event) => { - const { from } = txs.find( - ({ hash }) => hash === event.transactionHash - ); + const { from } = txs.find(({ hash }) => hash === event.transactionHash); return { ...event, from }; }); } else { - const formattedEvents = events.map( - ({ blockNumber, index: logIndex, transactionHash, args }) => { - const { nullifierHash, to, fee } = args; - return { - blockNumber, - logIndex, - transactionHash, - nullifierHash: String(nullifierHash), - to: address_getAddress(to), - fee: String(fee) - }; - } - ); + const formattedEvents = events.map(({ blockNumber, index: logIndex, transactionHash, args }) => { + const { nullifierHash, to, fee } = args; + return { + blockNumber, + logIndex, + transactionHash, + nullifierHash: String(nullifierHash), + to: address_getAddress(to), + fee: String(fee) + }; + }); const blocks = await this.batchBlockService.getBatchBlocks([ - ...new Set( - formattedEvents.map(({ blockNumber }) => blockNumber) - ) + ...new Set(formattedEvents.map(({ blockNumber }) => blockNumber)) ]); return formattedEvents.map((event) => { - const { timestamp } = blocks.find( - ({ number }) => number === event.blockNumber - ); + const { timestamp } = blocks.find(({ number }) => number === event.blockNumber); return { ...event, timestamp @@ -185049,9 +184814,7 @@ class BaseTornadoService extends BaseEventsService { throw new Error(errMsg); } if (this.merkleTreeService && (!this.optionalTree || hasNewEvents)) { - return await this.merkleTreeService.verifyTree( - depositEvents - ); + return await this.merkleTreeService.verifyTree(depositEvents); } } return void 0; @@ -185105,9 +184868,7 @@ class BaseEchoService extends BaseEventsService { } }).filter((e) => e); } - async getEventsFromGraph({ - fromBlock - }) { + async getEventsFromGraph({ fromBlock }) { if (!this.graphApi || this.graphApi.includes("api.thegraph.com")) { return { events: [], @@ -185205,20 +184966,14 @@ function parseDescription(id, text) { function parseComment(Governance, calldata) { try { const methodLength = 4; - const result = abiCoder.decode( - ["address[]", "uint256", "bool"], - data_dataSlice(calldata, methodLength) - ); + const result = abiCoder.decode(["address[]", "uint256", "bool"], data_dataSlice(calldata, methodLength)); const data = Governance.interface.encodeFunctionData( // @ts-expect-error encodeFunctionData is broken lol "castDelegatedVote", result ); const length = dataLength(data); - const str = abiCoder.decode( - ["string"], - data_dataSlice(calldata, length) - )[0]; + const str = abiCoder.decode(["string"], data_dataSlice(calldata, length))[0]; const [contact, message] = JSON.parse(str); return { contact, @@ -185265,84 +185020,61 @@ class BaseGovernanceService extends BaseEventsService { const votedEvents = []; const delegatedEvents = []; const undelegatedEvents = []; - events.forEach( - ({ + events.forEach(({ blockNumber, index: logIndex, transactionHash, args, eventName: event }) => { + const eventObjects = { blockNumber, - index: logIndex, + logIndex, transactionHash, - args, - eventName: event - }) => { - const eventObjects = { - blockNumber, - logIndex, - transactionHash, - event - }; - if (event === "ProposalCreated") { - const { - id, - proposer, - target, - startTime, - endTime, - description - } = args; - proposalEvents.push({ - ...eventObjects, - id: Number(id), - proposer, - target, - startTime: Number(startTime), - endTime: Number(endTime), - description - }); - } - if (event === "Voted") { - const { proposalId, voter, support, votes } = args; - votedEvents.push({ - ...eventObjects, - proposalId: Number(proposalId), - voter, - support, - votes, - from: "", - input: "" - }); - } - if (event === "Delegated") { - const { account, to: delegateTo } = args; - delegatedEvents.push({ - ...eventObjects, - account, - delegateTo - }); - } - if (event === "Undelegated") { - const { account, from: delegateFrom } = args; - undelegatedEvents.push({ - ...eventObjects, - account, - delegateFrom - }); - } + event + }; + if (event === "ProposalCreated") { + const { id, proposer, target, startTime, endTime, description } = args; + proposalEvents.push({ + ...eventObjects, + id: Number(id), + proposer, + target, + startTime: Number(startTime), + endTime: Number(endTime), + description + }); } - ); + if (event === "Voted") { + const { proposalId, voter, support, votes } = args; + votedEvents.push({ + ...eventObjects, + proposalId: Number(proposalId), + voter, + support, + votes, + from: "", + input: "" + }); + } + if (event === "Delegated") { + const { account, to: delegateTo } = args; + delegatedEvents.push({ + ...eventObjects, + account, + delegateTo + }); + } + if (event === "Undelegated") { + const { account, from: delegateFrom } = args; + undelegatedEvents.push({ + ...eventObjects, + account, + delegateFrom + }); + } + }); if (votedEvents.length) { this.updateTransactionProgress({ percentage: 0 }); - const txs = await this.batchTransactionService.getBatchTransactions( - [ - ...new Set( - votedEvents.map( - ({ transactionHash }) => transactionHash - ) - ) - ] - ); + const txs = await this.batchTransactionService.getBatchTransactions([ + ...new Set(votedEvents.map(({ transactionHash }) => transactionHash)) + ]); votedEvents.forEach((event, index) => { - let { data: input, from } = txs.find( - (t) => t.hash === event.transactionHash - ); + let { data: input, from } = txs.find((t) => t.hash === event.transactionHash); if (!input || input.length > 2048) { input = ""; } @@ -185350,16 +185082,9 @@ class BaseGovernanceService extends BaseEventsService { votedEvents[index].input = input; }); } - return [ - ...proposalEvents, - ...votedEvents, - ...delegatedEvents, - ...undelegatedEvents - ]; + return [...proposalEvents, ...votedEvents, ...delegatedEvents, ...undelegatedEvents]; } - async getEventsFromGraph({ - fromBlock - }) { + async getEventsFromGraph({ fromBlock }) { if (!this.graphApi || !this.subgraphName || this.graphApi.includes("api.thegraph.com")) { return { events: [], @@ -185370,12 +185095,8 @@ class BaseGovernanceService extends BaseEventsService { } async getAllProposals() { const { events } = await this.updateEvents(); - const proposalEvents = events.filter( - (e) => e.event === "ProposalCreated" - ); - const allProposers = [ - ...new Set(proposalEvents.map((e) => [e.proposer]).flat()) - ]; + const proposalEvents = events.filter((e) => e.event === "ProposalCreated"); + const allProposers = [...new Set(proposalEvents.map((e) => [e.proposer]).flat())]; const [QUORUM_VOTES, proposalStatus, proposerNameRecords] = await Promise.all([ this.Governance.QUORUM_VOTES(), this.Aggregator.getAllProposals(this.Governance.target), @@ -185415,9 +185136,7 @@ class BaseGovernanceService extends BaseEventsService { const votedEvents = events.filter( (e) => e.event === "Voted" && e.proposalId === proposalId ); - const allVoters = [ - ...new Set(votedEvents.map((e) => [e.from, e.voter]).flat()) - ]; + const allVoters = [...new Set(votedEvents.map((e) => [e.from, e.voter]).flat())]; const names = await this.ReverseRecords.getNames(allVoters); const ensNames = allVoters.reduce( (acc, address, index) => { @@ -185430,10 +185149,7 @@ class BaseGovernanceService extends BaseEventsService { ); const votes = votedEvents.map((event) => { const { from, voter } = event; - const { contact, message } = parseComment( - this.Governance, - event.input - ); + const { contact, message } = parseComment(this.Governance, event.input); return { ...event, contact, @@ -185446,12 +185162,8 @@ class BaseGovernanceService extends BaseEventsService { } async getDelegatedBalance(ethAccount) { const { events } = await this.getSavedEvents(); - const delegatedAccs = events.filter( - (e) => e.event === "Delegated" && e.delegateTo === ethAccount - ).map((e) => e.account); - const undelegatedAccs = events.filter( - (e) => e.event === "Undelegated" && e.delegateFrom === ethAccount - ).map((e) => e.account); + const delegatedAccs = events.filter((e) => e.event === "Delegated" && e.delegateTo === ethAccount).map((e) => e.account); + const undelegatedAccs = events.filter((e) => e.event === "Undelegated" && e.delegateFrom === ethAccount).map((e) => e.account); const undel = [...undelegatedAccs]; const uniq = delegatedAccs.filter((acc) => { const indexUndelegated = undel.indexOf(acc); @@ -185488,17 +185200,14 @@ async function getTovarishNetworks(registryService, relayers) { await Promise.all( relayers.filter((r) => r.tovarishHost).map(async (relayer) => { try { - relayer.tovarishNetworks = await fetchData( - relayer.tovarishHost, - { - ...registryService.fetchDataOptions, - headers: { - "Content-Type": "application/json" - }, - timeout: 3e4, - maxRetry: registryService.fetchDataOptions?.torPort ? 2 : 0 - } - ); + relayer.tovarishNetworks = await fetchData(relayer.tovarishHost, { + ...registryService.fetchDataOptions, + headers: { + "Content-Type": "application/json" + }, + timeout: 3e4, + maxRetry: registryService.fetchDataOptions?.torPort ? 2 : 0 + }); } catch { relayer.tovarishNetworks = []; } @@ -185519,11 +185228,7 @@ class BaseRegistryService extends BaseEventsService { relayerEnsSubdomains; updateInterval; constructor(serviceConstructor) { - const { - RelayerRegistry: contract, - Aggregator, - relayerEnsSubdomains - } = serviceConstructor; + const { RelayerRegistry: contract, Aggregator, relayerEnsSubdomains } = serviceConstructor; super({ ...serviceConstructor, contract, @@ -185544,20 +185249,18 @@ class BaseRegistryService extends BaseEventsService { return "getAllRegisters"; } async formatEvents(events) { - return events.map( - ({ blockNumber, index: logIndex, transactionHash, args }) => { - const eventObjects = { - blockNumber, - logIndex, - transactionHash - }; - return { - ...eventObjects, - ensName: args.ensName, - relayerAddress: args.relayerAddress - }; - } - ); + return events.map(({ blockNumber, index: logIndex, transactionHash, args }) => { + const eventObjects = { + blockNumber, + logIndex, + transactionHash + }; + return { + ...eventObjects, + ensName: args.ensName, + relayerAddress: args.relayerAddress + }; + }); } /** * Get saved or cached relayers @@ -185598,50 +185301,38 @@ class BaseRegistryService extends BaseEventsService { } return false; }); - const relayerNameHashes = uniqueRegisters.map( - (r) => namehash_namehash(r.ensName) - ); + const relayerNameHashes = uniqueRegisters.map((r) => namehash_namehash(r.ensName)); const [relayersData, timestamp] = await Promise.all([ - this.Aggregator.relayersData.staticCall( - relayerNameHashes, - subdomains.concat("tovarish-relayer") - ), + this.Aggregator.relayersData.staticCall(relayerNameHashes, subdomains.concat("tovarish-relayer")), this.provider.getBlock(lastBlock).then((b) => Number(b?.timestamp)) ]); - const relayers = relayersData.map( - ({ owner, balance: stakeBalance, records, isRegistered }, index) => { - const { ensName, relayerAddress } = uniqueRegisters[index]; - let tovarishHost = void 0; - const hostnames = records.reduce( - (acc, record, recordIndex) => { - if (record) { - if (recordIndex === records.length - 1) { - tovarishHost = record; - return acc; - } - acc[Number( - Object.keys(this.relayerEnsSubdomains)[recordIndex] - )] = record; - } + const relayers = relayersData.map(({ owner, balance: stakeBalance, records, isRegistered }, index) => { + const { ensName, relayerAddress } = uniqueRegisters[index]; + let tovarishHost = void 0; + const hostnames = records.reduce((acc, record, recordIndex) => { + if (record) { + if (recordIndex === records.length - 1) { + tovarishHost = record; return acc; - }, - {} - ); - const hasMinBalance = stakeBalance >= MIN_STAKE_BALANCE; - const preCondition = Object.keys(hostnames).length && isRegistered && hasMinBalance; - if (preCondition) { - return { - ensName, - relayerAddress: owner, - registeredAddress: owner !== relayerAddress ? relayerAddress : void 0, - isRegistered, - stakeBalance: formatEther(stakeBalance), - hostnames, - tovarishHost - }; + } + acc[Number(Object.keys(this.relayerEnsSubdomains)[recordIndex])] = record; } + return acc; + }, {}); + const hasMinBalance = stakeBalance >= MIN_STAKE_BALANCE; + const preCondition = Object.keys(hostnames).length && isRegistered && hasMinBalance; + if (preCondition) { + return { + ensName, + relayerAddress: owner, + registeredAddress: owner !== relayerAddress ? relayerAddress : void 0, + isRegistered, + stakeBalance: formatEther(stakeBalance), + hostnames, + tovarishHost + }; } - ).filter((r) => r); + }).filter((r) => r); await getTovarishNetworks(this, relayers); const allRelayers = [...staticRelayers, ...relayers]; const tovarishRelayers = allRelayers.filter((r) => r.tovarishHost); @@ -185718,9 +185409,7 @@ async function downloadZip({ } } const { [zipName]: content } = await unzipAsync(data); - console.log( - `Downloaded ${url}${zipDigest ? ` ( Digest: ${zipDigest} )` : ""}` - ); + console.log(`Downloaded ${url}${zipDigest ? ` ( Digest: ${zipDigest} )` : ""}`); if (parseJson) { return JSON.parse(new TextDecoder().decode(content)); } @@ -185845,10 +185534,7 @@ class DBTornadoService extends BaseTornadoService { zipDigest: this.zipDigest }); } - async saveEvents({ - events, - lastBlock - }) { + async saveEvents({ events, lastBlock }) { await saveDBEvents({ idb: this.idb, instanceName: this.getInstanceName(), @@ -191259,9 +190945,7 @@ class Pedersen { } async unpackPoint(buffer) { await this.pedersenPromise; - return this.babyJub?.unpackPoint( - this.pedersenHash?.hash(buffer) - ); + return this.babyJub?.unpackPoint(this.pedersenHash?.hash(buffer)); } toStringBuffer(buffer) { return this.babyJub?.F.toString(buffer); @@ -191303,14 +190987,8 @@ function parseInvoice(invoiceString) { invoice: invoiceString }; } -async function createDeposit({ - nullifier, - secret -}) { - const preimage = new Uint8Array([ - ...dist_leInt2Buff(nullifier), - ...dist_leInt2Buff(secret) - ]); +async function createDeposit({ nullifier, secret }) { + const preimage = new Uint8Array([...dist_leInt2Buff(nullifier), ...dist_leInt2Buff(secret)]); const noteHex = toFixedHex(bytesToBN(preimage), 62); const commitment = BigInt(await buffPedersenHash(preimage)); const commitmentHex = toFixedHex(commitment); @@ -191377,13 +191055,7 @@ class Deposit { 2 ); } - static async createNote({ - currency, - amount, - netId, - nullifier, - secret - }) { + static async createNote({ currency, amount, netId, nullifier, secret }) { if (!nullifier) { nullifier = rBigInt(31); } @@ -191413,13 +191085,7 @@ class Deposit { if (!parsedNote) { throw new Error("The note has invalid format"); } - const { - currency, - amount, - netId, - note, - noteHex: parsedNoteHex - } = parsedNote; + const { currency, amount, netId, note, noteHex: parsedNoteHex } = parsedNote; const bytes = bnToBytes(parsedNoteHex); const nullifier = BigInt(leBuff2Int(bytes.slice(0, 31)).toString()); const secret = BigInt(leBuff2Int(bytes.slice(31, 62)).toString()); @@ -191476,22 +191142,11 @@ class Invoice { } } -function packEncryptedMessage({ - nonce, - ephemPublicKey, - ciphertext -}) { +function packEncryptedMessage({ nonce, ephemPublicKey, ciphertext }) { const nonceBuf = toFixedHex(dist_bytesToHex(base64ToBytes(nonce)), 24); - const ephemPublicKeyBuf = toFixedHex( - dist_bytesToHex(base64ToBytes(ephemPublicKey)), - 32 - ); + const ephemPublicKeyBuf = toFixedHex(dist_bytesToHex(base64ToBytes(ephemPublicKey)), 32); const ciphertextBuf = dist_bytesToHex(base64ToBytes(ciphertext)); - const messageBuff = dist_concatBytes( - dist_hexToBytes(nonceBuf), - dist_hexToBytes(ephemPublicKeyBuf), - dist_hexToBytes(ciphertextBuf) - ); + const messageBuff = dist_concatBytes(dist_hexToBytes(nonceBuf), dist_hexToBytes(ephemPublicKeyBuf), dist_hexToBytes(ciphertextBuf)); return dist_bytesToHex(messageBuff); } function unpackEncryptedMessage(encryptedMessage) { @@ -191570,9 +191225,7 @@ class NoteAccount { continue; } try { - const unpackedMessage = unpackEncryptedMessage( - event.encryptedAccount - ); + const unpackedMessage = unpackEncryptedMessage(event.encryptedAccount); let recoveryKey; if (signer.privateKey) { const wallet = signer; @@ -191594,10 +191247,7 @@ class NoteAccount { ) ); const provider = signer.provider; - recoveryKey = await provider.send("eth_decrypt", [ - unpackedBuffer, - signerAddress - ]); + recoveryKey = await provider.send("eth_decrypt", [unpackedBuffer, signerAddress]); } decryptedEvents.push( new NoteAccount({ @@ -191615,9 +191265,7 @@ class NoteAccount { const decryptedEvents = []; for (const event of events) { try { - const unpackedMessage = unpackEncryptedMessage( - event.encryptedNote - ); + const unpackedMessage = unpackEncryptedMessage(event.encryptedNote); const [address, noteHex] = (0,dist.decrypt)({ encryptedData: unpackedMessage, privateKey: this.recoveryKey @@ -191690,18 +191338,9 @@ class ENSUtils { async getContracts() { const { chainId } = await this.provider.getNetwork(); const { ensRegistry, ensPublicResolver, ensNameWrapper } = EnsContracts[Number(chainId)]; - this.ENSRegistry = ENSRegistry__factory.connect( - ensRegistry, - this.provider - ); - this.ENSResolver = ENSResolver__factory.connect( - ensPublicResolver, - this.provider - ); - this.ENSNameWrapper = ENSNameWrapper__factory.connect( - ensNameWrapper, - this.provider - ); + this.ENSRegistry = ENSRegistry__factory.connect(ensRegistry, this.provider); + this.ENSResolver = ENSResolver__factory.connect(ensPublicResolver, this.provider); + this.ENSNameWrapper = ENSNameWrapper__factory.connect(ensNameWrapper, this.provider); } async getOwner(name) { if (!this.ENSRegistry) { @@ -191715,9 +191354,7 @@ class ENSUtils { await this.getContracts(); } const owner = signer.address; - const nameWrapper = this.ENSNameWrapper.connect( - signer - ); + const nameWrapper = this.ENSNameWrapper.connect(signer); const { labelhash: labelhash2 } = makeLabelNodeAndParent(name); return nameWrapper.unwrapETH2LD(labelhash2, owner, owner); } @@ -191730,20 +191367,11 @@ class ENSUtils { const registry = this.ENSRegistry.connect(signer); const owner = signer.address; const { labelhash: labelhash2, parentNode } = makeLabelNodeAndParent(name); - return registry.setSubnodeRecord( - parentNode, - labelhash2, - owner, - resolver.target, - BigInt(0) - ); + return registry.setSubnodeRecord(parentNode, labelhash2, owner, resolver.target, BigInt(0)); } // https://github.com/ensdomains/ensjs/blob/main/packages/ensjs/src/functions/wallet/setTextRecord.ts async setText(signer, name, key, value) { - const resolver = ENSResolver__factory.connect( - (await this.getResolver(name))?.address, - signer - ); + const resolver = ENSResolver__factory.connect((await this.getResolver(name))?.address, signer); return resolver.setText(namehash(name), key, value); } getResolver(name) { @@ -191793,12 +191421,7 @@ class TornadoFeeOracle { })(), (async () => { try { - return BigInt( - await this.provider.send( - "eth_maxPriorityFeePerGas", - [] - ) - ); + return BigInt(await this.provider.send("eth_maxPriorityFeePerGas", [])); } catch { return BigInt(0); } @@ -191825,9 +191448,7 @@ class TornadoFeeOracle { to: DUMMY_ADDRESS }; } - return this.ovmGasPriceOracle.getL1Fee.staticCall( - Transaction.from(tx).unsignedSerialized - ); + return this.ovmGasPriceOracle.getL1Fee.staticCall(Transaction.from(tx).unsignedSerialized); } /** * We don't need to distinguish default refunds by tokens since most users interact with other defi protocols after withdrawal @@ -191841,11 +191462,7 @@ class TornadoFeeOracle { * Calculates token amount for required ethRefund purchases required to calculate fees */ calculateTokenAmount(ethRefund, tokenPriceInEth, tokenDecimals) { - return convertETHToTokenAmount( - ethRefund, - tokenPriceInEth, - tokenDecimals - ); + return convertETHToTokenAmount(ethRefund, tokenPriceInEth, tokenDecimals); } /** * Warning: For tokens you need to check if the fees are above denomination @@ -191869,11 +191486,7 @@ class TornadoFeeOracle { return (gasCosts + relayerFee) * BigInt(premiumPercent ? 100 + premiumPercent : 100) / BigInt(100); } const feeInEth = gasCosts + BigInt(ethRefund); - return (convertETHToTokenAmount( - feeInEth, - tokenPriceInWei, - tokenDecimals - ) + relayerFee) * BigInt(premiumPercent ? 100 + premiumPercent : 100) / BigInt(100); + return (convertETHToTokenAmount(feeInEth, tokenPriceInWei, tokenDecimals) + relayerFee) * BigInt(premiumPercent ? 100 + premiumPercent : 100) / BigInt(100); } } @@ -191943,19 +191556,17 @@ class IndexedDB { Object.values(db.objectStoreNames).forEach((value) => { db.deleteObjectStore(value); }); - [{ name: "keyval" }, ...stores || []].forEach( - ({ name, keyPath, indexes }) => { - const store = db.createObjectStore(name, { - keyPath, - autoIncrement: true + [{ name: "keyval" }, ...stores || []].forEach(({ name, keyPath, indexes }) => { + const store = db.createObjectStore(name, { + keyPath, + autoIncrement: true + }); + if (Array.isArray(indexes)) { + indexes.forEach(({ name: name2, unique = false }) => { + store.createIndex(name2, name2, { unique }); }); - if (Array.isArray(indexes)) { - indexes.forEach(({ name: name2, unique = false }) => { - store.createIndex(name2, name2, { unique }); - }); - } } - ); + }); } }; this.dbName = dbName; @@ -192016,20 +191627,12 @@ class IndexedDB { return []; } try { - return await this.db.getAllFromIndex( - storeName, - indexName, - key, - count - ); + return await this.db.getAllFromIndex(storeName, indexName, key, count); } catch (err) { throw new Error(`Method getAllFromIndex has error: ${err.message}`); } } - async getItem({ - storeName, - key - }) { + async getItem({ storeName, key }) { await this.initDB(); if (!this.db) { return; @@ -192041,11 +191644,7 @@ class IndexedDB { throw new Error(`Method getItem has error: ${err.message}`); } } - async addItem({ - storeName, - data, - key = "" - }) { + async addItem({ storeName, data, key = "" }) { await this.initDB(); if (!this.db) { return; @@ -192060,11 +191659,7 @@ class IndexedDB { throw new Error(`Method addItem has error: ${err.message}`); } } - async putItem({ - storeName, - data, - key - }) { + async putItem({ storeName, data, key }) { await this.initDB(); if (!this.db) { return; @@ -192112,10 +191707,7 @@ class IndexedDB { delValue(key) { return this.deleteItem({ storeName: "keyval", key }); } - async clearStore({ - storeName, - mode = "readwrite" - }) { + async clearStore({ storeName, mode = "readwrite" }) { await this.initDB(); if (!this.db) { return; @@ -192141,9 +191733,7 @@ class IndexedDB { await tx.objectStore(storeName).add(data); await tx.done; } catch (err) { - throw new Error( - `Method createTransactions has error: ${err.message}` - ); + throw new Error(`Method createTransactions has error: ${err.message}`); } } async createMultipleTransactions({ @@ -192164,9 +191754,7 @@ class IndexedDB { } } } catch (err) { - throw new Error( - `Method createMultipleTransactions has error: ${err.message}` - ); + throw new Error(`Method createMultipleTransactions has error: ${err.message}`); } } } @@ -192311,9 +191899,7 @@ class Mimc { } async initMimc() { this.sponge = await mimcsponge_buildMimcSponge(); - this.hash = (left, right) => this.sponge?.F.toString( - this.sponge?.multiHash([BigInt(left), BigInt(right)]) - ); + this.hash = (left, right) => this.sponge?.F.toString(this.sponge?.multiHash([BigInt(left), BigInt(right)])); } async getHash() { await this.mimcPromise; @@ -192362,63 +191948,42 @@ class MerkleTreeService { console.log("Using merkleWorker\n"); try { if (isNode) { - const merkleWorkerPromise = new Promise( - (resolve, reject) => { - const worker = new external_worker_threads_.Worker( - this.merkleWorkerPath, - { - workerData: { - merkleTreeHeight: this.merkleTreeHeight, - elements: events, - zeroElement: this.emptyElement - } - } - ); - worker.on("message", resolve); - worker.on("error", reject); - worker.on("exit", (code) => { - if (code !== 0) { - reject( - new Error( - `Worker stopped with exit code ${code}` - ) - ); - } - }); - } - ); - return fixed_merkle_tree_lib.MerkleTree.deserialize( - JSON.parse(await merkleWorkerPromise), - hashFunction - ); - } else { - const merkleWorkerPromise = new Promise( - (resolve, reject) => { - const worker = new Worker( - this.merkleWorkerPath - ); - worker.onmessage = (e) => { - resolve(e.data); - }; - worker.onerror = (e) => { - reject(e); - }; - worker.postMessage({ + const merkleWorkerPromise = new Promise((resolve, reject) => { + const worker = new external_worker_threads_.Worker(this.merkleWorkerPath, { + workerData: { merkleTreeHeight: this.merkleTreeHeight, elements: events, zeroElement: this.emptyElement - }); - } - ); - return fixed_merkle_tree_lib.MerkleTree.deserialize( - JSON.parse(await merkleWorkerPromise), - hashFunction - ); + } + }); + worker.on("message", resolve); + worker.on("error", reject); + worker.on("exit", (code) => { + if (code !== 0) { + reject(new Error(`Worker stopped with exit code ${code}`)); + } + }); + }); + return fixed_merkle_tree_lib.MerkleTree.deserialize(JSON.parse(await merkleWorkerPromise), hashFunction); + } else { + const merkleWorkerPromise = new Promise((resolve, reject) => { + const worker = new Worker(this.merkleWorkerPath); + worker.onmessage = (e) => { + resolve(e.data); + }; + worker.onerror = (e) => { + reject(e); + }; + worker.postMessage({ + merkleTreeHeight: this.merkleTreeHeight, + elements: events, + zeroElement: this.emptyElement + }); + }); + return fixed_merkle_tree_lib.MerkleTree.deserialize(JSON.parse(await merkleWorkerPromise), hashFunction); } } catch (err) { - console.log( - "merkleWorker failed, falling back to synchronous merkle tree" - ); + console.log("merkleWorker failed, falling back to synchronous merkle tree"); console.log(err); } } @@ -192427,74 +191992,50 @@ class MerkleTreeService { hashFunction }); } - async createPartialTree({ - edge, - elements - }) { + async createPartialTree({ edge, elements }) { const { hash: hashFunction } = await mimc.getHash(); if (this.merkleWorkerPath) { console.log("Using merkleWorker\n"); try { if (isNode) { - const merkleWorkerPromise = new Promise( - (resolve, reject) => { - const worker = new external_worker_threads_.Worker( - this.merkleWorkerPath, - { - workerData: { - merkleTreeHeight: this.merkleTreeHeight, - edge, - elements, - zeroElement: this.emptyElement - } - } - ); - worker.on("message", resolve); - worker.on("error", reject); - worker.on("exit", (code) => { - if (code !== 0) { - reject( - new Error( - `Worker stopped with exit code ${code}` - ) - ); - } - }); - } - ); - return fixed_merkle_tree_lib.PartialMerkleTree.deserialize( - JSON.parse(await merkleWorkerPromise), - hashFunction - ); - } else { - const merkleWorkerPromise = new Promise( - (resolve, reject) => { - const worker = new Worker( - this.merkleWorkerPath - ); - worker.onmessage = (e) => { - resolve(e.data); - }; - worker.onerror = (e) => { - reject(e); - }; - worker.postMessage({ + const merkleWorkerPromise = new Promise((resolve, reject) => { + const worker = new external_worker_threads_.Worker(this.merkleWorkerPath, { + workerData: { merkleTreeHeight: this.merkleTreeHeight, edge, elements, zeroElement: this.emptyElement - }); - } - ); - return fixed_merkle_tree_lib.PartialMerkleTree.deserialize( - JSON.parse(await merkleWorkerPromise), - hashFunction - ); + } + }); + worker.on("message", resolve); + worker.on("error", reject); + worker.on("exit", (code) => { + if (code !== 0) { + reject(new Error(`Worker stopped with exit code ${code}`)); + } + }); + }); + return fixed_merkle_tree_lib.PartialMerkleTree.deserialize(JSON.parse(await merkleWorkerPromise), hashFunction); + } else { + const merkleWorkerPromise = new Promise((resolve, reject) => { + const worker = new Worker(this.merkleWorkerPath); + worker.onmessage = (e) => { + resolve(e.data); + }; + worker.onerror = (e) => { + reject(e); + }; + worker.postMessage({ + merkleTreeHeight: this.merkleTreeHeight, + edge, + elements, + zeroElement: this.emptyElement + }); + }); + return fixed_merkle_tree_lib.PartialMerkleTree.deserialize(JSON.parse(await merkleWorkerPromise), hashFunction); } } catch (err) { - console.log( - "merkleWorker failed, falling back to synchronous merkle tree" - ); + console.log("merkleWorker failed, falling back to synchronous merkle tree"); console.log(err); } } @@ -192510,12 +192051,8 @@ Creating deposit tree for ${this.netId} ${this.amount} ${this.currency.toUpperCa ` ); const timeStart = Date.now(); - const tree = await this.createTree( - events.map(({ commitment }) => commitment) - ); - const isKnownRoot = await this.Tornado.isKnownRoot( - toFixedHex(BigInt(tree.root)) - ); + const tree = await this.createTree(events.map(({ commitment }) => commitment)); + const isKnownRoot = await this.Tornado.isKnownRoot(toFixedHex(BigInt(tree.root))); if (!isKnownRoot) { const errMsg = `Deposit Event ${this.netId} ${this.amount} ${this.currency} is invalid`; throw new Error(errMsg); @@ -192599,10 +192136,7 @@ async function getPermitCommitmentsSignature({ nonce }) { const value = BigInt(commitments.length) * denomination; - const commitmentsHash = solidityPackedKeccak256( - ["bytes32[]"], - [commitments] - ); + const commitmentsHash = solidityPackedKeccak256(["bytes32[]"], [commitments]); return await getPermitSignature({ Token, signer, @@ -192667,9 +192201,7 @@ async function getPermit2Signature({ values.witness = witness.witness; } const hash = new TypedDataEncoder(types).hash(values); - const signature = Signature.from( - await sigSigner.signTypedData(domain, types, values) - ); + const signature = Signature.from(await sigSigner.signTypedData(domain, types, values)); return { domain, types, @@ -192688,10 +192220,7 @@ async function getPermit2CommitmentsSignature({ deadline }) { const value = BigInt(commitments.length) * denomination; - const commitmentsHash = solidityPackedKeccak256( - ["bytes32[]"], - [commitments] - ); + const commitmentsHash = solidityPackedKeccak256(["bytes32[]"], [commitments]); return await getPermit2Signature({ Token, signer, @@ -192735,10 +192264,7 @@ class TokenPriceOracle { })); } buildStable(stablecoinAddress) { - const stablecoin = dist_ERC20_factory.connect( - stablecoinAddress, - this.provider - ); + const stablecoin = dist_ERC20_factory.connect(stablecoinAddress, this.provider); return [ { contract: stablecoin, @@ -192769,14 +192295,9 @@ class TokenPriceOracle { } async fetchPrices(tokens) { if (!this.oracle) { - return new Promise( - (resolve) => resolve(tokens.map(() => this.fallbackPrice)) - ); + return new Promise((resolve) => resolve(tokens.map(() => this.fallbackPrice))); } - const prices = await multicall( - this.multicall, - this.buildCalls(tokens) - ); + const prices = await multicall(this.multicall, this.buildCalls(tokens)); return prices.map((price, index) => { if (!price) { price = this.fallbackPrice; @@ -192786,14 +192307,9 @@ class TokenPriceOracle { } async fetchEthUSD(stablecoinAddress) { if (!this.oracle) { - return new Promise( - (resolve) => resolve(10 ** 18 / Number(this.fallbackPrice)) - ); + return new Promise((resolve) => resolve(10 ** 18 / Number(this.fallbackPrice))); } - const [decimals, price] = await multicall( - this.multicall, - this.buildStable(stablecoinAddress) - ); + const [decimals, price] = await multicall(this.multicall, this.buildStable(stablecoinAddress)); const ethPrice = (price || this.fallbackPrice) * BigInt(10n ** decimals) / BigInt(10 ** 18); return 1 / Number(formatEther(ethPrice)); } @@ -192837,10 +192353,7 @@ async function getTokenBalances({ ...tokenCalls.length ? tokenCalls : [] ]); const ethResults = multicallResults[0]; - const tokenResults = multicallResults.slice(1).length ? chunk( - multicallResults.slice(1), - tokenCalls.length / tokenAddresses.length - ) : []; + const tokenResults = multicallResults.slice(1).length ? chunk(multicallResults.slice(1), tokenCalls.length / tokenAddresses.length) : []; const tokenBalances = tokenResults.map((tokenResult, index) => { const [tokenBalance, tokenName, tokenSymbol, tokenDecimals] = tokenResult; const tokenAddress = tokenAddresses[index]; @@ -192937,9 +192450,7 @@ class TovarishClient extends RelayerClient { throw new Error("This relayer serves a different network"); } if (relayerAddress && status.netId === NetId.MAINNET && status.rewardAccount !== relayerAddress) { - throw new Error( - "The Relayer reward address must match registered address" - ); + throw new Error("The Relayer reward address must match registered address"); } if (!status.version.includes("tovarish")) { throw new Error("Not a tovarish relayer!"); @@ -192990,18 +192501,18 @@ class TovarishClient extends RelayerClient { } async getValidRelayers(relayers) { const invalidRelayers = []; - const validRelayers = (await Promise.all( - relayers.map((relayer) => this.filterRelayer(relayer)) - )).filter((r) => { - if (!r) { - return false; + const validRelayers = (await Promise.all(relayers.map((relayer) => this.filterRelayer(relayer)))).filter( + (r) => { + if (!r) { + return false; + } + if (r.hasError) { + invalidRelayers.push(r); + return false; + } + return true; } - if (r.hasError) { - invalidRelayers.push(r); - return false; - } - return true; - }); + ); return { validRelayers, invalidRelayers @@ -193026,9 +192537,7 @@ class TovarishClient extends RelayerClient { ensName, relayerAddress, rewardAccount: address_getAddress(status.rewardAccount), - instances: getSupportedInstances( - status.instances - ), + instances: getSupportedInstances(status.instances), stakeBalance: relayer.stakeBalance, gasPrice: status.gasPrices?.fast, ethPrices: status.ethPrices, @@ -193109,9 +192618,7 @@ class TovarishClient extends RelayerClient { events.push(...fetchedEvents); break; } - fetchedEvents = fetchedEvents.filter( - (e) => e.blockNumber !== lastEvent.blockNumber - ); + fetchedEvents = fetchedEvents.filter((e) => e.blockNumber !== lastEvent.blockNumber); fromBlock = Number(lastEvent.blockNumber); events.push(...fetchedEvents); } @@ -193154,12 +192661,7 @@ async function calculateSnarkProof(input, circuit, provingKey) { }; console.log("Start generating SNARK proof", snarkInput); console.time("SNARK proof time"); - const proofData = await src_utils.genWitnessAndProve( - await dist_groth16, - snarkInput, - circuit, - provingKey - ); + const proofData = await src_utils.genWitnessAndProve(await dist_groth16, snarkInput, circuit, provingKey); const proof = src_utils.toSolidityInput(proofData).proof; console.timeEnd("SNARK proof time"); const args = [ @@ -193433,12 +192935,18 @@ class NodeTornadoService extends BaseTornadoService { lastBlock, hasNewEvents }) { - const tree = await super.validateEvents({ events, lastBlock, hasNewEvents }); + const tree = await super.validateEvents({ + events, + lastBlock, + hasNewEvents + }); if (tree && this.currency === this.nativeCurrency && this.treeCache) { const merkleTree = tree; await this.treeCache.createTree(events, merkleTree); - console.log(`${this.getInstanceName()}: Updated tree cache with root ${toFixedHex(BigInt(merkleTree.root))} -`); + console.log( + `${this.getInstanceName()}: Updated tree cache with root ${toFixedHex(BigInt(merkleTree.root))} +` + ); } return tree; } @@ -193448,7 +192956,12 @@ class NodeTornadoService extends BaseTornadoService { [{ colSpan: 2, content: `${this.getType()}s`, hAlign: "center" }], ["Instance", `${this.netId} chain ${this.amount} ${this.currency.toUpperCase()}`], ["Anonymity set", `${events.length} equal user ${this.getType().toLowerCase()}s`], - [{ colSpan: 2, content: `Latest ${this.getType().toLowerCase()}s` }], + [ + { + colSpan: 2, + content: `Latest ${this.getType().toLowerCase()}s` + } + ], ...events.slice(events.length - 10).reverse().map(({ timestamp }, index) => { const eventIndex = events.length - index; const eventTime = moment_default().unix(timestamp).fromNow(); @@ -193925,7 +193438,10 @@ async function promptConfirmation(nonInteractive) { if (nonInteractive) { return; } - const prompt = (0,external_readline_namespaceObject.createInterface)({ input: (external_process_default()).stdin, output: (external_process_default()).stdout }); + const prompt = (0,external_readline_namespaceObject.createInterface)({ + input: (external_process_default()).stdin, + output: (external_process_default()).stdout + }); const query = "Confirm? [Y/n]\n"; const confirmation = await new Promise((resolve) => prompt.question(query, resolve)); if (!confirmation || confirmation.toUpperCase() !== "Y") { @@ -194233,7 +193749,11 @@ function tornadoProgram() { } = currencyConfig; const isEth = nativeCurrency === currency; const denomination = parseUnits(amount, decimals); - const deposit = await Deposit.createNote({ currency, amount, netId }); + const deposit = await Deposit.createNote({ + currency, + amount, + netId + }); const { noteHex, note, commitmentHex } = deposit; const ERC20Interface = dist_ERC20_factory.createInterface(); const TornadoRouterInterface = TornadoRouter__factory.createInterface(); @@ -194242,13 +193762,19 @@ function tornadoProgram() { await (0,promises_namespaceObject.writeFile)(`./backup-tornado-${currency}-${amount}-${netId}-${noteHex.slice(0, 10)}.txt`, note, { encoding: "utf8" }); - const depositData = TornadoRouterInterface.encodeFunctionData("deposit", [instanceAddress, commitmentHex, "0x"]); + const depositData = TornadoRouterInterface.encodeFunctionData("deposit", [ + instanceAddress, + commitmentHex, + "0x" + ]); if (!isEth) { const approveData = ERC20Interface.encodeFunctionData("approve", [routerContract, numbers_MaxUint256]); console.log(`Approve Data: ${JSON.stringify({ to: tokenAddress, data: approveData }, null, 2)}] `); - console.log(`Transaction Data: ${JSON.stringify({ to: routerContract, data: depositData }, null, 2)} -`); + console.log( + `Transaction Data: ${JSON.stringify({ to: routerContract, data: depositData }, null, 2)} +` + ); } else { console.log( `Transaction Data: ${JSON.stringify({ to: routerContract, value: denomination.toString(), data: depositData }, null, 2)}` @@ -194323,7 +193849,10 @@ function tornadoProgram() { const resp = await programSendTransaction({ signer, options, - populatedTransaction: await Token.approve.populateTransaction(routerContract, numbers_MaxUint256) + populatedTransaction: await Token.approve.populateTransaction( + routerContract, + numbers_MaxUint256 + ) }); if (signer instanceof VoidSigner || options.localRpc) { console.log( @@ -194334,7 +193863,11 @@ function tornadoProgram() { await resp?.wait(); } } - const deposit = await Deposit.createNote({ currency, amount, netId }); + const deposit = await Deposit.createNote({ + currency, + amount, + netId + }); const { note, noteHex, commitmentHex } = deposit; const encryptedNote = noteAccount ? noteAccount.encryptNote({ address: instanceAddress, @@ -194430,7 +193963,10 @@ function tornadoProgram() { await programSendTransaction({ signer, options, - populatedTransaction: await Token.approve.populateTransaction(routerContract, numbers_MaxUint256) + populatedTransaction: await Token.approve.populateTransaction( + routerContract, + numbers_MaxUint256 + ) }); if (signer instanceof VoidSigner) { console.log( @@ -194442,9 +193978,14 @@ function tornadoProgram() { await programSendTransaction({ signer, options, - populatedTransaction: await TornadoProxy.deposit.populateTransaction(instanceAddress, commitmentHex, "0x", { - value: isEth ? denomination : BigInt(0) - }) + populatedTransaction: await TornadoProxy.deposit.populateTransaction( + instanceAddress, + commitmentHex, + "0x", + { + value: isEth ? denomination : BigInt(0) + } + ) }); external_process_default().exit(0); }); @@ -194476,7 +194017,9 @@ function tornadoProgram() { } = currencyConfig; const isEth = nativeCurrency === currency; const denomination = parseUnits(amount, decimals); - const firstAmount = Object.keys(currencyConfig.instanceAddress).sort((a, b) => Number(a) - Number(b))[0]; + const firstAmount = Object.keys(currencyConfig.instanceAddress).sort( + (a, b) => Number(a) - Number(b) + )[0]; const isFirstAmount = Number(amount) === Number(firstAmount); const provider = await getProgramProvider(rpc, { netId, @@ -194511,7 +194054,10 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} throw new Error("Wallet withdrawal is configured however could not find any wallets"); } const Tornado = Tornado__factory.connect(instanceAddress, provider); - const TornadoProxy = TornadoRouter__factory.connect(routerContract, !walletWithdrawal ? provider : signer); + const TornadoProxy = TornadoRouter__factory.connect( + routerContract, + !walletWithdrawal ? provider : signer + ); const Multicall = Multicall__factory.connect(multicallContract, provider); const tornadoFeeOracle = new TornadoFeeOracle( provider, @@ -194552,7 +194098,9 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} }); const { events: depositEvents, validateResult: tree } = await depositsService.updateEvents(); const withdrawalEvents = (await withdrawalsService.updateEvents()).events; - const depositEvent = depositEvents.find(({ commitment }) => commitment === commitmentHex); + const depositEvent = depositEvents.find( + ({ commitment }) => commitment === commitmentHex + ); const withdrawalEvent = withdrawalEvents.find(({ nullifierHash }) => nullifierHash === nullifierHex); if (!depositEvent) { throw new Error("Deposit not found"); @@ -194669,7 +194217,13 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} const txFeeString = !isEth ? `( ${Number(formatUnits(txFeeInToken, decimals)).toFixed(5)} ${currency.toUpperCase()} worth ) ( ${(Number(formatUnits(txFeeInToken, decimals)) / Number(amount) * 100).toFixed(5)}% )` : `( ${(Number(formatUnits(txFee, decimals)) / Number(amount) * 100).toFixed(5)}% )`; const withdrawTable = new (cli_table3_default())(); withdrawTable.push( - [{ colSpan: 2, content: "Withdrawal Info", hAlign: "center" }], + [ + { + colSpan: 2, + content: "Withdrawal Info", + hAlign: "center" + } + ], [ "Deposit Date", `${depositDate.toLocaleDateString()} ${depositDate.toLocaleTimeString()} (${moment_default().unix(depositEvent.timestamp).fromNow()})` @@ -194701,7 +194255,10 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} "Amount to receive", `${Number(formatUnits(denomination - fee, decimals)).toFixed(5)} ${currency.toUpperCase()}` ], - [`${nativeCurrency.toUpperCase()} purchase`, `${formatEther(refund)} ${nativeCurrency.toUpperCase()}`], + [ + `${nativeCurrency.toUpperCase()} purchase`, + `${formatEther(refund)} ${nativeCurrency.toUpperCase()}` + ], ["To", recipient], ["Nullifier", nullifierHex] ); @@ -194726,7 +194283,11 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} await programSendTransaction({ signer, options, - populatedTransaction: await TornadoProxy.withdraw.populateTransaction(instanceAddress, proof, ...args) + populatedTransaction: await TornadoProxy.withdraw.populateTransaction( + instanceAddress, + proof, + ...args + ) }); } external_process_default().exit(0); @@ -194753,7 +194314,11 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} netId, ...fetchDataOptions2 }); - const tovarishClient = !disableTovarish ? (await getTovarishRelayer({ options, fetchDataOptions: fetchDataOptions2, netId })).relayerClient : void 0; + const tovarishClient = !disableTovarish ? (await getTovarishRelayer({ + options, + fetchDataOptions: fetchDataOptions2, + netId + })).relayerClient : void 0; if (tovarishClient?.selectedRelayer) { console.log(` Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} @@ -194853,7 +194418,11 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} netId, ...fetchDataOptions2 }); - const tovarishClient = !disableTovarish ? (await getTovarishRelayer({ options, fetchDataOptions: fetchDataOptions2, netId })).relayerClient : void 0; + const tovarishClient = !disableTovarish ? (await getTovarishRelayer({ + options, + fetchDataOptions: fetchDataOptions2, + netId + })).relayerClient : void 0; if (tovarishClient?.selectedRelayer) { console.log(` Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} @@ -194989,7 +194558,16 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} "serviceFee" ].map((content) => ({ content: lib_default().red.bold(content) })), ...validRelayers.map( - ({ netId, url, ensName, stakeBalance, relayerAddress, rewardAccount, currentQueue, tornadoServiceFee }) => { + ({ + netId, + url, + ensName, + stakeBalance, + relayerAddress, + rewardAccount, + currentQueue, + tornadoServiceFee + }) => { return [ netId, url, @@ -195005,8 +194583,16 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} ); const invalidRelayersTable = new (cli_table3_default())(); invalidRelayersTable.push( - [{ colSpan: 3, content: `Invalid ${relayerName}`, hAlign: "center" }], - ["hostname", "relayerAddress", "errorMessage"].map((content) => ({ content: lib_default().red.bold(content) })), + [ + { + colSpan: 3, + content: `Invalid ${relayerName}`, + hAlign: "center" + } + ], + ["hostname", "relayerAddress", "errorMessage"].map((content) => ({ + content: lib_default().red.bold(content) + })), ...invalidRelayers.map(({ hostname, relayerAddress, errorMessage }) => { return [hostname, relayerAddress, errorMessage ? substring(errorMessage, 40) : ""]; }) @@ -195038,7 +194624,11 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} "No wallet found, make your you have supplied a valid mnemonic or private key before using this command" ); } - const tovarishClient = !disableTovarish ? (await getTovarishRelayer({ options, fetchDataOptions: fetchDataOptions2, netId })).relayerClient : void 0; + const tovarishClient = !disableTovarish ? (await getTovarishRelayer({ + options, + fetchDataOptions: fetchDataOptions2, + netId + })).relayerClient : void 0; if (tovarishClient?.selectedRelayer) { console.log(` Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} @@ -195061,9 +194651,23 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} const accountsTable = new (cli_table3_default())(); if (existingAccounts.length) { accountsTable.push( - [{ colSpan: 2, content: `Note Accounts (${netId})`, hAlign: "center" }], - [{ colSpan: 2, content: `Backed up by: ${signer.address}`, hAlign: "center" }], - ["blockNumber", "noteAccount"].map((content) => ({ content: lib_default().red.bold(content) })), + [ + { + colSpan: 2, + content: `Note Accounts (${netId})`, + hAlign: "center" + } + ], + [ + { + colSpan: 2, + content: `Backed up by: ${signer.address}`, + hAlign: "center" + } + ], + ["blockNumber", "noteAccount"].map((content) => ({ + content: lib_default().red.bold(content) + })), ...existingAccounts.map(({ blockNumber, recoveryKey }) => { return [blockNumber, recoveryKey]; }) @@ -195072,10 +194676,24 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} } else { const newAccount = new NoteAccount({}); accountsTable.push( - [{ colSpan: 1, content: `New Note Account (${netId})`, hAlign: "center" }], - ["noteAccount"].map((content) => ({ content: lib_default().red.bold(content) })), + [ + { + colSpan: 1, + content: `New Note Account (${netId})`, + hAlign: "center" + } + ], + ["noteAccount"].map((content) => ({ + content: lib_default().red.bold(content) + })), [newAccount.recoveryKey], - [{ colSpan: 1, content: `Would be backed up by: ${signer.address}`, hAlign: "center" }] + [ + { + colSpan: 1, + content: `Would be backed up by: ${signer.address}`, + hAlign: "center" + } + ] ); const fileName = `backup-note-account-key-0x${newAccount.recoveryKey.slice(0, 8)}.txt`; console.log("\n" + accountsTable.toString() + "\n"); @@ -195115,7 +194733,11 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} netId, ...fetchDataOptions2 }); - const tovarishClient = !disableTovarish ? (await getTovarishRelayer({ options, fetchDataOptions: fetchDataOptions2, netId })).relayerClient : void 0; + const tovarishClient = !disableTovarish ? (await getTovarishRelayer({ + options, + fetchDataOptions: fetchDataOptions2, + netId + })).relayerClient : void 0; if (tovarishClient?.selectedRelayer) { console.log(` Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} @@ -195143,9 +194765,6 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} userDirectory: SAVED_DIR }); const accounts = []; - if (accountKey) { - accounts.push(new NoteAccount({ recoveryKey: accountKey })); - } const signer = getProgramSigner({ options, provider @@ -195154,6 +194773,9 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} const echoEvents = (await echoService.updateEvents()).events; accounts.push(...await NoteAccount.decryptSignerNoteAccounts(signer, echoEvents)); } + if (accountKey && !accounts.find(({ recoveryKey }) => recoveryKey === accountKey)) { + accounts.push(new NoteAccount({ recoveryKey: accountKey })); + } if (!accounts.length) { throw new Error( "No encryption key find! Please supply encryption key from either UI or create one with createAccount command" @@ -195161,9 +194783,23 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} } const accountsTable = new (cli_table3_default())(); accountsTable.push( - [{ colSpan: 2, content: `Note Accounts (${netId})`, hAlign: "center" }], - [{ colSpan: 2, content: `Backed up by: ${signer?.address}`, hAlign: "center" }], - ["blockNumber", "noteAccount"].map((content) => ({ content: lib_default().red.bold(content) })), + [ + { + colSpan: 2, + content: `Note Accounts (${netId})`, + hAlign: "center" + } + ], + [ + { + colSpan: 2, + content: `Backed up by: ${signer?.address}`, + hAlign: "center" + } + ], + ["blockNumber", "noteAccount"].map((content) => ({ + content: lib_default().red.bold(content) + })), ...accounts.map(({ blockNumber, recoveryKey }) => { return [blockNumber, recoveryKey]; }) @@ -195171,13 +194807,30 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} const encryptedNoteEvents = (await encryptedNotesService.updateEvents()).events; const decryptedNotes = accounts.map((noteAccount) => noteAccount.decryptNotes(encryptedNoteEvents)).flat().map(({ blockNumber, address, noteHex }) => { const { amount, currency } = getInstanceByAddress(config, address) || {}; - return [blockNumber, `tornado-${currency}-${amount}-${netId}-${noteHex}`]; + if (amount) { + return [blockNumber, `tornado-${currency}-${amount}-${netId}-${noteHex}`]; + } + return [blockNumber, noteHex]; }); const notesTable = new (cli_table3_default())(); notesTable.push( - [{ colSpan: 2, content: `Note Accounts (${netId})`, hAlign: "center" }], - [{ colSpan: 2, content: `Account key: ${accountKey}`, hAlign: "center" }], - ["blockNumber", "note"].map((content) => ({ content: lib_default().red.bold(content) })), + [ + { + colSpan: 2, + content: `Note Accounts (${netId})`, + hAlign: "center" + } + ], + [ + { + colSpan: 2, + content: `Account key: ${accountKey}`, + hAlign: "center" + } + ], + ["blockNumber", "note"].map((content) => ({ + content: lib_default().red.bold(content) + })), ...decryptedNotes ); console.log(accountsTable.toString() + "\n"); @@ -195306,9 +194959,17 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} userAddress, tokenAddresses: [...tokenAddress ? [tokenAddress] : tokenAddresses] }); - const balanceTable = new (cli_table3_default())({ head: ["Token", "Contract Address", "Balance"] }); + const balanceTable = new (cli_table3_default())({ + head: ["Token", "Contract Address", "Balance"] + }); balanceTable.push( - [{ colSpan: 3, content: `User: ${userAddress}`, hAlign: "center" }], + [ + { + colSpan: 3, + content: `User: ${userAddress}`, + hAlign: "center" + } + ], ...tokenBalances2.map(({ address, name, symbol, decimals, balance }) => { return [`${name} (${symbol})`, address, `${formatUnits(balance, decimals)} ${symbol}`]; }) @@ -195342,7 +195003,9 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} const { rpc } = options; const netId = Number(Transaction.from(signedTx).chainId); if (!netId) { - throw new Error("NetId for the transaction is invalid, this command only supports EIP-155 transactions"); + throw new Error( + "NetId for the transaction is invalid, this command only supports EIP-155 transactions" + ); } const provider = await getProgramProvider(rpc, { netId, @@ -195369,7 +195032,11 @@ Broadcastd tx: ${hash} reverseRecordsContract, constants: { GOVERNANCE_BLOCK } } = config; - const tovarishClient = !disableTovarish ? (await getTovarishRelayer({ options, fetchDataOptions: fetchDataOptions2, netId })).relayerClient : void 0; + const tovarishClient = !disableTovarish ? (await getTovarishRelayer({ + options, + fetchDataOptions: fetchDataOptions2, + netId + })).relayerClient : void 0; if (tovarishClient?.selectedRelayer) { console.log(` Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} @@ -195393,10 +195060,37 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} const recentProposals = proposals.reverse().slice(0, 20); const proposalTable2 = new (cli_table3_default())(); proposalTable2.push( - [{ colSpan: 9, content: "Last 20 Proposals", hAlign: "center" }], - ["ID", "Title", "Proposer", "Start Time", "End Time", "Quorum", "For Votes", "Against Votes", "State"], + [ + { + colSpan: 9, + content: "Last 20 Proposals", + hAlign: "center" + } + ], + [ + "ID", + "Title", + "Proposer", + "Start Time", + "End Time", + "Quorum", + "For Votes", + "Against Votes", + "State" + ], ...recentProposals.map( - ({ id, title: title2, proposer: proposer2, proposerName: proposerName2, startTime: startTime2, endTime: endTime2, quorum: quorum2, forVotes: forVotes2, againstVotes: againstVotes2, state: state2 }) => { + ({ + id, + title: title2, + proposer: proposer2, + proposerName: proposerName2, + startTime: startTime2, + endTime: endTime2, + quorum: quorum2, + forVotes: forVotes2, + againstVotes: againstVotes2, + state: state2 + }) => { return [ id, title2, @@ -195434,7 +195128,13 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} const allVotes = forVotes + againstVotes; const proposalTable = new (cli_table3_default())(); proposalTable.push( - [{ colSpan: 2, content: `Proposal ${proposalId}`, hAlign: "center" }], + [ + { + colSpan: 2, + content: `Proposal ${proposalId}`, + hAlign: "center" + } + ], ["Title", title], ["Description", description2], ["Proposer", proposerName || proposer], @@ -195489,7 +195189,11 @@ Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} reverseRecordsContract, constants: { GOVERNANCE_BLOCK } } = config; - const tovarishClient = !disableTovarish ? (await getTovarishRelayer({ options, fetchDataOptions: fetchDataOptions2, netId })).relayerClient : void 0; + const tovarishClient = !disableTovarish ? (await getTovarishRelayer({ + options, + fetchDataOptions: fetchDataOptions2, + netId + })).relayerClient : void 0; if (tovarishClient?.selectedRelayer) { console.log(` Connected with Tovarish Relayer ${tovarishClient.selectedRelayer.url} diff --git a/package.json b/package.json index 1766317..5ce82df 100644 --- a/package.json +++ b/package.json @@ -1,84 +1,84 @@ { - "name": "@tornado/cli", - "version": "1.0.11-alpha", - "description": "Modern Toolsets for Privacy Pools on Ethereum", - "main": "./dist/cli.js", - "types": "./dist/cli.d.ts", - "bin": { - "tornado-cli": "./dist/cli.js" - }, - "scripts": { - "typechain": "typechain --target ethers-v6 --out-dir src/typechain src/abi/*.json", - "types": "tsc --declaration --emitDeclarationOnly", - "lint": "eslint src/**/*.ts --ext .ts --ignore-pattern src/typechain", - "copy:worker": "ts-node scripts/copyFile.ts node_modules/@tornado/core/dist/merkleTreeWorker.js static/merkleTreeWorker.js", - "build:node": "webpack", - "build": "yarn types && yarn build:node", - "start": "ts-node src/cli.ts", - "help": "ts-node src/cli.ts help", - "create": "ts-node src/cli.ts create", - "deposit": "ts-node src/cli.ts deposit", - "depositInvoice": "ts-node src/cli.ts depositInvoice", - "withdraw": "ts-node src/cli.ts withdraw", - "compliance": "ts-node src/cli.ts compliance", - "updateEvents": "ts-node src/cli.ts updateEvents", - "relayers": "ts-node src/cli.ts relayers", - "createAccount": "ts-node src/cli.ts createAccount", - "decrypt": "ts-node src/cli.ts decrypt", - "send": "ts-node src/cli.ts send", - "balance": "ts-node src/cli.ts balance", - "sign": "ts-node src/cli.ts sign", - "broadcast": "ts-node src/cli.ts broadcast", - "proposals": "ts-node src/cli.ts proposals", - "delegates": "ts-node src/cli.ts delegates" - }, - "author": "", - "license": "MIT", - "files": [ - "dist", - "scripts", - "src", - "static", - ".env.example", - ".eslintrc.js", - ".gitattributes", - ".gitignore", - ".npmrc", - "logo.png", - "logo2.png", - "tsconfig.json", - "yarn.lock" - ], - "dependencies": {}, - "optionalDependencies": {}, - "devDependencies": { - "@colors/colors": "^1.6.0", - "@tornado/core": "git+https://git.tornado.ws/tornadocontrib/tornado-core.git#8041bd7f7801fd97a87d8c1945c0251b49032ec3", - "@typechain/ethers-v6": "^0.5.1", - "@types/figlet": "^1.7.0", - "@typescript-eslint/eslint-plugin": "^8.11.0", - "@typescript-eslint/parser": "^8.11.0", - "bloomfilter.js": "^1.0.2", - "cli-table3": "^0.6.4", - "commander": "^12.0.0", - "dotenv": "^16.4.5", - "esbuild-loader": "^4.2.2", - "eslint": "8.57.0", - "eslint-config-prettier": "^9.1.0", - "eslint-import-resolver-typescript": "^3.6.3", - "eslint-plugin-import": "^2.31.0", - "eslint-plugin-prettier": "^5.2.1", - "figlet": "^1.8.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.4", - "moment": "^2.30.1", - "prettier": "^3.2.5", - "socks-proxy-agent": "^8.0.3", - "ts-node": "^10.9.2", - "tsc": "^2.0.4", - "typechain": "^8.3.2", - "typescript": "^5.6.3", - "webpack": "^5.95.0", - "webpack-cli": "^5.1.4" - } + "name": "@tornado/cli", + "version": "1.0.11-alpha", + "description": "Modern Toolsets for Privacy Pools on Ethereum", + "main": "./dist/cli.js", + "types": "./dist/cli.d.ts", + "bin": { + "tornado-cli": "./dist/cli.js" + }, + "scripts": { + "typechain": "typechain --target ethers-v6 --out-dir src/typechain src/abi/*.json", + "types": "tsc --declaration --emitDeclarationOnly", + "lint": "eslint src/**/*.ts --ext .ts --ignore-pattern src/typechain", + "copy:worker": "ts-node scripts/copyFile.ts node_modules/@tornado/core/dist/merkleTreeWorker.js static/merkleTreeWorker.js", + "build:node": "webpack", + "build": "yarn types && yarn build:node", + "start": "ts-node src/cli.ts", + "help": "ts-node src/cli.ts help", + "create": "ts-node src/cli.ts create", + "deposit": "ts-node src/cli.ts deposit", + "depositInvoice": "ts-node src/cli.ts depositInvoice", + "withdraw": "ts-node src/cli.ts withdraw", + "compliance": "ts-node src/cli.ts compliance", + "updateEvents": "ts-node src/cli.ts updateEvents", + "relayers": "ts-node src/cli.ts relayers", + "createAccount": "ts-node src/cli.ts createAccount", + "decrypt": "ts-node src/cli.ts decrypt", + "send": "ts-node src/cli.ts send", + "balance": "ts-node src/cli.ts balance", + "sign": "ts-node src/cli.ts sign", + "broadcast": "ts-node src/cli.ts broadcast", + "proposals": "ts-node src/cli.ts proposals", + "delegates": "ts-node src/cli.ts delegates" + }, + "author": "", + "license": "MIT", + "files": [ + "dist", + "scripts", + "src", + "static", + ".env.example", + ".eslintrc.js", + ".gitattributes", + ".gitignore", + ".npmrc", + "logo.png", + "logo2.png", + "tsconfig.json", + "yarn.lock" + ], + "dependencies": {}, + "optionalDependencies": {}, + "devDependencies": { + "@colors/colors": "^1.6.0", + "@tornado/core": "git+https://git.tornado.ws/tornadocontrib/tornado-core.git#f411159f15566cb0cfe46d07b1c2c4eb23af2e1f", + "@typechain/ethers-v6": "^0.5.1", + "@types/figlet": "^1.7.0", + "@typescript-eslint/eslint-plugin": "^8.11.0", + "@typescript-eslint/parser": "^8.11.0", + "bloomfilter.js": "^1.0.2", + "cli-table3": "^0.6.4", + "commander": "^12.0.0", + "dotenv": "^16.4.5", + "esbuild-loader": "^4.2.2", + "eslint": "8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-typescript": "^3.6.3", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-prettier": "^5.2.1", + "figlet": "^1.8.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.4", + "moment": "^2.30.1", + "prettier": "^3.2.5", + "socks-proxy-agent": "^8.0.3", + "ts-node": "^10.9.2", + "tsc": "^2.0.4", + "typechain": "^8.3.2", + "typescript": "^5.6.3", + "webpack": "^5.95.0", + "webpack-cli": "^5.1.4" + } } diff --git a/scripts/copyFile.ts b/scripts/copyFile.ts index edcc5eb..81ed45b 100644 --- a/scripts/copyFile.ts +++ b/scripts/copyFile.ts @@ -2,14 +2,14 @@ import { argv } from 'process'; import { copyFile } from 'fs'; function copyFiles() { - const [, , inFile, outFile] = argv; + const [, , inFile, outFile] = argv; - copyFile(inFile, outFile, function(err) { - if (err) { - throw err; - } + copyFile(inFile, outFile, function(err) { + if (err) { + throw err; + } - console.log(`Copied ${inFile} to ${outFile}`) - }) + console.log(`Copied ${inFile} to ${outFile}`) + }) } copyFiles() \ No newline at end of file diff --git a/src/program.ts b/src/program.ts index d3ffcbd..0304f45 100644 --- a/src/program.ts +++ b/src/program.ts @@ -8,86 +8,86 @@ import Table from 'cli-table3'; import colors from '@colors/colors'; import moment from 'moment'; import { - Tornado__factory, - TornadoRouter__factory, - RelayerRegistry__factory, - Aggregator__factory, - Governance__factory, - Echoer__factory, + Tornado__factory, + TornadoRouter__factory, + RelayerRegistry__factory, + Aggregator__factory, + Governance__factory, + Echoer__factory, } from '@tornado/contracts'; import { - JsonRpcProvider, - Provider, - TransactionLike, - Wallet, - VoidSigner, - formatEther, - formatUnits, - parseEther, - parseUnits, - ZeroAddress, - MaxUint256, - Transaction, - getAddress, + JsonRpcProvider, + Provider, + TransactionLike, + Wallet, + VoidSigner, + formatEther, + formatUnits, + parseEther, + parseUnits, + ZeroAddress, + MaxUint256, + Transaction, + getAddress, } from 'ethers'; import { - ERC20, - ERC20__factory, - Multicall__factory, - OffchainOracle__factory, - OvmGasPriceOracle__factory, - getProviderOptions, - getProvider, - getTokenBalances, - TornadoWallet, - TornadoVoidSigner, - tokenBalances, - Deposit, - DepositsEvents, - RelayerInfo, - RelayerError, - RelayerClient, - WithdrawalsEvents, - TornadoFeeOracle, - TokenPriceOracle, - calculateSnarkProof, - MerkleTreeService, - multicall, - Invoice, - fetchData, - fetchDataOptions, - NetId, - NetIdType, - getInstanceByAddress, - getConfig, - enabledChains, - substring, - NoteAccount, - getSupportedInstances, - initGroth16, - getRelayerEnsSubdomains, - getActiveTokens, - TovarishClient, - TovarishInfo, - ReverseRecords__factory, - numberFormatter, + ERC20, + ERC20__factory, + Multicall__factory, + OffchainOracle__factory, + OvmGasPriceOracle__factory, + getProviderOptions, + getProvider, + getTokenBalances, + TornadoWallet, + TornadoVoidSigner, + tokenBalances, + Deposit, + DepositsEvents, + RelayerInfo, + RelayerError, + RelayerClient, + WithdrawalsEvents, + TornadoFeeOracle, + TokenPriceOracle, + calculateSnarkProof, + MerkleTreeService, + multicall, + Invoice, + fetchData, + fetchDataOptions, + NetId, + NetIdType, + getInstanceByAddress, + getConfig, + enabledChains, + substring, + NoteAccount, + getSupportedInstances, + initGroth16, + getRelayerEnsSubdomains, + getActiveTokens, + TovarishClient, + TovarishInfo, + ReverseRecords__factory, + numberFormatter, } from '@tornado/core'; import type { MerkleTree } from '@tornado/fixed-merkle-tree'; import * as packageJson from '../package.json'; import { - parseUrl, - parseRelayer, - parseNumber, - parseMnemonic, - parseKey, - parseAddress, - parseRecoveryKey, - NodeTornadoService, - NodeRegistryService, - NodeEchoService, - NodeEncryptedNotesService, - NodeGovernanceService, - TreeCache, + parseUrl, + parseRelayer, + parseNumber, + parseMnemonic, + parseKey, + parseAddress, + parseRecoveryKey, + NodeTornadoService, + NodeRegistryService, + NodeEchoService, + NodeEncryptedNotesService, + NodeGovernanceService, + TreeCache, } from './services'; const EXEC_NAME = 'tornado-cli'; @@ -113,1364 +113,1236 @@ const CIRCUIT_PATH = path.join(__dirname, '../static/tornado.json'); const KEY_PATH = path.join(__dirname, '../static/tornadoProvingKey.bin'); interface packageJson { - name: string; - version: string; - description: string; + name: string; + version: string; + description: string; } export interface commonProgramOptions { - rpc?: string; - ethRpc?: string; - disableTovarish?: boolean; - accountKey?: string; - relayer?: string; - walletWithdrawal?: boolean; - torPort?: number; - token?: string; - viewOnly?: string; - mnemonic?: string; - mnemonicIndex?: number; - privateKey?: string; - nonInteractive?: boolean; - localRpc?: boolean; + rpc?: string; + ethRpc?: string; + disableTovarish?: boolean; + accountKey?: string; + relayer?: string; + walletWithdrawal?: boolean; + torPort?: number; + token?: string; + viewOnly?: string; + mnemonic?: string; + mnemonicIndex?: number; + privateKey?: string; + nonInteractive?: boolean; + localRpc?: boolean; } export async function promptConfirmation(nonInteractive?: boolean) { - if (nonInteractive) { - return; - } + if (nonInteractive) { + return; + } - const prompt = createInterface({ input: process.stdin, output: process.stdout }); - const query = 'Confirm? [Y/n]\n'; + const prompt = createInterface({ + input: process.stdin, + output: process.stdout, + }); + const query = 'Confirm? [Y/n]\n'; - const confirmation = await new Promise((resolve) => prompt.question(query, resolve)); + const confirmation = await new Promise((resolve) => prompt.question(query, resolve)); - if (!confirmation || (confirmation as string).toUpperCase() !== 'Y') { - throw new Error('User canceled'); - } + if (!confirmation || (confirmation as string).toUpperCase() !== 'Y') { + throw new Error('User canceled'); + } } export async function getIPAddress(fetchDataOptions: fetchDataOptions) { - const htmlIPInfo = await fetchData('https://check.torproject.org', { - ...fetchDataOptions, - method: 'GET', - headers: { - 'Content-Type': 'text/html; charset=utf-8', - }, - }); - const ip = htmlIPInfo.split('Your IP address appears to be: ').pop().split('').pop().split(' { - options = { - rpc: options.rpc || (process.env.RPC_URL ? parseUrl(process.env.RPC_URL) : undefined), - ethRpc: options.ethRpc || (process.env.ETHRPC_URL ? parseUrl(process.env.ETHRPC_URL) : undefined), - disableTovarish: Boolean(options.disableTovarish) || (process.env.DISABLE_TOVARISH === 'true' ? true : undefined), - accountKey: options.accountKey || (process.env.ACCOUNT_KEY ? parseRecoveryKey(process.env.ACCOUNT_KEY) : undefined), - relayer: options.relayer || (process.env.RELAYER ? parseRelayer(process.env.RELAYER) : undefined), - walletWithdrawal: - Boolean(options.walletWithdrawal) || (process.env.WALLET_WITHDRAWAL === 'true' ? true : undefined), - torPort: options.torPort || (process.env.TOR_PORT ? parseNumber(process.env.TOR_PORT) : undefined), - token: options.token || (process.env.TOKEN ? parseAddress(process.env.TOKEN) : undefined), - viewOnly: options.viewOnly || (process.env.VIEW_ONLY ? parseAddress(process.env.VIEW_ONLY) : undefined), - mnemonic: options.mnemonic || (process.env.MNEMONIC ? parseMnemonic(process.env.MNEMONIC) : undefined), - mnemonicIndex: - options.mnemonicIndex || (process.env.MNEMONIC_INDEX ? parseNumber(process.env.MNEMONIC_INDEX) : undefined), - privateKey: options.privateKey || (process.env.PRIVATE_KEY ? parseKey(process.env.PRIVATE_KEY) : undefined), - nonInteractive: Boolean(options.nonInteractive) || (process.env.NON_INTERACTIVE === 'true' ? true : undefined), - localRpc: Boolean(options.localRpc) || (process.env.LOCAL_RPC === 'true' ? true : undefined), - }; + options = { + rpc: options.rpc || (process.env.RPC_URL ? parseUrl(process.env.RPC_URL) : undefined), + ethRpc: options.ethRpc || (process.env.ETHRPC_URL ? parseUrl(process.env.ETHRPC_URL) : undefined), + disableTovarish: + Boolean(options.disableTovarish) || (process.env.DISABLE_TOVARISH === 'true' ? true : undefined), + accountKey: + options.accountKey || (process.env.ACCOUNT_KEY ? parseRecoveryKey(process.env.ACCOUNT_KEY) : undefined), + relayer: options.relayer || (process.env.RELAYER ? parseRelayer(process.env.RELAYER) : undefined), + walletWithdrawal: + Boolean(options.walletWithdrawal) || (process.env.WALLET_WITHDRAWAL === 'true' ? true : undefined), + torPort: options.torPort || (process.env.TOR_PORT ? parseNumber(process.env.TOR_PORT) : undefined), + token: options.token || (process.env.TOKEN ? parseAddress(process.env.TOKEN) : undefined), + viewOnly: options.viewOnly || (process.env.VIEW_ONLY ? parseAddress(process.env.VIEW_ONLY) : undefined), + mnemonic: options.mnemonic || (process.env.MNEMONIC ? parseMnemonic(process.env.MNEMONIC) : undefined), + mnemonicIndex: + options.mnemonicIndex || (process.env.MNEMONIC_INDEX ? parseNumber(process.env.MNEMONIC_INDEX) : undefined), + privateKey: options.privateKey || (process.env.PRIVATE_KEY ? parseKey(process.env.PRIVATE_KEY) : undefined), + nonInteractive: Boolean(options.nonInteractive) || (process.env.NON_INTERACTIVE === 'true' ? true : undefined), + localRpc: Boolean(options.localRpc) || (process.env.LOCAL_RPC === 'true' ? true : undefined), + }; - const fetchDataOptions = { - torPort: options.torPort, - }; + const fetchDataOptions = { + torPort: options.torPort, + }; - const { ip, isTor } = await getIPAddress(fetchDataOptions); + const { ip, isTor } = await getIPAddress(fetchDataOptions); - const optionsTable = new Table(); + const optionsTable = new Table(); - optionsTable.push( - [{ colSpan: 2, content: 'Program Options', hAlign: 'center' }], - ['IP', ip], - ['Is Tor', isTor], - ...(Object.entries(options) - .map(([key, value]) => { - if (typeof value !== 'undefined') { - return [key, value]; - } - }) - .filter((r) => r) as string[][]), - ); + optionsTable.push( + [{ colSpan: 2, content: 'Program Options', hAlign: 'center' }], + ['IP', ip], + ['Is Tor', isTor], + ...(Object.entries(options) + .map(([key, value]) => { + if (typeof value !== 'undefined') { + return [key, value]; + } + }) + .filter((r) => r) as string[][]), + ); - console.log('\n' + optionsTable.toString() + '\n'); + console.log('\n' + optionsTable.toString() + '\n'); - await promptConfirmation(options.nonInteractive); + await promptConfirmation(options.nonInteractive); - return { - options, - fetchDataOptions, - }; + return { + options, + fetchDataOptions, + }; } export function getProgramProvider(rpcUrl: string = '', providerOptions: getProviderOptions): Promise { - const { netId } = providerOptions; + const { netId } = providerOptions; - const config = getConfig(netId); + const config = getConfig(netId); - if (!rpcUrl) { - rpcUrl = Object.values(config.rpcUrls)[0]?.url || ''; - } + if (!rpcUrl) { + rpcUrl = Object.values(config.rpcUrls)[0]?.url || ''; + } - return getProvider(rpcUrl, providerOptions); + return getProvider(rpcUrl, providerOptions); } export function getProgramSigner({ - options, - provider, + options, + provider, }: { - options: commonProgramOptions; - provider: Provider; + options: commonProgramOptions; + provider: Provider; }): TornadoVoidSigner | TornadoWallet | undefined { - if (options.viewOnly) { - return new TornadoVoidSigner(options.viewOnly, provider); - } + if (options.viewOnly) { + return new TornadoVoidSigner(options.viewOnly, provider); + } - if (options.privateKey) { - return new TornadoWallet(options.privateKey, provider); - } + if (options.privateKey) { + return new TornadoWallet(options.privateKey, provider); + } - if (options.mnemonic) { - return TornadoWallet.fromMnemonic(options.mnemonic, provider, options.mnemonicIndex); - } + if (options.mnemonic) { + return TornadoWallet.fromMnemonic(options.mnemonic, provider, options.mnemonicIndex); + } } export async function getProgramRelayer({ - options, - fetchDataOptions, - netId, -}: { - options: commonProgramOptions; - fetchDataOptions?: fetchDataOptions; - netId: NetIdType; -}): Promise<{ - validRelayers: RelayerInfo[]; - invalidRelayers: RelayerError[]; - relayerClient: RelayerClient; -}> { - const { ethRpc, relayer } = options; - - const netConfig = getConfig(netId); - - const ethConfig = getConfig(RELAYER_NETWORK); - - const { - aggregatorContract, - registryContract, - constants: { REGISTRY_BLOCK }, - } = ethConfig; - - const provider = await getProgramProvider(ethRpc, { - netId: RELAYER_NETWORK, - ...fetchDataOptions, - }); - - const registryService = new NodeRegistryService({ - netId: RELAYER_NETWORK, - provider, - RelayerRegistry: RelayerRegistry__factory.connect(registryContract as string, provider), - Aggregator: Aggregator__factory.connect(aggregatorContract as string, provider), - relayerEnsSubdomains: getRelayerEnsSubdomains(), - deployedBlock: REGISTRY_BLOCK, + options, fetchDataOptions, - cacheDirectory: EVENTS_DIR, - userDirectory: SAVED_DIR, - }); - - const relayerClient = new RelayerClient({ netId, - config: netConfig, - fetchDataOptions, - }); +}: { + options: commonProgramOptions; + fetchDataOptions?: fetchDataOptions; + netId: NetIdType; +}): Promise<{ + validRelayers: RelayerInfo[]; + invalidRelayers: RelayerError[]; + relayerClient: RelayerClient; +}> { + const { ethRpc, relayer } = options; - // If user provided relayer is a full working URL - if (relayer && relayer.startsWith('http')) { - const relayerStatus = await relayerClient.askRelayerStatus({ - url: relayer, + const netConfig = getConfig(netId); + + const ethConfig = getConfig(RELAYER_NETWORK); + + const { + aggregatorContract, + registryContract, + constants: { REGISTRY_BLOCK }, + } = ethConfig; + + const provider = await getProgramProvider(ethRpc, { + netId: RELAYER_NETWORK, + ...fetchDataOptions, }); - relayerClient.selectedRelayer = { - ensName: new URL(relayerStatus.url).hostname, - relayerAddress: getAddress(relayerStatus.rewardAccount), - netId: relayerStatus.netId, - url: relayerStatus.url, - hostname: new URL(relayerStatus.url).hostname, - rewardAccount: getAddress(relayerStatus.rewardAccount), - instances: getSupportedInstances(relayerStatus.instances), - gasPrice: relayerStatus.gasPrices?.fast, - ethPrices: relayerStatus.ethPrices, - currentQueue: relayerStatus.currentQueue, - tornadoServiceFee: relayerStatus.tornadoServiceFee, - }; + const registryService = new NodeRegistryService({ + netId: RELAYER_NETWORK, + provider, + RelayerRegistry: RelayerRegistry__factory.connect(registryContract as string, provider), + Aggregator: Aggregator__factory.connect(aggregatorContract as string, provider), + relayerEnsSubdomains: getRelayerEnsSubdomains(), + deployedBlock: REGISTRY_BLOCK, + fetchDataOptions, + cacheDirectory: EVENTS_DIR, + userDirectory: SAVED_DIR, + }); + + const relayerClient = new RelayerClient({ + netId, + config: netConfig, + fetchDataOptions, + }); + + // If user provided relayer is a full working URL + if (relayer && relayer.startsWith('http')) { + const relayerStatus = await relayerClient.askRelayerStatus({ + url: relayer, + }); + + relayerClient.selectedRelayer = { + ensName: new URL(relayerStatus.url).hostname, + relayerAddress: getAddress(relayerStatus.rewardAccount), + netId: relayerStatus.netId, + url: relayerStatus.url, + hostname: new URL(relayerStatus.url).hostname, + rewardAccount: getAddress(relayerStatus.rewardAccount), + instances: getSupportedInstances(relayerStatus.instances), + gasPrice: relayerStatus.gasPrices?.fast, + ethPrices: relayerStatus.ethPrices, + currentQueue: relayerStatus.currentQueue, + tornadoServiceFee: relayerStatus.tornadoServiceFee, + }; + + return { + validRelayers: [relayerClient.selectedRelayer], + invalidRelayers: [], + relayerClient, + }; + } + + console.log('\nGetting valid relayers from registry, would take some time\n'); + + const { validRelayers, invalidRelayers } = await relayerClient.getValidRelayers( + (await registryService.updateRelayers()).relayers, + ); + + // If user has provided either a hostname or ensname of known registered relayer + const foundRelayer = relayer + ? validRelayers.find((r) => r.hostname.includes(relayer) || r.ensName.includes(relayer)) + : undefined; + + if (foundRelayer) { + relayerClient.selectedRelayer = foundRelayer; + + return { + validRelayers: [foundRelayer], + invalidRelayers: [], + relayerClient, + }; + } + + const relayerStatus = relayerClient.pickWeightedRandomRelayer(validRelayers); + + if (relayerStatus) { + relayerClient.selectedRelayer = relayerStatus; + } return { - validRelayers: [relayerClient.selectedRelayer], - invalidRelayers: [], - relayerClient, + validRelayers, + invalidRelayers, + relayerClient, }; - } - - console.log('\nGetting valid relayers from registry, would take some time\n'); - - const { validRelayers, invalidRelayers } = await relayerClient.getValidRelayers( - (await registryService.updateRelayers()).relayers, - ); - - // If user has provided either a hostname or ensname of known registered relayer - const foundRelayer = relayer - ? validRelayers.find((r) => r.hostname.includes(relayer) || r.ensName.includes(relayer)) - : undefined; - - if (foundRelayer) { - relayerClient.selectedRelayer = foundRelayer; - - return { - validRelayers: [foundRelayer], - invalidRelayers: [], - relayerClient, - }; - } - - const relayerStatus = relayerClient.pickWeightedRandomRelayer(validRelayers); - - if (relayerStatus) { - relayerClient.selectedRelayer = relayerStatus; - } - - return { - validRelayers, - invalidRelayers, - relayerClient, - }; } export async function getTovarishRelayer({ - options, - fetchDataOptions, - netId, -}: { - options: commonProgramOptions; - fetchDataOptions?: fetchDataOptions; - netId: NetIdType; -}): Promise<{ - validRelayers: TovarishInfo[]; - invalidRelayers: RelayerError[]; - relayerClient: TovarishClient; -}> { - const { ethRpc, relayer } = options; - - const netConfig = getConfig(netId); - - const ethConfig = getConfig(RELAYER_NETWORK); - - const { - aggregatorContract, - registryContract, - constants: { REGISTRY_BLOCK }, - } = ethConfig; - - const provider = await getProgramProvider(ethRpc, { - netId: RELAYER_NETWORK, - ...fetchDataOptions, - }); - - const registryService = new NodeRegistryService({ - netId: RELAYER_NETWORK, - provider, - RelayerRegistry: RelayerRegistry__factory.connect(registryContract as string, provider), - Aggregator: Aggregator__factory.connect(aggregatorContract as string, provider), - relayerEnsSubdomains: getRelayerEnsSubdomains(), - deployedBlock: REGISTRY_BLOCK, + options, fetchDataOptions, - cacheDirectory: EVENTS_DIR, - userDirectory: SAVED_DIR, - }); - - const relayerClient = new TovarishClient({ netId, - config: netConfig, - fetchDataOptions, - }); +}: { + options: commonProgramOptions; + fetchDataOptions?: fetchDataOptions; + netId: NetIdType; +}): Promise<{ + validRelayers: TovarishInfo[]; + invalidRelayers: RelayerError[]; + relayerClient: TovarishClient; +}> { + const { ethRpc, relayer } = options; - // If user provided relayer is a full working URL - if (relayer && relayer.startsWith('http')) { - const relayerStatus = await relayerClient.askRelayerStatus({ - url: relayer, + const netConfig = getConfig(netId); + + const ethConfig = getConfig(RELAYER_NETWORK); + + const { + aggregatorContract, + registryContract, + constants: { REGISTRY_BLOCK }, + } = ethConfig; + + const provider = await getProgramProvider(ethRpc, { + netId: RELAYER_NETWORK, + ...fetchDataOptions, }); - relayerClient.selectedRelayer = { - ensName: new URL(relayerStatus.url).hostname, - relayerAddress: getAddress(relayerStatus.rewardAccount), - netId: relayerStatus.netId, - url: relayerStatus.url, - hostname: new URL(relayerStatus.url).hostname, - rewardAccount: getAddress(relayerStatus.rewardAccount), - instances: getSupportedInstances(relayerStatus.instances), - gasPrice: relayerStatus.gasPrices?.fast, - ethPrices: relayerStatus.ethPrices, - currentQueue: relayerStatus.currentQueue, - tornadoServiceFee: relayerStatus.tornadoServiceFee, - latestBlock: Number(relayerStatus.latestBlock), - latestBalance: relayerStatus.latestBalance, - version: relayerStatus.version, - events: relayerStatus.events, - syncStatus: relayerStatus.syncStatus, - }; + const registryService = new NodeRegistryService({ + netId: RELAYER_NETWORK, + provider, + RelayerRegistry: RelayerRegistry__factory.connect(registryContract as string, provider), + Aggregator: Aggregator__factory.connect(aggregatorContract as string, provider), + relayerEnsSubdomains: getRelayerEnsSubdomains(), + deployedBlock: REGISTRY_BLOCK, + fetchDataOptions, + cacheDirectory: EVENTS_DIR, + userDirectory: SAVED_DIR, + }); + + const relayerClient = new TovarishClient({ + netId, + config: netConfig, + fetchDataOptions, + }); + + // If user provided relayer is a full working URL + if (relayer && relayer.startsWith('http')) { + const relayerStatus = await relayerClient.askRelayerStatus({ + url: relayer, + }); + + relayerClient.selectedRelayer = { + ensName: new URL(relayerStatus.url).hostname, + relayerAddress: getAddress(relayerStatus.rewardAccount), + netId: relayerStatus.netId, + url: relayerStatus.url, + hostname: new URL(relayerStatus.url).hostname, + rewardAccount: getAddress(relayerStatus.rewardAccount), + instances: getSupportedInstances(relayerStatus.instances), + gasPrice: relayerStatus.gasPrices?.fast, + ethPrices: relayerStatus.ethPrices, + currentQueue: relayerStatus.currentQueue, + tornadoServiceFee: relayerStatus.tornadoServiceFee, + latestBlock: Number(relayerStatus.latestBlock), + latestBalance: relayerStatus.latestBalance, + version: relayerStatus.version, + events: relayerStatus.events, + syncStatus: relayerStatus.syncStatus, + }; + + return { + validRelayers: [relayerClient.selectedRelayer], + invalidRelayers: [], + relayerClient, + }; + } + + console.log('\nGetting valid tovarish relayers\n'); + + const { validRelayers, invalidRelayers } = await relayerClient.getValidRelayers( + (await registryService.getRelayersFromCache()).relayers, + ); + + // If user has provided either a hostname or ensname of known registered relayer + const foundRelayer = relayer + ? validRelayers.find((r) => r.hostname.includes(relayer) || r.ensName.includes(relayer)) + : undefined; + + if (foundRelayer) { + relayerClient.selectedRelayer = foundRelayer; + + return { + validRelayers: [foundRelayer], + invalidRelayers: [], + relayerClient, + }; + } + + if (validRelayers.length) { + relayerClient.selectedRelayer = validRelayers[0]; + } else { + const errMsg = `Working Tovarish Relayer for net ${netId} not found, you can try with legacy relayer with DISABLE_TOVARISH='true' env and try again`; + throw new Error(errMsg); + } return { - validRelayers: [relayerClient.selectedRelayer], - invalidRelayers: [], - relayerClient, + validRelayers, + invalidRelayers, + relayerClient, }; - } - - console.log('\nGetting valid tovarish relayers\n'); - - const { validRelayers, invalidRelayers } = await relayerClient.getValidRelayers( - (await registryService.getRelayersFromCache()).relayers, - ); - - // If user has provided either a hostname or ensname of known registered relayer - const foundRelayer = relayer - ? validRelayers.find((r) => r.hostname.includes(relayer) || r.ensName.includes(relayer)) - : undefined; - - if (foundRelayer) { - relayerClient.selectedRelayer = foundRelayer; - - return { - validRelayers: [foundRelayer], - invalidRelayers: [], - relayerClient, - }; - } - - if (validRelayers.length) { - relayerClient.selectedRelayer = validRelayers[0]; - } else { - const errMsg = `Working Tovarish Relayer for net ${netId} not found, you can try with legacy relayer with DISABLE_TOVARISH='true' env and try again`; - throw new Error(errMsg); - } - - return { - validRelayers, - invalidRelayers, - relayerClient, - }; } export async function programSendTransaction({ - signer, - options, - populatedTransaction, + signer, + options, + populatedTransaction, }: { - signer: VoidSigner | Wallet; - options: commonProgramOptions; - populatedTransaction: TransactionLike; + signer: VoidSigner | Wallet; + options: commonProgramOptions; + populatedTransaction: TransactionLike; }) { - const txTable = new Table(); - // ethers.js doesn't output complete transaction from the contract runner so we populate the transaction once again - const txObject = - !populatedTransaction.gasLimit || !populatedTransaction.from - ? (JSON.parse(JSON.stringify(await signer.populateTransaction(populatedTransaction))) as TransactionLike) - : (JSON.parse(JSON.stringify(populatedTransaction, null, 2)) as TransactionLike); + const txTable = new Table(); + // ethers.js doesn't output complete transaction from the contract runner so we populate the transaction once again + const txObject = + !populatedTransaction.gasLimit || !populatedTransaction.from + ? (JSON.parse(JSON.stringify(await signer.populateTransaction(populatedTransaction))) as TransactionLike) + : (JSON.parse(JSON.stringify(populatedTransaction, null, 2)) as TransactionLike); - const txType = - signer instanceof VoidSigner - ? 'Unsigned Transaction' - : options.localRpc - ? 'Unbroadcasted Transaction' - : 'Send Transaction?'; + const txType = + signer instanceof VoidSigner + ? 'Unsigned Transaction' + : options.localRpc + ? 'Unbroadcasted Transaction' + : 'Send Transaction?'; - txTable.push( - [{ colSpan: 2, content: txType, hAlign: 'center' }], - ...Object.entries(txObject).map(([key, value]) => { - if (key === 'data' && value) { - return ['data', substring(value, 40)]; - } - return [key, value]; - }), - ); + txTable.push( + [{ colSpan: 2, content: txType, hAlign: 'center' }], + ...Object.entries(txObject).map(([key, value]) => { + if (key === 'data' && value) { + return ['data', substring(value, 40)]; + } + return [key, value]; + }), + ); + + if (txType === 'Unsigned Transaction') { + // delete from field as the Transaction.from method doesn't accept it from unsigned tx + delete txObject.from; + const transaction = Transaction.from(txObject); + console.log('\n' + txTable.toString() + '\n'); + console.log('Sign this transaction:\n'); + console.log(`${transaction.unsignedSerialized}\n`); + return; + } + + if (txType === 'Unbroadcasted Transaction') { + const signedTx = await signer.signTransaction(txObject); + console.log('\n' + txTable.toString() + '\n'); + console.log('Broadcast transaction:\n'); + console.log(`${signedTx}\n`); + return; + } - if (txType === 'Unsigned Transaction') { - // delete from field as the Transaction.from method doesn't accept it from unsigned tx - delete txObject.from; - const transaction = Transaction.from(txObject); console.log('\n' + txTable.toString() + '\n'); - console.log('Sign this transaction:\n'); - console.log(`${transaction.unsignedSerialized}\n`); - return; - } - if (txType === 'Unbroadcasted Transaction') { - const signedTx = await signer.signTransaction(txObject); - console.log('\n' + txTable.toString() + '\n'); - console.log('Broadcast transaction:\n'); - console.log(`${signedTx}\n`); - return; - } + await promptConfirmation(options.nonInteractive); - console.log('\n' + txTable.toString() + '\n'); + const resp = await signer.sendTransaction(txObject); - await promptConfirmation(options.nonInteractive); + console.log(`Sent transaction ${resp.hash}\n`); - const resp = await signer.sendTransaction(txObject); - - console.log(`Sent transaction ${resp.hash}\n`); - - return resp; + return resp; } export function tornadoProgram() { - const { version, description } = packageJson as packageJson; - - const program = new Command(); - - program.name(EXEC_NAME).version(version).description(description); - - program - .command('create') - .description('Creates Tornado Cash deposit note and deposit invoice') - .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) - .argument('', 'Currency to deposit on Tornado Cash') - .argument('', 'Amount to deposit on Tornado Cash') - .action(async (netId: NetIdType, currency: string, amount: string) => { - currency = currency.toLowerCase(); - - const config = getConfig(netId); - - const { - routerContract, - nativeCurrency, - tokens: { [currency]: currencyConfig }, - } = config; - - const { - decimals, - tokenAddress, - instanceAddress: { [amount]: instanceAddress }, - } = currencyConfig; - - const isEth = nativeCurrency === currency; - const denomination = parseUnits(amount, decimals); - - const deposit = await Deposit.createNote({ currency, amount, netId }); - - const { noteHex, note, commitmentHex } = deposit; - - const ERC20Interface = ERC20__factory.createInterface(); - const TornadoRouterInterface = TornadoRouter__factory.createInterface(); - - console.log(`New deposit: ${deposit.toString()}\n`); - - await writeFile(`./backup-tornado-${currency}-${amount}-${netId}-${noteHex.slice(0, 10)}.txt`, note, { - encoding: 'utf8', - }); - - const depositData = TornadoRouterInterface.encodeFunctionData('deposit', [instanceAddress, commitmentHex, '0x']); - - if (!isEth) { - const approveData = ERC20Interface.encodeFunctionData('approve', [routerContract, MaxUint256]); - - console.log(`Approve Data: ${JSON.stringify({ to: tokenAddress, data: approveData }, null, 2)}]\n`); - - console.log(`Transaction Data: ${JSON.stringify({ to: routerContract, data: depositData }, null, 2)}\n`); - } else { - console.log( - `Transaction Data: ${JSON.stringify({ to: routerContract, value: denomination.toString(), data: depositData }, null, 2)}`, - ); - } - - process.exit(0); - }); - - program - .command('deposit') - .description( - 'Submit a deposit of specified currency and amount from default eth account and return the resulting note. \n\n' + - 'The currency is one of (ETH|DAI|cDAI|USDC|cUSDC|USDT). \n\n' + - 'The amount depends on currency, see config.js file or see Tornado Cash UI.', - ) - .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) - .argument('', 'Currency to deposit on Tornado Cash') - .argument('', 'Amount to deposit on Tornado Cash') - .action(async (netId: NetIdType, currency: string, amount: string, cmdOptions: commonProgramOptions) => { - const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); - currency = currency.toLowerCase(); - const { rpc, accountKey } = options; - - const config = getConfig(netId); - - const { - multicallContract, - routerContract, - nativeCurrency, - tokens: { [currency]: currencyConfig }, - } = config; - - const { - decimals, - tokenAddress, - instanceAddress: { [amount]: instanceAddress }, - } = currencyConfig; - - const isEth = nativeCurrency === currency; - const denomination = parseUnits(amount, decimals); - - const provider = await getProgramProvider(rpc, { - netId, - ...fetchDataOptions, - }); - - const signer = getProgramSigner({ - options, - provider, - }); - - const noteAccount = accountKey ? new NoteAccount({ recoveryKey: accountKey }) : undefined; - - if (!signer) { - throw new Error( - 'Signer not defined, make sure you have either viewOnly address, mnemonic, or private key configured', - ); - } - - const TornadoProxy = TornadoRouter__factory.connect(routerContract, signer); - const Multicall = Multicall__factory.connect(multicallContract, provider); - const Token = tokenAddress ? ERC20__factory.connect(tokenAddress, signer) : undefined; - - const [ethBalance, tokenBalance, tokenApprovals] = await multicall(Multicall, [ - { - contract: Multicall, - name: 'getEthBalance', - params: [signer.address], - }, - ...(!isEth - ? [ - { - contract: Token as ERC20, - name: 'balanceOf', - params: [signer.address], - }, - { - contract: Token as ERC20, - name: 'allowance', - params: [signer.address, routerContract], - }, - ] - : []), - ]); - - if (isEth && denomination > ethBalance) { - const errMsg = `Invalid ${currency.toUpperCase()} balance, wants ${amount} have ${formatUnits(ethBalance, decimals)}`; - throw new Error(errMsg); - } else if (!isEth && denomination > tokenBalance) { - const errMsg = `Invalid ${currency.toUpperCase()} balance, wants ${amount} have ${formatUnits(tokenBalance, decimals)}`; - throw new Error(errMsg); - } - - if (!isEth && denomination > tokenApprovals) { - // token approval - const resp = await programSendTransaction({ - signer, - options, - populatedTransaction: await (Token as ERC20).approve.populateTransaction(routerContract, MaxUint256), - }); - - // wait until signer sends the approve transaction offline - if (signer instanceof VoidSigner || options.localRpc) { - console.log( - 'Signer can not sign or broadcast transactions, please send the token approve transaction first and try again.\n', - ); - process.exit(0); - } else { - // Wait until the approval tx is confirmed - await resp?.wait(); - } - } - - const deposit = await Deposit.createNote({ currency, amount, netId }); - - const { note, noteHex, commitmentHex } = deposit; - - const encryptedNote = noteAccount - ? noteAccount.encryptNote({ - address: instanceAddress, - noteHex, - }) - : '0x'; - - const backupFile = `./backup-tornado-${currency}-${amount}-${netId}-${noteHex.slice(0, 10)}.txt`; - - console.log(`New deposit: ${deposit.toString()}\n`); - - console.log(`Writing note backup at ${backupFile}\n`); - - await writeFile(backupFile, note, { encoding: 'utf8' }); - - if (encryptedNote !== '0x') { - console.log(`Storing encrypted note on-chain for backup (Account key: ${accountKey})\n`); - } - - await programSendTransaction({ - signer, - options, - populatedTransaction: await TornadoProxy.deposit.populateTransaction( - instanceAddress, - commitmentHex, - encryptedNote, - { - value: isEth ? denomination : BigInt(0), - }, - ), - }); - - process.exit(0); - }); - - program - .command('depositInvoice') - .description( - 'Submit a deposit of tornado invoice from default eth account and return the resulting note. \n\n' + - 'Useful to deposit on online computer without exposing note', - ) - .argument('', 'Tornado Cash Invoice generated from create command') - .action(async (invoiceString: string, cmdOptions: commonProgramOptions) => { - const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); - const { rpc } = options; - - const { currency, amount, netId, commitmentHex } = new Invoice(invoiceString); - - const config = getConfig(netId); - - const { - multicallContract, - routerContract, - nativeCurrency, - tokens: { [currency]: currencyConfig }, - } = config; - - const { - decimals, - tokenAddress, - instanceAddress: { [amount]: instanceAddress }, - } = currencyConfig; - - const isEth = nativeCurrency === currency; - const denomination = parseUnits(amount, decimals); - - const provider = await getProgramProvider(rpc, { - netId, - ...fetchDataOptions, - }); - - const signer = getProgramSigner({ - options, - provider, - }); - - if (!signer) { - throw new Error( - 'Signer not defined, make sure you have either viewOnly address, mnemonic, or private key configured', - ); - } - - const TornadoProxy = TornadoRouter__factory.connect(routerContract, signer); - const Multicall = Multicall__factory.connect(multicallContract, provider); - const Token = tokenAddress ? ERC20__factory.connect(tokenAddress, signer) : undefined; - - const [ethBalance, tokenBalance, tokenApprovals] = await multicall(Multicall, [ - { - contract: Multicall, - name: 'getEthBalance', - params: [signer.address], - }, - ...(!isEth - ? [ - { - contract: Token as ERC20, - name: 'balanceOf', - params: [signer.address], - }, - { - contract: Token as ERC20, - name: 'allowance', - params: [signer.address, routerContract], - }, - ] - : []), - ]); - - if (isEth && denomination > ethBalance) { - const errMsg = `Invalid ${currency.toUpperCase()} balance, wants ${amount} have ${formatUnits(ethBalance, decimals)}`; - throw new Error(errMsg); - } else if (!isEth && denomination > tokenBalance) { - const errMsg = `Invalid ${currency.toUpperCase()} balance, wants ${amount} have ${formatUnits(tokenBalance, decimals)}`; - throw new Error(errMsg); - } - - if (!isEth && denomination > tokenApprovals) { - // token approval - await programSendTransaction({ - signer, - options, - populatedTransaction: await (Token as ERC20).approve.populateTransaction(routerContract, MaxUint256), - }); - - // wait until signer sends the approve transaction offline - if (signer instanceof VoidSigner) { - console.log( - 'Signer can not sign transactions, please send the token approve transaction first and try again.\n', - ); - process.exit(0); - } - } - - await programSendTransaction({ - signer, - options, - populatedTransaction: await TornadoProxy.deposit.populateTransaction(instanceAddress, commitmentHex, '0x', { - value: isEth ? denomination : BigInt(0), - }), - }); - - process.exit(0); - }); - - program - .command('withdraw') - .description( - 'Withdraw a note to a recipient account using relayer or specified private key. \n\n' + - '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. \n\n' + - 'Also see the --relayer option.\n\n', - ) - .argument('', 'Tornado Cash Deposit Note') - .argument('', 'Recipient to receive withdrawn amount', parseAddress) - .argument('[ETH_purchase]', 'ETH to purchase', parseNumber) - .action( - async (note: string, recipient: string, ethPurchase: number | undefined, cmdOptions: commonProgramOptions) => { - const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); - const { rpc, walletWithdrawal, disableTovarish } = options; - - // Prepare groth16 in advance - initGroth16(); - - const deposit = await Deposit.parseNote(note); - - const { netId, currency, amount, commitmentHex, nullifierHex, nullifier, secret } = deposit; - - const config = getConfig(netId); - - const { - deployedBlock, - nativeCurrency, - multicallContract, - routerContract, - offchainOracleContract, - ovmGasPriceOracleContract, - tokens: { [currency]: currencyConfig }, - } = config; - - const { - decimals, - tokenAddress, - gasLimit: instanceGasLimit, - tokenGasLimit, - instanceAddress: { [amount]: instanceAddress }, - } = currencyConfig; - - const isEth = nativeCurrency === currency; - const denomination = parseUnits(amount, decimals); - const firstAmount = Object.keys(currencyConfig.instanceAddress).sort((a, b) => Number(a) - Number(b))[0]; - const isFirstAmount = Number(amount) === Number(firstAmount); - - const provider = await getProgramProvider(rpc, { - netId, - ...fetchDataOptions, - }); - - const { relayerClient } = disableTovarish - ? await getProgramRelayer({ - options, - fetchDataOptions, - netId, - }) - : await getTovarishRelayer({ - options, - fetchDataOptions, - netId, + const { version, description } = packageJson as packageJson; + + const program = new Command(); + + program.name(EXEC_NAME).version(version).description(description); + + program + .command('create') + .description('Creates Tornado Cash deposit note and deposit invoice') + .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) + .argument('', 'Currency to deposit on Tornado Cash') + .argument('', 'Amount to deposit on Tornado Cash') + .action(async (netId: NetIdType, currency: string, amount: string) => { + currency = currency.toLowerCase(); + + const config = getConfig(netId); + + const { + routerContract, + nativeCurrency, + tokens: { [currency]: currencyConfig }, + } = config; + + const { + decimals, + tokenAddress, + instanceAddress: { [amount]: instanceAddress }, + } = currencyConfig; + + const isEth = nativeCurrency === currency; + const denomination = parseUnits(amount, decimals); + + const deposit = await Deposit.createNote({ + currency, + amount, + netId, }); - const tovarishClient = relayerClient.tovarish ? (relayerClient as TovarishClient) : undefined; + const { noteHex, note, commitmentHex } = deposit; - if (tovarishClient?.selectedRelayer) { - console.log(`\nConnected with Tovarish Relayer ${tovarishClient.selectedRelayer.url}\n`); - } + const ERC20Interface = ERC20__factory.createInterface(); + const TornadoRouterInterface = TornadoRouter__factory.createInterface(); - if (!walletWithdrawal && !relayerClient?.selectedRelayer) { - throw new Error( - 'No valid relayer found for the network, you can either try again, or find any relayers using the relayers command and set with --relayer option', - ); - } + console.log(`New deposit: ${deposit.toString()}\n`); - const signer = getProgramSigner({ - options, - provider, - }); - const noSigner = Boolean(!signer || signer instanceof VoidSigner); + await writeFile(`./backup-tornado-${currency}-${amount}-${netId}-${noteHex.slice(0, 10)}.txt`, note, { + encoding: 'utf8', + }); - if (walletWithdrawal && noSigner) { - throw new Error('Wallet withdrawal is configured however could not find any wallets'); - } + const depositData = TornadoRouterInterface.encodeFunctionData('deposit', [ + instanceAddress, + commitmentHex, + '0x', + ]); - const Tornado = Tornado__factory.connect(instanceAddress, provider); - const TornadoProxy = TornadoRouter__factory.connect(routerContract, !walletWithdrawal ? provider : signer); - const Multicall = Multicall__factory.connect(multicallContract, provider); - - const tornadoFeeOracle = new TornadoFeeOracle( - provider, - ovmGasPriceOracleContract - ? OvmGasPriceOracle__factory.connect(ovmGasPriceOracleContract, provider) - : undefined, - ); - - const tokenPriceOracle = new TokenPriceOracle( - provider, - Multicall, - offchainOracleContract ? OffchainOracle__factory.connect(offchainOracleContract, provider) : undefined, - ); - - const TornadoServiceConstructor = { - netId, - provider, - Tornado, - amount, - currency, - deployedBlock, - fetchDataOptions, - tovarishClient, - cacheDirectory: EVENTS_DIR, - userDirectory: SAVED_DIR, - nativeCurrency, - }; - - const depositsService = new NodeTornadoService({ - ...TornadoServiceConstructor, - type: 'Deposit', - merkleTreeService: new MerkleTreeService({ - netId, - amount, - currency, - Tornado, - merkleWorkerPath, - }), - }); - - const withdrawalsService = new NodeTornadoService({ - ...TornadoServiceConstructor, - type: 'Withdrawal', - }); - - const { events: depositEvents, validateResult: tree } = await depositsService.updateEvents(); - - const withdrawalEvents = (await withdrawalsService.updateEvents()).events as WithdrawalsEvents[]; - - const depositEvent = (depositEvents as DepositsEvents[]).find(({ commitment }) => commitment === commitmentHex); - - const withdrawalEvent = withdrawalEvents.find(({ nullifierHash }) => nullifierHash === nullifierHex); - - if (!depositEvent) { - throw new Error('Deposit not found'); - } - - const complianceTable = new Table(); - - const depositDate = new Date(depositEvent.timestamp * 1000); - - complianceTable.push( - [{ colSpan: 2, content: 'Deposit', hAlign: 'center' }], - ['Deposit', `${amount} ${currency.toUpperCase()}`], - [ - 'Date', - `${depositDate.toLocaleDateString()} ${depositDate.toLocaleTimeString()} (${moment.unix(depositEvent.timestamp).fromNow()})`, - ], - ['From', depositEvent.from], - ['Transaction', depositEvent.transactionHash], - ['Commitment', commitmentHex], - ['Spent', Boolean(withdrawalEvent)], - ); - - if (withdrawalEvent) { - const withdrawalDate = new Date(withdrawalEvent.timestamp * 1000); - - complianceTable.push( - [{ colSpan: 2, content: 'Withdraw', hAlign: 'center' }], - ['Withdrawal', `${amount} ${currency.toUpperCase()}`], - ['Relayer Fee', `${formatUnits(withdrawalEvent.fee, decimals)} ${currency.toUpperCase()}`], - [ - 'Date', - `${withdrawalDate.toLocaleDateString()} ${withdrawalDate.toLocaleTimeString()} (${moment.unix(withdrawalEvent.timestamp).fromNow()})`, - ], - ['To', withdrawalEvent.to], - ['Transaction', withdrawalEvent.transactionHash], - ['Nullifier', nullifierHex], - ); - } - - console.log('\n\n' + complianceTable.toString() + '\n'); - - if (withdrawalEvent) { - throw new Error('Note is already spent'); - } - - const [circuit, provingKey, netGasPrice, l1Fee, tokenPriceInWei] = await Promise.all([ - readFile(CIRCUIT_PATH, { encoding: 'utf8' }).then((s) => JSON.parse(s)), - readFile(KEY_PATH).then((b) => new Uint8Array(b).buffer), - tornadoFeeOracle.gasPrice(), - tornadoFeeOracle.fetchL1OptimismFee(), - !isEth ? tokenPriceOracle.fetchPrice(tokenAddress as string, decimals) : BigInt(0), - ]); - - const { pathElements, pathIndices } = tree.path((depositEvent as DepositsEvents).leafIndex); - - let gasPrice = netGasPrice; - - if (!walletWithdrawal && !tovarishClient && netId === NetId.BSC) { - gasPrice = parseUnits('3.3', 'gwei'); - } - - // If the config overrides default gas limit we override - const defaultGasLimit = instanceGasLimit ? BigInt(instanceGasLimit) : BigInt(DEFAULT_GAS_LIMIT); - let gasLimit = defaultGasLimit; - - // If the denomination is small only refund small amount otherwise use the default value - const refundGasLimit = isFirstAmount && tokenGasLimit ? BigInt(tokenGasLimit) : undefined; - - async function getProof() { - let relayer = ZeroAddress; - let fee = BigInt(0); - let refund = BigInt(0); - - if (!walletWithdrawal) { if (!isEth) { - refund = ethPurchase - ? parseEther(`${ethPurchase}`) - : tornadoFeeOracle.defaultEthRefund(gasPrice, refundGasLimit); + const approveData = ERC20Interface.encodeFunctionData('approve', [routerContract, MaxUint256]); + + console.log(`Approve Data: ${JSON.stringify({ to: tokenAddress, data: approveData }, null, 2)}]\n`); + + console.log( + `Transaction Data: ${JSON.stringify({ to: routerContract, data: depositData }, null, 2)}\n`, + ); + } else { + console.log( + `Transaction Data: ${JSON.stringify({ to: routerContract, value: denomination.toString(), data: depositData }, null, 2)}`, + ); } - const { rewardAccount, tornadoServiceFee: relayerFeePercent } = relayerClient?.selectedRelayer || {}; + process.exit(0); + }); - fee = tornadoFeeOracle.calculateRelayerFee({ - gasPrice, - gasLimit, - l1Fee, - denomination, - ethRefund: refund, - tokenPriceInWei, - tokenDecimals: decimals, - relayerFeePercent, - isEth, + program + .command('deposit') + .description( + 'Submit a deposit of specified currency and amount from default eth account and return the resulting note. \n\n' + + 'The currency is one of (ETH|DAI|cDAI|USDC|cUSDC|USDT). \n\n' + + 'The amount depends on currency, see config.js file or see Tornado Cash UI.', + ) + .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) + .argument('', 'Currency to deposit on Tornado Cash') + .argument('', 'Amount to deposit on Tornado Cash') + .action(async (netId: NetIdType, currency: string, amount: string, cmdOptions: commonProgramOptions) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); + currency = currency.toLowerCase(); + const { rpc, accountKey } = options; + + const config = getConfig(netId); + + const { + multicallContract, + routerContract, + nativeCurrency, + tokens: { [currency]: currencyConfig }, + } = config; + + const { + decimals, + tokenAddress, + instanceAddress: { [amount]: instanceAddress }, + } = currencyConfig; + + const isEth = nativeCurrency === currency; + const denomination = parseUnits(amount, decimals); + + const provider = await getProgramProvider(rpc, { + netId, + ...fetchDataOptions, }); - relayer = rewardAccount as string; + const signer = getProgramSigner({ + options, + provider, + }); - if (fee > denomination) { - const errMsg = - `Relayer fee ${formatUnits(fee, decimals)} ${currency.toUpperCase()} ` + - `exceeds the deposit amount ${amount} ${currency.toUpperCase()}.`; - throw new Error(errMsg); + const noteAccount = accountKey ? new NoteAccount({ recoveryKey: accountKey }) : undefined; + + if (!signer) { + throw new Error( + 'Signer not defined, make sure you have either viewOnly address, mnemonic, or private key configured', + ); } - } - const { proof, args } = await calculateSnarkProof( - { - root: tree.root, - nullifierHex, - recipient, - relayer, - fee, - refund, - nullifier, - secret, - pathElements, - pathIndices, + const TornadoProxy = TornadoRouter__factory.connect(routerContract, signer); + const Multicall = Multicall__factory.connect(multicallContract, provider); + const Token = tokenAddress ? ERC20__factory.connect(tokenAddress, signer) : undefined; + + const [ethBalance, tokenBalance, tokenApprovals] = await multicall(Multicall, [ + { + contract: Multicall, + name: 'getEthBalance', + params: [signer.address], + }, + ...(!isEth + ? [ + { + contract: Token as ERC20, + name: 'balanceOf', + params: [signer.address], + }, + { + contract: Token as ERC20, + name: 'allowance', + params: [signer.address, routerContract], + }, + ] + : []), + ]); + + if (isEth && denomination > ethBalance) { + const errMsg = `Invalid ${currency.toUpperCase()} balance, wants ${amount} have ${formatUnits(ethBalance, decimals)}`; + throw new Error(errMsg); + } else if (!isEth && denomination > tokenBalance) { + const errMsg = `Invalid ${currency.toUpperCase()} balance, wants ${amount} have ${formatUnits(tokenBalance, decimals)}`; + throw new Error(errMsg); + } + + if (!isEth && denomination > tokenApprovals) { + // token approval + const resp = await programSendTransaction({ + signer, + options, + populatedTransaction: await (Token as ERC20).approve.populateTransaction( + routerContract, + MaxUint256, + ), + }); + + // wait until signer sends the approve transaction offline + if (signer instanceof VoidSigner || options.localRpc) { + console.log( + 'Signer can not sign or broadcast transactions, please send the token approve transaction first and try again.\n', + ); + process.exit(0); + } else { + // Wait until the approval tx is confirmed + await resp?.wait(); + } + } + + const deposit = await Deposit.createNote({ + currency, + amount, + netId, + }); + + const { note, noteHex, commitmentHex } = deposit; + + const encryptedNote = noteAccount + ? noteAccount.encryptNote({ + address: instanceAddress, + noteHex, + }) + : '0x'; + + const backupFile = `./backup-tornado-${currency}-${amount}-${netId}-${noteHex.slice(0, 10)}.txt`; + + console.log(`New deposit: ${deposit.toString()}\n`); + + console.log(`Writing note backup at ${backupFile}\n`); + + await writeFile(backupFile, note, { encoding: 'utf8' }); + + if (encryptedNote !== '0x') { + console.log(`Storing encrypted note on-chain for backup (Account key: ${accountKey})\n`); + } + + await programSendTransaction({ + signer, + options, + populatedTransaction: await TornadoProxy.deposit.populateTransaction( + instanceAddress, + commitmentHex, + encryptedNote, + { + value: isEth ? denomination : BigInt(0), + }, + ), + }); + + process.exit(0); + }); + + program + .command('depositInvoice') + .description( + 'Submit a deposit of tornado invoice from default eth account and return the resulting note. \n\n' + + 'Useful to deposit on online computer without exposing note', + ) + .argument('', 'Tornado Cash Invoice generated from create command') + .action(async (invoiceString: string, cmdOptions: commonProgramOptions) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); + const { rpc } = options; + + const { currency, amount, netId, commitmentHex } = new Invoice(invoiceString); + + const config = getConfig(netId); + + const { + multicallContract, + routerContract, + nativeCurrency, + tokens: { [currency]: currencyConfig }, + } = config; + + const { + decimals, + tokenAddress, + instanceAddress: { [amount]: instanceAddress }, + } = currencyConfig; + + const isEth = nativeCurrency === currency; + const denomination = parseUnits(amount, decimals); + + const provider = await getProgramProvider(rpc, { + netId, + ...fetchDataOptions, + }); + + const signer = getProgramSigner({ + options, + provider, + }); + + if (!signer) { + throw new Error( + 'Signer not defined, make sure you have either viewOnly address, mnemonic, or private key configured', + ); + } + + const TornadoProxy = TornadoRouter__factory.connect(routerContract, signer); + const Multicall = Multicall__factory.connect(multicallContract, provider); + const Token = tokenAddress ? ERC20__factory.connect(tokenAddress, signer) : undefined; + + const [ethBalance, tokenBalance, tokenApprovals] = await multicall(Multicall, [ + { + contract: Multicall, + name: 'getEthBalance', + params: [signer.address], + }, + ...(!isEth + ? [ + { + contract: Token as ERC20, + name: 'balanceOf', + params: [signer.address], + }, + { + contract: Token as ERC20, + name: 'allowance', + params: [signer.address, routerContract], + }, + ] + : []), + ]); + + if (isEth && denomination > ethBalance) { + const errMsg = `Invalid ${currency.toUpperCase()} balance, wants ${amount} have ${formatUnits(ethBalance, decimals)}`; + throw new Error(errMsg); + } else if (!isEth && denomination > tokenBalance) { + const errMsg = `Invalid ${currency.toUpperCase()} balance, wants ${amount} have ${formatUnits(tokenBalance, decimals)}`; + throw new Error(errMsg); + } + + if (!isEth && denomination > tokenApprovals) { + // token approval + await programSendTransaction({ + signer, + options, + populatedTransaction: await (Token as ERC20).approve.populateTransaction( + routerContract, + MaxUint256, + ), + }); + + // wait until signer sends the approve transaction offline + if (signer instanceof VoidSigner) { + console.log( + 'Signer can not sign transactions, please send the token approve transaction first and try again.\n', + ); + process.exit(0); + } + } + + await programSendTransaction({ + signer, + options, + populatedTransaction: await TornadoProxy.deposit.populateTransaction( + instanceAddress, + commitmentHex, + '0x', + { + value: isEth ? denomination : BigInt(0), + }, + ), + }); + + process.exit(0); + }); + + program + .command('withdraw') + .description( + 'Withdraw a note to a recipient account using relayer or specified private key. \n\n' + + '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. \n\n' + + 'Also see the --relayer option.\n\n', + ) + .argument('', 'Tornado Cash Deposit Note') + .argument('', 'Recipient to receive withdrawn amount', parseAddress) + .argument('[ETH_purchase]', 'ETH to purchase', parseNumber) + .action( + async ( + note: string, + recipient: string, + ethPurchase: number | undefined, + cmdOptions: commonProgramOptions, + ) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); + const { rpc, walletWithdrawal, disableTovarish } = options; + + // Prepare groth16 in advance + initGroth16(); + + const deposit = await Deposit.parseNote(note); + + const { netId, currency, amount, commitmentHex, nullifierHex, nullifier, secret } = deposit; + + const config = getConfig(netId); + + const { + deployedBlock, + nativeCurrency, + multicallContract, + routerContract, + offchainOracleContract, + ovmGasPriceOracleContract, + tokens: { [currency]: currencyConfig }, + } = config; + + const { + decimals, + tokenAddress, + gasLimit: instanceGasLimit, + tokenGasLimit, + instanceAddress: { [amount]: instanceAddress }, + } = currencyConfig; + + const isEth = nativeCurrency === currency; + const denomination = parseUnits(amount, decimals); + const firstAmount = Object.keys(currencyConfig.instanceAddress).sort( + (a, b) => Number(a) - Number(b), + )[0]; + const isFirstAmount = Number(amount) === Number(firstAmount); + + const provider = await getProgramProvider(rpc, { + netId, + ...fetchDataOptions, + }); + + const { relayerClient } = disableTovarish + ? await getProgramRelayer({ + options, + fetchDataOptions, + netId, + }) + : await getTovarishRelayer({ + options, + fetchDataOptions, + netId, + }); + + const tovarishClient = relayerClient.tovarish ? (relayerClient as TovarishClient) : undefined; + + if (tovarishClient?.selectedRelayer) { + console.log(`\nConnected with Tovarish Relayer ${tovarishClient.selectedRelayer.url}\n`); + } + + if (!walletWithdrawal && !relayerClient?.selectedRelayer) { + throw new Error( + 'No valid relayer found for the network, you can either try again, or find any relayers using the relayers command and set with --relayer option', + ); + } + + const signer = getProgramSigner({ + options, + provider, + }); + const noSigner = Boolean(!signer || signer instanceof VoidSigner); + + if (walletWithdrawal && noSigner) { + throw new Error('Wallet withdrawal is configured however could not find any wallets'); + } + + const Tornado = Tornado__factory.connect(instanceAddress, provider); + const TornadoProxy = TornadoRouter__factory.connect( + routerContract, + !walletWithdrawal ? provider : signer, + ); + const Multicall = Multicall__factory.connect(multicallContract, provider); + + const tornadoFeeOracle = new TornadoFeeOracle( + provider, + ovmGasPriceOracleContract + ? OvmGasPriceOracle__factory.connect(ovmGasPriceOracleContract, provider) + : undefined, + ); + + const tokenPriceOracle = new TokenPriceOracle( + provider, + Multicall, + offchainOracleContract + ? OffchainOracle__factory.connect(offchainOracleContract, provider) + : undefined, + ); + + const TornadoServiceConstructor = { + netId, + provider, + Tornado, + amount, + currency, + deployedBlock, + fetchDataOptions, + tovarishClient, + cacheDirectory: EVENTS_DIR, + userDirectory: SAVED_DIR, + nativeCurrency, + }; + + const depositsService = new NodeTornadoService({ + ...TornadoServiceConstructor, + type: 'Deposit', + merkleTreeService: new MerkleTreeService({ + netId, + amount, + currency, + Tornado, + merkleWorkerPath, + }), + }); + + const withdrawalsService = new NodeTornadoService({ + ...TornadoServiceConstructor, + type: 'Withdrawal', + }); + + const { events: depositEvents, validateResult: tree } = + await depositsService.updateEvents(); + + const withdrawalEvents = (await withdrawalsService.updateEvents()).events as WithdrawalsEvents[]; + + const depositEvent = (depositEvents as DepositsEvents[]).find( + ({ commitment }) => commitment === commitmentHex, + ); + + const withdrawalEvent = withdrawalEvents.find(({ nullifierHash }) => nullifierHash === nullifierHex); + + if (!depositEvent) { + throw new Error('Deposit not found'); + } + + const complianceTable = new Table(); + + const depositDate = new Date(depositEvent.timestamp * 1000); + + complianceTable.push( + [{ colSpan: 2, content: 'Deposit', hAlign: 'center' }], + ['Deposit', `${amount} ${currency.toUpperCase()}`], + [ + 'Date', + `${depositDate.toLocaleDateString()} ${depositDate.toLocaleTimeString()} (${moment.unix(depositEvent.timestamp).fromNow()})`, + ], + ['From', depositEvent.from], + ['Transaction', depositEvent.transactionHash], + ['Commitment', commitmentHex], + ['Spent', Boolean(withdrawalEvent)], + ); + + if (withdrawalEvent) { + const withdrawalDate = new Date(withdrawalEvent.timestamp * 1000); + + complianceTable.push( + [{ colSpan: 2, content: 'Withdraw', hAlign: 'center' }], + ['Withdrawal', `${amount} ${currency.toUpperCase()}`], + ['Relayer Fee', `${formatUnits(withdrawalEvent.fee, decimals)} ${currency.toUpperCase()}`], + [ + 'Date', + `${withdrawalDate.toLocaleDateString()} ${withdrawalDate.toLocaleTimeString()} (${moment.unix(withdrawalEvent.timestamp).fromNow()})`, + ], + ['To', withdrawalEvent.to], + ['Transaction', withdrawalEvent.transactionHash], + ['Nullifier', nullifierHex], + ); + } + + console.log('\n\n' + complianceTable.toString() + '\n'); + + if (withdrawalEvent) { + throw new Error('Note is already spent'); + } + + const [circuit, provingKey, netGasPrice, l1Fee, tokenPriceInWei] = await Promise.all([ + readFile(CIRCUIT_PATH, { encoding: 'utf8' }).then((s) => JSON.parse(s)), + readFile(KEY_PATH).then((b) => new Uint8Array(b).buffer), + tornadoFeeOracle.gasPrice(), + tornadoFeeOracle.fetchL1OptimismFee(), + !isEth ? tokenPriceOracle.fetchPrice(tokenAddress as string, decimals) : BigInt(0), + ]); + + const { pathElements, pathIndices } = tree.path((depositEvent as DepositsEvents).leafIndex); + + let gasPrice = netGasPrice; + + if (!walletWithdrawal && !tovarishClient && netId === NetId.BSC) { + gasPrice = parseUnits('3.3', 'gwei'); + } + + // If the config overrides default gas limit we override + const defaultGasLimit = instanceGasLimit ? BigInt(instanceGasLimit) : BigInt(DEFAULT_GAS_LIMIT); + let gasLimit = defaultGasLimit; + + // If the denomination is small only refund small amount otherwise use the default value + const refundGasLimit = isFirstAmount && tokenGasLimit ? BigInt(tokenGasLimit) : undefined; + + async function getProof() { + let relayer = ZeroAddress; + let fee = BigInt(0); + let refund = BigInt(0); + + if (!walletWithdrawal) { + if (!isEth) { + refund = ethPurchase + ? parseEther(`${ethPurchase}`) + : tornadoFeeOracle.defaultEthRefund(gasPrice, refundGasLimit); + } + + const { rewardAccount, tornadoServiceFee: relayerFeePercent } = + relayerClient?.selectedRelayer || {}; + + fee = tornadoFeeOracle.calculateRelayerFee({ + gasPrice, + gasLimit, + l1Fee, + denomination, + ethRefund: refund, + tokenPriceInWei, + tokenDecimals: decimals, + relayerFeePercent, + isEth, + }); + + relayer = rewardAccount as string; + + if (fee > denomination) { + const errMsg = + `Relayer fee ${formatUnits(fee, decimals)} ${currency.toUpperCase()} ` + + `exceeds the deposit amount ${amount} ${currency.toUpperCase()}.`; + throw new Error(errMsg); + } + } + + const { proof, args } = await calculateSnarkProof( + { + root: tree.root, + nullifierHex, + recipient, + relayer, + fee, + refund, + nullifier, + secret, + pathElements, + pathIndices, + }, + circuit, + provingKey, + ); + + return { + fee, + refund, + proof, + args, + }; + } + + let { fee, refund, proof, args } = await getProof(); + + const withdrawOverrides = { + from: !walletWithdrawal + ? relayerClient?.selectedRelayer?.rewardAccount + : (signer?.address as string), + value: args[5] || 0, + }; + + gasLimit = await TornadoProxy.withdraw.estimateGas(instanceAddress, proof, ...args, withdrawOverrides); + + if (fee) { + ({ fee, refund, proof, args } = await getProof()); + + // Verify if our recalculated proof can be withdrawn + await TornadoProxy.withdraw.estimateGas(instanceAddress, proof, ...args, withdrawOverrides); + } + + const txFee = gasPrice * gasLimit; + const txFeeInToken = !isEth + ? tornadoFeeOracle.calculateTokenAmount(txFee, tokenPriceInWei, decimals) + : BigInt(0); + const txFeeString = !isEth + ? `( ${Number(formatUnits(txFeeInToken, decimals)).toFixed(5)} ${currency.toUpperCase()} worth ) ` + + `( ${((Number(formatUnits(txFeeInToken, decimals)) / Number(amount)) * 100).toFixed(5)}% )` + : `( ${((Number(formatUnits(txFee, decimals)) / Number(amount)) * 100).toFixed(5)}% )`; + + const withdrawTable = new Table(); + withdrawTable.push( + [ + { + colSpan: 2, + content: 'Withdrawal Info', + hAlign: 'center', + }, + ], + [ + 'Deposit Date', + `${depositDate.toLocaleDateString()} ${depositDate.toLocaleTimeString()} (${moment.unix(depositEvent.timestamp).fromNow()})`, + ], + ['From', depositEvent.from], + ['Deposit Transaction', depositEvent.transactionHash], + ['Commitment', depositEvent.commitment], + ['Gas Price', `${formatUnits(gasPrice, 'gwei')} gwei`], + ['Gas Limit', gasLimit], + [ + 'Transaction Fee', + `${Number(formatEther(txFee)).toFixed(5)} ${nativeCurrency.toUpperCase()} ${txFeeString}`, + ], + ); + + // withdraw using relayer + if (!walletWithdrawal) { + const relayerFeePercent = Number(relayerClient?.selectedRelayer?.tornadoServiceFee) || 0; + const relayerFee = + (BigInt(denomination) * BigInt(Math.floor(10000 * relayerFeePercent))) / BigInt(10000 * 100); + + withdrawTable.push( + ['Relayer', `${relayerClient?.selectedRelayer?.url}`], + [ + 'Relayer Fee', + `${formatUnits(relayerFee, decimals)} ${currency.toUpperCase()} ` + + `( ${relayerFeePercent}% )`, + ], + [ + 'Total Fee', + `${formatUnits(fee, decimals)} ${currency.toUpperCase()} ` + + `( ${((Number(fee) / Number(denomination)) * 100).toFixed(5)}% )`, + ], + [ + 'Amount to receive', + `${Number(formatUnits(denomination - fee, decimals)).toFixed(5)} ${currency.toUpperCase()}`, + ], + [ + `${nativeCurrency.toUpperCase()} purchase`, + `${formatEther(refund)} ${nativeCurrency.toUpperCase()}`, + ], + ['To', recipient], + ['Nullifier', nullifierHex], + ); + + console.log('\n' + withdrawTable.toString() + '\n'); + + await promptConfirmation(options.nonInteractive); + + console.log('Sending withdraw transaction through relay\n'); + + await relayerClient.tornadoWithdraw({ + contract: instanceAddress, + proof, + args, + }); + } else { + // withdraw from wallet + withdrawTable.push( + ['Signer', `${signer?.address}`], + ['Amount to receive', `${amount} ${currency.toUpperCase()}`], + ['To', recipient], + ['Nullifier', nullifierHex], + ); + + console.log('\n' + withdrawTable.toString() + '\n'); + + await promptConfirmation(options.nonInteractive); + + console.log('Sending withdraw transaction through wallet\n'); + + await programSendTransaction({ + signer: signer as TornadoVoidSigner | TornadoWallet, + options, + populatedTransaction: await TornadoProxy.withdraw.populateTransaction( + instanceAddress, + proof, + ...args, + ), + }); + } + + process.exit(0); }, - circuit, - provingKey, - ); - - return { - fee, - refund, - proof, - args, - }; - } - - let { fee, refund, proof, args } = await getProof(); - - const withdrawOverrides = { - from: !walletWithdrawal ? relayerClient?.selectedRelayer?.rewardAccount : (signer?.address as string), - value: args[5] || 0, - }; - - gasLimit = await TornadoProxy.withdraw.estimateGas(instanceAddress, proof, ...args, withdrawOverrides); - - if (fee) { - ({ fee, refund, proof, args } = await getProof()); - - // Verify if our recalculated proof can be withdrawn - await TornadoProxy.withdraw.estimateGas(instanceAddress, proof, ...args, withdrawOverrides); - } - - const txFee = gasPrice * gasLimit; - const txFeeInToken = !isEth - ? tornadoFeeOracle.calculateTokenAmount(txFee, tokenPriceInWei, decimals) - : BigInt(0); - const txFeeString = !isEth - ? `( ${Number(formatUnits(txFeeInToken, decimals)).toFixed(5)} ${currency.toUpperCase()} worth ) ` + - `( ${((Number(formatUnits(txFeeInToken, decimals)) / Number(amount)) * 100).toFixed(5)}% )` - : `( ${((Number(formatUnits(txFee, decimals)) / Number(amount)) * 100).toFixed(5)}% )`; - - const withdrawTable = new Table(); - withdrawTable.push( - [{ colSpan: 2, content: 'Withdrawal Info', hAlign: 'center' }], - [ - 'Deposit Date', - `${depositDate.toLocaleDateString()} ${depositDate.toLocaleTimeString()} (${moment.unix(depositEvent.timestamp).fromNow()})`, - ], - ['From', depositEvent.from], - ['Deposit Transaction', depositEvent.transactionHash], - ['Commitment', depositEvent.commitment], - ['Gas Price', `${formatUnits(gasPrice, 'gwei')} gwei`], - ['Gas Limit', gasLimit], - [ - 'Transaction Fee', - `${Number(formatEther(txFee)).toFixed(5)} ${nativeCurrency.toUpperCase()} ${txFeeString}`, - ], ); - // withdraw using relayer - if (!walletWithdrawal) { - const relayerFeePercent = Number(relayerClient?.selectedRelayer?.tornadoServiceFee) || 0; - const relayerFee = - (BigInt(denomination) * BigInt(Math.floor(10000 * relayerFeePercent))) / BigInt(10000 * 100); + program + .command('compliance') + .description( + 'Shows the deposit and withdrawal of the provided note. \n\n' + + 'This might be necessary to show the origin of assets held in your withdrawal address. \n\n', + ) + .argument('', 'Tornado Cash Deposit Note') + .action(async (note: string, cmdOptions: commonProgramOptions) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); + const { rpc, disableTovarish } = options; - withdrawTable.push( - ['Relayer', `${relayerClient?.selectedRelayer?.url}`], - [ - 'Relayer Fee', - `${formatUnits(relayerFee, decimals)} ${currency.toUpperCase()} ` + `( ${relayerFeePercent}% )`, - ], - [ - 'Total Fee', - `${formatUnits(fee, decimals)} ${currency.toUpperCase()} ` + - `( ${((Number(fee) / Number(denomination)) * 100).toFixed(5)}% )`, - ], - [ - 'Amount to receive', - `${Number(formatUnits(denomination - fee, decimals)).toFixed(5)} ${currency.toUpperCase()}`, - ], - [`${nativeCurrency.toUpperCase()} purchase`, `${formatEther(refund)} ${nativeCurrency.toUpperCase()}`], - ['To', recipient], - ['Nullifier', nullifierHex], - ); + const deposit = await Deposit.parseNote(note); + const { netId, currency, amount, commitmentHex, nullifierHex } = deposit; - console.log('\n' + withdrawTable.toString() + '\n'); + const config = getConfig(netId); - await promptConfirmation(options.nonInteractive); + const { + deployedBlock, + nativeCurrency, + tokens: { [currency]: currencyConfig }, + } = config; - console.log('Sending withdraw transaction through relay\n'); + const { + decimals, + instanceAddress: { [amount]: instanceAddress }, + } = currencyConfig; - await relayerClient.tornadoWithdraw({ - contract: instanceAddress, - proof, - args, - }); - } else { - // withdraw from wallet - withdrawTable.push( - ['Signer', `${signer?.address}`], - ['Amount to receive', `${amount} ${currency.toUpperCase()}`], - ['To', recipient], - ['Nullifier', nullifierHex], - ); - - console.log('\n' + withdrawTable.toString() + '\n'); - - await promptConfirmation(options.nonInteractive); - - console.log('Sending withdraw transaction through wallet\n'); - - await programSendTransaction({ - signer: signer as TornadoVoidSigner | TornadoWallet, - options, - populatedTransaction: await TornadoProxy.withdraw.populateTransaction(instanceAddress, proof, ...args), - }); - } - - process.exit(0); - }, - ); - - program - .command('compliance') - .description( - 'Shows the deposit and withdrawal of the provided note. \n\n' + - 'This might be necessary to show the origin of assets held in your withdrawal address. \n\n', - ) - .argument('', 'Tornado Cash Deposit Note') - .action(async (note: string, cmdOptions: commonProgramOptions) => { - const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); - const { rpc, disableTovarish } = options; - - const deposit = await Deposit.parseNote(note); - const { netId, currency, amount, commitmentHex, nullifierHex } = deposit; - - const config = getConfig(netId); - - const { - deployedBlock, - nativeCurrency, - tokens: { [currency]: currencyConfig }, - } = config; - - const { - decimals, - instanceAddress: { [amount]: instanceAddress }, - } = currencyConfig; - - const provider = await getProgramProvider(rpc, { - netId, - ...fetchDataOptions, - }); - - const tovarishClient = !disableTovarish - ? (await getTovarishRelayer({ options, fetchDataOptions, netId })).relayerClient - : undefined; - - if (tovarishClient?.selectedRelayer) { - console.log(`\nConnected with Tovarish Relayer ${tovarishClient.selectedRelayer.url}\n`); - } - - const Tornado = Tornado__factory.connect(instanceAddress, provider); - - const TornadoServiceConstructor = { - netId, - provider, - Tornado, - amount, - currency, - deployedBlock, - fetchDataOptions, - tovarishClient, - cacheDirectory: EVENTS_DIR, - userDirectory: SAVED_DIR, - nativeCurrency, - }; - - const depositsService = new NodeTornadoService({ - ...TornadoServiceConstructor, - type: 'Deposit', - merkleTreeService: new MerkleTreeService({ - netId, - amount, - currency, - Tornado, - merkleWorkerPath, - }), - optionalTree: true, - }); - - const withdrawalsService = new NodeTornadoService({ - ...TornadoServiceConstructor, - type: 'Withdrawal', - }); - - const depositEvents = (await depositsService.updateEvents()).events as DepositsEvents[]; - - const withdrawalEvents = (await withdrawalsService.updateEvents()).events as WithdrawalsEvents[]; - - const depositEvent = depositEvents.find(({ commitment }) => commitment === commitmentHex); - - const withdrawalEvent = withdrawalEvents.find(({ nullifierHash }) => nullifierHash === nullifierHex); - - const complianceTable = new Table(); - complianceTable.push([{ colSpan: 2, content: 'Compliance Info', hAlign: 'center' }]); - - if (!depositEvent) { - complianceTable.push([{ colSpan: 2, content: 'Deposit', hAlign: 'center' }], ['Deposit', 'Not Found']); - } else { - const depositDate = new Date(depositEvent.timestamp * 1000); - - complianceTable.push( - [{ colSpan: 2, content: 'Deposit', hAlign: 'center' }], - ['Deposit', `${amount} ${currency.toUpperCase()}`], - [ - 'Date', - `${depositDate.toLocaleDateString()} ${depositDate.toLocaleTimeString()} (${moment.unix(depositEvent.timestamp).fromNow()})`, - ], - ['From', depositEvent.from], - ['Transaction', depositEvent.transactionHash], - ['Commitment', commitmentHex], - ['Spent', Boolean(withdrawalEvent)], - ); - } - - if (withdrawalEvent) { - const withdrawalDate = new Date(withdrawalEvent.timestamp * 1000); - - complianceTable.push( - [{ colSpan: 2, content: 'Withdraw', hAlign: 'center' }], - ['Withdrawal', `${amount} ${currency.toUpperCase()}`], - ['Relayer Fee', `${formatUnits(withdrawalEvent.fee, decimals)} ${currency.toUpperCase()}`], - [ - 'Date', - `${withdrawalDate.toLocaleDateString()} ${withdrawalDate.toLocaleTimeString()} (${moment.unix(withdrawalEvent.timestamp).fromNow()})`, - ], - ['To', withdrawalEvent.to], - ['Transaction', withdrawalEvent.transactionHash], - ['Nullifier', nullifierHex], - ); - } - - console.log('\n\n' + complianceTable.toString() + '\n'); - - process.exit(0); - }); - - program - .command('updateEvents') - .description('Sync the local cache file of tornado cash events.\n\n') - .argument('[netId]', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) - .argument('[currency]', 'Currency to sync events') - .action( - async (netIdOpts: NetIdType | undefined, currencyOpts: string | undefined, cmdOptions: commonProgramOptions) => { - const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); - const { rpc, disableTovarish } = options; - - const networks = netIdOpts ? [netIdOpts] : enabledChains; - - for (const netId of networks) { - const config = getConfig(netId); - const { - tokens, - nativeCurrency, - routerContract, - echoContract, - registryContract, - reverseRecordsContract, - aggregatorContract, - governanceContract, - deployedBlock, - constants: { GOVERNANCE_BLOCK, REGISTRY_BLOCK, NOTE_ACCOUNT_BLOCK, ENCRYPTED_NOTES_BLOCK }, - } = config; - - const provider = await getProgramProvider(rpc, { - netId, - ...fetchDataOptions, - }); - - const tovarishClient = !disableTovarish - ? (await getTovarishRelayer({ options, fetchDataOptions, netId })).relayerClient - : undefined; - - if (tovarishClient?.selectedRelayer) { - console.log(`\nConnected with Tovarish Relayer ${tovarishClient.selectedRelayer.url}\n`); - } - - if (netId === RELAYER_NETWORK && governanceContract && aggregatorContract && reverseRecordsContract) { - const governanceService = new NodeGovernanceService({ - netId, - provider, - Governance: Governance__factory.connect(governanceContract, provider), - Aggregator: Aggregator__factory.connect(aggregatorContract, provider), - ReverseRecords: ReverseRecords__factory.connect(reverseRecordsContract, provider), - deployedBlock: GOVERNANCE_BLOCK, - fetchDataOptions, - tovarishClient, - cacheDirectory: EVENTS_DIR, - userDirectory: SAVED_DIR, + const provider = await getProgramProvider(rpc, { + netId, + ...fetchDataOptions, }); - await governanceService.updateEvents(); - } + const tovarishClient = !disableTovarish + ? ( + await getTovarishRelayer({ + options, + fetchDataOptions, + netId, + }) + ).relayerClient + : undefined; - if (netId === RELAYER_NETWORK && registryContract && aggregatorContract) { - const registryService = new NodeRegistryService({ - netId, - provider, - RelayerRegistry: RelayerRegistry__factory.connect(registryContract, provider), - Aggregator: Aggregator__factory.connect(aggregatorContract, provider), - relayerEnsSubdomains: getRelayerEnsSubdomains(), - deployedBlock: REGISTRY_BLOCK, - fetchDataOptions, - // Exclude tovarish relayer from updating registry events and use RPC directly - // tovarishClient, - cacheDirectory: EVENTS_DIR, - userDirectory: SAVED_DIR, - }); + if (tovarishClient?.selectedRelayer) { + console.log(`\nConnected with Tovarish Relayer ${tovarishClient.selectedRelayer.url}\n`); + } - await registryService.updateRelayers(); - } + const Tornado = Tornado__factory.connect(instanceAddress, provider); - const echoService = new NodeEchoService({ - netId, - provider, - Echoer: Echoer__factory.connect(echoContract, provider), - deployedBlock: NOTE_ACCOUNT_BLOCK, - fetchDataOptions, - tovarishClient, - cacheDirectory: EVENTS_DIR, - userDirectory: SAVED_DIR, - }); - - await echoService.updateEvents(); - - const encryptedNotesService = new NodeEncryptedNotesService({ - netId, - provider, - Router: TornadoRouter__factory.connect(routerContract, provider), - deployedBlock: ENCRYPTED_NOTES_BLOCK, - fetchDataOptions, - tovarishClient, - cacheDirectory: EVENTS_DIR, - userDirectory: SAVED_DIR, - }); - - await encryptedNotesService.updateEvents(); - - const currencies = currencyOpts ? [currencyOpts.toLowerCase()] : getActiveTokens(config); - - for (const currency of currencies) { - const currencyConfig = tokens[currency]; - // Now load the denominations and address - const instance = Object.entries(currencyConfig.instanceAddress); - - // And now sync - for (const [amount, instanceAddress] of instance) { - const Tornado = Tornado__factory.connect(instanceAddress, provider); - - const TornadoServiceConstructor = { + const TornadoServiceConstructor = { netId, provider, Tornado, @@ -1482,867 +1354,1224 @@ export function tornadoProgram() { cacheDirectory: EVENTS_DIR, userDirectory: SAVED_DIR, nativeCurrency, - }; + }; - const depositsService = new NodeTornadoService({ + const depositsService = new NodeTornadoService({ ...TornadoServiceConstructor, type: 'Deposit', merkleTreeService: new MerkleTreeService({ - netId, - amount, - currency, - Tornado, - merkleWorkerPath, - }), - treeCache: new TreeCache({ - netId, - amount, - currency, - userDirectory: SAVED_TREE_DIR, + netId, + amount, + currency, + Tornado, + merkleWorkerPath, }), optionalTree: true, - }); - - const withdrawalsService = new NodeTornadoService({ - ...TornadoServiceConstructor, - type: 'Withdrawal', - }); - - await depositsService.updateEvents(); - - await withdrawalsService.updateEvents(); - } - } - } - - process.exit(0); - }, - ); - - program - .command('relayers') - .description('List all registered relayers from the tornado cash registry.\n\n') - .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) - .action(async (netIdOpts: NetIdType, cmdOptions: commonProgramOptions) => { - const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); - - const allRelayers = options.disableTovarish - ? await getProgramRelayer({ - options, - fetchDataOptions, - netId: netIdOpts, - }) - : await getTovarishRelayer({ - options, - fetchDataOptions, - netId: netIdOpts, - }); - - const validRelayers = allRelayers.validRelayers as RelayerInfo[]; - const invalidRelayers = allRelayers.invalidRelayers as RelayerError[]; - - const relayersTable = new Table(); - const relayerName = options.disableTovarish ? 'Relayers' : 'Tovarish Relayers'; - - relayersTable.push( - [{ colSpan: 8, content: relayerName, hAlign: 'center' }], - [ - 'netId', - 'url', - 'ensName', - 'stakeBalance', - 'relayerAddress', - 'rewardAccount', - 'currentQueue', - 'serviceFee', - ].map((content) => ({ content: colors.red.bold(content) })), - ...validRelayers.map( - ({ netId, url, ensName, stakeBalance, relayerAddress, rewardAccount, currentQueue, tornadoServiceFee }) => { - return [ - netId, - url, - ensName, - stakeBalance ? `${Number(stakeBalance).toFixed(5)} TORN` : '', - relayerAddress, - rewardAccount, - currentQueue, - `${tornadoServiceFee}%`, - ]; - }, - ), - ); - - const invalidRelayersTable = new Table(); - - invalidRelayersTable.push( - [{ colSpan: 3, content: `Invalid ${relayerName}`, hAlign: 'center' }], - ['hostname', 'relayerAddress', 'errorMessage'].map((content) => ({ content: colors.red.bold(content) })), - ...invalidRelayers.map(({ hostname, relayerAddress, errorMessage }) => { - return [hostname, relayerAddress, errorMessage ? substring(errorMessage, 40) : '']; - }), - ); - - console.log(relayersTable.toString() + '\n'); - console.log(invalidRelayersTable.toString() + '\n'); - - process.exit(0); - }); - - program - .command('createAccount') - .description( - 'Creates and save on-chain account that would store encrypted notes. \n\n' + - 'Would first lookup on on-chain records to see if the notes are stored. \n\n' + - 'Requires a valid signable wallet (mnemonic or a private key) to work (Since they would encrypt or encrypted)', - ) - .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) - .action(async (netId: NetIdType, cmdOptions: commonProgramOptions) => { - const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); - const { rpc, disableTovarish } = options; - - const config = getConfig(netId); - - const { - echoContract, - constants: { NOTE_ACCOUNT_BLOCK }, - } = config; - - const provider = await getProgramProvider(rpc, { - netId, - ...fetchDataOptions, - }); - - const signer = getProgramSigner({ - options, - provider, - }); - - if (!signer || signer instanceof VoidSigner) { - throw new Error( - 'No wallet found, make your you have supplied a valid mnemonic or private key before using this command', - ); - } - - const tovarishClient = !disableTovarish - ? (await getTovarishRelayer({ options, fetchDataOptions, netId })).relayerClient - : undefined; - - if (tovarishClient?.selectedRelayer) { - console.log(`\nConnected with Tovarish Relayer ${tovarishClient.selectedRelayer.url}\n`); - } - - /** - * Find for any existing note accounts - */ - const Echoer = Echoer__factory.connect(echoContract, provider); - - const echoService = new NodeEchoService({ - netId, - provider, - Echoer, - deployedBlock: NOTE_ACCOUNT_BLOCK, - fetchDataOptions, - tovarishClient, - cacheDirectory: EVENTS_DIR, - userDirectory: SAVED_DIR, - }); - - console.log('Getting historic note accounts would take a while\n'); - - const echoEvents = (await echoService.updateEvents()).events; - - const existingAccounts = await NoteAccount.decryptSignerNoteAccounts(signer, echoEvents); - - const accountsTable = new Table(); - - if (existingAccounts.length) { - accountsTable.push( - [{ colSpan: 2, content: `Note Accounts (${netId})`, hAlign: 'center' }], - [{ colSpan: 2, content: `Backed up by: ${signer.address}`, hAlign: 'center' }], - ['blockNumber', 'noteAccount'].map((content) => ({ content: colors.red.bold(content) })), - ...existingAccounts.map(({ blockNumber, recoveryKey }) => { - return [blockNumber, recoveryKey]; - }), - ); - - console.log(accountsTable.toString() + '\n'); - } else { - const newAccount = new NoteAccount({}); - - accountsTable.push( - [{ colSpan: 1, content: `New Note Account (${netId})`, hAlign: 'center' }], - ['noteAccount'].map((content) => ({ content: colors.red.bold(content) })), - [newAccount.recoveryKey], - [{ colSpan: 1, content: `Would be backed up by: ${signer.address}`, hAlign: 'center' }], - ); - - const fileName = `backup-note-account-key-0x${newAccount.recoveryKey.slice(0, 8)}.txt`; - - console.log('\n' + accountsTable.toString() + '\n'); - - console.log(`Writing backup to ${fileName}\n`); - - await writeFile(fileName, newAccount.recoveryKey + '\n'); - - console.log('Backup encrypted account on-chain to use on UI?\n'); - - await promptConfirmation(options.nonInteractive); - - const signerPublicKey = await NoteAccount.getSignerPublicKey(signer); - - const { data } = newAccount.getEncryptedAccount(signerPublicKey); - - console.log('Sending encrypted note account backup transaction through wallet\n'); - - await programSendTransaction({ - signer: signer as TornadoVoidSigner | TornadoWallet, - options, - populatedTransaction: await Echoer.echo.populateTransaction(data), - }); - } - - process.exit(0); - }); - - program - .command('decrypt') - .description('Fetch encryption keys and encrypted notes from on-chain. \n\n') - .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) - .argument( - '[accountKey]', - 'Account key generated from UI or the createAccount to store encrypted notes on-chain', - parseRecoveryKey, - ) - .action(async (netId: NetIdType, accountKey: string | undefined, cmdOptions: commonProgramOptions) => { - const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); - const { rpc, disableTovarish } = options; - if (!accountKey) { - accountKey = options.accountKey; - } - - const config = getConfig(netId); - - const { - routerContract, - echoContract, - constants: { NOTE_ACCOUNT_BLOCK, ENCRYPTED_NOTES_BLOCK }, - } = config; - - const provider = await getProgramProvider(rpc, { - netId, - ...fetchDataOptions, - }); - - const tovarishClient = !disableTovarish - ? (await getTovarishRelayer({ options, fetchDataOptions, netId })).relayerClient - : undefined; - - if (tovarishClient?.selectedRelayer) { - console.log(`\nConnected with Tovarish Relayer ${tovarishClient.selectedRelayer.url}\n`); - } - - const Echoer = Echoer__factory.connect(echoContract, provider); - - const echoService = new NodeEchoService({ - netId, - provider, - Echoer, - deployedBlock: NOTE_ACCOUNT_BLOCK, - fetchDataOptions, - tovarishClient, - cacheDirectory: EVENTS_DIR, - userDirectory: SAVED_DIR, - }); - - const encryptedNotesService = new NodeEncryptedNotesService({ - netId, - provider, - Router: TornadoRouter__factory.connect(routerContract, provider), - deployedBlock: ENCRYPTED_NOTES_BLOCK, - fetchDataOptions, - tovarishClient, - cacheDirectory: EVENTS_DIR, - userDirectory: SAVED_DIR, - }); - - const accounts = []; - - if (accountKey) { - accounts.push(new NoteAccount({ recoveryKey: accountKey })); - } - - // Recover encryption keys possibly encrypted by a signer - const signer = getProgramSigner({ - options, - provider, - }) as TornadoWallet; - - if (signer?.privateKey) { - const echoEvents = (await echoService.updateEvents()).events; - - accounts.push(...(await NoteAccount.decryptSignerNoteAccounts(signer, echoEvents))); - } - - if (!accounts.length) { - throw new Error( - 'No encryption key find! Please supply encryption key from either UI or create one with createAccount command', - ); - } - - const accountsTable = new Table(); - - accountsTable.push( - [{ colSpan: 2, content: `Note Accounts (${netId})`, hAlign: 'center' }], - [{ colSpan: 2, content: `Backed up by: ${signer?.address}`, hAlign: 'center' }], - ['blockNumber', 'noteAccount'].map((content) => ({ content: colors.red.bold(content) })), - ...accounts.map(({ blockNumber, recoveryKey }) => { - return [blockNumber, recoveryKey]; - }), - ); - - // Decrypting notes - const encryptedNoteEvents = (await encryptedNotesService.updateEvents()).events; - - const decryptedNotes = accounts - .map((noteAccount) => noteAccount.decryptNotes(encryptedNoteEvents)) - .flat() - .map(({ blockNumber, address, noteHex }) => { - const { amount, currency } = getInstanceByAddress(config, address) || {}; - - return [blockNumber, `tornado-${currency}-${amount}-${netId}-${noteHex}`]; - }); - - const notesTable = new Table(); - - notesTable.push( - [{ colSpan: 2, content: `Note Accounts (${netId})`, hAlign: 'center' }], - [{ colSpan: 2, content: `Account key: ${accountKey}`, hAlign: 'center' }], - ['blockNumber', 'note'].map((content) => ({ content: colors.red.bold(content) })), - ...decryptedNotes, - ); - - console.log(accountsTable.toString() + '\n'); - - console.log('\n' + notesTable.toString() + '\n'); - - process.exit(0); - }); - - program - .command('send') - .description('Send ETH or ERC20 token to address.\n\n') - .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) - .argument('', 'To address', parseAddress) - .argument('[amount]', 'Sending amounts', parseNumber) - .argument('[token]', 'ERC20 Token Contract to check Token Balance', parseAddress) - .action( - async ( - netId: NetIdType, - to: string, - amountArgs: number | undefined, - tokenArgs: string | undefined, - cmdOptions: commonProgramOptions, - ) => { - const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); - const { rpc, token: tokenOpts } = options; - - const config = getConfig(netId); - - const { currencyName, multicallContract } = config; - - const provider = await getProgramProvider(rpc, { - netId, - ...fetchDataOptions, - }); - - const signer = getProgramSigner({ options, provider }); - - if (!signer) { - throw new Error( - 'Signer not defined, make sure you have either viewOnly address, mnemonic, or private key configured', - ); - } - - const tokenAddress = tokenArgs ? parseAddress(tokenArgs) : tokenOpts; - - const Multicall = Multicall__factory.connect(multicallContract, provider); - const Token = (tokenAddress ? ERC20__factory.connect(tokenAddress, signer) : undefined) as ERC20; - - // Fetching feeData or nonce is unnecessary however we do this to estimate transfer amounts - const [feeData, nonce, [{ balance: ethBalance }, tokenResults]] = await Promise.all([ - provider.getFeeData(), - provider.getTransactionCount(signer.address, 'pending'), - getTokenBalances({ - provider, - Multicall, - currencyName, - userAddress: signer.address, - tokenAddresses: tokenAddress ? [tokenAddress] : [], - }), - ]); - - const { - symbol: tokenSymbol, - decimals: tokenDecimals, - balance: tokenBalance, - }: tokenBalances = tokenResults || {}; - - const txType = feeData.maxFeePerGas ? 2 : 0; - const txGasPrice = feeData.maxFeePerGas - ? feeData.maxFeePerGas + (feeData.maxPriorityFeePerGas || BigInt(0)) - : feeData.gasPrice || BigInt(0); - const txFees = feeData.maxFeePerGas - ? { - maxFeePerGas: feeData.maxFeePerGas, - maxPriorityFeePerGas: feeData.maxPriorityFeePerGas, - } - : { - gasPrice: feeData.gasPrice, - }; - - let toSend: bigint; - - if (amountArgs) { - if (tokenAddress) { - toSend = parseUnits(`${amountArgs}`, tokenDecimals); - - if (toSend > tokenBalance) { - const errMsg = `Invalid ${tokenSymbol} balance, wants ${amountArgs} have ${formatUnits(tokenBalance, tokenDecimals)}`; - throw new Error(errMsg); - } - } else { - toSend = parseEther(`${amountArgs}`); - - if (toSend > ethBalance) { - const errMsg = `Invalid ${currencyName} balance, wants ${amountArgs} have ${formatEther(ethBalance)}`; - throw new Error(errMsg); - } - } - } else { - if (tokenAddress) { - toSend = tokenBalance; - } else { - const initCost = txGasPrice * BigInt('400000'); - toSend = ethBalance - initCost; - - if (ethBalance === BigInt(0) || ethBalance < initCost) { - const errMsg = `Invalid ${currencyName} balance, wants ${formatEther(initCost)} have ${formatEther(ethBalance)}`; - throw new Error(errMsg); - } - - const estimatedGas = await provider.estimateGas({ - type: txType, - from: signer.address, - to, - value: toSend, - nonce, - ...txFees, }); - const bumpedGas = - estimatedGas !== BigInt(21000) && signer.gasLimitBump - ? (estimatedGas * (BigInt(10000) + BigInt(signer.gasLimitBump))) / BigInt(10000) - : estimatedGas; + const withdrawalsService = new NodeTornadoService({ + ...TornadoServiceConstructor, + type: 'Withdrawal', + }); - toSend = ethBalance - txGasPrice * bumpedGas; - } - } + const depositEvents = (await depositsService.updateEvents()).events as DepositsEvents[]; - await programSendTransaction({ - signer, - options, - populatedTransaction: tokenAddress - ? await Token.transfer.populateTransaction(to, toSend) - : await signer.populateTransaction({ - type: txType, - from: signer.address, - to, - value: toSend, - nonce, - ...txFees, - }), + const withdrawalEvents = (await withdrawalsService.updateEvents()).events as WithdrawalsEvents[]; + + const depositEvent = depositEvents.find(({ commitment }) => commitment === commitmentHex); + + const withdrawalEvent = withdrawalEvents.find(({ nullifierHash }) => nullifierHash === nullifierHex); + + const complianceTable = new Table(); + complianceTable.push([{ colSpan: 2, content: 'Compliance Info', hAlign: 'center' }]); + + if (!depositEvent) { + complianceTable.push([{ colSpan: 2, content: 'Deposit', hAlign: 'center' }], ['Deposit', 'Not Found']); + } else { + const depositDate = new Date(depositEvent.timestamp * 1000); + + complianceTable.push( + [{ colSpan: 2, content: 'Deposit', hAlign: 'center' }], + ['Deposit', `${amount} ${currency.toUpperCase()}`], + [ + 'Date', + `${depositDate.toLocaleDateString()} ${depositDate.toLocaleTimeString()} (${moment.unix(depositEvent.timestamp).fromNow()})`, + ], + ['From', depositEvent.from], + ['Transaction', depositEvent.transactionHash], + ['Commitment', commitmentHex], + ['Spent', Boolean(withdrawalEvent)], + ); + } + + if (withdrawalEvent) { + const withdrawalDate = new Date(withdrawalEvent.timestamp * 1000); + + complianceTable.push( + [{ colSpan: 2, content: 'Withdraw', hAlign: 'center' }], + ['Withdrawal', `${amount} ${currency.toUpperCase()}`], + ['Relayer Fee', `${formatUnits(withdrawalEvent.fee, decimals)} ${currency.toUpperCase()}`], + [ + 'Date', + `${withdrawalDate.toLocaleDateString()} ${withdrawalDate.toLocaleTimeString()} (${moment.unix(withdrawalEvent.timestamp).fromNow()})`, + ], + ['To', withdrawalEvent.to], + ['Transaction', withdrawalEvent.transactionHash], + ['Nullifier', nullifierHex], + ); + } + + console.log('\n\n' + complianceTable.toString() + '\n'); + + process.exit(0); }); - process.exit(0); - }, - ); + program + .command('updateEvents') + .description('Sync the local cache file of tornado cash events.\n\n') + .argument('[netId]', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) + .argument('[currency]', 'Currency to sync events') + .action( + async ( + netIdOpts: NetIdType | undefined, + currencyOpts: string | undefined, + cmdOptions: commonProgramOptions, + ) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); + const { rpc, disableTovarish } = options; - program - .command('balance') - .description('Check ETH and ERC20 balance.\n\n') - .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) - .argument('[address]', 'ETH Address to check balance', parseAddress) - .argument('[token]', 'ERC20 Token Contract to check Token Balance', parseAddress) - .action( - async ( - netId: NetIdType, - addressArgs: string | undefined, - tokenArgs: string | undefined, - cmdOptions: commonProgramOptions, - ) => { - const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); - const { rpc, token: tokenOpts } = options; + const networks = netIdOpts ? [netIdOpts] : enabledChains; - const config = getConfig(netId); + for (const netId of networks) { + const config = getConfig(netId); + const { + tokens, + nativeCurrency, + routerContract, + echoContract, + registryContract, + reverseRecordsContract, + aggregatorContract, + governanceContract, + deployedBlock, + constants: { GOVERNANCE_BLOCK, REGISTRY_BLOCK, NOTE_ACCOUNT_BLOCK, ENCRYPTED_NOTES_BLOCK }, + } = config; - const { currencyName, multicallContract, tornContract, tokens } = config; + const provider = await getProgramProvider(rpc, { + netId, + ...fetchDataOptions, + }); - const provider = await getProgramProvider(rpc, { - netId, - ...fetchDataOptions, - }); + const tovarishClient = !disableTovarish + ? ( + await getTovarishRelayer({ + options, + fetchDataOptions, + netId, + }) + ).relayerClient + : undefined; - const userAddress = addressArgs ? parseAddress(addressArgs) : getProgramSigner({ options, provider })?.address; - const tokenAddress = tokenArgs ? parseAddress(tokenArgs) : tokenOpts; + if (tovarishClient?.selectedRelayer) { + console.log(`\nConnected with Tovarish Relayer ${tovarishClient.selectedRelayer.url}\n`); + } - if (!userAddress) { - throw new Error('Address is required however no user address is supplied'); - } + if ( + netId === RELAYER_NETWORK && + governanceContract && + aggregatorContract && + reverseRecordsContract + ) { + const governanceService = new NodeGovernanceService({ + netId, + provider, + Governance: Governance__factory.connect(governanceContract, provider), + Aggregator: Aggregator__factory.connect(aggregatorContract, provider), + ReverseRecords: ReverseRecords__factory.connect(reverseRecordsContract, provider), + deployedBlock: GOVERNANCE_BLOCK, + fetchDataOptions, + tovarishClient, + cacheDirectory: EVENTS_DIR, + userDirectory: SAVED_DIR, + }); - const Multicall = Multicall__factory.connect(multicallContract, provider); + await governanceService.updateEvents(); + } - const tokenAddresses = Object.values(tokens) - .map(({ tokenAddress }) => tokenAddress) - .filter((t) => t) as string[]; + if (netId === RELAYER_NETWORK && registryContract && aggregatorContract) { + const registryService = new NodeRegistryService({ + netId, + provider, + RelayerRegistry: RelayerRegistry__factory.connect(registryContract, provider), + Aggregator: Aggregator__factory.connect(aggregatorContract, provider), + relayerEnsSubdomains: getRelayerEnsSubdomains(), + deployedBlock: REGISTRY_BLOCK, + fetchDataOptions, + // Exclude tovarish relayer from updating registry events and use RPC directly + // tovarishClient, + cacheDirectory: EVENTS_DIR, + userDirectory: SAVED_DIR, + }); - if (tornContract) { - tokenAddresses.push(tornContract); - } + await registryService.updateRelayers(); + } - const tokenBalances = await getTokenBalances({ - provider, - Multicall, - currencyName, - userAddress, - tokenAddresses: [...(tokenAddress ? [tokenAddress] : tokenAddresses)], - }); + const echoService = new NodeEchoService({ + netId, + provider, + Echoer: Echoer__factory.connect(echoContract, provider), + deployedBlock: NOTE_ACCOUNT_BLOCK, + fetchDataOptions, + tovarishClient, + cacheDirectory: EVENTS_DIR, + userDirectory: SAVED_DIR, + }); - const balanceTable = new Table({ head: ['Token', 'Contract Address', 'Balance'] }); + await echoService.updateEvents(); - balanceTable.push( - [{ colSpan: 3, content: `User: ${userAddress}`, hAlign: 'center' }], - ...tokenBalances.map(({ address, name, symbol, decimals, balance }) => { - return [`${name} (${symbol})`, address, `${formatUnits(balance, decimals)} ${symbol}`]; - }), - ); + const encryptedNotesService = new NodeEncryptedNotesService({ + netId, + provider, + Router: TornadoRouter__factory.connect(routerContract, provider), + deployedBlock: ENCRYPTED_NOTES_BLOCK, + fetchDataOptions, + tovarishClient, + cacheDirectory: EVENTS_DIR, + userDirectory: SAVED_DIR, + }); - console.log(balanceTable.toString()); + await encryptedNotesService.updateEvents(); - process.exit(0); - }, - ); + const currencies = currencyOpts ? [currencyOpts.toLowerCase()] : getActiveTokens(config); - program - .command('sign') - .description('Sign unsigned transaction with signer.\n\n') - .argument('', 'Unsigned Transaction') - .action(async (unsignedTx: string, cmdOptions: commonProgramOptions) => { - const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); - const { rpc } = options; + for (const currency of currencies) { + const currencyConfig = tokens[currency]; + // Now load the denominations and address + const instance = Object.entries(currencyConfig.instanceAddress); - const deserializedTx = Transaction.from(unsignedTx).toJSON(); + // And now sync + for (const [amount, instanceAddress] of instance) { + const Tornado = Tornado__factory.connect(instanceAddress, provider); - const netId = Number(deserializedTx.chainId); + const TornadoServiceConstructor = { + netId, + provider, + Tornado, + amount, + currency, + deployedBlock, + fetchDataOptions, + tovarishClient, + cacheDirectory: EVENTS_DIR, + userDirectory: SAVED_DIR, + nativeCurrency, + }; - const provider = await getProgramProvider(rpc, { - netId, - ...fetchDataOptions, - }); + const depositsService = new NodeTornadoService({ + ...TornadoServiceConstructor, + type: 'Deposit', + merkleTreeService: new MerkleTreeService({ + netId, + amount, + currency, + Tornado, + merkleWorkerPath, + }), + treeCache: new TreeCache({ + netId, + amount, + currency, + userDirectory: SAVED_TREE_DIR, + }), + optionalTree: true, + }); - const signer = getProgramSigner({ options, provider }); + const withdrawalsService = new NodeTornadoService({ + ...TornadoServiceConstructor, + type: 'Withdrawal', + }); - if (!signer || signer instanceof VoidSigner) { - throw new Error('Signer not defined or not signable signer'); - } + await depositsService.updateEvents(); - await programSendTransaction({ - signer, - options, - populatedTransaction: deserializedTx, - }); + await withdrawalsService.updateEvents(); + } + } + } - process.exit(0); - }); - - program - .command('broadcast') - .description('Broadcast signed transaction.\n\n') - .argument('', 'Signed Transaction') - .action(async (signedTx: string, cmdOptions: commonProgramOptions) => { - const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); - const { rpc } = options; - - const netId = Number(Transaction.from(signedTx).chainId); - - if (!netId) { - throw new Error('NetId for the transaction is invalid, this command only supports EIP-155 transactions'); - } - - const provider = await getProgramProvider(rpc, { - netId, - ...fetchDataOptions, - }); - - const { hash } = await provider.broadcastTransaction(signedTx); - - console.log(`\nBroadcastd tx: ${hash}\n`); - - process.exit(0); - }); - - program - .command('proposals') - .description('Get list or detail about TORN proposal') - .argument('[proposalId]', 'Proposal ID') - .action(async (proposalId: number, cmdOptions: commonProgramOptions) => { - const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); - const { rpc, disableTovarish } = options; - const netId = RELAYER_NETWORK; - - const provider = await getProgramProvider(rpc, { - netId, - ...fetchDataOptions, - }); - - const config = getConfig(netId); - - const { - governanceContract, - aggregatorContract, - reverseRecordsContract, - constants: { GOVERNANCE_BLOCK }, - } = config; - - const tovarishClient = !disableTovarish - ? (await getTovarishRelayer({ options, fetchDataOptions, netId })).relayerClient - : undefined; - - if (tovarishClient?.selectedRelayer) { - console.log(`\nConnected with Tovarish Relayer ${tovarishClient.selectedRelayer.url}\n`); - } - - const governanceService = new NodeGovernanceService({ - netId, - provider, - Governance: Governance__factory.connect(governanceContract as string, provider), - Aggregator: Aggregator__factory.connect(aggregatorContract as string, provider), - ReverseRecords: ReverseRecords__factory.connect(reverseRecordsContract as string, provider), - deployedBlock: GOVERNANCE_BLOCK, - fetchDataOptions, - tovarishClient, - cacheDirectory: EVENTS_DIR, - userDirectory: SAVED_DIR, - }); - - const proposals = await governanceService.getAllProposals(); - - const proposal = proposals.find((p) => p.id === Number(proposalId)); - - if (!proposal) { - const recentProposals = proposals.reverse().slice(0, 20); - - const proposalTable = new Table(); - - proposalTable.push( - [{ colSpan: 9, content: 'Last 20 Proposals', hAlign: 'center' }], - ['ID', 'Title', 'Proposer', 'Start Time', 'End Time', 'Quorum', 'For Votes', 'Against Votes', 'State'], - ...recentProposals.map( - ({ id, title, proposer, proposerName, startTime, endTime, quorum, forVotes, againstVotes, state }) => { - return [ - id, - title, - proposerName || proposer.slice(0, 20), - moment.unix(startTime).format('DD/MM/YYYY'), - moment.unix(endTime).format('DD/MM/YYYY'), - quorum, - numberFormatter(formatEther(forVotes)) + ' TORN', - numberFormatter(formatEther(againstVotes)) + ' TORN', - state, - ]; + process.exit(0); }, - ), ); - console.log(proposalTable.toString()); + program + .command('relayers') + .description('List all registered relayers from the tornado cash registry.\n\n') + .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) + .action(async (netIdOpts: NetIdType, cmdOptions: commonProgramOptions) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); - process.exit(0); - } + const allRelayers = options.disableTovarish + ? await getProgramRelayer({ + options, + fetchDataOptions, + netId: netIdOpts, + }) + : await getTovarishRelayer({ + options, + fetchDataOptions, + netId: netIdOpts, + }); - proposalId = Number(proposalId); + const validRelayers = allRelayers.validRelayers as RelayerInfo[]; + const invalidRelayers = allRelayers.invalidRelayers as RelayerError[]; - const { - transactionHash, - proposer, - proposerName, - target, - startTime, - endTime, - description, - title, - forVotes, - againstVotes, - executed, - extended, - quorum, - state, - } = proposal; + const relayersTable = new Table(); + const relayerName = options.disableTovarish ? 'Relayers' : 'Tovarish Relayers'; - const allVotes = forVotes + againstVotes; + relayersTable.push( + [{ colSpan: 8, content: relayerName, hAlign: 'center' }], + [ + 'netId', + 'url', + 'ensName', + 'stakeBalance', + 'relayerAddress', + 'rewardAccount', + 'currentQueue', + 'serviceFee', + ].map((content) => ({ content: colors.red.bold(content) })), + ...validRelayers.map( + ({ + netId, + url, + ensName, + stakeBalance, + relayerAddress, + rewardAccount, + currentQueue, + tornadoServiceFee, + }) => { + return [ + netId, + url, + ensName, + stakeBalance ? `${Number(stakeBalance).toFixed(5)} TORN` : '', + relayerAddress, + rewardAccount, + currentQueue, + `${tornadoServiceFee}%`, + ]; + }, + ), + ); - const proposalTable = new Table(); + const invalidRelayersTable = new Table(); - proposalTable.push( - [{ colSpan: 2, content: `Proposal ${proposalId}`, hAlign: 'center' }], - ['Title', title], - ['Description', description], - ['Proposer', proposerName || proposer], - ['Proposal Address', target], - ['Proposal TX', transactionHash], - ['Start Time', String(moment.unix(startTime).toDate())], - ['End Time', String(moment.unix(endTime).toDate())], - ['For', numberFormatter(formatEther(forVotes)) + ' TORN'], - ['Against', numberFormatter(formatEther(againstVotes)) + ' TORN'], - ['Quorum', quorum], - ['State', state], - ['Extended', extended], - ['Executed', executed], - ); + invalidRelayersTable.push( + [ + { + colSpan: 3, + content: `Invalid ${relayerName}`, + hAlign: 'center', + }, + ], + ['hostname', 'relayerAddress', 'errorMessage'].map((content) => ({ + content: colors.red.bold(content), + })), + ...invalidRelayers.map(({ hostname, relayerAddress, errorMessage }) => { + return [hostname, relayerAddress, errorMessage ? substring(errorMessage, 40) : '']; + }), + ); - console.log(proposalTable.toString()); + console.log(relayersTable.toString() + '\n'); + console.log(invalidRelayersTable.toString() + '\n'); - const votes = await governanceService.getVotes(proposalId); + process.exit(0); + }); - const votersSet = new Set(); - const uniqueVotes = votes - .reverse() - .filter((v) => { - if (!votersSet.has(v.voter)) { - votersSet.add(v.voter); - return true; - } - return false; - }) - .sort((a, b) => Number(b.votes) - Number(a.votes)); + program + .command('createAccount') + .description( + 'Creates and save on-chain account that would store encrypted notes. \n\n' + + 'Would first lookup on on-chain records to see if the notes are stored. \n\n' + + 'Requires a valid signable wallet (mnemonic or a private key) to work (Since they would encrypt or encrypted)', + ) + .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) + .action(async (netId: NetIdType, cmdOptions: commonProgramOptions) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); + const { rpc, disableTovarish } = options; - const commentTable = new Table(); + const config = getConfig(netId); - commentTable.push( - [{ colSpan: 5, content: 'Votes', hAlign: 'center' }], - ['Support', 'Votes', 'Voter', 'Delegate', 'Percent'], - ...uniqueVotes.map(({ support, votes, voter, voterName, from, fromName }) => { - const supportStr = support ? 'For' : 'Against'; - const votesStr = numberFormatter(formatEther(votes)) + ' TORN'; - const delegate = voter !== from ? fromName || from.slice(0, 20) : ''; - const percentage = ((Number(votes) / Number(allVotes)) * 100).toFixed(2) + '%'; + const { + echoContract, + constants: { NOTE_ACCOUNT_BLOCK }, + } = config; - return [supportStr, votesStr, voterName || voter.slice(0, 20), delegate, percentage]; - }), - ); + const provider = await getProgramProvider(rpc, { + netId, + ...fetchDataOptions, + }); - console.log(commentTable.toString()); + const signer = getProgramSigner({ + options, + provider, + }); - process.exit(0); + if (!signer || signer instanceof VoidSigner) { + throw new Error( + 'No wallet found, make your you have supplied a valid mnemonic or private key before using this command', + ); + } + + const tovarishClient = !disableTovarish + ? ( + await getTovarishRelayer({ + options, + fetchDataOptions, + netId, + }) + ).relayerClient + : undefined; + + if (tovarishClient?.selectedRelayer) { + console.log(`\nConnected with Tovarish Relayer ${tovarishClient.selectedRelayer.url}\n`); + } + + /** + * Find for any existing note accounts + */ + const Echoer = Echoer__factory.connect(echoContract, provider); + + const echoService = new NodeEchoService({ + netId, + provider, + Echoer, + deployedBlock: NOTE_ACCOUNT_BLOCK, + fetchDataOptions, + tovarishClient, + cacheDirectory: EVENTS_DIR, + userDirectory: SAVED_DIR, + }); + + console.log('Getting historic note accounts would take a while\n'); + + const echoEvents = (await echoService.updateEvents()).events; + + const existingAccounts = await NoteAccount.decryptSignerNoteAccounts(signer, echoEvents); + + const accountsTable = new Table(); + + if (existingAccounts.length) { + accountsTable.push( + [ + { + colSpan: 2, + content: `Note Accounts (${netId})`, + hAlign: 'center', + }, + ], + [ + { + colSpan: 2, + content: `Backed up by: ${signer.address}`, + hAlign: 'center', + }, + ], + ['blockNumber', 'noteAccount'].map((content) => ({ + content: colors.red.bold(content), + })), + ...existingAccounts.map(({ blockNumber, recoveryKey }) => { + return [blockNumber, recoveryKey]; + }), + ); + + console.log(accountsTable.toString() + '\n'); + } else { + const newAccount = new NoteAccount({}); + + accountsTable.push( + [ + { + colSpan: 1, + content: `New Note Account (${netId})`, + hAlign: 'center', + }, + ], + ['noteAccount'].map((content) => ({ + content: colors.red.bold(content), + })), + [newAccount.recoveryKey], + [ + { + colSpan: 1, + content: `Would be backed up by: ${signer.address}`, + hAlign: 'center', + }, + ], + ); + + const fileName = `backup-note-account-key-0x${newAccount.recoveryKey.slice(0, 8)}.txt`; + + console.log('\n' + accountsTable.toString() + '\n'); + + console.log(`Writing backup to ${fileName}\n`); + + await writeFile(fileName, newAccount.recoveryKey + '\n'); + + console.log('Backup encrypted account on-chain to use on UI?\n'); + + await promptConfirmation(options.nonInteractive); + + const signerPublicKey = await NoteAccount.getSignerPublicKey(signer); + + const { data } = newAccount.getEncryptedAccount(signerPublicKey); + + console.log('Sending encrypted note account backup transaction through wallet\n'); + + await programSendTransaction({ + signer: signer as TornadoVoidSigner | TornadoWallet, + options, + populatedTransaction: await Echoer.echo.populateTransaction(data), + }); + } + + process.exit(0); + }); + + program + .command('decrypt') + .description('Fetch encryption keys and encrypted notes from on-chain. \n\n') + .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) + .argument( + '[accountKey]', + 'Account key generated from UI or the createAccount to store encrypted notes on-chain', + parseRecoveryKey, + ) + .action(async (netId: NetIdType, accountKey: string | undefined, cmdOptions: commonProgramOptions) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); + const { rpc, disableTovarish } = options; + if (!accountKey) { + accountKey = options.accountKey; + } + + const config = getConfig(netId); + + const { + routerContract, + echoContract, + constants: { NOTE_ACCOUNT_BLOCK, ENCRYPTED_NOTES_BLOCK }, + } = config; + + const provider = await getProgramProvider(rpc, { + netId, + ...fetchDataOptions, + }); + + const tovarishClient = !disableTovarish + ? ( + await getTovarishRelayer({ + options, + fetchDataOptions, + netId, + }) + ).relayerClient + : undefined; + + if (tovarishClient?.selectedRelayer) { + console.log(`\nConnected with Tovarish Relayer ${tovarishClient.selectedRelayer.url}\n`); + } + + const Echoer = Echoer__factory.connect(echoContract, provider); + + const echoService = new NodeEchoService({ + netId, + provider, + Echoer, + deployedBlock: NOTE_ACCOUNT_BLOCK, + fetchDataOptions, + tovarishClient, + cacheDirectory: EVENTS_DIR, + userDirectory: SAVED_DIR, + }); + + const encryptedNotesService = new NodeEncryptedNotesService({ + netId, + provider, + Router: TornadoRouter__factory.connect(routerContract, provider), + deployedBlock: ENCRYPTED_NOTES_BLOCK, + fetchDataOptions, + tovarishClient, + cacheDirectory: EVENTS_DIR, + userDirectory: SAVED_DIR, + }); + + const accounts = []; + + // Recover encryption keys possibly encrypted by a signer + const signer = getProgramSigner({ + options, + provider, + }) as TornadoWallet; + + if (signer?.privateKey) { + const echoEvents = (await echoService.updateEvents()).events; + + accounts.push(...(await NoteAccount.decryptSignerNoteAccounts(signer, echoEvents))); + } + + if (accountKey && !accounts.find(({ recoveryKey }) => recoveryKey === accountKey)) { + accounts.push(new NoteAccount({ recoveryKey: accountKey })); + } + + if (!accounts.length) { + throw new Error( + 'No encryption key find! Please supply encryption key from either UI or create one with createAccount command', + ); + } + + const accountsTable = new Table(); + + accountsTable.push( + [ + { + colSpan: 2, + content: `Note Accounts (${netId})`, + hAlign: 'center', + }, + ], + [ + { + colSpan: 2, + content: `Backed up by: ${signer?.address}`, + hAlign: 'center', + }, + ], + ['blockNumber', 'noteAccount'].map((content) => ({ + content: colors.red.bold(content), + })), + ...accounts.map(({ blockNumber, recoveryKey }) => { + return [blockNumber, recoveryKey]; + }), + ); + + // Decrypting notes + const encryptedNoteEvents = (await encryptedNotesService.updateEvents()).events; + + const decryptedNotes = accounts + .map((noteAccount) => noteAccount.decryptNotes(encryptedNoteEvents)) + .flat() + .map(({ blockNumber, address, noteHex }) => { + const { amount, currency } = getInstanceByAddress(config, address) || {}; + + if (amount) { + return [blockNumber, `tornado-${currency}-${amount}-${netId}-${noteHex}`]; + } + + return [blockNumber, noteHex]; + }); + + const notesTable = new Table(); + + notesTable.push( + [ + { + colSpan: 2, + content: `Note Accounts (${netId})`, + hAlign: 'center', + }, + ], + [ + { + colSpan: 2, + content: `Account key: ${accountKey}`, + hAlign: 'center', + }, + ], + ['blockNumber', 'note'].map((content) => ({ + content: colors.red.bold(content), + })), + ...decryptedNotes, + ); + + console.log(accountsTable.toString() + '\n'); + + console.log('\n' + notesTable.toString() + '\n'); + + process.exit(0); + }); + + program + .command('send') + .description('Send ETH or ERC20 token to address.\n\n') + .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) + .argument('', 'To address', parseAddress) + .argument('[amount]', 'Sending amounts', parseNumber) + .argument('[token]', 'ERC20 Token Contract to check Token Balance', parseAddress) + .action( + async ( + netId: NetIdType, + to: string, + amountArgs: number | undefined, + tokenArgs: string | undefined, + cmdOptions: commonProgramOptions, + ) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); + const { rpc, token: tokenOpts } = options; + + const config = getConfig(netId); + + const { currencyName, multicallContract } = config; + + const provider = await getProgramProvider(rpc, { + netId, + ...fetchDataOptions, + }); + + const signer = getProgramSigner({ options, provider }); + + if (!signer) { + throw new Error( + 'Signer not defined, make sure you have either viewOnly address, mnemonic, or private key configured', + ); + } + + const tokenAddress = tokenArgs ? parseAddress(tokenArgs) : tokenOpts; + + const Multicall = Multicall__factory.connect(multicallContract, provider); + const Token = (tokenAddress ? ERC20__factory.connect(tokenAddress, signer) : undefined) as ERC20; + + // Fetching feeData or nonce is unnecessary however we do this to estimate transfer amounts + const [feeData, nonce, [{ balance: ethBalance }, tokenResults]] = await Promise.all([ + provider.getFeeData(), + provider.getTransactionCount(signer.address, 'pending'), + getTokenBalances({ + provider, + Multicall, + currencyName, + userAddress: signer.address, + tokenAddresses: tokenAddress ? [tokenAddress] : [], + }), + ]); + + const { + symbol: tokenSymbol, + decimals: tokenDecimals, + balance: tokenBalance, + }: tokenBalances = tokenResults || {}; + + const txType = feeData.maxFeePerGas ? 2 : 0; + const txGasPrice = feeData.maxFeePerGas + ? feeData.maxFeePerGas + (feeData.maxPriorityFeePerGas || BigInt(0)) + : feeData.gasPrice || BigInt(0); + const txFees = feeData.maxFeePerGas + ? { + maxFeePerGas: feeData.maxFeePerGas, + maxPriorityFeePerGas: feeData.maxPriorityFeePerGas, + } + : { + gasPrice: feeData.gasPrice, + }; + + let toSend: bigint; + + if (amountArgs) { + if (tokenAddress) { + toSend = parseUnits(`${amountArgs}`, tokenDecimals); + + if (toSend > tokenBalance) { + const errMsg = `Invalid ${tokenSymbol} balance, wants ${amountArgs} have ${formatUnits(tokenBalance, tokenDecimals)}`; + throw new Error(errMsg); + } + } else { + toSend = parseEther(`${amountArgs}`); + + if (toSend > ethBalance) { + const errMsg = `Invalid ${currencyName} balance, wants ${amountArgs} have ${formatEther(ethBalance)}`; + throw new Error(errMsg); + } + } + } else { + if (tokenAddress) { + toSend = tokenBalance; + } else { + const initCost = txGasPrice * BigInt('400000'); + toSend = ethBalance - initCost; + + if (ethBalance === BigInt(0) || ethBalance < initCost) { + const errMsg = `Invalid ${currencyName} balance, wants ${formatEther(initCost)} have ${formatEther(ethBalance)}`; + throw new Error(errMsg); + } + + const estimatedGas = await provider.estimateGas({ + type: txType, + from: signer.address, + to, + value: toSend, + nonce, + ...txFees, + }); + + const bumpedGas = + estimatedGas !== BigInt(21000) && signer.gasLimitBump + ? (estimatedGas * (BigInt(10000) + BigInt(signer.gasLimitBump))) / BigInt(10000) + : estimatedGas; + + toSend = ethBalance - txGasPrice * bumpedGas; + } + } + + await programSendTransaction({ + signer, + options, + populatedTransaction: tokenAddress + ? await Token.transfer.populateTransaction(to, toSend) + : await signer.populateTransaction({ + type: txType, + from: signer.address, + to, + value: toSend, + nonce, + ...txFees, + }), + }); + + process.exit(0); + }, + ); + + program + .command('balance') + .description('Check ETH and ERC20 balance.\n\n') + .argument('', 'Network Chain ID to connect with (see https://chainlist.org for examples)', parseNumber) + .argument('[address]', 'ETH Address to check balance', parseAddress) + .argument('[token]', 'ERC20 Token Contract to check Token Balance', parseAddress) + .action( + async ( + netId: NetIdType, + addressArgs: string | undefined, + tokenArgs: string | undefined, + cmdOptions: commonProgramOptions, + ) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); + const { rpc, token: tokenOpts } = options; + + const config = getConfig(netId); + + const { currencyName, multicallContract, tornContract, tokens } = config; + + const provider = await getProgramProvider(rpc, { + netId, + ...fetchDataOptions, + }); + + const userAddress = addressArgs + ? parseAddress(addressArgs) + : getProgramSigner({ options, provider })?.address; + const tokenAddress = tokenArgs ? parseAddress(tokenArgs) : tokenOpts; + + if (!userAddress) { + throw new Error('Address is required however no user address is supplied'); + } + + const Multicall = Multicall__factory.connect(multicallContract, provider); + + const tokenAddresses = Object.values(tokens) + .map(({ tokenAddress }) => tokenAddress) + .filter((t) => t) as string[]; + + if (tornContract) { + tokenAddresses.push(tornContract); + } + + const tokenBalances = await getTokenBalances({ + provider, + Multicall, + currencyName, + userAddress, + tokenAddresses: [...(tokenAddress ? [tokenAddress] : tokenAddresses)], + }); + + const balanceTable = new Table({ + head: ['Token', 'Contract Address', 'Balance'], + }); + + balanceTable.push( + [ + { + colSpan: 3, + content: `User: ${userAddress}`, + hAlign: 'center', + }, + ], + ...tokenBalances.map(({ address, name, symbol, decimals, balance }) => { + return [`${name} (${symbol})`, address, `${formatUnits(balance, decimals)} ${symbol}`]; + }), + ); + + console.log(balanceTable.toString()); + + process.exit(0); + }, + ); + + program + .command('sign') + .description('Sign unsigned transaction with signer.\n\n') + .argument('', 'Unsigned Transaction') + .action(async (unsignedTx: string, cmdOptions: commonProgramOptions) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); + const { rpc } = options; + + const deserializedTx = Transaction.from(unsignedTx).toJSON(); + + const netId = Number(deserializedTx.chainId); + + const provider = await getProgramProvider(rpc, { + netId, + ...fetchDataOptions, + }); + + const signer = getProgramSigner({ options, provider }); + + if (!signer || signer instanceof VoidSigner) { + throw new Error('Signer not defined or not signable signer'); + } + + await programSendTransaction({ + signer, + options, + populatedTransaction: deserializedTx, + }); + + process.exit(0); + }); + + program + .command('broadcast') + .description('Broadcast signed transaction.\n\n') + .argument('', 'Signed Transaction') + .action(async (signedTx: string, cmdOptions: commonProgramOptions) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); + const { rpc } = options; + + const netId = Number(Transaction.from(signedTx).chainId); + + if (!netId) { + throw new Error( + 'NetId for the transaction is invalid, this command only supports EIP-155 transactions', + ); + } + + const provider = await getProgramProvider(rpc, { + netId, + ...fetchDataOptions, + }); + + const { hash } = await provider.broadcastTransaction(signedTx); + + console.log(`\nBroadcastd tx: ${hash}\n`); + + process.exit(0); + }); + + program + .command('proposals') + .description('Get list or detail about TORN proposal') + .argument('[proposalId]', 'Proposal ID') + .action(async (proposalId: number, cmdOptions: commonProgramOptions) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); + const { rpc, disableTovarish } = options; + const netId = RELAYER_NETWORK; + + const provider = await getProgramProvider(rpc, { + netId, + ...fetchDataOptions, + }); + + const config = getConfig(netId); + + const { + governanceContract, + aggregatorContract, + reverseRecordsContract, + constants: { GOVERNANCE_BLOCK }, + } = config; + + const tovarishClient = !disableTovarish + ? ( + await getTovarishRelayer({ + options, + fetchDataOptions, + netId, + }) + ).relayerClient + : undefined; + + if (tovarishClient?.selectedRelayer) { + console.log(`\nConnected with Tovarish Relayer ${tovarishClient.selectedRelayer.url}\n`); + } + + const governanceService = new NodeGovernanceService({ + netId, + provider, + Governance: Governance__factory.connect(governanceContract as string, provider), + Aggregator: Aggregator__factory.connect(aggregatorContract as string, provider), + ReverseRecords: ReverseRecords__factory.connect(reverseRecordsContract as string, provider), + deployedBlock: GOVERNANCE_BLOCK, + fetchDataOptions, + tovarishClient, + cacheDirectory: EVENTS_DIR, + userDirectory: SAVED_DIR, + }); + + const proposals = await governanceService.getAllProposals(); + + const proposal = proposals.find((p) => p.id === Number(proposalId)); + + if (!proposal) { + const recentProposals = proposals.reverse().slice(0, 20); + + const proposalTable = new Table(); + + proposalTable.push( + [ + { + colSpan: 9, + content: 'Last 20 Proposals', + hAlign: 'center', + }, + ], + [ + 'ID', + 'Title', + 'Proposer', + 'Start Time', + 'End Time', + 'Quorum', + 'For Votes', + 'Against Votes', + 'State', + ], + ...recentProposals.map( + ({ + id, + title, + proposer, + proposerName, + startTime, + endTime, + quorum, + forVotes, + againstVotes, + state, + }) => { + return [ + id, + title, + proposerName || proposer.slice(0, 20), + moment.unix(startTime).format('DD/MM/YYYY'), + moment.unix(endTime).format('DD/MM/YYYY'), + quorum, + numberFormatter(formatEther(forVotes)) + ' TORN', + numberFormatter(formatEther(againstVotes)) + ' TORN', + state, + ]; + }, + ), + ); + + console.log(proposalTable.toString()); + + process.exit(0); + } + + proposalId = Number(proposalId); + + const { + transactionHash, + proposer, + proposerName, + target, + startTime, + endTime, + description, + title, + forVotes, + againstVotes, + executed, + extended, + quorum, + state, + } = proposal; + + const allVotes = forVotes + againstVotes; + + const proposalTable = new Table(); + + proposalTable.push( + [ + { + colSpan: 2, + content: `Proposal ${proposalId}`, + hAlign: 'center', + }, + ], + ['Title', title], + ['Description', description], + ['Proposer', proposerName || proposer], + ['Proposal Address', target], + ['Proposal TX', transactionHash], + ['Start Time', String(moment.unix(startTime).toDate())], + ['End Time', String(moment.unix(endTime).toDate())], + ['For', numberFormatter(formatEther(forVotes)) + ' TORN'], + ['Against', numberFormatter(formatEther(againstVotes)) + ' TORN'], + ['Quorum', quorum], + ['State', state], + ['Extended', extended], + ['Executed', executed], + ); + + console.log(proposalTable.toString()); + + const votes = await governanceService.getVotes(proposalId); + + const votersSet = new Set(); + const uniqueVotes = votes + .reverse() + .filter((v) => { + if (!votersSet.has(v.voter)) { + votersSet.add(v.voter); + return true; + } + return false; + }) + .sort((a, b) => Number(b.votes) - Number(a.votes)); + + const commentTable = new Table(); + + commentTable.push( + [{ colSpan: 5, content: 'Votes', hAlign: 'center' }], + ['Support', 'Votes', 'Voter', 'Delegate', 'Percent'], + ...uniqueVotes.map(({ support, votes, voter, voterName, from, fromName }) => { + const supportStr = support ? 'For' : 'Against'; + const votesStr = numberFormatter(formatEther(votes)) + ' TORN'; + const delegate = voter !== from ? fromName || from.slice(0, 20) : ''; + const percentage = ((Number(votes) / Number(allVotes)) * 100).toFixed(2) + '%'; + + return [supportStr, votesStr, voterName || voter.slice(0, 20), delegate, percentage]; + }), + ); + + console.log(commentTable.toString()); + + process.exit(0); + }); + + program + .command('delegates') + .description('Get list of delegates to address or ens') + .argument('
', 'Address or ENS name to lookup for delegation') + .action(async (address: string, cmdOptions: commonProgramOptions) => { + const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); + const { rpc, disableTovarish } = options; + const netId = RELAYER_NETWORK; + + const provider = await getProgramProvider(rpc, { + netId, + ...fetchDataOptions, + }); + + const config = getConfig(netId); + + const { + governanceContract, + aggregatorContract, + reverseRecordsContract, + constants: { GOVERNANCE_BLOCK }, + } = config; + + const tovarishClient = !disableTovarish + ? ( + await getTovarishRelayer({ + options, + fetchDataOptions, + netId, + }) + ).relayerClient + : undefined; + + if (tovarishClient?.selectedRelayer) { + console.log(`\nConnected with Tovarish Relayer ${tovarishClient.selectedRelayer.url}\n`); + } + + const account = address.endsWith('.eth') ? await provider._getAddress(address) : getAddress(address); + + const governanceService = new NodeGovernanceService({ + netId, + provider, + Governance: Governance__factory.connect(governanceContract as string, provider), + Aggregator: Aggregator__factory.connect(aggregatorContract as string, provider), + ReverseRecords: ReverseRecords__factory.connect(reverseRecordsContract as string, provider), + deployedBlock: GOVERNANCE_BLOCK, + fetchDataOptions, + tovarishClient, + cacheDirectory: EVENTS_DIR, + userDirectory: SAVED_DIR, + }); + + const { uniq, uniqNames, balances, balance } = await governanceService.getDelegatedBalance(account); + + const delegateTable = new Table(); + + delegateTable.push( + [{ colSpan: 2, content: 'Delegates', hAlign: 'center' }], + ['Account', account.toLowerCase() !== address.toLowerCase() ? `${address} (${account})` : address], + ['Delegated Balance', formatEther(balance) + ' TORN'], + ...uniq.map((acc, index) => { + return [uniqNames[acc] || acc, formatEther(balances[index]) + ' TORN']; + }), + ); + + console.log(delegateTable.toString()); + + process.exit(0); + }); + + // common options + program.commands.forEach((cmd) => { + cmd.option('-r, --rpc ', 'The RPC that CLI should interact with', parseUrl); + cmd.option('-e, --eth-rpc ', 'The Ethereum Mainnet RPC that CLI should interact with', parseUrl); + cmd.option( + '-a, --account-key ', + 'Account key generated from UI or the createAccount to store encrypted notes on-chain', + parseRecoveryKey, + ); + cmd.option('-R, --relayer ', 'Withdraw via relayer (Should be either .eth name or URL)', parseRelayer); + cmd.option('-w, --wallet-withdrawal', 'Withdrawal via wallet (Should not be linked with deposits)'); + cmd.option('-T, --tor-port ', 'Optional tor port', parseNumber); + cmd.option('-t, --token ', 'Token Contract address to view token balance', parseAddress); + cmd.option( + '-v, --view-only ', + 'Wallet address to view balance or to create unsigned transactions', + parseAddress, + ); + cmd.option( + '-m, --mnemonic ', + 'Wallet BIP39 Mnemonic Phrase - If you did not add it to .env file and it is needed for operation', + parseMnemonic, + ); + cmd.option('-i, --mnemonic-index ', 'Optional wallet mnemonic index', parseNumber); + cmd.option( + '-p, --private-key ', + 'Wallet private key - If you did not add it to .env file and it is needed for operation', + parseKey, + ); + cmd.option( + '-n, --non-interactive', + 'No confirmation mode - Does not show prompt for confirmation and allow to use scripts non-interactive', + ); + cmd.option('-l, --local-rpc', 'Local node mode - Does not submit signed transaction to the node'); }); - program - .command('delegates') - .description('Get list of delegates to address or ens') - .argument('
', 'Address or ENS name to lookup for delegation') - .action(async (address: string, cmdOptions: commonProgramOptions) => { - const { options, fetchDataOptions } = await getProgramOptions(cmdOptions); - const { rpc, disableTovarish } = options; - const netId = RELAYER_NETWORK; - - const provider = await getProgramProvider(rpc, { - netId, - ...fetchDataOptions, - }); - - const config = getConfig(netId); - - const { - governanceContract, - aggregatorContract, - reverseRecordsContract, - constants: { GOVERNANCE_BLOCK }, - } = config; - - const tovarishClient = !disableTovarish - ? (await getTovarishRelayer({ options, fetchDataOptions, netId })).relayerClient - : undefined; - - if (tovarishClient?.selectedRelayer) { - console.log(`\nConnected with Tovarish Relayer ${tovarishClient.selectedRelayer.url}\n`); - } - - const account = address.endsWith('.eth') ? await provider._getAddress(address) : getAddress(address); - - const governanceService = new NodeGovernanceService({ - netId, - provider, - Governance: Governance__factory.connect(governanceContract as string, provider), - Aggregator: Aggregator__factory.connect(aggregatorContract as string, provider), - ReverseRecords: ReverseRecords__factory.connect(reverseRecordsContract as string, provider), - deployedBlock: GOVERNANCE_BLOCK, - fetchDataOptions, - tovarishClient, - cacheDirectory: EVENTS_DIR, - userDirectory: SAVED_DIR, - }); - - const { uniq, uniqNames, balances, balance } = await governanceService.getDelegatedBalance(account); - - const delegateTable = new Table(); - - delegateTable.push( - [{ colSpan: 2, content: 'Delegates', hAlign: 'center' }], - ['Account', account.toLowerCase() !== address.toLowerCase() ? `${address} (${account})` : address], - ['Delegated Balance', formatEther(balance) + ' TORN'], - ...uniq.map((acc, index) => { - return [uniqNames[acc] || acc, formatEther(balances[index]) + ' TORN']; - }), - ); - - console.log(delegateTable.toString()); - - process.exit(0); - }); - - // common options - program.commands.forEach((cmd) => { - cmd.option('-r, --rpc ', 'The RPC that CLI should interact with', parseUrl); - cmd.option('-e, --eth-rpc ', 'The Ethereum Mainnet RPC that CLI should interact with', parseUrl); - cmd.option( - '-a, --account-key ', - 'Account key generated from UI or the createAccount to store encrypted notes on-chain', - parseRecoveryKey, - ); - cmd.option('-R, --relayer ', 'Withdraw via relayer (Should be either .eth name or URL)', parseRelayer); - cmd.option('-w, --wallet-withdrawal', 'Withdrawal via wallet (Should not be linked with deposits)'); - cmd.option('-T, --tor-port ', 'Optional tor port', parseNumber); - cmd.option('-t, --token ', 'Token Contract address to view token balance', parseAddress); - cmd.option( - '-v, --view-only ', - 'Wallet address to view balance or to create unsigned transactions', - parseAddress, - ); - cmd.option( - '-m, --mnemonic ', - 'Wallet BIP39 Mnemonic Phrase - If you did not add it to .env file and it is needed for operation', - parseMnemonic, - ); - cmd.option('-i, --mnemonic-index ', 'Optional wallet mnemonic index', parseNumber); - cmd.option( - '-p, --private-key ', - 'Wallet private key - If you did not add it to .env file and it is needed for operation', - parseKey, - ); - cmd.option( - '-n, --non-interactive', - 'No confirmation mode - Does not show prompt for confirmation and allow to use scripts non-interactive', - ); - cmd.option('-l, --local-rpc', 'Local node mode - Does not submit signed transaction to the node'); - }); - - return program; + return program; } diff --git a/src/services/data.ts b/src/services/data.ts index 123b2f7..93c6c2f 100644 --- a/src/services/data.ts +++ b/src/services/data.ts @@ -4,164 +4,164 @@ import { stat, mkdir, readFile, writeFile } from 'fs/promises'; import { BaseEvents, CachedEvents, MinimalEvents, zipAsync, unzipAsync } from '@tornado/core'; export async function existsAsync(fileOrDir: string): Promise { - try { - await stat(fileOrDir); + try { + await stat(fileOrDir); - return true; - } catch { - return false; - } + return true; + } catch { + return false; + } } /** * Supports legacy gz format for legacy UI */ export function deflateAsync(data: Uint8Array): Promise { - return new Promise((resolve, reject) => { - deflate( - data, - { - level: constants.Z_BEST_COMPRESSION, - strategy: constants.Z_FILTERED, - }, - (err, buffer) => { - if (!err) { - resolve(buffer); - } else { - reject(err); - } - }, - ); - }); + return new Promise((resolve, reject) => { + deflate( + data, + { + level: constants.Z_BEST_COMPRESSION, + strategy: constants.Z_FILTERED, + }, + (err, buffer) => { + if (!err) { + resolve(buffer); + } else { + reject(err); + } + }, + ); + }); } export async function saveLegacyFile({ - fileName, - userDirectory, - dataString, + fileName, + userDirectory, + dataString, }: { - fileName: string; - userDirectory: string; - dataString: string; + fileName: string; + userDirectory: string; + dataString: string; }) { - fileName = fileName.toLowerCase(); + fileName = fileName.toLowerCase(); - const filePath = path.join(userDirectory, fileName); + const filePath = path.join(userDirectory, fileName); - const payload = await deflateAsync(new TextEncoder().encode(dataString)); + const payload = await deflateAsync(new TextEncoder().encode(dataString)); - if (!(await existsAsync(userDirectory))) { - await mkdir(userDirectory, { recursive: true }); - } + if (!(await existsAsync(userDirectory))) { + await mkdir(userDirectory, { recursive: true }); + } - await writeFile(filePath + '.gz', payload); + await writeFile(filePath + '.gz', payload); } export async function saveUserFile({ - fileName, - userDirectory, - dataString, + fileName, + userDirectory, + dataString, }: { - fileName: string; - userDirectory: string; - dataString: string; + fileName: string; + userDirectory: string; + dataString: string; }) { - fileName = fileName.toLowerCase(); + fileName = fileName.toLowerCase(); - const filePath = path.join(userDirectory, fileName); + const filePath = path.join(userDirectory, fileName); - const payload = await zipAsync({ - [fileName]: new TextEncoder().encode(dataString), - }); + const payload = await zipAsync({ + [fileName]: new TextEncoder().encode(dataString), + }); - if (!(await existsAsync(userDirectory))) { - await mkdir(userDirectory, { recursive: true }); - } + if (!(await existsAsync(userDirectory))) { + await mkdir(userDirectory, { recursive: true }); + } - await writeFile(filePath + '.zip', payload); - await writeFile(filePath, dataString); + await writeFile(filePath + '.zip', payload); + await writeFile(filePath, dataString); } export async function loadSavedEvents({ - name, - userDirectory, + name, + userDirectory, }: { - name: string; - userDirectory: string; + name: string; + userDirectory: string; }): Promise> { - const filePath = path.join(userDirectory, `${name}.json`.toLowerCase()); + const filePath = path.join(userDirectory, `${name}.json`.toLowerCase()); - if (!(await existsAsync(filePath))) { - return { - events: [] as T[], - lastBlock: 0, - }; - } + if (!(await existsAsync(filePath))) { + return { + events: [] as T[], + lastBlock: 0, + }; + } - try { - const events = JSON.parse(await readFile(filePath, { encoding: 'utf8' })) as T[]; + try { + const events = JSON.parse(await readFile(filePath, { encoding: 'utf8' })) as T[]; - return { - events, - lastBlock: events[events.length - 1]?.blockNumber || 0, - }; - } catch (err) { - console.log('Method loadSavedEvents has error'); - console.log(err); - return { - events: [], - lastBlock: 0, - }; - } + return { + events, + lastBlock: events[events.length - 1]?.blockNumber || 0, + }; + } catch (err) { + console.log('Method loadSavedEvents has error'); + console.log(err); + return { + events: [], + lastBlock: 0, + }; + } } export async function download({ name, cacheDirectory }: { name: string; cacheDirectory: string }) { - const fileName = `${name}.json`.toLowerCase(); - const zipName = `${fileName}.zip`; - const zipPath = path.join(cacheDirectory, zipName); + const fileName = `${name}.json`.toLowerCase(); + const zipName = `${fileName}.zip`; + const zipPath = path.join(cacheDirectory, zipName); - const data = await readFile(zipPath); - const { [fileName]: content } = await unzipAsync(data); + const data = await readFile(zipPath); + const { [fileName]: content } = await unzipAsync(data); - return new TextDecoder().decode(content); + return new TextDecoder().decode(content); } export async function loadCachedEvents({ - name, - cacheDirectory, - deployedBlock, + name, + cacheDirectory, + deployedBlock, }: { - name: string; - cacheDirectory: string; - deployedBlock: number; + name: string; + cacheDirectory: string; + deployedBlock: number; }): Promise> { - try { - const module = await download({ cacheDirectory, name }); + try { + const module = await download({ cacheDirectory, name }); - if (module) { - const events = JSON.parse(module); + if (module) { + const events = JSON.parse(module); - const lastBlock = events && events.length ? events[events.length - 1].blockNumber : deployedBlock; + const lastBlock = events && events.length ? events[events.length - 1].blockNumber : deployedBlock; - return { - events, - lastBlock, - fromCache: true, - }; + return { + events, + lastBlock, + fromCache: true, + }; + } + + return { + events: [], + lastBlock: deployedBlock, + fromCache: true, + }; + } catch (err) { + console.log('Method loadCachedEvents has error'); + console.log(err); + return { + events: [], + lastBlock: deployedBlock, + fromCache: true, + }; } - - return { - events: [], - lastBlock: deployedBlock, - fromCache: true, - }; - } catch (err) { - console.log('Method loadCachedEvents has error'); - console.log(err); - return { - events: [], - lastBlock: deployedBlock, - fromCache: true, - }; - } } diff --git a/src/services/nodeEvents.ts b/src/services/nodeEvents.ts index 339ee18..3b90b0a 100644 --- a/src/services/nodeEvents.ts +++ b/src/services/nodeEvents.ts @@ -3,587 +3,600 @@ import { readFile } from 'fs/promises'; import Table from 'cli-table3'; import moment from 'moment'; import { - BatchBlockOnProgress, - BatchEventOnProgress, - BaseTornadoService, - BaseEncryptedNotesService, - BaseGovernanceService, - BaseRegistryService, - BaseTornadoServiceConstructor, - BaseEncryptedNotesServiceConstructor, - BaseGovernanceServiceConstructor, - BaseRegistryServiceConstructor, - BaseEchoServiceConstructor, - BaseEchoService, - CachedRelayers, - toFixedHex, + BatchBlockOnProgress, + BatchEventOnProgress, + BaseTornadoService, + BaseEncryptedNotesService, + BaseGovernanceService, + BaseRegistryService, + BaseTornadoServiceConstructor, + BaseEncryptedNotesServiceConstructor, + BaseGovernanceServiceConstructor, + BaseRegistryServiceConstructor, + BaseEchoServiceConstructor, + BaseEchoService, + CachedRelayers, + toFixedHex, } from '@tornado/core'; import type { - BaseEvents, - DepositsEvents, - WithdrawalsEvents, - EncryptedNotesEvents, - RegistersEvents, - AllGovernanceEvents, - EchoEvents, - BaseEventsService, - MinimalEvents, + BaseEvents, + DepositsEvents, + WithdrawalsEvents, + EncryptedNotesEvents, + RegistersEvents, + AllGovernanceEvents, + EchoEvents, + BaseEventsService, + MinimalEvents, } from '@tornado/core'; import type { MerkleTree } from '@tornado/fixed-merkle-tree'; import { TreeCache } from './treeCache'; import { saveUserFile, loadSavedEvents, loadCachedEvents, existsAsync } from './data'; async function getEventsFromDB(service: BaseEventsService & { userDirectory: string }) { - if (!service.userDirectory) { + if (!service.userDirectory) { + console.log(`Updating ${service.getInstanceName()} events\n`); + console.log(`savedEvents count - ${0}`); + console.log(`savedEvents lastBlock - ${service.deployedBlock}\n`); + + return { + events: [], + lastBlock: 0, + }; + } + + const savedEvents = await loadSavedEvents({ + name: service.getInstanceName(), + userDirectory: service.userDirectory, + }); + console.log(`Updating ${service.getInstanceName()} events\n`); - console.log(`savedEvents count - ${0}`); - console.log(`savedEvents lastBlock - ${service.deployedBlock}\n`); + console.log(`savedEvents count - ${savedEvents.events.length}`); + console.log(`savedEvents lastBlock - ${savedEvents.lastBlock}\n`); - return { - events: [], - lastBlock: 0, - }; - } - - const savedEvents = await loadSavedEvents({ - name: service.getInstanceName(), - userDirectory: service.userDirectory, - }); - - console.log(`Updating ${service.getInstanceName()} events\n`); - console.log(`savedEvents count - ${savedEvents.events.length}`); - console.log(`savedEvents lastBlock - ${savedEvents.lastBlock}\n`); - - return savedEvents; + return savedEvents; } async function getEventsFromCache(service: BaseEventsService & { cacheDirectory: string }) { - if (!service.cacheDirectory) { - console.log(`cachedEvents count - ${0}`); - console.log(`cachedEvents lastBlock - ${service.deployedBlock}\n`); + if (!service.cacheDirectory) { + console.log(`cachedEvents count - ${0}`); + console.log(`cachedEvents lastBlock - ${service.deployedBlock}\n`); - return { - events: [], - lastBlock: service.deployedBlock, - fromCache: true, - }; - } + return { + events: [], + lastBlock: service.deployedBlock, + fromCache: true, + }; + } - const cachedEvents = await loadCachedEvents({ - name: service.getInstanceName(), - cacheDirectory: service.cacheDirectory, - deployedBlock: service.deployedBlock, - }); + const cachedEvents = await loadCachedEvents({ + name: service.getInstanceName(), + cacheDirectory: service.cacheDirectory, + deployedBlock: service.deployedBlock, + }); - console.log(`cachedEvents count - ${cachedEvents.events.length}`); - console.log(`cachedEvents lastBlock - ${cachedEvents.lastBlock}\n`); + console.log(`cachedEvents count - ${cachedEvents.events.length}`); + console.log(`cachedEvents lastBlock - ${cachedEvents.lastBlock}\n`); - return cachedEvents; + return cachedEvents; } async function saveEvents( - service: BaseEventsService & { userDirectory: string }, - { events, lastBlock }: BaseEvents, - eventTable?: Table.Table, + service: BaseEventsService & { userDirectory: string }, + { events, lastBlock }: BaseEvents, + eventTable?: Table.Table, ) { - const instanceName = service.getInstanceName(); + const instanceName = service.getInstanceName(); - console.log('\ntotalEvents count - ', events.length); - console.log( - `totalEvents lastBlock - ${events[events.length - 1] ? events[events.length - 1].blockNumber : lastBlock}\n`, - ); + console.log('\ntotalEvents count - ', events.length); + console.log( + `totalEvents lastBlock - ${events[events.length - 1] ? events[events.length - 1].blockNumber : lastBlock}\n`, + ); - if (eventTable) { - console.log(eventTable.toString() + '\n'); - } + if (eventTable) { + console.log(eventTable.toString() + '\n'); + } - if (service.userDirectory) { - await saveUserFile({ - fileName: instanceName + '.json', - userDirectory: service.userDirectory, - dataString: JSON.stringify(events, null, 2) + '\n', - }); - } + if (service.userDirectory) { + await saveUserFile({ + fileName: instanceName + '.json', + userDirectory: service.userDirectory, + dataString: JSON.stringify(events, null, 2) + '\n', + }); + } } export type NodeServiceConstructor = { - cacheDirectory: string; - userDirectory: string; + cacheDirectory: string; + userDirectory: string; }; export type NodeTornadoServiceConstructor = BaseTornadoServiceConstructor & - NodeServiceConstructor & { - nativeCurrency: string; - treeCache?: TreeCache; - }; + NodeServiceConstructor & { + nativeCurrency: string; + treeCache?: TreeCache; + }; export class NodeTornadoService extends BaseTornadoService { - cacheDirectory: string; - userDirectory: string; - nativeCurrency: string; + cacheDirectory: string; + userDirectory: string; + nativeCurrency: string; - treeCache?: TreeCache; + treeCache?: TreeCache; - constructor(serviceConstructor: NodeTornadoServiceConstructor) { - super(serviceConstructor); + constructor(serviceConstructor: NodeTornadoServiceConstructor) { + super(serviceConstructor); - const { cacheDirectory, userDirectory, nativeCurrency, treeCache } = serviceConstructor; + const { cacheDirectory, userDirectory, nativeCurrency, treeCache } = serviceConstructor; - this.cacheDirectory = cacheDirectory; - this.userDirectory = userDirectory; - this.nativeCurrency = nativeCurrency; + this.cacheDirectory = cacheDirectory; + this.userDirectory = userDirectory; + this.nativeCurrency = nativeCurrency; - this.treeCache = treeCache; - } - - updateEventProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { - if (toBlock) { - console.log(`fromBlock - ${fromBlock}`); - console.log(`toBlock - ${toBlock}`); - - if (count) { - console.log(`downloaded ${type} events count - ${count}`); - console.log('____________________________________________'); - console.log(`Fetched ${type} events from ${fromBlock} to ${toBlock}\n`); - } - } - } - - updateTransactionProgress({ currentIndex, totalIndex }: Parameters[0]) { - if (totalIndex) { - console.log(`Fetched ${currentIndex} deposit txs of ${totalIndex}`); - } - } - - updateBlockProgress({ currentIndex, totalIndex }: Parameters[0]) { - if (totalIndex) { - console.log(`Fetched ${currentIndex} withdrawal blocks of ${totalIndex}`); - } - } - - updateGraphProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { - if (toBlock) { - console.log(`fromBlock - ${fromBlock}`); - console.log(`toBlock - ${toBlock}`); - - if (count) { - console.log(`downloaded ${type} events from graph node count - ${count}`); - console.log('____________________________________________'); - console.log(`Fetched ${type} events from graph node ${fromBlock} to ${toBlock}\n`); - } - } - } - - async getEventsFromDB() { - return await getEventsFromDB(this); - } - - async getEventsFromCache() { - return await getEventsFromCache(this); - } - - async validateEvents({ - events, - lastBlock, - hasNewEvents, - }: BaseEvents & { hasNewEvents?: boolean }): Promise { - const tree = await super.validateEvents({ events, lastBlock, hasNewEvents }); - - if (tree && this.currency === this.nativeCurrency && this.treeCache) { - const merkleTree = tree as unknown as MerkleTree; - - await this.treeCache.createTree(events as DepositsEvents[], merkleTree); - - console.log(`${this.getInstanceName()}: Updated tree cache with root ${toFixedHex(BigInt(merkleTree.root))}\n`); + this.treeCache = treeCache; } - return tree; - } + updateEventProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { + if (toBlock) { + console.log(`fromBlock - ${fromBlock}`); + console.log(`toBlock - ${toBlock}`); - async saveEvents({ events, lastBlock }: BaseEvents) { - const eventTable = new Table(); + if (count) { + console.log(`downloaded ${type} events count - ${count}`); + console.log('____________________________________________'); + console.log(`Fetched ${type} events from ${fromBlock} to ${toBlock}\n`); + } + } + } - eventTable.push( - [{ colSpan: 2, content: `${this.getType()}s`, hAlign: 'center' }], - ['Instance', `${this.netId} chain ${this.amount} ${this.currency.toUpperCase()}`], - ['Anonymity set', `${events.length} equal user ${this.getType().toLowerCase()}s`], - [{ colSpan: 2, content: `Latest ${this.getType().toLowerCase()}s` }], - ...events - .slice(events.length - 10) - .reverse() - .map(({ timestamp }, index) => { - const eventIndex = events.length - index; - const eventTime = moment.unix(timestamp).fromNow(); + updateTransactionProgress({ currentIndex, totalIndex }: Parameters[0]) { + if (totalIndex) { + console.log(`Fetched ${currentIndex} deposit txs of ${totalIndex}`); + } + } - return [eventIndex, eventTime]; - }), - ); + updateBlockProgress({ currentIndex, totalIndex }: Parameters[0]) { + if (totalIndex) { + console.log(`Fetched ${currentIndex} withdrawal blocks of ${totalIndex}`); + } + } - await saveEvents(this, { events, lastBlock }, eventTable); - } + updateGraphProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { + if (toBlock) { + console.log(`fromBlock - ${fromBlock}`); + console.log(`toBlock - ${toBlock}`); + + if (count) { + console.log(`downloaded ${type} events from graph node count - ${count}`); + console.log('____________________________________________'); + console.log(`Fetched ${type} events from graph node ${fromBlock} to ${toBlock}\n`); + } + } + } + + async getEventsFromDB() { + return await getEventsFromDB(this); + } + + async getEventsFromCache() { + return await getEventsFromCache(this); + } + + async validateEvents({ + events, + lastBlock, + hasNewEvents, + }: BaseEvents & { + hasNewEvents?: boolean; + }): Promise { + const tree = await super.validateEvents({ + events, + lastBlock, + hasNewEvents, + }); + + if (tree && this.currency === this.nativeCurrency && this.treeCache) { + const merkleTree = tree as unknown as MerkleTree; + + await this.treeCache.createTree(events as DepositsEvents[], merkleTree); + + console.log( + `${this.getInstanceName()}: Updated tree cache with root ${toFixedHex(BigInt(merkleTree.root))}\n`, + ); + } + + return tree; + } + + async saveEvents({ events, lastBlock }: BaseEvents) { + const eventTable = new Table(); + + eventTable.push( + [{ colSpan: 2, content: `${this.getType()}s`, hAlign: 'center' }], + ['Instance', `${this.netId} chain ${this.amount} ${this.currency.toUpperCase()}`], + ['Anonymity set', `${events.length} equal user ${this.getType().toLowerCase()}s`], + [ + { + colSpan: 2, + content: `Latest ${this.getType().toLowerCase()}s`, + }, + ], + ...events + .slice(events.length - 10) + .reverse() + .map(({ timestamp }, index) => { + const eventIndex = events.length - index; + const eventTime = moment.unix(timestamp).fromNow(); + + return [eventIndex, eventTime]; + }), + ); + + await saveEvents(this, { events, lastBlock }, eventTable); + } } export type NodeEchoServiceConstructor = BaseEchoServiceConstructor & NodeServiceConstructor; export class NodeEchoService extends BaseEchoService { - cacheDirectory: string; - userDirectory: string; + cacheDirectory: string; + userDirectory: string; - constructor(serviceConstructor: NodeEchoServiceConstructor) { - super(serviceConstructor); + constructor(serviceConstructor: NodeEchoServiceConstructor) { + super(serviceConstructor); - const { cacheDirectory, userDirectory } = serviceConstructor; + const { cacheDirectory, userDirectory } = serviceConstructor; - this.cacheDirectory = cacheDirectory; - this.userDirectory = userDirectory; - } - - updateEventProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { - if (toBlock) { - console.log(`fromBlock - ${fromBlock}`); - console.log(`toBlock - ${toBlock}`); - - if (count) { - console.log(`downloaded ${type} events count - ${count}`); - console.log('____________________________________________'); - console.log(`Fetched ${type} events from ${fromBlock} to ${toBlock}\n`); - } + this.cacheDirectory = cacheDirectory; + this.userDirectory = userDirectory; } - } - updateGraphProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { - if (toBlock) { - console.log(`fromBlock - ${fromBlock}`); - console.log(`toBlock - ${toBlock}`); + updateEventProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { + if (toBlock) { + console.log(`fromBlock - ${fromBlock}`); + console.log(`toBlock - ${toBlock}`); - if (count) { - console.log(`downloaded ${type} events from graph node count - ${count}`); - console.log('____________________________________________'); - console.log(`Fetched ${type} events from graph node ${fromBlock} to ${toBlock}\n`); - } + if (count) { + console.log(`downloaded ${type} events count - ${count}`); + console.log('____________________________________________'); + console.log(`Fetched ${type} events from ${fromBlock} to ${toBlock}\n`); + } + } } - } - async getEventsFromDB() { - return await getEventsFromDB(this); - } + updateGraphProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { + if (toBlock) { + console.log(`fromBlock - ${fromBlock}`); + console.log(`toBlock - ${toBlock}`); - async getEventsFromCache() { - return await getEventsFromCache(this); - } + if (count) { + console.log(`downloaded ${type} events from graph node count - ${count}`); + console.log('____________________________________________'); + console.log(`Fetched ${type} events from graph node ${fromBlock} to ${toBlock}\n`); + } + } + } - async saveEvents({ events, lastBlock }: BaseEvents) { - const eventTable = new Table(); + async getEventsFromDB() { + return await getEventsFromDB(this); + } - eventTable.push( - [{ colSpan: 2, content: 'Echo Accounts', hAlign: 'center' }], - ['Network', `${this.netId} chain`], - ['Events', `${events.length} events`], - [{ colSpan: 2, content: 'Latest events' }], - ...events - .slice(events.length - 10) - .reverse() - .map(({ blockNumber }, index) => { - const eventIndex = events.length - index; + async getEventsFromCache() { + return await getEventsFromCache(this); + } - return [eventIndex, blockNumber]; - }), - ); + async saveEvents({ events, lastBlock }: BaseEvents) { + const eventTable = new Table(); - await saveEvents(this, { events, lastBlock }, eventTable); - } + eventTable.push( + [{ colSpan: 2, content: 'Echo Accounts', hAlign: 'center' }], + ['Network', `${this.netId} chain`], + ['Events', `${events.length} events`], + [{ colSpan: 2, content: 'Latest events' }], + ...events + .slice(events.length - 10) + .reverse() + .map(({ blockNumber }, index) => { + const eventIndex = events.length - index; + + return [eventIndex, blockNumber]; + }), + ); + + await saveEvents(this, { events, lastBlock }, eventTable); + } } export type NodeEncryptedNotesServiceConstructor = BaseEncryptedNotesServiceConstructor & NodeServiceConstructor; export class NodeEncryptedNotesService extends BaseEncryptedNotesService { - cacheDirectory: string; - userDirectory: string; + cacheDirectory: string; + userDirectory: string; - constructor(serviceConstructor: NodeEncryptedNotesServiceConstructor) { - super(serviceConstructor); + constructor(serviceConstructor: NodeEncryptedNotesServiceConstructor) { + super(serviceConstructor); - const { cacheDirectory, userDirectory } = serviceConstructor; + const { cacheDirectory, userDirectory } = serviceConstructor; - this.cacheDirectory = cacheDirectory; - this.userDirectory = userDirectory; - } - - updateEventProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { - if (toBlock) { - console.log(`fromBlock - ${fromBlock}`); - console.log(`toBlock - ${toBlock}`); - - if (count) { - console.log(`downloaded ${type} events count - ${count}`); - console.log('____________________________________________'); - console.log(`Fetched ${type} events from ${fromBlock} to ${toBlock}\n`); - } + this.cacheDirectory = cacheDirectory; + this.userDirectory = userDirectory; } - } - updateGraphProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { - if (toBlock) { - console.log(`fromBlock - ${fromBlock}`); - console.log(`toBlock - ${toBlock}`); + updateEventProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { + if (toBlock) { + console.log(`fromBlock - ${fromBlock}`); + console.log(`toBlock - ${toBlock}`); - if (count) { - console.log(`downloaded ${type} events from graph node count - ${count}`); - console.log('____________________________________________'); - console.log(`Fetched ${type} events from graph node ${fromBlock} to ${toBlock}\n`); - } + if (count) { + console.log(`downloaded ${type} events count - ${count}`); + console.log('____________________________________________'); + console.log(`Fetched ${type} events from ${fromBlock} to ${toBlock}\n`); + } + } } - } - async getEventsFromDB() { - return await getEventsFromDB(this); - } + updateGraphProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { + if (toBlock) { + console.log(`fromBlock - ${fromBlock}`); + console.log(`toBlock - ${toBlock}`); - async getEventsFromCache() { - return await getEventsFromCache(this); - } + if (count) { + console.log(`downloaded ${type} events from graph node count - ${count}`); + console.log('____________________________________________'); + console.log(`Fetched ${type} events from graph node ${fromBlock} to ${toBlock}\n`); + } + } + } - async saveEvents({ events, lastBlock }: BaseEvents) { - const eventTable = new Table(); + async getEventsFromDB() { + return await getEventsFromDB(this); + } - eventTable.push( - [{ colSpan: 2, content: 'Encrypted Notes', hAlign: 'center' }], - ['Network', `${this.netId} chain`], - ['Events', `${events.length} events`], - [{ colSpan: 2, content: 'Latest events' }], - ...events - .slice(events.length - 10) - .reverse() - .map(({ blockNumber }, index) => { - const eventIndex = events.length - index; + async getEventsFromCache() { + return await getEventsFromCache(this); + } - return [eventIndex, blockNumber]; - }), - ); + async saveEvents({ events, lastBlock }: BaseEvents) { + const eventTable = new Table(); - await saveEvents(this, { events, lastBlock }, eventTable); - } + eventTable.push( + [{ colSpan: 2, content: 'Encrypted Notes', hAlign: 'center' }], + ['Network', `${this.netId} chain`], + ['Events', `${events.length} events`], + [{ colSpan: 2, content: 'Latest events' }], + ...events + .slice(events.length - 10) + .reverse() + .map(({ blockNumber }, index) => { + const eventIndex = events.length - index; + + return [eventIndex, blockNumber]; + }), + ); + + await saveEvents(this, { events, lastBlock }, eventTable); + } } export type NodeGovernanceServiceConstructor = BaseGovernanceServiceConstructor & NodeServiceConstructor; export class NodeGovernanceService extends BaseGovernanceService { - cacheDirectory: string; - userDirectory: string; + cacheDirectory: string; + userDirectory: string; - constructor(serviceConstructor: NodeGovernanceServiceConstructor) { - super(serviceConstructor); + constructor(serviceConstructor: NodeGovernanceServiceConstructor) { + super(serviceConstructor); - const { cacheDirectory, userDirectory } = serviceConstructor; + const { cacheDirectory, userDirectory } = serviceConstructor; - this.cacheDirectory = cacheDirectory; - this.userDirectory = userDirectory; - } - - updateEventProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { - if (toBlock) { - console.log(`fromBlock - ${fromBlock}`); - console.log(`toBlock - ${toBlock}`); - - if (count) { - console.log(`downloaded ${type} events count - ${count}`); - console.log('____________________________________________'); - console.log(`Fetched ${type} events from ${fromBlock} to ${toBlock}\n`); - } + this.cacheDirectory = cacheDirectory; + this.userDirectory = userDirectory; } - } - updateGraphProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { - if (toBlock) { - console.log(`fromBlock - ${fromBlock}`); - console.log(`toBlock - ${toBlock}`); + updateEventProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { + if (toBlock) { + console.log(`fromBlock - ${fromBlock}`); + console.log(`toBlock - ${toBlock}`); - if (count) { - console.log(`downloaded ${type} events from graph node count - ${count}`); - console.log('____________________________________________'); - console.log(`Fetched ${type} events from graph node ${fromBlock} to ${toBlock}\n`); - } + if (count) { + console.log(`downloaded ${type} events count - ${count}`); + console.log('____________________________________________'); + console.log(`Fetched ${type} events from ${fromBlock} to ${toBlock}\n`); + } + } } - } - updateTransactionProgress({ currentIndex, totalIndex }: Parameters[0]) { - if (totalIndex) { - console.log(`Fetched ${currentIndex} governance txs of ${totalIndex}`); + updateGraphProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { + if (toBlock) { + console.log(`fromBlock - ${fromBlock}`); + console.log(`toBlock - ${toBlock}`); + + if (count) { + console.log(`downloaded ${type} events from graph node count - ${count}`); + console.log('____________________________________________'); + console.log(`Fetched ${type} events from graph node ${fromBlock} to ${toBlock}\n`); + } + } } - } - async getEventsFromDB() { - return await getEventsFromDB(this); - } + updateTransactionProgress({ currentIndex, totalIndex }: Parameters[0]) { + if (totalIndex) { + console.log(`Fetched ${currentIndex} governance txs of ${totalIndex}`); + } + } - async getEventsFromCache() { - return await getEventsFromCache(this); - } + async getEventsFromDB() { + return await getEventsFromDB(this); + } - async saveEvents({ events, lastBlock }: BaseEvents) { - const eventTable = new Table(); + async getEventsFromCache() { + return await getEventsFromCache(this); + } - eventTable.push( - [{ colSpan: 2, content: 'Governance Events', hAlign: 'center' }], - ['Network', `${this.netId} chain`], - ['Events', `${events.length} events`], - [{ colSpan: 2, content: 'Latest events' }], - ...events - .slice(events.length - 10) - .reverse() - .map(({ blockNumber }, index) => { - const eventIndex = events.length - index; + async saveEvents({ events, lastBlock }: BaseEvents) { + const eventTable = new Table(); - return [eventIndex, blockNumber]; - }), - ); + eventTable.push( + [{ colSpan: 2, content: 'Governance Events', hAlign: 'center' }], + ['Network', `${this.netId} chain`], + ['Events', `${events.length} events`], + [{ colSpan: 2, content: 'Latest events' }], + ...events + .slice(events.length - 10) + .reverse() + .map(({ blockNumber }, index) => { + const eventIndex = events.length - index; - await saveEvents(this, { events, lastBlock }, eventTable); - } + return [eventIndex, blockNumber]; + }), + ); + + await saveEvents(this, { events, lastBlock }, eventTable); + } } export type NodeRegistryServiceConstructor = BaseRegistryServiceConstructor & NodeServiceConstructor; export class NodeRegistryService extends BaseRegistryService { - cacheDirectory: string; - userDirectory: string; + cacheDirectory: string; + userDirectory: string; - constructor(serviceConstructor: NodeRegistryServiceConstructor) { - super(serviceConstructor); + constructor(serviceConstructor: NodeRegistryServiceConstructor) { + super(serviceConstructor); - const { cacheDirectory, userDirectory } = serviceConstructor; + const { cacheDirectory, userDirectory } = serviceConstructor; - this.cacheDirectory = cacheDirectory; - this.userDirectory = userDirectory; - } - - updateEventProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { - if (toBlock) { - console.log(`fromBlock - ${fromBlock}`); - console.log(`toBlock - ${toBlock}`); - - if (count) { - console.log(`downloaded ${type} events count - ${count}`); - console.log('____________________________________________'); - console.log(`Fetched ${type} events from ${fromBlock} to ${toBlock}\n`); - } - } - } - - updateGraphProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { - if (toBlock) { - console.log(`fromBlock - ${fromBlock}`); - console.log(`toBlock - ${toBlock}`); - - if (count) { - console.log(`downloaded ${type} events from graph node count - ${count}`); - console.log('____________________________________________'); - console.log(`Fetched ${type} events from graph node ${fromBlock} to ${toBlock}\n`); - } - } - } - - async getEventsFromDB() { - return await getEventsFromDB(this); - } - - async getEventsFromCache() { - return await getEventsFromCache(this); - } - - async saveEvents({ events, lastBlock }: BaseEvents) { - const eventTable = new Table(); - - eventTable.push( - [{ colSpan: 2, content: 'Registered Relayers', hAlign: 'center' }], - ['Network', `${this.netId} chain`], - ['Events', `${events.length} events`], - [{ colSpan: 2, content: 'Latest events' }], - ...events - .slice(events.length - 10) - .reverse() - .map(({ blockNumber }, index) => { - const eventIndex = events.length - index; - - return [eventIndex, blockNumber]; - }), - ); - - await saveEvents(this, { events, lastBlock }, eventTable); - } - - async getRelayersFromDB(): Promise { - const filePath = path.join(this.userDirectory || '', 'relayers.json'); - - if (!this.userDirectory || !(await existsAsync(filePath))) { - return { - lastBlock: 0, - timestamp: 0, - relayers: [], - }; + this.cacheDirectory = cacheDirectory; + this.userDirectory = userDirectory; } - try { - const { lastBlock, timestamp, relayers } = JSON.parse(await readFile(filePath, { encoding: 'utf8' })); + updateEventProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { + if (toBlock) { + console.log(`fromBlock - ${fromBlock}`); + console.log(`toBlock - ${toBlock}`); - return { - lastBlock, - timestamp, - relayers, - }; - } catch (err) { - console.log('Method getRelayersFromDB has error'); - console.log(err); - - return { - lastBlock: 0, - timestamp: 0, - relayers: [], - }; - } - } - - async getRelayersFromCache(): Promise { - const filePath = path.join(this.cacheDirectory || '', 'relayers.json'); - - if (!this.cacheDirectory || !(await existsAsync(filePath))) { - return { - lastBlock: 0, - timestamp: 0, - relayers: [], - fromCache: true, - }; + if (count) { + console.log(`downloaded ${type} events count - ${count}`); + console.log('____________________________________________'); + console.log(`Fetched ${type} events from ${fromBlock} to ${toBlock}\n`); + } + } } - try { - const { lastBlock, timestamp, relayers } = JSON.parse(await readFile(filePath, { encoding: 'utf8' })); + updateGraphProgress({ type, fromBlock, toBlock, count }: Parameters[0]) { + if (toBlock) { + console.log(`fromBlock - ${fromBlock}`); + console.log(`toBlock - ${toBlock}`); - return { - lastBlock, - timestamp, - relayers, - fromCache: true, - }; - } catch (err) { - console.log('Method getRelayersFromDB has error'); - console.log(err); - - return { - lastBlock: 0, - timestamp: 0, - relayers: [], - fromCache: true, - }; + if (count) { + console.log(`downloaded ${type} events from graph node count - ${count}`); + console.log('____________________________________________'); + console.log(`Fetched ${type} events from graph node ${fromBlock} to ${toBlock}\n`); + } + } } - } - async saveRelayers({ lastBlock, timestamp, relayers }: CachedRelayers) { - if (this.userDirectory) { - await saveUserFile({ - fileName: 'relayers.json', - userDirectory: this.userDirectory, - dataString: JSON.stringify({ lastBlock, timestamp, relayers }, null, 2) + '\n', - }); + async getEventsFromDB() { + return await getEventsFromDB(this); + } + + async getEventsFromCache() { + return await getEventsFromCache(this); + } + + async saveEvents({ events, lastBlock }: BaseEvents) { + const eventTable = new Table(); + + eventTable.push( + [{ colSpan: 2, content: 'Registered Relayers', hAlign: 'center' }], + ['Network', `${this.netId} chain`], + ['Events', `${events.length} events`], + [{ colSpan: 2, content: 'Latest events' }], + ...events + .slice(events.length - 10) + .reverse() + .map(({ blockNumber }, index) => { + const eventIndex = events.length - index; + + return [eventIndex, blockNumber]; + }), + ); + + await saveEvents(this, { events, lastBlock }, eventTable); + } + + async getRelayersFromDB(): Promise { + const filePath = path.join(this.userDirectory || '', 'relayers.json'); + + if (!this.userDirectory || !(await existsAsync(filePath))) { + return { + lastBlock: 0, + timestamp: 0, + relayers: [], + }; + } + + try { + const { lastBlock, timestamp, relayers } = JSON.parse(await readFile(filePath, { encoding: 'utf8' })); + + return { + lastBlock, + timestamp, + relayers, + }; + } catch (err) { + console.log('Method getRelayersFromDB has error'); + console.log(err); + + return { + lastBlock: 0, + timestamp: 0, + relayers: [], + }; + } + } + + async getRelayersFromCache(): Promise { + const filePath = path.join(this.cacheDirectory || '', 'relayers.json'); + + if (!this.cacheDirectory || !(await existsAsync(filePath))) { + return { + lastBlock: 0, + timestamp: 0, + relayers: [], + fromCache: true, + }; + } + + try { + const { lastBlock, timestamp, relayers } = JSON.parse(await readFile(filePath, { encoding: 'utf8' })); + + return { + lastBlock, + timestamp, + relayers, + fromCache: true, + }; + } catch (err) { + console.log('Method getRelayersFromDB has error'); + console.log(err); + + return { + lastBlock: 0, + timestamp: 0, + relayers: [], + fromCache: true, + }; + } + } + + async saveRelayers({ lastBlock, timestamp, relayers }: CachedRelayers) { + if (this.userDirectory) { + await saveUserFile({ + fileName: 'relayers.json', + userDirectory: this.userDirectory, + dataString: JSON.stringify({ lastBlock, timestamp, relayers }, null, 2) + '\n', + }); + } } - } } diff --git a/src/services/parser.ts b/src/services/parser.ts index 3229b56..a9578ad 100644 --- a/src/services/parser.ts +++ b/src/services/parser.ts @@ -3,75 +3,75 @@ import { computeAddress, getAddress, Mnemonic } from 'ethers'; import { validateUrl } from '@tornado/core'; export function parseNumber(value?: string | number): number { - if (!value || isNaN(Number(value))) { - throw new InvalidArgumentError('Invalid Number'); - } - return Number(value); + if (!value || isNaN(Number(value))) { + throw new InvalidArgumentError('Invalid Number'); + } + return Number(value); } export function parseUrl(value?: string): string { - if (!value || !validateUrl(value, ['http:', 'https:'])) { - throw new InvalidArgumentError('Invalid URL'); - } - return value; + if (!value || !validateUrl(value, ['http:', 'https:'])) { + throw new InvalidArgumentError('Invalid URL'); + } + return value; } export function parseRelayer(value?: string): string { - if (!value || !(value.endsWith('.eth') || validateUrl(value, ['http:', 'https:']))) { - throw new InvalidArgumentError('Invalid Relayer ETH address or URL'); - } - return value; + if (!value || !(value.endsWith('.eth') || validateUrl(value, ['http:', 'https:']))) { + throw new InvalidArgumentError('Invalid Relayer ETH address or URL'); + } + return value; } export function parseAddress(value?: string): string { - if (!value) { - throw new InvalidArgumentError('Invalid Address'); - } - try { - return getAddress(value); - } catch { - throw new InvalidArgumentError('Invalid Address'); - } + if (!value) { + throw new InvalidArgumentError('Invalid Address'); + } + try { + return getAddress(value); + } catch { + throw new InvalidArgumentError('Invalid Address'); + } } export function parseMnemonic(value?: string): string { - if (!value) { - throw new InvalidArgumentError('Invalid Mnemonic'); - } - try { - Mnemonic.fromPhrase(value); - } catch { - throw new InvalidArgumentError('Invalid Mnemonic'); - } - return value; + if (!value) { + throw new InvalidArgumentError('Invalid Mnemonic'); + } + try { + Mnemonic.fromPhrase(value); + } catch { + throw new InvalidArgumentError('Invalid Mnemonic'); + } + return value; } export function parseKey(value?: string): string { - if (!value) { - throw new InvalidArgumentError('Invalid Private Key'); - } - if (value.length === 64) { - value = '0x' + value; - } - try { - computeAddress(value); - } catch { - throw new InvalidArgumentError('Invalid Private Key'); - } - return value; + if (!value) { + throw new InvalidArgumentError('Invalid Private Key'); + } + if (value.length === 64) { + value = '0x' + value; + } + try { + computeAddress(value); + } catch { + throw new InvalidArgumentError('Invalid Private Key'); + } + return value; } /** * Recovery key shouldn't have a 0x prefix (Also this is how the UI generates) */ export function parseRecoveryKey(value?: string): string { - if (!value) { - throw new InvalidArgumentError('Invalid Recovery Key'); - } - try { - computeAddress('0x' + value); - } catch { - throw new InvalidArgumentError('Invalid Recovery Key'); - } - return value; + if (!value) { + throw new InvalidArgumentError('Invalid Recovery Key'); + } + try { + computeAddress('0x' + value); + } catch { + throw new InvalidArgumentError('Invalid Recovery Key'); + } + return value; } diff --git a/src/services/treeCache.ts b/src/services/treeCache.ts index 3e4454b..08ffce5 100644 --- a/src/services/treeCache.ts +++ b/src/services/treeCache.ts @@ -10,104 +10,104 @@ import type { NetIdType } from '@tornado/core'; import { saveUserFile } from './data'; export interface TreeCacheConstructor { - netId: NetIdType; - amount: string; - currency: string; - userDirectory: string; - PARTS_COUNT?: number; - LEAVES?: number; - zeroElement?: string; + netId: NetIdType; + amount: string; + currency: string; + userDirectory: string; + PARTS_COUNT?: number; + LEAVES?: number; + zeroElement?: string; } export interface treeMetadata { - blockNumber: number; - logIndex: number; - transactionHash: string; - timestamp: number; - from: string; - leafIndex: number; + blockNumber: number; + logIndex: number; + transactionHash: string; + timestamp: number; + from: string; + leafIndex: number; } export class TreeCache { - netId: NetIdType; - amount: string; - currency: string; - userDirectory: string; + netId: NetIdType; + amount: string; + currency: string; + userDirectory: string; - PARTS_COUNT: number; + PARTS_COUNT: number; - constructor({ netId, amount, currency, userDirectory, PARTS_COUNT = 4 }: TreeCacheConstructor) { - this.netId = netId; - this.amount = amount; - this.currency = currency; - this.userDirectory = userDirectory; + constructor({ netId, amount, currency, userDirectory, PARTS_COUNT = 4 }: TreeCacheConstructor) { + this.netId = netId; + this.amount = amount; + this.currency = currency; + this.userDirectory = userDirectory; - this.PARTS_COUNT = PARTS_COUNT; - } + this.PARTS_COUNT = PARTS_COUNT; + } - getInstanceName(): string { - return `deposits_${this.netId}_${this.currency}_${this.amount}`; - } + getInstanceName(): string { + return `deposits_${this.netId}_${this.currency}_${this.amount}`; + } - async createTree(events: DepositsEvents[], tree: MerkleTree) { - const bloom = new BloomFilter(events.length); + async createTree(events: DepositsEvents[], tree: MerkleTree) { + const bloom = new BloomFilter(events.length); - console.log(`Creating cached tree for ${this.getInstanceName()}\n`); + console.log(`Creating cached tree for ${this.getInstanceName()}\n`); - // events indexed by commitment - const eventsData = events.reduce( - (acc, { leafIndex, commitment, ...rest }, i) => { - if (leafIndex !== i) { - throw new Error(`leafIndex (${leafIndex}) !== i (${i})`); - } + // events indexed by commitment + const eventsData = events.reduce( + (acc, { leafIndex, commitment, ...rest }, i) => { + if (leafIndex !== i) { + throw new Error(`leafIndex (${leafIndex}) !== i (${i})`); + } - acc[commitment] = { ...rest, leafIndex }; + acc[commitment] = { ...rest, leafIndex }; - return acc; - }, - {} as { [key in string]: treeMetadata }, - ); - - const slices = tree.getTreeSlices(this.PARTS_COUNT); - - await Promise.all( - slices.map(async (slice, index) => { - const metadata = slice.elements.reduce((acc, curr) => { - if (index < this.PARTS_COUNT - 1) { - bloom.add(curr); - } - acc.push(eventsData[curr]); - return acc; - }, [] as treeMetadata[]); - - const dataString = - JSON.stringify( - { - ...slice, - metadata, + return acc; }, - null, - 2, - ) + '\n'; + {} as { [key in string]: treeMetadata }, + ); - const fileName = `${this.getInstanceName()}_slice${index + 1}.json`; + const slices = tree.getTreeSlices(this.PARTS_COUNT); + + await Promise.all( + slices.map(async (slice, index) => { + const metadata = slice.elements.reduce((acc, curr) => { + if (index < this.PARTS_COUNT - 1) { + bloom.add(curr); + } + acc.push(eventsData[curr]); + return acc; + }, [] as treeMetadata[]); + + const dataString = + JSON.stringify( + { + ...slice, + metadata, + }, + null, + 2, + ) + '\n'; + + const fileName = `${this.getInstanceName()}_slice${index + 1}.json`; + + await saveUserFile({ + fileName, + userDirectory: this.userDirectory, + dataString, + }); + }), + ); + + const dataString = bloom.serialize() + '\n'; + + const fileName = `${this.getInstanceName()}_bloom.json`; await saveUserFile({ - fileName, - userDirectory: this.userDirectory, - dataString, + fileName, + userDirectory: this.userDirectory, + dataString, }); - }), - ); - - const dataString = bloom.serialize() + '\n'; - - const fileName = `${this.getInstanceName()}_bloom.json`; - - await saveUserFile({ - fileName, - userDirectory: this.userDirectory, - dataString, - }); - } + } } diff --git a/src/types/bloomfilter.js.d.ts b/src/types/bloomfilter.js.d.ts index 925515d..3813634 100644 --- a/src/types/bloomfilter.js.d.ts +++ b/src/types/bloomfilter.js.d.ts @@ -1,25 +1,25 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ declare module 'bloomfilter.js' { - export default class BloomFilter { - m: number; - k: number; - size: number; - bitview: any; + export default class BloomFilter { + m: number; + k: number; + size: number; + bitview: any; - constructor(n: number, false_postive_tolerance?: number); + constructor(n: number, false_postive_tolerance?: number); - calculateHash(x: number, m: number, i: number): number; + calculateHash(x: number, m: number, i: number): number; - test(data: any): boolean; + test(data: any): boolean; - add(data: any): void; + add(data: any): void; - bytelength(): number; + bytelength(): number; - view(): Uint8Array; + view(): Uint8Array; - serialize(): string; + serialize(): string; - deserialize(serialized: string): BloomFilter; - } + deserialize(serialized: string): BloomFilter; + } } diff --git a/webpack.config.js b/webpack.config.js index 1bcd658..7e92e8c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,44 +2,44 @@ const path = require('path'); const { BannerPlugin } = require('webpack'); const esbuildLoader = { - test: /\.ts?$/, - loader: 'esbuild-loader', - options: { - loader: 'ts', - target: 'es2022', - } + test: /\.ts?$/, + loader: 'esbuild-loader', + options: { + loader: 'ts', + target: 'es2022', + } } module.exports = [ - { - mode: 'production', - module: { - rules: [esbuildLoader] - }, - entry: './src/cli.ts', - output: { - filename: 'cli.js', - path: path.resolve(__dirname, './dist'), - }, - target: 'node', - plugins: [ - new BannerPlugin({ - banner: '#!/usr/bin/env node\n', - raw: true - }) - ], - resolve: { - extensions: ['.tsx', '.ts', '.js'], - alias: { - 'fflate': 'fflate/node' - }, - fallback: { - '@tornado/websnark/src/utils': '@tornado/websnark/src/utils.js', - '@tornado/websnark/src/groth16': '@tornado/websnark/src/groth16.js', - } - }, - optimization: { - minimize: false, + { + mode: 'production', + module: { + rules: [esbuildLoader] + }, + entry: './src/cli.ts', + output: { + filename: 'cli.js', + path: path.resolve(__dirname, './dist'), + }, + target: 'node', + plugins: [ + new BannerPlugin({ + banner: '#!/usr/bin/env node\n', + raw: true + }) + ], + resolve: { + extensions: ['.tsx', '.ts', '.js'], + alias: { + 'fflate': 'fflate/node' + }, + fallback: { + '@tornado/websnark/src/utils': '@tornado/websnark/src/utils.js', + '@tornado/websnark/src/groth16': '@tornado/websnark/src/groth16.js', + } + }, + optimization: { + minimize: false, + } } - } ]; diff --git a/yarn.lock b/yarn.lock index 11649bd..60a0ec5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -785,9 +785,9 @@ "@openzeppelin/contracts-v3" "npm:@openzeppelin/contracts@3.2.0-rc.0" ethers "^6.13.4" -"@tornado/core@git+https://git.tornado.ws/tornadocontrib/tornado-core.git#8041bd7f7801fd97a87d8c1945c0251b49032ec3": +"@tornado/core@git+https://git.tornado.ws/tornadocontrib/tornado-core.git#f411159f15566cb0cfe46d07b1c2c4eb23af2e1f": version "1.0.19" - resolved "git+https://git.tornado.ws/tornadocontrib/tornado-core.git#8041bd7f7801fd97a87d8c1945c0251b49032ec3" + resolved "git+https://git.tornado.ws/tornadocontrib/tornado-core.git#f411159f15566cb0cfe46d07b1c2c4eb23af2e1f" dependencies: "@metamask/eth-sig-util" "^8.0.0" "@tornado/contracts" "git+https://git.tornado.ws/tornadocontrib/tornado-contracts.git#1b1d707878c16a3dc60d295299d4f0e7ce6ba831"