Skip to content

Commit f2d6360

Browse files
add enqueue and status API
1 parent 844c80c commit f2d6360

File tree

4 files changed

+327
-142
lines changed

4 files changed

+327
-142
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { searchTransactions } from "@thirdweb-dev/engine";
2+
import type { Chain } from "../chains/types.js";
3+
import { getCachedChain } from "../chains/utils.js";
4+
import type { ThirdwebClient } from "../client/client.js";
5+
import type { WaitForReceiptOptions } from "../transaction/actions/wait-for-tx-receipt.js";
6+
import { getThirdwebBaseUrl } from "../utils/domains.js";
7+
import type { Hex } from "../utils/encoding/hex.js";
8+
import { getClientFetch } from "../utils/fetch.js";
9+
import { stringify } from "../utils/json.js";
10+
import type { Prettify } from "../utils/type-utils.js";
11+
12+
export type RevertData = {
13+
errorName: string;
14+
errorArgs: Record<string, unknown>;
15+
};
16+
17+
type ExecutionResult4337Serialized =
18+
| {
19+
status: "QUEUED";
20+
}
21+
| {
22+
status: "FAILED";
23+
error: string;
24+
}
25+
| {
26+
status: "SUBMITTED";
27+
monitoringStatus: "WILL_MONITOR" | "CANNOT_MONITOR";
28+
userOpHash: string;
29+
}
30+
| ({
31+
status: "CONFIRMED";
32+
userOpHash: Hex;
33+
transactionHash: Hex;
34+
actualGasCost: string;
35+
actualGasUsed: string;
36+
nonce: string;
37+
} & (
38+
| {
39+
onchainStatus: "SUCCESS";
40+
}
41+
| {
42+
onchainStatus: "REVERTED";
43+
revertData?: RevertData;
44+
}
45+
));
46+
47+
export type ExecutionResult = Prettify<
48+
ExecutionResult4337Serialized & {
49+
chain: Chain;
50+
from: string | undefined;
51+
id: string;
52+
}
53+
>;
54+
55+
/**
56+
* Get the execution status of a transaction.
57+
* @param args - The arguments for the transaction.
58+
* @param args.client - The thirdweb client to use.
59+
* @param args.transactionId - The id of the transaction to get the status of.
60+
* @engine
61+
* @example
62+
* ```ts
63+
* import { Engine } from "thirdweb";
64+
*
65+
* const executionResult = await Engine.getTransactionStatus({
66+
* client,
67+
* transactionId,
68+
* });
69+
* console.log(executionResult.status);
70+
* ```
71+
*/
72+
export async function getTransactionStatus(args: {
73+
client: ThirdwebClient;
74+
transactionId: string;
75+
}): Promise<ExecutionResult> {
76+
const { client, transactionId } = args;
77+
const searchResult = await searchTransactions({
78+
baseUrl: getThirdwebBaseUrl("engineCloud"),
79+
fetch: getClientFetch(client),
80+
body: {
81+
filters: [
82+
{
83+
field: "id",
84+
values: [transactionId],
85+
operation: "OR",
86+
},
87+
],
88+
},
89+
});
90+
91+
if (searchResult.error) {
92+
throw new Error(
93+
`Error searching for transaction ${transactionId}: ${stringify(
94+
searchResult.error,
95+
)}`,
96+
);
97+
}
98+
99+
const data = searchResult.data?.result?.transactions?.[0];
100+
101+
if (!data) {
102+
throw new Error(`Transaction ${transactionId} not found`);
103+
}
104+
105+
const executionResult = data.executionResult as ExecutionResult4337Serialized;
106+
return {
107+
...executionResult,
108+
chain: getCachedChain(Number(data.chainId)),
109+
from: data.from ?? undefined,
110+
id: data.id,
111+
};
112+
}
113+
114+
/**
115+
* Wait for a transaction to be submitted onchain and return the transaction hash.
116+
* @param args - The arguments for the transaction.
117+
* @param args.client - The thirdweb client to use.
118+
* @param args.transactionId - The id of the transaction to wait for.
119+
* @param args.timeoutInSeconds - The timeout in seconds.
120+
* @engine
121+
* @example
122+
* ```ts
123+
* import { Engine } from "thirdweb";
124+
*
125+
* const { transactionHash } = await Engine.waitForTransactionHash({
126+
* client,
127+
* transactionId, // the transaction id returned from enqueueTransaction
128+
* });
129+
* ```
130+
*/
131+
export async function waitForTransactionHash(args: {
132+
client: ThirdwebClient;
133+
transactionId: string;
134+
timeoutInSeconds?: number;
135+
}): Promise<WaitForReceiptOptions> {
136+
const startTime = Date.now();
137+
const TIMEOUT_IN_MS = args.timeoutInSeconds
138+
? args.timeoutInSeconds * 1000
139+
: 5 * 60 * 1000; // 5 minutes in milliseconds
140+
141+
while (Date.now() - startTime < TIMEOUT_IN_MS) {
142+
const executionResult = await getTransactionStatus(args);
143+
const status = executionResult.status;
144+
145+
switch (status) {
146+
case "FAILED": {
147+
throw new Error(
148+
`Transaction failed: ${executionResult.error || "Unknown error"}`,
149+
);
150+
}
151+
case "CONFIRMED": {
152+
const onchainStatus =
153+
executionResult && "onchainStatus" in executionResult
154+
? executionResult.onchainStatus
155+
: null;
156+
if (onchainStatus === "REVERTED") {
157+
const revertData =
158+
"revertData" in executionResult
159+
? executionResult.revertData
160+
: undefined;
161+
throw new Error(
162+
`Transaction reverted: ${revertData?.errorName || ""} ${revertData?.errorArgs ? stringify(revertData.errorArgs) : ""}`,
163+
);
164+
}
165+
return {
166+
transactionHash: executionResult.transactionHash as Hex,
167+
client: args.client,
168+
chain: executionResult.chain,
169+
};
170+
}
171+
default: {
172+
// wait for the transaction to be confirmed
173+
await new Promise((resolve) => setTimeout(resolve, 1000));
174+
}
175+
}
176+
}
177+
throw new Error(
178+
`Transaction timed out after ${TIMEOUT_IN_MS / 1000} seconds`,
179+
);
180+
}
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,11 @@
1-
export { serverWallet, type ServerWalletOptions } from "./server-wallet.js";
1+
export {
2+
serverWallet,
3+
type ServerWalletOptions,
4+
type ServerWallet,
5+
} from "./server-wallet.js";
6+
export {
7+
getTransactionStatus,
8+
waitForTransactionHash,
9+
type ExecutionResult,
10+
type RevertData,
11+
} from "./get-status.js";

