Skip to content

Commit ea3e997

Browse files
[Engine] Add search transactions and batch transaction support
1 parent 97b8926 commit ea3e997

File tree

8 files changed

+515
-88
lines changed

8 files changed

+515
-88
lines changed

.changeset/engine-enhancements.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
"thirdweb": minor
3+
---
4+
5+
Enhanced Engine functionality with search transactions and batch transaction support:
6+
7+
- Added `Engine.searchTransactions()` to search for transactions by various filters (id, chainId, from address, etc.)
8+
9+
```ts
10+
// Search by transaction IDs
11+
const transactions = await Engine.searchTransactions({
12+
client,
13+
filters: [
14+
{
15+
field: "id",
16+
values: ["1", "2", "3"],
17+
operation: "OR",
18+
},
19+
],
20+
});
21+
22+
// Search by chain ID and sender address
23+
const transactions = await Engine.searchTransactions({
24+
client,
25+
filters: [
26+
{
27+
filters: [
28+
{
29+
field: "from",
30+
values: ["0x1234567890123456789012345678901234567890"],
31+
operation: "OR",
32+
},
33+
{
34+
field: "chainId",
35+
values: ["8453"],
36+
operation: "OR",
37+
},
38+
],
39+
operation: "AND",
40+
},
41+
],
42+
pageSize: 100,
43+
page: 0,
44+
});
45+
```
46+
47+
- Added `serverWallet.enqueueBatchTransaction()` to enqueue multiple transactions in a single batch
48+
49+
```ts
50+
// Prepare multiple transactions
51+
const transaction1 = claimTo({
52+
contract,
53+
to: firstRecipient,
54+
quantity: 1n,
55+
});
56+
const transaction2 = claimTo({
57+
contract,
58+
to: secondRecipient,
59+
quantity: 1n,
60+
});
61+
62+
// Enqueue as a batch
63+
const { transactionId } = await serverWallet.enqueueBatchTransaction({
64+
transactions: [transaction1, transaction2],
65+
});
66+
67+
// Wait for batch completion
68+
const { transactionHash } = await Engine.waitForTransactionHash({
69+
client,
70+
transactionId,
71+
});
72+
```
73+
74+
- Improved server wallet transaction handling with better error reporting

packages/thirdweb/src/engine/get-status.ts

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { searchTransactions } from "@thirdweb-dev/engine";
22
import type { Chain } from "../chains/types.js";
33
import { getCachedChain } from "../chains/utils.js";
44
import type { ThirdwebClient } from "../client/client.js";
5-
import type { WaitForReceiptOptions } from "../transaction/actions/wait-for-tx-receipt.js";
65
import { getThirdwebBaseUrl } from "../utils/domains.js";
76
import type { Hex } from "../utils/encoding/hex.js";
87
import { getClientFetch } from "../utils/fetch.js";
@@ -117,71 +116,3 @@ export async function getTransactionStatus(args: {
117116
id: data.id,
118117
};
119118
}
120-
121-
/**
122-
* Wait for a transaction to be submitted onchain and return the transaction hash.
123-
* @param args - The arguments for the transaction.
124-
* @param args.client - The thirdweb client to use.
125-
* @param args.transactionId - The id of the transaction to wait for.
126-
* @param args.timeoutInSeconds - The timeout in seconds.
127-
* @engine
128-
* @example
129-
* ```ts
130-
* import { Engine } from "thirdweb";
131-
*
132-
* const { transactionHash } = await Engine.waitForTransactionHash({
133-
* client,
134-
* transactionId, // the transaction id returned from enqueueTransaction
135-
* });
136-
* ```
137-
*/
138-
export async function waitForTransactionHash(args: {
139-
client: ThirdwebClient;
140-
transactionId: string;
141-
timeoutInSeconds?: number;
142-
}): Promise<WaitForReceiptOptions> {
143-
const startTime = Date.now();
144-
const TIMEOUT_IN_MS = args.timeoutInSeconds
145-
? args.timeoutInSeconds * 1000
146-
: 5 * 60 * 1000; // 5 minutes in milliseconds
147-
148-
while (Date.now() - startTime < TIMEOUT_IN_MS) {
149-
const executionResult = await getTransactionStatus(args);
150-
const status = executionResult.status;
151-
152-
switch (status) {
153-
case "FAILED": {
154-
throw new Error(
155-
`Transaction failed: ${executionResult.error || "Unknown error"}`,
156-
);
157-
}
158-
case "CONFIRMED": {
159-
const onchainStatus =
160-
executionResult && "onchainStatus" in executionResult
161-
? executionResult.onchainStatus
162-
: null;
163-
if (onchainStatus === "REVERTED") {
164-
const revertData =
165-
"revertData" in executionResult
166-
? executionResult.revertData
167-
: undefined;
168-
throw new Error(
169-
`Transaction reverted: ${revertData?.errorName || ""} ${revertData?.errorArgs ? stringify(revertData.errorArgs) : ""}`,
170-
);
171-
}
172-
return {
173-
transactionHash: executionResult.transactionHash as Hex,
174-
client: args.client,
175-
chain: executionResult.chain,
176-
};
177-
}
178-
default: {
179-
// wait for the transaction to be confirmed
180-
await new Promise((resolve) => setTimeout(resolve, 1000));
181-
}
182-
}
183-
}
184-
throw new Error(
185-
`Transaction timed out after ${TIMEOUT_IN_MS / 1000} seconds`,
186-
);
187-
}

