Add multiQueryFilter function

This commit is contained in:
Tornado Contrib 2024-11-17 22:37:15 +00:00
parent 092989ebaa
commit 8644cd3c82
Signed by: tornadocontrib
GPG Key ID: 60B4DF1A076C64B1
7 changed files with 814 additions and 302 deletions

17
dist/batch.d.ts vendored

@ -1,4 +1,14 @@
import type { Provider, BlockTag, Block, TransactionResponse, BaseContract, ContractEventName, EventLog, TransactionReceipt } from 'ethers'; import { Provider, BlockTag, Block, TransactionResponse, BaseContract, ContractEventName, EventLog, TransactionReceipt, EventFragment, TopicFilter, Interface, Log } from 'ethers';
/**
* Copied from ethers.js as they don't export this function
* https://github.com/ethers-io/ethers.js/blob/main/src.ts/contract/contract.ts#L464
*/
export declare function getSubInfo(abiInterface: Interface, event: ContractEventName): Promise<{
fragment: null | EventFragment;
tag: string;
topics: TopicFilter;
}>;
export declare function multiQueryFilter(address: string | string[], contract: BaseContract, event: ContractEventName, fromBlock?: BlockTag, toBlock?: BlockTag): Promise<Log[]>;
export interface BatchBlockServiceConstructor { export interface BatchBlockServiceConstructor {
provider: Provider; provider: Provider;
onProgress?: BatchBlockOnProgress; onProgress?: BatchBlockOnProgress;
@ -65,6 +75,7 @@ export type BatchEventOnProgress = ({ percentage, type, fromBlock, toBlock, coun
count?: number; count?: number;
}) => void; }) => void;
export interface EventInput { export interface EventInput {
address?: string | string[];
fromBlock: number; fromBlock: number;
toBlock: number; toBlock: number;
type: ContractEventName; type: ContractEventName;
@ -82,7 +93,7 @@ export declare class BatchEventsService {
retryMax: number; retryMax: number;
retryOn: number; retryOn: number;
constructor({ provider, contract, onProgress, concurrencySize, blocksPerRequest, shouldRetry, retryMax, retryOn, }: BatchEventServiceConstructor); constructor({ provider, contract, onProgress, concurrencySize, blocksPerRequest, shouldRetry, retryMax, retryOn, }: BatchEventServiceConstructor);
getPastEvents({ fromBlock, toBlock, type }: EventInput): Promise<EventLog[]>; getPastEvents({ address, fromBlock, toBlock, type }: EventInput): Promise<EventLog[]>;
createBatchRequest(batchArray: EventInput[]): Promise<EventLog[]>[]; createBatchRequest(batchArray: EventInput[]): Promise<EventLog[]>[];
getBatchEvents({ fromBlock, toBlock, type }: EventInput): Promise<EventLog[]>; getBatchEvents({ address, fromBlock, toBlock, type }: EventInput): Promise<EventLog[]>;
} }

118
dist/index.js vendored

@ -154,6 +154,107 @@ function fromContentHash(contentHash) {
return contentHashUtils__namespace.decode(contentHash); return contentHashUtils__namespace.decode(contentHash);
} }
function isDeferred(value) {
return value && typeof value === "object" && "getTopicFilter" in value && typeof value.getTopicFilter === "function" && value.fragment;
}
async function getSubInfo(abiInterface, event) {
let topics;
let fragment = null;
if (Array.isArray(event)) {
const topicHashify = function(name) {
if (ethers.isHexString(name, 32)) {
return name;
}
const fragment2 = abiInterface.getEvent(name);
ethers.assertArgument(fragment2, "unknown fragment", "name", name);
return fragment2.topicHash;
};
topics = event.map((e) => {
if (e == null) {
return null;
}
if (Array.isArray(e)) {
return e.map(topicHashify);
}
return topicHashify(e);
});
} else if (event === "*") {
topics = [null];
} else if (typeof event === "string") {
if (ethers.isHexString(event, 32)) {
topics = [event];
} else {
fragment = abiInterface.getEvent(event);
ethers.assertArgument(fragment, "unknown fragment", "event", event);
topics = [fragment.topicHash];
}
} else if (isDeferred(event)) {
topics = await event.getTopicFilter();
} else if ("fragment" in event) {
fragment = event.fragment;
topics = [fragment.topicHash];
} else {
ethers.assertArgument(false, "unknown event name", "event", event);
}
topics = topics.map((t) => {
if (t == null) {
return null;
}
if (Array.isArray(t)) {
const items = Array.from(new Set(t.map((t2) => t2.toLowerCase())).values());
if (items.length === 1) {
return items[0];
}
items.sort();
return items;
}
return t.toLowerCase();
});
const tag = topics.map((t) => {
if (t == null) {
return "null";
}
if (Array.isArray(t)) {
return t.join("|");
}
return t;
}).join("&");
return { fragment, tag, topics };
}
async function multiQueryFilter(address, contract, event, fromBlock, toBlock) {
if (fromBlock == null) {
fromBlock = 0;
}
if (toBlock == null) {
toBlock = "latest";
}
const { fragment, topics } = await getSubInfo(contract.interface, event);
const filter = {
address: address === "*" ? void 0 : address,
topics,
fromBlock,
toBlock
};
const provider = contract.runner;
ethers.assert(provider, "contract runner does not have a provider", "UNSUPPORTED_OPERATION", { operation: "queryFilter" });
return (await provider.getLogs(filter)).map((log) => {
let foundFragment = fragment;
if (foundFragment == null) {
try {
foundFragment = contract.interface.getEvent(log.topics[0]);
} catch {
}
}
if (foundFragment) {
try {
return new ethers.EventLog(log, contract.interface, foundFragment);
} catch (error) {
return new ethers.UndecodedEventLog(log, error);
}
}
return new ethers.Log(log, provider);
});
}
class BatchBlockService { class BatchBlockService {
provider; provider;
onProgress; onProgress;
@ -351,11 +452,14 @@ class BatchEventsService {
this.retryMax = retryMax; this.retryMax = retryMax;
this.retryOn = retryOn; this.retryOn = retryOn;
} }
async getPastEvents({ fromBlock, toBlock, type }) { async getPastEvents({ address, fromBlock, toBlock, type }) {
let err; let err;
let retries = 0; let retries = 0;
while (!this.shouldRetry && retries === 0 || this.shouldRetry && retries < this.retryMax) { while (!this.shouldRetry && retries === 0 || this.shouldRetry && retries < this.retryMax) {
try { try {
if (address) {
return await multiQueryFilter(address, this.contract, type, fromBlock, toBlock);
}
return await this.contract.queryFilter(type, fromBlock, toBlock); return await this.contract.queryFilter(type, fromBlock, toBlock);
} catch (e) { } catch (e) {
err = e; err = e;
@ -375,14 +479,14 @@ class BatchEventsService {
return this.getPastEvents(event); return this.getPastEvents(event);
}); });
} }
async getBatchEvents({ fromBlock, toBlock, type = "*" }) { async getBatchEvents({ address, fromBlock, toBlock, type = "*" }) {
if (!toBlock) { if (!toBlock) {
toBlock = await this.provider.getBlockNumber(); toBlock = await this.provider.getBlockNumber();
} }
const eventsToSync = []; const eventsToSync = [];
for (let i = fromBlock; i < toBlock; i += this.blocksPerRequest) { for (let i = fromBlock; i < toBlock; i += this.blocksPerRequest) {
const j = i + this.blocksPerRequest - 1 > toBlock ? toBlock : i + this.blocksPerRequest - 1; const j = i + this.blocksPerRequest - 1 > toBlock ? toBlock : i + this.blocksPerRequest - 1;
eventsToSync.push({ fromBlock: i, toBlock: j, type }); eventsToSync.push({ address, fromBlock: i, toBlock: j, type });
} }
const events = []; const events = [];
const eventChunk = chunk(eventsToSync, this.concurrencySize); const eventChunk = chunk(eventsToSync, this.concurrencySize);
@ -2086,7 +2190,7 @@ class BaseEventsService {
} }
} }
async getLatestEvents({ fromBlock }) { async getLatestEvents({ fromBlock }) {
if (this.tovarishClient?.selectedRelayer && !["Deposit", "Withdrawal"].includes(this.type)) { if (this.tovarishClient?.selectedRelayer) {
const { events, lastSyncBlock: lastBlock } = await this.tovarishClient.getEvents({ const { events, lastSyncBlock: lastBlock } = await this.tovarishClient.getEvents({
type: this.getTovarishType(), type: this.getTovarishType(),
fromBlock fromBlock
@ -2250,7 +2354,9 @@ class BaseTornadoService extends BaseEventsService {
lastBlock lastBlock
}; };
} }
return super.getLatestEvents({ fromBlock }); return await this.getEventsFromRpc({
fromBlock
});
} }
} }
class BaseEchoService extends BaseEventsService { class BaseEchoService extends BaseEventsService {
@ -10396,6 +10502,7 @@ exports.getProvider = getProvider;
exports.getProviderWithNetId = getProviderWithNetId; exports.getProviderWithNetId = getProviderWithNetId;
exports.getRelayerEnsSubdomains = getRelayerEnsSubdomains; exports.getRelayerEnsSubdomains = getRelayerEnsSubdomains;
exports.getStatusSchema = getStatusSchema; exports.getStatusSchema = getStatusSchema;
exports.getSubInfo = getSubInfo;
exports.getSupportedInstances = getSupportedInstances; exports.getSupportedInstances = getSupportedInstances;
exports.getTokenBalances = getTokenBalances; exports.getTokenBalances = getTokenBalances;
exports.getTovarishNetworks = getTovarishNetworks; exports.getTovarishNetworks = getTovarishNetworks;
@ -10415,6 +10522,7 @@ exports.loadDBEvents = loadDBEvents;
exports.loadRemoteEvents = loadRemoteEvents; exports.loadRemoteEvents = loadRemoteEvents;
exports.makeLabelNodeAndParent = makeLabelNodeAndParent; exports.makeLabelNodeAndParent = makeLabelNodeAndParent;
exports.mimc = mimc; exports.mimc = mimc;
exports.multiQueryFilter = multiQueryFilter;
exports.multicall = multicall; exports.multicall = multicall;
exports.numberFormatter = numberFormatter; exports.numberFormatter = numberFormatter;
exports.packEncryptedMessage = packEncryptedMessage; exports.packEncryptedMessage = packEncryptedMessage;

120
dist/index.mjs vendored

@ -1,4 +1,4 @@
import { FetchRequest, JsonRpcProvider, Network, EnsPlugin, GasCostPlugin, Wallet, HDNodeWallet, VoidSigner, JsonRpcSigner, BrowserProvider, isAddress, parseEther, getAddress, AbiCoder, formatEther, namehash, dataSlice, dataLength, Interface, Contract, computeAddress, keccak256, EnsResolver, parseUnits, Transaction, Signature, MaxUint256, solidityPackedKeccak256, TypedDataEncoder, ZeroAddress } from 'ethers'; import { isHexString, assertArgument, assert, EventLog, UndecodedEventLog, Log, FetchRequest, JsonRpcProvider, Network, EnsPlugin, GasCostPlugin, Wallet, HDNodeWallet, VoidSigner, JsonRpcSigner, BrowserProvider, isAddress, parseEther, getAddress, AbiCoder, formatEther, namehash, dataSlice, dataLength, Interface, Contract, computeAddress, keccak256, EnsResolver, parseUnits, Transaction, Signature, MaxUint256, solidityPackedKeccak256, TypedDataEncoder, ZeroAddress } from 'ethers';
import { Tornado__factory } from '@tornado/contracts'; import { Tornado__factory } from '@tornado/contracts';
import { webcrypto } from 'crypto'; import { webcrypto } from 'crypto';
import BN from 'bn.js'; import BN from 'bn.js';
@ -132,6 +132,107 @@ function fromContentHash(contentHash) {
return contentHashUtils.decode(contentHash); return contentHashUtils.decode(contentHash);
} }
function isDeferred(value) {
return value && typeof value === "object" && "getTopicFilter" in value && typeof value.getTopicFilter === "function" && value.fragment;
}
async function getSubInfo(abiInterface, event) {
let topics;
let fragment = null;
if (Array.isArray(event)) {
const topicHashify = function(name) {
if (isHexString(name, 32)) {
return name;
}
const fragment2 = abiInterface.getEvent(name);
assertArgument(fragment2, "unknown fragment", "name", name);
return fragment2.topicHash;
};
topics = event.map((e) => {
if (e == null) {
return null;
}
if (Array.isArray(e)) {
return e.map(topicHashify);
}
return topicHashify(e);
});
} else if (event === "*") {
topics = [null];
} else if (typeof event === "string") {
if (isHexString(event, 32)) {
topics = [event];
} else {
fragment = abiInterface.getEvent(event);
assertArgument(fragment, "unknown fragment", "event", event);
topics = [fragment.topicHash];
}
} else if (isDeferred(event)) {
topics = await event.getTopicFilter();
} else if ("fragment" in event) {
fragment = event.fragment;
topics = [fragment.topicHash];
} else {
assertArgument(false, "unknown event name", "event", event);
}
topics = topics.map((t) => {
if (t == null) {
return null;
}
if (Array.isArray(t)) {
const items = Array.from(new Set(t.map((t2) => t2.toLowerCase())).values());
if (items.length === 1) {
return items[0];
}
items.sort();
return items;
}
return t.toLowerCase();
});
const tag = topics.map((t) => {
if (t == null) {
return "null";
}
if (Array.isArray(t)) {
return t.join("|");
}
return t;
}).join("&");
return { fragment, tag, topics };
}
async function multiQueryFilter(address, contract, event, fromBlock, toBlock) {
if (fromBlock == null) {
fromBlock = 0;
}
if (toBlock == null) {
toBlock = "latest";
}
const { fragment, topics } = await getSubInfo(contract.interface, event);
const filter = {
address: address === "*" ? void 0 : address,
topics,
fromBlock,
toBlock
};
const provider = contract.runner;
assert(provider, "contract runner does not have a provider", "UNSUPPORTED_OPERATION", { operation: "queryFilter" });
return (await provider.getLogs(filter)).map((log) => {
let foundFragment = fragment;
if (foundFragment == null) {
try {
foundFragment = contract.interface.getEvent(log.topics[0]);
} catch {
}
}
if (foundFragment) {
try {
return new EventLog(log, contract.interface, foundFragment);
} catch (error) {
return new UndecodedEventLog(log, error);
}
}
return new Log(log, provider);
});
}
class BatchBlockService { class BatchBlockService {
provider; provider;
onProgress; onProgress;
@ -329,11 +430,14 @@ class BatchEventsService {
this.retryMax = retryMax; this.retryMax = retryMax;
this.retryOn = retryOn; this.retryOn = retryOn;
} }
async getPastEvents({ fromBlock, toBlock, type }) { async getPastEvents({ address, fromBlock, toBlock, type }) {
let err; let err;
let retries = 0; let retries = 0;
while (!this.shouldRetry && retries === 0 || this.shouldRetry && retries < this.retryMax) { while (!this.shouldRetry && retries === 0 || this.shouldRetry && retries < this.retryMax) {
try { try {
if (address) {
return await multiQueryFilter(address, this.contract, type, fromBlock, toBlock);
}
return await this.contract.queryFilter(type, fromBlock, toBlock); return await this.contract.queryFilter(type, fromBlock, toBlock);
} catch (e) { } catch (e) {
err = e; err = e;
@ -353,14 +457,14 @@ class BatchEventsService {
return this.getPastEvents(event); return this.getPastEvents(event);
}); });
} }
async getBatchEvents({ fromBlock, toBlock, type = "*" }) { async getBatchEvents({ address, fromBlock, toBlock, type = "*" }) {
if (!toBlock) { if (!toBlock) {
toBlock = await this.provider.getBlockNumber(); toBlock = await this.provider.getBlockNumber();
} }
const eventsToSync = []; const eventsToSync = [];
for (let i = fromBlock; i < toBlock; i += this.blocksPerRequest) { for (let i = fromBlock; i < toBlock; i += this.blocksPerRequest) {
const j = i + this.blocksPerRequest - 1 > toBlock ? toBlock : i + this.blocksPerRequest - 1; const j = i + this.blocksPerRequest - 1 > toBlock ? toBlock : i + this.blocksPerRequest - 1;
eventsToSync.push({ fromBlock: i, toBlock: j, type }); eventsToSync.push({ address, fromBlock: i, toBlock: j, type });
} }
const events = []; const events = [];
const eventChunk = chunk(eventsToSync, this.concurrencySize); const eventChunk = chunk(eventsToSync, this.concurrencySize);
@ -2064,7 +2168,7 @@ class BaseEventsService {
} }
} }
async getLatestEvents({ fromBlock }) { async getLatestEvents({ fromBlock }) {
if (this.tovarishClient?.selectedRelayer && !["Deposit", "Withdrawal"].includes(this.type)) { if (this.tovarishClient?.selectedRelayer) {
const { events, lastSyncBlock: lastBlock } = await this.tovarishClient.getEvents({ const { events, lastSyncBlock: lastBlock } = await this.tovarishClient.getEvents({
type: this.getTovarishType(), type: this.getTovarishType(),
fromBlock fromBlock
@ -2228,7 +2332,9 @@ class BaseTornadoService extends BaseEventsService {
lastBlock lastBlock
}; };
} }
return super.getLatestEvents({ fromBlock }); return await this.getEventsFromRpc({
fromBlock
});
} }
} }
class BaseEchoService extends BaseEventsService { class BaseEchoService extends BaseEventsService {
@ -10270,4 +10376,4 @@ async function calculateSnarkProof(input, circuit, provingKey) {
return { proof, args }; 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, getSupportedInstances, getTokenBalances, getTovarishNetworks, getWeightRandom, governanceEventsSchema, hasherBytecode, hexToBytes, initGroth16, isHex, isNode, jobRequestSchema, jobsSchema, labelhash, leBuff2Int, leInt2Buff, loadDBEvents, loadRemoteEvents, makeLabelNodeAndParent, mimc, 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, 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 };

679
dist/tornado.umd.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -1,4 +1,4 @@
import type { import {
Provider, Provider,
BlockTag, BlockTag,
Block, Block,
@ -7,9 +7,171 @@ import type {
ContractEventName, ContractEventName,
EventLog, EventLog,
TransactionReceipt, TransactionReceipt,
isHexString,
assert,
assertArgument,
DeferredTopicFilter,
EventFragment,
TopicFilter,
Interface,
UndecodedEventLog,
Log,
} from 'ethers'; } from 'ethers';
import { chunk, sleep } from './utils'; import { chunk, sleep } from './utils';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isDeferred(value: any): value is DeferredTopicFilter {
return (
value &&
typeof value === 'object' &&
'getTopicFilter' in value &&
typeof value.getTopicFilter === 'function' &&
value.fragment
);
}
/**
* Copied from ethers.js as they don't export this function
* https://github.com/ethers-io/ethers.js/blob/main/src.ts/contract/contract.ts#L464
*/
export async function getSubInfo(
abiInterface: Interface,
event: ContractEventName,
): Promise<{
fragment: null | EventFragment;
tag: string;
topics: TopicFilter;
}> {
let topics: Array<null | string | Array<string>>;
let fragment: null | EventFragment = null;
// Convert named events to topicHash and get the fragment for
// events which need deconstructing.
if (Array.isArray(event)) {
const topicHashify = function (name: string): string {
if (isHexString(name, 32)) {
return name;
}
const fragment = abiInterface.getEvent(name);
assertArgument(fragment, 'unknown fragment', 'name', name);
return fragment.topicHash;
};
// Array of Topics and Names; e.g. `[ "0x1234...89ab", "Transfer(address)" ]`
topics = event.map((e) => {
if (e == null) {
return null;
}
if (Array.isArray(e)) {
return e.map(topicHashify);
}
return topicHashify(e);
});
} else if (event === '*') {
topics = [null];
} else if (typeof event === 'string') {
if (isHexString(event, 32)) {
// Topic Hash
topics = [event];
} else {
// Name or Signature; e.g. `"Transfer", `"Transfer(address)"`
fragment = abiInterface.getEvent(event);
assertArgument(fragment, 'unknown fragment', 'event', event);
topics = [fragment.topicHash];
}
} else if (isDeferred(event)) {
// Deferred Topic Filter; e.g. `contract.filter.Transfer(from)`
topics = await event.getTopicFilter();
} else if ('fragment' in event) {
// ContractEvent; e.g. `contract.filter.Transfer`
fragment = event.fragment;
topics = [fragment.topicHash];
} else {
assertArgument(false, 'unknown event name', 'event', event);
}
// Normalize topics and sort TopicSets
topics = topics.map((t) => {
if (t == null) {
return null;
}
if (Array.isArray(t)) {
const items = Array.from(new Set(t.map((t) => t.toLowerCase())).values());
if (items.length === 1) {
return items[0];
}
items.sort();
return items;
}
return t.toLowerCase();
});
const tag = topics
.map((t) => {
if (t == null) {
return 'null';
}
if (Array.isArray(t)) {
return t.join('|');
}
return t;
})
.join('&');
return { fragment, tag, topics };
}
export async function multiQueryFilter(
// Single address will scan for a single contract, array for multiple, and * for all contracts with event topic
address: string | string[],
contract: BaseContract,
event: ContractEventName,
fromBlock?: BlockTag,
toBlock?: BlockTag,
) {
if (fromBlock == null) {
fromBlock = 0;
}
if (toBlock == null) {
toBlock = 'latest';
}
const { fragment, topics } = await getSubInfo(contract.interface, event);
const filter = {
address: address === '*' ? undefined : address,
topics,
fromBlock,
toBlock,
};
const provider = contract.runner as Provider | null;
assert(provider, 'contract runner does not have a provider', 'UNSUPPORTED_OPERATION', { operation: 'queryFilter' });
return (await provider.getLogs(filter)).map((log) => {
let foundFragment = fragment;
if (foundFragment == null) {
try {
foundFragment = contract.interface.getEvent(log.topics[0]);
// eslint-disable-next-line no-empty
} catch {}
}
if (foundFragment) {
try {
return new EventLog(log, contract.interface, foundFragment);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
return new UndecodedEventLog(log, error);
}
}
return new Log(log, provider);
});
}
export interface BatchBlockServiceConstructor { export interface BatchBlockServiceConstructor {
provider: Provider; provider: Provider;
onProgress?: BatchBlockOnProgress; onProgress?: BatchBlockOnProgress;
@ -284,6 +446,7 @@ export type BatchEventOnProgress = ({
// To enable iteration only numbers are accepted for fromBlock input // To enable iteration only numbers are accepted for fromBlock input
export interface EventInput { export interface EventInput {
address?: string | string[];
fromBlock: number; fromBlock: number;
toBlock: number; toBlock: number;
type: ContractEventName; type: ContractEventName;
@ -321,13 +484,16 @@ export class BatchEventsService {
this.retryOn = retryOn; this.retryOn = retryOn;
} }
async getPastEvents({ fromBlock, toBlock, type }: EventInput): Promise<EventLog[]> { async getPastEvents({ address, fromBlock, toBlock, type }: EventInput): Promise<EventLog[]> {
let err; let err;
let retries = 0; let retries = 0;
// eslint-disable-next-line no-unmodified-loop-condition // eslint-disable-next-line no-unmodified-loop-condition
while ((!this.shouldRetry && retries === 0) || (this.shouldRetry && retries < this.retryMax)) { while ((!this.shouldRetry && retries === 0) || (this.shouldRetry && retries < this.retryMax)) {
try { try {
if (address) {
return (await multiQueryFilter(address, this.contract, type, fromBlock, toBlock)) as EventLog[];
}
return (await this.contract.queryFilter(type, fromBlock, toBlock)) as EventLog[]; return (await this.contract.queryFilter(type, fromBlock, toBlock)) as EventLog[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) { } catch (e: any) {
@ -357,7 +523,7 @@ export class BatchEventsService {
}); });
} }
async getBatchEvents({ fromBlock, toBlock, type = '*' }: EventInput): Promise<EventLog[]> { async getBatchEvents({ address, fromBlock, toBlock, type = '*' }: EventInput): Promise<EventLog[]> {
if (!toBlock) { if (!toBlock) {
toBlock = await this.provider.getBlockNumber(); toBlock = await this.provider.getBlockNumber();
} }
@ -367,7 +533,7 @@ export class BatchEventsService {
for (let i = fromBlock; i < toBlock; i += this.blocksPerRequest) { for (let i = fromBlock; i < toBlock; i += this.blocksPerRequest) {
const j = i + this.blocksPerRequest - 1 > toBlock ? toBlock : i + this.blocksPerRequest - 1; const j = i + this.blocksPerRequest - 1 > toBlock ? toBlock : i + this.blocksPerRequest - 1;
eventsToSync.push({ fromBlock: i, toBlock: j, type }); eventsToSync.push({ address, fromBlock: i, toBlock: j, type });
} }
const events = []; const events = [];

@ -205,7 +205,7 @@ export class BaseEventsService<EventType extends MinimalEvents> {
} }
async getLatestEvents({ fromBlock }: { fromBlock: number }): Promise<BaseEvents<EventType>> { async getLatestEvents({ fromBlock }: { fromBlock: number }): Promise<BaseEvents<EventType>> {
if (this.tovarishClient?.selectedRelayer && !['Deposit', 'Withdrawal'].includes(this.type)) { if (this.tovarishClient?.selectedRelayer) {
const { events, lastSyncBlock: lastBlock } = await this.tovarishClient.getEvents<EventType>({ const { events, lastSyncBlock: lastBlock } = await this.tovarishClient.getEvents<EventType>({
type: this.getTovarishType(), type: this.getTovarishType(),
fromBlock, fromBlock,
@ -423,7 +423,9 @@ export class BaseTornadoService extends BaseEventsService<DepositsEvents | Withd
}; };
} }
return super.getLatestEvents({ fromBlock }); return await this.getEventsFromRpc({
fromBlock,
});
} }
} }