packages/thirdweb/src/engine/server-wallet.test.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { claimTo } from "../extensions/erc1155/drops/write/claimTo.js";
1010
import { getAllActiveSigners } from "../extensions/erc4337/__generated__/IAccountPermissions/read/getAllActiveSigners.js";
1111
import { sendTransaction } from "../transaction/actions/send-transaction.js";
1212
import { setThirdwebDomains } from "../utils/domains.js";
13-
import type { Account } from "../wallets/interfaces/wallet.js";
1413
import {
1514
DEFAULT_ACCOUNT_FACTORY_V0_6,
1615
ENTRYPOINT_ADDRESS_v0_6,
@@ -30,14 +29,14 @@ describe.runIf(
3029
retry: 0,
3130
},
3231
() => {
33-
let serverWallet: Account;
32+
let serverWallet: Engine.ServerWallet;
3433

3534
beforeAll(async () => {
3635
setThirdwebDomains({
3736
rpc: "rpc.thirdweb-dev.com",
3837
storage: "storage.thirdweb-dev.com",
3938
bundler: "bundler.thirdweb-dev.com",
40-
engineCloud: "localhost:3009", // "engine-cloud-dev-l8wt.chainsaw-dev.zeet.app",
39+
engineCloud: "engine.thirdweb-dev.com",
4140
});
4241
serverWallet = Engine.serverWallet({
4342
client: TEST_CLIENT,
@@ -61,7 +60,7 @@ describe.runIf(
6160
expect(signature).toBeDefined();
6261
});
6362

64-
it("should send a tx", async () => {
63+
it("should send a tx with regular API", async () => {
6564
const tx = await sendTransaction({
6665
account: serverWallet,
6766
transaction: {
@@ -74,6 +73,30 @@ describe.runIf(
7473
expect(tx).toBeDefined();
7574
});
7675

76+
it("should enqueue a tx", async () => {
77+
const nftContract = getContract({
78+
client: TEST_CLIENT,
79+
chain: sepolia,
80+
address: "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8",
81+
});
82+
const claimTx = claimTo({
83+
contract: nftContract,
84+
to: TEST_ACCOUNT_B.address,
85+
tokenId: 0n,
86+
quantity: 1n,
87+
});
88+
console.time("enqueue");
89+
const result = await serverWallet.enqueueTransaction({
90+
transaction: claimTx,
91+
});
92+
expect(result.transactionId).toBeDefined();
93+
const txHash = await Engine.waitForTransactionHash({
94+
client: TEST_CLIENT,
95+
transactionId: result.transactionId,
96+
});
97+
expect(txHash.transactionHash).toBeDefined();
98+
});
99+
77100
it("should send a extension tx", async () => {
78101
const nftContract = getContract({
79102
client: TEST_CLIENT,

0 commit comments

Comments
 (0)