Add BaseMultiTornadoService

This commit is contained in:
Tornado Contrib 2024-11-19 00:38:46 +00:00
parent b65998fca5
commit 6ac1025bc1
Signed by: tornadocontrib
GPG Key ID: 60B4DF1A076C64B1
10 changed files with 800 additions and 19 deletions

38
dist/events/base.d.ts vendored

@ -1,5 +1,6 @@
import { BaseContract, Provider, EventLog } from 'ethers';
import { Tornado, TornadoRouter, TornadoProxyLight, Governance, RelayerRegistry, Echoer, Aggregator } from '@tornado/contracts';
import type { MerkleTree } from '@tornado/fixed-merkle-tree';
import { BatchEventsService, BatchBlockService, BatchTransactionService, BatchEventOnProgress, BatchBlockOnProgress } from '../batch';
import { fetchDataOptions } from '../providers';
import { type NetIdType, type SubdomainMap } from '../networkConfig';
@ -7,7 +8,8 @@ import { RelayerParams } from '../relayerClient';
import type { TovarishClient } from '../tovarishClient';
import type { ReverseRecords } from '../typechain';
import type { MerkleTreeService } from '../merkleTree';
import type { BaseEvents, CachedEvents, MinimalEvents, DepositsEvents, WithdrawalsEvents, EncryptedNotesEvents, AllGovernanceEvents, GovernanceProposalCreatedEvents, GovernanceVotedEvents, EchoEvents, AllRelayerRegistryEvents, StakeBurnedEvents } from './types';
import type { DepositType } from '../deposits';
import type { BaseEvents, CachedEvents, MinimalEvents, DepositsEvents, WithdrawalsEvents, EncryptedNotesEvents, AllGovernanceEvents, GovernanceProposalCreatedEvents, GovernanceVotedEvents, EchoEvents, AllRelayerRegistryEvents, StakeBurnedEvents, MultiDepositEvents, MultiWithdrawalsEvents } from './types';
export interface BaseEventsServiceConstructor {
netId: NetIdType;
provider: Provider;
@ -56,7 +58,9 @@ export declare class BaseEventsService<EventType extends MinimalEvents> {
/**
* Handle saving events
*/
saveEvents({ events, lastBlock }: BaseEvents<EventType>): Promise<void>;
saveEvents({ events, newEvents, lastBlock }: BaseEvents<EventType> & {
newEvents: EventType[];
}): Promise<void>;
/**
* Trigger saving and receiving latest events
*/
@ -90,6 +94,36 @@ export declare class BaseTornadoService extends BaseEventsService<DepositsEvents
fromBlock: number;
}): Promise<BaseEvents<DepositsEvents | WithdrawalsEvents>>;
}
export interface BaseMultiTornadoServiceConstructor extends Omit<BaseEventsServiceConstructor, 'contract' | 'type'> {
instances: {
[key in string]: DepositType;
};
optionalTree?: boolean;
merkleTreeService?: MerkleTreeService;
}
export declare class BaseMultiTornadoService extends BaseEventsService<MultiDepositEvents | MultiWithdrawalsEvents> {
instances: {
[key in string]: DepositType;
};
optionalTree?: boolean;
merkleTreeService?: MerkleTreeService;
batchTransactionService: BatchTransactionService;
batchBlockService: BatchBlockService;
constructor(serviceConstructor: BaseMultiTornadoServiceConstructor);
getInstanceName(): string;
getTovarishType(): string;
updateEventProgress({ percentage, type, fromBlock, toBlock, count }: Parameters<BatchEventOnProgress>[0]): void;
formatEvents(events: EventLog[]): Promise<(MultiDepositEvents | MultiWithdrawalsEvents)[]>;
validateEvents<S>({ events, newEvents, }: BaseEvents<MultiDepositEvents | MultiWithdrawalsEvents> & {
newEvents: (MultiDepositEvents | MultiWithdrawalsEvents)[];
}): Promise<S>;
getEvents(instanceAddress: string): Promise<{
depositEvents: MultiDepositEvents[];
withdrawalEvents: MultiWithdrawalsEvents[];
tree: MerkleTree | undefined;
lastBlock: number;
}>;
}
export interface BaseEchoServiceConstructor extends Omit<BaseEventsServiceConstructor, 'contract' | 'type'> {
Echoer: Echoer;
}

