thirdweb 5.103.0 → 5.103.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/cjs/cli/bin.js +2 -2
  2. package/dist/cjs/cli/bin.js.map +1 -1
  3. package/dist/cjs/react/core/hooks/useTransactionDetails.js +1 -1
  4. package/dist/cjs/react/core/hooks/useTransactionDetails.js.map +1 -1
  5. package/dist/cjs/react/core/hooks/wallets/useSendToken.js +17 -2
  6. package/dist/cjs/react/core/hooks/wallets/useSendToken.js.map +1 -1
  7. package/dist/cjs/react/web/ui/Bridge/CheckoutWidget.js +0 -11
  8. package/dist/cjs/react/web/ui/Bridge/CheckoutWidget.js.map +1 -1
  9. package/dist/cjs/react/web/ui/Bridge/TransactionWidget.js +0 -17
  10. package/dist/cjs/react/web/ui/Bridge/TransactionWidget.js.map +1 -1
  11. package/dist/cjs/version.js +1 -1
  12. package/dist/esm/cli/bin.js +2 -2
  13. package/dist/esm/cli/bin.js.map +1 -1
  14. package/dist/esm/react/core/hooks/useTransactionDetails.js +1 -1
  15. package/dist/esm/react/core/hooks/useTransactionDetails.js.map +1 -1
  16. package/dist/esm/react/core/hooks/wallets/useSendToken.js +17 -2
  17. package/dist/esm/react/core/hooks/wallets/useSendToken.js.map +1 -1
  18. package/dist/esm/react/web/ui/Bridge/CheckoutWidget.js +0 -11
  19. package/dist/esm/react/web/ui/Bridge/CheckoutWidget.js.map +1 -1
  20. package/dist/esm/react/web/ui/Bridge/TransactionWidget.js +0 -17
  21. package/dist/esm/react/web/ui/Bridge/TransactionWidget.js.map +1 -1
  22. package/dist/esm/version.js +1 -1
  23. package/dist/types/react/core/hooks/wallets/useSendToken.d.ts.map +1 -1
  24. package/dist/types/react/web/ui/Bridge/CheckoutWidget.d.ts +0 -11
  25. package/dist/types/react/web/ui/Bridge/CheckoutWidget.d.ts.map +1 -1
  26. package/dist/types/react/web/ui/Bridge/TransactionWidget.d.ts +0 -17
  27. package/dist/types/react/web/ui/Bridge/TransactionWidget.d.ts.map +1 -1
  28. package/dist/types/version.d.ts +1 -1
  29. package/dist/types/wallets/utils/getWalletBalance.d.ts +1 -1
  30. package/dist/types/wallets/utils/getWalletBalance.d.ts.map +1 -1
  31. package/package.json +1 -1
  32. package/src/bridge/Status.test.ts +1 -1
  33. package/src/cli/bin.ts +2 -2
  34. package/src/react/core/hooks/useTransactionDetails.ts +1 -1
  35. package/src/react/core/hooks/wallets/useSendToken.ts +17 -3
  36. package/src/react/core/machines/paymentMachine.test.ts +0 -172
  37. package/src/react/web/adapters/adapters.test.ts +0 -8
  38. package/src/react/web/ui/Bridge/CheckoutWidget.tsx +0 -11
  39. package/src/react/web/ui/Bridge/TransactionWidget.tsx +0 -17
  40. package/src/version.ts +1 -1
  41. package/src/wallets/utils/getWalletBalance.ts +1 -1
  42. package/src/react/core/hooks/useBridgeError.test.ts +0 -172
  43. package/src/react/core/hooks/usePaymentMethods.test.ts +0 -336
@@ -3,12 +3,14 @@ import type { ThirdwebClient } from "../../../../client/client.js";
3
3
  import { getContract } from "../../../../contract/contract.js";
4
4
  import { resolveAddress } from "../../../../extensions/ens/resolve-address.js";
5
5
  import { transfer } from "../../../../extensions/erc20/write/transfer.js";
6
+ import { estimateGas } from "../../../../transaction/actions/estimate-gas.js";
6
7
  import { sendTransaction } from "../../../../transaction/actions/send-transaction.js";
7
8
  import { waitForReceipt } from "../../../../transaction/actions/wait-for-tx-receipt.js";
