Compare commits

...

25 Commits

Author SHA1 Message Date
a0f11d6ff4 Initial client for Tovarish Relayer 2024-09-29 17:32:32 +00:00
be0e2419aa Add block num params for relayers event 2024-09-29 05:56:53 +00:00
bdcf48db73 Add getTovarishType for events 2024-09-29 03:49:00 +00:00
df1fcebfe3 tornado-core 1.0.16
* simplify populateTransaction func
* fixed relayerClient type defs
2024-09-28 02:42:27 +00:00
82aeabd739 Allow custom URL format relayers
should be convenient  to test locally
2024-09-27 04:56:19 +00:00
c155649719 Fixed snarkArgs type defs 2024-09-26 12:29:29 +00:00
69faa7a974 tornado-core 1.0.15
use more universal type defs for snark proofs
2024-09-26 12:23:55 +00:00
f7fdf7db0a tornado-core 1.0.14
* disable relayer version checks

* disable inactive cdai, usdc, usdt pools
2024-09-25 07:37:57 +00:00
d0b032d7be tornado-core 1.0.13
audit provider networks
2024-09-24 05:27:35 +00:00
20368b243b tornado-core 1.0.12
added minified umd files
2024-09-23 23:37:15 +00:00
29744cfce4 tornado-core 1.0.11
fixed encryptedNote filtering bug
2024-09-23 04:26:07 +00:00
48bb7aed82 Use @tornado/contracts 1.0.1 2024-09-22 15:07:01 +00:00
d6cfea1d19 tornado-core 1.0.10
revert reverse ordering for relayers
2024-09-22 05:56:21 +00:00
0bd87f9b67 tornado-core 1.0.9
optimize getting events
2024-09-22 03:16:12 +00:00
2bd991aa45 tornado-core 1.0.8
* minor fix
2024-09-22 02:37:33 +00:00
700426acb7 tornado-core 1.0.7
revert back to .ws
2024-09-21 17:25:44 +00:00
ef56beb29b tornado-core 1.0.6
* fixed save condition
2024-09-21 03:56:48 +00:00
e506c373de tornado-core 1.0.5
* support saving relayers
2024-09-21 02:55:32 +00:00
3df238e55f tornado-core 1.0.4
* remove obsolete rpc urls
2024-09-19 18:34:36 +00:00
95dbf208c3 tornado-core 1.0.3
* Removed polygon gas oracle and use default oracle provided by ethers.js

* Simplify events saving process

* Use codeberg.org to fetch dependencies

* Update dependencies
2024-09-19 17:49:25 +00:00
84b6ed368e Tornado Core 1.0.2 2024-06-29 17:02:29 -07:00
391ca0df37 Removed obsolete rpc endpoints 2024-06-29 16:54:30 -07:00
cf9e32f46d Disable fee bump for BSC by default 2024-05-15 08:55:32 +00:00
29bd7dc66e Improve op l1 fee calculation 2024-05-12 23:18:54 +00:00
b32cf2dbbc Tornado Core 1.0.1 2024-05-12 13:24:02 +00:00
41 changed files with 39370 additions and 62515 deletions

89
dist/events/base.d.ts vendored
View File

