tornado-core 1.0.5

* support saving relayers
This commit is contained in:
Tornado Contrib 2024-09-21 01:26:50 +00:00
parent 3df238e55f
commit e506c373de
Signed by: tornadocontrib
GPG Key ID: 60B4DF1A076C64B1
10 changed files with 2448 additions and 2063 deletions

48
dist/events/base.d.ts vendored

@ -1,8 +1,9 @@
import { BaseContract, Provider, EventLog, ContractEventName } from 'ethers'; 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 { BatchEventsService, BatchBlockService, BatchTransactionService, BatchEventOnProgress, BatchBlockOnProgress } from '../batch';
import { fetchDataOptions } from '../providers'; import type { fetchDataOptions } from '../providers';
import type { NetIdType } from '../networkConfig'; import type { NetIdType, SubdomainMap } from '../networkConfig';
import { RelayerParams } from '../relayerClient';
import type { BaseEvents, CachedEvents, MinimalEvents, DepositsEvents, WithdrawalsEvents, EncryptedNotesEvents, AllGovernanceEvents, RegistersEvents, EchoEvents } from './types'; import type { BaseEvents, CachedEvents, MinimalEvents, DepositsEvents, WithdrawalsEvents, EncryptedNotesEvents, AllGovernanceEvents, RegistersEvents, EchoEvents } from './types';
export declare const DEPOSIT = "deposit"; export declare const DEPOSIT = "deposit";
export declare const WITHDRAWAL = "withdrawal"; export declare const WITHDRAWAL = "withdrawal";
@ -169,17 +170,37 @@ export declare class BaseGovernanceService extends BaseEventsService<AllGovernan
fromBlock: number; fromBlock: number;
}): Promise<BaseEvents<AllGovernanceEvents>>; }): Promise<BaseEvents<AllGovernanceEvents>>;
} }
/**
* 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 = { export type BaseRegistryServiceConstructor = {
netId: NetIdType; netId: NetIdType;
provider: Provider; provider: Provider;
graphApi?: string; graphApi?: string;
subgraphName?: string; subgraphName?: string;
RelayerRegistry: RelayerRegistry; RelayerRegistry: RelayerRegistry;
Aggregator: Aggregator;
relayerEnsSubdomains: SubdomainMap;
deployedBlock?: number; deployedBlock?: number;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
}; };
export declare class BaseRegistryService extends BaseEventsService<RegistersEvents> { export declare class BaseRegistryService extends BaseEventsService<RegistersEvents> {
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; getInstanceName(): string;
getType(): string; getType(): string;
getGraphMethod(): string; getGraphMethod(): string;
@ -190,5 +211,22 @@ export declare class BaseRegistryService extends BaseEventsService<RegistersEven
logIndex: number; logIndex: number;
transactionHash: string; transactionHash: string;
}[]>; }[]>;
fetchRelayers(): Promise<RegistersEvents[]>; /**
* Get saved or cached relayers
*/
getRelayersFromDB(): Promise<CachedRelayers>;
/**
* Relayers from remote cache (Either from local cache, CDN, or from IPFS)
*/
getRelayersFromCache(): Promise<CachedRelayers>;
getSavedRelayers(): Promise<CachedRelayers>;
getLatestRelayers(): Promise<CachedRelayers>;
/**
* Handle saving relayers
*/
saveRelayers({ timestamp, relayers }: CachedRelayers): Promise<void>;
/**
* Get cached or latest relayer and save to local
*/
updateRelayers(): Promise<CachedRelayers>;
} }

1882
dist/index.js vendored

File diff suppressed because it is too large Load Diff

1883
dist/index.mjs vendored

File diff suppressed because it is too large Load Diff

299
dist/index.umd.js vendored