8
9
  import { prepareTransaction } from "../../../../transaction/prepare-transaction.js";
9
10
  import { isAddress } from "../../../../utils/address.js";
10
11
  import { isValidENSName } from "../../../../utils/ens/isValidENSName.js";
11
12
  import { toWei } from "../../../../utils/units.js";
13
+ import { getWalletBalance } from "../../../../wallets/utils/getWalletBalance.js";
12
14
  import { invalidateWalletBalance } from "../../providers/invalidateWalletBalance.js";
13
15
  import { useActiveWallet } from "./useActiveWallet.js";
14
16
 
@@ -85,13 +87,24 @@ export function useSendToken(client: ThirdwebClient) {
85
87
  to,
86
88
  value: toWei(amount),
87
89
  });
90
+ const gasEstimate = await estimateGas({
91
+ transaction: sendNativeTokenTx,
92
+ account,
93
+ });
94
+ const balance = await getWalletBalance({
95
+ address: account.address,
96
+ chain: activeChain,
97
+ client,
98
+ });
99
+ if (toWei(amount) + gasEstimate > balance.value) {
100
+ throw new Error("Insufficient balance for transfer amount and gas");
101
+ }
88
102
 
89
- return sendTransaction({
103
+ return await sendTransaction({
90
104
  transaction: sendNativeTokenTx,
91
105
  account,
92
106
  });
93
107
  }
94
-
95
108
  // erc20 token transfer
96
109
  else {
97
110
  const contract = getContract({
@@ -106,7 +119,7 @@ export function useSendToken(client: ThirdwebClient) {
106
119
  to,
107
120
  });
108
121
 
109
- return sendTransaction({
122
+ return await sendTransaction({
110
123
  transaction: tx,
111
124
  account,
112
125
  });
@@ -121,6 +134,7 @@ export function useSendToken(client: ThirdwebClient) {
121
134
  transactionHash: data.transactionHash,
122
135
  client,
123
136
  chain: data.chain,
137
+ maxBlocksWaitTime: 10_000,
124
138
  });
125
139
  }
126
140
  invalidateWalletBalance(queryClient);
@@ -124,114 +124,6 @@ describe("PaymentMachine", () => {
124
124
  expect(state.context.adapters).toBe(adapters);
125
125
  });
126
126
 
127
- it("should transition through happy path with wallet payment method", () => {
128
- const { result } = renderHook(() =>
129
- usePaymentMachine(adapters, "fund_wallet"),
130
- );
131
-
132
- // Confirm destination
133
- act(() => {
134
- const [, send] = result.current;
135
- send({
136
- type: "DESTINATION_CONFIRMED",
137
- destinationToken: testTokenForPayment,
138
- destinationAmount: "100",
139
- receiverAddress: "0xa3841994009B4fEabb01ebcC62062F9E56F701CD",
140
- });
141
- });
142
-
143
- let [state] = result.current;
144
- expect(state.value).toBe("methodSelection");
145
- expect(state.context.destinationToken).toEqual(testTokenForPayment);
146
- expect(state.context.destinationAmount).toBe("100");
147
- expect(state.context.receiverAddress).toBe(
148
- "0xa3841994009B4fEabb01ebcC62062F9E56F701CD",
149
- );
150
-
151
- // Select wallet payment method
152
- const walletPaymentMethod: PaymentMethod = {
153
- type: "wallet",
154
- originToken: testUSDCToken,
155
- payerWallet: TEST_IN_APP_WALLET_A,
156
- balance: 1000000000000000000n,
157
- };
158
-
159
- act(() => {
160
- const [, send] = result.current;
161
- send({
162
- type: "PAYMENT_METHOD_SELECTED",
163
- paymentMethod: walletPaymentMethod,
164
- });
165
- });
166
-
167
- [state] = result.current;
168
- expect(state.value).toBe("quote");
169
- expect(state.context.selectedPaymentMethod).toEqual(walletPaymentMethod);
170
-
171
- // Receive quote
172
- act(() => {
173
- const [, send] = result.current;
174
- send({
175
- type: "QUOTE_RECEIVED",
176
- preparedQuote: mockBuyQuote,
177
- });
178
- });
179
-
180
- [state] = result.current;
181
- expect(state.value).toBe("preview");
182
- expect(state.context.preparedQuote).toBe(mockBuyQuote);
183
-
184
- // Confirm route
185
- act(() => {
186
- const [, send] = result.current;
187
- send({
188
- type: "ROUTE_CONFIRMED",
189
- });
190
- });
191
-
192
- [state] = result.current;
193
- expect(state.value).toBe("execute");
194
- expect(state.context.selectedPaymentMethod).toBe(walletPaymentMethod);
195
-
196
- // Complete execution
197
- act(() => {
198
- const [, send] = result.current;
199
- send({
200
- type: "EXECUTION_COMPLETE",
201
- completedStatuses: [
202
- {
203
- type: "buy",
204
- status: "COMPLETED",
205
- paymentId: "test-payment-id",
206
- originAmount: 1000000000000000000n,
207
- destinationAmount: 100000000n,
208
- originChainId: 1,
209
- destinationChainId: 137,
210
- originTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
211
- destinationTokenAddress:
212
- "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
213
- originToken: testETHToken,
214
- destinationToken: testUSDCToken,
215
- sender: "0xa3841994009B4fEabb01ebcC62062F9E56F701CD",
216
- receiver: "0xa3841994009B4fEabb01ebcC62062F9E56F701CD",
217
- transactions: [
218
- {
219
- chainId: 1,
220
- transactionHash: "0xtest123",
221
- },
222
- ],
223
- },
224
- ],
225
- });
226
- });
227
-
228
- [state] = result.current;
229
- expect(state.value).toBe("success");
230
- expect(state.context.completedStatuses).toBeDefined();
231
- expect(state.context.completedStatuses).toHaveLength(1);
232
- expect(state.context.completedStatuses?.[0]?.status).toBe("COMPLETED");
233
- });
234
-
235
127
  it("should handle errors and allow retry", () => {
236
128
  const { result } = renderHook(() =>
237
129
  usePaymentMachine(adapters, "fund_wallet"),
@@ -517,70 +409,6 @@ describe("PaymentMachine", () => {
517
409
  expect(state.value).toBe("init");
518
410
  });
519
411
 
520
- it("should clear prepared quote when payment method changes", () => {
521
- const { result } = renderHook(() =>
522
- usePaymentMachine(adapters, "fund_wallet"),
523
- );
524
-
525
- // Go to methodSelection
526
- act(() => {
527
- const [, send] = result.current;
528
- send({
529
- type: "DESTINATION_CONFIRMED",
530
- destinationToken: testTokenForPayment,
531
- destinationAmount: "100",
532
- receiverAddress: "0xa3841994009B4fEabb01ebcC62062F9E56F701CD",
533
- });
534
- });
535
-
536
- // Select first payment method and get quote
537
- act(() => {
538
- const [, send] = result.current;
539
- send({
540
- type: "PAYMENT_METHOD_SELECTED",
541
- paymentMethod: {
542
- type: "fiat",
543
- currency: "USD",
544
- payerWallet: TEST_IN_APP_WALLET_A,
545
- onramp: "stripe",
546
- },
547
- });
548
- });
549
-
550
- act(() => {
551
- const [, send] = result.current;
552
- send({
553
- type: "QUOTE_RECEIVED",
554
- preparedQuote: mockBuyQuote,
555
- });
556
- });
557
-
558
- let [state] = result.current;
559
- expect(state.context.preparedQuote).toBe(mockBuyQuote);
560
-
561
- // Go back and select different payment method
562
- act(() => {
563
- const [, send] = result.current;
564
- send({ type: "BACK" });
565
- });
566
-
567
- act(() => {
568
- const [, send] = result.current;
569
- send({
570
- type: "PAYMENT_METHOD_SELECTED",
571
- paymentMethod: {
572
- type: "wallet",
573
- payerWallet: TEST_IN_APP_WALLET_A,
574
- originToken: testUSDCToken,
575
- balance: 1000000000000000000n,
576
- },
577
- });
578
- });
579
-
580
- [state] = result.current;
581
- expect(state.context.preparedQuote).toBeUndefined(); // Should be cleared
582
- });
583
-
584
412
  it("should handle post-buy-transaction state flow", () => {
585
413
  const { result } = renderHook(() =>
586
414
  usePaymentMachine(adapters, "fund_wallet"),
@@ -27,12 +27,4 @@ describe("WebWindowAdapter", () => {
27
27
  "noopener,noreferrer",
28
28
  );
29
29
  });
30
-
31
- it("should throw error when popup is blocked", async () => {
32
- mockOpen.mockReturnValue(null);
33
-
34
- await expect(windowAdapter.open("https://example.com")).rejects.toThrow(
35
- "Failed to open URL - popup may be blocked",
36
- );
37
- });
38
30
  });
@@ -198,17 +198,6 @@ type UIOptionsResult =
198
198
  * />
199
199
  * ```
200
200
  *
201
- * ### Enable/Disable payment methods
202
- *
203
- * You can use `disableOnramps` to prevent the use of onramps in the widget.
204
- *
205
- * ```tsx
206
- * <CheckoutWidget
207
- * client={client}
208
- * disableOnramps
209
- * />
210
- * ```
211
- *
212
201
  * ### Customize the UI
213
202
  *
214
203
  * You can customize the UI of the `CheckoutWidget` component by passing a custom theme object to the `theme` prop.
@@ -201,23 +201,6 @@ type UIOptionsResult =
201
201
  * />
202
202
  * ```
203
203
  *
204
- * ### Enable/Disable payment methods
205
- *
206
- * You can use `disableOnramps` to prevent the use of onramps in the widget.
207
- *
208
- * ```tsx
209
- * <TransactionWidget
210
- * client={client}
211
- * transaction={prepareTransaction({
212
- * to: "0x...",
213
- * chain: ethereum,
214
- * client: client,
215
- * value: toUnits("0.001", 18),
216
- * })}
217
- * disableOnramps
218
- * />
219
- * ```
220
- *
221
204
  * ### Customize the UI
222
205
  *
223
206
  * You can customize the UI of the `TransactionWidget` component by passing a custom theme object to the `theme` prop.
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = "5.103.0";
1
+ export const version = "5.103.1";
@@ -41,7 +41,7 @@ export type GetWalletBalanceResult = GetBalanceResult;
41
41
  */
42
42
  export async function getWalletBalance(
43
43
  options: GetWalletBalanceOptions,
44
- ): Promise<GetWalletBalanceResult> {
44
+ ): Promise<GetBalanceResult> {
45
45
  const { address, client, chain, tokenAddress } = options;
46
46
  // erc20 case
47
47
  if (tokenAddress) {
@@ -1,172 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { ApiError } from "../../../bridge/types/Errors.js";
3
- import { useBridgeError } from "./useBridgeError.js";
4
-
5
- describe("useBridgeError", () => {
6
- it("should handle null error", () => {
7
- const result = useBridgeError({ error: null });
8
-
9
- expect(result).toEqual({
10
- mappedError: null,
11
- isRetryable: false,
12
- userMessage: "",
13
- errorCode: null,
14
- statusCode: null,
15
- isClientError: false,
16
- isServerError: false,
17
- });
18
- });
19
-
20
- it("should handle undefined error", () => {
21
- const result = useBridgeError({ error: undefined });
22
-
23
- expect(result).toEqual({
24
- mappedError: null,
25
- isRetryable: false,
26
- userMessage: "",
27
- errorCode: null,
28
- statusCode: null,
29
- isClientError: false,
30
- isServerError: false,
31
- });
32
- });
33
-
34
- it("should process ApiError correctly", () => {
35
- const apiError = new ApiError({
36
- code: "INVALID_INPUT",
37
- message: "Invalid parameters provided",
38
- statusCode: 400,
39
- });
40
-
41
- const result = useBridgeError({ error: apiError });
42
-
43
- expect(result.mappedError).toBeInstanceOf(ApiError);
44
- expect(result.errorCode).toBe("INVALID_INPUT");
45
- expect(result.statusCode).toBe(400);
46
- expect(result.isClientError).toBe(true);
47
- expect(result.isServerError).toBe(false);
48
- expect(result.isRetryable).toBe(false); // INVALID_INPUT is not retryable
49
- expect(result.userMessage).toBe(
50
- "Invalid input provided. Please check your parameters and try again.",
51
- );
52
- });
53
-
54
- it("should convert generic Error to ApiError", () => {
55
- const genericError = new Error("Network connection failed");
56
-
57
- const result = useBridgeError({ error: genericError });
58
-
59
- expect(result.mappedError).toBeInstanceOf(ApiError);
60
- expect(result.errorCode).toBe("UNKNOWN_ERROR");
61
- expect(result.statusCode).toBe(500);
62
- expect(result.isClientError).toBe(false);
63
- expect(result.isServerError).toBe(true);
64
- expect(result.isRetryable).toBe(true);
65
- expect(result.userMessage).toBe(
66
- "An unexpected error occurred. Please try again.",
67
- );
68
- });
69
-
70
- it("should identify server errors correctly", () => {
71
- const serverError = new ApiError({
72
- code: "INTERNAL_SERVER_ERROR",
73
- message: "Server error",
74
- statusCode: 500,
75
- });
76
-
77
- const result = useBridgeError({ error: serverError });
78
-
79
- expect(result.statusCode).toBe(500);
80
- expect(result.isClientError).toBe(false);
81
- expect(result.isServerError).toBe(true);
82
- expect(result.isRetryable).toBe(true); // INTERNAL_SERVER_ERROR is retryable
83
- expect(result.userMessage).toBe(
84
- "A temporary error occurred. Please try again in a moment.",
85
- );
86
- });
87
-
88
- it("should provide user-friendly messages for known error codes", () => {
89
- // Test INVALID_INPUT
90
- const invalidInputError = new ApiError({
91
- code: "INVALID_INPUT",
92
- message: "Technical error message",
93
- statusCode: 400,
94
- });
95
- const invalidInputResult = useBridgeError({ error: invalidInputError });
96
- expect(invalidInputResult.userMessage).toBe(
97
- "Invalid input provided. Please check your parameters and try again.",
98
- );
99
-
100
- // Test INTERNAL_SERVER_ERROR
101
- const serverError = new ApiError({
102
- code: "INTERNAL_SERVER_ERROR",
103
- message: "Technical error message",
104
- statusCode: 500,
105
- });
106
- const serverResult = useBridgeError({ error: serverError });
107
- expect(serverResult.userMessage).toBe(
108
- "A temporary error occurred. Please try again in a moment.",
109
- );
110
- });
111
-
112
- it("should use original error message for unknown error codes", () => {
113
- const unknownError = new ApiError({
114
- code: "UNKNOWN_ERROR",
115
- message: "Custom error message",
116
- statusCode: 418,
117
- });
118
-
119
- const result = useBridgeError({ error: unknownError });
120
-
121
- expect(result.userMessage).toBe(
122
- "An unexpected error occurred. Please try again.",
123
- );
124
- expect(result.errorCode).toBe("UNKNOWN_ERROR");
125
- });
126
-
127
- it("should detect client vs server errors correctly", () => {
128
- // Client error (4xx)
129
- const clientError = new ApiError({
130
- code: "INVALID_INPUT",
131
- message: "Bad request",
132
- statusCode: 400,
133
- });
134
-
135
- const clientResult = useBridgeError({ error: clientError });
136
- expect(clientResult.isClientError).toBe(true);
137
- expect(clientResult.isServerError).toBe(false);
138
-
139
- // Server error (5xx)
140
- const serverError = new ApiError({
141
- code: "INTERNAL_SERVER_ERROR",
142
- message: "Internal error",
143
- statusCode: 503,
144
- });
145
-
146
- const serverResult = useBridgeError({ error: serverError });
147
- expect(serverResult.isClientError).toBe(false);
148
- expect(serverResult.isServerError).toBe(true);
149
-
150
- // No status code
151
- const noStatusError = new ApiError({
152
- code: "UNKNOWN_ERROR",
153
- message: "Unknown error",
154
- statusCode: 500,
155
- });
156
-
157
- const noStatusResult = useBridgeError({ error: noStatusError });
158
- expect(noStatusResult.isClientError).toBe(false);
159
- expect(noStatusResult.isServerError).toBe(true); // 500 is a server error
160
- });
161
-
162
- it("should handle Error without message", () => {
163
- const errorWithoutMessage = new Error();
164
-
165
- const result = useBridgeError({ error: errorWithoutMessage });
166
-
167
- expect(result.userMessage).toBe(
168
- "An unexpected error occurred. Please try again.",
169
- );
170
- expect(result.errorCode).toBe("UNKNOWN_ERROR");
171
- });
172
- });