Skip to content

Commit 7056a9f

Browse files
[Engine] Add search transactions and batch transaction support
1 parent e8a25d7 commit 7056a9f

File tree

12 files changed

+547
-92
lines changed

12 files changed

+547
-92
lines changed

.changeset/engine-enhancements.md

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

packages/engine/src/client/types.gen.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export type TransactionsFilterValue = {
99
| "smartAccountAddress"
1010
| "chainId";
1111
values: Array<string>;
12-
operation: "AND" | "OR";
12+
operation?: "AND" | "OR";
1313
};
1414

1515
export type TransactionsFilterNested = {
@@ -1018,5 +1018,5 @@ export type GetTransactionAnalyticsSummaryResponse =
10181018
GetTransactionAnalyticsSummaryResponses[keyof GetTransactionAnalyticsSummaryResponses];
10191019

10201020
export type ClientOptions = {
1021-
baseUrl: "https://engine.thirdweb.com" | (string & {});
1021+
baseUrl: "https://engine.thirdweb-dev.com" | (string & {});
10221022
};

packages/thirdweb/src/bridge/Chains.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
22
import { TEST_CLIENT } from "~test/test-clients.js";
33
import { chains } from "./Chains.js";
44

5-
describe("chains", () => {
5+
describe.runIf(process.env.TW_SECRET_KEY)("chains", () => {
66
it("should fetch chains", async () => {
77
// Setup
88
const client = TEST_CLIENT;

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: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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+
* },
37+
* ],
38+
* });
39+
* console.log(transactions);
40+
* ```
41+
*
42+
* ## Search for transactions by chain id
43+
*
44+
* ```ts
45+
* import { Engine } from "thirdweb";
46+
*
47+
* const transactions = await Engine.searchTransactions({
48+
* client,
49+
* filters: [
50+
* {
51+
* field: "chainId",
52+
* values: ["1", "137"],
53+
* },
54+
* ],
55+
* });
56+
* console.log(transactions);
57+
* ```
58+
*
59+
* ## Search for transactions by sender wallet address
60+
*
61+
* ```ts
62+
* import { Engine } from "thirdweb";
63+
*
64+
* const transactions = await Engine.searchTransactions({
65+
* client,
66+
* filters: [
67+
* {
68+
* field: "from",
69+
* values: ["0x1234567890123456789012345678901234567890"],
70+
* },
71+
* ],
72+
* });
73+
* console.log(transactions);
74+
* ```
75+
*
76+
* ## Combined search
77+
*
78+
* ```ts
79+
* import { Engine } from "thirdweb";
80+
*
81+
* const transactions = await Engine.searchTransactions({
82+
* client,
83+
* filters: [
84+
* {
85+
* filters: [
86+
* {
87+
* field: "from",
88+
* values: ["0x1234567890123456789012345678901234567890"],
89+
* },
90+
* {
91+
* field: "chainId",
92+
* values: ["8453"],
93+
* },
94+
* ],
95+
* operation: "AND",
96+
* },
97+
* ],
98+
* pageSize: 100,
99+
* page: 0,
100+
* });
101+
* console.log(transactions);
102+
* ```
103+
*/
104+
export async function searchTransactions(args: SearchTransactionsArgs) {
105+
const { client, filters, pageSize = 100, page = 1 } = args;
106+
const searchResult = await engineSearchTransactions({
107+
baseUrl: getThirdwebBaseUrl("engineCloud"),
108+
bodySerializer: stringify,
109+
fetch: getClientFetch(client),
110+
body: {
111+
filters,
112+
limit: pageSize,
113+
page,
114+
},
115+
});
116+
117+
if (searchResult.error) {
118+
throw new Error(
119+
`Error searching for transaction with filters ${stringify(filters)}: ${stringify(
120+
searchResult.error,
121+
)}`,
122+
);
123+
}
124+
125+
const data = searchResult.data?.result;
126+
127+
if (!data) {
128+
throw new Error(`No transactions found with filters ${stringify(filters)}`);
129+
}
130+
131+
return data;
132+
}

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)