@ -59069,9 +59069,12 @@ class NoteAccount {
/* harmony export */ oW: () => (/* binding */ WITHDRAWAL), /* harmony export */ oW: () => (/* binding */ WITHDRAWAL),
/* harmony export */ uw: () => (/* binding */ BaseEventsService) /* harmony export */ uw: () => (/* binding */ BaseEventsService)
/* harmony export */ }); /* 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 _graphql__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(52049);
/* harmony import */ var _batch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9723); /* 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 __defProp = Object.defineProperty;
var __defProps = Object.defineProperties; var __defProps = Object.defineProperties;
@ -59118,6 +59121,7 @@ var __async = (__this, __arguments, generator) => {
const DEPOSIT = "deposit"; const DEPOSIT = "deposit";
const WITHDRAWAL = "withdrawal"; const WITHDRAWAL = "withdrawal";
class BaseEventsService { class BaseEventsService {
@ -59129,13 +59133,13 @@ class BaseEventsService {
contract, contract,
type = "", type = "",
deployedBlock = 0, deployedBlock = 0,
fetchDataOptions: fetchDataOptions2 fetchDataOptions
}) { }) {
this.netId = netId; this.netId = netId;
this.provider = provider; this.provider = provider;
this.graphApi = graphApi; this.graphApi = graphApi;
this.subgraphName = subgraphName; this.subgraphName = subgraphName;
this.fetchDataOptions = fetchDataOptions2; this.fetchDataOptions = fetchDataOptions;
this.contract = contract; this.contract = contract;
this.type = type; this.type = type;
this.deployedBlock = deployedBlock; this.deployedBlock = deployedBlock;
@ -59345,9 +59349,9 @@ class BaseTornadoService extends BaseEventsService {
amount, amount,
currency, currency,
deployedBlock, 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.amount = amount;
this.currency = currency; this.currency = currency;
this.batchTransactionService = new _batch__WEBPACK_IMPORTED_MODULE_1__/* .BatchTransactionService */ .AF({ this.batchTransactionService = new _batch__WEBPACK_IMPORTED_MODULE_1__/* .BatchTransactionService */ .AF({
@ -59407,7 +59411,7 @@ class BaseTornadoService extends BaseEventsService {
logIndex, logIndex,
transactionHash, transactionHash,
nullifierHash: String(nullifierHash), 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) fee: String(fee)
}; };
}); });
@ -59441,9 +59445,9 @@ class BaseEchoService extends BaseEventsService {
subgraphName, subgraphName,
Echoer, Echoer,
deployedBlock, deployedBlock,
fetchDataOptions: fetchDataOptions2 fetchDataOptions
}) { }) {
super({ netId, provider, graphApi, subgraphName, contract: Echoer, deployedBlock, fetchDataOptions: fetchDataOptions2 }); super({ netId, provider, graphApi, subgraphName, contract: Echoer, deployedBlock, fetchDataOptions });
} }
getInstanceName() { getInstanceName() {
return `echo_${this.netId}`; return `echo_${this.netId}`;
@ -59492,9 +59496,9 @@ class BaseEncryptedNotesService extends BaseEventsService {
subgraphName, subgraphName,
Router, Router,
deployedBlock, deployedBlock,
fetchDataOptions: fetchDataOptions2 fetchDataOptions
}) { }) {
super({ netId, provider, graphApi, subgraphName, contract: Router, deployedBlock, fetchDataOptions: fetchDataOptions2 }); super({ netId, provider, graphApi, subgraphName, contract: Router, deployedBlock, fetchDataOptions });
} }
getInstanceName() { getInstanceName() {
return `encrypted_notes_${this.netId}`; return `encrypted_notes_${this.netId}`;
@ -59531,9 +59535,9 @@ class BaseGovernanceService extends BaseEventsService {
subgraphName, subgraphName,
Governance, Governance,
deployedBlock, 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({ this.batchTransactionService = new _batch__WEBPACK_IMPORTED_MODULE_1__/* .BatchTransactionService */ .AF({
provider, provider,
onProgress: this.updateTransactionProgress onProgress: this.updateTransactionProgress
@ -59634,10 +59638,15 @@ class BaseRegistryService extends BaseEventsService {
graphApi, graphApi,
subgraphName, subgraphName,
RelayerRegistry, RelayerRegistry,
Aggregator,
relayerEnsSubdomains,
deployedBlock, 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() { getInstanceName() {
return `registered_${this.netId}`; 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 __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 */ Zh: () => (/* binding */ getInstanceByAddress),
/* harmony export */ cF: () => (/* binding */ getSubdomains), /* harmony export */ cF: () => (/* binding */ getSubdomains),
/* harmony export */ cX: () => (/* binding */ customConfig), /* harmony export */ cX: () => (/* binding */ customConfig),
/* harmony export */ o2: () => (/* binding */ getRelayerEnsSubdomains),
/* harmony export */ sb: () => (/* binding */ defaultConfig), /* harmony export */ sb: () => (/* binding */ defaultConfig),
/* harmony export */ zj: () => (/* binding */ getConfig), /* harmony export */ zj: () => (/* binding */ getConfig),
/* harmony export */ zr: () => (/* binding */ NetId) /* harmony export */ zr: () => (/* binding */ NetId)
@ -61679,10 +61782,6 @@ const defaultConfig = {
tornadoSubgraph: "tornadocash/sepolia-tornado-subgraph", tornadoSubgraph: "tornadocash/sepolia-tornado-subgraph",
subgraphs: {}, subgraphs: {},
rpcUrls: { rpcUrls: {
pandaops: {
name: "ethpandaops",
url: "https://rpc.sepolia.ethpandaops.io"
},
sepolia: { sepolia: {
name: "Sepolia RPC", name: "Sepolia RPC",
url: "https://rpc.sepolia.org" url: "https://rpc.sepolia.org"
@ -61694,6 +61793,10 @@ const defaultConfig = {
onerpc: { onerpc: {
name: "1rpc", name: "1rpc",
url: "https://1rpc.io/sepolia" url: "https://1rpc.io/sepolia"
},
ethpandaops: {
name: "ethpandaops",
url: "https://rpc.sepolia.ethpandaops.io"
} }
}, },
tokens: { tokens: {
@ -61772,6 +61875,13 @@ function getSubdomains() {
const allConfig = getNetworkConfig(); const allConfig = getNetworkConfig();
return enabledChains.map((chain) => allConfig[chain].relayerEnsSubdomain); 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"; "use strict";
/* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ KN: () => (/* binding */ MAX_FEE),
/* harmony export */ OR: () => (/* binding */ RelayerClient), /* harmony export */ OR: () => (/* binding */ RelayerClient),
/* harmony export */ Ss: () => (/* binding */ MIN_FEE),
/* harmony export */ XF: () => (/* binding */ getSupportedInstances), /* harmony export */ XF: () => (/* binding */ getSupportedInstances),
/* harmony export */ c$: () => (/* binding */ getWeightRandom), /* harmony export */ c$: () => (/* binding */ getWeightRandom),
/* harmony export */ mU: () => (/* binding */ isRelayerUpdated), /* harmony export */ mU: () => (/* binding */ isRelayerUpdated),
@ -71317,7 +71429,6 @@ class TornadoBrowserProvider extends BrowserProvider {
/* harmony export */ }); /* harmony export */ });
/* harmony import */ var ethers__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(99770); /* 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_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 _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(67418);
/* harmony import */ var _networkConfig__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(59499); /* harmony import */ var _networkConfig__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(59499);
/* harmony import */ var _providers__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(68434); /* 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 MIN_STAKE_BALANCE = (0,ethers__WEBPACK_IMPORTED_MODULE_4__/* .parseEther */ .g5)("500");
const semVerRegex = new RegExp("^(?<major>0|[1-9]\\d*)\\.(?<minor>0|[1-9]\\d*)\\.(?<patch>0|[1-9]\\d*)(?:-(?<prerelease>(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); const semVerRegex = new RegExp("^(?<major>0|[1-9]\\d*)\\.(?<minor>0|[1-9]\\d*)\\.(?<patch>0|[1-9]\\d*)(?:-(?<prerelease>(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$");
function parseSemanticVersion(version) { function parseSemanticVersion(version) {
@ -71380,16 +71493,16 @@ function isRelayerUpdated(relayerVersion, netId) {
if (prerelease) return false; if (prerelease) return false;
return isUpdatedMajor && (Number(patch) >= 5 || netId !== _networkConfig__WEBPACK_IMPORTED_MODULE_1__/* .NetId */ .zr.MAINNET); return isUpdatedMajor && (Number(patch) >= 5 || netId !== _networkConfig__WEBPACK_IMPORTED_MODULE_1__/* .NetId */ .zr.MAINNET);
} }
function calculateScore({ stakeBalance, tornadoServiceFee }, minFee = 0.33, maxFee = 0.53) { function calculateScore({ stakeBalance, tornadoServiceFee }) {
if (tornadoServiceFee < minFee) { if (tornadoServiceFee < MIN_FEE) {
tornadoServiceFee = minFee; tornadoServiceFee = MIN_FEE;
} else if (tornadoServiceFee >= maxFee) { } else if (tornadoServiceFee >= MAX_FEE) {
return BigInt(0); return BigInt(0);
} }
const serviceFeeCoefficient = (tornadoServiceFee - minFee) ** 2; const serviceFeeCoefficient = (tornadoServiceFee - MIN_FEE) ** 2;
const feeDiffCoefficient = 1 / (maxFee - minFee) ** 2; const feeDiffCoefficient = 1 / (MAX_FEE - MIN_FEE) ** 2;
const coefficientsMultiplier = 1 - feeDiffCoefficient * serviceFeeCoefficient; const coefficientsMultiplier = 1 - feeDiffCoefficient * serviceFeeCoefficient;
return BigInt(Math.floor(Number(stakeBalance) * coefficientsMultiplier)); return BigInt(Math.floor(Number(stakeBalance || "0") * coefficientsMultiplier));
} }
function getWeightRandom(weightsScores, random) { function getWeightRandom(weightsScores, random) {
for (let i = 0; i < weightsScores.length; i++) { for (let i = 0; i < weightsScores.length; i++) {
@ -71406,25 +71519,19 @@ function getSupportedInstances(instanceList) {
}).flat(); }).flat();
return rawList.map((l) => (0,ethers__WEBPACK_IMPORTED_MODULE_5__/* .getAddress */ .b)(l)); return rawList.map((l) => (0,ethers__WEBPACK_IMPORTED_MODULE_5__/* .getAddress */ .b)(l));
} }
function pickWeightedRandomRelayer(relayers, netId) { function pickWeightedRandomRelayer(relayers) {
let minFee, maxFee; const weightsScores = relayers.map((el) => calculateScore(el));
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));
const totalWeight = weightsScores.reduce((acc, curr) => { const totalWeight = weightsScores.reduce((acc, curr) => {
return acc = acc + curr; return acc = acc + curr;
}, BigInt("0")); }, BigInt("0"));
const random = BigInt(Number(totalWeight) * Math.random()); const random = BigInt(Math.floor(Number(totalWeight) * Math.random()));
const weightRandomIndex = getWeightRandom(weightsScores, random); const weightRandomIndex = getWeightRandom(weightsScores, random);
return relayers[weightRandomIndex]; return relayers[weightRandomIndex];
} }
class RelayerClient { class RelayerClient {
constructor({ netId, config, Aggregator, fetchDataOptions: fetchDataOptions2 }) { constructor({ netId, config, fetchDataOptions: fetchDataOptions2 }) {
this.netId = netId; this.netId = netId;
this.config = config; this.config = config;
this.Aggregator = Aggregator;
this.fetchDataOptions = fetchDataOptions2; this.fetchDataOptions = fetchDataOptions2;
} }
askRelayerStatus(_0) { askRelayerStatus(_0) {
@ -71463,76 +71570,48 @@ class RelayerClient {
return status; return status;
}); });
} }
filterRelayer(curr, relayer, subdomains, debugRelayer = false) { filterRelayer(relayer) {
return __async(this, null, function* () { return __async(this, null, function* () {
var _a; var _a;
const { relayerEnsSubdomain } = this.config; const hostname = relayer.hostnames[this.netId];
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 { ensName, relayerAddress } = relayer;
const isOwner = !relayerAddress || relayerAddress === owner; if (!hostname) {
const hasMinBalance = stakeBalance >= MIN_STAKE_BALANCE; return;
const preCondition = hostname && isOwner && mainnetSubdomain && isRegistered && hasMinBalance && !isHostWithProtocol; }
if (preCondition || debugRelayer) { try {
try { const status = yield this.askRelayerStatus({ hostname, relayerAddress });
const status = yield this.askRelayerStatus({ hostname, relayerAddress }); return {
return { netId: status.netId,
netId: status.netId, url: status.url,
url: status.url, hostname,
hostname, ensName,
ensName, relayerAddress,
stakeBalance, rewardAccount: (0,ethers__WEBPACK_IMPORTED_MODULE_5__/* .getAddress */ .b)(status.rewardAccount),
relayerAddress, instances: getSupportedInstances(status.instances),
rewardAccount: (0,ethers__WEBPACK_IMPORTED_MODULE_5__/* .getAddress */ .b)(status.rewardAccount), stakeBalance: relayer.stakeBalance,
instances: getSupportedInstances(status.instances), gasPrice: (_a = status.gasPrices) == null ? void 0 : _a.fast,
gasPrice: (_a = status.gasPrices) == null ? void 0 : _a.fast, ethPrices: status.ethPrices,
ethPrices: status.ethPrices, currentQueue: status.currentQueue,
currentQueue: status.currentQueue, tornadoServiceFee: status.tornadoServiceFee
tornadoServiceFee: status.tornadoServiceFee };
}; } catch (err) {
} 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 { return {
hostname, hostname,
relayerAddress, relayerAddress,
errorMessage: `Relayer ${hostname} condition not met` errorMessage: err.message,
hasError: true
}; };
} }
}); });
} }
getValidRelayers(relayers, subdomains, debugRelayer = false) { getValidRelayers(relayers) {
return __async(this, null, function* () { 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 invalidRelayers = [];
const validRelayers = (yield Promise.all( const validRelayers = (yield Promise.all(relayers.map((relayer) => this.filterRelayer(relayer)))).filter((r) => {
relayersData.map((curr, index) => this.filterRelayer(curr, uniqueRelayers[index], subdomains, debugRelayer)) if (!r) {
)).filter((r) => { return false;
if (r.errorMessage) { }
if (r.hasError) {
invalidRelayers.push(r); invalidRelayers.push(r);
return false; return false;
} }
@ -71545,7 +71624,7 @@ class RelayerClient {
}); });
} }
pickWeightedRandomRelayer(relayers) { pickWeightedRandomRelayer(relayers) {
return pickWeightedRandomRelayer(relayers, this.netId); return pickWeightedRandomRelayer(relayers);
} }
tornadoWithdraw(_0) { tornadoWithdraw(_0) {
return __async(this, arguments, function* ({ contract, proof, args }) { return __async(this, arguments, function* ({ contract, proof, args }) {
@ -171078,11 +171157,12 @@ function encodeRlp(object) {
// EXPORTS // EXPORTS
__webpack_require__.d(__webpack_exports__, { __webpack_require__.d(__webpack_exports__, {
ck: () => (/* binding */ formatEther),
g5: () => (/* binding */ parseEther), g5: () => (/* binding */ parseEther),
XS: () => (/* binding */ parseUnits) XS: () => (/* binding */ parseUnits)
}); });
// UNUSED EXPORTS: formatEther, formatUnits // UNUSED EXPORTS: formatUnits
// EXTERNAL MODULE: ./node_modules/ethers/lib.esm/utils/errors.js // EXTERNAL MODULE: ./node_modules/ethers/lib.esm/utils/errors.js
var errors = __webpack_require__(57339); var errors = __webpack_require__(57339);
@ -171253,7 +171333,7 @@ function fixednumber_toString(val, decimals) {
* variant will silently ignore underflow, while the //signalling// variant * variant will silently ignore underflow, while the //signalling// variant
* will thow a [[NumericFaultError]] on underflow. * will thow a [[NumericFaultError]] on underflow.
*/ */
class fixednumber_FixedNumber { class FixedNumber {
/** /**
* The specific fixed-point arithmetic field for this value. * The specific fixed-point arithmetic field for this value.
*/ */
@ -171328,7 +171408,7 @@ class fixednumber_FixedNumber {
} }
*/ */
val = checkValue(val, this.#format, safeOp); val = checkValue(val, this.#format, safeOp);
return new fixednumber_FixedNumber(_guard, val, this.#format); return new FixedNumber(_guard, val, this.#format);
} }
#add(o, safeOp) { #add(o, safeOp) {
this.#checkFormat(o); this.#checkFormat(o);
@ -171515,7 +171595,7 @@ class fixednumber_FixedNumber {
const tens = getTens(delta); const tens = getTens(delta);
value = (value / tens) * tens; value = (value / tens) * tens;
checkValue(value, this.#format, "round"); 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``. * 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%%. * This will throw if the value cannot fit into %%format%%.
*/ */
toFormat(format) { toFormat(format) {
return fixednumber_FixedNumber.fromString(this.toString(), format); return FixedNumber.fromString(this.toString(), format);
} }
/** /**
* Creates a new [[FixedNumber]] for %%value%% divided by * Creates a new [[FixedNumber]] for %%value%% divided by
@ -171570,7 +171650,7 @@ class fixednumber_FixedNumber {
value *= getTens(-delta); value *= getTens(-delta);
} }
checkValue(value, format, "fromValue"); 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%%. * Creates a new [[FixedNumber]] for %%value%% with %%format%%.
@ -171595,7 +171675,7 @@ class fixednumber_FixedNumber {
decimal = decimal.substring(0, format.decimals); decimal = decimal.substring(0, format.decimals);
const value = BigInt(match[1] + whole + decimal); const value = BigInt(match[1] + whole + decimal);
checkValue(value, format, "fromString"); 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 * Creates a new [[FixedNumber]] with the big-endian representation
@ -171611,7 +171691,7 @@ class fixednumber_FixedNumber {
value = (0,maths/* fromTwos */.ST)(value, format.width); value = (0,maths/* fromTwos */.ST)(value, format.width);
} }
checkValue(value, format, "fromBytes"); checkValue(value, format, "fromBytes");
return new fixednumber_FixedNumber(_guard, value, format); return new FixedNumber(_guard, value, format);
} }
} }
//const f1 = FixedNumber.fromString("12.56", "fixed16x2"); //const f1 = FixedNumber.fromString("12.56", "fixed16x2");
@ -171663,11 +171743,11 @@ function formatUnits(value, unit) {
let decimals = 18; let decimals = 18;
if (typeof (unit) === "string") { if (typeof (unit) === "string") {
const index = names.indexOf(unit); 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; decimals = 3 * index;
} }
else if (unit != null) { else if (unit != null) {
decimals = getNumber(unit, "unit"); decimals = (0,maths/* getNumber */.WZ)(unit, "unit");
} }
return FixedNumber.fromValue(value, decimals, { decimals, width: 512 }).toString(); return FixedNumber.fromValue(value, decimals, { decimals, width: 512 }).toString();
} }
@ -171687,7 +171767,7 @@ function parseUnits(value, unit) {
else if (unit != null) { else if (unit != null) {
decimals = (0,maths/* getNumber */.WZ)(unit, "unit"); 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. * 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_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 */ GET_WITHDRAWALS: () => (/* reexport safe */ _graphql__WEBPACK_IMPORTED_MODULE_1__.GET_WITHDRAWALS),
/* harmony export */ Invoice: () => (/* reexport safe */ _deposits__WEBPACK_IMPORTED_MODULE_5__.qO), /* 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 */ MIN_STAKE_BALANCE: () => (/* reexport safe */ _relayerClient__WEBPACK_IMPORTED_MODULE_15__.pO),
/* harmony export */ MerkleTreeService: () => (/* reexport safe */ _merkleTree__WEBPACK_IMPORTED_MODULE_8__.s), /* harmony export */ MerkleTreeService: () => (/* reexport safe */ _merkleTree__WEBPACK_IMPORTED_MODULE_8__.s),
/* harmony export */ Mimc: () => (/* reexport safe */ _mimc__WEBPACK_IMPORTED_MODULE_9__.p), /* 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 */ getProvider: () => (/* reexport safe */ _providers__WEBPACK_IMPORTED_MODULE_14__.sO),
/* harmony export */ getProviderWithNetId: () => (/* reexport safe */ _providers__WEBPACK_IMPORTED_MODULE_14__.MF), /* harmony export */ getProviderWithNetId: () => (/* reexport safe */ _providers__WEBPACK_IMPORTED_MODULE_14__.MF),
/* harmony export */ getRegisters: () => (/* reexport safe */ _graphql__WEBPACK_IMPORTED_MODULE_1__.getRegisters), /* 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 */ getStatistic: () => (/* reexport safe */ _graphql__WEBPACK_IMPORTED_MODULE_1__.getStatistic),
/* harmony export */ getStatusSchema: () => (/* reexport safe */ _schemas__WEBPACK_IMPORTED_MODULE_2__.c_), /* harmony export */ getStatusSchema: () => (/* reexport safe */ _schemas__WEBPACK_IMPORTED_MODULE_2__.c_),
/* harmony export */ getSubdomains: () => (/* reexport safe */ _networkConfig__WEBPACK_IMPORTED_MODULE_11__.cF), /* harmony export */ getSubdomains: () => (/* reexport safe */ _networkConfig__WEBPACK_IMPORTED_MODULE_11__.cF),

@ -87,6 +87,9 @@ export type Config = {
export type networkConfig = { export type networkConfig = {
[key in NetIdType]: Config; [key in NetIdType]: Config;
}; };
export type SubdomainMap = {
[key in NetIdType]: string;
};
export declare const defaultConfig: networkConfig; export declare const defaultConfig: networkConfig;
export declare const enabledChains: NetIdType[]; export declare const enabledChains: NetIdType[];
/** /**
@ -112,3 +115,4 @@ export declare function getInstanceByAddress({ netId, address }: {
currency: string; currency: string;
} | undefined; } | undefined;
export declare function getSubdomains(): string[]; export declare function getSubdomains(): string[];
export declare function getRelayerEnsSubdomains(): SubdomainMap;

@ -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 { NetIdType, Config } from './networkConfig';
import { fetchDataOptions } from './providers'; import { fetchDataOptions } from './providers';
import type { snarkProofs } from './websnark'; 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 declare const MIN_STAKE_BALANCE: bigint;
export interface RelayerParams { export interface RelayerParams {
ensName: string; ensName: string;
relayerAddress?: string; relayerAddress: string;
} }
export interface Relayer { /**
* Info from relayer status
*/
export type RelayerInfo = RelayerParams & {
netId: NetIdType; netId: NetIdType;
url: string; url: string;
hostname: string; hostname: string;
rewardAccount: string; rewardAccount: string;
instances: string[]; instances: string[];
stakeBalance?: string;
gasPrice?: number; gasPrice?: number;
ethPrices?: { ethPrices?: {
[key in string]: string; [key in string]: string;
}; };
currentQueue: number; currentQueue: number;
tornadoServiceFee: number; tornadoServiceFee: number;
}
export type RelayerInfo = Relayer & {
ensName: string;
stakeBalance: bigint;
relayerAddress: string;
}; };
export type RelayerError = { export type RelayerError = {
hostname: string; hostname: string;
relayerAddress?: string; relayerAddress?: string;
errorMessage?: string; errorMessage?: string;
hasError: boolean;
}; };
export interface RelayerStatus { export interface RelayerStatus {
url: string; url: string;
@ -87,7 +88,7 @@ export interface semanticVersion {
} }
export declare function parseSemanticVersion(version: string): semanticVersion; export declare function parseSemanticVersion(version: string): semanticVersion;
export declare function isRelayerUpdated(relayerVersion: string, netId: NetIdType): boolean; 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 declare function getWeightRandom(weightsScores: bigint[], random: bigint): number;
export type RelayerInstanceList = { export type RelayerInstanceList = {
[key in string]: { [key in string]: {
@ -97,11 +98,10 @@ export type RelayerInstanceList = {
}; };
}; };
export declare function getSupportedInstances(instanceList: RelayerInstanceList): string[]; 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 { export interface RelayerClientConstructor {
netId: NetIdType; netId: NetIdType;
config: Config; config: Config;
Aggregator: Aggregator;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
} }
export type RelayerClientWithdraw = snarkProofs & { export type RelayerClientWithdraw = snarkProofs & {
@ -110,16 +110,15 @@ export type RelayerClientWithdraw = snarkProofs & {
export declare class RelayerClient { export declare class RelayerClient {
netId: NetIdType; netId: NetIdType;
config: Config; config: Config;
Aggregator: Aggregator; selectedRelayer?: RelayerInfo;
selectedRelayer?: Relayer;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
constructor({ netId, config, Aggregator, fetchDataOptions }: RelayerClientConstructor); constructor({ netId, config, fetchDataOptions }: RelayerClientConstructor);
askRelayerStatus({ hostname, relayerAddress, }: { askRelayerStatus({ hostname, relayerAddress, }: {
hostname: string; hostname: string;
relayerAddress?: string; relayerAddress?: string;
}): Promise<RelayerStatus>; }): Promise<RelayerStatus>;
filterRelayer(curr: RelayerStructOutput, relayer: RelayerParams, subdomains: string[], debugRelayer?: boolean): Promise<RelayerInfo | RelayerError>; filterRelayer(relayer: CachedRelayerInfo): Promise<RelayerInfo | RelayerError | undefined>;
getValidRelayers(relayers: RelayerParams[], subdomains: string[], debugRelayer?: boolean): Promise<{ getValidRelayers(relayers: CachedRelayerInfo[]): Promise<{
validRelayers: RelayerInfo[]; validRelayers: RelayerInfo[];
invalidRelayers: RelayerError[]; invalidRelayers: RelayerError[];
}>; }>;

@ -1,6 +1,6 @@
{ {
"name": "@tornado/core", "name": "@tornado/core",
"version": "1.0.4", "version": "1.0.5",
"description": "An SDK for building applications on top of Privacy Pools", "description": "An SDK for building applications on top of Privacy Pools",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.mjs", "module": "./dist/index.mjs",

@ -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 { import type {
Tornado, Tornado,
TornadoRouter, TornadoRouter,
@ -6,8 +17,11 @@ import type {
Governance, Governance,
RelayerRegistry, RelayerRegistry,
Echoer, Echoer,
Aggregator,
} from '@tornado/contracts'; } from '@tornado/contracts';
import * as graph from '../graphql'; import * as graph from '../graphql';
import { import {
BatchEventsService, BatchEventsService,
BatchBlockService, BatchBlockService,
@ -15,8 +29,11 @@ import {
BatchEventOnProgress, BatchEventOnProgress,
BatchBlockOnProgress, BatchBlockOnProgress,
} from '../batch'; } 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 { import type {
BaseEvents, BaseEvents,
CachedEvents, CachedEvents,
@ -741,27 +758,57 @@ export class BaseGovernanceService extends BaseEventsService<AllGovernanceEvents
} }
} }
/**
* 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 = { export type BaseRegistryServiceConstructor = {
netId: NetIdType; netId: NetIdType;
provider: Provider; provider: Provider;
graphApi?: string; graphApi?: string;
subgraphName?: string; subgraphName?: string;
RelayerRegistry: RelayerRegistry; RelayerRegistry: RelayerRegistry;
Aggregator: Aggregator;
relayerEnsSubdomains: SubdomainMap;
deployedBlock?: number; deployedBlock?: number;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
}; };
export class BaseRegistryService extends BaseEventsService<RegistersEvents> { export class BaseRegistryService extends BaseEventsService<RegistersEvents> {
Aggregator: Aggregator;
relayerEnsSubdomains: SubdomainMap;
updateInterval: number;
constructor({ constructor({
netId, netId,
provider, provider,
graphApi, graphApi,
subgraphName, subgraphName,
RelayerRegistry, RelayerRegistry,
Aggregator,
relayerEnsSubdomains,
deployedBlock, deployedBlock,
fetchDataOptions, fetchDataOptions,
}: BaseRegistryServiceConstructor) { }: BaseRegistryServiceConstructor) {
super({ netId, provider, graphApi, subgraphName, contract: RelayerRegistry, deployedBlock, fetchDataOptions }); super({ netId, provider, graphApi, subgraphName, contract: RelayerRegistry, deployedBlock, fetchDataOptions });
this.Aggregator = Aggregator;
this.relayerEnsSubdomains = relayerEnsSubdomains;
this.updateInterval = 86400;
} }
getInstanceName() { getInstanceName() {
@ -794,7 +841,114 @@ export class BaseRegistryService extends BaseEventsService<RegistersEvents> {
}); });
} }
async fetchRelayers(): Promise<RegistersEvents[]> { /**
return (await this.updateEvents()).events; * Get saved or cached relayers
*/
async getRelayersFromDB(): Promise<CachedRelayers> {
return {
timestamp: 0,
relayers: [],
};
}
/**
* Relayers from remote cache (Either from local cache, CDN, or from IPFS)
*/
async getRelayersFromCache(): Promise<CachedRelayers> {
return {
timestamp: 0,
relayers: [],
};
}
async getSavedRelayers(): Promise<CachedRelayers> {
let cachedRelayers = await this.getRelayersFromDB();
if (!cachedRelayers || !cachedRelayers.relayers.length) {
cachedRelayers = await this.getRelayersFromCache();
}
return cachedRelayers;
}
async getLatestRelayers(): Promise<CachedRelayers> {
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<CachedRelayers> {
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 };
} }
} }

@ -102,6 +102,10 @@ export type networkConfig = {
[key in NetIdType]: Config; [key in NetIdType]: Config;
}; };
export type SubdomainMap = {
[key in NetIdType]: string;
};
export const defaultConfig: networkConfig = { export const defaultConfig: networkConfig = {
[NetId.MAINNET]: { [NetId.MAINNET]: {
rpcCallRetryAttempt: 15, rpcCallRetryAttempt: 15,
@ -611,10 +615,6 @@ export const defaultConfig: networkConfig = {
tornadoSubgraph: 'tornadocash/sepolia-tornado-subgraph', tornadoSubgraph: 'tornadocash/sepolia-tornado-subgraph',
subgraphs: {}, subgraphs: {},
rpcUrls: { rpcUrls: {
pandaops: {
name: 'ethpandaops',
url: 'https://rpc.sepolia.ethpandaops.io',
},
sepolia: { sepolia: {
name: 'Sepolia RPC', name: 'Sepolia RPC',
url: 'https://rpc.sepolia.org', url: 'https://rpc.sepolia.org',
@ -627,6 +627,10 @@ export const defaultConfig: networkConfig = {
name: '1rpc', name: '1rpc',
url: 'https://1rpc.io/sepolia', url: 'https://1rpc.io/sepolia',
}, },
ethpandaops: {
name: 'ethpandaops',
url: 'https://rpc.sepolia.ethpandaops.io',
},
}, },
tokens: { tokens: {
eth: { eth: {
@ -738,3 +742,12 @@ export function getSubdomains() {
return enabledChains.map((chain) => allConfig[chain].relayerEnsSubdomain); 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);
}

@ -1,43 +1,45 @@
import { getAddress, namehash, parseEther } from 'ethers'; import { getAddress, parseEther } from 'ethers';
import type { Aggregator } from '@tornado/contracts';
import type { RelayerStructOutput } from '@tornado/contracts/dist/contracts/Governance/Aggregator/Aggregator';
import { sleep } from './utils'; import { sleep } from './utils';
import { NetId, NetIdType, Config } from './networkConfig'; import { NetId, NetIdType, Config } from './networkConfig';
import { fetchData, fetchDataOptions } from './providers'; import { fetchData, fetchDataOptions } from './providers';
import { ajv, jobsSchema, getStatusSchema } from './schemas'; import { ajv, jobsSchema, getStatusSchema } from './schemas';
import type { snarkProofs } from './websnark'; 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 const MIN_STAKE_BALANCE = parseEther('500');
export interface RelayerParams { export interface RelayerParams {
ensName: string; ensName: string;
relayerAddress?: string; relayerAddress: string;
} }
export interface Relayer { /**
* Info from relayer status
*/
export type RelayerInfo = RelayerParams & {
netId: NetIdType; netId: NetIdType;
url: string; url: string;
hostname: string; hostname: string;
rewardAccount: string; rewardAccount: string;
instances: string[]; instances: string[];
stakeBalance?: string;
gasPrice?: number; gasPrice?: number;
ethPrices?: { ethPrices?: {
[key in string]: string; [key in string]: string;
}; };
currentQueue: number; currentQueue: number;
tornadoServiceFee: number; tornadoServiceFee: number;
}
export type RelayerInfo = Relayer & {
ensName: string;
stakeBalance: bigint;
relayerAddress: string;
}; };
export type RelayerError = { export type RelayerError = {
hostname: string; hostname: string;
relayerAddress?: string; relayerAddress?: string;
errorMessage?: string; errorMessage?: string;
hasError: boolean;
}; };
export interface RelayerStatus { 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 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) { export function calculateScore({ stakeBalance, tornadoServiceFee }: RelayerInfo) {
if (tornadoServiceFee < minFee) { if (tornadoServiceFee < MIN_FEE) {
tornadoServiceFee = minFee; tornadoServiceFee = MIN_FEE;
} else if (tornadoServiceFee >= maxFee) { } else if (tornadoServiceFee >= MAX_FEE) {
return BigInt(0); return BigInt(0);
} }
const serviceFeeCoefficient = (tornadoServiceFee - minFee) ** 2; const serviceFeeCoefficient = (tornadoServiceFee - MIN_FEE) ** 2;
const feeDiffCoefficient = 1 / (maxFee - minFee) ** 2; const feeDiffCoefficient = 1 / (MAX_FEE - MIN_FEE) ** 2;
const coefficientsMultiplier = 1 - feeDiffCoefficient * serviceFeeCoefficient; 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) { export function getWeightRandom(weightsScores: bigint[], random: bigint) {
@ -159,20 +161,13 @@ export function getSupportedInstances(instanceList: RelayerInstanceList) {
return rawList.map((l) => getAddress(l)); return rawList.map((l) => getAddress(l));
} }
export function pickWeightedRandomRelayer(relayers: RelayerInfo[], netId: NetIdType) { export function pickWeightedRandomRelayer(relayers: RelayerInfo[]) {
let minFee: number, maxFee: number; const weightsScores = relayers.map((el) => calculateScore(el));
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) => { const totalWeight = weightsScores.reduce((acc, curr) => {
return (acc = acc + curr); return (acc = acc + curr);
}, BigInt('0')); }, BigInt('0'));
const random = BigInt(Number(totalWeight) * Math.random()); const random = BigInt(Math.floor(Number(totalWeight) * Math.random()));
const weightRandomIndex = getWeightRandom(weightsScores, random); const weightRandomIndex = getWeightRandom(weightsScores, random);
return relayers[weightRandomIndex]; return relayers[weightRandomIndex];
@ -181,7 +176,6 @@ export function pickWeightedRandomRelayer(relayers: RelayerInfo[], netId: NetIdT
export interface RelayerClientConstructor { export interface RelayerClientConstructor {
netId: NetIdType; netId: NetIdType;
config: Config; config: Config;
Aggregator: Aggregator;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
} }
@ -192,14 +186,12 @@ export type RelayerClientWithdraw = snarkProofs & {
export class RelayerClient { export class RelayerClient {
netId: NetIdType; netId: NetIdType;
config: Config; config: Config;
Aggregator: Aggregator; selectedRelayer?: RelayerInfo;
selectedRelayer?: Relayer;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
constructor({ netId, config, Aggregator, fetchDataOptions }: RelayerClientConstructor) { constructor({ netId, config, fetchDataOptions }: RelayerClientConstructor) {
this.netId = netId; this.netId = netId;
this.config = config; this.config = config;
this.Aggregator = Aggregator;
this.fetchDataOptions = fetchDataOptions; this.fetchDataOptions = fetchDataOptions;
} }
@ -251,101 +243,54 @@ export class RelayerClient {
return status; return status;
} }
async filterRelayer( async filterRelayer(relayer: CachedRelayerInfo): Promise<RelayerInfo | RelayerError | undefined> {
curr: RelayerStructOutput, const hostname = relayer.hostnames[this.netId];
relayer: RelayerParams,
subdomains: string[],
debugRelayer: boolean = false,
): Promise<RelayerInfo | RelayerError> {
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 { ensName, relayerAddress } = relayer;
const isOwner = !relayerAddress || relayerAddress === owner; if (!hostname) {
const hasMinBalance = stakeBalance >= MIN_STAKE_BALANCE; return;
}
const preCondition = try {
hostname && isOwner && mainnetSubdomain && isRegistered && hasMinBalance && !isHostWithProtocol; const status = await this.askRelayerStatus({ hostname, relayerAddress });
if (preCondition || debugRelayer) { return {
try { netId: status.netId,
const status = await this.askRelayerStatus({ hostname, relayerAddress }); url: status.url,
hostname,
return { ensName,
netId: status.netId, relayerAddress,
url: status.url, rewardAccount: getAddress(status.rewardAccount),
hostname, instances: getSupportedInstances(status.instances),
ensName, stakeBalance: relayer.stakeBalance,
stakeBalance, gasPrice: status.gasPrices?.fast,
relayerAddress, ethPrices: status.ethPrices,
rewardAccount: getAddress(status.rewardAccount), currentQueue: status.currentQueue,
instances: getSupportedInstances(status.instances), tornadoServiceFee: status.tornadoServiceFee,
gasPrice: status.gasPrices?.fast, } as RelayerInfo;
ethPrices: status.ethPrices, // eslint-disable-next-line @typescript-eslint/no-explicit-any
currentQueue: status.currentQueue, } catch (err: any) {
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 { return {
hostname, hostname,
relayerAddress, relayerAddress,
errorMessage: `Relayer ${hostname} condition not met`, errorMessage: err.message,
}; hasError: true,
} as RelayerError;
} }
} }
async getValidRelayers( async getValidRelayers(relayers: CachedRelayerInfo[]): Promise<{
// this should be ascending order of events
relayers: RelayerParams[],
subdomains: string[],
debugRelayer: boolean = false,
): Promise<{
validRelayers: RelayerInfo[]; validRelayers: RelayerInfo[];
invalidRelayers: RelayerError[]; 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 invalidRelayers: RelayerError[] = [];
const validRelayers = ( const validRelayers = (await Promise.all(relayers.map((relayer) => this.filterRelayer(relayer)))).filter((r) => {
await Promise.all( if (!r) {
relayersData.map((curr, index) => this.filterRelayer(curr, uniqueRelayers[index], subdomains, debugRelayer)), return false;
) }
).filter((r) => { if ((r as RelayerError).hasError) {
if ((r as RelayerError).errorMessage) { invalidRelayers.push(r as RelayerError);
invalidRelayers.push(r);
return false; return false;
} }
return true; return true;
@ -358,11 +303,11 @@ export class RelayerClient {
} }
pickWeightedRandomRelayer(relayers: RelayerInfo[]) { pickWeightedRandomRelayer(relayers: RelayerInfo[]) {
return pickWeightedRandomRelayer(relayers, this.netId); return pickWeightedRandomRelayer(relayers);
} }
async tornadoWithdraw({ contract, proof, args }: RelayerClientWithdraw) { 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`, { const withdrawResponse = (await fetchData(`${url}v1/tornadoWithdraw`, {
...this.fetchDataOptions, ...this.fetchDataOptions,