@ -83,6 +83,15 @@ export interface WithdrawalsEvents extends MinimalEvents {
fee: string;
timestamp: number;
}
export interface BaseMultiTornadoEvents {
event: string;
instanceAddress: string;
}
export interface MultiDepositEvents extends BaseMultiTornadoEvents, DepositsEvents {
}
export interface MultiWithdrawalsEvents extends BaseMultiTornadoEvents, WithdrawalsEvents {
relayerAddress: string;
}
export interface EchoEvents extends MinimalEvents {
address: string;
encryptedAccount: string;

174
dist/index.js vendored

@ -1426,7 +1426,7 @@ function getActiveTokenInstances(config) {
}
function getInstanceByAddress(config, address) {
const { tokens, disabledTokens } = config;
for (const [currency, { instanceAddress }] of Object.entries(tokens)) {
for (const [currency, { instanceAddress, tokenAddress, symbol, decimals }] of Object.entries(tokens)) {
if (disabledTokens?.includes(currency)) {
continue;
}
@ -1434,7 +1434,10 @@ function getInstanceByAddress(config, address) {
if (instance === address) {
return {
amount,
currency
currency,
symbol,
decimals,
tokenAddress
};
}
}
@ -1447,6 +1450,21 @@ function getRelayerEnsSubdomains() {
return acc;
}, {});
}
function getMultiInstances(netId, config) {
return Object.entries(config.tokens).reduce(
(acc, [currency, { instanceAddress }]) => {
Object.entries(instanceAddress).forEach(([amount, contractAddress]) => {
acc[contractAddress] = {
currency,
amount,
netId
};
});
return acc;
},
{}
);
}
const ajv = new Ajv({ allErrors: true });
ajv.addKeyword({
@ -2226,7 +2244,7 @@ class BaseEventsService {
* Handle saving events
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async saveEvents({ events, lastBlock }) {
async saveEvents({ events, newEvents, lastBlock }) {
}
/**
* Trigger saving and receiving latest events
@ -2257,7 +2275,7 @@ class BaseEventsService {
lastBlock
});
if (savedEvents.fromCache || newEvents.events.length) {
await this.saveEvents({ events: allEvents, lastBlock });
await this.saveEvents({ events: allEvents, newEvents: newEvents.events, lastBlock });
}
return {
events: allEvents,
@ -2368,6 +2386,152 @@ class BaseTornadoService extends BaseEventsService {
});
}
}
class BaseMultiTornadoService extends BaseEventsService {
instances;
optionalTree;
merkleTreeService;
batchTransactionService;
batchBlockService;
constructor(serviceConstructor) {
const { instances, provider, optionalTree, merkleTreeService } = serviceConstructor;
const contract = merkleTreeService?.Tornado || contracts.Tornado__factory.connect(Object.keys(instances)[0], provider);
super({
...serviceConstructor,
contract,
type: "*"
});
this.batchEventsService = new BatchEventsService({
provider,
contract,
address: Object.keys(instances),
onProgress: this.updateEventProgress
});
this.instances = instances;
this.optionalTree = optionalTree;
this.merkleTreeService = merkleTreeService;
this.batchTransactionService = new BatchTransactionService({
provider,
onProgress: this.updateTransactionProgress
});
this.batchBlockService = new BatchBlockService({
provider,
onProgress: this.updateBlockProgress
});
}
getInstanceName() {
return `tornado_${this.netId}`;
}
getTovarishType() {
return "tornado";
}
updateEventProgress({ percentage, type, fromBlock, toBlock, count }) {
console.log({ percentage, type, fromBlock, toBlock, count });
}
async formatEvents(events) {
const txs = await this.batchTransactionService.getBatchTransactions([
...new Set(
events.filter(({ eventName }) => eventName === "Deposit").map(({ transactionHash }) => transactionHash)
)
]);
const blocks = await this.batchBlockService.getBatchBlocks([
...new Set(
events.filter(({ eventName }) => eventName === "Withdrawal").map(({ blockNumber }) => blockNumber)
)
]);
return events.map(
({
address: instanceAddress,
blockNumber,
index: logIndex,
transactionHash,
args,
eventName: event
}) => {
const eventObjects = {
blockNumber,
logIndex,
transactionHash,
event,
instanceAddress
};
if (event === "Deposit") {
const { commitment, leafIndex, timestamp } = args;
return {
...eventObjects,
commitment,
leafIndex: Number(leafIndex),
timestamp: Number(timestamp),
from: txs.find(({ hash }) => hash === transactionHash)?.from || ""
};
}
if (event === "Withdrawal") {
const { nullifierHash, to, relayer: relayerAddress, fee } = args;
return {
...eventObjects,
logIndex,
transactionHash,
nullifierHash: String(nullifierHash),
to,
relayerAddress,
fee: String(fee),
timestamp: blocks.find(({ number }) => number === blockNumber)?.timestamp || 0
};
}
}
).filter((e) => e);
}
async validateEvents({
events,
newEvents
}) {
const instancesWithNewEvents = [
...new Set(
newEvents.filter(({ event }) => event === "Deposit").map(({ instanceAddress }) => instanceAddress)
)
];
let tree;
const requiredTree = this.merkleTreeService?.Tornado?.target;
if (requiredTree && !instancesWithNewEvents.includes(requiredTree)) {
instancesWithNewEvents.push(requiredTree);
}
for (const instance of instancesWithNewEvents) {
const depositEvents = events.filter(
({ instanceAddress, event }) => instanceAddress === instance && event === "Deposit"
);
const lastEvent = depositEvents[depositEvents.length - 1];
if (lastEvent.leafIndex !== depositEvents.length - 1) {
const errMsg = `Invalid deposit events for ${instance} wants ${depositEvents.length - 1} leafIndex have ${lastEvent.leafIndex}`;
throw new Error(errMsg);
}
if (requiredTree === instance && !this.optionalTree) {
tree = await this.merkleTreeService?.verifyTree(depositEvents);
}
}
return tree;
}
async getEvents(instanceAddress) {
const { events, validateResult: tree, lastBlock } = await this.updateEvents();
const { depositEvents, withdrawalEvents } = events.reduce(
(acc, curr) => {
if (curr.instanceAddress === instanceAddress) {
if (curr.event === "Deposit") {
acc.depositEvents.push(curr);
} else if (curr.event === "Withdrawal") {
acc.withdrawalEvents.push(curr);
}
}
return acc;
},
{}
);
return {
depositEvents,
withdrawalEvents,
tree,
lastBlock
};
}
}
class BaseEchoService extends BaseEventsService {
constructor(serviceConstructor) {
super({
@ -10411,6 +10575,7 @@ exports.BaseEchoService = BaseEchoService;
exports.BaseEncryptedNotesService = BaseEncryptedNotesService;
exports.BaseEventsService = BaseEventsService;
exports.BaseGovernanceService = BaseGovernanceService;
exports.BaseMultiTornadoService = BaseMultiTornadoService;
exports.BaseRegistryService = BaseRegistryService;
exports.BaseRevenueService = BaseRevenueService;
exports.BaseTornadoService = BaseTornadoService;
@ -10502,6 +10667,7 @@ exports.getEventsSchemaValidator = getEventsSchemaValidator;
exports.getHttpAgent = getHttpAgent;
exports.getIndexedDB = getIndexedDB;
exports.getInstanceByAddress = getInstanceByAddress;
exports.getMultiInstances = getMultiInstances;
exports.getNetworkConfig = getNetworkConfig;
exports.getPermit2CommitmentsSignature = getPermit2CommitmentsSignature;
exports.getPermit2Signature = getPermit2Signature;

174
dist/index.mjs vendored

@ -1404,7 +1404,7 @@ function getActiveTokenInstances(config) {
}
function getInstanceByAddress(config, address) {
const { tokens, disabledTokens } = config;
for (const [currency, { instanceAddress }] of Object.entries(tokens)) {
for (const [currency, { instanceAddress, tokenAddress, symbol, decimals }] of Object.entries(tokens)) {
if (disabledTokens?.includes(currency)) {
continue;
}
@ -1412,7 +1412,10 @@ function getInstanceByAddress(config, address) {
if (instance === address) {
return {
amount,
currency
currency,
symbol,
decimals,
tokenAddress
};
}
}
@ -1425,6 +1428,21 @@ function getRelayerEnsSubdomains() {
return acc;
}, {});
}
function getMultiInstances(netId, config) {
return Object.entries(config.tokens).reduce(
(acc, [currency, { instanceAddress }]) => {
Object.entries(instanceAddress).forEach(([amount, contractAddress]) => {
acc[contractAddress] = {
currency,
amount,
netId
};
});
return acc;
},
{}
);
}
const ajv = new Ajv({ allErrors: true });
ajv.addKeyword({
@ -2204,7 +2222,7 @@ class BaseEventsService {
* Handle saving events
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async saveEvents({ events, lastBlock }) {
async saveEvents({ events, newEvents, lastBlock }) {
}
/**
* Trigger saving and receiving latest events
@ -2235,7 +2253,7 @@ class BaseEventsService {
lastBlock
});
if (savedEvents.fromCache || newEvents.events.length) {
await this.saveEvents({ events: allEvents, lastBlock });
await this.saveEvents({ events: allEvents, newEvents: newEvents.events, lastBlock });
}
return {
events: allEvents,
@ -2346,6 +2364,152 @@ class BaseTornadoService extends BaseEventsService {
});
}
}
class BaseMultiTornadoService extends BaseEventsService {
instances;
optionalTree;
merkleTreeService;
batchTransactionService;
batchBlockService;
constructor(serviceConstructor) {
const { instances, provider, optionalTree, merkleTreeService } = serviceConstructor;
const contract = merkleTreeService?.Tornado || Tornado__factory.connect(Object.keys(instances)[0], provider);
super({
...serviceConstructor,
contract,
type: "*"
});
this.batchEventsService = new BatchEventsService({
provider,
contract,
address: Object.keys(instances),
onProgress: this.updateEventProgress
});
this.instances = instances;
this.optionalTree = optionalTree;
this.merkleTreeService = merkleTreeService;
this.batchTransactionService = new BatchTransactionService({
provider,
onProgress: this.updateTransactionProgress
});
this.batchBlockService = new BatchBlockService({
provider,
onProgress: this.updateBlockProgress
});
}
getInstanceName() {
return `tornado_${this.netId}`;
}
getTovarishType() {
return "tornado";
}
updateEventProgress({ percentage, type, fromBlock, toBlock, count }) {
console.log({ percentage, type, fromBlock, toBlock, count });
}
async formatEvents(events) {
const txs = await this.batchTransactionService.getBatchTransactions([
...new Set(
events.filter(({ eventName }) => eventName === "Deposit").map(({ transactionHash }) => transactionHash)
)
]);
const blocks = await this.batchBlockService.getBatchBlocks([
...new Set(
events.filter(({ eventName }) => eventName === "Withdrawal").map(({ blockNumber }) => blockNumber)
)
]);
return events.map(
({
address: instanceAddress,
blockNumber,
index: logIndex,
transactionHash,
args,
eventName: event
}) => {
const eventObjects = {
blockNumber,
logIndex,
transactionHash,
event,
instanceAddress
};
if (event === "Deposit") {
const { commitment, leafIndex, timestamp } = args;
return {
...eventObjects,
commitment,
leafIndex: Number(leafIndex),
timestamp: Number(timestamp),
from: txs.find(({ hash }) => hash === transactionHash)?.from || ""
};
}
if (event === "Withdrawal") {
const { nullifierHash, to, relayer: relayerAddress, fee } = args;
return {
...eventObjects,
logIndex,
transactionHash,
nullifierHash: String(nullifierHash),
to,
relayerAddress,
fee: String(fee),
timestamp: blocks.find(({ number }) => number === blockNumber)?.timestamp || 0
};
}
}
).filter((e) => e);
}
async validateEvents({
events,
newEvents
}) {
const instancesWithNewEvents = [
...new Set(
newEvents.filter(({ event }) => event === "Deposit").map(({ instanceAddress }) => instanceAddress)
)
];
let tree;
const requiredTree = this.merkleTreeService?.Tornado?.target;
if (requiredTree && !instancesWithNewEvents.includes(requiredTree)) {
instancesWithNewEvents.push(requiredTree);
}
for (const instance of instancesWithNewEvents) {
const depositEvents = events.filter(
({ instanceAddress, event }) => instanceAddress === instance && event === "Deposit"
);
const lastEvent = depositEvents[depositEvents.length - 1];
if (lastEvent.leafIndex !== depositEvents.length - 1) {
const errMsg = `Invalid deposit events for ${instance} wants ${depositEvents.length - 1} leafIndex have ${lastEvent.leafIndex}`;
throw new Error(errMsg);
}
if (requiredTree === instance && !this.optionalTree) {
tree = await this.merkleTreeService?.verifyTree(depositEvents);
}
}
return tree;
}
async getEvents(instanceAddress) {
const { events, validateResult: tree, lastBlock } = await this.updateEvents();
const { depositEvents, withdrawalEvents } = events.reduce(
(acc, curr) => {
if (curr.instanceAddress === instanceAddress) {
if (curr.event === "Deposit") {
acc.depositEvents.push(curr);
} else if (curr.event === "Withdrawal") {
acc.withdrawalEvents.push(curr);
}
}
return acc;
},
{}
);
return {
depositEvents,
withdrawalEvents,
tree,
lastBlock
};
}
}
class BaseEchoService extends BaseEventsService {
constructor(serviceConstructor) {
super({
@ -10385,4 +10549,4 @@ async function calculateSnarkProof(input, circuit, provingKey) {
return { proof, args };
}
export { BaseEchoService, BaseEncryptedNotesService, BaseEventsService, BaseGovernanceService, BaseRegistryService, BaseRevenueService, BaseTornadoService, BatchBlockService, BatchEventsService, BatchTransactionService, DBEchoService, DBEncryptedNotesService, DBGovernanceService, DBRegistryService, DBRevenueService, DBTornadoService, Deposit, ENSNameWrapper__factory, ENSRegistry__factory, ENSResolver__factory, ENSUtils, ENS__factory, ERC20__factory, EnsContracts, INDEX_DB_ERROR, IndexedDB, Invoice, MAX_FEE, MAX_TOVARISH_EVENTS, MIN_FEE, MIN_STAKE_BALANCE, MerkleTreeService, Mimc, Multicall__factory, NetId, NoteAccount, OffchainOracle__factory, OvmGasPriceOracle__factory, Pedersen, RelayerClient, ReverseRecords__factory, TokenPriceOracle, TornadoBrowserProvider, TornadoFeeOracle, TornadoRpcSigner, TornadoVoidSigner, TornadoWallet, TovarishClient, addNetwork, addressSchemaType, ajv, base64ToBytes, bigIntReplacer, bnSchemaType, bnToBytes, buffPedersenHash, bufferToBytes, bytes32BNSchemaType, bytes32SchemaType, bytesToBN, bytesToBase64, bytesToHex, calculateScore, calculateSnarkProof, chunk, concatBytes, convertETHToTokenAmount, createDeposit, crypto, customConfig, defaultConfig, defaultUserAgent, deployHasher, depositsEventsSchema, digest, downloadZip, echoEventsSchema, enabledChains, encodedLabelToLabelhash, encryptedNotesSchema, index as factories, fetchData, fetchGetUrlFunc, fetchIp, fromContentHash, gasZipID, gasZipInbounds, gasZipInput, gasZipMinMax, getActiveTokenInstances, getActiveTokens, getConfig, getEventsSchemaValidator, getHttpAgent, getIndexedDB, getInstanceByAddress, getNetworkConfig, getPermit2CommitmentsSignature, getPermit2Signature, getPermitCommitmentsSignature, getPermitSignature, getProvider, getProviderWithNetId, getRelayerEnsSubdomains, getStatusSchema, getSubInfo, getSupportedInstances, getTokenBalances, getTovarishNetworks, getWeightRandom, governanceEventsSchema, hasherBytecode, hexToBytes, initGroth16, isHex, isNode, jobRequestSchema, jobsSchema, labelhash, leBuff2Int, leInt2Buff, loadDBEvents, loadRemoteEvents, makeLabelNodeAndParent, mimc, multiQueryFilter, multicall, numberFormatter, packEncryptedMessage, parseInvoice, parseNote, pedersen, permit2Address, pickWeightedRandomRelayer, populateTransaction, proofSchemaType, proposalState, rBigInt, rHex, relayerRegistryEventsSchema, saveDBEvents, sleep, stakeBurnedEventsSchema, substring, toContentHash, toFixedHex, toFixedLength, unpackEncryptedMessage, unzipAsync, validateUrl, withdrawalsEventsSchema, zipAsync };
export { BaseEchoService, BaseEncryptedNotesService, BaseEventsService, BaseGovernanceService, BaseMultiTornadoService, BaseRegistryService, BaseRevenueService, BaseTornadoService, BatchBlockService, BatchEventsService, BatchTransactionService, DBEchoService, DBEncryptedNotesService, DBGovernanceService, DBRegistryService, DBRevenueService, DBTornadoService, Deposit, ENSNameWrapper__factory, ENSRegistry__factory, ENSResolver__factory, ENSUtils, ENS__factory, ERC20__factory, EnsContracts, INDEX_DB_ERROR, IndexedDB, Invoice, MAX_FEE, MAX_TOVARISH_EVENTS, MIN_FEE, MIN_STAKE_BALANCE, MerkleTreeService, Mimc, Multicall__factory, NetId, NoteAccount, OffchainOracle__factory, OvmGasPriceOracle__factory, Pedersen, RelayerClient, ReverseRecords__factory, TokenPriceOracle, TornadoBrowserProvider, TornadoFeeOracle, TornadoRpcSigner, TornadoVoidSigner, TornadoWallet, TovarishClient, addNetwork, addressSchemaType, ajv, base64ToBytes, bigIntReplacer, bnSchemaType, bnToBytes, buffPedersenHash, bufferToBytes, bytes32BNSchemaType, bytes32SchemaType, bytesToBN, bytesToBase64, bytesToHex, calculateScore, calculateSnarkProof, chunk, concatBytes, convertETHToTokenAmount, createDeposit, crypto, customConfig, defaultConfig, defaultUserAgent, deployHasher, depositsEventsSchema, digest, downloadZip, echoEventsSchema, enabledChains, encodedLabelToLabelhash, encryptedNotesSchema, index as factories, fetchData, fetchGetUrlFunc, fetchIp, fromContentHash, gasZipID, gasZipInbounds, gasZipInput, gasZipMinMax, getActiveTokenInstances, getActiveTokens, getConfig, getEventsSchemaValidator, getHttpAgent, getIndexedDB, getInstanceByAddress, getMultiInstances, getNetworkConfig, getPermit2CommitmentsSignature, getPermit2Signature, getPermitCommitmentsSignature, getPermitSignature, getProvider, getProviderWithNetId, getRelayerEnsSubdomains, getStatusSchema, getSubInfo, getSupportedInstances, getTokenBalances, getTovarishNetworks, getWeightRandom, governanceEventsSchema, hasherBytecode, hexToBytes, initGroth16, isHex, isNode, jobRequestSchema, jobsSchema, labelhash, leBuff2Int, leInt2Buff, loadDBEvents, loadRemoteEvents, makeLabelNodeAndParent, mimc, multiQueryFilter, multicall, numberFormatter, packEncryptedMessage, parseInvoice, parseNote, pedersen, permit2Address, pickWeightedRandomRelayer, populateTransaction, proofSchemaType, proposalState, rBigInt, rHex, relayerRegistryEventsSchema, saveDBEvents, sleep, stakeBurnedEventsSchema, substring, toContentHash, toFixedHex, toFixedLength, unpackEncryptedMessage, unzipAsync, validateUrl, withdrawalsEventsSchema, zipAsync };

@ -1,3 +1,4 @@
import type { DepositType } from './deposits';
/**
* Type of default supported networks
*/
@ -114,5 +115,11 @@ export declare function getActiveTokenInstances(config: Config): TokenInstances;
export declare function getInstanceByAddress(config: Config, address: string): {
amount: string;
currency: string;
symbol: string;
decimals: number;
tokenAddress: string | undefined;
} | undefined;
export declare function getRelayerEnsSubdomains(): SubdomainMap;
export declare function getMultiInstances(netId: NetIdType, config: Config): {
[key in string]: DepositType;
};

176
dist/tornado.umd.js vendored

@ -60854,6 +60854,7 @@ __webpack_require__.d(__webpack_exports__, {
O_: () => (/* binding */ BaseEncryptedNotesService),
uw: () => (/* binding */ BaseEventsService),
JJ: () => (/* binding */ BaseGovernanceService),
lG: () => (/* binding */ BaseMultiTornadoService),
cE: () => (/* binding */ BaseRegistryService),
Do: () => (/* binding */ BaseRevenueService),
e0: () => (/* binding */ BaseTornadoService),
@ -90513,7 +90514,7 @@ class BaseEventsService {
* Handle saving events
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async saveEvents({ events, lastBlock }) {
async saveEvents({ events, newEvents, lastBlock }) {
}
/**
* Trigger saving and receiving latest events
@ -90544,7 +90545,7 @@ class BaseEventsService {
lastBlock
});
if (savedEvents.fromCache || newEvents.events.length) {
await this.saveEvents({ events: allEvents, lastBlock });
await this.saveEvents({ events: allEvents, newEvents: newEvents.events, lastBlock });
}
return {
events: allEvents,
@ -90655,6 +90656,152 @@ class BaseTornadoService extends BaseEventsService {
});
}
}
class BaseMultiTornadoService extends BaseEventsService {
instances;
optionalTree;
merkleTreeService;
batchTransactionService;
batchBlockService;
constructor(serviceConstructor) {
const { instances, provider, optionalTree, merkleTreeService } = serviceConstructor;
const contract = merkleTreeService?.Tornado || Tornado__factory.connect(Object.keys(instances)[0], provider);
super({
...serviceConstructor,
contract,
type: "*"
});
this.batchEventsService = new batch/* BatchEventsService */.JY({
provider,
contract,
address: Object.keys(instances),
onProgress: this.updateEventProgress
});
this.instances = instances;
this.optionalTree = optionalTree;
this.merkleTreeService = merkleTreeService;
this.batchTransactionService = new batch/* BatchTransactionService */.AF({
provider,
onProgress: this.updateTransactionProgress
});
this.batchBlockService = new batch/* BatchBlockService */.B3({
provider,
onProgress: this.updateBlockProgress
});
}
getInstanceName() {
return `tornado_${this.netId}`;
}
getTovarishType() {
return "tornado";
}
updateEventProgress({ percentage, type, fromBlock, toBlock, count }) {
console.log({ percentage, type, fromBlock, toBlock, count });
}
async formatEvents(events) {
const txs = await this.batchTransactionService.getBatchTransactions([
...new Set(
events.filter(({ eventName }) => eventName === "Deposit").map(({ transactionHash }) => transactionHash)
)
]);
const blocks = await this.batchBlockService.getBatchBlocks([
...new Set(
events.filter(({ eventName }) => eventName === "Withdrawal").map(({ blockNumber }) => blockNumber)
)
]);
return events.map(
({
address: instanceAddress,
blockNumber,
index: logIndex,
transactionHash,
args,
eventName: event
}) => {
const eventObjects = {
blockNumber,
logIndex,
transactionHash,
event,
instanceAddress
};
if (event === "Deposit") {
const { commitment, leafIndex, timestamp } = args;
return {
...eventObjects,
commitment,
leafIndex: Number(leafIndex),
timestamp: Number(timestamp),
from: txs.find(({ hash }) => hash === transactionHash)?.from || ""
};
}
if (event === "Withdrawal") {
const { nullifierHash, to, relayer: relayerAddress, fee } = args;
return {
...eventObjects,
logIndex,
transactionHash,
nullifierHash: String(nullifierHash),
to,
relayerAddress,
fee: String(fee),
timestamp: blocks.find(({ number }) => number === blockNumber)?.timestamp || 0
};
}
}
).filter((e) => e);
}
async validateEvents({
events,
newEvents
}) {
const instancesWithNewEvents = [
...new Set(
newEvents.filter(({ event }) => event === "Deposit").map(({ instanceAddress }) => instanceAddress)
)
];
let tree;
const requiredTree = this.merkleTreeService?.Tornado?.target;
if (requiredTree && !instancesWithNewEvents.includes(requiredTree)) {
instancesWithNewEvents.push(requiredTree);
}
for (const instance of instancesWithNewEvents) {
const depositEvents = events.filter(
({ instanceAddress, event }) => instanceAddress === instance && event === "Deposit"
);
const lastEvent = depositEvents[depositEvents.length - 1];
if (lastEvent.leafIndex !== depositEvents.length - 1) {
const errMsg = `Invalid deposit events for ${instance} wants ${depositEvents.length - 1} leafIndex have ${lastEvent.leafIndex}`;
throw new Error(errMsg);
}
if (requiredTree === instance && !this.optionalTree) {
tree = await this.merkleTreeService?.verifyTree(depositEvents);
}
}
return tree;
}
async getEvents(instanceAddress) {
const { events, validateResult: tree, lastBlock } = await this.updateEvents();
const { depositEvents, withdrawalEvents } = events.reduce(
(acc, curr) => {
if (curr.instanceAddress === instanceAddress) {
if (curr.event === "Deposit") {
acc.depositEvents.push(curr);
} else if (curr.event === "Withdrawal") {
acc.withdrawalEvents.push(curr);
}
}
return acc;
},
{}
);
return {
depositEvents,
withdrawalEvents,
tree,
lastBlock
};
}
}
class BaseEchoService extends BaseEventsService {
constructor(serviceConstructor) {
super({
@ -91662,6 +91809,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export */ BaseEncryptedNotesService: () => (/* reexport safe */ _base__WEBPACK_IMPORTED_MODULE_1__.O_),
/* harmony export */ BaseEventsService: () => (/* reexport safe */ _base__WEBPACK_IMPORTED_MODULE_1__.uw),
/* harmony export */ BaseGovernanceService: () => (/* reexport safe */ _base__WEBPACK_IMPORTED_MODULE_1__.JJ),
/* harmony export */ BaseMultiTornadoService: () => (/* reexport safe */ _base__WEBPACK_IMPORTED_MODULE_1__.lG),
/* harmony export */ BaseRegistryService: () => (/* reexport safe */ _base__WEBPACK_IMPORTED_MODULE_1__.cE),
/* harmony export */ BaseRevenueService: () => (/* reexport safe */ _base__WEBPACK_IMPORTED_MODULE_1__.Do),
/* harmony export */ BaseTornadoService: () => (/* reexport safe */ _base__WEBPACK_IMPORTED_MODULE_1__.e0),
@ -92866,6 +93014,7 @@ async function multicall(Multicall2, calls) {
/* harmony export */ h9: () => (/* binding */ getActiveTokens),
/* harmony export */ o2: () => (/* binding */ getRelayerEnsSubdomains),
/* harmony export */ oY: () => (/* binding */ getActiveTokenInstances),
/* harmony export */ sX: () => (/* binding */ getMultiInstances),
/* harmony export */ sb: () => (/* binding */ defaultConfig),
/* harmony export */ zj: () => (/* binding */ getConfig),
/* harmony export */ zr: () => (/* binding */ NetId)
@ -93468,7 +93617,7 @@ function getActiveTokenInstances(config) {
}
function getInstanceByAddress(config, address) {
const { tokens, disabledTokens } = config;
for (const [currency, { instanceAddress }] of Object.entries(tokens)) {
for (const [currency, { instanceAddress, tokenAddress, symbol, decimals }] of Object.entries(tokens)) {
if (disabledTokens?.includes(currency)) {
continue;
}
@ -93476,7 +93625,10 @@ function getInstanceByAddress(config, address) {
if (instance === address) {
return {
amount,
currency
currency,
symbol,
decimals,
tokenAddress
};
}
}
@ -93489,6 +93641,21 @@ function getRelayerEnsSubdomains() {
return acc;
}, {});
}
function getMultiInstances(netId, config) {
return Object.entries(config.tokens).reduce(
(acc, [currency, { instanceAddress }]) => {
Object.entries(instanceAddress).forEach(([amount, contractAddress]) => {
acc[contractAddress] = {
currency,
amount,
netId
};
});
return acc;
},
{}
);
}
/***/ }),
@ -217571,6 +217738,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export */ getHttpAgent: () => (/* reexport safe */ _providers__WEBPACK_IMPORTED_MODULE_19__.WU),
/* harmony export */ getIndexedDB: () => (/* reexport safe */ _idb__WEBPACK_IMPORTED_MODULE_10__.W7),
/* harmony export */ getInstanceByAddress: () => (/* reexport safe */ _networkConfig__WEBPACK_IMPORTED_MODULE_15__.Zh),
/* harmony export */ getMultiInstances: () => (/* reexport safe */ _networkConfig__WEBPACK_IMPORTED_MODULE_15__.sX),
/* harmony export */ getNetworkConfig: () => (/* reexport safe */ _networkConfig__WEBPACK_IMPORTED_MODULE_15__.RY),
/* harmony export */ getPermit2CommitmentsSignature: () => (/* reexport safe */ _permit__WEBPACK_IMPORTED_MODULE_17__.Sl),
/* harmony export */ getPermit2Signature: () => (/* reexport safe */ _permit__WEBPACK_IMPORTED_MODULE_17__.KM),

File diff suppressed because one or more lines are too long

@ -22,6 +22,7 @@ import {
Tornado__factory,
} from '@tornado/contracts';
import type { MerkleTree } from '@tornado/fixed-merkle-tree';
import {
BatchEventsService,
BatchBlockService,
@ -37,6 +38,7 @@ import type { TovarishClient } from '../tovarishClient';
import type { ReverseRecords } from '../typechain';
import type { MerkleTreeService } from '../merkleTree';
import type { DepositType } from '../deposits';
import type {
BaseEvents,
CachedEvents,
@ -56,6 +58,8 @@ import type {
WorkerUnregisteredEvents,
AllRelayerRegistryEvents,
StakeBurnedEvents,
MultiDepositEvents,
MultiWithdrawalsEvents,
} from './types';
export interface BaseEventsServiceConstructor {
@ -237,7 +241,7 @@ export class BaseEventsService<EventType extends MinimalEvents> {
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async saveEvents({ events, lastBlock }: BaseEvents<EventType>) {}
async saveEvents({ events, newEvents, lastBlock }: BaseEvents<EventType> & { newEvents: EventType[] }) {}
/**
* Trigger saving and receiving latest events
@ -280,7 +284,7 @@ export class BaseEventsService<EventType extends MinimalEvents> {
// 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 });
await this.saveEvents({ events: allEvents, newEvents: newEvents.events, lastBlock });
}
return {
@ -429,6 +433,203 @@ export class BaseTornadoService extends BaseEventsService<DepositsEvents | Withd
}
}
export interface BaseMultiTornadoServiceConstructor extends Omit<BaseEventsServiceConstructor, 'contract' | 'type'> {
instances: {
[key in string]: DepositType;
};
optionalTree?: boolean;
merkleTreeService?: MerkleTreeService;
}
export class BaseMultiTornadoService extends BaseEventsService<MultiDepositEvents | MultiWithdrawalsEvents> {
instances: {
[key in string]: DepositType;
};
optionalTree?: boolean;
merkleTreeService?: MerkleTreeService;
batchTransactionService: BatchTransactionService;
batchBlockService: BatchBlockService;
constructor(serviceConstructor: BaseMultiTornadoServiceConstructor) {
const { instances, provider, optionalTree, merkleTreeService } = serviceConstructor;
const contract =
merkleTreeService?.Tornado || Tornado__factory.connect(Object.keys(instances)[0] as string, provider);
super({
...serviceConstructor,
contract,
type: '*',
});
this.batchEventsService = new BatchEventsService({
provider,
contract,
address: Object.keys(instances),
onProgress: this.updateEventProgress,
});
this.instances = instances;
this.optionalTree = optionalTree;
this.merkleTreeService = merkleTreeService;
this.batchTransactionService = new BatchTransactionService({
provider,
onProgress: this.updateTransactionProgress,
});
this.batchBlockService = new BatchBlockService({
provider,
onProgress: this.updateBlockProgress,
});
}
getInstanceName(): string {
return `tornado_${this.netId}`;
}
getTovarishType(): string {
return 'tornado';
}
updateEventProgress({ percentage, type, fromBlock, toBlock, count }: Parameters<BatchEventOnProgress>[0]) {
console.log({ percentage, type, fromBlock, toBlock, count });
}
async formatEvents(events: EventLog[]): Promise<(MultiDepositEvents | MultiWithdrawalsEvents)[]> {
const txs = await this.batchTransactionService.getBatchTransactions([
...new Set(
events.filter(({ eventName }) => eventName === 'Deposit').map(({ transactionHash }) => transactionHash),
),
]);
const blocks = await this.batchBlockService.getBatchBlocks([
...new Set(
events.filter(({ eventName }) => eventName === 'Withdrawal').map(({ blockNumber }) => blockNumber),
),
]);
return events
.map(
({
address: instanceAddress,
blockNumber,
index: logIndex,
transactionHash,
args,
eventName: event,
}) => {
const eventObjects = {
blockNumber,
logIndex,
transactionHash,
event,
instanceAddress,
};
if (event === 'Deposit') {
const { commitment, leafIndex, timestamp } = args;
return {
...eventObjects,
commitment: commitment as string,
leafIndex: Number(leafIndex),
timestamp: Number(timestamp),
from: txs.find(({ hash }) => hash === transactionHash)?.from || '',
} as MultiDepositEvents;
}
if (event === 'Withdrawal') {
const { nullifierHash, to, relayer: relayerAddress, fee } = args;
return {
...eventObjects,
logIndex,
transactionHash,
nullifierHash: String(nullifierHash),
to,
relayerAddress,
fee: String(fee),
timestamp: blocks.find(({ number }) => number === blockNumber)?.timestamp || 0,
} as MultiWithdrawalsEvents;
}
},
)
.filter((e) => e) as (MultiDepositEvents | MultiWithdrawalsEvents)[];
}
async validateEvents<S>({
events,
newEvents,
}: BaseEvents<MultiDepositEvents | MultiWithdrawalsEvents> & {
newEvents: (MultiDepositEvents | MultiWithdrawalsEvents)[];
}) {
const instancesWithNewEvents = [
...new Set(
newEvents.filter(({ event }) => event === 'Deposit').map(({ instanceAddress }) => instanceAddress),
),
];
let tree: S | undefined;
const requiredTree = this.merkleTreeService?.Tornado?.target as string | undefined;
// Audit and create deposit tree
if (requiredTree && !instancesWithNewEvents.includes(requiredTree)) {
instancesWithNewEvents.push(requiredTree);
}
for (const instance of instancesWithNewEvents) {
const depositEvents = events.filter(
({ instanceAddress, event }) => instanceAddress === instance && event === 'Deposit',
) as MultiDepositEvents[];
const lastEvent = depositEvents[depositEvents.length - 1];
if (lastEvent.leafIndex !== depositEvents.length - 1) {
const errMsg = `Invalid deposit events for ${instance} wants ${depositEvents.length - 1} leafIndex have ${lastEvent.leafIndex}`;
throw new Error(errMsg);
}
if (requiredTree === instance && !this.optionalTree) {
tree = (await this.merkleTreeService?.verifyTree(depositEvents)) as S;
}
}
return tree as S;
}
async getEvents(instanceAddress: string) {
const { events, validateResult: tree, lastBlock } = await this.updateEvents<MerkleTree | undefined>();
const { depositEvents, withdrawalEvents } = events.reduce(
(acc, curr) => {
if (curr.instanceAddress === instanceAddress) {
if (curr.event === 'Deposit') {
acc.depositEvents.push(curr as MultiDepositEvents);
} else if (curr.event === 'Withdrawal') {
acc.withdrawalEvents.push(curr as MultiWithdrawalsEvents);
}
}
return acc;
},
{} as {
depositEvents: MultiDepositEvents[];
withdrawalEvents: MultiWithdrawalsEvents[];
},
);
return {
depositEvents,
withdrawalEvents,
tree,
lastBlock,
};
}
}
export interface BaseEchoServiceConstructor extends Omit<BaseEventsServiceConstructor, 'contract' | 'type'> {
Echoer: Echoer;
}

@ -113,6 +113,17 @@ export interface WithdrawalsEvents extends MinimalEvents {
timestamp: number;
}
export interface BaseMultiTornadoEvents {
event: string;
instanceAddress: string;
}
export interface MultiDepositEvents extends BaseMultiTornadoEvents, DepositsEvents {}
export interface MultiWithdrawalsEvents extends BaseMultiTornadoEvents, WithdrawalsEvents {
relayerAddress: string;
}
export interface EchoEvents extends MinimalEvents {
address: string;
encryptedAccount: string;

@ -1,3 +1,5 @@
import type { DepositType } from './deposits';
/**
* Type of default supported networks
*/
@ -725,7 +727,7 @@ export function getActiveTokenInstances(config: Config): TokenInstances {
export function getInstanceByAddress(config: Config, address: string) {
const { tokens, disabledTokens } = config;
for (const [currency, { instanceAddress }] of Object.entries(tokens)) {
for (const [currency, { instanceAddress, tokenAddress, symbol, decimals }] of Object.entries(tokens)) {
if (disabledTokens?.includes(currency)) {
continue;
}
@ -734,6 +736,9 @@ export function getInstanceByAddress(config: Config, address: string) {
return {
amount,
currency,
symbol,
decimals,
tokenAddress,
};
}
}
@ -748,3 +753,19 @@ export function getRelayerEnsSubdomains() {
return acc;
}, {} as SubdomainMap);
}
export function getMultiInstances(netId: NetIdType, config: Config): { [key in string]: DepositType } {
return Object.entries(config.tokens).reduce(
(acc, [currency, { instanceAddress }]) => {
Object.entries(instanceAddress).forEach(([amount, contractAddress]) => {
acc[contractAddress] = {
currency,
amount,
netId,
};
});
return acc;
},
{} as { [key in string]: DepositType },
);
}