Skip to content

Commit

Permalink
event listening for react & lots of fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
jnsdls committed Jan 29, 2024
1 parent 8b949d1 commit 94f7fed
Show file tree
Hide file tree
Showing 25 changed files with 381 additions and 190 deletions.
14 changes: 0 additions & 14 deletions packages/thirdweb/src/abi/resolveAbiFunction.ts

This file was deleted.

54 changes: 54 additions & 0 deletions packages/thirdweb/src/event/actions/get-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { Abi, AbiEvent } from "abitype";
import type { BlockTag } from "viem";
import { eth_getLogs, getRpcClient } from "../../rpc/index.js";
import { resolveAbi } from "./resolve-abi.js";
import type { ContractEvent } from "../event.js";
import {
resolveContractAbi,
type ThirdwebContract,
} from "../../contract/index.js";

type GetContractEventsOptions<
abi extends Abi,
abiEvent extends AbiEvent,
contractEvent extends ContractEvent<abiEvent>,
fBlock extends bigint | BlockTag,
tBlock extends bigint | BlockTag,
> = {
contract: ThirdwebContract<abi>;
events?: contractEvent[];
fromBlock?: fBlock;
toBlock?: tBlock;
};

export async function getContractEvents<
const abi extends Abi,
const abiEvent extends AbiEvent,
const contractEvent extends ContractEvent<abiEvent>,
const fBlock extends bigint | BlockTag,
const tBlock extends bigint | BlockTag,
>(
options: GetContractEventsOptions<
abi,
abiEvent,
contractEvent,
fBlock,
tBlock
>,
) {
const rpcRequest = getRpcClient(options.contract);
const parsedEvents = await (options.events
? Promise.all(options.events.map((e) => resolveAbi(e)))
: // if we don't have events passed then resolve the abi for the contract -> all events!
(resolveContractAbi(options.contract).then((abi) =>
abi.filter((item) => item.type === "event"),
) as Promise<AbiEvent[]>));

// @ts-expect-error - fromBlock and toBlock ARE allowed to be undefined
return await eth_getLogs(rpcRequest, {
fromBlock: options.fromBlock,
toBlock: options.toBlock,
address: options.contract.address,
events: parsedEvents,
});
}
71 changes: 71 additions & 0 deletions packages/thirdweb/src/event/actions/resolve-abi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { parseAbiItem, type Abi, type AbiEvent } from "abitype";
import {
isAbiEvent,
type ContractEvent,
type ContractEventInput,
} from "../event.js";
import type { ParseEvent } from "../../abi/types.js";

const ABI_FN_RESOLUTION_CACHE = new WeakMap<
ContractEvent<AbiEvent>,
Promise<AbiEvent>
>();

export function resolveAbi<abiEvent extends AbiEvent | string, abi extends Abi>(
contractEvent: ContractEventInput<abi, abiEvent>,
): Promise<ParseEvent<abi, abiEvent>> {
if (ABI_FN_RESOLUTION_CACHE.has(contractEvent as ContractEvent<AbiEvent>)) {
return ABI_FN_RESOLUTION_CACHE.get(
contractEvent as ContractEvent<AbiEvent>,
) as Promise<ParseEvent<abi, abiEvent>>;
}
const prom = (async () => {
if (isAbiEvent(contractEvent.event)) {
return contractEvent.event as ParseEvent<abi, abiEvent>;
}
// if the method starts with the string `event ` we always will want to try to parse it
if (contractEvent.event.startsWith("event ")) {
const abiItem = parseAbiItem(contractEvent.event);
if (abiItem.type === "event") {
return abiItem as ParseEvent<abi, abiEvent>;
}
throw new Error(`"method" passed is not of type "function"`);
}
// check if we have a "abi" on the contract
if (contractEvent.contract.abi && contractEvent.contract.abi?.length > 0) {
// extract the abiEv from it
const abiEv = contractEvent.contract.abi?.find(
(item) => item.type === "event" && item.name === contractEvent.event,
);
// if we were able to find it -> return it
if (isAbiEvent(abiEv)) {
return abiEv as ParseEvent<abi, abiEvent>;
}
}

// if we get here we need to async resolve the ABI and try to find the method on there
const { resolveContractAbi } = await import(
"../../contract/actions/resolve-abi.js"
);

const abi = await resolveContractAbi(contractEvent.contract);
// we try to find the abiEv in the abi
const abiEv = abi.find((item) => {
// if the item is not an event we can ignore it
if (item.type !== "event") {
return false;
}
// if the item is a function we can compare the name
return item.name === contractEvent.event;
}) as ParseEvent<abi, abiEvent> | undefined;

if (!abiEv) {
throw new Error(
`could not find event with name ${contractEvent.event} in abi`,
);
}
return abiEv;
})();
ABI_FN_RESOLUTION_CACHE.set(contractEvent as ContractEvent<AbiEvent>, prom);
return prom;
}
68 changes: 68 additions & 0 deletions packages/thirdweb/src/event/actions/watch-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { Abi, AbiEvent } from "abitype";
import type { GetLogsReturnType } from "viem";
import {
eth_getLogs,
getRpcClient,
watchBlockNumber,
} from "../../rpc/index.js";
import { resolveAbi } from "./resolve-abi.js";
import type { ContractEvent } from "../event.js";
import {
resolveContractAbi,
type ThirdwebContract,
} from "../../contract/index.js";

export type WatchContractEventsOptions<
abi extends Abi,
abiEvent extends AbiEvent,
contractEvent extends ContractEvent<abiEvent>,
> = {
onLogs: (
logs: GetLogsReturnType<undefined, abiEvent[], undefined, bigint, bigint>,
) => void | undefined;
contract: ThirdwebContract<abi>;
events?: contractEvent[] | undefined;
};