packages/thirdweb/src/engine/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ export {
55
} from "./server-wallet.js";
66
export {
77
getTransactionStatus,
8-
waitForTransactionHash,
98
type ExecutionResult,
109
type RevertData,
1110
} from "./get-status.js";
11+
export { waitForTransactionHash } from "./wait-for-tx-hash.js";
12+
export {
13+
searchTransactions,
14+
type SearchTransactionsArgs,
15+
} from "./search-transactions.js";
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import {
2+
type TransactionsFilterNested,
3+
type TransactionsFilterValue,
4+
searchTransactions as engineSearchTransactions,
5+
} from "@thirdweb-dev/engine";
6+
import type { ThirdwebClient } from "../client/client.js";
7+
import { getThirdwebBaseUrl } from "../utils/domains.js";
8+
import { getClientFetch } from "../utils/fetch.js";
9+
import { stringify } from "../utils/json.js";
10+
11+
export type SearchTransactionsArgs = {
12+
client: ThirdwebClient;
13+
filters?: (TransactionsFilterValue | TransactionsFilterNested)[];
14+
pageSize?: number;
15+
page?: number;
16+
};
17+
18+
/**
19+
* Search for transactions by their ids.
20+
* @param args - The arguments for the search.
21+
* @param args.client - The thirdweb client to use.
22+
* @param args.transactionIds - The ids of the transactions to search for.
23+
* @engine
24+
* @example
25+
* ## Search for transactions by their ids
26+
*
27+
* ```ts
28+
* import { Engine } from "thirdweb";
29+
*
30+
* const transactions = await Engine.searchTransactions({
31+
* client,
32+
* filters: [
33+
* {
34+
* field: "id",
35+
* values: ["1", "2", "3"],
36+
* operation: "OR",
37+
* },
38+
* ],
39+
* });
40+
* console.log(transactions);
41+
* ```
42+
*
43+
* ## Search for transactions by chain id
44+
*
45+
* ```ts
46+
* import { Engine } from "thirdweb";
47+
*
48+
* const transactions = await Engine.searchTransactions({
49+
* client,
50+
* filters: [
51+
* {
52+
* field: "chainId",
53+
* values: ["1", "137"],
54+
* operation: "OR",
55+
* },
56+
* ],
57+
* });
58+
* console.log(transactions);
59+
* ```
60+
*
61+
* ## Search for transactions by sender wallet address
62+
*
63+
* ```ts
64+
* import { Engine } from "thirdweb";
65+
*
66+
* const transactions = await Engine.searchTransactions({
67+
* client,
68+
* filters: [
69+
* {
70+
* field: "from",
71+
* values: ["0x1234567890123456789012345678901234567890"],
72+
* operation: "OR",
73+
* },
74+
* ],
75+
* });
76+
* console.log(transactions);
77+
* ```
78+
*
79+
* ## Combined search
80+
*
81+
* ```ts
82+
* import { Engine } from "thirdweb";
83+
*
84+
* const transactions = await Engine.searchTransactions({
85+
* client,
86+
* filters: [
87+
* {
88+
* filters: [
89+
* {
90+
* field: "from",
91+
* values: ["0x1234567890123456789012345678901234567890"],
92+
* operation: "OR",
93+
* },
94+
* {
95+
* field: "chainId",
96+
* values: ["8453"],
97+
* operation: "OR",
98+
* },
99+
* ],
100+
* operation: "AND",
101+
* },
102+
* ],
103+
* pageSize: 100,
104+
* page: 0,
105+
* });
106+
* console.log(transactions);
107+
* ```
108+
*/
109+
export async function searchTransactions(args: SearchTransactionsArgs) {
110+
const { client, filters, pageSize = 100, page = 0 } = args;
111+
const searchResult = await engineSearchTransactions({
112+
baseUrl: getThirdwebBaseUrl("engineCloud"),
113+
bodySerializer: stringify,
114+
fetch: getClientFetch(client),
115+
body: {
116+
filters,
117+
limit: pageSize || 100,
118+
page: page || 1,
119+
},
120+
});
121+
122+
if (searchResult.error) {
123+
throw new Error(
124+
`Error searching for transaction with filters ${stringify(filters)}: ${stringify(
125+
searchResult.error,
126+
)}`,
127+
);
128+
}
129+
130+
const data = searchResult.data?.result;
131+
132+
if (!data) {
133+
throw new Error(`No transactions found with filters ${stringify(filters)}`);
134+
}
135+
136+
return data;
137+
}

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,16 @@ describe.runIf(
9595
transactionId: result.transactionId,
9696
});
9797
expect(txHash.transactionHash).toBeDefined();
98+
99+
const res = await Engine.searchTransactions({
100+
client: TEST_CLIENT,
101+
filters: [
102+
{ field: "id", values: [result.transactionId], operation: "OR" },
103+
],
104+
});
105+
expect(res).toBeDefined();
106+
expect(res.transactions.length).toBe(1);
107+
expect(res.transactions[0]?.id).toBe(result.transactionId);
98108
});
99109

