shove-js
Version:
SDK for shove.bet
123 lines (107 loc) • 3.35 kB
text/typescript
import { TopicFilter } from "ethers";
import { JsonRpcProvider } from "ethers";
import { Interface } from "ethers";
import { Contract, ContractRunner, InterfaceAbi, Overrides } from "ethers";
export class BaseContract {
protected readonly contract: Contract;
public address: string;
private blockTimestampCache = new Map<number, number>();
constructor(address: string, abi: InterfaceAbi, runner: ContractRunner) {
this.address = address;
this.contract = new Contract(address, abi, runner);
}
public getInterface(): Interface {
return this.contract.interface;
}
private async call<T = any>(
method: string,
args: any,
overrides?: Overrides,
): Promise<T> {
const fn = (this.contract as any)[method];
if (typeof fn !== "function") {
throw new Error(`Method "${method}" not found on contract`);
}
const result = await fn(...args, { ...overrides });
return result as T;
}
protected async read<T = any>(
method: string,
args: unknown[] = [],
overrides?: Overrides,
): Promise<T> {
return this.call<T>(method, args, overrides);
}
protected async write<T = any>(
method: string,
args: unknown[] = [],
overrides?: Overrides,
): Promise<T> {
return this.call<T>(method, args, overrides);
}
async getEvents<T = unknown>(
filterArgs: TopicFilter = [],
fromBlock?: number | string,
toBlock?: number | string,
parser?: (entry: {
name?: string;
args?: any;
blockNumber: number;
transactionHash: string;
log: any;
timestamp: number | null;
}) => T,
): Promise<T[]> {
const provider = this.contract.runner?.provider! as JsonRpcProvider;
if (!provider || typeof provider.getLogs !== "function") {
throw new Error("Provider not available to fetch logs from the contract");
}
const parseRaw = (log: any) => {
try {
const parsed = this.contract.interface.parseLog(log);
return {
name: parsed!.name,
args: parsed!.args,
blockNumber: log.blockNumber,
transactionHash: log.transactionHash,
log,
};
} catch (err) {
return {
name: undefined,
args: undefined,
blockNumber: log.blockNumber,
transactionHash: log.transactionHash,
log,
};
}
};
let logs: any[];
logs = await provider.getLogs({
address: this.address,
topics: filterArgs,
fromBlock,
toBlock,
});
const parsedEntries = logs.map(parseRaw);
const uniqueBlocks = Array.from(
new Set(parsedEntries.map((e) => e.blockNumber).filter((bn) => !!bn)),
) as number[];
const missingBlocks = uniqueBlocks.filter(
(bn) => !this.blockTimestampCache.has(bn),
);
if (missingBlocks.length) {
const blockPromises = missingBlocks.map((bn) => provider.getBlock(bn));
const blocks = await Promise.all(blockPromises);
for (const b of blocks) {
if (b) this.blockTimestampCache.set(b.number, b.timestamp);
}
}
const entriesWithTs = parsedEntries.map((e) => ({
...e,
timestamp: this.blockTimestampCache.get(e.blockNumber) ?? null,
}));
if (parser) return entriesWithTs.map(parser);
return entriesWithTs as unknown as T[];
}
}