export function watchContractEvents<
const abi extends Abi,
const abiEvent extends AbiEvent,
const contractEvent extends ContractEvent<abiEvent>,
>(options: WatchContractEventsOptions<abi, abiEvent, contractEvent>) {
const rpcRequest = getRpcClient(options.contract);
const resolveAbiPromise = options.events
? Promise.all(options.events.map((e) => resolveAbi(e)))
: // if we don't have events passed then resolve the abi for the contract -> all events!
(resolveContractAbi(options.contract).then((abi) =>
abi.filter((item) => item.type === "event"),
) as Promise<abiEvent[]>);

// returning this returns the underlying "unwatch" function
return watchBlockNumber({
...options.contract,
onNewBlockNumber: async (blockNumber) => {
const parsedEvents = await resolveAbiPromise;

const logs = (await eth_getLogs(rpcRequest, {
// onNewBlockNumber fires exactly once per block
// => we want to get the logs for the block that just happened
// fromBlock is inclusive
fromBlock: blockNumber,
// toBlock is exclusive
toBlock: blockNumber,
address: options.contract.address,
events: parsedEvents,
})) as GetLogsReturnType<
undefined,
abiEvent[],
undefined,
bigint,
bigint
>;
// if there were any logs associated with our event(s)
if (logs.length) {
options.onLogs(logs);
}
},
});
}
80 changes: 0 additions & 80 deletions packages/thirdweb/src/event/actions/watch.ts

This file was deleted.

9 changes: 9 additions & 0 deletions packages/thirdweb/src/event/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,12 @@ export function contractEvent<
>(options: ContractEventInput<abi, event>) {
return options as unknown as ContractEvent<ParseEvent<abi, event>>;
}

export function isAbiEvent(item: unknown): item is AbiEvent {
return !!(
item &&
typeof item === "object" &&
"type" in item &&
item.type === "event"
);
}
3 changes: 2 additions & 1 deletion packages/thirdweb/src/event/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export {
type ContractEventInput,
} from "./event.js";

export { watch } from "./actions/watch.js";
export { watchContractEvents } from "./actions/watch-events.js";
export { getContractEvents } from "./actions/get-events.js";
4 changes: 2 additions & 2 deletions packages/thirdweb/src/gas/fee-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export async function getDynamicFeeData(
let maxFeePerGas: null | bigint = null;
let maxPriorityFeePerGas_: null | bigint = null;

const rpcRequest = getRpcClient(client, { chainId });
const rpcRequest = getRpcClient({ client, chainId });

const [block, maxPriorityFeePerGas] = await Promise.all([
eth_getBlockByNumber(rpcRequest, { blockTag: "latest" }),
Expand Down Expand Up @@ -103,7 +103,7 @@ export async function getGasPrice(
client: ThirdwebClient,
chainId: number,
): Promise<bigint> {
const rpcClient = getRpcClient(client, { chainId });
const rpcClient = getRpcClient({ client, chainId });
const gasPrice_ = await eth_gasPrice(rpcClient);
const maxGasPrice = 300n; // 300 gwei
const extraTip = (gasPrice_ / BigInt(100)) * BigInt(10);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { AbiFunction } from "abitype";
import { useMutation, type UseMutationResult } from "@tanstack/react-query";
import { estimateGas } from "../../transaction/index.js";
import type { Transaction } from "../../transaction/transaction.js";
import { useActiveWallet } from "../providers/wallet-provider.js";
import { estimateGas } from "../../../transaction/index.js";
import type { Transaction } from "../../../transaction/transaction.js";
import { useActiveWallet } from "../../providers/wallet-provider.js";

export function useEstimateGas<
const abiFn extends AbiFunction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ import {
type UseQueryOptions,
} from "@tanstack/react-query";
import type { Abi, AbiFunction, ExtractAbiFunctionNames } from "abitype";
import type { ParseMethod } from "../../abi/types.js";
import type { ReadOutputs } from "../../transaction/actions/read.js";
import { read } from "../../transaction/index.js";
import type { ParseMethod } from "../../../abi/types.js";
import type { ReadOutputs } from "../../../transaction/actions/read.js";
import { read } from "../../../transaction/index.js";
import {
type TransactionInput,
type TxOpts,
} from "../../transaction/transaction.js";
} from "../../../transaction/transaction.js";
import {
getExtensionId,
isReadExtension,
type ReadExtension,
} from "../../utils/extension.js";
import { stringify } from "../../utils/json.js";
} from "../../../utils/extension.js";
import { stringify } from "../../../utils/json.js";

type PickedQueryOptions = Pick<UseQueryOptions, "enabled">;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { AbiFunction } from "abitype";
import { useMutation, type UseMutationResult } from "@tanstack/react-query";
import type { Transaction } from "../../transaction/transaction.js";
import { useActiveWallet } from "../providers/wallet-provider.js";
import type { Transaction } from "../../../transaction/transaction.js";
import { useActiveWallet } from "../../providers/wallet-provider.js";
import type { Hex } from "viem";
import { sendTransaction } from "../../transaction/actions/send-transaction.js";
import { sendTransaction } from "../../../transaction/actions/send-transaction.js";

export function useSendTransaction<
const abiFn extends AbiFunction,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Abi } from "abitype";
import type { ThirdwebContract } from "../../contract/index.js";
import type { ThirdwebContract } from "../../../contract/index.js";
import { useQuery, type UseQueryResult } from "@tanstack/react-query";
import { waitForReceipt } from "../../transaction/index.js";
import { waitForReceipt } from "../../../transaction/index.js";
import type { TransactionReceipt } from "viem";

export function useWaitForReceipt<abi extends Abi>({
Expand Down
Loading

0 comments on commit 94f7fed

Please sign in to comment.