import { Contract, ContractInterface, JsonFragment, InterfaceAbi, JsonRpcSigner, ethers, } from "ethers"; export function shallowCopy(object: T): T { const result: any = {}; for (const key in object) { result[key] = object[key]; } return result; } function NoEventException(value: any) { this.value = value; this.message = "No event found!"; // eslint-disable-next-line func-names this.toString = function () { return this.value + this.message; }; } const cache: { [key: string]: { contract: ethers.Contract; proxy: any; }; } = {}; type InterceptorFn = (...args: any) => Promise; export class Web3ProviderExtras { event: EventTypes; signer: () => JsonRpcSigner; readOnlySigner: () => JsonRpcSigner; beforeRequest: InterceptorFn; afterRequest: InterceptorFn; transactionState: ( state: "started" | "rejected" | "waiting" | "done", ...args: any ) => void; skipEvent: boolean; constructor(extras?: Web3ProviderExtras) { if (extras?.event) { this.event = extras?.event; } if (extras?.skipEvent) { this.skipEvent = extras.skipEvent; } if (extras?.beforeRequest) { this.beforeRequest = extras.beforeRequest; } else { this.beforeRequest = (_) => Promise.resolve(_); } if (extras?.afterRequest) { this.afterRequest = extras.afterRequest; } else { this.afterRequest = (_) => Promise.resolve(_); } if (extras?.signer) { this.signer = extras.signer; } if (extras?.readOnlySigner) { this.readOnlySigner = extras.readOnlySigner; } if (extras?.transactionState) { this.transactionState = extras.transactionState; } else { this.transactionState = (_) => null; } } } const waitForTransaction = async ( fnCallResult: Promise<{ wait: () => Promise }>, extras: Web3ProviderExtras ) => { const getEvent = (waitResult: EventTypes) => { if (extras?.event) { const { events } = waitResult as unknown as EventTypes as any; const foundEvent = events.find((e: any) => e.event === extras.event); if (!foundEvent) { throw NoEventException({ code: -32603, message: "Internal JSON-RPC error.", data: { code: 3, data: "", message: `Aut:${extras.event}EventMissing`, }, }); } return foundEvent.args; } return waitResult; }; // @ts-ignore if (fnCallResult?.then) { extras.transactionState("started"); const tx = await fnCallResult; extras.transactionState("waiting", tx); if (tx?.wait) { const eventResult = getEvent(await tx.wait()); extras.transactionState("done", eventResult); return extras.afterRequest(eventResult); } return extras.afterRequest(tx); } return extras.afterRequest(fnCallResult); }; const createContractProxy = ( contract: Contract, extras: Web3ProviderExtras ) => { return new Proxy(shallowCopy(contract as unknown as ContractFunctions), { get(target: any, prop: string, receiver: any) { if (!(prop in target)) { return undefined; } if (typeof target[prop] === "function") { const _updateSigner = () => { const functionFragment = contract.interface.getFunction(prop); const isReadOnly = functionFragment?.stateMutability === "view" || functionFragment?.stateMutability === "pure"; if (isReadOnly && extras.readOnlySigner) { contract.connect(extras.readOnlySigner()); } else if (extras.signer) { contract.connect(extras.signer()); } }; if (extras?.skipEvent) { return new Proxy(target[prop], { apply(fn, thisArg, args) { _updateSigner(); return Reflect.apply(fn, thisArg, args); }, }); } return new Proxy(target[prop], { apply(fn, thisArg, args) { _updateSigner(); return waitForTransaction( Reflect.apply(fn, thisArg, args), extras ); }, }); } return Reflect.get(target, prop, receiver); }, }); }; export const Web3ContractProvider = ( addressOrName: string, contractInterface: InterfaceAbi, extras: Web3ProviderExtras ) => { extras = new Web3ProviderExtras(extras); let signer: JsonRpcSigner; if (extras.signer) { signer = extras.signer(); } else { signer = extras.readOnlySigner(); } let contract: Contract; // let proxy: any; if (cache[addressOrName]) { const data = cache[addressOrName]; contract = data.contract; // proxy = data.proxy; contract.connect(signer); } else { contract = new ethers.Contract(addressOrName, contractInterface, signer); // proxy = createContractProxy( // contract, // extras // ); } return contract as unknown as ContractFunctions & ContractInterface; };