@@ -1,9 +1,11 @@
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 { fetchDataOptions } from '../providers';
import type { NetIdType } from '../networkConfig'; import type { NetIdType, SubdomainMap } from '../networkConfig';
import type { BaseEvents, MinimalEvents, DepositsEvents, WithdrawalsEvents, EncryptedNotesEvents, AllGovernanceEvents, RegistersEvents, EchoEvents } from './types'; import { RelayerParams } from '../relayerClient';
import type { TovarishClient } from '../tovarishClient';
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";
export type BaseEventsServiceConstructor = { export type BaseEventsServiceConstructor = {
@@ -15,6 +17,7 @@ export type BaseEventsServiceConstructor = {
type?: string; type?: string;
deployedBlock?: number; deployedBlock?: number;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
tovarishClient?: TovarishClient;
}; };
export type BatchGraphOnProgress = ({ type, fromBlock, toBlock, count, }: { export type BatchGraphOnProgress = ({ type, fromBlock, toBlock, count, }: {
type?: ContractEventName; type?: ContractEventName;
@@ -38,10 +41,11 @@ export declare class BaseEventsService<EventType extends MinimalEvents> {
deployedBlock: number; deployedBlock: number;
batchEventsService: BatchEventsService; batchEventsService: BatchEventsService;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
saveEventsPromise?: Promise<void>; tovarishClient?: TovarishClient;
constructor({ netId, provider, graphApi, subgraphName, contract, type, deployedBlock, fetchDataOptions, }: BaseEventsServiceConstructor); constructor({ netId, provider, graphApi, subgraphName, contract, type, deployedBlock, fetchDataOptions, tovarishClient, }: BaseEventsServiceConstructor);
getInstanceName(): string; getInstanceName(): string;
getType(): string; getType(): string;
getTovarishType(): string;
getGraphMethod(): string; getGraphMethod(): string;
getGraphParams(): BaseGraphParams; getGraphParams(): BaseGraphParams;
updateEventProgress({ percentage, type, fromBlock, toBlock, count }: Parameters<BatchEventOnProgress>[0]): void; updateEventProgress({ percentage, type, fromBlock, toBlock, count }: Parameters<BatchEventOnProgress>[0]): void;
@@ -53,8 +57,11 @@ export declare class BaseEventsService<EventType extends MinimalEvents> {
* Get saved or cached events * Get saved or cached events
*/ */
getEventsFromDB(): Promise<BaseEvents<EventType>>; getEventsFromDB(): Promise<BaseEvents<EventType>>;
getEventsFromCache(): Promise<BaseEvents<EventType>>; /**
getSavedEvents(): Promise<BaseEvents<EventType>>; * Events from remote cache (Either from local cache, CDN, or from IPFS)
*/
getEventsFromCache(): Promise<CachedEvents<EventType>>;
getSavedEvents(): Promise<BaseEvents<EventType> | CachedEvents<EventType>>;
/** /**
* Get latest events * Get latest events
*/ */
@@ -79,7 +86,7 @@ export declare class BaseEventsService<EventType extends MinimalEvents> {
*/ */
updateEvents(): Promise<{ updateEvents(): Promise<{
events: EventType[]; events: EventType[];
lastBlock: number | null; lastBlock: number;
}>; }>;
} }
export type BaseTornadoServiceConstructor = { export type BaseTornadoServiceConstructor = {
@@ -93,6 +100,7 @@ export type BaseTornadoServiceConstructor = {
currency: string; currency: string;
deployedBlock?: number; deployedBlock?: number;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
tovarishClient?: TovarishClient;
}; };
export type DepositsGraphParams = BaseGraphParams & { export type DepositsGraphParams = BaseGraphParams & {
amount: string; amount: string;
@@ -103,7 +111,8 @@ export declare class BaseTornadoService extends BaseEventsService<DepositsEvents
currency: string; currency: string;
batchTransactionService: BatchTransactionService; batchTransactionService: BatchTransactionService;
batchBlockService: BatchBlockService; batchBlockService: BatchBlockService;
constructor({ netId, provider, graphApi, subgraphName, Tornado, type, amount, currency, deployedBlock, fetchDataOptions, }: BaseTornadoServiceConstructor); tovarishClient?: TovarishClient;
constructor({ netId, provider, graphApi, subgraphName, Tornado, type, amount, currency, deployedBlock, fetchDataOptions, tovarishClient, }: BaseTornadoServiceConstructor);
getInstanceName(): string; getInstanceName(): string;
getGraphMethod(): string; getGraphMethod(): string;
getGraphParams(): DepositsGraphParams; getGraphParams(): DepositsGraphParams;
@@ -111,6 +120,9 @@ export declare class BaseTornadoService extends BaseEventsService<DepositsEvents
validateEvents({ events }: { validateEvents({ events }: {
events: (DepositsEvents | WithdrawalsEvents)[]; events: (DepositsEvents | WithdrawalsEvents)[];
}): void; }): void;
getLatestEvents({ fromBlock }: {
fromBlock: number;
}): Promise<BaseEvents<DepositsEvents | WithdrawalsEvents>>;
} }
export type BaseEchoServiceConstructor = { export type BaseEchoServiceConstructor = {
netId: NetIdType; netId: NetIdType;
@@ -120,9 +132,10 @@ export type BaseEchoServiceConstructor = {
Echoer: Echoer; Echoer: Echoer;
deployedBlock?: number; deployedBlock?: number;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
tovarishClient?: TovarishClient;
}; };
export declare class BaseEchoService extends BaseEventsService<EchoEvents> { export declare class BaseEchoService extends BaseEventsService<EchoEvents> {
constructor({ netId, provider, graphApi, subgraphName, Echoer, deployedBlock, fetchDataOptions, }: BaseEchoServiceConstructor); constructor({ netId, provider, graphApi, subgraphName, Echoer, deployedBlock, fetchDataOptions, tovarishClient, }: BaseEchoServiceConstructor);
getInstanceName(): string; getInstanceName(): string;
getType(): string; getType(): string;
getGraphMethod(): string; getGraphMethod(): string;
@@ -139,11 +152,13 @@ export type BaseEncryptedNotesServiceConstructor = {
Router: TornadoRouter | TornadoProxyLight; Router: TornadoRouter | TornadoProxyLight;
deployedBlock?: number; deployedBlock?: number;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
tovarishClient?: TovarishClient;
}; };
export declare class BaseEncryptedNotesService extends BaseEventsService<EncryptedNotesEvents> { export declare class BaseEncryptedNotesService extends BaseEventsService<EncryptedNotesEvents> {
constructor({ netId, provider, graphApi, subgraphName, Router, deployedBlock, fetchDataOptions, }: BaseEncryptedNotesServiceConstructor); constructor({ netId, provider, graphApi, subgraphName, Router, deployedBlock, fetchDataOptions, tovarishClient, }: BaseEncryptedNotesServiceConstructor);
getInstanceName(): string; getInstanceName(): string;
getType(): string; getType(): string;
getTovarishType(): string;
getGraphMethod(): string; getGraphMethod(): string;
formatEvents(events: EventLog[]): Promise<EncryptedNotesEvents[]>; formatEvents(events: EventLog[]): Promise<EncryptedNotesEvents[]>;
} }
@@ -155,31 +170,60 @@ export type BaseGovernanceServiceConstructor = {
Governance: Governance; Governance: Governance;
deployedBlock?: number; deployedBlock?: number;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
tovarishClient?: TovarishClient;
}; };
export declare class BaseGovernanceService extends BaseEventsService<AllGovernanceEvents> { export declare class BaseGovernanceService extends BaseEventsService<AllGovernanceEvents> {
batchTransactionService: BatchTransactionService; batchTransactionService: BatchTransactionService;
constructor({ netId, provider, graphApi, subgraphName, Governance, deployedBlock, fetchDataOptions, }: BaseGovernanceServiceConstructor); constructor({ netId, provider, graphApi, subgraphName, Governance, deployedBlock, fetchDataOptions, tovarishClient, }: BaseGovernanceServiceConstructor);
getInstanceName(): string; getInstanceName(): string;
getType(): string; getType(): string;
getTovarishType(): string;
getGraphMethod(): string; getGraphMethod(): string;
formatEvents(events: EventLog[]): Promise<AllGovernanceEvents[]>; formatEvents(events: EventLog[]): Promise<AllGovernanceEvents[]>;
getEventsFromGraph({ fromBlock }: { getEventsFromGraph({ fromBlock }: {
fromBlock: number; fromBlock: number;
}): Promise<BaseEvents<AllGovernanceEvents>>; }): Promise<BaseEvents<AllGovernanceEvents>>;
} }
export declare function getTovarishNetworks(registryService: BaseRegistryService, relayers: CachedRelayerInfo[]): Promise<void>;
/**
* 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;
tovarishHost?: string;
tovarishNetworks?: number[];
}
export interface CachedRelayers {
lastBlock: number;
timestamp: number;
relayers: CachedRelayerInfo[];
fromCache?: boolean;
}
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;
tovarishClient?: TovarishClient;
}; };
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, tovarishClient, }: BaseRegistryServiceConstructor);
getInstanceName(): string; getInstanceName(): string;
getType(): string; getType(): string;
getTovarishType(): string;
getGraphMethod(): string; getGraphMethod(): string;
formatEvents(events: EventLog[]): Promise<{ formatEvents(events: EventLog[]): Promise<{
ensName: any; ensName: any;
@@ -188,5 +232,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({ lastBlock, timestamp, relayers }: CachedRelayers): Promise<void>;
/**
* Get cached or latest relayer and save to local
*/
updateRelayers(): Promise<CachedRelayers>;
} }

View File

@@ -3,6 +3,9 @@ export interface BaseEvents<T> {
events: T[]; events: T[];
lastBlock: number | null; lastBlock: number | null;
} }
export interface CachedEvents<T> extends BaseEvents<T> {
fromCache: boolean;
}
export interface BaseGraphEvents<T> { export interface BaseGraphEvents<T> {
events: T[]; events: T[];
lastSyncBlock: number; lastSyncBlock: number;

1
dist/index.d.ts vendored
View File

@@ -15,5 +15,6 @@ export * from './prices';
export * from './providers'; export * from './providers';
export * from './relayerClient'; export * from './relayerClient';
export * from './tokens'; export * from './tokens';
export * from './tovarishClient';
export * from './utils'; export * from './utils';
export * from './websnark'; export * from './websnark';

7472
dist/index.js vendored

File diff suppressed because it is too large Load Diff

7462
dist/index.mjs vendored

File diff suppressed because it is too large Load Diff

17963
dist/merkleTreeWorker.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

2
dist/merkleTreeWorker.umd.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,26 @@
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
* @license MIT
*/
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/**
* [js-sha3]{@link https://github.com/emn178/js-sha3}
*
* @version 0.8.0
* @author Chen, Yi-Cyuan [emn178@gmail.com]
* @copyright Chen, Yi-Cyuan 2015-2018
* @license MIT
*/

View File

@@ -67,8 +67,6 @@ export type Config = {
registryContract?: string; registryContract?: string;
aggregatorContract?: string; aggregatorContract?: string;
reverseRecordsContract?: string; reverseRecordsContract?: string;
gasPriceOracleContract?: string;
gasStationApi?: string;
ovmGasPriceOracleContract?: string; ovmGasPriceOracleContract?: string;
tornadoSubgraph: string; tornadoSubgraph: string;
registrySubgraph?: string; registrySubgraph?: string;
@@ -76,6 +74,7 @@ export type Config = {
subgraphs: SubgraphUrls; subgraphs: SubgraphUrls;
tokens: TokenInstances; tokens: TokenInstances;
optionalTokens?: string[]; optionalTokens?: string[];
disabledTokens?: string[];
relayerEnsSubdomain: string; relayerEnsSubdomain: string;
pollInterval: number; pollInterval: number;
constants: { constants: {
@@ -89,8 +88,11 @@ 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: number[]; export declare const enabledChains: NetIdType[];
/** /**
* Custom config object to extend default config * Custom config object to extend default config
* *
@@ -106,11 +108,10 @@ export declare let customConfig: networkConfig;
export declare function addNetwork(newConfig: networkConfig): void; export declare function addNetwork(newConfig: networkConfig): void;
export declare function getNetworkConfig(): networkConfig; export declare function getNetworkConfig(): networkConfig;
export declare function getConfig(netId: NetIdType): Config; export declare function getConfig(netId: NetIdType): Config;
export declare function getInstanceByAddress({ netId, address }: { export declare function getActiveTokens(config: Config): string[];
netId: NetIdType; export declare function getActiveTokenInstances(config: Config): TokenInstances;
address: string; export declare function getInstanceByAddress(config: Config, address: string): {
}): {
amount: string; amount: string;
currency: string; currency: string;
} | undefined; } | undefined;
export declare function getSubdomains(): string[]; export declare function getRelayerEnsSubdomains(): SubdomainMap;

9
dist/providers.d.ts vendored
View File

@@ -1,9 +1,6 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import type { EventEmitter } from 'stream'; import type { EventEmitter } from 'stream';
import type { RequestOptions } from 'http'; import type { RequestOptions } from 'http';
import { JsonRpcApiProvider, JsonRpcProvider, Wallet, FetchGetUrlFunc, Provider, SigningKey, TransactionRequest, JsonRpcSigner, BrowserProvider, Networkish, Eip1193Provider, VoidSigner, FetchUrlFeeDataNetworkPlugin } from 'ethers'; import { JsonRpcApiProvider, JsonRpcProvider, Wallet, FetchGetUrlFunc, Provider, SigningKey, TransactionRequest, JsonRpcSigner, BrowserProvider, Networkish, Eip1193Provider, VoidSigner } from 'ethers';
import type { RequestInfo, RequestInit, Response, HeadersInit } from 'node-fetch'; import type { RequestInfo, RequestInit, Response, HeadersInit } from 'node-fetch';
import type { Config, NetIdType } from './networkConfig'; import type { Config, NetIdType } from './networkConfig';
declare global { declare global {
@@ -35,11 +32,9 @@ export declare function getHttpAgent({ fetchUrl, proxyUrl, torPort, retry, }: {
export declare function fetchData(url: string, options?: fetchDataOptions): Promise<any>; export declare function fetchData(url: string, options?: fetchDataOptions): Promise<any>;
export declare const fetchGetUrlFunc: (options?: fetchDataOptions) => FetchGetUrlFunc; export declare const fetchGetUrlFunc: (options?: fetchDataOptions) => FetchGetUrlFunc;
export type getProviderOptions = fetchDataOptions & { export type getProviderOptions = fetchDataOptions & {
netId?: NetIdType;
pollingInterval?: number; pollingInterval?: number;
gasPriceOracle?: string;
gasStationApi?: string;
}; };
export declare function getGasOraclePlugin(networkKey: string, fetchOptions?: getProviderOptions): FetchUrlFeeDataNetworkPlugin;
export declare function getProvider(rpcUrl: string, fetchOptions?: getProviderOptions): Promise<JsonRpcProvider>; export declare function getProvider(rpcUrl: string, fetchOptions?: getProviderOptions): Promise<JsonRpcProvider>;
export declare function getProviderWithNetId(netId: NetIdType, rpcUrl: string, config: Config, fetchOptions?: getProviderOptions): JsonRpcProvider; export declare function getProviderWithNetId(netId: NetIdType, rpcUrl: string, config: Config, fetchOptions?: getProviderOptions): JsonRpcProvider;
export declare const populateTransaction: (signer: TornadoWallet | TornadoVoidSigner | TornadoRpcSigner, tx: TransactionRequest) => Promise<TransactionRequest>; export declare const populateTransaction: (signer: TornadoWallet | TornadoVoidSigner | TornadoRpcSigner, tx: TransactionRequest) => Promise<TransactionRequest>;

View File

@@ -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 type { CachedRelayerInfo } from './events';
export declare const MIN_FEE = 0.1;
export declare const MAX_FEE = 0.9;
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;
@@ -62,6 +63,9 @@ export interface RelayerStatus {
}; };
currentQueue: number; currentQueue: number;
} }
export type TornadoWithdrawParams = snarkProofs & {
contract: string;
};
export interface RelayerTornadoWithdraw { export interface RelayerTornadoWithdraw {
id?: string; id?: string;
error?: string; error?: string;
@@ -78,16 +82,34 @@ export interface RelayerTornadoJobs {
confirmations?: number; confirmations?: number;
failedReason?: string; failedReason?: string;
} }
/**
const semVerRegex =
/^(?<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-]+)*))?$/;
export interface semanticVersion { export interface semanticVersion {
major: string; major: string;
minor: string; minor: string;
patch: string; patch: string;
prerelease?: string; prerelease?: string;
buildmetadata?: string; buildmetadata?: string;
} }
export declare function parseSemanticVersion(version: string): semanticVersion;
export declare function isRelayerUpdated(relayerVersion: string, netId: NetIdType): boolean; export function parseSemanticVersion(version: string) {
export declare function calculateScore({ stakeBalance, tornadoServiceFee }: RelayerInfo, minFee?: number, maxFee?: number): bigint; const { groups } = semVerRegex.exec(version) as RegExpExecArray;
return groups as unknown as semanticVersion;
}
export function isRelayerUpdated(relayerVersion: string, netId: NetIdType) {
const { major, patch, prerelease } = parseSemanticVersion(relayerVersion);
// Save backwards compatibility with V4 relayers for Ethereum Mainnet
const requiredMajor = netId === NetId.MAINNET ? '4' : '5';
const isUpdatedMajor = major === requiredMajor;
if (prerelease) return false;
return isUpdatedMajor && (Number(patch) >= 5 || netId !== NetId.MAINNET); // Patch checking - also backwards compatibility for Mainnet
}
**/
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,32 +119,29 @@ 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 & {
contract: string;
};
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); tovarish: boolean;
askRelayerStatus({ hostname, relayerAddress, }: { constructor({ netId, config, fetchDataOptions }: RelayerClientConstructor);
hostname: string; askRelayerStatus({ hostname, url, relayerAddress, }: {
hostname?: string;
url?: 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[];
}>; }>;
pickWeightedRandomRelayer(relayers: RelayerInfo[]): RelayerInfo; pickWeightedRandomRelayer(relayers: RelayerInfo[]): RelayerInfo;
tornadoWithdraw({ contract, proof, args }: RelayerClientWithdraw): Promise<void>; tornadoWithdraw({ contract, proof, args }: TornadoWithdrawParams): Promise<void>;
} }

View File

@@ -74,6 +74,24 @@ export type statusSchema = {
}; };
required: string[]; required: string[];
}; };
syncStatus: {
type: string;
properties: {
events: {
type: string;
};
tokenPrice: {
type: string;
};
gasPrice: {
type: string;
};
};
required: string[];
};
onSyncEvents: {
type: string;
};
currentQueue: { currentQueue: {
type: string; type: string;
}; };
@@ -88,5 +106,5 @@ declare const bnType: {
type: string; type: string;
BN: boolean; BN: boolean;
}; };
export declare function getStatusSchema(netId: NetIdType, config: Config): statusSchema; export declare function getStatusSchema(netId: NetIdType, config: Config, tovarish: boolean): statusSchema;
export {}; export {};

File diff suppressed because one or more lines are too long

8
dist/tornado.umd.min.js vendored Normal file

File diff suppressed because one or more lines are too long

34
dist/tornado.umd.min.js.LICENSE.txt vendored Normal file
View File

@@ -0,0 +1,34 @@
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
* @license MIT
*/
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/*! MIT License. Copyright 2015-2022 Richard Moore <me@ricmoo.com>. See LICENSE.txt. */
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
/**
* [js-sha3]{@link https://github.com/emn178/js-sha3}
*
* @version 0.8.0
* @author Chen, Yi-Cyuan [emn178@gmail.com]
* @copyright Chen, Yi-Cyuan 2015-2018
* @license MIT
*/

69
dist/tovarishClient.d.ts vendored Normal file
View File

@@ -0,0 +1,69 @@
import { RelayerClient, RelayerClientConstructor, RelayerError, RelayerInfo, RelayerStatus } from './relayerClient';
import { CachedRelayerInfo, MinimalEvents } from './events';
export declare const MAX_TOVARISH_EVENTS = 5000;
export interface EventsStatus {
events: number;
lastBlock: number;
}
export interface InstanceEventsStatus {
[index: string]: {
deposits: EventsStatus;
withdrawals: EventsStatus;
};
}
export interface CurrencyEventsStatus {
[index: string]: InstanceEventsStatus;
}
export interface TovarishEventsStatus {
governance?: EventsStatus;
registered?: {
lastBlock: number;
timestamp: number;
relayers: number;
};
echo: EventsStatus;
encrypted_notes: EventsStatus;
instances: CurrencyEventsStatus;
}
export interface TovarishSyncStatus {
events: boolean;
tokenPrice: boolean;
gasPrice: boolean;
}
export interface TovarishStatus extends RelayerStatus {
events: TovarishEventsStatus;
syncStatus: TovarishSyncStatus;
onSyncEvents: boolean;
}
export interface TovarishInfo extends RelayerInfo {
latestBlock: number;
version: string;
events: TovarishEventsStatus;
syncStatus: TovarishSyncStatus;
}
export interface TovarishEventsQuery {
type: string;
currency?: string;
amount?: string;
fromBlock: number;
recent?: boolean;
}
export interface BaseTovarishEvents<T> {
events: T[];
lastSyncBlock: number;
}
export declare class TovarishClient extends RelayerClient {
selectedRelayer?: TovarishInfo;
constructor({ netId, config, fetchDataOptions }: RelayerClientConstructor);
askRelayerStatus({ hostname, url, relayerAddress, }: {
hostname?: string;
url?: string;
relayerAddress?: string;
}): Promise<TovarishStatus>;
filterRelayer(relayer: CachedRelayerInfo): Promise<TovarishInfo | RelayerError | undefined>;
getValidRelayers(relayers: CachedRelayerInfo[]): Promise<{
validRelayers: TovarishInfo[];
invalidRelayers: RelayerError[];
}>;
getEvents<T extends MinimalEvents>({ type, currency, amount, fromBlock, recent, }: TovarishEventsQuery): Promise<BaseTovarishEvents<T>>;
}

View File

@@ -1,6 +1,5 @@
export { ENS__factory } from "./ENS__factory"; export { ENS__factory } from "./ENS__factory";
export { ERC20__factory } from "./ERC20__factory"; export { ERC20__factory } from "./ERC20__factory";
export { GasPriceOracle__factory } from "./GasPriceOracle__factory";
export { Multicall__factory } from "./Multicall__factory"; export { Multicall__factory } from "./Multicall__factory";
export { OffchainOracle__factory } from "./OffchainOracle__factory"; export { OffchainOracle__factory } from "./OffchainOracle__factory";
export { OvmGasPriceOracle__factory } from "./OvmGasPriceOracle__factory"; export { OvmGasPriceOracle__factory } from "./OvmGasPriceOracle__factory";

View File

@@ -1,6 +1,5 @@
export type { ENS } from "./ENS"; export type { ENS } from "./ENS";
export type { ERC20 } from "./ERC20"; export type { ERC20 } from "./ERC20";
export type { GasPriceOracle } from "./GasPriceOracle";
export type { Multicall } from "./Multicall"; export type { Multicall } from "./Multicall";
export type { OffchainOracle } from "./OffchainOracle"; export type { OffchainOracle } from "./OffchainOracle";
export type { OvmGasPriceOracle } from "./OvmGasPriceOracle"; export type { OvmGasPriceOracle } from "./OvmGasPriceOracle";
@@ -8,7 +7,6 @@ export type { ReverseRecords } from "./ReverseRecords";
export * as factories from "./factories"; export * as factories from "./factories";
export { ENS__factory } from "./factories/ENS__factory"; export { ENS__factory } from "./factories/ENS__factory";
export { ERC20__factory } from "./factories/ERC20__factory"; export { ERC20__factory } from "./factories/ERC20__factory";
export { GasPriceOracle__factory } from "./factories/GasPriceOracle__factory";
export { Multicall__factory } from "./factories/Multicall__factory"; export { Multicall__factory } from "./factories/Multicall__factory";
export { OffchainOracle__factory } from "./factories/OffchainOracle__factory"; export { OffchainOracle__factory } from "./factories/OffchainOracle__factory";
export { OvmGasPriceOracle__factory } from "./factories/OvmGasPriceOracle__factory"; export { OvmGasPriceOracle__factory } from "./factories/OvmGasPriceOracle__factory";

3
dist/utils.d.ts vendored
View File

@@ -1,5 +1,3 @@
/// <reference types="node" />
/// <reference types="node" />
import { webcrypto } from 'crypto'; import { webcrypto } from 'crypto';
import BN from 'bn.js'; import BN from 'bn.js';
import type { BigNumberish } from 'ethers'; import type { BigNumberish } from 'ethers';
@@ -24,4 +22,5 @@ export declare function toFixedLength(string: string, length?: number): string;
export declare function rBigInt(nbytes?: number): bigint; export declare function rBigInt(nbytes?: number): bigint;
export declare function bigIntReplacer(key: any, value: any): any; export declare function bigIntReplacer(key: any, value: any): any;
export declare function substring(str: string, length?: number): string; export declare function substring(str: string, length?: number): string;
export declare function digest(bytes: Uint8Array, algo?: string): Promise<Uint8Array>;
export {}; export {};

19
dist/websnark.d.ts vendored
View File

@@ -1,10 +1,9 @@
import type { Element } from '@tornado/fixed-merkle-tree'; import type { Element } from '@tornado/fixed-merkle-tree';
import type { AddressLike, BytesLike, BigNumberish } from 'ethers';
export type snarkInputs = { export type snarkInputs = {
root: Element; root: Element;
nullifierHex: string; nullifierHex: string;
recipient: AddressLike; recipient: string;
relayer: AddressLike; relayer: string;
fee: bigint; fee: bigint;
refund: bigint; refund: bigint;
nullifier: bigint; nullifier: bigint;
@@ -13,15 +12,15 @@ export type snarkInputs = {
pathIndices: Element[]; pathIndices: Element[];
}; };
export type snarkArgs = [ export type snarkArgs = [
_root: BytesLike, _root: string,
_nullifierHash: BytesLike, _nullifierHash: string,
_recipient: AddressLike, _recipient: string,
_relayer: AddressLike, _relayer: string,
_fee: BigNumberish, _fee: string,
_refund: BigNumberish _refund: string
]; ];
export type snarkProofs = { export type snarkProofs = {
proof: BytesLike; proof: string;
args: snarkArgs; args: snarkArgs;
}; };
export declare function initGroth16(): Promise<void>; export declare function initGroth16(): Promise<void>;

View File

@@ -1,12 +1,12 @@
{ {
"name": "@tornado/core", "name": "@tornado/core",
"version": "1.0.1", "version": "1.0.16",
"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",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"unpkg": "./dist/index.umd.js", "unpkg": "./dist/tornado.umd.min.js",
"jsdelivr": "./dist/index.umd.js", "jsdelivr": "./dist/tornado.umd.min.js",
"scripts": { "scripts": {
"typechain": "typechain --target ethers-v6 --out-dir src/typechain src/abi/*.json", "typechain": "typechain --target ethers-v6 --out-dir src/typechain src/abi/*.json",
"types": "tsc --declaration --emitDeclarationOnly", "types": "tsc --declaration --emitDeclarationOnly",
@@ -31,46 +31,44 @@
"yarn.lock" "yarn.lock"
], ],
"dependencies": { "dependencies": {
"@metamask/eth-sig-util": "^7.0.1", "@metamask/eth-sig-util": "^7.0.3",
"@tornado/contracts": "^1.0.0", "@tornado/contracts": "^1.0.1",
"@tornado/fixed-merkle-tree": "^0.7.3", "@tornado/fixed-merkle-tree": "^0.7.3",
"@tornado/snarkjs": "^0.1.20", "@tornado/snarkjs": "^0.1.20",
"@tornado/websnark": "^0.0.4", "@tornado/websnark": "^0.0.4",
"ajv": "^8.12.0", "ajv": "^8.17.1",
"bn.js": "^5.2.1", "bn.js": "^5.2.1",
"circomlibjs": "0.1.7", "circomlibjs": "0.1.7",
"cross-fetch": "^4.0.0", "cross-fetch": "^4.0.0",
"ethers": "^6.4.0", "ethers": "^6.13.2",
"ffjavascript": "0.2.48", "ffjavascript": "0.2.48",
"fflate": "^0.8.2" "fflate": "^0.8.2"
}, },
"optionalDependencies": {},
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-json": "^6.1.0", "@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
"@typechain/ethers-v6": "^0.5.1", "@typechain/ethers-v6": "^0.5.1",
"@types/bn.js": "^5.1.5", "@types/bn.js": "^5.1.6",
"@types/circomlibjs": "^0.1.6", "@types/circomlibjs": "^0.1.6",
"@types/node": "^20.12.5", "@types/node": "^22.5.5",
"@types/node-fetch": "^2.6.11", "@types/node-fetch": "^2.6.11",
"@typescript-eslint/eslint-plugin": "^7.6.0", "@typescript-eslint/eslint-plugin": "^8.6.0",
"@typescript-eslint/parser": "^7.6.0", "@typescript-eslint/parser": "^8.6.0",
"esbuild": "^0.20.2", "esbuild-loader": "^4.2.2",
"esbuild-loader": "^4.1.0", "eslint": "8.57.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1", "eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.30.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.2.1",
"node-polyfill-webpack-plugin": "^3.0.0", "node-polyfill-webpack-plugin": "^4.0.0",
"prettier": "^3.2.5", "prettier": "^3.3.3",
"rollup": "^4.14.1", "rollup": "^4.22.0",
"rollup-plugin-esbuild": "^6.1.1", "rollup-plugin-esbuild": "^6.1.1",
"tsc": "^2.0.4", "tsc": "^2.0.4",
"typechain": "^8.3.2", "typechain": "^8.3.2",
"typescript": "^5.4.4", "typescript": "^5.6.2",
"webpack": "^5.91.0", "webpack": "^5.94.0",
"webpack-cli": "^5.1.4" "webpack-cli": "^5.1.4"
} }
} }

View File

@@ -7,7 +7,7 @@ import { readFileSync } from 'fs';
const pkgJson = JSON.parse(readFileSync("./package.json")); const pkgJson = JSON.parse(readFileSync("./package.json"));
const external = Object.keys(pkgJson.dependencies).concat( const external = Object.keys(pkgJson.dependencies).concat(
Object.keys(pkgJson.optionalDependencies), Object.keys(pkgJson.optionalDependencies || {}),
[ [
'http-proxy-agent', 'http-proxy-agent',
'https-proxy-agent', 'https-proxy-agent',

View File

@@ -1,189 +0,0 @@
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "GAS_UNIT",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "_derivationThresold",
"type": "uint32"
}
],
"name": "changeDerivationThresold",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "_gasUnit",
"type": "uint32"
}
],
"name": "changeGasUnit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "_heartbeat",
"type": "uint32"
}
],
"name": "changeHeartbeat",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"name": "changeOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "derivationThresold",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gasPrice",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "heartbeat",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxFeePerGas",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxPriorityFeePerGas",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pastGasPrice",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32",
"name": "_gasPrice",
"type": "uint32"
}
],
"name": "setGasPrice",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "timestamp",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
}
]

