Skip to content

Commit 10a69d3

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

File tree

16 files changed

+797
-91
lines changed

16 files changed

+797
-91
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

.changeset/stupid-adults-flow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@thirdweb-dev/engine": patch
3+
---
4+
5+
Updated to latest API

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import type {
77
} from "@hey-api/client-fetch";
88
import { client as _heyApiClient } from "./client.gen.js";
99
import type {
10+
CreateAccountData,
11+
CreateAccountResponse,
1012
EncodeFunctionDataData,
1113
EncodeFunctionDataResponse,
1214
GetNativeBalanceData,
@@ -15,6 +17,8 @@ import type {
1517
GetTransactionAnalyticsResponse,
1618
GetTransactionAnalyticsSummaryData,
1719
GetTransactionAnalyticsSummaryResponse,
20+
ListAccountsData,
21+
ListAccountsResponse,
1822
ReadContractData,
1923
ReadContractResponse,
2024
SearchTransactionsData,
@@ -48,6 +52,56 @@ export type Options<
4852
meta?: Record<string, unknown>;
4953
};
5054

55+
/**
56+
* List Server Wallets
57+
* List all engine server wallets for the current project. Returns an array of EOA addresses with their corresponding predicted smart account addresses.
58+
*/
59+
export const listAccounts = <ThrowOnError extends boolean = false>(
60+
options?: Options<ListAccountsData, ThrowOnError>,
61+
) => {
62+
return (options?.client ?? _heyApiClient).get<
63+
ListAccountsResponse,
64+
unknown,
65+
ThrowOnError
66+
>({
67+
security: [
68+
{
69+
name: "x-secret-key",
70+
type: "apiKey",
71+
},
72+
],
73+
url: "/v1/accounts",
74+
...options,
75+
});
76+
};
77+
78+
/**
79+
* Create Server Wallet
80+
* Create a new engine server wallet. This is a helper route for creating a new EOA with your KMS provider, provided as a convenient alternative to creating an EOA directly with your KMS provider. Your KMS credentials are not stored, and usage of created accounts require your KMS credentials to be sent with requests.
81+
*/
82+
export const createAccount = <ThrowOnError extends boolean = false>(
83+
options?: Options<CreateAccountData, ThrowOnError>,
84+
) => {
85+
return (options?.client ?? _heyApiClient).post<
86+
CreateAccountResponse,
87+
unknown,
88+
ThrowOnError
89+
>({
90+
security: [
91+
{
92+
name: "x-secret-key",
93+
type: "apiKey",
94+
},
95+
],
96+
url: "/v1/accounts",
97+
...options,
98+
headers: {
99+
"Content-Type": "application/json",
100+
...options?.headers,
101+
},
102+
});
103+
};
104+
51105
/**
52106
* Write Contract
53107
* Call a write function on a contract.

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

Lines changed: 65 additions & 1 deletion
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 = {
@@ -92,6 +92,70 @@ export type AaZksyncExecutionOptions = {
9292
chainId: string;
9393
};
9494

95+
export type ListAccountsData = {
96+
body?: never;
97+
path?: never;
98+
query?: never;
99+
url: "/v1/accounts";
100+
};
101+
102+
export type ListAccountsResponses = {
103+
/**
104+
* Accounts retrieved successfully
105+
*/
106+
200: {
107+
result: Array<{
108+
/**
109+
* EVM address in hex format
110+
*/
111+
address: string;
112+
/**
113+
* The predicted smart account address for use with the default thirdweb v0.7 AccountFactory
114+
*/
115+
smartAccountAddress?: string;
116+
}>;
117+
};
118+
};
119+
120+
export type ListAccountsResponse =
121+
ListAccountsResponses[keyof ListAccountsResponses];
122+
123+
export type CreateAccountData = {
124+
body?: {
125+
label: string;
126+
};
127+
headers?: {
128+
/**
129+
* Vault Access Token used to access your EOA
130+
*/
131+
"x-vault-access-token"?: string;
132+
};
133+
path?: never;
134+
query?: never;
135+
url: "/v1/accounts";
136+
};
137+
138+
export type CreateAccountResponses = {
139+
/**
140+
* Account created successfully
141+
*/
142+
201: {
143+
result: {
144+
/**
145+
* EVM address in hex format
146+
*/
147+
address: string;
148+
/**
149+
* The predicted smart account address for use with the default thirdweb v0.7 AccountFactory
150+
*/
151+
smartAccountAddress?: string;
152+
};
153+
};
154+
};
155+
156+
export type CreateAccountResponse =
157+
CreateAccountResponses[keyof CreateAccountResponses];
158+
95159
export type WriteContractData = {
96160
body?: {
97161
/**

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;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { createAccount } from "@thirdweb-dev/engine";
2+
import { stringify } from "viem";
3+
import type { ThirdwebClient } from "../client/client.js";
4+
import { getThirdwebBaseUrl } from "../utils/domains.js";
5+
import { getClientFetch } from "../utils/fetch.js";
6+
7+
export type CreateServerWalletArgs = {
8+
client: ThirdwebClient;
9+
label: string;
10+
};
11+
12+
/**
13+
* Create a server wallet.
14+
* @param params - The parameters for the server wallet.
15+
* @param params.client - The thirdweb client to use.
16+
* @param params.label - The label for the server wallet.
17+
* @returns The server wallet signer address and the predicted smart account address.
18+
* @engine
19+
* @example
20+
* ```ts
21+
* import { Engine } from "thirdweb";
22+
*
23+
* const serverWallet = await Engine.createServerWallet({
24+
* client,
25+
* label: "My Server Wallet",
26+
* });
27+
* console.log(serverWallet.address);
28+
* console.log(serverWallet.smartAccountAddress);
29+
* ```
30+
*/
31+
export async function createServerWallet(params: CreateServerWalletArgs) {
32+
const { client, label } = params;
33+
const result = await createAccount({
34+
baseUrl: getThirdwebBaseUrl("engineCloud"),
35+
bodySerializer: stringify,
36+
fetch: getClientFetch(client),
37+
body: {
38+
label,
39+
},
40+
});
41+
42+
if (result.error) {
43+
throw new Error(
44+
`Error creating server wallet with label ${label}: ${stringify(
45+
result.error,
46+
)}`,
47+
);
48+
}
49+
50+
const data = result.data?.result;
51+
52+
if (!data) {
53+
throw new Error(`No server wallet created with label ${label}`);
54+
}
55+
56+
return data;
57+
}

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-
}

0 commit comments

Comments
 (0)