diff --git a/dist/events/base.d.ts b/dist/events/base.d.ts index 36fd0b3..e5fb683 100644 --- a/dist/events/base.d.ts +++ b/dist/events/base.d.ts @@ -1,8 +1,9 @@ import { BaseContract, Provider, EventLog, ContractEventName } from 'ethers'; -import type { Tornado, TornadoRouter, TornadoProxyLight, Governance, RelayerRegistry, Echoer } from '@tornado/contracts'; +import type { Tornado, TornadoRouter, TornadoProxyLight, Governance, RelayerRegistry, Echoer, Aggregator } from '@tornado/contracts'; import { BatchEventsService, BatchBlockService, BatchTransactionService, BatchEventOnProgress, BatchBlockOnProgress } from '../batch'; -import { fetchDataOptions } from '../providers'; -import type { NetIdType } from '../networkConfig'; +import type { fetchDataOptions } from '../providers'; +import type { NetIdType, SubdomainMap } from '../networkConfig'; +import { RelayerParams } from '../relayerClient'; import type { BaseEvents, CachedEvents, MinimalEvents, DepositsEvents, WithdrawalsEvents, EncryptedNotesEvents, AllGovernanceEvents, RegistersEvents, EchoEvents } from './types'; export declare const DEPOSIT = "deposit"; export declare const WITHDRAWAL = "withdrawal"; @@ -169,17 +170,37 @@ export declare class BaseGovernanceService extends BaseEventsService>; } +/** + * Essential params: + * ensName, relayerAddress, hostnames + * Other data is for historic purpose from relayer registry + */ +export interface CachedRelayerInfo extends RelayerParams { + isRegistered?: boolean; + owner?: string; + stakeBalance?: string; + hostnames: SubdomainMap; +} +export interface CachedRelayers { + timestamp: number; + relayers: CachedRelayerInfo[]; +} export type BaseRegistryServiceConstructor = { netId: NetIdType; provider: Provider; graphApi?: string; subgraphName?: string; RelayerRegistry: RelayerRegistry; + Aggregator: Aggregator; + relayerEnsSubdomains: SubdomainMap; deployedBlock?: number; fetchDataOptions?: fetchDataOptions; }; export declare class BaseRegistryService extends BaseEventsService { - constructor({ netId, provider, graphApi, subgraphName, RelayerRegistry, deployedBlock, fetchDataOptions, }: BaseRegistryServiceConstructor); + Aggregator: Aggregator; + relayerEnsSubdomains: SubdomainMap; + updateInterval: number; + constructor({ netId, provider, graphApi, subgraphName, RelayerRegistry, Aggregator, relayerEnsSubdomains, deployedBlock, fetchDataOptions, }: BaseRegistryServiceConstructor); getInstanceName(): string; getType(): string; getGraphMethod(): string; @@ -190,5 +211,22 @@ export declare class BaseRegistryService extends BaseEventsService; - fetchRelayers(): Promise; + /** + * Get saved or cached relayers + */ + getRelayersFromDB(): Promise; + /** + * Relayers from remote cache (Either from local cache, CDN, or from IPFS) + */ + getRelayersFromCache(): Promise; + getSavedRelayers(): Promise; + getLatestRelayers(): Promise; + /** + * Handle saving relayers + */ + saveRelayers({ timestamp, relayers }: CachedRelayers): Promise; + /** + * Get cached or latest relayer and save to local + */ + updateRelayers(): Promise; } diff --git a/dist/index.js b/dist/index.js index 6b09ad6..e70e754 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1722,13 +1722,9 @@ class BatchEventsService { } var __defProp$2 = Object.defineProperty; -var __defProps$1 = Object.defineProperties; -var __getOwnPropDescs$1 = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols$2 = Object.getOwnPropertySymbols; -var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp$2 = Object.prototype.hasOwnProperty; var __propIsEnum$2 = Object.prototype.propertyIsEnumerable; -var __reflectGet = Reflect.get; var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues$2 = (a, b) => { for (var prop in b || (b = {})) @@ -1741,598 +1737,6 @@ var __spreadValues$2 = (a, b) => { } return a; }; -var __spreadProps$1 = (a, b) => __defProps$1(a, __getOwnPropDescs$1(b)); -var __superGet = (cls, obj, key) => __reflectGet(__getProtoOf(cls), key, obj); -var __async$9 = (__this, __arguments, generator) => { - return new Promise((resolve, reject) => { - var fulfilled = (value) => { - try { - step(generator.next(value)); - } catch (e) { - reject(e); - } - }; - var rejected = (value) => { - try { - step(generator.throw(value)); - } catch (e) { - reject(e); - } - }; - var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); - step((generator = generator.apply(__this, __arguments)).next()); - }); -}; -const DEPOSIT = "deposit"; -const WITHDRAWAL = "withdrawal"; -class BaseEventsService { - constructor({ - netId, - provider, - graphApi, - subgraphName, - contract, - type = "", - deployedBlock = 0, - fetchDataOptions: fetchDataOptions2 - }) { - this.netId = netId; - this.provider = provider; - this.graphApi = graphApi; - this.subgraphName = subgraphName; - this.fetchDataOptions = fetchDataOptions2; - this.contract = contract; - this.type = type; - this.deployedBlock = deployedBlock; - this.batchEventsService = new BatchEventsService({ - provider, - contract, - onProgress: this.updateEventProgress - }); - } - getInstanceName() { - return ""; - } - getType() { - return this.type || ""; - } - getGraphMethod() { - return ""; - } - getGraphParams() { - return { - graphApi: this.graphApi || "", - subgraphName: this.subgraphName || "", - fetchDataOptions: this.fetchDataOptions, - onProgress: this.updateGraphProgress - }; - } - /* eslint-disable @typescript-eslint/no-unused-vars */ - updateEventProgress({ percentage, type, fromBlock, toBlock, count }) { - } - updateBlockProgress({ percentage, currentIndex, totalIndex }) { - } - updateTransactionProgress({ percentage, currentIndex, totalIndex }) { - } - updateGraphProgress({ type, fromBlock, toBlock, count }) { - } - /* eslint-enable @typescript-eslint/no-unused-vars */ - formatEvents(events) { - return __async$9(this, null, function* () { - return yield new Promise((resolve) => resolve(events)); - }); - } - /** - * Get saved or cached events - */ - getEventsFromDB() { - return __async$9(this, null, function* () { - return { - events: [], - lastBlock: null - }; - }); - } - /** - * Events from remote cache (Either from local cache, CDN, or from IPFS) - */ - getEventsFromCache() { - return __async$9(this, null, function* () { - return { - events: [], - lastBlock: null, - fromCache: true - }; - }); - } - getSavedEvents() { - return __async$9(this, null, function* () { - let cachedEvents = yield this.getEventsFromDB(); - if (!cachedEvents || !cachedEvents.events.length) { - cachedEvents = yield this.getEventsFromCache(); - } - return cachedEvents; - }); - } - /** - * Get latest events - */ - getEventsFromGraph(_0) { - return __async$9(this, arguments, function* ({ - fromBlock, - methodName = "" - }) { - if (!this.graphApi || !this.subgraphName) { - return { - events: [], - lastBlock: fromBlock - }; - } - const { events, lastSyncBlock } = yield graph[methodName || this.getGraphMethod()](__spreadValues$2({ - fromBlock - }, this.getGraphParams())); - return { - events, - lastBlock: lastSyncBlock - }; - }); - } - getEventsFromRpc(_0) { - return __async$9(this, arguments, function* ({ - fromBlock, - toBlock - }) { - try { - if (!toBlock) { - toBlock = yield this.provider.getBlockNumber(); - } - if (fromBlock >= toBlock) { - return { - events: [], - lastBlock: toBlock - }; - } - this.updateEventProgress({ percentage: 0, type: this.getType() }); - const events = yield this.formatEvents( - yield this.batchEventsService.getBatchEvents({ fromBlock, toBlock, type: this.getType() }) - ); - if (!events.length) { - return { - events, - lastBlock: toBlock - }; - } - return { - events, - lastBlock: toBlock - }; - } catch (err) { - console.log(err); - return { - events: [], - lastBlock: fromBlock - }; - } - }); - } - getLatestEvents(_0) { - return __async$9(this, arguments, function* ({ fromBlock }) { - const allEvents = []; - const graphEvents = yield this.getEventsFromGraph({ fromBlock }); - const lastSyncBlock = graphEvents.lastBlock && graphEvents.lastBlock >= fromBlock ? graphEvents.lastBlock : fromBlock; - const rpcEvents = yield this.getEventsFromRpc({ fromBlock: lastSyncBlock }); - allEvents.push(...graphEvents.events); - allEvents.push(...rpcEvents.events); - const lastBlock = rpcEvents ? rpcEvents.lastBlock : allEvents[allEvents.length - 1] ? allEvents[allEvents.length - 1].blockNumber : fromBlock; - return { - events: allEvents, - lastBlock - }; - }); - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - validateEvents({ events, lastBlock }) { - } - /** - * Handle saving events - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - saveEvents(_0) { - return __async$9(this, arguments, function* ({ events, lastBlock }) { - }); - } - /** - * Trigger saving and receiving latest events - */ - updateEvents() { - return __async$9(this, null, function* () { - const savedEvents = yield this.getSavedEvents(); - let fromBlock = this.deployedBlock; - if (savedEvents && savedEvents.lastBlock) { - fromBlock = savedEvents.lastBlock + 1; - } - const newEvents = yield this.getLatestEvents({ fromBlock }); - const eventSet = /* @__PURE__ */ new Set(); - let allEvents = []; - allEvents.push(...savedEvents.events); - allEvents.push(...newEvents.events); - allEvents = allEvents.sort((a, b) => { - if (a.blockNumber === b.blockNumber) { - return a.logIndex - b.logIndex; - } - return a.blockNumber - b.blockNumber; - }).filter(({ transactionHash, logIndex }) => { - const eventKey = `${transactionHash}_${logIndex}`; - const hasEvent = eventSet.has(eventKey); - eventSet.add(eventKey); - return !hasEvent; - }); - const lastBlock = newEvents ? newEvents.lastBlock : allEvents[allEvents.length - 1] ? allEvents[allEvents.length - 1].blockNumber : null; - this.validateEvents({ events: allEvents, lastBlock }); - if (savedEvents.fromCache || newEvents.events.length) { - yield this.saveEvents({ events: allEvents, lastBlock }); - } - return { - events: allEvents, - lastBlock - }; - }); - } -} -class BaseTornadoService extends BaseEventsService { - constructor({ - netId, - provider, - graphApi, - subgraphName, - Tornado, - type, - amount, - currency, - deployedBlock, - fetchDataOptions: fetchDataOptions2 - }) { - super({ netId, provider, graphApi, subgraphName, contract: Tornado, type, deployedBlock, fetchDataOptions: fetchDataOptions2 }); - this.amount = amount; - this.currency = currency; - this.batchTransactionService = new BatchTransactionService({ - provider, - onProgress: this.updateTransactionProgress - }); - this.batchBlockService = new BatchBlockService({ - provider, - onProgress: this.updateBlockProgress - }); - } - getInstanceName() { - return `${this.getType().toLowerCase()}s_${this.netId}_${this.currency}_${this.amount}`; - } - getGraphMethod() { - return `getAll${this.getType()}s`; - } - getGraphParams() { - return { - graphApi: this.graphApi || "", - subgraphName: this.subgraphName || "", - amount: this.amount, - currency: this.currency, - fetchDataOptions: this.fetchDataOptions, - onProgress: this.updateGraphProgress - }; - } - formatEvents(events) { - return __async$9(this, null, function* () { - 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 = yield this.batchTransactionService.getBatchTransactions([ - ...new Set(formattedEvents.map(({ transactionHash }) => transactionHash)) - ]); - return formattedEvents.map((event) => { - const { from } = txs.find(({ hash }) => hash === event.transactionHash); - return __spreadProps$1(__spreadValues$2({}, 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: ethers.getAddress(to), - fee: String(fee) - }; - }); - const blocks = yield this.batchBlockService.getBatchBlocks([ - ...new Set(formattedEvents.map(({ blockNumber }) => blockNumber)) - ]); - return formattedEvents.map((event) => { - const { timestamp } = blocks.find(({ number }) => number === event.blockNumber); - return __spreadProps$1(__spreadValues$2({}, event), { - timestamp - }); - }); - } - }); - } - validateEvents({ events }) { - if (events.length && this.getType().toLowerCase() === DEPOSIT) { - const lastEvent = events[events.length - 1]; - if (lastEvent.leafIndex !== events.length - 1) { - const errMsg = `Deposit events invalid wants ${events.length - 1} leafIndex have ${lastEvent.leafIndex}`; - throw new Error(errMsg); - } - } - } -} -class BaseEchoService extends BaseEventsService { - constructor({ - netId, - provider, - graphApi, - subgraphName, - Echoer, - deployedBlock, - fetchDataOptions: fetchDataOptions2 - }) { - super({ netId, provider, graphApi, subgraphName, contract: Echoer, deployedBlock, fetchDataOptions: fetchDataOptions2 }); - } - getInstanceName() { - return `echo_${this.netId}`; - } - getType() { - return "Echo"; - } - getGraphMethod() { - return "getAllGraphEchoEvents"; - } - formatEvents(events) { - return __async$9(this, null, function* () { - return events.map(({ blockNumber, index: logIndex, transactionHash, args }) => { - const { who, data } = args; - if (who && data) { - const eventObjects = { - blockNumber, - logIndex, - transactionHash - }; - return __spreadProps$1(__spreadValues$2({}, eventObjects), { - address: who, - encryptedAccount: data - }); - } - }).filter((e) => e); - }); - } - getEventsFromGraph(_0) { - return __async$9(this, arguments, function* ({ fromBlock }) { - if (!this.graphApi || this.graphApi.includes("api.thegraph.com")) { - return { - events: [], - lastBlock: fromBlock - }; - } - return __superGet(BaseEchoService.prototype, this, "getEventsFromGraph").call(this, { fromBlock }); - }); - } -} -class BaseEncryptedNotesService extends BaseEventsService { - constructor({ - netId, - provider, - graphApi, - subgraphName, - Router, - deployedBlock, - fetchDataOptions: fetchDataOptions2 - }) { - super({ netId, provider, graphApi, subgraphName, contract: Router, deployedBlock, fetchDataOptions: fetchDataOptions2 }); - } - getInstanceName() { - return `encrypted_notes_${this.netId}`; - } - getType() { - return "EncryptedNote"; - } - getGraphMethod() { - return "getAllEncryptedNotes"; - } - formatEvents(events) { - return __async$9(this, null, function* () { - return events.map(({ blockNumber, index: logIndex, transactionHash, args }) => { - const { encryptedNote } = args; - if (encryptedNote) { - const eventObjects = { - blockNumber, - logIndex, - transactionHash - }; - return __spreadProps$1(__spreadValues$2({}, eventObjects), { - encryptedNote - }); - } - }).filter((e) => e); - }); - } -} -class BaseGovernanceService extends BaseEventsService { - constructor({ - netId, - provider, - graphApi, - subgraphName, - Governance, - deployedBlock, - fetchDataOptions: fetchDataOptions2 - }) { - super({ netId, provider, graphApi, subgraphName, contract: Governance, deployedBlock, fetchDataOptions: fetchDataOptions2 }); - this.batchTransactionService = new BatchTransactionService({ - provider, - onProgress: this.updateTransactionProgress - }); - } - getInstanceName() { - return `governance_${this.netId}`; - } - getType() { - return "*"; - } - getGraphMethod() { - return "getAllGovernanceEvents"; - } - formatEvents(events) { - return __async$9(this, null, function* () { - const proposalEvents = []; - const votedEvents = []; - const delegatedEvents = []; - const undelegatedEvents = []; - events.forEach(({ blockNumber, index: logIndex, transactionHash, args, eventName: event }) => { - const eventObjects = { - blockNumber, - logIndex, - transactionHash, - event - }; - if (event === "ProposalCreated") { - const { id, proposer, target, startTime, endTime, description } = args; - proposalEvents.push(__spreadProps$1(__spreadValues$2({}, eventObjects), { - id: Number(id), - proposer, - target, - startTime: Number(startTime), - endTime: Number(endTime), - description - })); - } - if (event === "Voted") { - const { proposalId, voter, support, votes } = args; - votedEvents.push(__spreadProps$1(__spreadValues$2({}, eventObjects), { - proposalId: Number(proposalId), - voter, - support, - votes, - from: "", - input: "" - })); - } - if (event === "Delegated") { - const { account, to: delegateTo } = args; - delegatedEvents.push(__spreadProps$1(__spreadValues$2({}, eventObjects), { - account, - delegateTo - })); - } - if (event === "Undelegated") { - const { account, from: delegateFrom } = args; - undelegatedEvents.push(__spreadProps$1(__spreadValues$2({}, eventObjects), { - account, - delegateFrom - })); - } - }); - if (votedEvents.length) { - this.updateTransactionProgress({ percentage: 0 }); - const txs = yield this.batchTransactionService.getBatchTransactions([ - ...new Set(votedEvents.map(({ transactionHash }) => transactionHash)) - ]); - votedEvents.forEach((event, index) => { - let { data: input, from } = txs.find((t) => t.hash === event.transactionHash); - if (!input || input.length > 2048) { - input = ""; - } - votedEvents[index].from = from; - votedEvents[index].input = input; - }); - } - return [...proposalEvents, ...votedEvents, ...delegatedEvents, ...undelegatedEvents]; - }); - } - getEventsFromGraph(_0) { - return __async$9(this, arguments, function* ({ fromBlock }) { - if (!this.graphApi || !this.subgraphName || this.graphApi.includes("api.thegraph.com")) { - return { - events: [], - lastBlock: fromBlock - }; - } - return __superGet(BaseGovernanceService.prototype, this, "getEventsFromGraph").call(this, { fromBlock }); - }); - } -} -class BaseRegistryService extends BaseEventsService { - constructor({ - netId, - provider, - graphApi, - subgraphName, - RelayerRegistry, - deployedBlock, - fetchDataOptions: fetchDataOptions2 - }) { - super({ netId, provider, graphApi, subgraphName, contract: RelayerRegistry, deployedBlock, fetchDataOptions: fetchDataOptions2 }); - } - getInstanceName() { - return `registered_${this.netId}`; - } - // Name of type used for events - getType() { - return "RelayerRegistered"; - } - // Name of method used for graph - getGraphMethod() { - return "getAllRegisters"; - } - formatEvents(events) { - return __async$9(this, null, function* () { - return events.map(({ blockNumber, index: logIndex, transactionHash, args }) => { - const eventObjects = { - blockNumber, - logIndex, - transactionHash - }; - return __spreadProps$1(__spreadValues$2({}, eventObjects), { - ensName: args.ensName, - relayerAddress: args.relayerAddress - }); - }); - }); - } - fetchRelayers() { - return __async$9(this, null, function* () { - return (yield this.updateEvents()).events; - }); - } -} - -var __defProp$1 = Object.defineProperty; -var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols; -var __hasOwnProp$1 = Object.prototype.hasOwnProperty; -var __propIsEnum$1 = Object.prototype.propertyIsEnumerable; -var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; -var __spreadValues$1 = (a, b) => { - for (var prop in b || (b = {})) - if (__hasOwnProp$1.call(b, prop)) - __defNormalProp$1(a, prop, b[prop]); - if (__getOwnPropSymbols$1) - for (var prop of __getOwnPropSymbols$1(b)) { - if (__propIsEnum$1.call(b, prop)) - __defNormalProp$1(a, prop, b[prop]); - } - return a; -}; var NetId = /* @__PURE__ */ ((NetId2) => { NetId2[NetId2["MAINNET"] = 1] = "MAINNET"; NetId2[NetId2["BSC"] = 56] = "BSC"; @@ -2853,10 +2257,6 @@ const defaultConfig = { tornadoSubgraph: "tornadocash/sepolia-tornado-subgraph", subgraphs: {}, rpcUrls: { - pandaops: { - name: "ethpandaops", - url: "https://rpc.sepolia.ethpandaops.io" - }, sepolia: { name: "Sepolia RPC", url: "https://rpc.sepolia.org" @@ -2868,6 +2268,10 @@ const defaultConfig = { onerpc: { name: "1rpc", url: "https://1rpc.io/sepolia" + }, + ethpandaops: { + name: "ethpandaops", + url: "https://rpc.sepolia.ethpandaops.io" } }, tokens: { @@ -2911,10 +2315,10 @@ function addNetwork(newConfig) { enabledChains.push( ...Object.keys(newConfig).map((netId) => Number(netId)).filter((netId) => !enabledChains.includes(netId)) ); - exports.customConfig = __spreadValues$1(__spreadValues$1({}, exports.customConfig), newConfig); + exports.customConfig = __spreadValues$2(__spreadValues$2({}, exports.customConfig), newConfig); } function getNetworkConfig() { - const allConfig = __spreadValues$1(__spreadValues$1({}, defaultConfig), exports.customConfig); + const allConfig = __spreadValues$2(__spreadValues$2({}, defaultConfig), exports.customConfig); return enabledChains.reduce((acc, curr) => { acc[curr] = allConfig[curr]; return acc; @@ -2946,6 +2350,13 @@ function getSubdomains() { const allConfig = getNetworkConfig(); return enabledChains.map((chain) => allConfig[chain].relayerEnsSubdomain); } +function getRelayerEnsSubdomains() { + const allConfig = getNetworkConfig(); + return Object.keys(allConfig).reduce((acc, chain) => { + acc[Number(chain)] = allConfig[Number(chain)].relayerEnsSubdomain; + return acc; + }, {}); +} const addressType = { type: "string", pattern: "^0x[a-fA-F0-9]{40}$" }; const bnType = { type: "string", BN: true }; @@ -3071,6 +2482,950 @@ ajv.addKeyword({ errors: true }); +var __defProp$1 = Object.defineProperty; +var __defProps$1 = Object.defineProperties; +var __getOwnPropDescs$1 = Object.getOwnPropertyDescriptors; +var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols; +var __hasOwnProp$1 = Object.prototype.hasOwnProperty; +var __propIsEnum$1 = Object.prototype.propertyIsEnumerable; +var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __spreadValues$1 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$1.call(b, prop)) + __defNormalProp$1(a, prop, b[prop]); + if (__getOwnPropSymbols$1) + for (var prop of __getOwnPropSymbols$1(b)) { + if (__propIsEnum$1.call(b, prop)) + __defNormalProp$1(a, prop, b[prop]); + } + return a; +}; +var __spreadProps$1 = (a, b) => __defProps$1(a, __getOwnPropDescs$1(b)); +var __async$9 = (__this, __arguments, generator) => { + return new Promise((resolve, reject) => { + var fulfilled = (value) => { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + }; + var rejected = (value) => { + try { + step(generator.throw(value)); + } catch (e) { + reject(e); + } + }; + var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); + step((generator = generator.apply(__this, __arguments)).next()); + }); +}; +const MIN_FEE = 0.1; +const MAX_FEE = 0.6; +const MIN_STAKE_BALANCE = ethers.parseEther("500"); +const semVerRegex = new RegExp("^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)(?:-(?(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); +function parseSemanticVersion(version) { + const { groups } = semVerRegex.exec(version); + return groups; +} +function isRelayerUpdated(relayerVersion, netId) { + const { major, patch, prerelease } = parseSemanticVersion(relayerVersion); + const requiredMajor = netId === NetId.MAINNET ? "4" : "5"; + const isUpdatedMajor = major === requiredMajor; + if (prerelease) return false; + return isUpdatedMajor && (Number(patch) >= 5 || netId !== NetId.MAINNET); +} +function calculateScore({ stakeBalance, tornadoServiceFee }) { + if (tornadoServiceFee < MIN_FEE) { + tornadoServiceFee = MIN_FEE; + } else if (tornadoServiceFee >= MAX_FEE) { + return BigInt(0); + } + 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)); +} +function getWeightRandom(weightsScores, random) { + for (let i = 0; i < weightsScores.length; i++) { + if (random < weightsScores[i]) { + return i; + } + random = random - weightsScores[i]; + } + return Math.floor(Math.random() * weightsScores.length); +} +function getSupportedInstances(instanceList) { + const rawList = Object.values(instanceList).map(({ instanceAddress }) => { + return Object.values(instanceAddress); + }).flat(); + return rawList.map((l) => ethers.getAddress(l)); +} +function pickWeightedRandomRelayer(relayers) { + const weightsScores = relayers.map((el) => calculateScore(el)); + const totalWeight = weightsScores.reduce((acc, curr) => { + return acc = acc + curr; + }, BigInt("0")); + const random = BigInt(Math.floor(Number(totalWeight) * Math.random())); + const weightRandomIndex = getWeightRandom(weightsScores, random); + return relayers[weightRandomIndex]; +} +class RelayerClient { + constructor({ netId, config, fetchDataOptions: fetchDataOptions2 }) { + this.netId = netId; + this.config = config; + this.fetchDataOptions = fetchDataOptions2; + } + askRelayerStatus(_0) { + return __async$9(this, arguments, function* ({ + hostname, + relayerAddress + }) { + var _a, _b; + const url = `https://${!hostname.endsWith("/") ? hostname + "/" : hostname}`; + const rawStatus = yield fetchData(`${url}status`, __spreadProps$1(__spreadValues$1({}, this.fetchDataOptions), { + headers: { + "Content-Type": "application/json, application/x-www-form-urlencoded" + }, + timeout: ((_a = this.fetchDataOptions) == null ? void 0 : _a.torPort) ? 1e4 : 3e3, + maxRetry: ((_b = this.fetchDataOptions) == null ? void 0 : _b.torPort) ? 2 : 0 + })); + const statusValidator = ajv.compile(getStatusSchema(this.netId, this.config)); + if (!statusValidator(rawStatus)) { + throw new Error("Invalid status schema"); + } + const status = __spreadProps$1(__spreadValues$1({}, rawStatus), { + url + }); + if (status.currentQueue > 5) { + throw new Error("Withdrawal queue is overloaded"); + } + if (status.netId !== this.netId) { + 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"); + } + if (!isRelayerUpdated(status.version, this.netId)) { + throw new Error("Outdated version."); + } + return status; + }); + } + filterRelayer(relayer) { + return __async$9(this, null, function* () { + var _a; + const hostname = relayer.hostnames[this.netId]; + const { ensName, relayerAddress } = relayer; + if (!hostname) { + return; + } + try { + const status = yield this.askRelayerStatus({ hostname, relayerAddress }); + return { + netId: status.netId, + url: status.url, + hostname, + ensName, + relayerAddress, + rewardAccount: ethers.getAddress(status.rewardAccount), + instances: getSupportedInstances(status.instances), + stakeBalance: relayer.stakeBalance, + gasPrice: (_a = status.gasPrices) == null ? void 0 : _a.fast, + ethPrices: status.ethPrices, + currentQueue: status.currentQueue, + tornadoServiceFee: status.tornadoServiceFee + }; + } catch (err) { + return { + hostname, + relayerAddress, + errorMessage: err.message, + hasError: true + }; + } + }); + } + getValidRelayers(relayers) { + return __async$9(this, null, function* () { + const invalidRelayers = []; + const validRelayers = (yield Promise.all(relayers.map((relayer) => this.filterRelayer(relayer)))).filter((r) => { + if (!r) { + return false; + } + if (r.hasError) { + invalidRelayers.push(r); + return false; + } + return true; + }); + return { + validRelayers, + invalidRelayers + }; + }); + } + pickWeightedRandomRelayer(relayers) { + return pickWeightedRandomRelayer(relayers); + } + tornadoWithdraw(_0) { + return __async$9(this, arguments, function* ({ contract, proof, args }) { + const { url } = this.selectedRelayer; + const withdrawResponse = yield fetchData(`${url}v1/tornadoWithdraw`, __spreadProps$1(__spreadValues$1({}, this.fetchDataOptions), { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + contract, + proof, + args + }) + })); + const { id, error } = withdrawResponse; + if (error) { + throw new Error(error); + } + let relayerStatus; + const jobUrl = `${url}v1/jobs/${id}`; + console.log(`Job submitted: ${jobUrl} +`); + while (!relayerStatus || !["FAILED", "CONFIRMED"].includes(relayerStatus)) { + const jobResponse = yield fetchData(jobUrl, __spreadProps$1(__spreadValues$1({}, this.fetchDataOptions), { + method: "GET", + headers: { + "Content-Type": "application/json" + } + })); + if (jobResponse.error) { + throw new Error(error); + } + const jobValidator = ajv.compile(jobsSchema); + if (!jobValidator(jobResponse)) { + const errMsg = `${jobUrl} has an invalid job response`; + throw new Error(errMsg); + } + const { status, txHash, confirmations, failedReason } = jobResponse; + if (relayerStatus !== status) { + if (status === "FAILED") { + const errMsg = `Job ${status}: ${jobUrl} failed reason: ${failedReason}`; + throw new Error(errMsg); + } else if (status === "SENT") { + console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash} +`); + } else if (status === "MINED") { + console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations} +`); + } else if (status === "CONFIRMED") { + console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations} +`); + } else { + console.log(`Job ${status}: ${jobUrl} +`); + } + relayerStatus = status; + } + yield sleep(3e3); + } + }); + } +} + +var __defProp = Object.defineProperty; +var __defProps = Object.defineProperties; +var __getOwnPropDescs = Object.getOwnPropertyDescriptors; +var __getOwnPropSymbols = Object.getOwnPropertySymbols; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __propIsEnum = Object.prototype.propertyIsEnumerable; +var __reflectGet = Reflect.get; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __spreadValues = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + if (__getOwnPropSymbols) + for (var prop of __getOwnPropSymbols(b)) { + if (__propIsEnum.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + } + return a; +}; +var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); +var __superGet = (cls, obj, key) => __reflectGet(__getProtoOf(cls), key, obj); +var __async$8 = (__this, __arguments, generator) => { + return new Promise((resolve, reject) => { + var fulfilled = (value) => { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + }; + var rejected = (value) => { + try { + step(generator.throw(value)); + } catch (e) { + reject(e); + } + }; + var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); + step((generator = generator.apply(__this, __arguments)).next()); + }); +}; +const DEPOSIT = "deposit"; +const WITHDRAWAL = "withdrawal"; +class BaseEventsService { + constructor({ + netId, + provider, + graphApi, + subgraphName, + contract, + type = "", + deployedBlock = 0, + fetchDataOptions + }) { + this.netId = netId; + this.provider = provider; + this.graphApi = graphApi; + this.subgraphName = subgraphName; + this.fetchDataOptions = fetchDataOptions; + this.contract = contract; + this.type = type; + this.deployedBlock = deployedBlock; + this.batchEventsService = new BatchEventsService({ + provider, + contract, + onProgress: this.updateEventProgress + }); + } + getInstanceName() { + return ""; + } + getType() { + return this.type || ""; + } + getGraphMethod() { + return ""; + } + getGraphParams() { + return { + graphApi: this.graphApi || "", + subgraphName: this.subgraphName || "", + fetchDataOptions: this.fetchDataOptions, + onProgress: this.updateGraphProgress + }; + } + /* eslint-disable @typescript-eslint/no-unused-vars */ + updateEventProgress({ percentage, type, fromBlock, toBlock, count }) { + } + updateBlockProgress({ percentage, currentIndex, totalIndex }) { + } + updateTransactionProgress({ percentage, currentIndex, totalIndex }) { + } + updateGraphProgress({ type, fromBlock, toBlock, count }) { + } + /* eslint-enable @typescript-eslint/no-unused-vars */ + formatEvents(events) { + return __async$8(this, null, function* () { + return yield new Promise((resolve) => resolve(events)); + }); + } + /** + * Get saved or cached events + */ + getEventsFromDB() { + return __async$8(this, null, function* () { + return { + events: [], + lastBlock: null + }; + }); + } + /** + * Events from remote cache (Either from local cache, CDN, or from IPFS) + */ + getEventsFromCache() { + return __async$8(this, null, function* () { + return { + events: [], + lastBlock: null, + fromCache: true + }; + }); + } + getSavedEvents() { + return __async$8(this, null, function* () { + let cachedEvents = yield this.getEventsFromDB(); + if (!cachedEvents || !cachedEvents.events.length) { + cachedEvents = yield this.getEventsFromCache(); + } + return cachedEvents; + }); + } + /** + * Get latest events + */ + getEventsFromGraph(_0) { + return __async$8(this, arguments, function* ({ + fromBlock, + methodName = "" + }) { + if (!this.graphApi || !this.subgraphName) { + return { + events: [], + lastBlock: fromBlock + }; + } + const { events, lastSyncBlock } = yield graph[methodName || this.getGraphMethod()](__spreadValues({ + fromBlock + }, this.getGraphParams())); + return { + events, + lastBlock: lastSyncBlock + }; + }); + } + getEventsFromRpc(_0) { + return __async$8(this, arguments, function* ({ + fromBlock, + toBlock + }) { + try { + if (!toBlock) { + toBlock = yield this.provider.getBlockNumber(); + } + if (fromBlock >= toBlock) { + return { + events: [], + lastBlock: toBlock + }; + } + this.updateEventProgress({ percentage: 0, type: this.getType() }); + const events = yield this.formatEvents( + yield this.batchEventsService.getBatchEvents({ fromBlock, toBlock, type: this.getType() }) + ); + if (!events.length) { + return { + events, + lastBlock: toBlock + }; + } + return { + events, + lastBlock: toBlock + }; + } catch (err) { + console.log(err); + return { + events: [], + lastBlock: fromBlock + }; + } + }); + } + getLatestEvents(_0) { + return __async$8(this, arguments, function* ({ fromBlock }) { + const allEvents = []; + const graphEvents = yield this.getEventsFromGraph({ fromBlock }); + const lastSyncBlock = graphEvents.lastBlock && graphEvents.lastBlock >= fromBlock ? graphEvents.lastBlock : fromBlock; + const rpcEvents = yield this.getEventsFromRpc({ fromBlock: lastSyncBlock }); + allEvents.push(...graphEvents.events); + allEvents.push(...rpcEvents.events); + const lastBlock = rpcEvents ? rpcEvents.lastBlock : allEvents[allEvents.length - 1] ? allEvents[allEvents.length - 1].blockNumber : fromBlock; + return { + events: allEvents, + lastBlock + }; + }); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + validateEvents({ events, lastBlock }) { + } + /** + * Handle saving events + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + saveEvents(_0) { + return __async$8(this, arguments, function* ({ events, lastBlock }) { + }); + } + /** + * Trigger saving and receiving latest events + */ + updateEvents() { + return __async$8(this, null, function* () { + const savedEvents = yield this.getSavedEvents(); + let fromBlock = this.deployedBlock; + if (savedEvents && savedEvents.lastBlock) { + fromBlock = savedEvents.lastBlock + 1; + } + const newEvents = yield this.getLatestEvents({ fromBlock }); + const eventSet = /* @__PURE__ */ new Set(); + let allEvents = []; + allEvents.push(...savedEvents.events); + allEvents.push(...newEvents.events); + allEvents = allEvents.sort((a, b) => { + if (a.blockNumber === b.blockNumber) { + return a.logIndex - b.logIndex; + } + return a.blockNumber - b.blockNumber; + }).filter(({ transactionHash, logIndex }) => { + const eventKey = `${transactionHash}_${logIndex}`; + const hasEvent = eventSet.has(eventKey); + eventSet.add(eventKey); + return !hasEvent; + }); + const lastBlock = newEvents ? newEvents.lastBlock : allEvents[allEvents.length - 1] ? allEvents[allEvents.length - 1].blockNumber : null; + this.validateEvents({ events: allEvents, lastBlock }); + if (savedEvents.fromCache || newEvents.events.length) { + yield this.saveEvents({ events: allEvents, lastBlock }); + } + return { + events: allEvents, + lastBlock + }; + }); + } +} +class BaseTornadoService extends BaseEventsService { + constructor({ + netId, + provider, + graphApi, + subgraphName, + Tornado, + type, + amount, + currency, + deployedBlock, + fetchDataOptions + }) { + super({ netId, provider, graphApi, subgraphName, contract: Tornado, type, deployedBlock, fetchDataOptions }); + this.amount = amount; + this.currency = currency; + this.batchTransactionService = new BatchTransactionService({ + provider, + onProgress: this.updateTransactionProgress + }); + this.batchBlockService = new BatchBlockService({ + provider, + onProgress: this.updateBlockProgress + }); + } + getInstanceName() { + return `${this.getType().toLowerCase()}s_${this.netId}_${this.currency}_${this.amount}`; + } + getGraphMethod() { + return `getAll${this.getType()}s`; + } + getGraphParams() { + return { + graphApi: this.graphApi || "", + subgraphName: this.subgraphName || "", + amount: this.amount, + currency: this.currency, + fetchDataOptions: this.fetchDataOptions, + onProgress: this.updateGraphProgress + }; + } + formatEvents(events) { + return __async$8(this, null, function* () { + 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 = yield this.batchTransactionService.getBatchTransactions([ + ...new Set(formattedEvents.map(({ transactionHash }) => transactionHash)) + ]); + return formattedEvents.map((event) => { + const { from } = txs.find(({ hash }) => hash === event.transactionHash); + return __spreadProps(__spreadValues({}, 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: ethers.getAddress(to), + fee: String(fee) + }; + }); + const blocks = yield this.batchBlockService.getBatchBlocks([ + ...new Set(formattedEvents.map(({ blockNumber }) => blockNumber)) + ]); + return formattedEvents.map((event) => { + const { timestamp } = blocks.find(({ number }) => number === event.blockNumber); + return __spreadProps(__spreadValues({}, event), { + timestamp + }); + }); + } + }); + } + validateEvents({ events }) { + if (events.length && this.getType().toLowerCase() === DEPOSIT) { + const lastEvent = events[events.length - 1]; + if (lastEvent.leafIndex !== events.length - 1) { + const errMsg = `Deposit events invalid wants ${events.length - 1} leafIndex have ${lastEvent.leafIndex}`; + throw new Error(errMsg); + } + } + } +} +class BaseEchoService extends BaseEventsService { + constructor({ + netId, + provider, + graphApi, + subgraphName, + Echoer, + deployedBlock, + fetchDataOptions + }) { + super({ netId, provider, graphApi, subgraphName, contract: Echoer, deployedBlock, fetchDataOptions }); + } + getInstanceName() { + return `echo_${this.netId}`; + } + getType() { + return "Echo"; + } + getGraphMethod() { + return "getAllGraphEchoEvents"; + } + formatEvents(events) { + return __async$8(this, null, function* () { + return events.map(({ blockNumber, index: logIndex, transactionHash, args }) => { + const { who, data } = args; + if (who && data) { + const eventObjects = { + blockNumber, + logIndex, + transactionHash + }; + return __spreadProps(__spreadValues({}, eventObjects), { + address: who, + encryptedAccount: data + }); + } + }).filter((e) => e); + }); + } + getEventsFromGraph(_0) { + return __async$8(this, arguments, function* ({ fromBlock }) { + if (!this.graphApi || this.graphApi.includes("api.thegraph.com")) { + return { + events: [], + lastBlock: fromBlock + }; + } + return __superGet(BaseEchoService.prototype, this, "getEventsFromGraph").call(this, { fromBlock }); + }); + } +} +class BaseEncryptedNotesService extends BaseEventsService { + constructor({ + netId, + provider, + graphApi, + subgraphName, + Router, + deployedBlock, + fetchDataOptions + }) { + super({ netId, provider, graphApi, subgraphName, contract: Router, deployedBlock, fetchDataOptions }); + } + getInstanceName() { + return `encrypted_notes_${this.netId}`; + } + getType() { + return "EncryptedNote"; + } + getGraphMethod() { + return "getAllEncryptedNotes"; + } + formatEvents(events) { + return __async$8(this, null, function* () { + return events.map(({ blockNumber, index: logIndex, transactionHash, args }) => { + const { encryptedNote } = args; + if (encryptedNote) { + const eventObjects = { + blockNumber, + logIndex, + transactionHash + }; + return __spreadProps(__spreadValues({}, eventObjects), { + encryptedNote + }); + } + }).filter((e) => e); + }); + } +} +class BaseGovernanceService extends BaseEventsService { + constructor({ + netId, + provider, + graphApi, + subgraphName, + Governance, + deployedBlock, + fetchDataOptions + }) { + super({ netId, provider, graphApi, subgraphName, contract: Governance, deployedBlock, fetchDataOptions }); + this.batchTransactionService = new BatchTransactionService({ + provider, + onProgress: this.updateTransactionProgress + }); + } + getInstanceName() { + return `governance_${this.netId}`; + } + getType() { + return "*"; + } + getGraphMethod() { + return "getAllGovernanceEvents"; + } + formatEvents(events) { + return __async$8(this, null, function* () { + const proposalEvents = []; + const votedEvents = []; + const delegatedEvents = []; + const undelegatedEvents = []; + events.forEach(({ blockNumber, index: logIndex, transactionHash, args, eventName: event }) => { + const eventObjects = { + blockNumber, + logIndex, + transactionHash, + event + }; + if (event === "ProposalCreated") { + const { id, proposer, target, startTime, endTime, description } = args; + proposalEvents.push(__spreadProps(__spreadValues({}, eventObjects), { + id: Number(id), + proposer, + target, + startTime: Number(startTime), + endTime: Number(endTime), + description + })); + } + if (event === "Voted") { + const { proposalId, voter, support, votes } = args; + votedEvents.push(__spreadProps(__spreadValues({}, eventObjects), { + proposalId: Number(proposalId), + voter, + support, + votes, + from: "", + input: "" + })); + } + if (event === "Delegated") { + const { account, to: delegateTo } = args; + delegatedEvents.push(__spreadProps(__spreadValues({}, eventObjects), { + account, + delegateTo + })); + } + if (event === "Undelegated") { + const { account, from: delegateFrom } = args; + undelegatedEvents.push(__spreadProps(__spreadValues({}, eventObjects), { + account, + delegateFrom + })); + } + }); + if (votedEvents.length) { + this.updateTransactionProgress({ percentage: 0 }); + const txs = yield this.batchTransactionService.getBatchTransactions([ + ...new Set(votedEvents.map(({ transactionHash }) => transactionHash)) + ]); + votedEvents.forEach((event, index) => { + let { data: input, from } = txs.find((t) => t.hash === event.transactionHash); + if (!input || input.length > 2048) { + input = ""; + } + votedEvents[index].from = from; + votedEvents[index].input = input; + }); + } + return [...proposalEvents, ...votedEvents, ...delegatedEvents, ...undelegatedEvents]; + }); + } + getEventsFromGraph(_0) { + return __async$8(this, arguments, function* ({ fromBlock }) { + if (!this.graphApi || !this.subgraphName || this.graphApi.includes("api.thegraph.com")) { + return { + events: [], + lastBlock: fromBlock + }; + } + return __superGet(BaseGovernanceService.prototype, this, "getEventsFromGraph").call(this, { fromBlock }); + }); + } +} +class BaseRegistryService extends BaseEventsService { + constructor({ + netId, + provider, + graphApi, + subgraphName, + RelayerRegistry, + Aggregator, + relayerEnsSubdomains, + deployedBlock, + fetchDataOptions + }) { + super({ netId, provider, graphApi, subgraphName, contract: RelayerRegistry, deployedBlock, fetchDataOptions }); + this.Aggregator = Aggregator; + this.relayerEnsSubdomains = relayerEnsSubdomains; + this.updateInterval = 86400; + } + getInstanceName() { + return `registered_${this.netId}`; + } + // Name of type used for events + getType() { + return "RelayerRegistered"; + } + // Name of method used for graph + getGraphMethod() { + return "getAllRegisters"; + } + formatEvents(events) { + return __async$8(this, null, function* () { + return events.map(({ blockNumber, index: logIndex, transactionHash, args }) => { + const eventObjects = { + blockNumber, + logIndex, + transactionHash + }; + return __spreadProps(__spreadValues({}, eventObjects), { + ensName: args.ensName, + relayerAddress: args.relayerAddress + }); + }); + }); + } + /** + * Get saved or cached relayers + */ + getRelayersFromDB() { + return __async$8(this, null, function* () { + return { + timestamp: 0, + relayers: [] + }; + }); + } + /** + * Relayers from remote cache (Either from local cache, CDN, or from IPFS) + */ + getRelayersFromCache() { + return __async$8(this, null, function* () { + return { + timestamp: 0, + relayers: [] + }; + }); + } + getSavedRelayers() { + return __async$8(this, null, function* () { + let cachedRelayers = yield this.getRelayersFromDB(); + if (!cachedRelayers || !cachedRelayers.relayers.length) { + cachedRelayers = yield this.getRelayersFromCache(); + } + return cachedRelayers; + }); + } + getLatestRelayers() { + return __async$8(this, null, function* () { + const registerEvents = (yield this.updateEvents()).events; + const subdomains = Object.values(this.relayerEnsSubdomains); + const registerSet = /* @__PURE__ */ new Set(); + const uniqueRegisters = registerEvents.reverse().filter(({ ensName }) => { + if (!registerSet.has(ensName)) { + registerSet.add(ensName); + return true; + } + return false; + }); + const relayerNameHashes = uniqueRegisters.map((r) => ethers.namehash(r.ensName)); + const [relayersData, timestamp] = yield Promise.all([ + this.Aggregator.relayersData.staticCall(relayerNameHashes, subdomains), + this.provider.getBlock("latest").then((b) => Number(b == null ? void 0 : b.timestamp)) + ]); + const relayers = relayersData.map(({ owner, balance: stakeBalance, records, isRegistered }, index) => { + const { ensName, relayerAddress } = uniqueRegisters[index]; + const hostnames = {}; + records.forEach((record, recordIndex) => { + if (record) { + hostnames[Number(Object.keys(this.relayerEnsSubdomains)[recordIndex])] = record; + } + }); + const isOwner = !relayerAddress || relayerAddress === owner; + const hasMinBalance = stakeBalance >= MIN_STAKE_BALANCE; + const preCondition = Object.keys(hostnames).length && isOwner && isRegistered && hasMinBalance; + if (preCondition) { + return { + ensName, + relayerAddress, + isRegistered, + owner, + stakeBalance: ethers.formatEther(stakeBalance), + hostnames + }; + } + }).filter((r) => r); + return { + timestamp, + relayers + }; + }); + } + /** + * Handle saving relayers + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + saveRelayers(_0) { + return __async$8(this, arguments, function* ({ timestamp, relayers }) { + }); + } + /** + * Get cached or latest relayer and save to local + */ + updateRelayers() { + return __async$8(this, null, function* () { + let { timestamp, relayers } = yield this.getSavedRelayers(); + if (!relayers.length || timestamp + this.updateInterval < Math.floor(Date.now() / 1e3)) { + console.log("\nUpdating relayers from registry\n"); + ({ timestamp, relayers } = yield this.getLatestRelayers()); + yield this.saveRelayers({ timestamp, relayers }); + } + return { timestamp, relayers }; + }); + } +} + const _abi$5 = [ { constant: true, @@ -5408,7 +5763,7 @@ var index = /*#__PURE__*/Object.freeze({ ReverseRecords__factory: ReverseRecords__factory }); -var __async$8 = (__this, __arguments, generator) => { +var __async$7 = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { @@ -5433,13 +5788,13 @@ class Pedersen { this.pedersenPromise = this.initPedersen(); } initPedersen() { - return __async$8(this, null, function* () { + return __async$7(this, null, function* () { this.pedersenHash = yield circomlibjs.buildPedersenHash(); this.babyJub = this.pedersenHash.babyJub; }); } unpackPoint(buffer) { - return __async$8(this, null, function* () { + return __async$7(this, null, function* () { var _a, _b; yield this.pedersenPromise; return (_b = this.babyJub) == null ? void 0 : _b.unpackPoint((_a = this.pedersenHash) == null ? void 0 : _a.hash(buffer)); @@ -5452,13 +5807,13 @@ class Pedersen { } const pedersen = new Pedersen(); function buffPedersenHash(buffer) { - return __async$8(this, null, function* () { + return __async$7(this, null, function* () { const [hash] = yield pedersen.unpackPoint(buffer); return pedersen.toStringBuffer(hash); }); } -var __async$7 = (__this, __arguments, generator) => { +var __async$6 = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { @@ -5479,7 +5834,7 @@ var __async$7 = (__this, __arguments, generator) => { }); }; function createDeposit(_0) { - return __async$7(this, arguments, function* ({ nullifier, secret }) { + return __async$6(this, arguments, function* ({ nullifier, secret }) { const preimage = new Uint8Array([...leInt2Buff(nullifier), ...leInt2Buff(secret)]); const noteHex = toFixedHex(bytesToBN(preimage), 62); const commitment = BigInt(yield buffPedersenHash(preimage)); @@ -5539,7 +5894,7 @@ class Deposit { ); } static createNote(_0) { - return __async$7(this, arguments, function* ({ currency, amount, netId, nullifier, secret }) { + return __async$6(this, arguments, function* ({ currency, amount, netId, nullifier, secret }) { if (!nullifier) { nullifier = rBigInt(31); } @@ -5566,7 +5921,7 @@ class Deposit { }); } static parseNote(noteString) { - return __async$7(this, null, function* () { + return __async$6(this, null, function* () { const noteRegex = new RegExp("tornado-(?\\w+)-(?[\\d.]+)-(?\\d+)-0x(?[0-9a-fA-F]{124})", "g"); const match = noteRegex.exec(noteString); if (!match) { @@ -5825,7 +6180,7 @@ class TornadoFeeOracle { } } -var __async$6 = (__this, __arguments, generator) => { +var __async$5 = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { @@ -5850,7 +6205,7 @@ class Mimc { this.mimcPromise = this.initMimc(); } initMimc() { - return __async$6(this, null, function* () { + return __async$5(this, null, function* () { this.sponge = yield circomlibjs.buildMimcSponge(); this.hash = (left, right) => { var _a, _b; @@ -5859,7 +6214,7 @@ class Mimc { }); } getHash() { - return __async$6(this, null, function* () { + return __async$5(this, null, function* () { yield this.mimcPromise; return { sponge: this.sponge, @@ -5870,7 +6225,7 @@ class Mimc { } const mimc = new Mimc(); -var __async$5 = (__this, __arguments, generator) => { +var __async$4 = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { @@ -5913,7 +6268,7 @@ class MerkleTreeService { this.merkleWorkerPath = merkleWorkerPath; } createTree(events) { - return __async$5(this, null, function* () { + return __async$4(this, null, function* () { const { hash: hashFunction } = yield mimc.getHash(); if (this.merkleWorkerPath) { console.log("Using merkleWorker\n"); @@ -5965,7 +6320,7 @@ class MerkleTreeService { }); } createPartialTree(_0) { - return __async$5(this, arguments, function* ({ edge, elements }) { + return __async$4(this, arguments, function* ({ edge, elements }) { const { hash: hashFunction } = yield mimc.getHash(); if (this.merkleWorkerPath) { console.log("Using merkleWorker\n"); @@ -6019,7 +6374,7 @@ class MerkleTreeService { }); } verifyTree(events) { - return __async$5(this, null, function* () { + return __async$4(this, null, function* () { console.log( ` Creating deposit tree for ${this.netId} ${this.amount} ${this.currency.toUpperCase()} would take a while @@ -6039,7 +6394,7 @@ Creating deposit tree for ${this.netId} ${this.amount} ${this.currency.toUpperCa } } -var __async$4 = (__this, __arguments, generator) => { +var __async$3 = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { @@ -6060,7 +6415,7 @@ var __async$4 = (__this, __arguments, generator) => { }); }; function multicall(Multicall2, calls) { - return __async$4(this, null, function* () { + return __async$3(this, null, function* () { const calldata = calls.map((call) => { var _a, _b, _c; const target = ((_a = call.contract) == null ? void 0 : _a.target) || call.address; @@ -6083,7 +6438,7 @@ function multicall(Multicall2, calls) { }); } -var __async$3 = (__this, __arguments, generator) => { +var __async$2 = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { @@ -6110,7 +6465,7 @@ class TokenPriceOracle { this.oracle = oracle; } fetchPrices(tokens) { - return __async$3(this, null, function* () { + return __async$2(this, null, function* () { if (!this.oracle) { return new Promise((resolve) => resolve(tokens.map(() => ethers.parseEther("0.0001")))); } @@ -6129,288 +6484,6 @@ class TokenPriceOracle { } } -var __defProp = Object.defineProperty; -var __defProps = Object.defineProperties; -var __getOwnPropDescs = Object.getOwnPropertyDescriptors; -var __getOwnPropSymbols = Object.getOwnPropertySymbols; -var __hasOwnProp = Object.prototype.hasOwnProperty; -var __propIsEnum = Object.prototype.propertyIsEnumerable; -var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; -var __spreadValues = (a, b) => { - for (var prop in b || (b = {})) - if (__hasOwnProp.call(b, prop)) - __defNormalProp(a, prop, b[prop]); - if (__getOwnPropSymbols) - for (var prop of __getOwnPropSymbols(b)) { - if (__propIsEnum.call(b, prop)) - __defNormalProp(a, prop, b[prop]); - } - return a; -}; -var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); -var __async$2 = (__this, __arguments, generator) => { - return new Promise((resolve, reject) => { - var fulfilled = (value) => { - try { - step(generator.next(value)); - } catch (e) { - reject(e); - } - }; - var rejected = (value) => { - try { - step(generator.throw(value)); - } catch (e) { - reject(e); - } - }; - var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); - step((generator = generator.apply(__this, __arguments)).next()); - }); -}; -const MIN_STAKE_BALANCE = ethers.parseEther("500"); -const semVerRegex = new RegExp("^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)(?:-(?(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); -function parseSemanticVersion(version) { - const { groups } = semVerRegex.exec(version); - return groups; -} -function isRelayerUpdated(relayerVersion, netId) { - const { major, patch, prerelease } = parseSemanticVersion(relayerVersion); - const requiredMajor = netId === NetId.MAINNET ? "4" : "5"; - const isUpdatedMajor = major === requiredMajor; - if (prerelease) return false; - return isUpdatedMajor && (Number(patch) >= 5 || netId !== NetId.MAINNET); -} -function calculateScore({ stakeBalance, tornadoServiceFee }, minFee = 0.33, maxFee = 0.53) { - if (tornadoServiceFee < minFee) { - tornadoServiceFee = minFee; - } else if (tornadoServiceFee >= maxFee) { - return BigInt(0); - } - const serviceFeeCoefficient = (tornadoServiceFee - minFee) ** 2; - const feeDiffCoefficient = 1 / (maxFee - minFee) ** 2; - const coefficientsMultiplier = 1 - feeDiffCoefficient * serviceFeeCoefficient; - return BigInt(Math.floor(Number(stakeBalance) * coefficientsMultiplier)); -} -function getWeightRandom(weightsScores, random) { - for (let i = 0; i < weightsScores.length; i++) { - if (random < weightsScores[i]) { - return i; - } - random = random - weightsScores[i]; - } - return Math.floor(Math.random() * weightsScores.length); -} -function getSupportedInstances(instanceList) { - const rawList = Object.values(instanceList).map(({ instanceAddress }) => { - return Object.values(instanceAddress); - }).flat(); - return rawList.map((l) => ethers.getAddress(l)); -} -function pickWeightedRandomRelayer(relayers, netId) { - let minFee, maxFee; - if (netId !== NetId.MAINNET) { - minFee = 0.01; - maxFee = 0.3; - } - const weightsScores = relayers.map((el) => calculateScore(el, minFee, maxFee)); - const totalWeight = weightsScores.reduce((acc, curr) => { - return acc = acc + curr; - }, BigInt("0")); - const random = BigInt(Number(totalWeight) * Math.random()); - const weightRandomIndex = getWeightRandom(weightsScores, random); - return relayers[weightRandomIndex]; -} -class RelayerClient { - constructor({ netId, config, Aggregator, fetchDataOptions: fetchDataOptions2 }) { - this.netId = netId; - this.config = config; - this.Aggregator = Aggregator; - this.fetchDataOptions = fetchDataOptions2; - } - askRelayerStatus(_0) { - return __async$2(this, arguments, function* ({ - hostname, - relayerAddress - }) { - var _a, _b; - const url = `https://${!hostname.endsWith("/") ? hostname + "/" : hostname}`; - const rawStatus = yield fetchData(`${url}status`, __spreadProps(__spreadValues({}, this.fetchDataOptions), { - headers: { - "Content-Type": "application/json, application/x-www-form-urlencoded" - }, - timeout: ((_a = this.fetchDataOptions) == null ? void 0 : _a.torPort) ? 1e4 : 3e3, - maxRetry: ((_b = this.fetchDataOptions) == null ? void 0 : _b.torPort) ? 2 : 0 - })); - const statusValidator = ajv.compile(getStatusSchema(this.netId, this.config)); - if (!statusValidator(rawStatus)) { - throw new Error("Invalid status schema"); - } - const status = __spreadProps(__spreadValues({}, rawStatus), { - url - }); - if (status.currentQueue > 5) { - throw new Error("Withdrawal queue is overloaded"); - } - if (status.netId !== this.netId) { - 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"); - } - if (!isRelayerUpdated(status.version, this.netId)) { - throw new Error("Outdated version."); - } - return status; - }); - } - filterRelayer(curr, relayer, subdomains, debugRelayer = false) { - return __async$2(this, null, function* () { - var _a; - const { relayerEnsSubdomain } = this.config; - const subdomainIndex = subdomains.indexOf(relayerEnsSubdomain); - const mainnetSubdomain = curr.records[0]; - const hostname = curr.records[subdomainIndex]; - const isHostWithProtocol = hostname.includes("http"); - const { owner, balance: stakeBalance, isRegistered } = curr; - const { ensName, relayerAddress } = relayer; - const isOwner = !relayerAddress || relayerAddress === owner; - const hasMinBalance = stakeBalance >= MIN_STAKE_BALANCE; - const preCondition = hostname && isOwner && mainnetSubdomain && isRegistered && hasMinBalance && !isHostWithProtocol; - if (preCondition || debugRelayer) { - try { - const status = yield this.askRelayerStatus({ hostname, relayerAddress }); - return { - netId: status.netId, - url: status.url, - hostname, - ensName, - stakeBalance, - relayerAddress, - rewardAccount: ethers.getAddress(status.rewardAccount), - instances: getSupportedInstances(status.instances), - gasPrice: (_a = status.gasPrices) == null ? void 0 : _a.fast, - ethPrices: status.ethPrices, - currentQueue: status.currentQueue, - tornadoServiceFee: status.tornadoServiceFee - }; - } catch (err) { - if (debugRelayer) { - throw err; - } - return { - hostname, - relayerAddress, - errorMessage: err.message - }; - } - } else { - if (debugRelayer) { - const errMsg = `Relayer ${hostname} condition not met`; - throw new Error(errMsg); - } - return { - hostname, - relayerAddress, - errorMessage: `Relayer ${hostname} condition not met` - }; - } - }); - } - getValidRelayers(relayers, subdomains, debugRelayer = false) { - return __async$2(this, null, function* () { - const relayersSet = /* @__PURE__ */ new Set(); - const uniqueRelayers = relayers.reverse().filter(({ ensName }) => { - if (!relayersSet.has(ensName)) { - relayersSet.add(ensName); - return true; - } - return false; - }); - const relayerNameHashes = uniqueRelayers.map((r) => ethers.namehash(r.ensName)); - const relayersData = yield this.Aggregator.relayersData.staticCall(relayerNameHashes, subdomains); - const invalidRelayers = []; - const validRelayers = (yield Promise.all( - relayersData.map((curr, index) => this.filterRelayer(curr, uniqueRelayers[index], subdomains, debugRelayer)) - )).filter((r) => { - if (r.errorMessage) { - invalidRelayers.push(r); - return false; - } - return true; - }); - return { - validRelayers, - invalidRelayers - }; - }); - } - pickWeightedRandomRelayer(relayers) { - return pickWeightedRandomRelayer(relayers, this.netId); - } - tornadoWithdraw(_0) { - return __async$2(this, arguments, function* ({ contract, proof, args }) { - const { url } = this.selectedRelayer; - const withdrawResponse = yield fetchData(`${url}v1/tornadoWithdraw`, __spreadProps(__spreadValues({}, this.fetchDataOptions), { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - contract, - proof, - args - }) - })); - const { id, error } = withdrawResponse; - if (error) { - throw new Error(error); - } - let relayerStatus; - const jobUrl = `${url}v1/jobs/${id}`; - console.log(`Job submitted: ${jobUrl} -`); - while (!relayerStatus || !["FAILED", "CONFIRMED"].includes(relayerStatus)) { - const jobResponse = yield fetchData(jobUrl, __spreadProps(__spreadValues({}, this.fetchDataOptions), { - method: "GET", - headers: { - "Content-Type": "application/json" - } - })); - if (jobResponse.error) { - throw new Error(error); - } - const jobValidator = ajv.compile(jobsSchema); - if (!jobValidator(jobResponse)) { - const errMsg = `${jobUrl} has an invalid job response`; - throw new Error(errMsg); - } - const { status, txHash, confirmations, failedReason } = jobResponse; - if (relayerStatus !== status) { - if (status === "FAILED") { - const errMsg = `Job ${status}: ${jobUrl} failed reason: ${failedReason}`; - throw new Error(errMsg); - } else if (status === "SENT") { - console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash} -`); - } else if (status === "MINED") { - console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations} -`); - } else if (status === "CONFIRMED") { - console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations} -`); - } else { - console.log(`Job ${status}: ${jobUrl} -`); - } - relayerStatus = status; - } - yield sleep(3e3); - } - }); - } -} - var __async$1 = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { @@ -6580,6 +6653,8 @@ exports.GET_REGISTERED = GET_REGISTERED; exports.GET_STATISTIC = GET_STATISTIC; exports.GET_WITHDRAWALS = GET_WITHDRAWALS; exports.Invoice = Invoice; +exports.MAX_FEE = MAX_FEE; +exports.MIN_FEE = MIN_FEE; exports.MIN_STAKE_BALANCE = MIN_STAKE_BALANCE; exports.MerkleTreeService = MerkleTreeService; exports.Mimc = Mimc; @@ -6642,6 +6717,7 @@ exports.getNoteAccounts = getNoteAccounts; exports.getProvider = getProvider; exports.getProviderWithNetId = getProviderWithNetId; exports.getRegisters = getRegisters; +exports.getRelayerEnsSubdomains = getRelayerEnsSubdomains; exports.getStatistic = getStatistic; exports.getStatusSchema = getStatusSchema; exports.getSubdomains = getSubdomains; diff --git a/dist/index.mjs b/dist/index.mjs index 9fb5965..79ef4f2 100644 --- a/dist/index.mjs +++ b/dist/index.mjs @@ -1,4 +1,4 @@ -import { FetchRequest, Network, EnsPlugin, GasCostPlugin, JsonRpcProvider, Wallet, HDNodeWallet, VoidSigner, JsonRpcSigner, BrowserProvider, FeeData, getAddress, Interface, Contract, computeAddress, parseUnits, Transaction, parseEther, namehash, ZeroAddress } from 'ethers'; +import { FetchRequest, Network, EnsPlugin, GasCostPlugin, JsonRpcProvider, Wallet, HDNodeWallet, VoidSigner, JsonRpcSigner, BrowserProvider, FeeData, getAddress, parseEther, namehash, formatEther, Interface, Contract, computeAddress, parseUnits, Transaction, ZeroAddress } from 'ethers'; import crossFetch from 'cross-fetch'; import { webcrypto } from 'crypto'; import BN from 'bn.js'; @@ -1701,13 +1701,9 @@ class BatchEventsService { } var __defProp$2 = Object.defineProperty; -var __defProps$1 = Object.defineProperties; -var __getOwnPropDescs$1 = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols$2 = Object.getOwnPropertySymbols; -var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp$2 = Object.prototype.hasOwnProperty; var __propIsEnum$2 = Object.prototype.propertyIsEnumerable; -var __reflectGet = Reflect.get; var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues$2 = (a, b) => { for (var prop in b || (b = {})) @@ -1720,598 +1716,6 @@ var __spreadValues$2 = (a, b) => { } return a; }; -var __spreadProps$1 = (a, b) => __defProps$1(a, __getOwnPropDescs$1(b)); -var __superGet = (cls, obj, key) => __reflectGet(__getProtoOf(cls), key, obj); -var __async$9 = (__this, __arguments, generator) => { - return new Promise((resolve, reject) => { - var fulfilled = (value) => { - try { - step(generator.next(value)); - } catch (e) { - reject(e); - } - }; - var rejected = (value) => { - try { - step(generator.throw(value)); - } catch (e) { - reject(e); - } - }; - var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); - step((generator = generator.apply(__this, __arguments)).next()); - }); -}; -const DEPOSIT = "deposit"; -const WITHDRAWAL = "withdrawal"; -class BaseEventsService { - constructor({ - netId, - provider, - graphApi, - subgraphName, - contract, - type = "", - deployedBlock = 0, - fetchDataOptions: fetchDataOptions2 - }) { - this.netId = netId; - this.provider = provider; - this.graphApi = graphApi; - this.subgraphName = subgraphName; - this.fetchDataOptions = fetchDataOptions2; - this.contract = contract; - this.type = type; - this.deployedBlock = deployedBlock; - this.batchEventsService = new BatchEventsService({ - provider, - contract, - onProgress: this.updateEventProgress - }); - } - getInstanceName() { - return ""; - } - getType() { - return this.type || ""; - } - getGraphMethod() { - return ""; - } - getGraphParams() { - return { - graphApi: this.graphApi || "", - subgraphName: this.subgraphName || "", - fetchDataOptions: this.fetchDataOptions, - onProgress: this.updateGraphProgress - }; - } - /* eslint-disable @typescript-eslint/no-unused-vars */ - updateEventProgress({ percentage, type, fromBlock, toBlock, count }) { - } - updateBlockProgress({ percentage, currentIndex, totalIndex }) { - } - updateTransactionProgress({ percentage, currentIndex, totalIndex }) { - } - updateGraphProgress({ type, fromBlock, toBlock, count }) { - } - /* eslint-enable @typescript-eslint/no-unused-vars */ - formatEvents(events) { - return __async$9(this, null, function* () { - return yield new Promise((resolve) => resolve(events)); - }); - } - /** - * Get saved or cached events - */ - getEventsFromDB() { - return __async$9(this, null, function* () { - return { - events: [], - lastBlock: null - }; - }); - } - /** - * Events from remote cache (Either from local cache, CDN, or from IPFS) - */ - getEventsFromCache() { - return __async$9(this, null, function* () { - return { - events: [], - lastBlock: null, - fromCache: true - }; - }); - } - getSavedEvents() { - return __async$9(this, null, function* () { - let cachedEvents = yield this.getEventsFromDB(); - if (!cachedEvents || !cachedEvents.events.length) { - cachedEvents = yield this.getEventsFromCache(); - } - return cachedEvents; - }); - } - /** - * Get latest events - */ - getEventsFromGraph(_0) { - return __async$9(this, arguments, function* ({ - fromBlock, - methodName = "" - }) { - if (!this.graphApi || !this.subgraphName) { - return { - events: [], - lastBlock: fromBlock - }; - } - const { events, lastSyncBlock } = yield graph[methodName || this.getGraphMethod()](__spreadValues$2({ - fromBlock - }, this.getGraphParams())); - return { - events, - lastBlock: lastSyncBlock - }; - }); - } - getEventsFromRpc(_0) { - return __async$9(this, arguments, function* ({ - fromBlock, - toBlock - }) { - try { - if (!toBlock) { - toBlock = yield this.provider.getBlockNumber(); - } - if (fromBlock >= toBlock) { - return { - events: [], - lastBlock: toBlock - }; - } - this.updateEventProgress({ percentage: 0, type: this.getType() }); - const events = yield this.formatEvents( - yield this.batchEventsService.getBatchEvents({ fromBlock, toBlock, type: this.getType() }) - ); - if (!events.length) { - return { - events, - lastBlock: toBlock - }; - } - return { - events, - lastBlock: toBlock - }; - } catch (err) { - console.log(err); - return { - events: [], - lastBlock: fromBlock - }; - } - }); - } - getLatestEvents(_0) { - return __async$9(this, arguments, function* ({ fromBlock }) { - const allEvents = []; - const graphEvents = yield this.getEventsFromGraph({ fromBlock }); - const lastSyncBlock = graphEvents.lastBlock && graphEvents.lastBlock >= fromBlock ? graphEvents.lastBlock : fromBlock; - const rpcEvents = yield this.getEventsFromRpc({ fromBlock: lastSyncBlock }); - allEvents.push(...graphEvents.events); - allEvents.push(...rpcEvents.events); - const lastBlock = rpcEvents ? rpcEvents.lastBlock : allEvents[allEvents.length - 1] ? allEvents[allEvents.length - 1].blockNumber : fromBlock; - return { - events: allEvents, - lastBlock - }; - }); - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - validateEvents({ events, lastBlock }) { - } - /** - * Handle saving events - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - saveEvents(_0) { - return __async$9(this, arguments, function* ({ events, lastBlock }) { - }); - } - /** - * Trigger saving and receiving latest events - */ - updateEvents() { - return __async$9(this, null, function* () { - const savedEvents = yield this.getSavedEvents(); - let fromBlock = this.deployedBlock; - if (savedEvents && savedEvents.lastBlock) { - fromBlock = savedEvents.lastBlock + 1; - } - const newEvents = yield this.getLatestEvents({ fromBlock }); - const eventSet = /* @__PURE__ */ new Set(); - let allEvents = []; - allEvents.push(...savedEvents.events); - allEvents.push(...newEvents.events); - allEvents = allEvents.sort((a, b) => { - if (a.blockNumber === b.blockNumber) { - return a.logIndex - b.logIndex; - } - return a.blockNumber - b.blockNumber; - }).filter(({ transactionHash, logIndex }) => { - const eventKey = `${transactionHash}_${logIndex}`; - const hasEvent = eventSet.has(eventKey); - eventSet.add(eventKey); - return !hasEvent; - }); - const lastBlock = newEvents ? newEvents.lastBlock : allEvents[allEvents.length - 1] ? allEvents[allEvents.length - 1].blockNumber : null; - this.validateEvents({ events: allEvents, lastBlock }); - if (savedEvents.fromCache || newEvents.events.length) { - yield this.saveEvents({ events: allEvents, lastBlock }); - } - return { - events: allEvents, - lastBlock - }; - }); - } -} -class BaseTornadoService extends BaseEventsService { - constructor({ - netId, - provider, - graphApi, - subgraphName, - Tornado, - type, - amount, - currency, - deployedBlock, - fetchDataOptions: fetchDataOptions2 - }) { - super({ netId, provider, graphApi, subgraphName, contract: Tornado, type, deployedBlock, fetchDataOptions: fetchDataOptions2 }); - this.amount = amount; - this.currency = currency; - this.batchTransactionService = new BatchTransactionService({ - provider, - onProgress: this.updateTransactionProgress - }); - this.batchBlockService = new BatchBlockService({ - provider, - onProgress: this.updateBlockProgress - }); - } - getInstanceName() { - return `${this.getType().toLowerCase()}s_${this.netId}_${this.currency}_${this.amount}`; - } - getGraphMethod() { - return `getAll${this.getType()}s`; - } - getGraphParams() { - return { - graphApi: this.graphApi || "", - subgraphName: this.subgraphName || "", - amount: this.amount, - currency: this.currency, - fetchDataOptions: this.fetchDataOptions, - onProgress: this.updateGraphProgress - }; - } - formatEvents(events) { - return __async$9(this, null, function* () { - 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 = yield this.batchTransactionService.getBatchTransactions([ - ...new Set(formattedEvents.map(({ transactionHash }) => transactionHash)) - ]); - return formattedEvents.map((event) => { - const { from } = txs.find(({ hash }) => hash === event.transactionHash); - return __spreadProps$1(__spreadValues$2({}, 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: getAddress(to), - fee: String(fee) - }; - }); - const blocks = yield this.batchBlockService.getBatchBlocks([ - ...new Set(formattedEvents.map(({ blockNumber }) => blockNumber)) - ]); - return formattedEvents.map((event) => { - const { timestamp } = blocks.find(({ number }) => number === event.blockNumber); - return __spreadProps$1(__spreadValues$2({}, event), { - timestamp - }); - }); - } - }); - } - validateEvents({ events }) { - if (events.length && this.getType().toLowerCase() === DEPOSIT) { - const lastEvent = events[events.length - 1]; - if (lastEvent.leafIndex !== events.length - 1) { - const errMsg = `Deposit events invalid wants ${events.length - 1} leafIndex have ${lastEvent.leafIndex}`; - throw new Error(errMsg); - } - } - } -} -class BaseEchoService extends BaseEventsService { - constructor({ - netId, - provider, - graphApi, - subgraphName, - Echoer, - deployedBlock, - fetchDataOptions: fetchDataOptions2 - }) { - super({ netId, provider, graphApi, subgraphName, contract: Echoer, deployedBlock, fetchDataOptions: fetchDataOptions2 }); - } - getInstanceName() { - return `echo_${this.netId}`; - } - getType() { - return "Echo"; - } - getGraphMethod() { - return "getAllGraphEchoEvents"; - } - formatEvents(events) { - return __async$9(this, null, function* () { - return events.map(({ blockNumber, index: logIndex, transactionHash, args }) => { - const { who, data } = args; - if (who && data) { - const eventObjects = { - blockNumber, - logIndex, - transactionHash - }; - return __spreadProps$1(__spreadValues$2({}, eventObjects), { - address: who, - encryptedAccount: data - }); - } - }).filter((e) => e); - }); - } - getEventsFromGraph(_0) { - return __async$9(this, arguments, function* ({ fromBlock }) { - if (!this.graphApi || this.graphApi.includes("api.thegraph.com")) { - return { - events: [], - lastBlock: fromBlock - }; - } - return __superGet(BaseEchoService.prototype, this, "getEventsFromGraph").call(this, { fromBlock }); - }); - } -} -class BaseEncryptedNotesService extends BaseEventsService { - constructor({ - netId, - provider, - graphApi, - subgraphName, - Router, - deployedBlock, - fetchDataOptions: fetchDataOptions2 - }) { - super({ netId, provider, graphApi, subgraphName, contract: Router, deployedBlock, fetchDataOptions: fetchDataOptions2 }); - } - getInstanceName() { - return `encrypted_notes_${this.netId}`; - } - getType() { - return "EncryptedNote"; - } - getGraphMethod() { - return "getAllEncryptedNotes"; - } - formatEvents(events) { - return __async$9(this, null, function* () { - return events.map(({ blockNumber, index: logIndex, transactionHash, args }) => { - const { encryptedNote } = args; - if (encryptedNote) { - const eventObjects = { - blockNumber, - logIndex, - transactionHash - }; - return __spreadProps$1(__spreadValues$2({}, eventObjects), { - encryptedNote - }); - } - }).filter((e) => e); - }); - } -} -class BaseGovernanceService extends BaseEventsService { - constructor({ - netId, - provider, - graphApi, - subgraphName, - Governance, - deployedBlock, - fetchDataOptions: fetchDataOptions2 - }) { - super({ netId, provider, graphApi, subgraphName, contract: Governance, deployedBlock, fetchDataOptions: fetchDataOptions2 }); - this.batchTransactionService = new BatchTransactionService({ - provider, - onProgress: this.updateTransactionProgress - }); - } - getInstanceName() { - return `governance_${this.netId}`; - } - getType() { - return "*"; - } - getGraphMethod() { - return "getAllGovernanceEvents"; - } - formatEvents(events) { - return __async$9(this, null, function* () { - const proposalEvents = []; - const votedEvents = []; - const delegatedEvents = []; - const undelegatedEvents = []; - events.forEach(({ blockNumber, index: logIndex, transactionHash, args, eventName: event }) => { - const eventObjects = { - blockNumber, - logIndex, - transactionHash, - event - }; - if (event === "ProposalCreated") { - const { id, proposer, target, startTime, endTime, description } = args; - proposalEvents.push(__spreadProps$1(__spreadValues$2({}, eventObjects), { - id: Number(id), - proposer, - target, - startTime: Number(startTime), - endTime: Number(endTime), - description - })); - } - if (event === "Voted") { - const { proposalId, voter, support, votes } = args; - votedEvents.push(__spreadProps$1(__spreadValues$2({}, eventObjects), { - proposalId: Number(proposalId), - voter, - support, - votes, - from: "", - input: "" - })); - } - if (event === "Delegated") { - const { account, to: delegateTo } = args; - delegatedEvents.push(__spreadProps$1(__spreadValues$2({}, eventObjects), { - account, - delegateTo - })); - } - if (event === "Undelegated") { - const { account, from: delegateFrom } = args; - undelegatedEvents.push(__spreadProps$1(__spreadValues$2({}, eventObjects), { - account, - delegateFrom - })); - } - }); - if (votedEvents.length) { - this.updateTransactionProgress({ percentage: 0 }); - const txs = yield this.batchTransactionService.getBatchTransactions([ - ...new Set(votedEvents.map(({ transactionHash }) => transactionHash)) - ]); - votedEvents.forEach((event, index) => { - let { data: input, from } = txs.find((t) => t.hash === event.transactionHash); - if (!input || input.length > 2048) { - input = ""; - } - votedEvents[index].from = from; - votedEvents[index].input = input; - }); - } - return [...proposalEvents, ...votedEvents, ...delegatedEvents, ...undelegatedEvents]; - }); - } - getEventsFromGraph(_0) { - return __async$9(this, arguments, function* ({ fromBlock }) { - if (!this.graphApi || !this.subgraphName || this.graphApi.includes("api.thegraph.com")) { - return { - events: [], - lastBlock: fromBlock - }; - } - return __superGet(BaseGovernanceService.prototype, this, "getEventsFromGraph").call(this, { fromBlock }); - }); - } -} -class BaseRegistryService extends BaseEventsService { - constructor({ - netId, - provider, - graphApi, - subgraphName, - RelayerRegistry, - deployedBlock, - fetchDataOptions: fetchDataOptions2 - }) { - super({ netId, provider, graphApi, subgraphName, contract: RelayerRegistry, deployedBlock, fetchDataOptions: fetchDataOptions2 }); - } - getInstanceName() { - return `registered_${this.netId}`; - } - // Name of type used for events - getType() { - return "RelayerRegistered"; - } - // Name of method used for graph - getGraphMethod() { - return "getAllRegisters"; - } - formatEvents(events) { - return __async$9(this, null, function* () { - return events.map(({ blockNumber, index: logIndex, transactionHash, args }) => { - const eventObjects = { - blockNumber, - logIndex, - transactionHash - }; - return __spreadProps$1(__spreadValues$2({}, eventObjects), { - ensName: args.ensName, - relayerAddress: args.relayerAddress - }); - }); - }); - } - fetchRelayers() { - return __async$9(this, null, function* () { - return (yield this.updateEvents()).events; - }); - } -} - -var __defProp$1 = Object.defineProperty; -var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols; -var __hasOwnProp$1 = Object.prototype.hasOwnProperty; -var __propIsEnum$1 = Object.prototype.propertyIsEnumerable; -var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; -var __spreadValues$1 = (a, b) => { - for (var prop in b || (b = {})) - if (__hasOwnProp$1.call(b, prop)) - __defNormalProp$1(a, prop, b[prop]); - if (__getOwnPropSymbols$1) - for (var prop of __getOwnPropSymbols$1(b)) { - if (__propIsEnum$1.call(b, prop)) - __defNormalProp$1(a, prop, b[prop]); - } - return a; -}; var NetId = /* @__PURE__ */ ((NetId2) => { NetId2[NetId2["MAINNET"] = 1] = "MAINNET"; NetId2[NetId2["BSC"] = 56] = "BSC"; @@ -2832,10 +2236,6 @@ const defaultConfig = { tornadoSubgraph: "tornadocash/sepolia-tornado-subgraph", subgraphs: {}, rpcUrls: { - pandaops: { - name: "ethpandaops", - url: "https://rpc.sepolia.ethpandaops.io" - }, sepolia: { name: "Sepolia RPC", url: "https://rpc.sepolia.org" @@ -2847,6 +2247,10 @@ const defaultConfig = { onerpc: { name: "1rpc", url: "https://1rpc.io/sepolia" + }, + ethpandaops: { + name: "ethpandaops", + url: "https://rpc.sepolia.ethpandaops.io" } }, tokens: { @@ -2890,10 +2294,10 @@ function addNetwork(newConfig) { enabledChains.push( ...Object.keys(newConfig).map((netId) => Number(netId)).filter((netId) => !enabledChains.includes(netId)) ); - customConfig = __spreadValues$1(__spreadValues$1({}, customConfig), newConfig); + customConfig = __spreadValues$2(__spreadValues$2({}, customConfig), newConfig); } function getNetworkConfig() { - const allConfig = __spreadValues$1(__spreadValues$1({}, defaultConfig), customConfig); + const allConfig = __spreadValues$2(__spreadValues$2({}, defaultConfig), customConfig); return enabledChains.reduce((acc, curr) => { acc[curr] = allConfig[curr]; return acc; @@ -2925,6 +2329,13 @@ function getSubdomains() { const allConfig = getNetworkConfig(); return enabledChains.map((chain) => allConfig[chain].relayerEnsSubdomain); } +function getRelayerEnsSubdomains() { + const allConfig = getNetworkConfig(); + return Object.keys(allConfig).reduce((acc, chain) => { + acc[Number(chain)] = allConfig[Number(chain)].relayerEnsSubdomain; + return acc; + }, {}); +} const addressType = { type: "string", pattern: "^0x[a-fA-F0-9]{40}$" }; const bnType = { type: "string", BN: true }; @@ -3050,6 +2461,950 @@ ajv.addKeyword({ errors: true }); +var __defProp$1 = Object.defineProperty; +var __defProps$1 = Object.defineProperties; +var __getOwnPropDescs$1 = Object.getOwnPropertyDescriptors; +var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols; +var __hasOwnProp$1 = Object.prototype.hasOwnProperty; +var __propIsEnum$1 = Object.prototype.propertyIsEnumerable; +var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __spreadValues$1 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$1.call(b, prop)) + __defNormalProp$1(a, prop, b[prop]); + if (__getOwnPropSymbols$1) + for (var prop of __getOwnPropSymbols$1(b)) { + if (__propIsEnum$1.call(b, prop)) + __defNormalProp$1(a, prop, b[prop]); + } + return a; +}; +var __spreadProps$1 = (a, b) => __defProps$1(a, __getOwnPropDescs$1(b)); +var __async$9 = (__this, __arguments, generator) => { + return new Promise((resolve, reject) => { + var fulfilled = (value) => { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + }; + var rejected = (value) => { + try { + step(generator.throw(value)); + } catch (e) { + reject(e); + } + }; + var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); + step((generator = generator.apply(__this, __arguments)).next()); + }); +}; +const MIN_FEE = 0.1; +const MAX_FEE = 0.6; +const MIN_STAKE_BALANCE = parseEther("500"); +const semVerRegex = new RegExp("^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)(?:-(?(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); +function parseSemanticVersion(version) { + const { groups } = semVerRegex.exec(version); + return groups; +} +function isRelayerUpdated(relayerVersion, netId) { + const { major, patch, prerelease } = parseSemanticVersion(relayerVersion); + const requiredMajor = netId === NetId.MAINNET ? "4" : "5"; + const isUpdatedMajor = major === requiredMajor; + if (prerelease) return false; + return isUpdatedMajor && (Number(patch) >= 5 || netId !== NetId.MAINNET); +} +function calculateScore({ stakeBalance, tornadoServiceFee }) { + if (tornadoServiceFee < MIN_FEE) { + tornadoServiceFee = MIN_FEE; + } else if (tornadoServiceFee >= MAX_FEE) { + return BigInt(0); + } + 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)); +} +function getWeightRandom(weightsScores, random) { + for (let i = 0; i < weightsScores.length; i++) { + if (random < weightsScores[i]) { + return i; + } + random = random - weightsScores[i]; + } + return Math.floor(Math.random() * weightsScores.length); +} +function getSupportedInstances(instanceList) { + const rawList = Object.values(instanceList).map(({ instanceAddress }) => { + return Object.values(instanceAddress); + }).flat(); + return rawList.map((l) => getAddress(l)); +} +function pickWeightedRandomRelayer(relayers) { + const weightsScores = relayers.map((el) => calculateScore(el)); + const totalWeight = weightsScores.reduce((acc, curr) => { + return acc = acc + curr; + }, BigInt("0")); + const random = BigInt(Math.floor(Number(totalWeight) * Math.random())); + const weightRandomIndex = getWeightRandom(weightsScores, random); + return relayers[weightRandomIndex]; +} +class RelayerClient { + constructor({ netId, config, fetchDataOptions: fetchDataOptions2 }) { + this.netId = netId; + this.config = config; + this.fetchDataOptions = fetchDataOptions2; + } + askRelayerStatus(_0) { + return __async$9(this, arguments, function* ({ + hostname, + relayerAddress + }) { + var _a, _b; + const url = `https://${!hostname.endsWith("/") ? hostname + "/" : hostname}`; + const rawStatus = yield fetchData(`${url}status`, __spreadProps$1(__spreadValues$1({}, this.fetchDataOptions), { + headers: { + "Content-Type": "application/json, application/x-www-form-urlencoded" + }, + timeout: ((_a = this.fetchDataOptions) == null ? void 0 : _a.torPort) ? 1e4 : 3e3, + maxRetry: ((_b = this.fetchDataOptions) == null ? void 0 : _b.torPort) ? 2 : 0 + })); + const statusValidator = ajv.compile(getStatusSchema(this.netId, this.config)); + if (!statusValidator(rawStatus)) { + throw new Error("Invalid status schema"); + } + const status = __spreadProps$1(__spreadValues$1({}, rawStatus), { + url + }); + if (status.currentQueue > 5) { + throw new Error("Withdrawal queue is overloaded"); + } + if (status.netId !== this.netId) { + 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"); + } + if (!isRelayerUpdated(status.version, this.netId)) { + throw new Error("Outdated version."); + } + return status; + }); + } + filterRelayer(relayer) { + return __async$9(this, null, function* () { + var _a; + const hostname = relayer.hostnames[this.netId]; + const { ensName, relayerAddress } = relayer; + if (!hostname) { + return; + } + try { + const status = yield this.askRelayerStatus({ hostname, relayerAddress }); + return { + netId: status.netId, + url: status.url, + hostname, + ensName, + relayerAddress, + rewardAccount: getAddress(status.rewardAccount), + instances: getSupportedInstances(status.instances), + stakeBalance: relayer.stakeBalance, + gasPrice: (_a = status.gasPrices) == null ? void 0 : _a.fast, + ethPrices: status.ethPrices, + currentQueue: status.currentQueue, + tornadoServiceFee: status.tornadoServiceFee + }; + } catch (err) { + return { + hostname, + relayerAddress, + errorMessage: err.message, + hasError: true + }; + } + }); + } + getValidRelayers(relayers) { + return __async$9(this, null, function* () { + const invalidRelayers = []; + const validRelayers = (yield Promise.all(relayers.map((relayer) => this.filterRelayer(relayer)))).filter((r) => { + if (!r) { + return false; + } + if (r.hasError) { + invalidRelayers.push(r); + return false; + } + return true; + }); + return { + validRelayers, + invalidRelayers + }; + }); + } + pickWeightedRandomRelayer(relayers) { + return pickWeightedRandomRelayer(relayers); + } + tornadoWithdraw(_0) { + return __async$9(this, arguments, function* ({ contract, proof, args }) { + const { url } = this.selectedRelayer; + const withdrawResponse = yield fetchData(`${url}v1/tornadoWithdraw`, __spreadProps$1(__spreadValues$1({}, this.fetchDataOptions), { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + contract, + proof, + args + }) + })); + const { id, error } = withdrawResponse; + if (error) { + throw new Error(error); + } + let relayerStatus; + const jobUrl = `${url}v1/jobs/${id}`; + console.log(`Job submitted: ${jobUrl} +`); + while (!relayerStatus || !["FAILED", "CONFIRMED"].includes(relayerStatus)) { + const jobResponse = yield fetchData(jobUrl, __spreadProps$1(__spreadValues$1({}, this.fetchDataOptions), { + method: "GET", + headers: { + "Content-Type": "application/json" + } + })); + if (jobResponse.error) { + throw new Error(error); + } + const jobValidator = ajv.compile(jobsSchema); + if (!jobValidator(jobResponse)) { + const errMsg = `${jobUrl} has an invalid job response`; + throw new Error(errMsg); + } + const { status, txHash, confirmations, failedReason } = jobResponse; + if (relayerStatus !== status) { + if (status === "FAILED") { + const errMsg = `Job ${status}: ${jobUrl} failed reason: ${failedReason}`; + throw new Error(errMsg); + } else if (status === "SENT") { + console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash} +`); + } else if (status === "MINED") { + console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations} +`); + } else if (status === "CONFIRMED") { + console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations} +`); + } else { + console.log(`Job ${status}: ${jobUrl} +`); + } + relayerStatus = status; + } + yield sleep(3e3); + } + }); + } +} + +var __defProp = Object.defineProperty; +var __defProps = Object.defineProperties; +var __getOwnPropDescs = Object.getOwnPropertyDescriptors; +var __getOwnPropSymbols = Object.getOwnPropertySymbols; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __propIsEnum = Object.prototype.propertyIsEnumerable; +var __reflectGet = Reflect.get; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __spreadValues = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + if (__getOwnPropSymbols) + for (var prop of __getOwnPropSymbols(b)) { + if (__propIsEnum.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + } + return a; +}; +var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); +var __superGet = (cls, obj, key) => __reflectGet(__getProtoOf(cls), key, obj); +var __async$8 = (__this, __arguments, generator) => { + return new Promise((resolve, reject) => { + var fulfilled = (value) => { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + }; + var rejected = (value) => { + try { + step(generator.throw(value)); + } catch (e) { + reject(e); + } + }; + var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); + step((generator = generator.apply(__this, __arguments)).next()); + }); +}; +const DEPOSIT = "deposit"; +const WITHDRAWAL = "withdrawal"; +class BaseEventsService { + constructor({ + netId, + provider, + graphApi, + subgraphName, + contract, + type = "", + deployedBlock = 0, + fetchDataOptions + }) { + this.netId = netId; + this.provider = provider; + this.graphApi = graphApi; + this.subgraphName = subgraphName; + this.fetchDataOptions = fetchDataOptions; + this.contract = contract; + this.type = type; + this.deployedBlock = deployedBlock; + this.batchEventsService = new BatchEventsService({ + provider, + contract, + onProgress: this.updateEventProgress + }); + } + getInstanceName() { + return ""; + } + getType() { + return this.type || ""; + } + getGraphMethod() { + return ""; + } + getGraphParams() { + return { + graphApi: this.graphApi || "", + subgraphName: this.subgraphName || "", + fetchDataOptions: this.fetchDataOptions, + onProgress: this.updateGraphProgress + }; + } + /* eslint-disable @typescript-eslint/no-unused-vars */ + updateEventProgress({ percentage, type, fromBlock, toBlock, count }) { + } + updateBlockProgress({ percentage, currentIndex, totalIndex }) { + } + updateTransactionProgress({ percentage, currentIndex, totalIndex }) { + } + updateGraphProgress({ type, fromBlock, toBlock, count }) { + } + /* eslint-enable @typescript-eslint/no-unused-vars */ + formatEvents(events) { + return __async$8(this, null, function* () { + return yield new Promise((resolve) => resolve(events)); + }); + } + /** + * Get saved or cached events + */ + getEventsFromDB() { + return __async$8(this, null, function* () { + return { + events: [], + lastBlock: null + }; + }); + } + /** + * Events from remote cache (Either from local cache, CDN, or from IPFS) + */ + getEventsFromCache() { + return __async$8(this, null, function* () { + return { + events: [], + lastBlock: null, + fromCache: true + }; + }); + } + getSavedEvents() { + return __async$8(this, null, function* () { + let cachedEvents = yield this.getEventsFromDB(); + if (!cachedEvents || !cachedEvents.events.length) { + cachedEvents = yield this.getEventsFromCache(); + } + return cachedEvents; + }); + } + /** + * Get latest events + */ + getEventsFromGraph(_0) { + return __async$8(this, arguments, function* ({ + fromBlock, + methodName = "" + }) { + if (!this.graphApi || !this.subgraphName) { + return { + events: [], + lastBlock: fromBlock + }; + } + const { events, lastSyncBlock } = yield graph[methodName || this.getGraphMethod()](__spreadValues({ + fromBlock + }, this.getGraphParams())); + return { + events, + lastBlock: lastSyncBlock + }; + }); + } + getEventsFromRpc(_0) { + return __async$8(this, arguments, function* ({ + fromBlock, + toBlock + }) { + try { + if (!toBlock) { + toBlock = yield this.provider.getBlockNumber(); + } + if (fromBlock >= toBlock) { + return { + events: [], + lastBlock: toBlock + }; + } + this.updateEventProgress({ percentage: 0, type: this.getType() }); + const events = yield this.formatEvents( + yield this.batchEventsService.getBatchEvents({ fromBlock, toBlock, type: this.getType() }) + ); + if (!events.length) { + return { + events, + lastBlock: toBlock + }; + } + return { + events, + lastBlock: toBlock + }; + } catch (err) { + console.log(err); + return { + events: [], + lastBlock: fromBlock + }; + } + }); + } + getLatestEvents(_0) { + return __async$8(this, arguments, function* ({ fromBlock }) { + const allEvents = []; + const graphEvents = yield this.getEventsFromGraph({ fromBlock }); + const lastSyncBlock = graphEvents.lastBlock && graphEvents.lastBlock >= fromBlock ? graphEvents.lastBlock : fromBlock; + const rpcEvents = yield this.getEventsFromRpc({ fromBlock: lastSyncBlock }); + allEvents.push(...graphEvents.events); + allEvents.push(...rpcEvents.events); + const lastBlock = rpcEvents ? rpcEvents.lastBlock : allEvents[allEvents.length - 1] ? allEvents[allEvents.length - 1].blockNumber : fromBlock; + return { + events: allEvents, + lastBlock + }; + }); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + validateEvents({ events, lastBlock }) { + } + /** + * Handle saving events + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + saveEvents(_0) { + return __async$8(this, arguments, function* ({ events, lastBlock }) { + }); + } + /** + * Trigger saving and receiving latest events + */ + updateEvents() { + return __async$8(this, null, function* () { + const savedEvents = yield this.getSavedEvents(); + let fromBlock = this.deployedBlock; + if (savedEvents && savedEvents.lastBlock) { + fromBlock = savedEvents.lastBlock + 1; + } + const newEvents = yield this.getLatestEvents({ fromBlock }); + const eventSet = /* @__PURE__ */ new Set(); + let allEvents = []; + allEvents.push(...savedEvents.events); + allEvents.push(...newEvents.events); + allEvents = allEvents.sort((a, b) => { + if (a.blockNumber === b.blockNumber) { + return a.logIndex - b.logIndex; + } + return a.blockNumber - b.blockNumber; + }).filter(({ transactionHash, logIndex }) => { + const eventKey = `${transactionHash}_${logIndex}`; + const hasEvent = eventSet.has(eventKey); + eventSet.add(eventKey); + return !hasEvent; + }); + const lastBlock = newEvents ? newEvents.lastBlock : allEvents[allEvents.length - 1] ? allEvents[allEvents.length - 1].blockNumber : null; + this.validateEvents({ events: allEvents, lastBlock }); + if (savedEvents.fromCache || newEvents.events.length) { + yield this.saveEvents({ events: allEvents, lastBlock }); + } + return { + events: allEvents, + lastBlock + }; + }); + } +} +class BaseTornadoService extends BaseEventsService { + constructor({ + netId, + provider, + graphApi, + subgraphName, + Tornado, + type, + amount, + currency, + deployedBlock, + fetchDataOptions + }) { + super({ netId, provider, graphApi, subgraphName, contract: Tornado, type, deployedBlock, fetchDataOptions }); + this.amount = amount; + this.currency = currency; + this.batchTransactionService = new BatchTransactionService({ + provider, + onProgress: this.updateTransactionProgress + }); + this.batchBlockService = new BatchBlockService({ + provider, + onProgress: this.updateBlockProgress + }); + } + getInstanceName() { + return `${this.getType().toLowerCase()}s_${this.netId}_${this.currency}_${this.amount}`; + } + getGraphMethod() { + return `getAll${this.getType()}s`; + } + getGraphParams() { + return { + graphApi: this.graphApi || "", + subgraphName: this.subgraphName || "", + amount: this.amount, + currency: this.currency, + fetchDataOptions: this.fetchDataOptions, + onProgress: this.updateGraphProgress + }; + } + formatEvents(events) { + return __async$8(this, null, function* () { + 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 = yield this.batchTransactionService.getBatchTransactions([ + ...new Set(formattedEvents.map(({ transactionHash }) => transactionHash)) + ]); + return formattedEvents.map((event) => { + const { from } = txs.find(({ hash }) => hash === event.transactionHash); + return __spreadProps(__spreadValues({}, 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: getAddress(to), + fee: String(fee) + }; + }); + const blocks = yield this.batchBlockService.getBatchBlocks([ + ...new Set(formattedEvents.map(({ blockNumber }) => blockNumber)) + ]); + return formattedEvents.map((event) => { + const { timestamp } = blocks.find(({ number }) => number === event.blockNumber); + return __spreadProps(__spreadValues({}, event), { + timestamp + }); + }); + } + }); + } + validateEvents({ events }) { + if (events.length && this.getType().toLowerCase() === DEPOSIT) { + const lastEvent = events[events.length - 1]; + if (lastEvent.leafIndex !== events.length - 1) { + const errMsg = `Deposit events invalid wants ${events.length - 1} leafIndex have ${lastEvent.leafIndex}`; + throw new Error(errMsg); + } + } + } +} +class BaseEchoService extends BaseEventsService { + constructor({ + netId, + provider, + graphApi, + subgraphName, + Echoer, + deployedBlock, + fetchDataOptions + }) { + super({ netId, provider, graphApi, subgraphName, contract: Echoer, deployedBlock, fetchDataOptions }); + } + getInstanceName() { + return `echo_${this.netId}`; + } + getType() { + return "Echo"; + } + getGraphMethod() { + return "getAllGraphEchoEvents"; + } + formatEvents(events) { + return __async$8(this, null, function* () { + return events.map(({ blockNumber, index: logIndex, transactionHash, args }) => { + const { who, data } = args; + if (who && data) { + const eventObjects = { + blockNumber, + logIndex, + transactionHash + }; + return __spreadProps(__spreadValues({}, eventObjects), { + address: who, + encryptedAccount: data + }); + } + }).filter((e) => e); + }); + } + getEventsFromGraph(_0) { + return __async$8(this, arguments, function* ({ fromBlock }) { + if (!this.graphApi || this.graphApi.includes("api.thegraph.com")) { + return { + events: [], + lastBlock: fromBlock + }; + } + return __superGet(BaseEchoService.prototype, this, "getEventsFromGraph").call(this, { fromBlock }); + }); + } +} +class BaseEncryptedNotesService extends BaseEventsService { + constructor({ + netId, + provider, + graphApi, + subgraphName, + Router, + deployedBlock, + fetchDataOptions + }) { + super({ netId, provider, graphApi, subgraphName, contract: Router, deployedBlock, fetchDataOptions }); + } + getInstanceName() { + return `encrypted_notes_${this.netId}`; + } + getType() { + return "EncryptedNote"; + } + getGraphMethod() { + return "getAllEncryptedNotes"; + } + formatEvents(events) { + return __async$8(this, null, function* () { + return events.map(({ blockNumber, index: logIndex, transactionHash, args }) => { + const { encryptedNote } = args; + if (encryptedNote) { + const eventObjects = { + blockNumber, + logIndex, + transactionHash + }; + return __spreadProps(__spreadValues({}, eventObjects), { + encryptedNote + }); + } + }).filter((e) => e); + }); + } +} +class BaseGovernanceService extends BaseEventsService { + constructor({ + netId, + provider, + graphApi, + subgraphName, + Governance, + deployedBlock, + fetchDataOptions + }) { + super({ netId, provider, graphApi, subgraphName, contract: Governance, deployedBlock, fetchDataOptions }); + this.batchTransactionService = new BatchTransactionService({ + provider, + onProgress: this.updateTransactionProgress + }); + } + getInstanceName() { + return `governance_${this.netId}`; + } + getType() { + return "*"; + } + getGraphMethod() { + return "getAllGovernanceEvents"; + } + formatEvents(events) { + return __async$8(this, null, function* () { + const proposalEvents = []; + const votedEvents = []; + const delegatedEvents = []; + const undelegatedEvents = []; + events.forEach(({ blockNumber, index: logIndex, transactionHash, args, eventName: event }) => { + const eventObjects = { + blockNumber, + logIndex, + transactionHash, + event + }; + if (event === "ProposalCreated") { + const { id, proposer, target, startTime, endTime, description } = args; + proposalEvents.push(__spreadProps(__spreadValues({}, eventObjects), { + id: Number(id), + proposer, + target, + startTime: Number(startTime), + endTime: Number(endTime), + description + })); + } + if (event === "Voted") { + const { proposalId, voter, support, votes } = args; + votedEvents.push(__spreadProps(__spreadValues({}, eventObjects), { + proposalId: Number(proposalId), + voter, + support, + votes, + from: "", + input: "" + })); + } + if (event === "Delegated") { + const { account, to: delegateTo } = args; + delegatedEvents.push(__spreadProps(__spreadValues({}, eventObjects), { + account, + delegateTo + })); + } + if (event === "Undelegated") { + const { account, from: delegateFrom } = args; + undelegatedEvents.push(__spreadProps(__spreadValues({}, eventObjects), { + account, + delegateFrom + })); + } + }); + if (votedEvents.length) { + this.updateTransactionProgress({ percentage: 0 }); + const txs = yield this.batchTransactionService.getBatchTransactions([ + ...new Set(votedEvents.map(({ transactionHash }) => transactionHash)) + ]); + votedEvents.forEach((event, index) => { + let { data: input, from } = txs.find((t) => t.hash === event.transactionHash); + if (!input || input.length > 2048) { + input = ""; + } + votedEvents[index].from = from; + votedEvents[index].input = input; + }); + } + return [...proposalEvents, ...votedEvents, ...delegatedEvents, ...undelegatedEvents]; + }); + } + getEventsFromGraph(_0) { + return __async$8(this, arguments, function* ({ fromBlock }) { + if (!this.graphApi || !this.subgraphName || this.graphApi.includes("api.thegraph.com")) { + return { + events: [], + lastBlock: fromBlock + }; + } + return __superGet(BaseGovernanceService.prototype, this, "getEventsFromGraph").call(this, { fromBlock }); + }); + } +} +class BaseRegistryService extends BaseEventsService { + constructor({ + netId, + provider, + graphApi, + subgraphName, + RelayerRegistry, + Aggregator, + relayerEnsSubdomains, + deployedBlock, + fetchDataOptions + }) { + super({ netId, provider, graphApi, subgraphName, contract: RelayerRegistry, deployedBlock, fetchDataOptions }); + this.Aggregator = Aggregator; + this.relayerEnsSubdomains = relayerEnsSubdomains; + this.updateInterval = 86400; + } + getInstanceName() { + return `registered_${this.netId}`; + } + // Name of type used for events + getType() { + return "RelayerRegistered"; + } + // Name of method used for graph + getGraphMethod() { + return "getAllRegisters"; + } + formatEvents(events) { + return __async$8(this, null, function* () { + return events.map(({ blockNumber, index: logIndex, transactionHash, args }) => { + const eventObjects = { + blockNumber, + logIndex, + transactionHash + }; + return __spreadProps(__spreadValues({}, eventObjects), { + ensName: args.ensName, + relayerAddress: args.relayerAddress + }); + }); + }); + } + /** + * Get saved or cached relayers + */ + getRelayersFromDB() { + return __async$8(this, null, function* () { + return { + timestamp: 0, + relayers: [] + }; + }); + } + /** + * Relayers from remote cache (Either from local cache, CDN, or from IPFS) + */ + getRelayersFromCache() { + return __async$8(this, null, function* () { + return { + timestamp: 0, + relayers: [] + }; + }); + } + getSavedRelayers() { + return __async$8(this, null, function* () { + let cachedRelayers = yield this.getRelayersFromDB(); + if (!cachedRelayers || !cachedRelayers.relayers.length) { + cachedRelayers = yield this.getRelayersFromCache(); + } + return cachedRelayers; + }); + } + getLatestRelayers() { + return __async$8(this, null, function* () { + const registerEvents = (yield this.updateEvents()).events; + const subdomains = Object.values(this.relayerEnsSubdomains); + const registerSet = /* @__PURE__ */ new Set(); + const uniqueRegisters = registerEvents.reverse().filter(({ ensName }) => { + if (!registerSet.has(ensName)) { + registerSet.add(ensName); + return true; + } + return false; + }); + const relayerNameHashes = uniqueRegisters.map((r) => namehash(r.ensName)); + const [relayersData, timestamp] = yield Promise.all([ + this.Aggregator.relayersData.staticCall(relayerNameHashes, subdomains), + this.provider.getBlock("latest").then((b) => Number(b == null ? void 0 : b.timestamp)) + ]); + const relayers = relayersData.map(({ owner, balance: stakeBalance, records, isRegistered }, index) => { + const { ensName, relayerAddress } = uniqueRegisters[index]; + const hostnames = {}; + records.forEach((record, recordIndex) => { + if (record) { + hostnames[Number(Object.keys(this.relayerEnsSubdomains)[recordIndex])] = record; + } + }); + const isOwner = !relayerAddress || relayerAddress === owner; + const hasMinBalance = stakeBalance >= MIN_STAKE_BALANCE; + const preCondition = Object.keys(hostnames).length && isOwner && isRegistered && hasMinBalance; + if (preCondition) { + return { + ensName, + relayerAddress, + isRegistered, + owner, + stakeBalance: formatEther(stakeBalance), + hostnames + }; + } + }).filter((r) => r); + return { + timestamp, + relayers + }; + }); + } + /** + * Handle saving relayers + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + saveRelayers(_0) { + return __async$8(this, arguments, function* ({ timestamp, relayers }) { + }); + } + /** + * Get cached or latest relayer and save to local + */ + updateRelayers() { + return __async$8(this, null, function* () { + let { timestamp, relayers } = yield this.getSavedRelayers(); + if (!relayers.length || timestamp + this.updateInterval < Math.floor(Date.now() / 1e3)) { + console.log("\nUpdating relayers from registry\n"); + ({ timestamp, relayers } = yield this.getLatestRelayers()); + yield this.saveRelayers({ timestamp, relayers }); + } + return { timestamp, relayers }; + }); + } +} + const _abi$5 = [ { constant: true, @@ -5387,7 +5742,7 @@ var index = /*#__PURE__*/Object.freeze({ ReverseRecords__factory: ReverseRecords__factory }); -var __async$8 = (__this, __arguments, generator) => { +var __async$7 = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { @@ -5412,13 +5767,13 @@ class Pedersen { this.pedersenPromise = this.initPedersen(); } initPedersen() { - return __async$8(this, null, function* () { + return __async$7(this, null, function* () { this.pedersenHash = yield buildPedersenHash(); this.babyJub = this.pedersenHash.babyJub; }); } unpackPoint(buffer) { - return __async$8(this, null, function* () { + return __async$7(this, null, function* () { var _a, _b; yield this.pedersenPromise; return (_b = this.babyJub) == null ? void 0 : _b.unpackPoint((_a = this.pedersenHash) == null ? void 0 : _a.hash(buffer)); @@ -5431,13 +5786,13 @@ class Pedersen { } const pedersen = new Pedersen(); function buffPedersenHash(buffer) { - return __async$8(this, null, function* () { + return __async$7(this, null, function* () { const [hash] = yield pedersen.unpackPoint(buffer); return pedersen.toStringBuffer(hash); }); } -var __async$7 = (__this, __arguments, generator) => { +var __async$6 = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { @@ -5458,7 +5813,7 @@ var __async$7 = (__this, __arguments, generator) => { }); }; function createDeposit(_0) { - return __async$7(this, arguments, function* ({ nullifier, secret }) { + return __async$6(this, arguments, function* ({ nullifier, secret }) { const preimage = new Uint8Array([...leInt2Buff(nullifier), ...leInt2Buff(secret)]); const noteHex = toFixedHex(bytesToBN(preimage), 62); const commitment = BigInt(yield buffPedersenHash(preimage)); @@ -5518,7 +5873,7 @@ class Deposit { ); } static createNote(_0) { - return __async$7(this, arguments, function* ({ currency, amount, netId, nullifier, secret }) { + return __async$6(this, arguments, function* ({ currency, amount, netId, nullifier, secret }) { if (!nullifier) { nullifier = rBigInt(31); } @@ -5545,7 +5900,7 @@ class Deposit { }); } static parseNote(noteString) { - return __async$7(this, null, function* () { + return __async$6(this, null, function* () { const noteRegex = new RegExp("tornado-(?\\w+)-(?[\\d.]+)-(?\\d+)-0x(?[0-9a-fA-F]{124})", "g"); const match = noteRegex.exec(noteString); if (!match) { @@ -5804,7 +6159,7 @@ class TornadoFeeOracle { } } -var __async$6 = (__this, __arguments, generator) => { +var __async$5 = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { @@ -5829,7 +6184,7 @@ class Mimc { this.mimcPromise = this.initMimc(); } initMimc() { - return __async$6(this, null, function* () { + return __async$5(this, null, function* () { this.sponge = yield buildMimcSponge(); this.hash = (left, right) => { var _a, _b; @@ -5838,7 +6193,7 @@ class Mimc { }); } getHash() { - return __async$6(this, null, function* () { + return __async$5(this, null, function* () { yield this.mimcPromise; return { sponge: this.sponge, @@ -5849,7 +6204,7 @@ class Mimc { } const mimc = new Mimc(); -var __async$5 = (__this, __arguments, generator) => { +var __async$4 = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { @@ -5892,7 +6247,7 @@ class MerkleTreeService { this.merkleWorkerPath = merkleWorkerPath; } createTree(events) { - return __async$5(this, null, function* () { + return __async$4(this, null, function* () { const { hash: hashFunction } = yield mimc.getHash(); if (this.merkleWorkerPath) { console.log("Using merkleWorker\n"); @@ -5944,7 +6299,7 @@ class MerkleTreeService { }); } createPartialTree(_0) { - return __async$5(this, arguments, function* ({ edge, elements }) { + return __async$4(this, arguments, function* ({ edge, elements }) { const { hash: hashFunction } = yield mimc.getHash(); if (this.merkleWorkerPath) { console.log("Using merkleWorker\n"); @@ -5998,7 +6353,7 @@ class MerkleTreeService { }); } verifyTree(events) { - return __async$5(this, null, function* () { + return __async$4(this, null, function* () { console.log( ` Creating deposit tree for ${this.netId} ${this.amount} ${this.currency.toUpperCase()} would take a while @@ -6018,7 +6373,7 @@ Creating deposit tree for ${this.netId} ${this.amount} ${this.currency.toUpperCa } } -var __async$4 = (__this, __arguments, generator) => { +var __async$3 = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { @@ -6039,7 +6394,7 @@ var __async$4 = (__this, __arguments, generator) => { }); }; function multicall(Multicall2, calls) { - return __async$4(this, null, function* () { + return __async$3(this, null, function* () { const calldata = calls.map((call) => { var _a, _b, _c; const target = ((_a = call.contract) == null ? void 0 : _a.target) || call.address; @@ -6062,7 +6417,7 @@ function multicall(Multicall2, calls) { }); } -var __async$3 = (__this, __arguments, generator) => { +var __async$2 = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { @@ -6089,7 +6444,7 @@ class TokenPriceOracle { this.oracle = oracle; } fetchPrices(tokens) { - return __async$3(this, null, function* () { + return __async$2(this, null, function* () { if (!this.oracle) { return new Promise((resolve) => resolve(tokens.map(() => parseEther("0.0001")))); } @@ -6108,288 +6463,6 @@ class TokenPriceOracle { } } -var __defProp = Object.defineProperty; -var __defProps = Object.defineProperties; -var __getOwnPropDescs = Object.getOwnPropertyDescriptors; -var __getOwnPropSymbols = Object.getOwnPropertySymbols; -var __hasOwnProp = Object.prototype.hasOwnProperty; -var __propIsEnum = Object.prototype.propertyIsEnumerable; -var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; -var __spreadValues = (a, b) => { - for (var prop in b || (b = {})) - if (__hasOwnProp.call(b, prop)) - __defNormalProp(a, prop, b[prop]); - if (__getOwnPropSymbols) - for (var prop of __getOwnPropSymbols(b)) { - if (__propIsEnum.call(b, prop)) - __defNormalProp(a, prop, b[prop]); - } - return a; -}; -var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); -var __async$2 = (__this, __arguments, generator) => { - return new Promise((resolve, reject) => { - var fulfilled = (value) => { - try { - step(generator.next(value)); - } catch (e) { - reject(e); - } - }; - var rejected = (value) => { - try { - step(generator.throw(value)); - } catch (e) { - reject(e); - } - }; - var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); - step((generator = generator.apply(__this, __arguments)).next()); - }); -}; -const MIN_STAKE_BALANCE = parseEther("500"); -const semVerRegex = new RegExp("^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)(?:-(?(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); -function parseSemanticVersion(version) { - const { groups } = semVerRegex.exec(version); - return groups; -} -function isRelayerUpdated(relayerVersion, netId) { - const { major, patch, prerelease } = parseSemanticVersion(relayerVersion); - const requiredMajor = netId === NetId.MAINNET ? "4" : "5"; - const isUpdatedMajor = major === requiredMajor; - if (prerelease) return false; - return isUpdatedMajor && (Number(patch) >= 5 || netId !== NetId.MAINNET); -} -function calculateScore({ stakeBalance, tornadoServiceFee }, minFee = 0.33, maxFee = 0.53) { - if (tornadoServiceFee < minFee) { - tornadoServiceFee = minFee; - } else if (tornadoServiceFee >= maxFee) { - return BigInt(0); - } - const serviceFeeCoefficient = (tornadoServiceFee - minFee) ** 2; - const feeDiffCoefficient = 1 / (maxFee - minFee) ** 2; - const coefficientsMultiplier = 1 - feeDiffCoefficient * serviceFeeCoefficient; - return BigInt(Math.floor(Number(stakeBalance) * coefficientsMultiplier)); -} -function getWeightRandom(weightsScores, random) { - for (let i = 0; i < weightsScores.length; i++) { - if (random < weightsScores[i]) { - return i; - } - random = random - weightsScores[i]; - } - return Math.floor(Math.random() * weightsScores.length); -} -function getSupportedInstances(instanceList) { - const rawList = Object.values(instanceList).map(({ instanceAddress }) => { - return Object.values(instanceAddress); - }).flat(); - return rawList.map((l) => getAddress(l)); -} -function pickWeightedRandomRelayer(relayers, netId) { - let minFee, maxFee; - if (netId !== NetId.MAINNET) { - minFee = 0.01; - maxFee = 0.3; - } - const weightsScores = relayers.map((el) => calculateScore(el, minFee, maxFee)); - const totalWeight = weightsScores.reduce((acc, curr) => { - return acc = acc + curr; - }, BigInt("0")); - const random = BigInt(Number(totalWeight) * Math.random()); - const weightRandomIndex = getWeightRandom(weightsScores, random); - return relayers[weightRandomIndex]; -} -class RelayerClient { - constructor({ netId, config, Aggregator, fetchDataOptions: fetchDataOptions2 }) { - this.netId = netId; - this.config = config; - this.Aggregator = Aggregator; - this.fetchDataOptions = fetchDataOptions2; - } - askRelayerStatus(_0) { - return __async$2(this, arguments, function* ({ - hostname, - relayerAddress - }) { - var _a, _b; - const url = `https://${!hostname.endsWith("/") ? hostname + "/" : hostname}`; - const rawStatus = yield fetchData(`${url}status`, __spreadProps(__spreadValues({}, this.fetchDataOptions), { - headers: { - "Content-Type": "application/json, application/x-www-form-urlencoded" - }, - timeout: ((_a = this.fetchDataOptions) == null ? void 0 : _a.torPort) ? 1e4 : 3e3, - maxRetry: ((_b = this.fetchDataOptions) == null ? void 0 : _b.torPort) ? 2 : 0 - })); - const statusValidator = ajv.compile(getStatusSchema(this.netId, this.config)); - if (!statusValidator(rawStatus)) { - throw new Error("Invalid status schema"); - } - const status = __spreadProps(__spreadValues({}, rawStatus), { - url - }); - if (status.currentQueue > 5) { - throw new Error("Withdrawal queue is overloaded"); - } - if (status.netId !== this.netId) { - 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"); - } - if (!isRelayerUpdated(status.version, this.netId)) { - throw new Error("Outdated version."); - } - return status; - }); - } - filterRelayer(curr, relayer, subdomains, debugRelayer = false) { - return __async$2(this, null, function* () { - var _a; - const { relayerEnsSubdomain } = this.config; - const subdomainIndex = subdomains.indexOf(relayerEnsSubdomain); - const mainnetSubdomain = curr.records[0]; - const hostname = curr.records[subdomainIndex]; - const isHostWithProtocol = hostname.includes("http"); - const { owner, balance: stakeBalance, isRegistered } = curr; - const { ensName, relayerAddress } = relayer; - const isOwner = !relayerAddress || relayerAddress === owner; - const hasMinBalance = stakeBalance >= MIN_STAKE_BALANCE; - const preCondition = hostname && isOwner && mainnetSubdomain && isRegistered && hasMinBalance && !isHostWithProtocol; - if (preCondition || debugRelayer) { - try { - const status = yield this.askRelayerStatus({ hostname, relayerAddress }); - return { - netId: status.netId, - url: status.url, - hostname, - ensName, - stakeBalance, - relayerAddress, - rewardAccount: getAddress(status.rewardAccount), - instances: getSupportedInstances(status.instances), - gasPrice: (_a = status.gasPrices) == null ? void 0 : _a.fast, - ethPrices: status.ethPrices, - currentQueue: status.currentQueue, - tornadoServiceFee: status.tornadoServiceFee - }; - } catch (err) { - if (debugRelayer) { - throw err; - } - return { - hostname, - relayerAddress, - errorMessage: err.message - }; - } - } else { - if (debugRelayer) { - const errMsg = `Relayer ${hostname} condition not met`; - throw new Error(errMsg); - } - return { - hostname, - relayerAddress, - errorMessage: `Relayer ${hostname} condition not met` - }; - } - }); - } - getValidRelayers(relayers, subdomains, debugRelayer = false) { - return __async$2(this, null, function* () { - const relayersSet = /* @__PURE__ */ new Set(); - const uniqueRelayers = relayers.reverse().filter(({ ensName }) => { - if (!relayersSet.has(ensName)) { - relayersSet.add(ensName); - return true; - } - return false; - }); - const relayerNameHashes = uniqueRelayers.map((r) => namehash(r.ensName)); - const relayersData = yield this.Aggregator.relayersData.staticCall(relayerNameHashes, subdomains); - const invalidRelayers = []; - const validRelayers = (yield Promise.all( - relayersData.map((curr, index) => this.filterRelayer(curr, uniqueRelayers[index], subdomains, debugRelayer)) - )).filter((r) => { - if (r.errorMessage) { - invalidRelayers.push(r); - return false; - } - return true; - }); - return { - validRelayers, - invalidRelayers - }; - }); - } - pickWeightedRandomRelayer(relayers) { - return pickWeightedRandomRelayer(relayers, this.netId); - } - tornadoWithdraw(_0) { - return __async$2(this, arguments, function* ({ contract, proof, args }) { - const { url } = this.selectedRelayer; - const withdrawResponse = yield fetchData(`${url}v1/tornadoWithdraw`, __spreadProps(__spreadValues({}, this.fetchDataOptions), { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - contract, - proof, - args - }) - })); - const { id, error } = withdrawResponse; - if (error) { - throw new Error(error); - } - let relayerStatus; - const jobUrl = `${url}v1/jobs/${id}`; - console.log(`Job submitted: ${jobUrl} -`); - while (!relayerStatus || !["FAILED", "CONFIRMED"].includes(relayerStatus)) { - const jobResponse = yield fetchData(jobUrl, __spreadProps(__spreadValues({}, this.fetchDataOptions), { - method: "GET", - headers: { - "Content-Type": "application/json" - } - })); - if (jobResponse.error) { - throw new Error(error); - } - const jobValidator = ajv.compile(jobsSchema); - if (!jobValidator(jobResponse)) { - const errMsg = `${jobUrl} has an invalid job response`; - throw new Error(errMsg); - } - const { status, txHash, confirmations, failedReason } = jobResponse; - if (relayerStatus !== status) { - if (status === "FAILED") { - const errMsg = `Job ${status}: ${jobUrl} failed reason: ${failedReason}`; - throw new Error(errMsg); - } else if (status === "SENT") { - console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash} -`); - } else if (status === "MINED") { - console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations} -`); - } else if (status === "CONFIRMED") { - console.log(`Job ${status}: ${jobUrl}, txhash: ${txHash}, confirmations: ${confirmations} -`); - } else { - console.log(`Job ${status}: ${jobUrl} -`); - } - relayerStatus = status; - } - yield sleep(3e3); - } - }); - } -} - var __async$1 = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { @@ -6536,4 +6609,4 @@ function calculateSnarkProof(input, circuit, provingKey) { }); } -export { BaseEchoService, BaseEncryptedNotesService, BaseEventsService, BaseGovernanceService, BaseRegistryService, BaseTornadoService, BatchBlockService, BatchEventsService, BatchTransactionService, DEPOSIT, Deposit, ENS__factory, ERC20__factory, GET_DEPOSITS, GET_ECHO_EVENTS, GET_ENCRYPTED_NOTES, GET_GOVERNANCE_APY, GET_GOVERNANCE_EVENTS, GET_NOTE_ACCOUNTS, GET_REGISTERED, GET_STATISTIC, GET_WITHDRAWALS, Invoice, MIN_STAKE_BALANCE, MerkleTreeService, Mimc, Multicall__factory, NetId, NoteAccount, OffchainOracle__factory, OvmGasPriceOracle__factory, Pedersen, RelayerClient, ReverseRecords__factory, TokenPriceOracle, TornadoBrowserProvider, TornadoFeeOracle, TornadoRpcSigner, TornadoVoidSigner, TornadoWallet, WITHDRAWAL, _META, addNetwork, ajv, base64ToBytes, bigIntReplacer, bnToBytes, buffPedersenHash, bufferToBytes, bytesToBN, bytesToBase64, bytesToHex, calculateScore, calculateSnarkProof, chunk, concatBytes, convertETHToTokenAmount, createDeposit, crypto, customConfig, defaultConfig, defaultUserAgent, enabledChains, index as factories, fetch, fetchData, fetchGetUrlFunc, getAllDeposits, getAllEncryptedNotes, getAllGovernanceEvents, getAllGraphEchoEvents, getAllRegisters, getAllWithdrawals, getConfig, getDeposits, getEncryptedNotes, getGovernanceEvents, getGraphEchoEvents, getHttpAgent, getInstanceByAddress, getMeta, getNetworkConfig, getNoteAccounts, getProvider, getProviderWithNetId, getRegisters, getStatistic, getStatusSchema, getSubdomains, getSupportedInstances, getTokenBalances, getWeightRandom, getWithdrawals, hexToBytes, initGroth16, isNode, isRelayerUpdated, jobsSchema, leBuff2Int, leInt2Buff, mimc, multicall, packEncryptedMessage, parseSemanticVersion, pedersen, pickWeightedRandomRelayer, populateTransaction, queryGraph, rBigInt, sleep, substring, toFixedHex, toFixedLength, unpackEncryptedMessage, validateUrl }; +export { BaseEchoService, BaseEncryptedNotesService, BaseEventsService, BaseGovernanceService, BaseRegistryService, BaseTornadoService, BatchBlockService, BatchEventsService, BatchTransactionService, DEPOSIT, Deposit, ENS__factory, ERC20__factory, GET_DEPOSITS, GET_ECHO_EVENTS, GET_ENCRYPTED_NOTES, GET_GOVERNANCE_APY, GET_GOVERNANCE_EVENTS, GET_NOTE_ACCOUNTS, GET_REGISTERED, GET_STATISTIC, GET_WITHDRAWALS, Invoice, MAX_FEE, MIN_FEE, MIN_STAKE_BALANCE, MerkleTreeService, Mimc, Multicall__factory, NetId, NoteAccount, OffchainOracle__factory, OvmGasPriceOracle__factory, Pedersen, RelayerClient, ReverseRecords__factory, TokenPriceOracle, TornadoBrowserProvider, TornadoFeeOracle, TornadoRpcSigner, TornadoVoidSigner, TornadoWallet, WITHDRAWAL, _META, addNetwork, ajv, base64ToBytes, bigIntReplacer, bnToBytes, buffPedersenHash, bufferToBytes, bytesToBN, bytesToBase64, bytesToHex, calculateScore, calculateSnarkProof, chunk, concatBytes, convertETHToTokenAmount, createDeposit, crypto, customConfig, defaultConfig, defaultUserAgent, enabledChains, index as factories, fetch, fetchData, fetchGetUrlFunc, getAllDeposits, getAllEncryptedNotes, getAllGovernanceEvents, getAllGraphEchoEvents, getAllRegisters, getAllWithdrawals, getConfig, getDeposits, getEncryptedNotes, getGovernanceEvents, getGraphEchoEvents, getHttpAgent, getInstanceByAddress, getMeta, getNetworkConfig, getNoteAccounts, getProvider, getProviderWithNetId, getRegisters, getRelayerEnsSubdomains, getStatistic, getStatusSchema, getSubdomains, getSupportedInstances, getTokenBalances, getWeightRandom, getWithdrawals, hexToBytes, initGroth16, isNode, isRelayerUpdated, jobsSchema, leBuff2Int, leInt2Buff, mimc, multicall, packEncryptedMessage, parseSemanticVersion, pedersen, pickWeightedRandomRelayer, populateTransaction, queryGraph, rBigInt, sleep, substring, toFixedHex, toFixedLength, unpackEncryptedMessage, validateUrl }; diff --git a/dist/index.umd.js b/dist/index.umd.js index ebe3747..a3a20d8 100644 --- a/dist/index.umd.js +++ b/dist/index.umd.js @@ -59069,9 +59069,12 @@ class NoteAccount { /* harmony export */ oW: () => (/* binding */ WITHDRAWAL), /* harmony export */ uw: () => (/* binding */ BaseEventsService) /* harmony export */ }); -/* harmony import */ var ethers__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(30031); +/* harmony import */ var ethers__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(30031); +/* harmony import */ var ethers__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(64563); +/* harmony import */ var ethers__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(99770); /* harmony import */ var _graphql__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(52049); /* harmony import */ var _batch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9723); +/* harmony import */ var _relayerClient__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(57194); var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; @@ -59118,6 +59121,7 @@ var __async = (__this, __arguments, generator) => { + const DEPOSIT = "deposit"; const WITHDRAWAL = "withdrawal"; class BaseEventsService { @@ -59129,13 +59133,13 @@ class BaseEventsService { contract, type = "", deployedBlock = 0, - fetchDataOptions: fetchDataOptions2 + fetchDataOptions }) { this.netId = netId; this.provider = provider; this.graphApi = graphApi; this.subgraphName = subgraphName; - this.fetchDataOptions = fetchDataOptions2; + this.fetchDataOptions = fetchDataOptions; this.contract = contract; this.type = type; this.deployedBlock = deployedBlock; @@ -59345,9 +59349,9 @@ class BaseTornadoService extends BaseEventsService { amount, currency, deployedBlock, - fetchDataOptions: fetchDataOptions2 + fetchDataOptions }) { - super({ netId, provider, graphApi, subgraphName, contract: Tornado, type, deployedBlock, fetchDataOptions: fetchDataOptions2 }); + super({ netId, provider, graphApi, subgraphName, contract: Tornado, type, deployedBlock, fetchDataOptions }); this.amount = amount; this.currency = currency; this.batchTransactionService = new _batch__WEBPACK_IMPORTED_MODULE_1__/* .BatchTransactionService */ .AF({ @@ -59407,7 +59411,7 @@ class BaseTornadoService extends BaseEventsService { logIndex, transactionHash, nullifierHash: String(nullifierHash), - to: (0,ethers__WEBPACK_IMPORTED_MODULE_2__/* .getAddress */ .b)(to), + to: (0,ethers__WEBPACK_IMPORTED_MODULE_3__/* .getAddress */ .b)(to), fee: String(fee) }; }); @@ -59441,9 +59445,9 @@ class BaseEchoService extends BaseEventsService { subgraphName, Echoer, deployedBlock, - fetchDataOptions: fetchDataOptions2 + fetchDataOptions }) { - super({ netId, provider, graphApi, subgraphName, contract: Echoer, deployedBlock, fetchDataOptions: fetchDataOptions2 }); + super({ netId, provider, graphApi, subgraphName, contract: Echoer, deployedBlock, fetchDataOptions }); } getInstanceName() { return `echo_${this.netId}`; @@ -59492,9 +59496,9 @@ class BaseEncryptedNotesService extends BaseEventsService { subgraphName, Router, deployedBlock, - fetchDataOptions: fetchDataOptions2 + fetchDataOptions }) { - super({ netId, provider, graphApi, subgraphName, contract: Router, deployedBlock, fetchDataOptions: fetchDataOptions2 }); + super({ netId, provider, graphApi, subgraphName, contract: Router, deployedBlock, fetchDataOptions }); } getInstanceName() { return `encrypted_notes_${this.netId}`; @@ -59531,9 +59535,9 @@ class BaseGovernanceService extends BaseEventsService { subgraphName, Governance, deployedBlock, - fetchDataOptions: fetchDataOptions2 + fetchDataOptions }) { - super({ netId, provider, graphApi, subgraphName, contract: Governance, deployedBlock, fetchDataOptions: fetchDataOptions2 }); + super({ netId, provider, graphApi, subgraphName, contract: Governance, deployedBlock, fetchDataOptions }); this.batchTransactionService = new _batch__WEBPACK_IMPORTED_MODULE_1__/* .BatchTransactionService */ .AF({ provider, onProgress: this.updateTransactionProgress @@ -59634,10 +59638,15 @@ class BaseRegistryService extends BaseEventsService { graphApi, subgraphName, RelayerRegistry, + Aggregator, + relayerEnsSubdomains, deployedBlock, - fetchDataOptions: fetchDataOptions2 + fetchDataOptions }) { - super({ netId, provider, graphApi, subgraphName, contract: RelayerRegistry, deployedBlock, fetchDataOptions: fetchDataOptions2 }); + super({ netId, provider, graphApi, subgraphName, contract: RelayerRegistry, deployedBlock, fetchDataOptions }); + this.Aggregator = Aggregator; + this.relayerEnsSubdomains = relayerEnsSubdomains; + this.updateInterval = 86400; } getInstanceName() { return `registered_${this.netId}`; @@ -59665,9 +59674,102 @@ class BaseRegistryService extends BaseEventsService { }); }); } - fetchRelayers() { + /** + * Get saved or cached relayers + */ + getRelayersFromDB() { return __async(this, null, function* () { - return (yield this.updateEvents()).events; + return { + timestamp: 0, + relayers: [] + }; + }); + } + /** + * Relayers from remote cache (Either from local cache, CDN, or from IPFS) + */ + getRelayersFromCache() { + return __async(this, null, function* () { + return { + timestamp: 0, + relayers: [] + }; + }); + } + getSavedRelayers() { + return __async(this, null, function* () { + let cachedRelayers = yield this.getRelayersFromDB(); + if (!cachedRelayers || !cachedRelayers.relayers.length) { + cachedRelayers = yield this.getRelayersFromCache(); + } + return cachedRelayers; + }); + } + getLatestRelayers() { + return __async(this, null, function* () { + const registerEvents = (yield this.updateEvents()).events; + const subdomains = Object.values(this.relayerEnsSubdomains); + const registerSet = /* @__PURE__ */ new Set(); + const uniqueRegisters = registerEvents.reverse().filter(({ ensName }) => { + if (!registerSet.has(ensName)) { + registerSet.add(ensName); + return true; + } + return false; + }); + const relayerNameHashes = uniqueRegisters.map((r) => (0,ethers__WEBPACK_IMPORTED_MODULE_4__/* .namehash */ .kM)(r.ensName)); + const [relayersData, timestamp] = yield Promise.all([ + this.Aggregator.relayersData.staticCall(relayerNameHashes, subdomains), + this.provider.getBlock("latest").then((b) => Number(b == null ? void 0 : b.timestamp)) + ]); + const relayers = relayersData.map(({ owner, balance: stakeBalance, records, isRegistered }, index) => { + const { ensName, relayerAddress } = uniqueRegisters[index]; + const hostnames = {}; + records.forEach((record, recordIndex) => { + if (record) { + hostnames[Number(Object.keys(this.relayerEnsSubdomains)[recordIndex])] = record; + } + }); + const isOwner = !relayerAddress || relayerAddress === owner; + const hasMinBalance = stakeBalance >= _relayerClient__WEBPACK_IMPORTED_MODULE_2__/* .MIN_STAKE_BALANCE */ .pO; + const preCondition = Object.keys(hostnames).length && isOwner && isRegistered && hasMinBalance; + if (preCondition) { + return { + ensName, + relayerAddress, + isRegistered, + owner, + stakeBalance: (0,ethers__WEBPACK_IMPORTED_MODULE_5__/* .formatEther */ .ck)(stakeBalance), + hostnames + }; + } + }).filter((r) => r); + return { + timestamp, + relayers + }; + }); + } + /** + * Handle saving relayers + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + saveRelayers(_0) { + return __async(this, arguments, function* ({ timestamp, relayers }) { + }); + } + /** + * Get cached or latest relayer and save to local + */ + updateRelayers() { + return __async(this, null, function* () { + let { timestamp, relayers } = yield this.getSavedRelayers(); + if (!relayers.length || timestamp + this.updateInterval < Math.floor(Date.now() / 1e3)) { + console.log("\nUpdating relayers from registry\n"); + ({ timestamp, relayers } = yield this.getLatestRelayers()); + yield this.saveRelayers({ timestamp, relayers }); + } + return { timestamp, relayers }; }); } } @@ -61138,6 +61240,7 @@ function multicall(Multicall2, calls) { /* harmony export */ Zh: () => (/* binding */ getInstanceByAddress), /* harmony export */ cF: () => (/* binding */ getSubdomains), /* harmony export */ cX: () => (/* binding */ customConfig), +/* harmony export */ o2: () => (/* binding */ getRelayerEnsSubdomains), /* harmony export */ sb: () => (/* binding */ defaultConfig), /* harmony export */ zj: () => (/* binding */ getConfig), /* harmony export */ zr: () => (/* binding */ NetId) @@ -61679,10 +61782,6 @@ const defaultConfig = { tornadoSubgraph: "tornadocash/sepolia-tornado-subgraph", subgraphs: {}, rpcUrls: { - pandaops: { - name: "ethpandaops", - url: "https://rpc.sepolia.ethpandaops.io" - }, sepolia: { name: "Sepolia RPC", url: "https://rpc.sepolia.org" @@ -61694,6 +61793,10 @@ const defaultConfig = { onerpc: { name: "1rpc", url: "https://1rpc.io/sepolia" + }, + ethpandaops: { + name: "ethpandaops", + url: "https://rpc.sepolia.ethpandaops.io" } }, tokens: { @@ -61772,6 +61875,13 @@ function getSubdomains() { const allConfig = getNetworkConfig(); return enabledChains.map((chain) => allConfig[chain].relayerEnsSubdomain); } +function getRelayerEnsSubdomains() { + const allConfig = getNetworkConfig(); + return Object.keys(allConfig).reduce((acc, chain) => { + acc[Number(chain)] = allConfig[Number(chain)].relayerEnsSubdomain; + return acc; + }, {}); +} /***/ }), @@ -71306,7 +71416,9 @@ class TornadoBrowserProvider extends BrowserProvider { "use strict"; /* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ KN: () => (/* binding */ MAX_FEE), /* harmony export */ OR: () => (/* binding */ RelayerClient), +/* harmony export */ Ss: () => (/* binding */ MIN_FEE), /* harmony export */ XF: () => (/* binding */ getSupportedInstances), /* harmony export */ c$: () => (/* binding */ getWeightRandom), /* harmony export */ mU: () => (/* binding */ isRelayerUpdated), @@ -71317,7 +71429,6 @@ class TornadoBrowserProvider extends BrowserProvider { /* harmony export */ }); /* harmony import */ var ethers__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(99770); /* harmony import */ var ethers__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(30031); -/* harmony import */ var ethers__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(64563); /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(67418); /* harmony import */ var _networkConfig__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(59499); /* harmony import */ var _providers__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(68434); @@ -71367,6 +71478,8 @@ var __async = (__this, __arguments, generator) => { +const MIN_FEE = 0.1; +const MAX_FEE = 0.6; const MIN_STAKE_BALANCE = (0,ethers__WEBPACK_IMPORTED_MODULE_4__/* .parseEther */ .g5)("500"); const semVerRegex = new RegExp("^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)(?:-(?(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); function parseSemanticVersion(version) { @@ -71380,16 +71493,16 @@ function isRelayerUpdated(relayerVersion, netId) { if (prerelease) return false; return isUpdatedMajor && (Number(patch) >= 5 || netId !== _networkConfig__WEBPACK_IMPORTED_MODULE_1__/* .NetId */ .zr.MAINNET); } -function calculateScore({ stakeBalance, tornadoServiceFee }, minFee = 0.33, maxFee = 0.53) { - if (tornadoServiceFee < minFee) { - tornadoServiceFee = minFee; - } else if (tornadoServiceFee >= maxFee) { +function calculateScore({ stakeBalance, tornadoServiceFee }) { + if (tornadoServiceFee < MIN_FEE) { + tornadoServiceFee = MIN_FEE; + } else if (tornadoServiceFee >= MAX_FEE) { return BigInt(0); } - const serviceFeeCoefficient = (tornadoServiceFee - minFee) ** 2; - const feeDiffCoefficient = 1 / (maxFee - minFee) ** 2; + 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) * coefficientsMultiplier)); + return BigInt(Math.floor(Number(stakeBalance || "0") * coefficientsMultiplier)); } function getWeightRandom(weightsScores, random) { for (let i = 0; i < weightsScores.length; i++) { @@ -71406,25 +71519,19 @@ function getSupportedInstances(instanceList) { }).flat(); return rawList.map((l) => (0,ethers__WEBPACK_IMPORTED_MODULE_5__/* .getAddress */ .b)(l)); } -function pickWeightedRandomRelayer(relayers, netId) { - let minFee, maxFee; - if (netId !== _networkConfig__WEBPACK_IMPORTED_MODULE_1__/* .NetId */ .zr.MAINNET) { - minFee = 0.01; - maxFee = 0.3; - } - const weightsScores = relayers.map((el) => calculateScore(el, minFee, maxFee)); +function pickWeightedRandomRelayer(relayers) { + const weightsScores = relayers.map((el) => calculateScore(el)); const totalWeight = weightsScores.reduce((acc, curr) => { return acc = acc + curr; }, BigInt("0")); - const random = BigInt(Number(totalWeight) * Math.random()); + const random = BigInt(Math.floor(Number(totalWeight) * Math.random())); const weightRandomIndex = getWeightRandom(weightsScores, random); return relayers[weightRandomIndex]; } class RelayerClient { - constructor({ netId, config, Aggregator, fetchDataOptions: fetchDataOptions2 }) { + constructor({ netId, config, fetchDataOptions: fetchDataOptions2 }) { this.netId = netId; this.config = config; - this.Aggregator = Aggregator; this.fetchDataOptions = fetchDataOptions2; } askRelayerStatus(_0) { @@ -71463,76 +71570,48 @@ class RelayerClient { return status; }); } - filterRelayer(curr, relayer, subdomains, debugRelayer = false) { + filterRelayer(relayer) { return __async(this, null, function* () { var _a; - const { relayerEnsSubdomain } = this.config; - const subdomainIndex = subdomains.indexOf(relayerEnsSubdomain); - const mainnetSubdomain = curr.records[0]; - const hostname = curr.records[subdomainIndex]; - const isHostWithProtocol = hostname.includes("http"); - const { owner, balance: stakeBalance, isRegistered } = curr; + const hostname = relayer.hostnames[this.netId]; const { ensName, relayerAddress } = relayer; - const isOwner = !relayerAddress || relayerAddress === owner; - const hasMinBalance = stakeBalance >= MIN_STAKE_BALANCE; - const preCondition = hostname && isOwner && mainnetSubdomain && isRegistered && hasMinBalance && !isHostWithProtocol; - if (preCondition || debugRelayer) { - try { - const status = yield this.askRelayerStatus({ hostname, relayerAddress }); - return { - netId: status.netId, - url: status.url, - hostname, - ensName, - stakeBalance, - relayerAddress, - rewardAccount: (0,ethers__WEBPACK_IMPORTED_MODULE_5__/* .getAddress */ .b)(status.rewardAccount), - instances: getSupportedInstances(status.instances), - gasPrice: (_a = status.gasPrices) == null ? void 0 : _a.fast, - ethPrices: status.ethPrices, - currentQueue: status.currentQueue, - tornadoServiceFee: status.tornadoServiceFee - }; - } catch (err) { - if (debugRelayer) { - throw err; - } - return { - hostname, - relayerAddress, - errorMessage: err.message - }; - } - } else { - if (debugRelayer) { - const errMsg = `Relayer ${hostname} condition not met`; - throw new Error(errMsg); - } + if (!hostname) { + return; + } + try { + const status = yield this.askRelayerStatus({ hostname, relayerAddress }); + return { + netId: status.netId, + url: status.url, + hostname, + ensName, + relayerAddress, + rewardAccount: (0,ethers__WEBPACK_IMPORTED_MODULE_5__/* .getAddress */ .b)(status.rewardAccount), + instances: getSupportedInstances(status.instances), + stakeBalance: relayer.stakeBalance, + gasPrice: (_a = status.gasPrices) == null ? void 0 : _a.fast, + ethPrices: status.ethPrices, + currentQueue: status.currentQueue, + tornadoServiceFee: status.tornadoServiceFee + }; + } catch (err) { return { hostname, relayerAddress, - errorMessage: `Relayer ${hostname} condition not met` + errorMessage: err.message, + hasError: true }; } }); } - getValidRelayers(relayers, subdomains, debugRelayer = false) { + getValidRelayers(relayers) { return __async(this, null, function* () { - const relayersSet = /* @__PURE__ */ new Set(); - const uniqueRelayers = relayers.reverse().filter(({ ensName }) => { - if (!relayersSet.has(ensName)) { - relayersSet.add(ensName); - return true; - } - return false; - }); - const relayerNameHashes = uniqueRelayers.map((r) => (0,ethers__WEBPACK_IMPORTED_MODULE_6__/* .namehash */ .kM)(r.ensName)); - const relayersData = yield this.Aggregator.relayersData.staticCall(relayerNameHashes, subdomains); const invalidRelayers = []; - const validRelayers = (yield Promise.all( - relayersData.map((curr, index) => this.filterRelayer(curr, uniqueRelayers[index], subdomains, debugRelayer)) - )).filter((r) => { - if (r.errorMessage) { + const validRelayers = (yield Promise.all(relayers.map((relayer) => this.filterRelayer(relayer)))).filter((r) => { + if (!r) { + return false; + } + if (r.hasError) { invalidRelayers.push(r); return false; } @@ -71545,7 +71624,7 @@ class RelayerClient { }); } pickWeightedRandomRelayer(relayers) { - return pickWeightedRandomRelayer(relayers, this.netId); + return pickWeightedRandomRelayer(relayers); } tornadoWithdraw(_0) { return __async(this, arguments, function* ({ contract, proof, args }) { @@ -171078,11 +171157,12 @@ function encodeRlp(object) { // EXPORTS __webpack_require__.d(__webpack_exports__, { + ck: () => (/* binding */ formatEther), g5: () => (/* binding */ parseEther), XS: () => (/* binding */ parseUnits) }); -// UNUSED EXPORTS: formatEther, formatUnits +// UNUSED EXPORTS: formatUnits // EXTERNAL MODULE: ./node_modules/ethers/lib.esm/utils/errors.js var errors = __webpack_require__(57339); @@ -171253,7 +171333,7 @@ function fixednumber_toString(val, decimals) { * variant will silently ignore underflow, while the //signalling// variant * will thow a [[NumericFaultError]] on underflow. */ -class fixednumber_FixedNumber { +class FixedNumber { /** * The specific fixed-point arithmetic field for this value. */ @@ -171328,7 +171408,7 @@ class fixednumber_FixedNumber { } */ val = checkValue(val, this.#format, safeOp); - return new fixednumber_FixedNumber(_guard, val, this.#format); + return new FixedNumber(_guard, val, this.#format); } #add(o, safeOp) { this.#checkFormat(o); @@ -171515,7 +171595,7 @@ class fixednumber_FixedNumber { const tens = getTens(delta); value = (value / tens) * tens; checkValue(value, this.#format, "round"); - return new fixednumber_FixedNumber(_guard, value, this.#format); + return new FixedNumber(_guard, value, this.#format); } /** * Returns true if %%this%% is equal to ``0``. @@ -171544,7 +171624,7 @@ class fixednumber_FixedNumber { * This will throw if the value cannot fit into %%format%%. */ toFormat(format) { - return fixednumber_FixedNumber.fromString(this.toString(), format); + return FixedNumber.fromString(this.toString(), format); } /** * Creates a new [[FixedNumber]] for %%value%% divided by @@ -171570,7 +171650,7 @@ class fixednumber_FixedNumber { value *= getTens(-delta); } checkValue(value, format, "fromValue"); - return new fixednumber_FixedNumber(_guard, value, format); + return new FixedNumber(_guard, value, format); } /** * Creates a new [[FixedNumber]] for %%value%% with %%format%%. @@ -171595,7 +171675,7 @@ class fixednumber_FixedNumber { decimal = decimal.substring(0, format.decimals); const value = BigInt(match[1] + whole + decimal); checkValue(value, format, "fromString"); - return new fixednumber_FixedNumber(_guard, value, format); + return new FixedNumber(_guard, value, format); } /** * Creates a new [[FixedNumber]] with the big-endian representation @@ -171611,7 +171691,7 @@ class fixednumber_FixedNumber { value = (0,maths/* fromTwos */.ST)(value, format.width); } checkValue(value, format, "fromBytes"); - return new fixednumber_FixedNumber(_guard, value, format); + return new FixedNumber(_guard, value, format); } } //const f1 = FixedNumber.fromString("12.56", "fixed16x2"); @@ -171663,11 +171743,11 @@ function formatUnits(value, unit) { let decimals = 18; if (typeof (unit) === "string") { const index = names.indexOf(unit); - assertArgument(index >= 0, "invalid unit", "unit", unit); + (0,errors/* assertArgument */.MR)(index >= 0, "invalid unit", "unit", unit); decimals = 3 * index; } else if (unit != null) { - decimals = getNumber(unit, "unit"); + decimals = (0,maths/* getNumber */.WZ)(unit, "unit"); } return FixedNumber.fromValue(value, decimals, { decimals, width: 512 }).toString(); } @@ -171687,7 +171767,7 @@ function parseUnits(value, unit) { else if (unit != null) { decimals = (0,maths/* getNumber */.WZ)(unit, "unit"); } - return fixednumber_FixedNumber.fromString(value, { decimals, width: 512 }).value; + return FixedNumber.fromString(value, { decimals, width: 512 }).value; } /** * Converts %%value%% into a //decimal string// using 18 decimal places. @@ -172891,6 +172971,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ GET_STATISTIC: () => (/* reexport safe */ _graphql__WEBPACK_IMPORTED_MODULE_1__.GET_STATISTIC), /* harmony export */ GET_WITHDRAWALS: () => (/* reexport safe */ _graphql__WEBPACK_IMPORTED_MODULE_1__.GET_WITHDRAWALS), /* harmony export */ Invoice: () => (/* reexport safe */ _deposits__WEBPACK_IMPORTED_MODULE_5__.qO), +/* harmony export */ MAX_FEE: () => (/* reexport safe */ _relayerClient__WEBPACK_IMPORTED_MODULE_15__.KN), +/* harmony export */ MIN_FEE: () => (/* reexport safe */ _relayerClient__WEBPACK_IMPORTED_MODULE_15__.Ss), /* harmony export */ MIN_STAKE_BALANCE: () => (/* reexport safe */ _relayerClient__WEBPACK_IMPORTED_MODULE_15__.pO), /* harmony export */ MerkleTreeService: () => (/* reexport safe */ _merkleTree__WEBPACK_IMPORTED_MODULE_8__.s), /* harmony export */ Mimc: () => (/* reexport safe */ _mimc__WEBPACK_IMPORTED_MODULE_9__.p), @@ -172953,6 +173035,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ getProvider: () => (/* reexport safe */ _providers__WEBPACK_IMPORTED_MODULE_14__.sO), /* harmony export */ getProviderWithNetId: () => (/* reexport safe */ _providers__WEBPACK_IMPORTED_MODULE_14__.MF), /* harmony export */ getRegisters: () => (/* reexport safe */ _graphql__WEBPACK_IMPORTED_MODULE_1__.getRegisters), +/* harmony export */ getRelayerEnsSubdomains: () => (/* reexport safe */ _networkConfig__WEBPACK_IMPORTED_MODULE_11__.o2), /* harmony export */ getStatistic: () => (/* reexport safe */ _graphql__WEBPACK_IMPORTED_MODULE_1__.getStatistic), /* harmony export */ getStatusSchema: () => (/* reexport safe */ _schemas__WEBPACK_IMPORTED_MODULE_2__.c_), /* harmony export */ getSubdomains: () => (/* reexport safe */ _networkConfig__WEBPACK_IMPORTED_MODULE_11__.cF), diff --git a/dist/networkConfig.d.ts b/dist/networkConfig.d.ts index a8b375d..316be6a 100644 --- a/dist/networkConfig.d.ts +++ b/dist/networkConfig.d.ts @@ -87,6 +87,9 @@ export type Config = { export type networkConfig = { [key in NetIdType]: Config; }; +export type SubdomainMap = { + [key in NetIdType]: string; +}; export declare const defaultConfig: networkConfig; export declare const enabledChains: NetIdType[]; /** @@ -112,3 +115,4 @@ export declare function getInstanceByAddress({ netId, address }: { currency: string; } | undefined; export declare function getSubdomains(): string[]; +export declare function getRelayerEnsSubdomains(): SubdomainMap; diff --git a/dist/relayerClient.d.ts b/dist/relayerClient.d.ts index 351ff41..a2e26bf 100644 --- a/dist/relayerClient.d.ts +++ b/dist/relayerClient.d.ts @@ -1,35 +1,36 @@ -import type { Aggregator } from '@tornado/contracts'; -import type { RelayerStructOutput } from '@tornado/contracts/dist/contracts/Governance/Aggregator/Aggregator'; import { NetIdType, Config } from './networkConfig'; import { fetchDataOptions } from './providers'; import type { snarkProofs } from './websnark'; +import { CachedRelayerInfo } from './events/base'; +export declare const MIN_FEE = 0.1; +export declare const MAX_FEE = 0.6; export declare const MIN_STAKE_BALANCE: bigint; export interface RelayerParams { ensName: string; - relayerAddress?: string; + relayerAddress: string; } -export interface Relayer { +/** + * Info from relayer status + */ +export type RelayerInfo = RelayerParams & { netId: NetIdType; url: string; hostname: string; rewardAccount: string; instances: string[]; + stakeBalance?: string; gasPrice?: number; ethPrices?: { [key in string]: string; }; currentQueue: number; tornadoServiceFee: number; -} -export type RelayerInfo = Relayer & { - ensName: string; - stakeBalance: bigint; - relayerAddress: string; }; export type RelayerError = { hostname: string; relayerAddress?: string; errorMessage?: string; + hasError: boolean; }; export interface RelayerStatus { url: string; @@ -87,7 +88,7 @@ export interface semanticVersion { } export declare function parseSemanticVersion(version: string): semanticVersion; export declare function isRelayerUpdated(relayerVersion: string, netId: NetIdType): boolean; -export declare function calculateScore({ stakeBalance, tornadoServiceFee }: RelayerInfo, minFee?: number, maxFee?: number): bigint; +export declare function calculateScore({ stakeBalance, tornadoServiceFee }: RelayerInfo): bigint; export declare function getWeightRandom(weightsScores: bigint[], random: bigint): number; export type RelayerInstanceList = { [key in string]: { @@ -97,11 +98,10 @@ export type RelayerInstanceList = { }; }; export declare function getSupportedInstances(instanceList: RelayerInstanceList): string[]; -export declare function pickWeightedRandomRelayer(relayers: RelayerInfo[], netId: NetIdType): RelayerInfo; +export declare function pickWeightedRandomRelayer(relayers: RelayerInfo[]): RelayerInfo; export interface RelayerClientConstructor { netId: NetIdType; config: Config; - Aggregator: Aggregator; fetchDataOptions?: fetchDataOptions; } export type RelayerClientWithdraw = snarkProofs & { @@ -110,16 +110,15 @@ export type RelayerClientWithdraw = snarkProofs & { export declare class RelayerClient { netId: NetIdType; config: Config; - Aggregator: Aggregator; - selectedRelayer?: Relayer; + selectedRelayer?: RelayerInfo; fetchDataOptions?: fetchDataOptions; - constructor({ netId, config, Aggregator, fetchDataOptions }: RelayerClientConstructor); + constructor({ netId, config, fetchDataOptions }: RelayerClientConstructor); askRelayerStatus({ hostname, relayerAddress, }: { hostname: string; relayerAddress?: string; }): Promise; - filterRelayer(curr: RelayerStructOutput, relayer: RelayerParams, subdomains: string[], debugRelayer?: boolean): Promise; - getValidRelayers(relayers: RelayerParams[], subdomains: string[], debugRelayer?: boolean): Promise<{ + filterRelayer(relayer: CachedRelayerInfo): Promise; + getValidRelayers(relayers: CachedRelayerInfo[]): Promise<{ validRelayers: RelayerInfo[]; invalidRelayers: RelayerError[]; }>; diff --git a/package.json b/package.json index 1e1b5bd..5ced6df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tornado/core", - "version": "1.0.4", + "version": "1.0.5", "description": "An SDK for building applications on top of Privacy Pools", "main": "./dist/index.js", "module": "./dist/index.mjs", diff --git a/src/events/base.ts b/src/events/base.ts index aa34158..ff6aa78 100644 --- a/src/events/base.ts +++ b/src/events/base.ts @@ -1,4 +1,15 @@ -import { BaseContract, Provider, EventLog, TransactionResponse, getAddress, Block, ContractEventName } from 'ethers'; +import { + BaseContract, + Provider, + EventLog, + TransactionResponse, + getAddress, + Block, + ContractEventName, + namehash, + formatEther, +} from 'ethers'; + import type { Tornado, TornadoRouter, @@ -6,8 +17,11 @@ import type { Governance, RelayerRegistry, Echoer, + Aggregator, } from '@tornado/contracts'; + import * as graph from '../graphql'; + import { BatchEventsService, BatchBlockService, @@ -15,8 +29,11 @@ import { BatchEventOnProgress, BatchBlockOnProgress, } from '../batch'; -import { fetchDataOptions } from '../providers'; -import type { NetIdType } from '../networkConfig'; + +import type { fetchDataOptions } from '../providers'; +import type { NetIdType, Config, SubdomainMap } from '../networkConfig'; +import { RelayerParams, MIN_STAKE_BALANCE } from '../relayerClient'; + import type { BaseEvents, CachedEvents, @@ -741,27 +758,57 @@ export class BaseGovernanceService extends BaseEventsService { + Aggregator: Aggregator; + relayerEnsSubdomains: SubdomainMap; + updateInterval: number; + constructor({ netId, provider, graphApi, subgraphName, RelayerRegistry, + Aggregator, + relayerEnsSubdomains, deployedBlock, fetchDataOptions, }: BaseRegistryServiceConstructor) { super({ netId, provider, graphApi, subgraphName, contract: RelayerRegistry, deployedBlock, fetchDataOptions }); + + this.Aggregator = Aggregator; + this.relayerEnsSubdomains = relayerEnsSubdomains; + + this.updateInterval = 86400; } getInstanceName() { @@ -794,7 +841,114 @@ export class BaseRegistryService extends BaseEventsService { }); } - async fetchRelayers(): Promise { - return (await this.updateEvents()).events; + /** + * Get saved or cached relayers + */ + async getRelayersFromDB(): Promise { + return { + timestamp: 0, + relayers: [], + }; + } + + /** + * Relayers from remote cache (Either from local cache, CDN, or from IPFS) + */ + async getRelayersFromCache(): Promise { + return { + timestamp: 0, + relayers: [], + }; + } + + async getSavedRelayers(): Promise { + let cachedRelayers = await this.getRelayersFromDB(); + + if (!cachedRelayers || !cachedRelayers.relayers.length) { + cachedRelayers = await this.getRelayersFromCache(); + } + + return cachedRelayers; + } + + async getLatestRelayers(): Promise { + const registerEvents = (await this.updateEvents()).events; + + const subdomains = Object.values(this.relayerEnsSubdomains); + + const registerSet = new Set(); + + const uniqueRegisters = registerEvents.reverse().filter(({ ensName }) => { + if (!registerSet.has(ensName)) { + registerSet.add(ensName); + return true; + } + return false; + }); + + const relayerNameHashes = uniqueRegisters.map((r) => namehash(r.ensName)); + + const [relayersData, timestamp] = await Promise.all([ + this.Aggregator.relayersData.staticCall(relayerNameHashes, subdomains), + this.provider.getBlock('latest').then((b) => Number(b?.timestamp)), + ]); + + const relayers = relayersData + .map(({ owner, balance: stakeBalance, records, isRegistered }, index) => { + const { ensName, relayerAddress } = uniqueRegisters[index]; + + const hostnames = {} as SubdomainMap; + + records.forEach((record, recordIndex) => { + if (record) { + hostnames[Number(Object.keys(this.relayerEnsSubdomains)[recordIndex])] = record; + } + }); + + const isOwner = !relayerAddress || relayerAddress === owner; + const hasMinBalance = stakeBalance >= MIN_STAKE_BALANCE; + + const preCondition = Object.keys(hostnames).length && isOwner && isRegistered && hasMinBalance; + + if (preCondition) { + return { + ensName, + relayerAddress, + isRegistered, + owner, + stakeBalance: formatEther(stakeBalance), + hostnames, + } as CachedRelayerInfo; + } + }) + .filter((r) => r) as CachedRelayerInfo[]; + + return { + timestamp, + relayers, + }; + } + + /** + * Handle saving relayers + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async saveRelayers({ timestamp, relayers }: CachedRelayers) {} + + /** + * Get cached or latest relayer and save to local + */ + async updateRelayers(): Promise { + let { timestamp, relayers } = await this.getSavedRelayers(); + + if (!relayers.length || timestamp + this.updateInterval < Math.floor(Date.now() / 1000)) { + console.log('\nUpdating relayers from registry\n'); + + ({ timestamp, relayers } = await this.getLatestRelayers()); + + await this.saveRelayers({ timestamp, relayers }); + } + + return { timestamp, relayers }; } } diff --git a/src/networkConfig.ts b/src/networkConfig.ts index 2bae4b0..dd23218 100644 --- a/src/networkConfig.ts +++ b/src/networkConfig.ts @@ -102,6 +102,10 @@ export type networkConfig = { [key in NetIdType]: Config; }; +export type SubdomainMap = { + [key in NetIdType]: string; +}; + export const defaultConfig: networkConfig = { [NetId.MAINNET]: { rpcCallRetryAttempt: 15, @@ -611,10 +615,6 @@ export const defaultConfig: networkConfig = { tornadoSubgraph: 'tornadocash/sepolia-tornado-subgraph', subgraphs: {}, rpcUrls: { - pandaops: { - name: 'ethpandaops', - url: 'https://rpc.sepolia.ethpandaops.io', - }, sepolia: { name: 'Sepolia RPC', url: 'https://rpc.sepolia.org', @@ -627,6 +627,10 @@ export const defaultConfig: networkConfig = { name: '1rpc', url: 'https://1rpc.io/sepolia', }, + ethpandaops: { + name: 'ethpandaops', + url: 'https://rpc.sepolia.ethpandaops.io', + }, }, tokens: { eth: { @@ -738,3 +742,12 @@ export function getSubdomains() { return enabledChains.map((chain) => allConfig[chain].relayerEnsSubdomain); } + +export function getRelayerEnsSubdomains() { + const allConfig = getNetworkConfig(); + + return Object.keys(allConfig).reduce((acc, chain) => { + acc[Number(chain)] = allConfig[Number(chain)].relayerEnsSubdomain; + return acc; + }, {} as SubdomainMap); +} diff --git a/src/relayerClient.ts b/src/relayerClient.ts index 5f633c5..f838716 100644 --- a/src/relayerClient.ts +++ b/src/relayerClient.ts @@ -1,43 +1,45 @@ -import { getAddress, namehash, parseEther } from 'ethers'; -import type { Aggregator } from '@tornado/contracts'; -import type { RelayerStructOutput } from '@tornado/contracts/dist/contracts/Governance/Aggregator/Aggregator'; +import { getAddress, parseEther } from 'ethers'; import { sleep } from './utils'; import { NetId, NetIdType, Config } from './networkConfig'; import { fetchData, fetchDataOptions } from './providers'; import { ajv, jobsSchema, getStatusSchema } from './schemas'; import type { snarkProofs } from './websnark'; +import { CachedRelayerInfo } from './events/base'; + +export const MIN_FEE = 0.1; + +export const MAX_FEE = 0.6; export const MIN_STAKE_BALANCE = parseEther('500'); export interface RelayerParams { ensName: string; - relayerAddress?: string; + relayerAddress: string; } -export interface Relayer { +/** + * Info from relayer status + */ +export type RelayerInfo = RelayerParams & { netId: NetIdType; url: string; hostname: string; rewardAccount: string; instances: string[]; + stakeBalance?: string; gasPrice?: number; ethPrices?: { [key in string]: string; }; currentQueue: number; tornadoServiceFee: number; -} - -export type RelayerInfo = Relayer & { - ensName: string; - stakeBalance: bigint; - relayerAddress: string; }; export type RelayerError = { hostname: string; relayerAddress?: string; errorMessage?: string; + hasError: boolean; }; export interface RelayerStatus { @@ -117,18 +119,18 @@ export function isRelayerUpdated(relayerVersion: string, netId: NetIdType) { return isUpdatedMajor && (Number(patch) >= 5 || netId !== NetId.MAINNET); // Patch checking - also backwards compatibility for Mainnet } -export function calculateScore({ stakeBalance, tornadoServiceFee }: RelayerInfo, minFee = 0.33, maxFee = 0.53) { - if (tornadoServiceFee < minFee) { - tornadoServiceFee = minFee; - } else if (tornadoServiceFee >= maxFee) { +export function calculateScore({ stakeBalance, tornadoServiceFee }: RelayerInfo) { + if (tornadoServiceFee < MIN_FEE) { + tornadoServiceFee = MIN_FEE; + } else if (tornadoServiceFee >= MAX_FEE) { return BigInt(0); } - const serviceFeeCoefficient = (tornadoServiceFee - minFee) ** 2; - const feeDiffCoefficient = 1 / (maxFee - minFee) ** 2; + 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) * coefficientsMultiplier)); + return BigInt(Math.floor(Number(stakeBalance || '0') * coefficientsMultiplier)); } export function getWeightRandom(weightsScores: bigint[], random: bigint) { @@ -159,20 +161,13 @@ export function getSupportedInstances(instanceList: RelayerInstanceList) { return rawList.map((l) => getAddress(l)); } -export function pickWeightedRandomRelayer(relayers: RelayerInfo[], netId: NetIdType) { - let minFee: number, maxFee: number; - - if (netId !== NetId.MAINNET) { - minFee = 0.01; - maxFee = 0.3; - } - - const weightsScores = relayers.map((el) => calculateScore(el, minFee, maxFee)); +export function pickWeightedRandomRelayer(relayers: RelayerInfo[]) { + const weightsScores = relayers.map((el) => calculateScore(el)); const totalWeight = weightsScores.reduce((acc, curr) => { return (acc = acc + curr); }, BigInt('0')); - const random = BigInt(Number(totalWeight) * Math.random()); + const random = BigInt(Math.floor(Number(totalWeight) * Math.random())); const weightRandomIndex = getWeightRandom(weightsScores, random); return relayers[weightRandomIndex]; @@ -181,7 +176,6 @@ export function pickWeightedRandomRelayer(relayers: RelayerInfo[], netId: NetIdT export interface RelayerClientConstructor { netId: NetIdType; config: Config; - Aggregator: Aggregator; fetchDataOptions?: fetchDataOptions; } @@ -192,14 +186,12 @@ export type RelayerClientWithdraw = snarkProofs & { export class RelayerClient { netId: NetIdType; config: Config; - Aggregator: Aggregator; - selectedRelayer?: Relayer; + selectedRelayer?: RelayerInfo; fetchDataOptions?: fetchDataOptions; - constructor({ netId, config, Aggregator, fetchDataOptions }: RelayerClientConstructor) { + constructor({ netId, config, fetchDataOptions }: RelayerClientConstructor) { this.netId = netId; this.config = config; - this.Aggregator = Aggregator; this.fetchDataOptions = fetchDataOptions; } @@ -251,101 +243,54 @@ export class RelayerClient { return status; } - async filterRelayer( - curr: RelayerStructOutput, - relayer: RelayerParams, - subdomains: string[], - debugRelayer: boolean = false, - ): Promise { - const { relayerEnsSubdomain } = this.config; - const subdomainIndex = subdomains.indexOf(relayerEnsSubdomain); - const mainnetSubdomain = curr.records[0]; - const hostname = curr.records[subdomainIndex]; - const isHostWithProtocol = hostname.includes('http'); - - const { owner, balance: stakeBalance, isRegistered } = curr; + async filterRelayer(relayer: CachedRelayerInfo): Promise { + const hostname = relayer.hostnames[this.netId]; const { ensName, relayerAddress } = relayer; - const isOwner = !relayerAddress || relayerAddress === owner; - const hasMinBalance = stakeBalance >= MIN_STAKE_BALANCE; + if (!hostname) { + return; + } - const preCondition = - hostname && isOwner && mainnetSubdomain && isRegistered && hasMinBalance && !isHostWithProtocol; + try { + const status = await this.askRelayerStatus({ hostname, relayerAddress }); - if (preCondition || debugRelayer) { - try { - const status = await this.askRelayerStatus({ hostname, relayerAddress }); - - return { - netId: status.netId, - url: status.url, - hostname, - ensName, - stakeBalance, - relayerAddress, - rewardAccount: getAddress(status.rewardAccount), - instances: getSupportedInstances(status.instances), - gasPrice: status.gasPrices?.fast, - ethPrices: status.ethPrices, - currentQueue: status.currentQueue, - tornadoServiceFee: status.tornadoServiceFee, - } as RelayerInfo; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (err: any) { - if (debugRelayer) { - throw err; - } - return { - hostname, - relayerAddress, - errorMessage: err.message, - } as RelayerError; - } - } else { - if (debugRelayer) { - const errMsg = `Relayer ${hostname} condition not met`; - throw new Error(errMsg); - } + return { + netId: status.netId, + url: status.url, + hostname, + ensName, + relayerAddress, + rewardAccount: getAddress(status.rewardAccount), + instances: getSupportedInstances(status.instances), + stakeBalance: relayer.stakeBalance, + gasPrice: status.gasPrices?.fast, + ethPrices: status.ethPrices, + currentQueue: status.currentQueue, + tornadoServiceFee: status.tornadoServiceFee, + } as RelayerInfo; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { return { hostname, relayerAddress, - errorMessage: `Relayer ${hostname} condition not met`, - }; + errorMessage: err.message, + hasError: true, + } as RelayerError; } } - async getValidRelayers( - // this should be ascending order of events - relayers: RelayerParams[], - subdomains: string[], - debugRelayer: boolean = false, - ): Promise<{ + async getValidRelayers(relayers: CachedRelayerInfo[]): Promise<{ validRelayers: RelayerInfo[]; invalidRelayers: RelayerError[]; }> { - const relayersSet = new Set(); - - const uniqueRelayers = relayers.reverse().filter(({ ensName }) => { - if (!relayersSet.has(ensName)) { - relayersSet.add(ensName); - return true; - } - return false; - }); - - const relayerNameHashes = uniqueRelayers.map((r) => namehash(r.ensName)); - - const relayersData = await this.Aggregator.relayersData.staticCall(relayerNameHashes, subdomains); - const invalidRelayers: RelayerError[] = []; - const validRelayers = ( - await Promise.all( - relayersData.map((curr, index) => this.filterRelayer(curr, uniqueRelayers[index], subdomains, debugRelayer)), - ) - ).filter((r) => { - if ((r as RelayerError).errorMessage) { - invalidRelayers.push(r); + const validRelayers = (await Promise.all(relayers.map((relayer) => this.filterRelayer(relayer)))).filter((r) => { + if (!r) { + return false; + } + if ((r as RelayerError).hasError) { + invalidRelayers.push(r as RelayerError); return false; } return true; @@ -358,11 +303,11 @@ export class RelayerClient { } pickWeightedRandomRelayer(relayers: RelayerInfo[]) { - return pickWeightedRandomRelayer(relayers, this.netId); + return pickWeightedRandomRelayer(relayers); } async tornadoWithdraw({ contract, proof, args }: RelayerClientWithdraw) { - const { url } = this.selectedRelayer as Relayer; + const { url } = this.selectedRelayer as RelayerInfo; const withdrawResponse = (await fetchData(`${url}v1/tornadoWithdraw`, { ...this.fetchDataOptions,