thirdweb 5.115.4 → 5.116.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 (83) hide show
  1. package/dist/cjs/exports/react.js.map +1 -1
  2. package/dist/cjs/react/core/hooks/x402/useFetchWithPaymentCore.js +5 -1
  3. package/dist/cjs/react/core/hooks/x402/useFetchWithPaymentCore.js.map +1 -1
  4. package/dist/cjs/react/native/hooks/x402/useFetchWithPayment.js +6 -1
  5. package/dist/cjs/react/native/hooks/x402/useFetchWithPayment.js.map +1 -1
  6. package/dist/cjs/react/web/hooks/x402/useFetchWithPayment.js +7 -1
  7. package/dist/cjs/react/web/hooks/x402/useFetchWithPayment.js.map +1 -1
  8. package/dist/cjs/react/web/ui/Bridge/bridge-widget/bridge-widget.js +0 -6
  9. package/dist/cjs/react/web/ui/Bridge/bridge-widget/bridge-widget.js.map +1 -1
  10. package/dist/cjs/version.js +1 -1
  11. package/dist/cjs/x402/facilitator.js +1 -0
  12. package/dist/cjs/x402/facilitator.js.map +1 -1
  13. package/dist/cjs/x402/fetchWithPayment.js +13 -1
  14. package/dist/cjs/x402/fetchWithPayment.js.map +1 -1
  15. package/dist/cjs/x402/permitSignatureStorage.js +70 -0
  16. package/dist/cjs/x402/permitSignatureStorage.js.map +1 -0
  17. package/dist/cjs/x402/schemas.js +4 -0
  18. package/dist/cjs/x402/schemas.js.map +1 -1
  19. package/dist/cjs/x402/sign.js +51 -4
  20. package/dist/cjs/x402/sign.js.map +1 -1
  21. package/dist/cjs/x402/types.js.map +1 -1
  22. package/dist/cjs/x402/verify-payment.js +3 -0
  23. package/dist/cjs/x402/verify-payment.js.map +1 -1
  24. package/dist/esm/exports/react.js.map +1 -1
  25. package/dist/esm/react/core/hooks/x402/useFetchWithPaymentCore.js +5 -1
  26. package/dist/esm/react/core/hooks/x402/useFetchWithPaymentCore.js.map +1 -1
  27. package/dist/esm/react/native/hooks/x402/useFetchWithPayment.js +6 -1
  28. package/dist/esm/react/native/hooks/x402/useFetchWithPayment.js.map +1 -1
  29. package/dist/esm/react/web/hooks/x402/useFetchWithPayment.js +8 -2
  30. package/dist/esm/react/web/hooks/x402/useFetchWithPayment.js.map +1 -1
  31. package/dist/esm/react/web/ui/Bridge/bridge-widget/bridge-widget.js +0 -6
  32. package/dist/esm/react/web/ui/Bridge/bridge-widget/bridge-widget.js.map +1 -1
  33. package/dist/esm/version.js +1 -1
  34. package/dist/esm/x402/facilitator.js +1 -0
  35. package/dist/esm/x402/facilitator.js.map +1 -1
  36. package/dist/esm/x402/fetchWithPayment.js +13 -1
  37. package/dist/esm/x402/fetchWithPayment.js.map +1 -1
  38. package/dist/esm/x402/permitSignatureStorage.js +65 -0
  39. package/dist/esm/x402/permitSignatureStorage.js.map +1 -0
  40. package/dist/esm/x402/schemas.js +4 -0
  41. package/dist/esm/x402/schemas.js.map +1 -1
  42. package/dist/esm/x402/sign.js +51 -4
  43. package/dist/esm/x402/sign.js.map +1 -1
  44. package/dist/esm/x402/types.js.map +1 -1
  45. package/dist/esm/x402/verify-payment.js +3 -0
  46. package/dist/esm/x402/verify-payment.js.map +1 -1
  47. package/dist/scripts/bridge-widget.js +2 -2
  48. package/dist/types/exports/react.d.ts +1 -0
  49. package/dist/types/exports/react.d.ts.map +1 -1
  50. package/dist/types/react/core/hooks/x402/useFetchWithPaymentCore.d.ts +6 -0
  51. package/dist/types/react/core/hooks/x402/useFetchWithPaymentCore.d.ts.map +1 -1
  52. package/dist/types/react/native/hooks/x402/useFetchWithPayment.d.ts +1 -0
  53. package/dist/types/react/native/hooks/x402/useFetchWithPayment.d.ts.map +1 -1
  54. package/dist/types/react/web/hooks/x402/useFetchWithPayment.d.ts.map +1 -1
  55. package/dist/types/react/web/ui/Bridge/bridge-widget/bridge-widget.d.ts +0 -6
  56. package/dist/types/react/web/ui/Bridge/bridge-widget/bridge-widget.d.ts.map +1 -1
  57. package/dist/types/version.d.ts +1 -1
  58. package/dist/types/x402/facilitator.d.ts.map +1 -1
  59. package/dist/types/x402/fetchWithPayment.d.ts +6 -0
  60. package/dist/types/x402/fetchWithPayment.d.ts.map +1 -1
  61. package/dist/types/x402/permitSignatureStorage.d.ts +43 -0
  62. package/dist/types/x402/permitSignatureStorage.d.ts.map +1 -0
  63. package/dist/types/x402/schemas.d.ts +12 -0
  64. package/dist/types/x402/schemas.d.ts.map +1 -1
  65. package/dist/types/x402/sign.d.ts +3 -1
  66. package/dist/types/x402/sign.d.ts.map +1 -1
  67. package/dist/types/x402/types.d.ts +8 -0
  68. package/dist/types/x402/types.d.ts.map +1 -1
  69. package/dist/types/x402/verify-payment.d.ts.map +1 -1
  70. package/package.json +1 -1
  71. package/src/exports/react.ts +1 -0
  72. package/src/react/core/hooks/x402/useFetchWithPaymentCore.ts +11 -1
  73. package/src/react/native/hooks/x402/useFetchWithPayment.ts +6 -1
  74. package/src/react/web/hooks/x402/useFetchWithPayment.tsx +12 -2
  75. package/src/react/web/ui/Bridge/bridge-widget/bridge-widget.tsx +0 -6
  76. package/src/version.ts +1 -1
  77. package/src/x402/facilitator.ts +1 -0
  78. package/src/x402/fetchWithPayment.ts +21 -0
  79. package/src/x402/permitSignatureStorage.ts +99 -0
  80. package/src/x402/schemas.ts +4 -0
  81. package/src/x402/sign.ts +76 -1
  82. package/src/x402/types.ts +8 -0
  83. package/src/x402/verify-payment.ts +3 -0