100110
it("should send a extension tx", async () => {
@@ -115,6 +125,33 @@ describe.runIf(
115125
expect(tx).toBeDefined();
116126
});
117127

128+
it("should enqueue a batch of txs", async () => {
129+
const tokenContract = getContract({
130+
client: TEST_CLIENT,
131+
chain: baseSepolia,
132+
address: "0x87C52295891f208459F334975a3beE198fE75244",
133+
});
134+
const claimTx1 = mintTo({
135+
contract: tokenContract,
136+
to: serverWallet.address,
137+
amount: "0.001",
138+
});
139+
const claimTx2 = mintTo({
140+
contract: tokenContract,
141+
to: serverWallet.address,
142+
amount: "0.002",
143+
});
144+
const tx = await serverWallet.enqueueBatchTransaction({
145+
transactions: [claimTx1, claimTx2],
146+
});
147+
expect(tx.transactionId).toBeDefined();
148+
const txHash = await Engine.waitForTransactionHash({
149+
client: TEST_CLIENT,
150+
transactionId: tx.transactionId,
151+
});
152+
expect(txHash.transactionHash).toBeDefined();
153+
});
154+
118155
it("should get revert reason", async () => {
119156
const nftContract = getContract({
120157
client: TEST_CLIENT,

0 commit comments

Comments
 (0)