View File

@@ -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,10 +29,15 @@ import {
BatchEventOnProgress, BatchEventOnProgress,
BatchBlockOnProgress, BatchBlockOnProgress,
} from '../batch'; } from '../batch';
import { fetchDataOptions } from '../providers';
import type { NetIdType } from '../networkConfig'; import { fetchData, fetchDataOptions } from '../providers';
import type { NetIdType, SubdomainMap } from '../networkConfig';
import { RelayerParams, MIN_STAKE_BALANCE } from '../relayerClient';
import type { TovarishClient } from '../tovarishClient';
import type { import type {
BaseEvents, BaseEvents,
CachedEvents,
MinimalEvents, MinimalEvents,
DepositsEvents, DepositsEvents,
WithdrawalsEvents, WithdrawalsEvents,
@@ -45,6 +64,7 @@ export type BaseEventsServiceConstructor = {
type?: string; type?: string;
deployedBlock?: number; deployedBlock?: number;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
tovarishClient?: TovarishClient;
}; };
export type BatchGraphOnProgress = ({ export type BatchGraphOnProgress = ({
@@ -76,8 +96,7 @@ export class BaseEventsService<EventType extends MinimalEvents> {
deployedBlock: number; deployedBlock: number;
batchEventsService: BatchEventsService; batchEventsService: BatchEventsService;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
tovarishClient?: TovarishClient;
saveEventsPromise?: Promise<void>;
constructor({ constructor({
netId, netId,
@@ -88,6 +107,7 @@ export class BaseEventsService<EventType extends MinimalEvents> {
type = '', type = '',
deployedBlock = 0, deployedBlock = 0,
fetchDataOptions, fetchDataOptions,
tovarishClient,
}: BaseEventsServiceConstructor) { }: BaseEventsServiceConstructor) {
this.netId = netId; this.netId = netId;
this.provider = provider; this.provider = provider;
@@ -104,6 +124,8 @@ export class BaseEventsService<EventType extends MinimalEvents> {
contract, contract,
onProgress: this.updateEventProgress, onProgress: this.updateEventProgress,
}); });
this.tovarishClient = tovarishClient;
} }
getInstanceName(): string { getInstanceName(): string {
@@ -114,6 +136,10 @@ export class BaseEventsService<EventType extends MinimalEvents> {
return this.type || ''; return this.type || '';
} }
getTovarishType(): string {
return String(this.getType() || '').toLowerCase();
}
getGraphMethod(): string { getGraphMethod(): string {
return ''; return '';
} }
@@ -153,14 +179,18 @@ export class BaseEventsService<EventType extends MinimalEvents> {
}; };
} }
async getEventsFromCache(): Promise<BaseEvents<EventType>> { /**
* Events from remote cache (Either from local cache, CDN, or from IPFS)
*/
async getEventsFromCache(): Promise<CachedEvents<EventType>> {
return { return {
events: [], events: [],
lastBlock: null, lastBlock: null,
fromCache: true,
}; };
} }
async getSavedEvents(): Promise<BaseEvents<EventType>> { async getSavedEvents(): Promise<BaseEvents<EventType> | CachedEvents<EventType>> {
let cachedEvents = await this.getEventsFromDB(); let cachedEvents = await this.getEventsFromDB();
if (!cachedEvents || !cachedEvents.events.length) { if (!cachedEvents || !cachedEvents.events.length) {
@@ -224,13 +254,6 @@ export class BaseEventsService<EventType extends MinimalEvents> {
await this.batchEventsService.getBatchEvents({ fromBlock, toBlock, type: this.getType() }), await this.batchEventsService.getBatchEvents({ fromBlock, toBlock, type: this.getType() }),
); );
if (!events.length) {
return {
events,
lastBlock: toBlock,
};
}
return { return {
events, events,
lastBlock: toBlock, lastBlock: toBlock,
@@ -245,22 +268,25 @@ export class BaseEventsService<EventType extends MinimalEvents> {
} }
async getLatestEvents({ fromBlock }: { fromBlock: number }): Promise<BaseEvents<EventType>> { async getLatestEvents({ fromBlock }: { fromBlock: number }): Promise<BaseEvents<EventType>> {
const allEvents = []; if (this.tovarishClient?.selectedRelayer && ![DEPOSIT, WITHDRAWAL].includes(this.type.toLowerCase())) {
const { events, lastSyncBlock: lastBlock } = await this.tovarishClient.getEvents<EventType>({
type: this.getTovarishType(),
fromBlock,
});
return {
events,
lastBlock,
};
}
const graphEvents = await this.getEventsFromGraph({ fromBlock }); const graphEvents = await this.getEventsFromGraph({ fromBlock });
const lastSyncBlock = const lastSyncBlock =
graphEvents.lastBlock && graphEvents.lastBlock >= fromBlock ? graphEvents.lastBlock : fromBlock; graphEvents.lastBlock && graphEvents.lastBlock >= fromBlock ? graphEvents.lastBlock : fromBlock;
const rpcEvents = await this.getEventsFromRpc({ fromBlock: lastSyncBlock }); const rpcEvents = await 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 { return {
events: allEvents, events: [...graphEvents.events, ...rpcEvents.events],
lastBlock, lastBlock: rpcEvents.lastBlock,
}; };
} }
@@ -291,12 +317,7 @@ export class BaseEventsService<EventType extends MinimalEvents> {
const eventSet = new Set(); const eventSet = new Set();
let allEvents: EventType[] = []; const allEvents: EventType[] = [...savedEvents.events, ...newEvents.events]
allEvents.push(...savedEvents.events);
allEvents.push(...newEvents.events);
allEvents = allEvents
.sort((a, b) => { .sort((a, b) => {
if (a.blockNumber === b.blockNumber) { if (a.blockNumber === b.blockNumber) {
return a.logIndex - b.logIndex; return a.logIndex - b.logIndex;
@@ -309,15 +330,15 @@ export class BaseEventsService<EventType extends MinimalEvents> {
eventSet.add(eventKey); eventSet.add(eventKey);
return !hasEvent; return !hasEvent;
}); });
const lastBlock = newEvents
? newEvents.lastBlock const lastBlock = newEvents.lastBlock || allEvents[allEvents.length - 1]?.blockNumber;
: allEvents[allEvents.length - 1]
? allEvents[allEvents.length - 1].blockNumber
: null;
this.validateEvents({ events: allEvents, lastBlock }); this.validateEvents({ events: allEvents, lastBlock });
this.saveEventsPromise = this.saveEvents({ events: allEvents, lastBlock }); // If the events are loaded from cache or we have found new events, save them
if ((savedEvents as CachedEvents<EventType>).fromCache || newEvents.events.length) {
await this.saveEvents({ events: allEvents, lastBlock });
}
return { return {
events: allEvents, events: allEvents,
@@ -337,6 +358,7 @@ export type BaseTornadoServiceConstructor = {
currency: string; currency: string;
deployedBlock?: number; deployedBlock?: number;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
tovarishClient?: TovarishClient;
}; };
export type DepositsGraphParams = BaseGraphParams & { export type DepositsGraphParams = BaseGraphParams & {
@@ -349,6 +371,7 @@ export class BaseTornadoService extends BaseEventsService<DepositsEvents | Withd
currency: string; currency: string;
batchTransactionService: BatchTransactionService; batchTransactionService: BatchTransactionService;
batchBlockService: BatchBlockService; batchBlockService: BatchBlockService;
tovarishClient?: TovarishClient;
constructor({ constructor({
netId, netId,
@@ -361,8 +384,19 @@ export class BaseTornadoService extends BaseEventsService<DepositsEvents | Withd
currency, currency,
deployedBlock, deployedBlock,
fetchDataOptions, fetchDataOptions,
tovarishClient,
}: BaseTornadoServiceConstructor) { }: BaseTornadoServiceConstructor) {
super({ netId, provider, graphApi, subgraphName, contract: Tornado, type, deployedBlock, fetchDataOptions }); super({
netId,
provider,
graphApi,
subgraphName,
contract: Tornado,
type,
deployedBlock,
fetchDataOptions,
tovarishClient,
});
this.amount = amount; this.amount = amount;
this.currency = currency; this.currency = currency;
@@ -464,6 +498,26 @@ export class BaseTornadoService extends BaseEventsService<DepositsEvents | Withd
} }
} }
} }
async getLatestEvents({ fromBlock }: { fromBlock: number }): Promise<BaseEvents<DepositsEvents | WithdrawalsEvents>> {
if (this.tovarishClient?.selectedRelayer) {
const { events, lastSyncBlock: lastBlock } = await this.tovarishClient.getEvents<
DepositsEvents | WithdrawalsEvents
>({
type: this.getTovarishType(),
currency: this.currency,
amount: this.amount,
fromBlock,
});
return {
events,
lastBlock,
};
}
return super.getLatestEvents({ fromBlock });
}
} }
export type BaseEchoServiceConstructor = { export type BaseEchoServiceConstructor = {
@@ -474,6 +528,7 @@ export type BaseEchoServiceConstructor = {
Echoer: Echoer; Echoer: Echoer;
deployedBlock?: number; deployedBlock?: number;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
tovarishClient?: TovarishClient;
}; };
export class BaseEchoService extends BaseEventsService<EchoEvents> { export class BaseEchoService extends BaseEventsService<EchoEvents> {
@@ -485,8 +540,18 @@ export class BaseEchoService extends BaseEventsService<EchoEvents> {
Echoer, Echoer,
deployedBlock, deployedBlock,
fetchDataOptions, fetchDataOptions,
tovarishClient,
}: BaseEchoServiceConstructor) { }: BaseEchoServiceConstructor) {
super({ netId, provider, graphApi, subgraphName, contract: Echoer, deployedBlock, fetchDataOptions }); super({
netId,
provider,
graphApi,
subgraphName,
contract: Echoer,
deployedBlock,
fetchDataOptions,
tovarishClient,
});
} }
getInstanceName(): string { getInstanceName(): string {
@@ -544,6 +609,7 @@ export type BaseEncryptedNotesServiceConstructor = {
Router: TornadoRouter | TornadoProxyLight; Router: TornadoRouter | TornadoProxyLight;
deployedBlock?: number; deployedBlock?: number;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
tovarishClient?: TovarishClient;
}; };
export class BaseEncryptedNotesService extends BaseEventsService<EncryptedNotesEvents> { export class BaseEncryptedNotesService extends BaseEventsService<EncryptedNotesEvents> {
@@ -555,8 +621,18 @@ export class BaseEncryptedNotesService extends BaseEventsService<EncryptedNotesE
Router, Router,
deployedBlock, deployedBlock,
fetchDataOptions, fetchDataOptions,
tovarishClient,
}: BaseEncryptedNotesServiceConstructor) { }: BaseEncryptedNotesServiceConstructor) {
super({ netId, provider, graphApi, subgraphName, contract: Router, deployedBlock, fetchDataOptions }); super({
netId,
provider,
graphApi,
subgraphName,
contract: Router,
deployedBlock,
fetchDataOptions,
tovarishClient,
});
} }
getInstanceName(): string { getInstanceName(): string {
@@ -567,6 +643,10 @@ export class BaseEncryptedNotesService extends BaseEventsService<EncryptedNotesE
return 'EncryptedNote'; return 'EncryptedNote';
} }
getTovarishType(): string {
return 'encrypted_notes';
}
getGraphMethod(): string { getGraphMethod(): string {
return 'getAllEncryptedNotes'; return 'getAllEncryptedNotes';
} }
@@ -576,7 +656,7 @@ export class BaseEncryptedNotesService extends BaseEventsService<EncryptedNotesE
.map(({ blockNumber, index: logIndex, transactionHash, args }) => { .map(({ blockNumber, index: logIndex, transactionHash, args }) => {
const { encryptedNote } = args; const { encryptedNote } = args;
if (encryptedNote) { if (encryptedNote && encryptedNote !== '0x') {
const eventObjects = { const eventObjects = {
blockNumber, blockNumber,
logIndex, logIndex,
@@ -601,6 +681,7 @@ export type BaseGovernanceServiceConstructor = {
Governance: Governance; Governance: Governance;
deployedBlock?: number; deployedBlock?: number;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
tovarishClient?: TovarishClient;
}; };
export class BaseGovernanceService extends BaseEventsService<AllGovernanceEvents> { export class BaseGovernanceService extends BaseEventsService<AllGovernanceEvents> {
@@ -614,8 +695,18 @@ export class BaseGovernanceService extends BaseEventsService<AllGovernanceEvents
Governance, Governance,
deployedBlock, deployedBlock,
fetchDataOptions, fetchDataOptions,
tovarishClient,
}: BaseGovernanceServiceConstructor) { }: BaseGovernanceServiceConstructor) {
super({ netId, provider, graphApi, subgraphName, contract: Governance, deployedBlock, fetchDataOptions }); super({
netId,
provider,
graphApi,
subgraphName,
contract: Governance,
deployedBlock,
fetchDataOptions,
tovarishClient,
});
this.batchTransactionService = new BatchTransactionService({ this.batchTransactionService = new BatchTransactionService({
provider, provider,
@@ -631,6 +722,10 @@ export class BaseGovernanceService extends BaseEventsService<AllGovernanceEvents
return '*'; return '*';
} }
getTovarishType(): string {
return 'governance';
}
getGraphMethod() { getGraphMethod() {
return 'getAllGovernanceEvents'; return 'getAllGovernanceEvents';
} }
@@ -735,27 +830,94 @@ export class BaseGovernanceService extends BaseEventsService<AllGovernanceEvents
} }
} }
export async function getTovarishNetworks(registryService: BaseRegistryService, relayers: CachedRelayerInfo[]) {
await Promise.all(
relayers
.filter((r) => r.tovarishHost)
.map(async (relayer) => {
try {
relayer.tovarishNetworks = await fetchData(relayer.tovarishHost as string, {
...registryService.fetchDataOptions,
headers: {
'Content-Type': 'application/json',
},
timeout: registryService.fetchDataOptions?.torPort ? 10000 : 3000,
maxRetry: registryService.fetchDataOptions?.torPort ? 2 : 0,
});
} catch {
// Ignore error and disable relayer
relayer.tovarishNetworks = [];
}
}),
);
}
/**
* 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;
tovarishHost?: string;
tovarishNetworks?: number[];
}
export interface CachedRelayers {
lastBlock: number;
timestamp: number;
relayers: CachedRelayerInfo[];
fromCache?: boolean;
}
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;
tovarishClient?: TovarishClient;
}; };
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,
tovarishClient,
}: BaseRegistryServiceConstructor) { }: BaseRegistryServiceConstructor) {
super({ netId, provider, graphApi, subgraphName, contract: RelayerRegistry, deployedBlock, fetchDataOptions }); super({
netId,
provider,
graphApi,
subgraphName,
contract: RelayerRegistry,
deployedBlock,
fetchDataOptions,
tovarishClient,
});
this.Aggregator = Aggregator;
this.relayerEnsSubdomains = relayerEnsSubdomains;
this.updateInterval = 86400;
} }
getInstanceName() { getInstanceName() {
@@ -767,6 +929,10 @@ export class BaseRegistryService extends BaseEventsService<RegistersEvents> {
return 'RelayerRegistered'; return 'RelayerRegistered';
} }
getTovarishType(): string {
return 'registered';
}
// Name of method used for graph // Name of method used for graph
getGraphMethod() { getGraphMethod() {
return 'getAllRegisters'; return 'getAllRegisters';
@@ -788,7 +954,135 @@ 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 {
lastBlock: 0,
timestamp: 0,
relayers: [],
};
}
/**
* Relayers from remote cache (Either from local cache, CDN, or from IPFS)
*/
async getRelayersFromCache(): Promise<CachedRelayers> {
return {
lastBlock: 0,
timestamp: 0,
relayers: [],
fromCache: true,
};
}
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 { events, lastBlock } = await this.updateEvents();
const subdomains = Object.values(this.relayerEnsSubdomains);
const registerSet = new Set();
const uniqueRegisters = events.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.concat('tovarish-relayer')),
this.provider.getBlock(lastBlock).then((b) => Number(b?.timestamp)),
]);
const relayers = relayersData
.map(({ owner, balance: stakeBalance, records, isRegistered }, index) => {
const { ensName, relayerAddress } = uniqueRegisters[index];
let tovarishHost = undefined;
const hostnames = records.reduce((acc, record, recordIndex) => {
if (record) {
// tovarish-relayer.relayer.eth
if (recordIndex === records.length - 1) {
tovarishHost = record;
return acc;
}
acc[Number(Object.keys(this.relayerEnsSubdomains)[recordIndex])] = record;
}
return acc;
}, {} as SubdomainMap);
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,
tovarishHost,
} as CachedRelayerInfo;
}
})
.filter((r) => r) as CachedRelayerInfo[];
await getTovarishNetworks(this, relayers);
return {
lastBlock,
timestamp,
relayers,
};
}
/**
* Handle saving relayers
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async saveRelayers({ lastBlock, timestamp, relayers }: CachedRelayers) {}
/**
* Get cached or latest relayer and save to local
*/
async updateRelayers(): Promise<CachedRelayers> {
// eslint-disable-next-line prefer-const
let { lastBlock, timestamp, relayers, fromCache } = await this.getSavedRelayers();
let shouldSave = fromCache ?? false;
if (!relayers.length || timestamp + this.updateInterval < Math.floor(Date.now() / 1000)) {
console.log('\nUpdating relayers from registry\n');
({ lastBlock, timestamp, relayers } = await this.getLatestRelayers());
shouldSave = true;
}
if (shouldSave) {
await this.saveRelayers({ lastBlock, timestamp, relayers });
}
return { lastBlock, timestamp, relayers };
} }
} }

View File

@@ -5,6 +5,10 @@ export interface BaseEvents<T> {
lastBlock: number | null; lastBlock: number | null;
} }
export interface CachedEvents<T> extends BaseEvents<T> {
fromCache: boolean;
}
export interface BaseGraphEvents<T> { export interface BaseGraphEvents<T> {
events: T[]; events: T[];
lastSyncBlock: number; lastSyncBlock: number;

View File

@@ -4,7 +4,7 @@ import { OvmGasPriceOracle } from './typechain';
const DUMMY_ADDRESS = '0x1111111111111111111111111111111111111111'; const DUMMY_ADDRESS = '0x1111111111111111111111111111111111111111';
const DUMMY_NONCE = '0x1111111111111111111111111111111111111111111111111111111111111111'; const DUMMY_NONCE = 1024;
const DUMMY_WITHDRAW_DATA = const DUMMY_WITHDRAW_DATA =
'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111'; '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111';
@@ -61,10 +61,9 @@ export class TornadoFeeOracle {
tx = { tx = {
type: 0, type: 0,
gasLimit: 1_000_000, gasLimit: 1_000_000,
nonce: Number(DUMMY_NONCE), nonce: DUMMY_NONCE,
data: DUMMY_WITHDRAW_DATA, data: DUMMY_WITHDRAW_DATA,
gasPrice: parseUnits('1', 'gwei'), gasPrice: parseUnits('1', 'gwei'),
from: DUMMY_ADDRESS,
to: DUMMY_ADDRESS, to: DUMMY_ADDRESS,
}; };
} }

View File

@@ -15,5 +15,6 @@ export * from './prices';
export * from './providers'; export * from './providers';
export * from './relayerClient'; export * from './relayerClient';
export * from './tokens'; export * from './tokens';
export * from './tovarishClient';
export * from './utils'; export * from './utils';
export * from './websnark'; export * from './websnark';

View File

@@ -78,8 +78,6 @@ export type Config = {
registryContract?: string; registryContract?: string;
aggregatorContract?: string; aggregatorContract?: string;
reverseRecordsContract?: string; reverseRecordsContract?: string;
gasPriceOracleContract?: string;
gasStationApi?: string;
ovmGasPriceOracleContract?: string; ovmGasPriceOracleContract?: string;
tornadoSubgraph: string; tornadoSubgraph: string;
registrySubgraph?: string; registrySubgraph?: string;
@@ -87,6 +85,7 @@ export type Config = {
subgraphs: SubgraphUrls; subgraphs: SubgraphUrls;
tokens: TokenInstances; tokens: TokenInstances;
optionalTokens?: string[]; optionalTokens?: string[];
disabledTokens?: string[];
relayerEnsSubdomain: string; relayerEnsSubdomain: string;
// Should be in seconds // Should be in seconds
pollInterval: number; pollInterval: number;
@@ -104,13 +103,8 @@ export type networkConfig = {
[key in NetIdType]: Config; [key in NetIdType]: Config;
}; };
const theGraph = { export type SubdomainMap = {
name: 'Hosted Graph', [key in NetIdType]: string;
url: 'https://api.thegraph.com',
};
const tornado = {
name: 'Tornado Subgraphs',
url: 'https://tornadocash-rpc.com',
}; };
export const defaultConfig: networkConfig = { export const defaultConfig: networkConfig = {
@@ -130,18 +124,26 @@ export const defaultConfig: networkConfig = {
networkName: 'Ethereum Mainnet', networkName: 'Ethereum Mainnet',
deployedBlock: 9116966, deployedBlock: 9116966,
rpcUrls: { rpcUrls: {
tornadoRPC: {
name: 'Tornado RPC',
url: 'https://tornadocash-rpc.com/mainnet',
},
chainnodes: {
name: 'Chainnodes RPC',
url: 'https://mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607',
},
mevblockerRPC: { mevblockerRPC: {
name: 'MevblockerRPC', name: 'MevblockerRPC',
url: 'https://rpc.mevblocker.io', url: 'https://rpc.mevblocker.io',
}, },
keydonix: {
name: 'keydonix',
url: 'https://ethereum.keydonix.com/v1/mainnet',
},
SecureRpc: {
name: 'SecureRpc',
url: 'https://api.securerpc.com/v1',
},
meowrpc: {
name: 'Meow RPC',
url: 'https://eth.meowrpc.com',
},
stackup: {
name: 'Stackup RPC',
url: 'https://public.stackup.sh/api/v1/node/ethereum-mainnet',
},
oneRPC: { oneRPC: {
name: '1RPC', name: '1RPC',
url: 'https://1rpc.io/eth', url: 'https://1rpc.io/eth',
@@ -160,10 +162,7 @@ export const defaultConfig: networkConfig = {
tornadoSubgraph: 'tornadocash/mainnet-tornado-subgraph', tornadoSubgraph: 'tornadocash/mainnet-tornado-subgraph',
registrySubgraph: 'tornadocash/tornado-relayer-registry', registrySubgraph: 'tornadocash/tornado-relayer-registry',
governanceSubgraph: 'tornadocash/tornado-governance', governanceSubgraph: 'tornadocash/tornado-governance',
subgraphs: { subgraphs: {},
tornado,
theGraph,
},
tokens: { tokens: {
eth: { eth: {
instanceAddress: { instanceAddress: {
@@ -236,6 +235,8 @@ export const defaultConfig: networkConfig = {
gasLimit: 700_000, gasLimit: 700_000,
}, },
}, },
// Inactive tokens to filter from schema verification and syncing events
disabledTokens: ['cdai', 'usdt', 'usdc'],
relayerEnsSubdomain: 'mainnet-tornado', relayerEnsSubdomain: 'mainnet-tornado',
pollInterval: 15, pollInterval: 15,
constants: { constants: {
@@ -266,18 +267,27 @@ export const defaultConfig: networkConfig = {
echoContract: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', echoContract: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4',
offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8', offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8',
tornadoSubgraph: 'tornadocash/bsc-tornado-subgraph', tornadoSubgraph: 'tornadocash/bsc-tornado-subgraph',
subgraphs: { subgraphs: {},
tornado,
theGraph,
},
rpcUrls: { rpcUrls: {
tornadoRPC: { bnbchain: {
name: 'Tornado RPC', name: 'BNB Chain',
url: 'https://tornadocash-rpc.com/bsc', url: 'https://bsc-dataseed.bnbchain.org',
}, },
chainnodes: { ninicoin: {
name: 'Chainnodes RPC', name: 'ninicoin',
url: 'https://bsc-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607', url: 'https://bsc-dataseed1.ninicoin.io',
},
nodereal: {
name: 'NodeReal',
url: 'https://binance.nodereal.io',
},
meowrpc: {
name: 'Meow RPC',
url: 'https://bsc.meowrpc.com',
},
stackup: {
name: 'Stackup RPC',
url: 'https://public.stackup.sh/api/v1/node/bsc-mainnet',
}, },
oneRPC: { oneRPC: {
name: '1RPC', name: '1RPC',
@@ -322,21 +332,21 @@ export const defaultConfig: networkConfig = {
routerContract: '0x0D5550d52428E7e3175bfc9550207e4ad3859b17', routerContract: '0x0D5550d52428E7e3175bfc9550207e4ad3859b17',
echoContract: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', echoContract: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4',
offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8', offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8',
gasPriceOracleContract: '0xF81A8D8D3581985D3969fe53bFA67074aDFa8F3C',
tornadoSubgraph: 'tornadocash/matic-tornado-subgraph', tornadoSubgraph: 'tornadocash/matic-tornado-subgraph',
subgraphs: { subgraphs: {},
tornado,
theGraph,
},
rpcUrls: { rpcUrls: {
chainnodes: {
name: 'Chainnodes RPC',
url: 'https://polygon-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607',
},
oneRpc: { oneRpc: {
name: '1RPC', name: '1RPC',
url: 'https://1rpc.io/matic', url: 'https://1rpc.io/matic',
}, },
meowrpc: {
name: 'Meow RPC',
url: 'https://polygon.meowrpc.com',
},
stackup: {
name: 'Stackup RPC',
url: 'https://public.stackup.sh/api/v1/node/polygon-mainnet',
},
}, },
tokens: { tokens: {
matic: { matic: {
@@ -378,18 +388,19 @@ export const defaultConfig: networkConfig = {
offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8', offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8',
ovmGasPriceOracleContract: '0x420000000000000000000000000000000000000F', ovmGasPriceOracleContract: '0x420000000000000000000000000000000000000F',
tornadoSubgraph: 'tornadocash/optimism-tornado-subgraph', tornadoSubgraph: 'tornadocash/optimism-tornado-subgraph',
subgraphs: { subgraphs: {},
tornado,
theGraph,
},
rpcUrls: { rpcUrls: {
tornadoRPC: { optimism: {
name: 'Tornado RPC', name: 'Optimism',
url: 'https://tornadocash-rpc.com/op', url: 'https://mainnet.optimism.io',
}, },
chainnodes: { meowrpc: {
name: 'Chainnodes RPC', name: 'Meow RPC',
url: 'https://optimism-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607', url: 'https://optimism.meowrpc.com',
},
stackup: {
name: 'Stackup RPC',
url: 'https://public.stackup.sh/api/v1/node/optimism-mainnet',
}, },
oneRpc: { oneRpc: {
name: '1RPC', name: '1RPC',
@@ -435,27 +446,24 @@ export const defaultConfig: networkConfig = {
echoContract: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', echoContract: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4',
offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8', offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8',
tornadoSubgraph: 'tornadocash/arbitrum-tornado-subgraph', tornadoSubgraph: 'tornadocash/arbitrum-tornado-subgraph',
subgraphs: { subgraphs: {},
tornado,
theGraph,
},
rpcUrls: { rpcUrls: {
tornadoRPC: { Arbitrum: {
name: 'Tornado RPC', name: 'Arbitrum RPC',
url: 'https://tornadocash-rpc.com/arbitrum', url: 'https://arb1.arbitrum.io/rpc',
}, },
chainnodes: { meowrpc: {
name: 'Chainnodes RPC', name: 'Meow RPC',
url: 'https://arbitrum-one.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607', url: 'https://arbitrum.meowrpc.com',
},
stackup: {
name: 'Stackup RPC',
url: 'https://public.stackup.sh/api/v1/node/arbitrum-one',
}, },
oneRpc: { oneRpc: {
name: '1rpc', name: '1rpc',
url: 'https://1rpc.io/arb', url: 'https://1rpc.io/arb',
}, },
Arbitrum: {
name: 'Arbitrum RPC',
url: 'https://arb1.arbitrum.io/rpc',
},
}, },
tokens: { tokens: {
eth: { eth: {
@@ -496,23 +504,20 @@ export const defaultConfig: networkConfig = {
echoContract: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', echoContract: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4',
offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8', offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8',
tornadoSubgraph: 'tornadocash/xdai-tornado-subgraph', tornadoSubgraph: 'tornadocash/xdai-tornado-subgraph',
subgraphs: { subgraphs: {},
tornado,
theGraph,
},
rpcUrls: { rpcUrls: {
tornadoRPC: { gnosis: {
name: 'Tornado RPC', name: 'Gnosis',
url: 'https://tornadocash-rpc.com/gnosis', url: 'https://rpc.gnosischain.com',
},
chainnodes: {
name: 'Chainnodes RPC',
url: 'https://gnosis-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607',
}, },
blockPi: { blockPi: {
name: 'BlockPi', name: 'BlockPi',
url: 'https://gnosis.blockpi.network/v1/rpc/public', url: 'https://gnosis.blockpi.network/v1/rpc/public',
}, },
oneRpc: {
name: '1rpc',
url: 'https://1rpc.io/gnosis',
},
}, },
tokens: { tokens: {
xdai: { xdai: {
@@ -553,9 +558,7 @@ export const defaultConfig: networkConfig = {
echoContract: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', echoContract: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4',
offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8', offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8',
tornadoSubgraph: 'tornadocash/avalanche-tornado-subgraph', tornadoSubgraph: 'tornadocash/avalanche-tornado-subgraph',
subgraphs: { subgraphs: {},
theGraph,
},
rpcUrls: { rpcUrls: {
publicRpc: { publicRpc: {
name: 'Avalanche RPC', name: 'Avalanche RPC',
@@ -613,21 +616,23 @@ export const defaultConfig: networkConfig = {
aggregatorContract: '0x4088712AC9fad39ea133cdb9130E465d235e9642', aggregatorContract: '0x4088712AC9fad39ea133cdb9130E465d235e9642',
reverseRecordsContract: '0xEc29700C0283e5Be64AcdFe8077d6cC95dE23C23', reverseRecordsContract: '0xEc29700C0283e5Be64AcdFe8077d6cC95dE23C23',
tornadoSubgraph: 'tornadocash/sepolia-tornado-subgraph', tornadoSubgraph: 'tornadocash/sepolia-tornado-subgraph',
subgraphs: { subgraphs: {},
tornado,
},
rpcUrls: { rpcUrls: {
tornadoRPC: {
name: 'Tornado RPC',
url: 'https://tornadocash-rpc.com/sepolia',
},
sepolia: { sepolia: {
name: 'Sepolia RPC', name: 'Sepolia RPC',
url: 'https://rpc.sepolia.org', url: 'https://rpc.sepolia.org',
}, },
chainnodes: { stackup: {
name: 'Chainnodes RPC', name: 'Stackup',
url: 'https://sepolia.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607', url: 'https://public.stackup.sh/api/v1/node/ethereum-sepolia',
},
onerpc: {
name: '1rpc',
url: 'https://1rpc.io/sepolia',
},
ethpandaops: {
name: 'ethpandaops',
url: 'https://rpc.sepolia.ethpandaops.io',
}, },
}, },
tokens: { tokens: {
@@ -720,10 +725,30 @@ export function getConfig(netId: NetIdType) {
return chainConfig; return chainConfig;
} }
export function getInstanceByAddress({ netId, address }: { netId: NetIdType; address: string }) { export function getActiveTokens(config: Config): string[] {
const { tokens } = getConfig(netId); const { tokens, disabledTokens } = config;
return Object.keys(tokens).filter((t) => !disabledTokens?.includes(t));
}
export function getActiveTokenInstances(config: Config): TokenInstances {
const { tokens, disabledTokens } = config;
return Object.entries(tokens).reduce((acc, [token, instances]) => {
if (!disabledTokens?.includes(token)) {
acc[token] = instances;
}
return acc;
}, {} as TokenInstances);
}
export function getInstanceByAddress(config: Config, address: string) {
const { tokens, disabledTokens } = config;
for (const [currency, { instanceAddress }] of Object.entries(tokens)) { for (const [currency, { instanceAddress }] of Object.entries(tokens)) {
if (disabledTokens?.includes(currency)) {
continue;
}
for (const [amount, instance] of Object.entries(instanceAddress)) { for (const [amount, instance] of Object.entries(instanceAddress)) {
if (instance === address) { if (instance === address) {
return { return {
@@ -735,8 +760,11 @@ export function getInstanceByAddress({ netId, address }: { netId: NetIdType; add
} }
} }
export function getSubdomains() { export function getRelayerEnsSubdomains() {
const allConfig = getNetworkConfig(); const allConfig = getNetworkConfig();
return enabledChains.map((chain) => allConfig[chain].relayerEnsSubdomain); return enabledChains.reduce((acc, chain) => {
acc[chain] = allConfig[chain].relayerEnsSubdomain;
return acc;
}, {} as SubdomainMap);
} }

View File

@@ -17,17 +17,14 @@ import {
Eip1193Provider, Eip1193Provider,
VoidSigner, VoidSigner,
Network, Network,
parseUnits,
FetchUrlFeeDataNetworkPlugin,
FeeData,
EnsPlugin, EnsPlugin,
GasCostPlugin, GasCostPlugin,
} from 'ethers'; } from 'ethers';
import type { RequestInfo, RequestInit, Response, HeadersInit } from 'node-fetch'; import type { RequestInfo, RequestInit, Response, HeadersInit } from 'node-fetch';
import { GasPriceOracle, GasPriceOracle__factory, Multicall, Multicall__factory } from './typechain'; // Temporary workaround until @types/node-fetch is compatible with @types/node
import type { AbortSignal as FetchAbortSignal } from 'node-fetch/externals';
import { isNode, sleep } from './utils'; import { isNode, sleep } from './utils';
import type { Config, NetIdType } from './networkConfig'; import type { Config, NetIdType } from './networkConfig';
import { multicall } from './multicall';
declare global { declare global {
interface Window { interface Window {
@@ -51,7 +48,7 @@ export type fetchDataOptions = RequestInit & {
timeout?: number; timeout?: number;
proxy?: string; proxy?: string;
torPort?: number; torPort?: number;
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
debug?: Function; debug?: Function;
returnResponse?: boolean; returnResponse?: boolean;
}; };
@@ -69,11 +66,11 @@ export function getHttpAgent({
torPort?: number; torPort?: number;
retry: number; retry: number;
}): NodeAgent | undefined { }): NodeAgent | undefined {
/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-require-imports */
const { HttpProxyAgent } = require('http-proxy-agent'); const { HttpProxyAgent } = require('http-proxy-agent');
const { HttpsProxyAgent } = require('https-proxy-agent'); const { HttpsProxyAgent } = require('https-proxy-agent');
const { SocksProxyAgent } = require('socks-proxy-agent'); const { SocksProxyAgent } = require('socks-proxy-agent');
/* eslint-enable @typescript-eslint/no-var-requires */ /* eslint-enable @typescript-eslint/no-require-imports */
if (torPort) { if (torPort) {
return new SocksProxyAgent(`socks5h://tor${retry}@127.0.0.1:${torPort}`); return new SocksProxyAgent(`socks5h://tor${retry}@127.0.0.1:${torPort}`);
@@ -128,7 +125,8 @@ export async function fetchData(url: string, options: fetchDataOptions = {}) {
if (!options.signal && options.timeout) { if (!options.signal && options.timeout) {
const controller = new AbortController(); const controller = new AbortController();
options.signal = controller.signal; // Temporary workaround until @types/node-fetch is compatible with @types/node
options.signal = controller.signal as FetchAbortSignal;
// Define timeout in seconds // Define timeout in seconds
timeout = setTimeout(() => { timeout = setTimeout(() => {
@@ -223,7 +221,8 @@ export const fetchGetUrlFunc =
if (_signal) { if (_signal) {
const controller = new AbortController(); const controller = new AbortController();
signal = controller.signal; // Temporary workaround until @types/node-fetch is compatible with @types/node
signal = controller.signal as FetchAbortSignal;
_signal.addListener(() => { _signal.addListener(() => {
controller.abort(); controller.abort();
}); });
@@ -257,114 +256,30 @@ export const fetchGetUrlFunc =
}; };
/* eslint-enable @typescript-eslint/no-explicit-any */ /* eslint-enable @typescript-eslint/no-explicit-any */
// caching to improve performance
const oracleMapper = new Map();
const multicallMapper = new Map();
export type getProviderOptions = fetchDataOptions & { export type getProviderOptions = fetchDataOptions & {
// NetId to check against rpc
netId?: NetIdType;
pollingInterval?: number; pollingInterval?: number;
gasPriceOracle?: string;
gasStationApi?: string;
}; };
export function getGasOraclePlugin(networkKey: string, fetchOptions?: getProviderOptions) {
const gasStationApi = fetchOptions?.gasStationApi || 'https://gasstation.polygon.technology/v2';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
return new FetchUrlFeeDataNetworkPlugin(gasStationApi, async (fetchFeeData, provider, request) => {
if (!oracleMapper.has(networkKey)) {
oracleMapper.set(networkKey, GasPriceOracle__factory.connect(fetchOptions?.gasPriceOracle as string, provider));
}
if (!multicallMapper.has(networkKey)) {
multicallMapper.set(
networkKey,
Multicall__factory.connect('0xcA11bde05977b3631167028862bE2a173976CA11', provider),
);
}
const Oracle = oracleMapper.get(networkKey) as GasPriceOracle;
const Multicall = multicallMapper.get(networkKey) as Multicall;
const [timestamp, heartbeat, feePerGas, priorityFeePerGas] = await multicall(Multicall, [
{
contract: Oracle,
name: 'timestamp',
},
{
contract: Oracle,
name: 'heartbeat',
},
{
contract: Oracle,
name: 'maxFeePerGas',
},
{
contract: Oracle,
name: 'maxPriorityFeePerGas',
},
]);
const isOutdated = Number(timestamp) <= Date.now() / 1000 - Number(heartbeat);
if (!isOutdated) {
const maxPriorityFeePerGas = (priorityFeePerGas * BigInt(13)) / BigInt(10);
const maxFeePerGas = feePerGas * BigInt(2) + maxPriorityFeePerGas;
return {
gasPrice: maxFeePerGas,
maxFeePerGas,
maxPriorityFeePerGas,
};
}
const fetchReq = new FetchRequest(gasStationApi);
fetchReq.getUrlFunc = fetchGetUrlFunc(fetchOptions);
if (isNode) {
// Prevent Cloudflare from blocking our request in node.js
fetchReq.setHeader('User-Agent', 'ethers');
}
const [
{
bodyJson: { fast },
},
{ gasPrice },
] = await Promise.all([fetchReq.send(), fetchFeeData()]);
return {
gasPrice,
maxFeePerGas: parseUnits(`${fast.maxFee}`, 9),
maxPriorityFeePerGas: parseUnits(`${fast.maxPriorityFee}`, 9),
};
});
}
export async function getProvider(rpcUrl: string, fetchOptions?: getProviderOptions): Promise<JsonRpcProvider> { export async function getProvider(rpcUrl: string, fetchOptions?: getProviderOptions): Promise<JsonRpcProvider> {
const fetchReq = new FetchRequest(rpcUrl); const fetchReq = new FetchRequest(rpcUrl);
fetchReq.getUrlFunc = fetchGetUrlFunc(fetchOptions); fetchReq.getUrlFunc = fetchGetUrlFunc(fetchOptions);
// omit network plugins and mimic registerEth function (required for polygon)
const _staticNetwork = await new JsonRpcProvider(fetchReq).getNetwork(); const staticNetwork = await new JsonRpcProvider(fetchReq).getNetwork();
const ensPlugin = _staticNetwork.getPlugin('org.ethers.plugins.network.Ens');
const gasCostPlugin = _staticNetwork.getPlugin('org.ethers.plugins.network.GasCost'); const chainId = Number(staticNetwork.chainId);
const gasStationPlugin = <FetchUrlFeeDataNetworkPlugin>(
_staticNetwork.getPlugin('org.ethers.plugins.network.FetchUrlFeeDataPlugin') if (fetchOptions?.netId && fetchOptions.netId !== chainId) {
); const errMsg = `Wrong network for ${rpcUrl}, wants ${fetchOptions.netId} got ${chainId}`;
const staticNetwork = new Network(_staticNetwork.name, _staticNetwork.chainId); throw new Error(errMsg);
if (ensPlugin) {
staticNetwork.attachPlugin(ensPlugin);
} }
if (gasCostPlugin) {
staticNetwork.attachPlugin(gasCostPlugin); return new JsonRpcProvider(fetchReq, staticNetwork, {
}
if (fetchOptions?.gasPriceOracle) {
staticNetwork.attachPlugin(getGasOraclePlugin(`${_staticNetwork.chainId}_${rpcUrl}`, fetchOptions));
} else if (gasStationPlugin) {
staticNetwork.attachPlugin(gasStationPlugin);
}
const provider = new JsonRpcProvider(fetchReq, staticNetwork, {
staticNetwork, staticNetwork,
pollingInterval: fetchOptions?.pollingInterval || 1000,
}); });
provider.pollingInterval = fetchOptions?.pollingInterval || 1000;
return provider;
} }
export function getProviderWithNetId( export function getProviderWithNetId(
@@ -373,7 +288,7 @@ export function getProviderWithNetId(
config: Config, config: Config,
fetchOptions?: getProviderOptions, fetchOptions?: getProviderOptions,
): JsonRpcProvider { ): JsonRpcProvider {
const { networkName, reverseRecordsContract, gasPriceOracleContract, gasStationApi, pollInterval } = config; const { networkName, reverseRecordsContract, pollInterval } = config;
const hasEns = Boolean(reverseRecordsContract); const hasEns = Boolean(reverseRecordsContract);
const fetchReq = new FetchRequest(rpcUrl); const fetchReq = new FetchRequest(rpcUrl);
@@ -382,24 +297,13 @@ export function getProviderWithNetId(
if (hasEns) { if (hasEns) {
staticNetwork.attachPlugin(new EnsPlugin(null, Number(netId))); staticNetwork.attachPlugin(new EnsPlugin(null, Number(netId)));
} }
staticNetwork.attachPlugin(new GasCostPlugin()); staticNetwork.attachPlugin(new GasCostPlugin());
if (gasPriceOracleContract) {
staticNetwork.attachPlugin(
getGasOraclePlugin(`${netId}_${rpcUrl}`, {
gasPriceOracle: gasPriceOracleContract,
gasStationApi,
}),
);
}
const provider = new JsonRpcProvider(fetchReq, staticNetwork, { const provider = new JsonRpcProvider(fetchReq, staticNetwork, {
staticNetwork, staticNetwork,
pollingInterval: fetchOptions?.pollingInterval || pollInterval * 1000,
}); });
provider.pollingInterval = fetchOptions?.pollingInterval || pollInterval * 1000;
return provider; return provider;
} }
@@ -417,87 +321,52 @@ export const populateTransaction = async (
} }
const [feeData, nonce] = await Promise.all([ const [feeData, nonce] = await Promise.all([
(async () => { tx.maxFeePerGas || tx.gasPrice ? undefined : provider.getFeeData(),
if (tx.maxFeePerGas && tx.maxPriorityFeePerGas) { tx.nonce ? undefined : provider.getTransactionCount(signer.address, 'pending'),
return new FeeData(null, BigInt(tx.maxFeePerGas), BigInt(tx.maxPriorityFeePerGas));
}
if (tx.gasPrice) {
return new FeeData(BigInt(tx.gasPrice), null, null);
}
const fetchedFeeData = await provider.getFeeData();
if (fetchedFeeData.maxFeePerGas && fetchedFeeData.maxPriorityFeePerGas) {
return new FeeData(
null,
(fetchedFeeData.maxFeePerGas * (BigInt(10000) + BigInt(signer.gasPriceBump))) / BigInt(10000),
fetchedFeeData.maxPriorityFeePerGas,
);
} else {
return new FeeData(
((fetchedFeeData.gasPrice as bigint) * (BigInt(10000) + BigInt(signer.gasPriceBump))) / BigInt(10000),
null,
null,
);
}
})(),
(async () => {
if (tx.nonce) {
return tx.nonce;
}
let fetchedNonce = await provider.getTransactionCount(signer.address, 'pending');
// Deal with cached nonce results
if (signer.bumpNonce && signer.nonce && signer.nonce >= fetchedNonce) {
console.log(
`populateTransaction: bumping nonce from ${fetchedNonce} to ${fetchedNonce + 1} for ${signer.address}`,
);
fetchedNonce++;
}
return fetchedNonce;
})(),
]); ]);
tx.nonce = nonce; if (feeData) {
// EIP-1559
if (feeData.maxFeePerGas) {
if (!tx.type) {
tx.type = 2;
}
// EIP-1559 tx.maxFeePerGas = (feeData.maxFeePerGas * (BigInt(10000) + BigInt(signer.gasPriceBump))) / BigInt(10000);
if (feeData.maxFeePerGas && feeData.maxPriorityFeePerGas) { tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
tx.maxFeePerGas = feeData.maxFeePerGas; delete tx.gasPrice;
tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas; } else if (feeData.gasPrice) {
if (!tx.type) { if (!tx.type) {
tx.type = 2; tx.type = 0;
}
tx.gasPrice = feeData.gasPrice;
delete tx.maxFeePerGas;
delete tx.maxPriorityFeePerGas;
} }
delete tx.gasPrice;
} else if (feeData.gasPrice) {
tx.gasPrice = feeData.gasPrice;
if (!tx.type) {
tx.type = 0;
}
delete tx.maxFeePerGas;
delete tx.maxPriorityFeePerGas;
} }
// gasLimit if (nonce) {
tx.gasLimit = tx.nonce = nonce;
tx.gasLimit || }
(await (async () => {
try { if (!tx.gasLimit) {
const gasLimit = await provider.estimateGas(tx); try {
return gasLimit === BigInt(21000) const gasLimit = await provider.estimateGas(tx);
tx.gasLimit =
gasLimit === BigInt(21000)
? gasLimit ? gasLimit
: (gasLimit * (BigInt(10000) + BigInt(signer.gasLimitBump))) / BigInt(10000); : (gasLimit * (BigInt(10000) + BigInt(signer.gasLimitBump))) / BigInt(10000);
} catch (err) { } catch (error) {
if (signer.gasFailover) { if (signer.gasFailover) {
console.log('populateTransaction: warning gas estimation failed falling back to 3M gas'); console.log('populateTransaction: warning gas estimation failed falling back to 3M gas');
// Gas failover // Gas failover
return BigInt('3000000'); tx.gasLimit = BigInt('3000000');
} } else {
throw err; throw error;
} }
})()); }
}
return tx; return tx;
}; };
@@ -522,7 +391,7 @@ export class TornadoWallet extends Wallet {
) { ) {
super(key, provider); super(key, provider);
// 10% bump from the recommended fee // 10% bump from the recommended fee
this.gasPriceBump = gasPriceBump ?? 1000; this.gasPriceBump = gasPriceBump ?? 0;
// 30% bump from the recommended gaslimit // 30% bump from the recommended gaslimit
this.gasLimitBump = gasLimitBump ?? 3000; this.gasLimitBump = gasLimitBump ?? 3000;
this.gasFailover = gasFailover ?? false; this.gasFailover = gasFailover ?? false;
@@ -557,7 +426,7 @@ export class TornadoVoidSigner extends VoidSigner {
) { ) {
super(address, provider); super(address, provider);
// 10% bump from the recommended fee // 10% bump from the recommended fee
this.gasPriceBump = gasPriceBump ?? 1000; this.gasPriceBump = gasPriceBump ?? 0;
// 30% bump from the recommended gaslimit // 30% bump from the recommended gaslimit
this.gasLimitBump = gasLimitBump ?? 3000; this.gasLimitBump = gasLimitBump ?? 3000;
this.gasFailover = gasFailover ?? false; this.gasFailover = gasFailover ?? false;
@@ -586,7 +455,7 @@ export class TornadoRpcSigner extends JsonRpcSigner {
) { ) {
super(provider, address); super(provider, address);
// 10% bump from the recommended fee // 10% bump from the recommended fee
this.gasPriceBump = gasPriceBump ?? 1000; this.gasPriceBump = gasPriceBump ?? 0;
// 30% bump from the recommended gaslimit // 30% bump from the recommended gaslimit
this.gasLimitBump = gasLimitBump ?? 3000; this.gasLimitBump = gasLimitBump ?? 3000;
this.gasFailover = gasFailover ?? false; this.gasFailover = gasFailover ?? false;

View File

@@ -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 type { CachedRelayerInfo } from './events';
export const MIN_FEE = 0.1;
export const MAX_FEE = 0.9;
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 {
@@ -73,6 +75,10 @@ export interface RelayerStatus {
currentQueue: number; currentQueue: number;
} }
export type TornadoWithdrawParams = snarkProofs & {
contract: string;
};
export interface RelayerTornadoWithdraw { export interface RelayerTornadoWithdraw {
id?: string; id?: string;
error?: string; error?: string;
@@ -91,6 +97,7 @@ export interface RelayerTornadoJobs {
failedReason?: string; failedReason?: string;
} }
/**
const semVerRegex = const semVerRegex =
/^(?<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-]+)*))?$/; /^(?<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-]+)*))?$/;
@@ -116,19 +123,20 @@ export function isRelayerUpdated(relayerVersion: string, netId: NetIdType) {
if (prerelease) return false; if (prerelease) return false;
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 +167,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,36 +182,41 @@ 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;
} }
export type RelayerClientWithdraw = snarkProofs & {
contract: string;
};
export class RelayerClient { export class RelayerClient {
netId: NetIdType; netId: NetIdType;
config: Config; config: Config;
Aggregator: Aggregator; selectedRelayer?: RelayerInfo;
selectedRelayer?: Relayer;
fetchDataOptions?: fetchDataOptions; fetchDataOptions?: fetchDataOptions;
tovarish: boolean;
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;
this.tovarish = false;
} }
async askRelayerStatus({ async askRelayerStatus({
hostname, hostname,
url,
relayerAddress, relayerAddress,
}: { }: {
hostname: string; hostname?: string;
// optional url if entered manually
url?: string;
// relayerAddress from registry contract to prevent cheating
relayerAddress?: string; relayerAddress?: string;
}): Promise<RelayerStatus> { }): Promise<RelayerStatus> {
const url = `https://${!hostname.endsWith('/') ? hostname + '/' : hostname}`; if (!url && hostname) {
url = `https://${!hostname.endsWith('/') ? hostname + '/' : hostname}`;
} else if (url && !url.endsWith('/')) {
url += '/';
} else {
url = '';
}
const rawStatus = (await fetchData(`${url}status`, { const rawStatus = (await fetchData(`${url}status`, {
...this.fetchDataOptions, ...this.fetchDataOptions,
@@ -221,7 +227,7 @@ export class RelayerClient {
maxRetry: this.fetchDataOptions?.torPort ? 2 : 0, maxRetry: this.fetchDataOptions?.torPort ? 2 : 0,
})) as object; })) as object;
const statusValidator = ajv.compile(getStatusSchema(this.netId, this.config)); const statusValidator = ajv.compile(getStatusSchema(this.netId, this.config, this.tovarish));
if (!statusValidator(rawStatus)) { if (!statusValidator(rawStatus)) {
throw new Error('Invalid status schema'); throw new Error('Invalid status schema');
@@ -244,108 +250,57 @@ export class RelayerClient {
throw new Error('The Relayer reward address must match registered address'); throw new Error('The Relayer reward address must match registered address');
} }
if (!isRelayerUpdated(status.version, this.netId)) {
throw new Error('Outdated version.');
}
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 +313,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 }: TornadoWithdrawParams) {
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,

View File

@@ -10,7 +10,7 @@ ajv.addKeyword({
try { try {
BigInt(data); BigInt(data);
return true; return true;
} catch (e) { } catch {
return false; return false;
} }
}, },

View File

@@ -70,6 +70,16 @@ export type statusSchema = {
}; };
required: string[]; required: string[];
}; };
syncStatus: {
type: string;
properties: {
events: { type: string };
tokenPrice: { type: string };
gasPrice: { type: string };
};
required: string[];
};
onSyncEvents: { type: string };
currentQueue: { currentQueue: {
type: string; type: string;
}; };
@@ -105,13 +115,23 @@ const statusSchema: statusSchema = {
}, },
required: ['status'], required: ['status'],
}, },
syncStatus: {
type: 'object',
properties: {
events: { type: 'boolean' },
tokenPrice: { type: 'boolean' },
gasPrice: { type: 'boolean' },
},
required: ['events', 'tokenPrice', 'gasPrice'],
},
onSyncEvents: { type: 'boolean' },
currentQueue: { type: 'number' }, currentQueue: { type: 'number' },
}, },
required: ['rewardAccount', 'instances', 'netId', 'tornadoServiceFee', 'version', 'health'], required: ['rewardAccount', 'instances', 'netId', 'tornadoServiceFee', 'version', 'health', 'currentQueue'],
}; };
export function getStatusSchema(netId: NetIdType, config: Config) { export function getStatusSchema(netId: NetIdType, config: Config, tovarish: boolean) {
const { tokens, optionalTokens = [], nativeCurrency } = config; const { tokens, optionalTokens, disabledTokens, nativeCurrency } = config;
// deep copy schema // deep copy schema
const schema = JSON.parse(JSON.stringify(statusSchema)) as statusSchema; const schema = JSON.parse(JSON.stringify(statusSchema)) as statusSchema;
@@ -148,7 +168,7 @@ export function getStatusSchema(netId: NetIdType, config: Config) {
} }
acc.properties[token] = instanceProperties; acc.properties[token] = instanceProperties;
if (!optionalTokens.includes(token)) { if (!optionalTokens?.includes(token) && !disabledTokens?.includes(token)) {
acc.required.push(token); acc.required.push(token);
} }
return acc; return acc;
@@ -162,19 +182,29 @@ export function getStatusSchema(netId: NetIdType, config: Config) {
schema.properties.instances = instances; schema.properties.instances = instances;
if (netId === NetId.MAINNET) { const _tokens = Object.keys(tokens).filter(
const _tokens = Object.keys(tokens).filter((t) => t !== nativeCurrency); (t) => t !== nativeCurrency && !config.optionalTokens?.includes(t) && !config.disabledTokens?.includes(t),
);
if (netId === NetId.MAINNET) {
_tokens.push('torn');
}
if (_tokens.length) {
const ethPrices: statusEthPricesType = { const ethPrices: statusEthPricesType = {
type: 'object', type: 'object',
properties: _tokens.reduce((acc: { [key in string]: typeof bnType }, token: string) => { properties: _tokens.reduce((acc: { [key in string]: typeof bnType }, token: string) => {
acc[token] = bnType; acc[token] = bnType;
return acc; return acc;
}, {}), }, {}),
// required: _tokens required: _tokens,
}; };
schema.properties.ethPrices = ethPrices; schema.properties.ethPrices = ethPrices;
// schema.required.push('ethPrices') schema.required.push('ethPrices');
}
if (tovarish) {
schema.required.push('gasPrices', 'latestBlock', 'syncStatus', 'onSyncEvents');
} }
return schema; return schema;

242
src/tovarishClient.ts Normal file
View File

@@ -0,0 +1,242 @@
import { getAddress } from 'ethers';
import {
RelayerClient,
RelayerClientConstructor,
RelayerError,
RelayerInfo,
RelayerStatus,
getSupportedInstances,
} from './relayerClient';
import { fetchData } from './providers';
import { CachedRelayerInfo, MinimalEvents } from './events';
// Return no more than 5K events per query
export const MAX_TOVARISH_EVENTS = 5000;
export interface EventsStatus {
events: number;
lastBlock: number;
}
export interface InstanceEventsStatus {
[index: string]: {
deposits: EventsStatus;
withdrawals: EventsStatus;
};
}
export interface CurrencyEventsStatus {
[index: string]: InstanceEventsStatus;
}
export interface TovarishEventsStatus {
governance?: EventsStatus;
registered?: {
lastBlock: number;
timestamp: number;
relayers: number;
};
echo: EventsStatus;
encrypted_notes: EventsStatus;
instances: CurrencyEventsStatus;
}
export interface TovarishSyncStatus {
events: boolean;
tokenPrice: boolean;
gasPrice: boolean;
}
// Expected response from /status endpoint
export interface TovarishStatus extends RelayerStatus {
events: TovarishEventsStatus;
syncStatus: TovarishSyncStatus;
onSyncEvents: boolean;
}
// Formatted TovarishStatus for Frontend usage
export interface TovarishInfo extends RelayerInfo {
latestBlock: number;
version: string;
events: TovarishEventsStatus;
syncStatus: TovarishSyncStatus;
}
// Query input for TovarishEvents
export interface TovarishEventsQuery {
type: string;
currency?: string;
amount?: string;
fromBlock: number;
recent?: boolean;
}
export interface BaseTovarishEvents<T> {
events: T[];
lastSyncBlock: number;
}
export class TovarishClient extends RelayerClient {
selectedRelayer?: TovarishInfo;
constructor({ netId, config, fetchDataOptions }: RelayerClientConstructor) {
super({ netId, config, fetchDataOptions });
this.tovarish = true;
}
async askRelayerStatus({
hostname,
url,
relayerAddress,
}: {
hostname?: string;
// optional url if entered manually
url?: string;
// relayerAddress from registry contract to prevent cheating
relayerAddress?: string;
}): Promise<TovarishStatus> {
const status = (await super.askRelayerStatus({ hostname, url, relayerAddress })) as TovarishStatus;
if (!status.version.includes('tovarish')) {
throw new Error('Not a tovarish relayer!');
}
return status;
}
async filterRelayer(relayer: CachedRelayerInfo): Promise<TovarishInfo | RelayerError | undefined> {
const { ensName, relayerAddress, tovarishHost: hostname, tovarishNetworks } = relayer;
if (!hostname || !tovarishNetworks?.includes(this.netId)) {
return;
}
try {
const status = await 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: status.gasPrices?.fast,
ethPrices: status.ethPrices,
currentQueue: status.currentQueue,
tornadoServiceFee: status.tornadoServiceFee,
// Additional fields for tovarish relayer
latestBlock: Number(status.latestBlock),
version: status.version,
events: status.events,
syncStatus: status.syncStatus,
} as TovarishInfo;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
return {
hostname,
relayerAddress,
errorMessage: err.message,
hasError: true,
} as RelayerError;
}
}
async getValidRelayers(relayers: CachedRelayerInfo[]): Promise<{
validRelayers: TovarishInfo[];
invalidRelayers: RelayerError[];
}> {
const invalidRelayers: RelayerError[] = [];
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;
}) as TovarishInfo[];
return {
validRelayers,
invalidRelayers,
};
}
async getEvents<T extends MinimalEvents>({
type,
currency,
amount,
fromBlock,
recent,
}: TovarishEventsQuery): Promise<BaseTovarishEvents<T>> {
const url = `${this.selectedRelayer?.url}events`;
try {
const events = [];
let lastSyncBlock = fromBlock;
// eslint-disable-next-line no-constant-condition
while (true) {
// eslint-disable-next-line prefer-const
let { events: fetchedEvents, lastSyncBlock: currentBlock } = (await fetchData(url, {
...this.fetchDataOptions,
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type,
currency,
amount,
fromBlock,
recent,
}),
})) as BaseTovarishEvents<T>;
lastSyncBlock = currentBlock;
if (!Array.isArray(fetchedEvents) || !fetchedEvents.length) {
break;
}
fetchedEvents = fetchedEvents.sort((a, b) => {
if (a.blockNumber === b.blockNumber) {
return a.logIndex - b.logIndex;
}
return a.blockNumber - b.blockNumber;
});
const [lastEvent] = fetchedEvents.slice(-1);
if (fetchedEvents.length < MAX_TOVARISH_EVENTS - 100) {
events.push(...fetchedEvents);
break;
}
fetchedEvents = fetchedEvents.filter((e) => e.blockNumber !== lastEvent.blockNumber);
fromBlock = Number(lastEvent.blockNumber);
events.push(...fetchedEvents);
}
return {
events,
lastSyncBlock,
};
} catch (err) {
console.log('Error from TovarishClient events endpoint');
console.log(err);
return {
events: [],
lastSyncBlock: fromBlock,
};
}
}
}

View File

@@ -3,7 +3,6 @@
/* eslint-disable */ /* eslint-disable */
export { ENS__factory } from "./ENS__factory"; export { ENS__factory } from "./ENS__factory";
export { ERC20__factory } from "./ERC20__factory"; export { ERC20__factory } from "./ERC20__factory";
export { GasPriceOracle__factory } from "./GasPriceOracle__factory";
export { Multicall__factory } from "./Multicall__factory"; export { Multicall__factory } from "./Multicall__factory";
export { OffchainOracle__factory } from "./OffchainOracle__factory"; export { OffchainOracle__factory } from "./OffchainOracle__factory";
export { OvmGasPriceOracle__factory } from "./OvmGasPriceOracle__factory"; export { OvmGasPriceOracle__factory } from "./OvmGasPriceOracle__factory";

View File

@@ -3,7 +3,6 @@
/* eslint-disable */ /* eslint-disable */
export type { ENS } from "./ENS"; export type { ENS } from "./ENS";
export type { ERC20 } from "./ERC20"; export type { ERC20 } from "./ERC20";
export type { GasPriceOracle } from "./GasPriceOracle";
export type { Multicall } from "./Multicall"; export type { Multicall } from "./Multicall";
export type { OffchainOracle } from "./OffchainOracle"; export type { OffchainOracle } from "./OffchainOracle";
export type { OvmGasPriceOracle } from "./OvmGasPriceOracle"; export type { OvmGasPriceOracle } from "./OvmGasPriceOracle";
@@ -11,7 +10,6 @@ export type { ReverseRecords } from "./ReverseRecords";
export * as factories from "./factories"; export * as factories from "./factories";
export { ENS__factory } from "./factories/ENS__factory"; export { ENS__factory } from "./factories/ENS__factory";
export { ERC20__factory } from "./factories/ERC20__factory"; export { ERC20__factory } from "./factories/ERC20__factory";
export { GasPriceOracle__factory } from "./factories/GasPriceOracle__factory";
export { Multicall__factory } from "./factories/Multicall__factory"; export { Multicall__factory } from "./factories/Multicall__factory";
export { OffchainOracle__factory } from "./factories/OffchainOracle__factory"; export { OffchainOracle__factory } from "./factories/OffchainOracle__factory";
export { OvmGasPriceOracle__factory } from "./factories/OvmGasPriceOracle__factory"; export { OvmGasPriceOracle__factory } from "./factories/OvmGasPriceOracle__factory";

View File

@@ -54,7 +54,7 @@ export function bufferToBytes(b: Buffer) {
} }
export function bytesToBase64(bytes: Uint8Array) { export function bytesToBase64(bytes: Uint8Array) {
return btoa(String.fromCharCode.apply(null, Array.from(bytes))); return btoa(bytes.reduce((data, byte) => data + String.fromCharCode(byte), ''));
} }
export function base64ToBytes(base64: string) { export function base64ToBytes(base64: string) {
@@ -143,3 +143,7 @@ export function substring(str: string, length: number = 10) {
return `${str.substring(0, length)}...${str.substring(str.length - length)}`; return `${str.substring(0, length)}...${str.substring(str.length - length)}`;
} }
export async function digest(bytes: Uint8Array, algo: string = 'SHA-384') {
return new Uint8Array(await crypto.subtle.digest(algo, bytes));
}

View File

@@ -3,15 +3,14 @@ import * as websnarkUtils from '@tornado/websnark/src/utils';
// @ts-expect-error no-websnark-types // @ts-expect-error no-websnark-types
import websnarkGroth from '@tornado/websnark/src/groth16'; import websnarkGroth from '@tornado/websnark/src/groth16';
import type { Element } from '@tornado/fixed-merkle-tree'; import type { Element } from '@tornado/fixed-merkle-tree';
import type { AddressLike, BytesLike, BigNumberish } from 'ethers';
import { toFixedHex } from './utils'; import { toFixedHex } from './utils';
export type snarkInputs = { export type snarkInputs = {
// Public snark inputs // Public snark inputs
root: Element; root: Element;
nullifierHex: string; nullifierHex: string;
recipient: AddressLike; recipient: string;
relayer: AddressLike; relayer: string;
fee: bigint; fee: bigint;
refund: bigint; refund: bigint;
@@ -23,16 +22,16 @@ export type snarkInputs = {
}; };
export type snarkArgs = [ export type snarkArgs = [
_root: BytesLike, _root: string,
_nullifierHash: BytesLike, _nullifierHash: string,
_recipient: AddressLike, _recipient: string,
_relayer: AddressLike, _relayer: string,
_fee: BigNumberish, _fee: string,
_refund: BigNumberish, _refund: string,
]; ];
export type snarkProofs = { export type snarkProofs = {
proof: BytesLike; proof: string;
args: snarkArgs; args: snarkArgs;
}; };
@@ -71,16 +70,16 @@ export async function calculateSnarkProof(
console.log('Start generating SNARK proof', snarkInput); console.log('Start generating SNARK proof', snarkInput);
console.time('SNARK proof time'); console.time('SNARK proof time');
const proofData = await websnarkUtils.genWitnessAndProve(await groth16, snarkInput, circuit, provingKey); const proofData = await websnarkUtils.genWitnessAndProve(await groth16, snarkInput, circuit, provingKey);
const proof = websnarkUtils.toSolidityInput(proofData).proof as BytesLike; const proof = websnarkUtils.toSolidityInput(proofData).proof;
console.timeEnd('SNARK proof time'); console.timeEnd('SNARK proof time');
const args = [ const args = [
toFixedHex(input.root, 32) as BytesLike, toFixedHex(input.root, 32),
toFixedHex(input.nullifierHex, 32) as BytesLike, toFixedHex(input.nullifierHex, 32),
input.recipient, input.recipient,
input.relayer, input.relayer,
toFixedHex(input.fee, 32) as BigNumberish, toFixedHex(input.fee, 32),
toFixedHex(input.refund, 32) as BigNumberish, toFixedHex(input.refund, 32),
] as snarkArgs; ] as snarkArgs;
return { proof, args }; return { proof, args };

View File

@@ -15,7 +15,7 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */ /* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ "target": "es2018", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */ // "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */

View File

@@ -1,4 +1,3 @@
const esbuild = require('esbuild');
const path = require('path'); const path = require('path');
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin'); const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
@@ -8,7 +7,6 @@ const esbuildLoader = {
options: { options: {
loader: 'ts', loader: 'ts',
target: 'es2016', target: 'es2016',
implementation: esbuild
} }
} }
@@ -31,7 +29,7 @@ module.exports = [
}, },
entry: './src/index.ts', entry: './src/index.ts',
output: { output: {
filename: 'index.umd.js', filename: 'tornado.umd.js',
path: path.resolve(__dirname, './dist'), path: path.resolve(__dirname, './dist'),
library: 'Tornado', library: 'Tornado',
libraryTarget: 'umd' libraryTarget: 'umd'
@@ -49,6 +47,28 @@ module.exports = [
minimize: false, minimize: false,
} }
}, },
{
mode: 'production',
module: {
rules: [esbuildLoader]
},
entry: './src/index.ts',
output: {
filename: 'tornado.umd.min.js',
path: path.resolve(__dirname, './dist'),
library: 'Tornado',
libraryTarget: 'umd'
},
plugins: [
new NodePolyfillPlugin(),
],
resolve: {
extensions: ['.tsx', '.ts', '.js'],
alias: {
...commonAlias,
}
},
},
{ {
mode: 'production', mode: 'production',
module: { module: {
@@ -72,5 +92,26 @@ module.exports = [
optimization: { optimization: {
minimize: false, minimize: false,
} }
},
{
mode: 'production',
module: {
rules: [esbuildLoader]
},
entry: './src/merkleTreeWorker.ts',
output: {
filename: 'merkleTreeWorker.umd.min.js',
path: path.resolve(__dirname, './dist'),
libraryTarget: 'umd'
},
plugins: [
new NodePolyfillPlugin(),
],
resolve: {
extensions: ['.tsx', '.ts', '.js'],
alias: {
...commonAlias,
}
},
} }
]; ];

1615
yarn.lock

File diff suppressed because it is too large Load Diff