@@ -0,0 +1,99 @@
1
+ import type { AsyncStorage } from "../utils/storage/AsyncStorage.js";
2
+ import type { RequestedPaymentPayload } from "./schemas.js";
3
+
4
+ /**
5
+ * Cached permit signature data structure
6
+ */
7
+ type CachedPermitSignature = {
8
+ payload: RequestedPaymentPayload;
9
+ deadline: string;
10
+ maxAmount: string;
11
+ };
12
+
13
+ /**
14
+ * Parameters for generating a permit cache key
15
+ */
16
+ export type PermitCacheKeyParams = {
17
+ chainId: number;
18
+ asset: string;
19
+ owner: string;
20
+ spender: string;
21
+ };
22
+
23
+ const CACHE_KEY_PREFIX = "x402:permit";
24
+
25
+ /**
26
+ * Generates a cache key for permit signature storage
27
+ * @param params - The parameters to generate the cache key from
28
+ * @returns The cache key string
29
+ */
30
+ function getPermitCacheKey(params: PermitCacheKeyParams): string {
31
+ return `${CACHE_KEY_PREFIX}:${params.chainId}:${params.asset.toLowerCase()}:${params.owner.toLowerCase()}:${params.spender.toLowerCase()}`;
32
+ }
33
+
34
+ /**
35
+ * Retrieves a cached permit signature from storage
36
+ * @param storage - The AsyncStorage instance to use
37
+ * @param params - The parameters identifying the cached signature
38
+ * @returns The cached signature data or null if not found
39
+ */
40
+ export async function getPermitSignatureFromCache(
41
+ storage: AsyncStorage,
42
+ params: PermitCacheKeyParams,
43
+ ): Promise<CachedPermitSignature | null> {
44
+ try {
45
+ const key = getPermitCacheKey(params);
46
+ const cached = await storage.getItem(key);
47
+ if (!cached) {
48
+ return null;
49
+ }
50
+ return JSON.parse(cached) as CachedPermitSignature;
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Saves a permit signature to storage cache
58
+ * @param storage - The AsyncStorage instance to use
59
+ * @param params - The parameters identifying the signature
60
+ * @param payload - The signed payment payload to cache
61
+ * @param deadline - The deadline timestamp of the permit
62
+ * @param maxAmount - The maximum amount authorized
63
+ */
64
+ export async function savePermitSignatureToCache(
65
+ storage: AsyncStorage,
66
+ params: PermitCacheKeyParams,
67
+ payload: RequestedPaymentPayload,
68
+ deadline: string,
69
+ maxAmount: string,
70
+ ): Promise<void> {
71
+ try {
72
+ const key = getPermitCacheKey(params);
73
+ const data: CachedPermitSignature = {
74
+ payload,
75
+ deadline,
76
+ maxAmount,
77
+ };
78
+ await storage.setItem(key, JSON.stringify(data));
79
+ } catch {
80
+ // Silently fail - caching is optional
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Clears a cached permit signature from storage
86
+ * @param storage - The AsyncStorage instance to use
87
+ * @param params - The parameters identifying the cached signature
88
+ */
89
+ export async function clearPermitSignatureFromCache(
90
+ storage: AsyncStorage,
91
+ params: PermitCacheKeyParams,
92
+ ): Promise<void> {
93
+ try {
94
+ const key = getPermitCacheKey(params);
95
+ await storage.removeItem(key);
96
+ } catch {
97
+ // Silently fail
98
+ }
99
+ }
@@ -45,6 +45,8 @@ const FacilitatorSettleResponseSchema = SettleResponseSchema.extend({
45
45
  network: FacilitatorNetworkSchema,
46
46
  errorMessage: z.string().optional(),
47
47
  fundWalletLink: z.string().optional(),
48
+ allowance: z.string().optional(),
49
+ balance: z.string().optional(),
48
50
  });
49
51
  export type FacilitatorSettleResponse = z.infer<
50
52
  typeof FacilitatorSettleResponseSchema
@@ -53,6 +55,8 @@ export type FacilitatorSettleResponse = z.infer<
53
55
  const FacilitatorVerifyResponseSchema = VerifyResponseSchema.extend({
54
56
  errorMessage: z.string().optional(),
55
57
  fundWalletLink: z.string().optional(),
58
+ allowance: z.string().optional(),
59
+ balance: z.string().optional(),
56
60
  });
57
61
 
58
62
  export type FacilitatorVerifyResponse = z.infer<
package/src/x402/sign.ts CHANGED
@@ -3,12 +3,19 @@ import type { ExactEvmPayloadAuthorization } from "x402/types";
3
3
  import { getCachedChain } from "../chains/utils.js";
4
4
  import type { ThirdwebClient } from "../client/client.js";
5
5
  import { getContract } from "../contract/contract.js";
6
+ import { allowance } from "../extensions/erc20/__generated__/IERC20/read/allowance.js";
6
7
  import { nonces } from "../extensions/erc20/__generated__/IERC20Permit/read/nonces.js";
7
8
  import { type Address, getAddress } from "../utils/address.js";
8
9
  import { type Hex, toHex } from "../utils/encoding/hex.js";
10
+ import type { AsyncStorage } from "../utils/storage/AsyncStorage.js";
9
11
  import type { Account } from "../wallets/interfaces/wallet.js";
10
12
  import { getSupportedSignatureType } from "./common.js";
11
13
  import { encodePayment } from "./encode.js";
14
+ import {
15
+ getPermitSignatureFromCache,
16
+ type PermitCacheKeyParams,
17
+ savePermitSignatureToCache,
18
+ } from "./permitSignatureStorage.js";
12
19
  import {
13
20
  extractEvmChainId,
14
21
  networkToCaip2ChainId,
@@ -63,6 +70,7 @@ function preparePaymentHeader(
63
70
  * @param client - The signer wallet instance used to sign the payment header
64
71
  * @param paymentRequirements - The payment requirements containing scheme and network information
65
72
  * @param unsignedPaymentHeader - The unsigned payment payload to be signed
73
+ * @param storage - Optional storage for caching permit signatures (for "upto" scheme)
66
74
  * @returns A promise that resolves to the signed payment payload
67
75
  */
68
76
  async function signPaymentHeader(
@@ -70,6 +78,7 @@ async function signPaymentHeader(
70
78
  account: Account,
71
79
  paymentRequirements: RequestedPaymentRequirements,
72
80
  x402Version: number,
81
+ storage?: AsyncStorage,
73
82
  ): Promise<RequestedPaymentPayload> {
74
83
  const from = getAddress(account.address);
75
84
  const caip2ChainId = networkToCaip2ChainId(paymentRequirements.network);
@@ -91,6 +100,55 @@ async function signPaymentHeader(
91
100
 
92
101
  switch (supportedSignatureType) {
93
102
  case "Permit": {
103
+ const shouldCache =
104
+ paymentRequirements.scheme === "upto" && storage !== undefined;
105
+ const spender = getAddress(paymentRequirements.payTo);
106
+
107
+ const cacheParams: PermitCacheKeyParams = {
108
+ chainId,
109
+ asset: paymentRequirements.asset,
110
+ owner: from,
111
+ spender,
112
+ };
113
+
114
+ // Try to reuse cached signature for "upto" scheme
115
+ if (shouldCache && storage) {
116
+ const cached = await getPermitSignatureFromCache(storage, cacheParams);
117
+
118
+ if (cached) {
119
+ // Validate deadline hasn't passed
120
+ const now = BigInt(Math.floor(Date.now() / 1000));
121
+ if (BigInt(cached.deadline) > now) {
122
+ // Check on-chain allowance
123
+ const currentAllowance = await allowance({
124
+ contract: getContract({
125
+ address: paymentRequirements.asset,
126
+ chain: getCachedChain(chainId),
127
+ client,
128
+ }),
129
+ owner: from,
130
+ spender,
131
+ });
132
+
133
+ // Determine threshold - use minAmountRequired if present, else maxAmountRequired
134
+ const extra = paymentRequirements.extra as
135
+ | (ERC20TokenAmount["asset"]["eip712"] & {
136
+ minAmountRequired?: string;
137
+ })
138
+ | undefined;
139
+ const threshold = extra?.minAmountRequired
140
+ ? BigInt(extra.minAmountRequired)
141
+ : BigInt(paymentRequirements.maxAmountRequired);
142
+
143
+ // If allowance >= threshold, reuse signature
144
+ if (currentAllowance >= threshold) {
145
+ return cached.payload;
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ // Generate new signature
94
152
  const nonce = await nonces({
95
153
  contract: getContract({
96
154
  address: paymentRequirements.asset,
@@ -110,13 +168,27 @@ async function signPaymentHeader(
110
168
  unsignedPaymentHeader.payload.authorization,
111
169
  paymentRequirements,
112
170
  );
113
- return {
171
+
172
+ const signedPayload: RequestedPaymentPayload = {
114
173
  ...unsignedPaymentHeader,
115
174
  payload: {
116
175
  ...unsignedPaymentHeader.payload,
117
176
  signature,
118
177
  },
119
178
  };
179
+
180
+ // Cache the signature for "upto" scheme
181
+ if (shouldCache && storage) {
182
+ await savePermitSignatureToCache(
183
+ storage,
184
+ cacheParams,
185
+ signedPayload,
186
+ unsignedPaymentHeader.payload.authorization.validBefore,
187
+ paymentRequirements.maxAmountRequired,
188
+ );
189
+ }
190
+
191
+ return signedPayload;
120
192
  }
121
193
  case "TransferWithAuthorization": {
122
194
  // default to transfer with authorization
@@ -153,6 +225,7 @@ async function signPaymentHeader(
153
225
  * @param client - The signer wallet instance used to create the payment header
154
226
  * @param x402Version - The version of the X402 protocol to use
155
227
  * @param paymentRequirements - The payment requirements containing scheme and network information
228
+ * @param storage - Optional storage for caching permit signatures (for "upto" scheme)
156
229
  * @returns A promise that resolves to the encoded payment header string
157
230
  */
158
231
  export async function createPaymentHeader(
@@ -160,12 +233,14 @@ export async function createPaymentHeader(
160
233
  account: Account,
161
234
  paymentRequirements: RequestedPaymentRequirements,
162
235
  x402Version: number,
236
+ storage?: AsyncStorage,
163
237
  ): Promise<string> {
164
238
  const payment = await signPaymentHeader(
165
239
  client,
166
240
  account,
167
241
  paymentRequirements,
168
242
  x402Version,
243
+ storage,
169
244
  );
170
245
  return encodePayment(payment);
171
246
  }
package/src/x402/types.ts CHANGED
@@ -29,6 +29,8 @@ export type PaymentArgs = {
29
29
  network: FacilitatorNetwork | Chain;
30
30
  /** The price for accessing the resource - either a USD amount (e.g., "$0.10") or a specific token amount */
31
31
  price: Money | ERC20TokenAmount;
32
+ /** The minimum price for accessing the resource - Only applicable for the "upto" payment scheme */
33
+ minPrice?: Money | ERC20TokenAmount;
32
34
  /** The payment facilitator instance used to verify and settle payments */
33
35
  facilitator: ThirdwebX402Facilitator;
34
36
  /** The scheme of the payment, either "exact" or "upto", defaults to "exact" */
@@ -97,6 +99,12 @@ export type VerifyPaymentResult = Prettify<
97
99
  decodedPayment: RequestedPaymentPayload;
98
100
  /** The selected payment requirements */
99
101
  selectedPaymentRequirements: RequestedPaymentRequirements;
102
+ /** The current remaining allowance of the payment of the selected payment asset, only applicable for the "upto" payment scheme */
103
+ allowance?: string;
104
+ /** The current balance of the user's wallet in the selected payment asset */
105
+ balance?: string;
106
+ /** The payer address if verification succeeded */
107
+ payer?: string;
100
108
  }
101
109
  | PaymentRequiredResult
102
110
  >;
@@ -109,6 +109,9 @@ export async function verifyPayment(
109
109
  status: 200,
110
110
  decodedPayment,
111
111
  selectedPaymentRequirements,
112
+ allowance: verification.allowance,
113
+ balance: verification.balance,
114
+ payer: verification.payer,
112
115
  };
113
116
  } else {
114
117
  const error = verification.invalidReason || "Verification failed";