@voyage_ai/v402-web-ts 0.1.2 → 0.1.4
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.
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +139 -98
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +122 -80
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +50 -1
- package/dist/react/index.d.ts +50 -1
- package/dist/react/index.js +1559 -136
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +1541 -112
- package/dist/react/index.mjs.map +1 -1
- package/dist/react/styles.css +1 -168
- package/package.json +33 -10
- package/dist/react/components/WalletConnect.tsx +0 -152
- package/dist/react/hooks/usePayment.ts +0 -109
- package/dist/react/hooks/usePaymentInfo.ts +0 -128
- package/dist/react/hooks/useWallet.ts +0 -174
- package/dist/react/hooks/useWalletStore.ts +0 -61
- package/dist/react/index.ts +0 -38
- package/dist/react/store/walletStore.ts +0 -181
- package/dist/react/styles/inline-styles.ts +0 -238
package/dist/react/index.mjs
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __esm = (fn, res) => function __init() {
|
|
3
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
// src/types/common.ts
|
|
7
|
+
var PROD_BACK_URL, DEV_BACK_URL;
|
|
8
|
+
var init_common = __esm({
|
|
9
|
+
"src/types/common.ts"() {
|
|
10
|
+
"use strict";
|
|
11
|
+
PROD_BACK_URL = "https://v402.onvoyage.ai/api/pay";
|
|
12
|
+
DEV_BACK_URL = "http://localhost:3000/api/pay";
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// src/types/svm.ts
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { ExactSvmPayloadSchema } from "x402/types";
|
|
19
|
+
var SolanaNetworkSchema, SolanaPaymentPayloadSchema;
|
|
20
|
+
var init_svm = __esm({
|
|
21
|
+
"src/types/svm.ts"() {
|
|
22
|
+
"use strict";
|
|
23
|
+
SolanaNetworkSchema = z.enum([
|
|
24
|
+
"solana-devnet",
|
|
25
|
+
"solana",
|
|
26
|
+
"solana-mainnet"
|
|
27
|
+
// Alias for mainnet
|
|
28
|
+
]);
|
|
29
|
+
SolanaPaymentPayloadSchema = z.object({
|
|
30
|
+
x402Version: z.literal(1),
|
|
31
|
+
scheme: z.literal("exact"),
|
|
32
|
+
network: SolanaNetworkSchema,
|
|
33
|
+
payload: ExactSvmPayloadSchema
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// src/types/evm.ts
|
|
39
|
+
import { z as z2 } from "zod";
|
|
40
|
+
import { ExactEvmPayloadSchema } from "x402/types";
|
|
41
|
+
var EvmNetworkSchema, EvmPaymentPayloadSchema;
|
|
42
|
+
var init_evm = __esm({
|
|
43
|
+
"src/types/evm.ts"() {
|
|
44
|
+
"use strict";
|
|
45
|
+
EvmNetworkSchema = z2.enum([
|
|
46
|
+
"ethereum",
|
|
47
|
+
"sepolia",
|
|
48
|
+
"base",
|
|
49
|
+
"base-sepolia",
|
|
50
|
+
"polygon",
|
|
51
|
+
"arbitrum",
|
|
52
|
+
"optimism"
|
|
53
|
+
]);
|
|
54
|
+
EvmPaymentPayloadSchema = z2.object({
|
|
55
|
+
x402Version: z2.literal(1),
|
|
56
|
+
scheme: z2.literal("exact"),
|
|
57
|
+
network: EvmNetworkSchema,
|
|
58
|
+
payload: ExactEvmPayloadSchema
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
});
|
|
3
62
|
|
|
4
63
|
// src/types/index.ts
|
|
5
64
|
import {
|
|
@@ -9,53 +68,18 @@ import {
|
|
|
9
68
|
SettleResponseSchema,
|
|
10
69
|
SupportedPaymentKindSchema,
|
|
11
70
|
SupportedPaymentKindsResponseSchema,
|
|
12
|
-
ExactSvmPayloadSchema as ExactSvmPayloadSchema2,
|
|
13
|
-
ExactEvmPayloadSchema as ExactEvmPayloadSchema2,
|
|
14
71
|
SupportedSVMNetworks,
|
|
15
72
|
SvmNetworkToChainId
|
|
16
73
|
} from "x402/types";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import { ExactSvmPayloadSchema } from "x402/types";
|
|
24
|
-
var SolanaNetworkSchema = z.enum([
|
|
25
|
-
"solana-devnet",
|
|
26
|
-
"solana",
|
|
27
|
-
"solana-mainnet"
|
|
28
|
-
// Alias for mainnet
|
|
29
|
-
]);
|
|
30
|
-
var SolanaPaymentPayloadSchema = z.object({
|
|
31
|
-
x402Version: z.literal(1),
|
|
32
|
-
scheme: z.literal("exact"),
|
|
33
|
-
network: SolanaNetworkSchema,
|
|
34
|
-
payload: ExactSvmPayloadSchema
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// src/types/evm.ts
|
|
38
|
-
import { z as z2 } from "zod";
|
|
39
|
-
import { ExactEvmPayloadSchema } from "x402/types";
|
|
40
|
-
var EvmNetworkSchema = z2.enum([
|
|
41
|
-
"ethereum",
|
|
42
|
-
"sepolia",
|
|
43
|
-
"base",
|
|
44
|
-
"base-sepolia",
|
|
45
|
-
"polygon",
|
|
46
|
-
"arbitrum",
|
|
47
|
-
"optimism"
|
|
48
|
-
]);
|
|
49
|
-
var EvmPaymentPayloadSchema = z2.object({
|
|
50
|
-
x402Version: z2.literal(1),
|
|
51
|
-
scheme: z2.literal("exact"),
|
|
52
|
-
network: EvmNetworkSchema,
|
|
53
|
-
payload: ExactEvmPayloadSchema
|
|
74
|
+
var init_types = __esm({
|
|
75
|
+
"src/types/index.ts"() {
|
|
76
|
+
"use strict";
|
|
77
|
+
init_svm();
|
|
78
|
+
init_evm();
|
|
79
|
+
}
|
|
54
80
|
});
|
|
55
81
|
|
|
56
82
|
// src/utils/wallet.ts
|
|
57
|
-
var WALLET_DISCONNECTED_KEY = "wallet_manually_disconnected";
|
|
58
|
-
var CONNECTED_NETWORK_TYPE_KEY = "connected_network_type";
|
|
59
83
|
function isWalletInstalled(networkType) {
|
|
60
84
|
if (typeof window === "undefined") {
|
|
61
85
|
return false;
|
|
@@ -76,22 +100,50 @@ function formatAddress(address) {
|
|
|
76
100
|
}
|
|
77
101
|
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
78
102
|
}
|
|
79
|
-
function
|
|
103
|
+
function getDisconnectedNetworks() {
|
|
104
|
+
if (typeof window === "undefined") {
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const cached = localStorage.getItem(WALLET_DISCONNECTED_NETWORKS_KEY);
|
|
109
|
+
return cached ? JSON.parse(cached) : {};
|
|
110
|
+
} catch (error) {
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function markWalletDisconnected(networkType) {
|
|
80
115
|
if (typeof window !== "undefined") {
|
|
81
|
-
|
|
82
|
-
|
|
116
|
+
if (networkType) {
|
|
117
|
+
const disconnected = getDisconnectedNetworks();
|
|
118
|
+
disconnected[networkType] = true;
|
|
119
|
+
localStorage.setItem(WALLET_DISCONNECTED_NETWORKS_KEY, JSON.stringify(disconnected));
|
|
120
|
+
} else {
|
|
121
|
+
localStorage.setItem(WALLET_DISCONNECTED_KEY, "true");
|
|
122
|
+
localStorage.removeItem(CONNECTED_NETWORK_TYPE_KEY);
|
|
123
|
+
}
|
|
83
124
|
}
|
|
84
125
|
}
|
|
85
|
-
function clearWalletDisconnection() {
|
|
126
|
+
function clearWalletDisconnection(networkType) {
|
|
86
127
|
if (typeof window !== "undefined") {
|
|
87
|
-
|
|
128
|
+
if (networkType) {
|
|
129
|
+
const disconnected = getDisconnectedNetworks();
|
|
130
|
+
delete disconnected[networkType];
|
|
131
|
+
localStorage.setItem(WALLET_DISCONNECTED_NETWORKS_KEY, JSON.stringify(disconnected));
|
|
132
|
+
} else {
|
|
133
|
+
localStorage.removeItem(WALLET_DISCONNECTED_KEY);
|
|
134
|
+
}
|
|
88
135
|
}
|
|
89
136
|
}
|
|
90
|
-
function isWalletManuallyDisconnected() {
|
|
137
|
+
function isWalletManuallyDisconnected(networkType) {
|
|
91
138
|
if (typeof window === "undefined") {
|
|
92
139
|
return false;
|
|
93
140
|
}
|
|
94
|
-
|
|
141
|
+
if (networkType) {
|
|
142
|
+
const disconnected = getDisconnectedNetworks();
|
|
143
|
+
return disconnected[networkType] === true;
|
|
144
|
+
} else {
|
|
145
|
+
return localStorage.getItem(WALLET_DISCONNECTED_KEY) === "true";
|
|
146
|
+
}
|
|
95
147
|
}
|
|
96
148
|
function saveConnectedNetworkType(networkType) {
|
|
97
149
|
if (typeof window !== "undefined") {
|
|
@@ -116,8 +168,60 @@ function getWalletInstallUrl(networkType) {
|
|
|
116
168
|
return "#";
|
|
117
169
|
}
|
|
118
170
|
}
|
|
171
|
+
function getAllWalletAddresses() {
|
|
172
|
+
if (typeof window === "undefined") {
|
|
173
|
+
return {};
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const cached = localStorage.getItem(WALLET_ADDRESSES_KEY);
|
|
177
|
+
return cached ? JSON.parse(cached) : {};
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error("Failed to parse wallet addresses cache:", error);
|
|
180
|
+
return {};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function saveWalletAddress(networkType, address) {
|
|
184
|
+
if (typeof window === "undefined") {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const addresses = getAllWalletAddresses();
|
|
188
|
+
addresses[networkType] = address;
|
|
189
|
+
localStorage.setItem(WALLET_ADDRESSES_KEY, JSON.stringify(addresses));
|
|
190
|
+
}
|
|
191
|
+
function getCachedWalletAddress(networkType) {
|
|
192
|
+
const addresses = getAllWalletAddresses();
|
|
193
|
+
return addresses[networkType] || null;
|
|
194
|
+
}
|
|
195
|
+
function removeWalletAddress(networkType) {
|
|
196
|
+
if (typeof window === "undefined") {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const addresses = getAllWalletAddresses();
|
|
200
|
+
delete addresses[networkType];
|
|
201
|
+
localStorage.setItem(WALLET_ADDRESSES_KEY, JSON.stringify(addresses));
|
|
202
|
+
}
|
|
203
|
+
var WALLET_DISCONNECTED_KEY, WALLET_DISCONNECTED_NETWORKS_KEY, CONNECTED_NETWORK_TYPE_KEY, WALLET_ADDRESSES_KEY;
|
|
204
|
+
var init_wallet = __esm({
|
|
205
|
+
"src/utils/wallet.ts"() {
|
|
206
|
+
"use strict";
|
|
207
|
+
WALLET_DISCONNECTED_KEY = "wallet_manually_disconnected";
|
|
208
|
+
WALLET_DISCONNECTED_NETWORKS_KEY = "wallet_disconnected_networks";
|
|
209
|
+
CONNECTED_NETWORK_TYPE_KEY = "connected_network_type";
|
|
210
|
+
WALLET_ADDRESSES_KEY = "wallet_addresses_cache";
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// src/react/index.ts
|
|
215
|
+
import "./styles.css";
|
|
216
|
+
|
|
217
|
+
// src/react/hooks/useWalletStore.ts
|
|
218
|
+
import { useSyncExternalStore } from "react";
|
|
219
|
+
|
|
220
|
+
// src/utils/index.ts
|
|
221
|
+
init_wallet();
|
|
119
222
|
|
|
120
223
|
// src/utils/wallet-connect.ts
|
|
224
|
+
init_wallet();
|
|
121
225
|
async function connectWallet(networkType) {
|
|
122
226
|
if (typeof window === "undefined") {
|
|
123
227
|
throw new Error("\u8BF7\u5728\u6D4F\u89C8\u5668\u73AF\u5883\u4E2D\u4F7F\u7528");
|
|
@@ -152,13 +256,11 @@ async function connectWallet(networkType) {
|
|
|
152
256
|
default:
|
|
153
257
|
throw new Error("\u4E0D\u652F\u6301\u7684\u7F51\u7EDC\u7C7B\u578B");
|
|
154
258
|
}
|
|
155
|
-
clearWalletDisconnection();
|
|
259
|
+
clearWalletDisconnection(networkType);
|
|
156
260
|
saveConnectedNetworkType(networkType);
|
|
261
|
+
saveWalletAddress(networkType, address);
|
|
157
262
|
return address;
|
|
158
263
|
}
|
|
159
|
-
function disconnectWallet() {
|
|
160
|
-
markWalletDisconnected();
|
|
161
|
-
}
|
|
162
264
|
async function getCurrentWallet(networkType) {
|
|
163
265
|
if (typeof window === "undefined") {
|
|
164
266
|
return null;
|
|
@@ -167,28 +269,36 @@ async function getCurrentWallet(networkType) {
|
|
|
167
269
|
if (!type) {
|
|
168
270
|
return null;
|
|
169
271
|
}
|
|
272
|
+
const cachedAddress = getCachedWalletAddress(type);
|
|
170
273
|
try {
|
|
274
|
+
let currentAddress = null;
|
|
171
275
|
switch (type) {
|
|
172
276
|
case "evm" /* EVM */: {
|
|
173
|
-
if (!window.ethereum) return
|
|
277
|
+
if (!window.ethereum) return cachedAddress;
|
|
174
278
|
const accounts = await window.ethereum.request({
|
|
175
279
|
method: "eth_accounts",
|
|
176
280
|
params: []
|
|
177
281
|
});
|
|
178
|
-
|
|
282
|
+
currentAddress = accounts && accounts.length > 0 ? accounts[0] : null;
|
|
283
|
+
break;
|
|
179
284
|
}
|
|
180
285
|
case "solana" /* SOLANA */:
|
|
181
286
|
case "svm" /* SVM */: {
|
|
182
287
|
const solana = window.solana;
|
|
183
|
-
if (!solana || !solana.isConnected) return
|
|
184
|
-
|
|
288
|
+
if (!solana || !solana.isConnected) return cachedAddress;
|
|
289
|
+
currentAddress = solana.publicKey?.toString() || null;
|
|
290
|
+
break;
|
|
185
291
|
}
|
|
186
292
|
default:
|
|
187
|
-
return
|
|
293
|
+
return cachedAddress;
|
|
188
294
|
}
|
|
295
|
+
if (currentAddress && currentAddress !== cachedAddress) {
|
|
296
|
+
saveWalletAddress(type, currentAddress);
|
|
297
|
+
}
|
|
298
|
+
return currentAddress || cachedAddress;
|
|
189
299
|
} catch (error) {
|
|
190
300
|
console.error("Failed to get current wallet:", error);
|
|
191
|
-
return
|
|
301
|
+
return cachedAddress;
|
|
192
302
|
}
|
|
193
303
|
}
|
|
194
304
|
function onAccountsChanged(callback) {
|
|
@@ -239,6 +349,18 @@ function onWalletDisconnect(callback) {
|
|
|
239
349
|
solana.removeListener?.("disconnect", handler);
|
|
240
350
|
};
|
|
241
351
|
}
|
|
352
|
+
async function switchNetwork(networkType) {
|
|
353
|
+
const cachedAddress = getCachedWalletAddress(networkType);
|
|
354
|
+
if (cachedAddress) {
|
|
355
|
+
saveConnectedNetworkType(networkType);
|
|
356
|
+
clearWalletDisconnection(networkType);
|
|
357
|
+
const currentAddress = await getCurrentWallet(networkType);
|
|
358
|
+
if (currentAddress) {
|
|
359
|
+
return currentAddress;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
242
364
|
|
|
243
365
|
// src/services/svm/payment-header.ts
|
|
244
366
|
import {
|
|
@@ -255,11 +377,517 @@ import {
|
|
|
255
377
|
TOKEN_2022_PROGRAM_ID,
|
|
256
378
|
TOKEN_PROGRAM_ID
|
|
257
379
|
} from "@solana/spl-token";
|
|
380
|
+
async function createSvmPaymentHeader(params) {
|
|
381
|
+
const { wallet, paymentRequirements, x402Version, rpcUrl } = params;
|
|
382
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
383
|
+
const feePayer = paymentRequirements?.extra?.feePayer;
|
|
384
|
+
if (typeof feePayer !== "string" || !feePayer) {
|
|
385
|
+
throw new Error("Missing facilitator feePayer in payment requirements (extra.feePayer).");
|
|
386
|
+
}
|
|
387
|
+
const feePayerPubkey = new PublicKey(feePayer);
|
|
388
|
+
const walletAddress = wallet?.publicKey?.toString() || wallet?.address;
|
|
389
|
+
if (!walletAddress) {
|
|
390
|
+
throw new Error("Missing connected Solana wallet address or publicKey");
|
|
391
|
+
}
|
|
392
|
+
const userPubkey = new PublicKey(walletAddress);
|
|
393
|
+
if (!paymentRequirements?.payTo) {
|
|
394
|
+
throw new Error("Missing payTo in payment requirements");
|
|
395
|
+
}
|
|
396
|
+
const destination = new PublicKey(paymentRequirements.payTo);
|
|
397
|
+
const instructions = [];
|
|
398
|
+
instructions.push(
|
|
399
|
+
ComputeBudgetProgram.setComputeUnitLimit({
|
|
400
|
+
units: 7e3
|
|
401
|
+
// Sufficient for SPL token transfer
|
|
402
|
+
})
|
|
403
|
+
);
|
|
404
|
+
instructions.push(
|
|
405
|
+
ComputeBudgetProgram.setComputeUnitPrice({
|
|
406
|
+
microLamports: 1
|
|
407
|
+
// Minimal price
|
|
408
|
+
})
|
|
409
|
+
);
|
|
410
|
+
if (!paymentRequirements.asset) {
|
|
411
|
+
throw new Error("Missing token mint for SPL transfer");
|
|
412
|
+
}
|
|
413
|
+
const mintPubkey = new PublicKey(paymentRequirements.asset);
|
|
414
|
+
const mintInfo = await connection.getAccountInfo(mintPubkey, "confirmed");
|
|
415
|
+
const programId = mintInfo?.owner?.toBase58() === TOKEN_2022_PROGRAM_ID.toBase58() ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
|
|
416
|
+
const mint = await getMint(connection, mintPubkey, void 0, programId);
|
|
417
|
+
const sourceAta = await getAssociatedTokenAddress(
|
|
418
|
+
mintPubkey,
|
|
419
|
+
userPubkey,
|
|
420
|
+
false,
|
|
421
|
+
programId
|
|
422
|
+
);
|
|
423
|
+
const destinationAta = await getAssociatedTokenAddress(
|
|
424
|
+
mintPubkey,
|
|
425
|
+
destination,
|
|
426
|
+
false,
|
|
427
|
+
programId
|
|
428
|
+
);
|
|
429
|
+
const sourceAtaInfo = await connection.getAccountInfo(sourceAta, "confirmed");
|
|
430
|
+
if (!sourceAtaInfo) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`User does not have an Associated Token Account for ${paymentRequirements.asset}. Please create one first or ensure you have the required token.`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
const destAtaInfo = await connection.getAccountInfo(destinationAta, "confirmed");
|
|
436
|
+
if (!destAtaInfo) {
|
|
437
|
+
throw new Error(
|
|
438
|
+
`Destination does not have an Associated Token Account for ${paymentRequirements.asset}. The receiver must create their token account before receiving payments.`
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
const amount = BigInt(paymentRequirements.maxAmountRequired);
|
|
442
|
+
instructions.push(
|
|
443
|
+
createTransferCheckedInstruction(
|
|
444
|
+
sourceAta,
|
|
445
|
+
mintPubkey,
|
|
446
|
+
destinationAta,
|
|
447
|
+
userPubkey,
|
|
448
|
+
amount,
|
|
449
|
+
mint.decimals,
|
|
450
|
+
[],
|
|
451
|
+
programId
|
|
452
|
+
)
|
|
453
|
+
);
|
|
454
|
+
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
455
|
+
const message2 = new TransactionMessage({
|
|
456
|
+
payerKey: feePayerPubkey,
|
|
457
|
+
recentBlockhash: blockhash,
|
|
458
|
+
instructions
|
|
459
|
+
}).compileToV0Message();
|
|
460
|
+
const transaction = new VersionedTransaction(message2);
|
|
461
|
+
if (typeof wallet?.signTransaction !== "function") {
|
|
462
|
+
throw new Error("Connected wallet does not support signTransaction");
|
|
463
|
+
}
|
|
464
|
+
let userSignedTx;
|
|
465
|
+
try {
|
|
466
|
+
userSignedTx = await wallet.signTransaction(transaction);
|
|
467
|
+
console.log("\u2705 Transaction signed successfully");
|
|
468
|
+
} catch (error) {
|
|
469
|
+
console.error("\u274C Failed to sign transaction:", error);
|
|
470
|
+
throw wrapPaymentError(error);
|
|
471
|
+
}
|
|
472
|
+
const serializedTransaction = Buffer.from(userSignedTx.serialize()).toString("base64");
|
|
473
|
+
const paymentPayload = {
|
|
474
|
+
x402Version,
|
|
475
|
+
scheme: paymentRequirements.scheme,
|
|
476
|
+
network: paymentRequirements.network,
|
|
477
|
+
payload: {
|
|
478
|
+
transaction: serializedTransaction
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
const paymentHeader = Buffer.from(JSON.stringify(paymentPayload)).toString("base64");
|
|
482
|
+
return paymentHeader;
|
|
483
|
+
}
|
|
484
|
+
function getDefaultSolanaRpcUrl(network) {
|
|
485
|
+
const normalized = network.toLowerCase();
|
|
486
|
+
if (normalized === "solana" || normalized === "solana-mainnet") {
|
|
487
|
+
return "https://cathee-fu8ezd-fast-mainnet.helius-rpc.com";
|
|
488
|
+
} else if (normalized === "solana-devnet") {
|
|
489
|
+
return "https://api.devnet.solana.com";
|
|
490
|
+
}
|
|
491
|
+
throw new Error(`Unsupported Solana network: ${network}`);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/services/svm/payment-handler.ts
|
|
495
|
+
init_types();
|
|
496
|
+
async function handleSvmPayment(endpoint, config, requestInit) {
|
|
497
|
+
const { wallet, network, rpcUrl, maxPaymentAmount } = config;
|
|
498
|
+
const initialResponse = await fetch(endpoint, {
|
|
499
|
+
...requestInit,
|
|
500
|
+
method: requestInit?.method || "POST"
|
|
501
|
+
});
|
|
502
|
+
if (initialResponse.status !== 402) {
|
|
503
|
+
return initialResponse;
|
|
504
|
+
}
|
|
505
|
+
const rawResponse = await initialResponse.json();
|
|
506
|
+
const IGNORED_ERRORS = [
|
|
507
|
+
"X-PAYMENT header is required",
|
|
508
|
+
"missing X-PAYMENT header",
|
|
509
|
+
"payment_required"
|
|
510
|
+
];
|
|
511
|
+
if (rawResponse.error && !IGNORED_ERRORS.includes(rawResponse.error)) {
|
|
512
|
+
console.error(`\u274C Payment verification failed: ${rawResponse.error}`);
|
|
513
|
+
const ERROR_MESSAGES = {
|
|
514
|
+
"insufficient_funds": "Insufficient balance to complete this payment",
|
|
515
|
+
"invalid_signature": "Invalid payment signature",
|
|
516
|
+
"expired": "Payment authorization has expired",
|
|
517
|
+
"already_used": "This payment has already been used",
|
|
518
|
+
"network_mismatch": "Payment network does not match",
|
|
519
|
+
"invalid_payment": "Invalid payment data",
|
|
520
|
+
"verification_failed": "Payment verification failed",
|
|
521
|
+
"invalid_exact_svm_payload_transaction_simulation_failed": "Transaction simulation failed due to insufficient balance. Please check your wallet balance carefully and ensure you have enough funds to cover the payment and transaction fees."
|
|
522
|
+
};
|
|
523
|
+
const errorMessage = ERROR_MESSAGES[rawResponse.error] || `Payment failed: ${rawResponse.error}`;
|
|
524
|
+
const error = new Error(errorMessage);
|
|
525
|
+
throw wrapPaymentError(error);
|
|
526
|
+
}
|
|
527
|
+
const x402Version = rawResponse.x402Version;
|
|
528
|
+
const parsedPaymentRequirements = rawResponse.accepts || [];
|
|
529
|
+
const selectedRequirements = parsedPaymentRequirements.find(
|
|
530
|
+
(req) => req.scheme === "exact" && SolanaNetworkSchema.safeParse(req.network.toLowerCase()).success
|
|
531
|
+
);
|
|
532
|
+
if (!selectedRequirements) {
|
|
533
|
+
console.error(
|
|
534
|
+
"\u274C No suitable Solana payment requirements found. Available networks:",
|
|
535
|
+
parsedPaymentRequirements.map((req) => req.network)
|
|
536
|
+
);
|
|
537
|
+
throw new Error("No suitable Solana payment requirements found");
|
|
538
|
+
}
|
|
539
|
+
if (maxPaymentAmount && maxPaymentAmount > BigInt(0)) {
|
|
540
|
+
if (BigInt(selectedRequirements.maxAmountRequired) > maxPaymentAmount) {
|
|
541
|
+
throw new Error(
|
|
542
|
+
`Payment amount ${selectedRequirements.maxAmountRequired} exceeds maximum allowed ${maxPaymentAmount}`
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const effectiveRpcUrl = rpcUrl || getDefaultSolanaRpcUrl(selectedRequirements.network);
|
|
547
|
+
console.log(`\u{1F4CD} Using Solana RPC: ${effectiveRpcUrl.substring(0, 40)}...`);
|
|
548
|
+
console.log(`\u{1F4CD} Network from backend: ${selectedRequirements.network}`);
|
|
549
|
+
let paymentHeader;
|
|
550
|
+
try {
|
|
551
|
+
paymentHeader = await createSvmPaymentHeader({
|
|
552
|
+
wallet,
|
|
553
|
+
paymentRequirements: selectedRequirements,
|
|
554
|
+
x402Version,
|
|
555
|
+
rpcUrl: effectiveRpcUrl
|
|
556
|
+
});
|
|
557
|
+
console.log("\u2705 Payment header created successfully");
|
|
558
|
+
} catch (error) {
|
|
559
|
+
console.error("\u274C Failed to create payment header:", error);
|
|
560
|
+
throw wrapPaymentError(error);
|
|
561
|
+
}
|
|
562
|
+
const newInit = {
|
|
563
|
+
...requestInit,
|
|
564
|
+
method: requestInit?.method || "POST",
|
|
565
|
+
headers: {
|
|
566
|
+
...requestInit?.headers || {},
|
|
567
|
+
"X-PAYMENT": paymentHeader,
|
|
568
|
+
"Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE"
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
const retryResponse = await fetch(endpoint, newInit);
|
|
572
|
+
if (retryResponse.status === 402) {
|
|
573
|
+
try {
|
|
574
|
+
const retryData = await retryResponse.json();
|
|
575
|
+
const IGNORED_ERRORS2 = [
|
|
576
|
+
"X-PAYMENT header is required",
|
|
577
|
+
"missing X-PAYMENT header",
|
|
578
|
+
"payment_required"
|
|
579
|
+
];
|
|
580
|
+
if (retryData.error && !IGNORED_ERRORS2.includes(retryData.error)) {
|
|
581
|
+
console.error(`\u274C Payment verification failed: ${retryData.error}`);
|
|
582
|
+
const ERROR_MESSAGES = {
|
|
583
|
+
"insufficient_funds": "Insufficient balance to complete this payment",
|
|
584
|
+
"invalid_signature": "Invalid payment signature",
|
|
585
|
+
"expired": "Payment authorization has expired",
|
|
586
|
+
"already_used": "This payment has already been used",
|
|
587
|
+
"network_mismatch": "Payment network does not match",
|
|
588
|
+
"invalid_payment": "Invalid payment data",
|
|
589
|
+
"verification_failed": "Payment verification failed",
|
|
590
|
+
"invalid_exact_svm_payload_transaction_simulation_failed": "Transaction simulation failed due to insufficient balance. Please check your wallet balance carefully and ensure you have enough funds to cover the payment and transaction fees."
|
|
591
|
+
};
|
|
592
|
+
const errorMessage = ERROR_MESSAGES[retryData.error] || `Payment failed: ${retryData.error}`;
|
|
593
|
+
const error = new Error(errorMessage);
|
|
594
|
+
throw wrapPaymentError(error);
|
|
595
|
+
}
|
|
596
|
+
} catch (error) {
|
|
597
|
+
if (error instanceof PaymentOperationError) {
|
|
598
|
+
throw error;
|
|
599
|
+
}
|
|
600
|
+
console.warn("\u26A0\uFE0F Could not parse retry 402 response:", error);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return retryResponse;
|
|
604
|
+
}
|
|
258
605
|
|
|
259
606
|
// src/services/evm/payment-header.ts
|
|
260
607
|
import { ethers } from "ethers";
|
|
608
|
+
async function createEvmPaymentHeader(params) {
|
|
609
|
+
const { wallet, paymentRequirements, x402Version, chainId } = params;
|
|
610
|
+
if (!paymentRequirements?.payTo) {
|
|
611
|
+
throw new Error("Missing payTo in payment requirements");
|
|
612
|
+
}
|
|
613
|
+
if (!paymentRequirements?.asset) {
|
|
614
|
+
throw new Error("Missing asset (token contract) in payment requirements");
|
|
615
|
+
}
|
|
616
|
+
if (wallet.getChainId) {
|
|
617
|
+
try {
|
|
618
|
+
const currentChainIdHex = await wallet.getChainId();
|
|
619
|
+
const currentChainId = parseInt(currentChainIdHex, 16);
|
|
620
|
+
if (currentChainId !== chainId) {
|
|
621
|
+
const networkNames = {
|
|
622
|
+
1: "Ethereum Mainnet",
|
|
623
|
+
11155111: "Sepolia Testnet",
|
|
624
|
+
8453: "Base Mainnet",
|
|
625
|
+
84532: "Base Sepolia Testnet",
|
|
626
|
+
137: "Polygon Mainnet",
|
|
627
|
+
42161: "Arbitrum One",
|
|
628
|
+
10: "Optimism Mainnet"
|
|
629
|
+
};
|
|
630
|
+
const currentNetworkName = networkNames[currentChainId] || `Chain ${currentChainId}`;
|
|
631
|
+
const targetNetworkName = networkNames[chainId] || `Chain ${chainId}`;
|
|
632
|
+
throw new Error(
|
|
633
|
+
`Network mismatch: Your wallet is connected to ${currentNetworkName}, but payment requires ${targetNetworkName}. Please switch your wallet to the correct network.`
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
console.log(`\u2705 Chain ID verified: ${chainId}`);
|
|
637
|
+
} catch (error) {
|
|
638
|
+
if (error.message.includes("Network mismatch")) {
|
|
639
|
+
throw wrapPaymentError(error);
|
|
640
|
+
}
|
|
641
|
+
console.warn("Could not verify chainId:", error);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
645
|
+
const nonceBytes = ethers.randomBytes(32);
|
|
646
|
+
const nonceBytes32 = ethers.hexlify(nonceBytes);
|
|
647
|
+
const domain = {
|
|
648
|
+
name: paymentRequirements.extra?.name || "USDC",
|
|
649
|
+
version: paymentRequirements.extra?.version || "2",
|
|
650
|
+
chainId,
|
|
651
|
+
verifyingContract: paymentRequirements.asset
|
|
652
|
+
};
|
|
653
|
+
const types = {
|
|
654
|
+
TransferWithAuthorization: [
|
|
655
|
+
{ name: "from", type: "address" },
|
|
656
|
+
{ name: "to", type: "address" },
|
|
657
|
+
{ name: "value", type: "uint256" },
|
|
658
|
+
{ name: "validAfter", type: "uint256" },
|
|
659
|
+
{ name: "validBefore", type: "uint256" },
|
|
660
|
+
{ name: "nonce", type: "bytes32" }
|
|
661
|
+
]
|
|
662
|
+
};
|
|
663
|
+
const authorization = {
|
|
664
|
+
from: wallet.address,
|
|
665
|
+
to: paymentRequirements.payTo,
|
|
666
|
+
value: paymentRequirements.maxAmountRequired,
|
|
667
|
+
validAfter: "0",
|
|
668
|
+
// Effective immediately
|
|
669
|
+
validBefore: String(now + (paymentRequirements.maxTimeoutSeconds || 3600)),
|
|
670
|
+
nonce: nonceBytes32
|
|
671
|
+
};
|
|
672
|
+
let signature;
|
|
673
|
+
try {
|
|
674
|
+
signature = await wallet.signTypedData(domain, types, authorization);
|
|
675
|
+
console.log("\u2705 Signature created successfully");
|
|
676
|
+
} catch (error) {
|
|
677
|
+
console.error("\u274C Failed to create signature:", error);
|
|
678
|
+
throw wrapPaymentError(error);
|
|
679
|
+
}
|
|
680
|
+
const headerPayload = {
|
|
681
|
+
x402_version: x402Version,
|
|
682
|
+
x402Version,
|
|
683
|
+
scheme: paymentRequirements.scheme,
|
|
684
|
+
network: paymentRequirements.network,
|
|
685
|
+
payload: {
|
|
686
|
+
signature,
|
|
687
|
+
authorization: {
|
|
688
|
+
from: authorization.from,
|
|
689
|
+
to: authorization.to,
|
|
690
|
+
value: String(authorization.value),
|
|
691
|
+
valid_after: authorization.validAfter,
|
|
692
|
+
validAfter: authorization.validAfter,
|
|
693
|
+
valid_before: authorization.validBefore,
|
|
694
|
+
validBefore: authorization.validBefore,
|
|
695
|
+
nonce: authorization.nonce
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
const paymentHeader = btoa(JSON.stringify(headerPayload));
|
|
700
|
+
return paymentHeader;
|
|
701
|
+
}
|
|
702
|
+
function getChainIdFromNetwork(network) {
|
|
703
|
+
const chainIdMap = {
|
|
704
|
+
"ethereum": 1,
|
|
705
|
+
"sepolia": 11155111,
|
|
706
|
+
"base": 8453,
|
|
707
|
+
"base-sepolia": 84532,
|
|
708
|
+
"polygon": 137,
|
|
709
|
+
"arbitrum": 42161,
|
|
710
|
+
"optimism": 10
|
|
711
|
+
};
|
|
712
|
+
const chainId = chainIdMap[network.toLowerCase()];
|
|
713
|
+
if (!chainId) {
|
|
714
|
+
throw new Error(`Unknown network: ${network}`);
|
|
715
|
+
}
|
|
716
|
+
return chainId;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// src/services/evm/payment-handler.ts
|
|
720
|
+
init_types();
|
|
721
|
+
async function handleEvmPayment(endpoint, config, requestInit) {
|
|
722
|
+
const { wallet, network, maxPaymentAmount } = config;
|
|
723
|
+
const initialResponse = await fetch(endpoint, {
|
|
724
|
+
...requestInit,
|
|
725
|
+
method: requestInit?.method || "POST"
|
|
726
|
+
});
|
|
727
|
+
if (initialResponse.status !== 402) {
|
|
728
|
+
return initialResponse;
|
|
729
|
+
}
|
|
730
|
+
const rawResponse = await initialResponse.json();
|
|
731
|
+
const IGNORED_ERRORS = [
|
|
732
|
+
"X-PAYMENT header is required",
|
|
733
|
+
"missing X-PAYMENT header",
|
|
734
|
+
"payment_required"
|
|
735
|
+
];
|
|
736
|
+
if (rawResponse.error && !IGNORED_ERRORS.includes(rawResponse.error)) {
|
|
737
|
+
console.error(`\u274C Payment verification failed: ${rawResponse.error}`);
|
|
738
|
+
const ERROR_MESSAGES = {
|
|
739
|
+
"insufficient_funds": "Insufficient balance to complete this payment",
|
|
740
|
+
"invalid_signature": "Invalid payment signature",
|
|
741
|
+
"expired": "Payment authorization has expired",
|
|
742
|
+
"already_used": "This payment has already been used",
|
|
743
|
+
"network_mismatch": "Payment network does not match",
|
|
744
|
+
"invalid_payment": "Invalid payment data",
|
|
745
|
+
"verification_failed": "Payment verification failed"
|
|
746
|
+
};
|
|
747
|
+
const errorMessage = ERROR_MESSAGES[rawResponse.error] || `Payment failed: ${rawResponse.error}`;
|
|
748
|
+
const error = new Error(errorMessage);
|
|
749
|
+
throw wrapPaymentError(error);
|
|
750
|
+
}
|
|
751
|
+
const x402Version = rawResponse.x402Version;
|
|
752
|
+
const parsedPaymentRequirements = rawResponse.accepts || [];
|
|
753
|
+
const selectedRequirements = parsedPaymentRequirements.find(
|
|
754
|
+
(req) => req.scheme === "exact" && EvmNetworkSchema.safeParse(req.network.toLowerCase()).success
|
|
755
|
+
);
|
|
756
|
+
if (!selectedRequirements) {
|
|
757
|
+
console.error(
|
|
758
|
+
"\u274C No suitable EVM payment requirements found. Available networks:",
|
|
759
|
+
parsedPaymentRequirements.map((req) => req.network)
|
|
760
|
+
);
|
|
761
|
+
throw new Error("No suitable EVM payment requirements found");
|
|
762
|
+
}
|
|
763
|
+
if (maxPaymentAmount && maxPaymentAmount > BigInt(0)) {
|
|
764
|
+
if (BigInt(selectedRequirements.maxAmountRequired) > maxPaymentAmount) {
|
|
765
|
+
throw new Error(
|
|
766
|
+
`Payment amount ${selectedRequirements.maxAmountRequired} exceeds maximum allowed ${maxPaymentAmount}`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
const targetChainId = getChainIdFromNetwork(selectedRequirements.network);
|
|
771
|
+
let currentChainId;
|
|
772
|
+
if (wallet.getChainId) {
|
|
773
|
+
try {
|
|
774
|
+
const chainIdHex = await wallet.getChainId();
|
|
775
|
+
currentChainId = parseInt(chainIdHex, 16);
|
|
776
|
+
console.log(`\u{1F4CD} Current wallet chain: ${currentChainId}`);
|
|
777
|
+
} catch (error) {
|
|
778
|
+
console.warn("\u26A0\uFE0F Failed to get current chainId:", error);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
const networkNames = {
|
|
782
|
+
1: "Ethereum Mainnet",
|
|
783
|
+
11155111: "Sepolia Testnet",
|
|
784
|
+
8453: "Base Mainnet",
|
|
785
|
+
84532: "Base Sepolia Testnet",
|
|
786
|
+
137: "Polygon Mainnet",
|
|
787
|
+
42161: "Arbitrum One",
|
|
788
|
+
10: "Optimism Mainnet"
|
|
789
|
+
};
|
|
790
|
+
if (currentChainId && currentChainId !== targetChainId) {
|
|
791
|
+
if (!wallet.switchChain) {
|
|
792
|
+
const currentNetworkName = networkNames[currentChainId] || `Chain ${currentChainId}`;
|
|
793
|
+
const targetNetworkName = networkNames[targetChainId] || selectedRequirements.network;
|
|
794
|
+
const error = new Error(
|
|
795
|
+
`Network mismatch: Your wallet is connected to ${currentNetworkName}, but payment requires ${targetNetworkName}. Please switch to ${targetNetworkName} manually in your wallet.`
|
|
796
|
+
);
|
|
797
|
+
throw wrapPaymentError(error);
|
|
798
|
+
}
|
|
799
|
+
try {
|
|
800
|
+
console.log(`\u{1F504} Switching to chain ${targetChainId}...`);
|
|
801
|
+
await wallet.switchChain(`0x${targetChainId.toString(16)}`);
|
|
802
|
+
console.log(`\u2705 Successfully switched to chain ${targetChainId}`);
|
|
803
|
+
} catch (error) {
|
|
804
|
+
console.error("\u274C Failed to switch chain:", error);
|
|
805
|
+
const targetNetworkName = networkNames[targetChainId] || selectedRequirements.network;
|
|
806
|
+
const wrappedError = wrapPaymentError(error);
|
|
807
|
+
let finalError;
|
|
808
|
+
if (wrappedError.code === "USER_REJECTED" /* USER_REJECTED */) {
|
|
809
|
+
finalError = new PaymentOperationError({
|
|
810
|
+
code: wrappedError.code,
|
|
811
|
+
message: wrappedError.message,
|
|
812
|
+
userMessage: `You rejected the network switch request. Please switch to ${targetNetworkName} manually.`,
|
|
813
|
+
originalError: wrappedError.originalError
|
|
814
|
+
});
|
|
815
|
+
} else {
|
|
816
|
+
finalError = new PaymentOperationError({
|
|
817
|
+
code: "NETWORK_SWITCH_FAILED" /* NETWORK_SWITCH_FAILED */,
|
|
818
|
+
message: wrappedError.message,
|
|
819
|
+
userMessage: `Failed to switch to ${targetNetworkName}. Please switch manually in your wallet.`,
|
|
820
|
+
originalError: wrappedError.originalError
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
throw finalError;
|
|
824
|
+
}
|
|
825
|
+
} else if (wallet.switchChain && !currentChainId) {
|
|
826
|
+
try {
|
|
827
|
+
console.log(`\u{1F504} Attempting to switch to chain ${targetChainId}...`);
|
|
828
|
+
await wallet.switchChain(`0x${targetChainId.toString(16)}`);
|
|
829
|
+
console.log(`\u2705 Switch attempted successfully`);
|
|
830
|
+
} catch (error) {
|
|
831
|
+
console.warn("\u26A0\uFE0F Failed to switch chain (best effort):", error);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
let paymentHeader;
|
|
835
|
+
try {
|
|
836
|
+
paymentHeader = await createEvmPaymentHeader({
|
|
837
|
+
wallet,
|
|
838
|
+
paymentRequirements: selectedRequirements,
|
|
839
|
+
x402Version,
|
|
840
|
+
chainId: targetChainId
|
|
841
|
+
});
|
|
842
|
+
} catch (error) {
|
|
843
|
+
console.error("\u274C Failed to create payment header:", error);
|
|
844
|
+
throw wrapPaymentError(error);
|
|
845
|
+
}
|
|
846
|
+
const newInit = {
|
|
847
|
+
...requestInit,
|
|
848
|
+
method: requestInit?.method || "POST",
|
|
849
|
+
headers: {
|
|
850
|
+
...requestInit?.headers || {},
|
|
851
|
+
"X-PAYMENT": paymentHeader,
|
|
852
|
+
"Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE"
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
const retryResponse = await fetch(endpoint, newInit);
|
|
856
|
+
if (retryResponse.status === 402) {
|
|
857
|
+
try {
|
|
858
|
+
const retryData = await retryResponse.json();
|
|
859
|
+
const IGNORED_ERRORS2 = [
|
|
860
|
+
"X-PAYMENT header is required",
|
|
861
|
+
"missing X-PAYMENT header",
|
|
862
|
+
"payment_required"
|
|
863
|
+
];
|
|
864
|
+
if (retryData.error && !IGNORED_ERRORS2.includes(retryData.error)) {
|
|
865
|
+
console.error(`\u274C Payment verification failed: ${retryData.error}`);
|
|
866
|
+
const ERROR_MESSAGES = {
|
|
867
|
+
"insufficient_funds": "Insufficient balance to complete this payment",
|
|
868
|
+
"invalid_signature": "Invalid payment signature",
|
|
869
|
+
"expired": "Payment authorization has expired",
|
|
870
|
+
"already_used": "This payment has already been used",
|
|
871
|
+
"network_mismatch": "Payment network does not match",
|
|
872
|
+
"invalid_payment": "Invalid payment data",
|
|
873
|
+
"verification_failed": "Payment verification failed"
|
|
874
|
+
};
|
|
875
|
+
const errorMessage = ERROR_MESSAGES[retryData.error] || `Payment failed: ${retryData.error}`;
|
|
876
|
+
const error = new Error(errorMessage);
|
|
877
|
+
throw wrapPaymentError(error);
|
|
878
|
+
}
|
|
879
|
+
} catch (error) {
|
|
880
|
+
if (error instanceof PaymentOperationError) {
|
|
881
|
+
throw error;
|
|
882
|
+
}
|
|
883
|
+
console.warn("\u26A0\uFE0F Could not parse retry 402 response:", error);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return retryResponse;
|
|
887
|
+
}
|
|
261
888
|
|
|
262
889
|
// src/utils/payment-helpers.ts
|
|
890
|
+
init_common();
|
|
263
891
|
import { ethers as ethers2 } from "ethers";
|
|
264
892
|
function parsePaymentRequired(response) {
|
|
265
893
|
if (response && typeof response === "object") {
|
|
@@ -284,6 +912,62 @@ function getSupportedNetworkTypes(paymentRequirements) {
|
|
|
284
912
|
});
|
|
285
913
|
return Array.from(networkTypes);
|
|
286
914
|
}
|
|
915
|
+
async function makePayment(networkType, merchantId, endpoint = PROD_BACK_URL, additionalParams) {
|
|
916
|
+
const fullEndpoint = `${endpoint}/${merchantId}`;
|
|
917
|
+
let response;
|
|
918
|
+
const requestInit = additionalParams && Object.keys(additionalParams).length > 0 ? {
|
|
919
|
+
body: JSON.stringify(additionalParams),
|
|
920
|
+
headers: {
|
|
921
|
+
"Content-Type": "application/json"
|
|
922
|
+
}
|
|
923
|
+
} : {};
|
|
924
|
+
if (networkType === "solana" /* SOLANA */ || networkType === "svm" /* SVM */) {
|
|
925
|
+
const solana = window.solana;
|
|
926
|
+
if (!solana) {
|
|
927
|
+
throw new Error("\u8BF7\u5B89\u88C5 Phantom \u94B1\u5305");
|
|
928
|
+
}
|
|
929
|
+
if (!solana.isConnected) {
|
|
930
|
+
await solana.connect();
|
|
931
|
+
}
|
|
932
|
+
response = await handleSvmPayment(fullEndpoint, {
|
|
933
|
+
wallet: solana,
|
|
934
|
+
network: "solana"
|
|
935
|
+
// Will use backend's network configuration
|
|
936
|
+
}, requestInit);
|
|
937
|
+
} else if (networkType === "evm" /* EVM */) {
|
|
938
|
+
if (!window.ethereum) {
|
|
939
|
+
throw new Error("\u8BF7\u5B89\u88C5 MetaMask \u94B1\u5305");
|
|
940
|
+
}
|
|
941
|
+
const provider = new ethers2.BrowserProvider(window.ethereum);
|
|
942
|
+
const signer = await provider.getSigner();
|
|
943
|
+
const wallet = {
|
|
944
|
+
address: await signer.getAddress(),
|
|
945
|
+
signTypedData: async (domain, types, message2) => {
|
|
946
|
+
return await signer.signTypedData(domain, types, message2);
|
|
947
|
+
},
|
|
948
|
+
// Get current chain ID from wallet
|
|
949
|
+
getChainId: async () => {
|
|
950
|
+
const network = await provider.getNetwork();
|
|
951
|
+
return `0x${network.chainId.toString(16)}`;
|
|
952
|
+
},
|
|
953
|
+
// Switch to a different chain
|
|
954
|
+
switchChain: async (chainId) => {
|
|
955
|
+
await window.ethereum.request({
|
|
956
|
+
method: "wallet_switchEthereumChain",
|
|
957
|
+
params: [{ chainId }]
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
response = await handleEvmPayment(fullEndpoint, {
|
|
962
|
+
wallet,
|
|
963
|
+
network: "base"
|
|
964
|
+
// Will use backend's network configuration
|
|
965
|
+
}, requestInit);
|
|
966
|
+
} else {
|
|
967
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684\u7F51\u7EDC\u7C7B\u578B: ${networkType}`);
|
|
968
|
+
}
|
|
969
|
+
return response;
|
|
970
|
+
}
|
|
287
971
|
|
|
288
972
|
// src/utils/network.ts
|
|
289
973
|
var NETWORK_TYPE_MAP = {
|
|
@@ -322,6 +1006,138 @@ function getNetworkDisplayName(network) {
|
|
|
322
1006
|
return displayNames[network.toLowerCase()] || network;
|
|
323
1007
|
}
|
|
324
1008
|
|
|
1009
|
+
// src/utils/payment-error-handler.ts
|
|
1010
|
+
function parsePaymentError(error) {
|
|
1011
|
+
if (!error) {
|
|
1012
|
+
return {
|
|
1013
|
+
code: "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
1014
|
+
message: "Unknown error occurred",
|
|
1015
|
+
userMessage: "An unknown error occurred. Please try again.",
|
|
1016
|
+
originalError: error
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
const errorMessage = error.message || error.toString();
|
|
1020
|
+
const errorCode = error.code;
|
|
1021
|
+
if (errorCode === 4001 || errorCode === "ACTION_REJECTED" || errorMessage.includes("User rejected") || errorMessage.includes("user rejected") || errorMessage.includes("User denied") || errorMessage.includes("user denied") || errorMessage.includes("ethers-user-denied")) {
|
|
1022
|
+
return {
|
|
1023
|
+
code: "USER_REJECTED" /* USER_REJECTED */,
|
|
1024
|
+
message: "User rejected the transaction",
|
|
1025
|
+
userMessage: "You rejected the signature request. Please try again if you want to proceed.",
|
|
1026
|
+
originalError: error
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
if (errorMessage.includes("chainId") && (errorMessage.includes("must match") || errorMessage.includes("does not match"))) {
|
|
1030
|
+
const match = errorMessage.match(/chainId.*?"(\d+)".*?active.*?"(\d+)"/i) || errorMessage.match(/chain (\d+).*?chain (\d+)/i);
|
|
1031
|
+
if (match) {
|
|
1032
|
+
const [, requestedChain, activeChain] = match;
|
|
1033
|
+
return {
|
|
1034
|
+
code: "CHAIN_ID_MISMATCH" /* CHAIN_ID_MISMATCH */,
|
|
1035
|
+
message: `Network mismatch (wallet is on different chain): Requested ${requestedChain}, but wallet is on ${activeChain}`,
|
|
1036
|
+
userMessage: `Your wallet is on the wrong network. Please switch to the correct network and try again.`,
|
|
1037
|
+
originalError: error
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
return {
|
|
1041
|
+
code: "CHAIN_ID_MISMATCH" /* CHAIN_ID_MISMATCH */,
|
|
1042
|
+
message: "Network mismatch (wallet selected network does not match)",
|
|
1043
|
+
userMessage: "Your wallet is on the wrong network. Please switch to the correct network.",
|
|
1044
|
+
originalError: error
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
if (errorMessage.includes("Network mismatch") || errorMessage.includes("Wrong network") || errorMessage.includes("Incorrect network")) {
|
|
1048
|
+
return {
|
|
1049
|
+
code: "NETWORK_MISMATCH" /* NETWORK_MISMATCH */,
|
|
1050
|
+
message: errorMessage,
|
|
1051
|
+
userMessage: "Please switch your wallet to the correct network.",
|
|
1052
|
+
originalError: error
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
if (errorMessage.includes("locked") || errorMessage.includes("Wallet is locked")) {
|
|
1056
|
+
return {
|
|
1057
|
+
code: "WALLET_LOCKED" /* WALLET_LOCKED */,
|
|
1058
|
+
message: "Wallet is locked",
|
|
1059
|
+
userMessage: "Please unlock your wallet and try again.",
|
|
1060
|
+
originalError: error
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
if (errorMessage.includes("insufficient") && (errorMessage.includes("balance") || errorMessage.includes("funds"))) {
|
|
1064
|
+
return {
|
|
1065
|
+
code: "INSUFFICIENT_BALANCE" /* INSUFFICIENT_BALANCE */,
|
|
1066
|
+
message: "Insufficient balance",
|
|
1067
|
+
userMessage: "You don't have enough balance to complete this payment.",
|
|
1068
|
+
originalError: error
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
if (errorMessage.includes("Failed to switch") || errorMessage.includes("switch chain")) {
|
|
1072
|
+
return {
|
|
1073
|
+
code: "NETWORK_SWITCH_FAILED" /* NETWORK_SWITCH_FAILED */,
|
|
1074
|
+
message: errorMessage,
|
|
1075
|
+
userMessage: "Failed to switch network. Please switch manually in your wallet.",
|
|
1076
|
+
originalError: error
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
if (errorMessage.includes("not connected") || errorMessage.includes("No wallet") || errorMessage.includes("Connect wallet")) {
|
|
1080
|
+
return {
|
|
1081
|
+
code: "WALLET_NOT_CONNECTED" /* WALLET_NOT_CONNECTED */,
|
|
1082
|
+
message: "Wallet not connected",
|
|
1083
|
+
userMessage: "Please connect your wallet first.",
|
|
1084
|
+
originalError: error
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
if (errorMessage.includes("No suitable") || errorMessage.includes("payment requirements") || errorMessage.includes("Missing payTo") || errorMessage.includes("Missing asset")) {
|
|
1088
|
+
return {
|
|
1089
|
+
code: "INVALID_PAYMENT_REQUIREMENTS" /* INVALID_PAYMENT_REQUIREMENTS */,
|
|
1090
|
+
message: errorMessage,
|
|
1091
|
+
userMessage: "Invalid payment configuration. Please contact support.",
|
|
1092
|
+
originalError: error
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
if (errorMessage.includes("exceeds maximum")) {
|
|
1096
|
+
return {
|
|
1097
|
+
code: "AMOUNT_EXCEEDED" /* AMOUNT_EXCEEDED */,
|
|
1098
|
+
message: errorMessage,
|
|
1099
|
+
userMessage: "Payment amount exceeds the maximum allowed.",
|
|
1100
|
+
originalError: error
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
if (errorMessage.includes("signature") || errorMessage.includes("sign") || errorCode === "UNKNOWN_ERROR") {
|
|
1104
|
+
return {
|
|
1105
|
+
code: "SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
|
|
1106
|
+
message: errorMessage,
|
|
1107
|
+
userMessage: "Failed to sign the transaction. Please try again.",
|
|
1108
|
+
originalError: error
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
return {
|
|
1112
|
+
code: "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
1113
|
+
message: errorMessage,
|
|
1114
|
+
userMessage: "An unexpected error occurred. Please try again or contact support.",
|
|
1115
|
+
originalError: error
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
var PaymentOperationError = class _PaymentOperationError extends Error {
|
|
1119
|
+
constructor(paymentError) {
|
|
1120
|
+
super(paymentError.message);
|
|
1121
|
+
this.name = "PaymentOperationError";
|
|
1122
|
+
this.code = paymentError.code;
|
|
1123
|
+
this.userMessage = paymentError.userMessage;
|
|
1124
|
+
this.originalError = paymentError.originalError;
|
|
1125
|
+
if (Error.captureStackTrace) {
|
|
1126
|
+
Error.captureStackTrace(this, _PaymentOperationError);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Get a formatted error message for logging
|
|
1131
|
+
*/
|
|
1132
|
+
toLogString() {
|
|
1133
|
+
return `[${this.code}] ${this.message} | User Message: ${this.userMessage}`;
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
function wrapPaymentError(error) {
|
|
1137
|
+
const parsedError = parsePaymentError(error);
|
|
1138
|
+
return new PaymentOperationError(parsedError);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
325
1141
|
// src/react/store/walletStore.ts
|
|
326
1142
|
var WalletStore = class {
|
|
327
1143
|
constructor() {
|
|
@@ -338,60 +1154,28 @@ var WalletStore = class {
|
|
|
338
1154
|
init() {
|
|
339
1155
|
if (this.initialized) return;
|
|
340
1156
|
this.initialized = true;
|
|
341
|
-
this.autoReconnect();
|
|
342
1157
|
onAccountsChanged((accounts) => {
|
|
343
|
-
|
|
344
|
-
if (connectedType === "evm" /* EVM */) {
|
|
1158
|
+
if (this.state.networkType === "evm" /* EVM */) {
|
|
345
1159
|
if (accounts.length === 0) {
|
|
346
1160
|
this.setState({ address: null });
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
this.setState({ address: accounts[0] });
|
|
351
|
-
console.log("\u{1F504} Account changed:", accounts[0]);
|
|
352
|
-
}
|
|
1161
|
+
} else if (!isWalletManuallyDisconnected("evm" /* EVM */)) {
|
|
1162
|
+
this.setState({ address: accounts[0] });
|
|
1163
|
+
saveWalletAddress("evm" /* EVM */, accounts[0]);
|
|
353
1164
|
}
|
|
354
1165
|
}
|
|
355
1166
|
});
|
|
356
1167
|
onChainChanged(() => {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
console.log("\u26A0\uFE0F Network changed detected - disconnecting wallet");
|
|
360
|
-
disconnectWallet();
|
|
361
|
-
this.setState({
|
|
362
|
-
address: null,
|
|
363
|
-
networkType: null,
|
|
364
|
-
error: "Network changed. Please reconnect your wallet."
|
|
365
|
-
});
|
|
1168
|
+
if (this.state.networkType === "evm" /* EVM */) {
|
|
1169
|
+
this.handleDisconnect("evm" /* EVM */, "Chain changed. Please reconnect your wallet.");
|
|
366
1170
|
}
|
|
367
1171
|
});
|
|
368
1172
|
onWalletDisconnect(() => {
|
|
369
|
-
const
|
|
370
|
-
if (
|
|
371
|
-
|
|
372
|
-
disconnectWallet();
|
|
373
|
-
this.setState({
|
|
374
|
-
address: null,
|
|
375
|
-
networkType: null
|
|
376
|
-
});
|
|
1173
|
+
const svmTypes = ["solana" /* SOLANA */, "svm" /* SVM */];
|
|
1174
|
+
if (this.state.networkType && svmTypes.includes(this.state.networkType)) {
|
|
1175
|
+
this.handleDisconnect(this.state.networkType);
|
|
377
1176
|
}
|
|
378
1177
|
});
|
|
379
1178
|
}
|
|
380
|
-
async autoReconnect() {
|
|
381
|
-
if (!isWalletManuallyDisconnected()) {
|
|
382
|
-
const connectedType = getConnectedNetworkType();
|
|
383
|
-
if (connectedType) {
|
|
384
|
-
const currentAddress = await getCurrentWallet(connectedType);
|
|
385
|
-
if (currentAddress) {
|
|
386
|
-
this.setState({
|
|
387
|
-
address: currentAddress,
|
|
388
|
-
networkType: connectedType
|
|
389
|
-
});
|
|
390
|
-
console.log("\u{1F504} Auto-reconnected wallet:", currentAddress);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
1179
|
// Get current state
|
|
396
1180
|
getState() {
|
|
397
1181
|
return this.state;
|
|
@@ -412,18 +1196,32 @@ var WalletStore = class {
|
|
|
412
1196
|
notifyListeners() {
|
|
413
1197
|
this.listeners.forEach((listener) => listener());
|
|
414
1198
|
}
|
|
1199
|
+
// Handle wallet disconnect (internal helper)
|
|
1200
|
+
handleDisconnect(networkType, error) {
|
|
1201
|
+
removeWalletAddress(networkType);
|
|
1202
|
+
markWalletDisconnected(networkType);
|
|
1203
|
+
if (typeof window !== "undefined") {
|
|
1204
|
+
localStorage.removeItem("connected_network_type");
|
|
1205
|
+
}
|
|
1206
|
+
this.setState({
|
|
1207
|
+
address: null,
|
|
1208
|
+
networkType: null,
|
|
1209
|
+
error: error || null
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
415
1212
|
// Connect wallet
|
|
416
1213
|
async connect(type) {
|
|
1214
|
+
if (this.state.address && this.state.networkType && this.state.networkType !== type) {
|
|
1215
|
+
saveWalletAddress(this.state.networkType, this.state.address);
|
|
1216
|
+
}
|
|
417
1217
|
this.setState({ isConnecting: true, error: null });
|
|
418
1218
|
try {
|
|
419
1219
|
const walletAddress = await connectWallet(type);
|
|
420
|
-
console.log("\u2705 Wallet connected:", walletAddress, "Network:", type);
|
|
421
1220
|
this.setState({
|
|
422
1221
|
address: walletAddress,
|
|
423
1222
|
networkType: type,
|
|
424
1223
|
isConnecting: false
|
|
425
1224
|
});
|
|
426
|
-
console.log("\u{1F4DD} Store state updated");
|
|
427
1225
|
} catch (err) {
|
|
428
1226
|
this.setState({
|
|
429
1227
|
error: err.message || "Failed to connect wallet",
|
|
@@ -432,20 +1230,67 @@ var WalletStore = class {
|
|
|
432
1230
|
throw err;
|
|
433
1231
|
}
|
|
434
1232
|
}
|
|
1233
|
+
// Switch network (use cached wallet if available)
|
|
1234
|
+
async switchNetwork(type) {
|
|
1235
|
+
if (this.state.address && this.state.networkType) {
|
|
1236
|
+
saveWalletAddress(this.state.networkType, this.state.address);
|
|
1237
|
+
}
|
|
1238
|
+
this.setState({ isConnecting: true, error: null });
|
|
1239
|
+
try {
|
|
1240
|
+
const address = await switchNetwork(type);
|
|
1241
|
+
if (address) {
|
|
1242
|
+
this.setState({
|
|
1243
|
+
address,
|
|
1244
|
+
networkType: type,
|
|
1245
|
+
isConnecting: false
|
|
1246
|
+
});
|
|
1247
|
+
} else {
|
|
1248
|
+
this.setState({
|
|
1249
|
+
address: null,
|
|
1250
|
+
networkType: type,
|
|
1251
|
+
isConnecting: true
|
|
1252
|
+
});
|
|
1253
|
+
await this.connect(type);
|
|
1254
|
+
}
|
|
1255
|
+
} catch (err) {
|
|
1256
|
+
this.setState({
|
|
1257
|
+
error: err.message || "Failed to switch network",
|
|
1258
|
+
isConnecting: false
|
|
1259
|
+
});
|
|
1260
|
+
throw err;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
435
1263
|
// Disconnect wallet
|
|
436
1264
|
disconnect() {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
1265
|
+
const currentNetwork = this.state.networkType;
|
|
1266
|
+
if (currentNetwork) {
|
|
1267
|
+
this.handleDisconnect(currentNetwork);
|
|
1268
|
+
} else {
|
|
1269
|
+
this.setState({
|
|
1270
|
+
address: null,
|
|
1271
|
+
networkType: null,
|
|
1272
|
+
error: null
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
444
1275
|
}
|
|
445
1276
|
// Clear error
|
|
446
1277
|
clearError() {
|
|
447
1278
|
this.setState({ error: null });
|
|
448
1279
|
}
|
|
1280
|
+
// Ensure network matches expected type (for page-specific network requirements)
|
|
1281
|
+
async ensureNetwork(expectedNetwork) {
|
|
1282
|
+
if (isWalletManuallyDisconnected(expectedNetwork)) {
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
if (this.state.networkType === expectedNetwork && this.state.address) {
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
if (this.state.networkType !== expectedNetwork) {
|
|
1289
|
+
await this.switchNetwork(expectedNetwork);
|
|
1290
|
+
} else if (!this.state.address) {
|
|
1291
|
+
await this.connect(expectedNetwork);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
449
1294
|
};
|
|
450
1295
|
var walletStore = new WalletStore();
|
|
451
1296
|
if (typeof window !== "undefined") {
|
|
@@ -463,11 +1308,30 @@ function useWallet() {
|
|
|
463
1308
|
return {
|
|
464
1309
|
...state,
|
|
465
1310
|
connect: (type) => walletStore.connect(type),
|
|
1311
|
+
switchNetwork: (type) => walletStore.switchNetwork(type),
|
|
1312
|
+
ensureNetwork: (type) => walletStore.ensureNetwork(type),
|
|
466
1313
|
disconnect: () => walletStore.disconnect(),
|
|
467
1314
|
clearError: () => walletStore.clearError()
|
|
468
1315
|
};
|
|
469
1316
|
}
|
|
470
1317
|
|
|
1318
|
+
// src/react/hooks/usePageNetwork.ts
|
|
1319
|
+
import { useEffect } from "react";
|
|
1320
|
+
function usePageNetwork(expectedNetwork, options = {}) {
|
|
1321
|
+
const {
|
|
1322
|
+
autoSwitch = true,
|
|
1323
|
+
switchOnMount = true
|
|
1324
|
+
} = options;
|
|
1325
|
+
const wallet = useWallet();
|
|
1326
|
+
useEffect(() => {
|
|
1327
|
+
if (!autoSwitch || !switchOnMount) return;
|
|
1328
|
+
wallet.ensureNetwork(expectedNetwork).catch((err) => {
|
|
1329
|
+
console.error("Failed to ensure network:", err);
|
|
1330
|
+
});
|
|
1331
|
+
}, [expectedNetwork]);
|
|
1332
|
+
return wallet;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
471
1335
|
// src/react/hooks/usePayment.ts
|
|
472
1336
|
import { useCallback, useState } from "react";
|
|
473
1337
|
function usePayment() {
|
|
@@ -499,7 +1363,8 @@ function usePayment() {
|
|
|
499
1363
|
}
|
|
500
1364
|
|
|
501
1365
|
// src/react/hooks/usePaymentInfo.ts
|
|
502
|
-
import { useEffect, useState as useState2 } from "react";
|
|
1366
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
1367
|
+
init_common();
|
|
503
1368
|
function usePaymentInfo(merchantId, endpoint = PROD_BACK_URL, additionalParams) {
|
|
504
1369
|
const [paymentInfo, setPaymentInfo] = useState2(null);
|
|
505
1370
|
const [supportedNetworks, setSupportedNetworks] = useState2([]);
|
|
@@ -538,7 +1403,7 @@ function usePaymentInfo(merchantId, endpoint = PROD_BACK_URL, additionalParams)
|
|
|
538
1403
|
setIsLoading(false);
|
|
539
1404
|
}
|
|
540
1405
|
};
|
|
541
|
-
|
|
1406
|
+
useEffect2(() => {
|
|
542
1407
|
fetchPaymentInfo();
|
|
543
1408
|
}, [endpoint, merchantId]);
|
|
544
1409
|
return {
|
|
@@ -787,8 +1652,572 @@ function WalletConnect({
|
|
|
787
1652
|
"Disconnect"
|
|
788
1653
|
)), /* @__PURE__ */ React.createElement("p", { style: getHintStyle() }, "Switch account in your wallet to change address")));
|
|
789
1654
|
}
|
|
1655
|
+
|
|
1656
|
+
// src/react/components/V402Checkout.tsx
|
|
1657
|
+
import React3, { useEffect as useEffect3, useState as useState4 } from "react";
|
|
1658
|
+
import { Button, Card, Divider, message, Spin, Tooltip, Typography } from "antd";
|
|
1659
|
+
import {
|
|
1660
|
+
DisconnectOutlined,
|
|
1661
|
+
InfoCircleOutlined,
|
|
1662
|
+
LinkOutlined,
|
|
1663
|
+
LoadingOutlined,
|
|
1664
|
+
LockOutlined,
|
|
1665
|
+
SafetyOutlined
|
|
1666
|
+
} from "@ant-design/icons";
|
|
1667
|
+
init_common();
|
|
1668
|
+
|
|
1669
|
+
// src/react/utils/CryptoIcons.tsx
|
|
1670
|
+
import React2 from "react";
|
|
1671
|
+
var SolanaIcon = ({ width = 16, height = 16, className, style }) => {
|
|
1672
|
+
return /* @__PURE__ */ React2.createElement(
|
|
1673
|
+
"svg",
|
|
1674
|
+
{
|
|
1675
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1676
|
+
viewBox: "0 0 16 16",
|
|
1677
|
+
width,
|
|
1678
|
+
height,
|
|
1679
|
+
className,
|
|
1680
|
+
style
|
|
1681
|
+
},
|
|
1682
|
+
/* @__PURE__ */ React2.createElement("desc", null, "Solana SOL Fill Streamline Icon: https://streamlinehq.com"),
|
|
1683
|
+
/* @__PURE__ */ React2.createElement("g", { fill: "none", fillRule: "evenodd" }, /* @__PURE__ */ React2.createElement(
|
|
1684
|
+
"path",
|
|
1685
|
+
{
|
|
1686
|
+
d: "M16 0v16H0V0h16ZM8.395333333333333 15.505333333333333l-0.007333333333333332 0.0013333333333333333 -0.047333333333333324 0.023333333333333334 -0.013333333333333332 0.0026666666666666666 -0.009333333333333332 -0.0026666666666666666 -0.047333333333333324 -0.023333333333333334c-0.006666666666666666 -0.0026666666666666666 -0.012666666666666666 -0.0006666666666666666 -0.016 0.003333333333333333l-0.0026666666666666666 0.006666666666666666 -0.011333333333333334 0.2853333333333333 0.003333333333333333 0.013333333333333332 0.006666666666666666 0.008666666666666666 0.06933333333333333 0.049333333333333326 0.009999999999999998 0.0026666666666666666 0.008 -0.0026666666666666666 0.06933333333333333 -0.049333333333333326 0.008 -0.010666666666666666 0.0026666666666666666 -0.011333333333333334 -0.011333333333333334 -0.2846666666666666c-0.0013333333333333333 -0.006666666666666666 -0.005999999999999999 -0.011333333333333334 -0.011333333333333334 -0.011999999999999999Zm0.17666666666666667 -0.07533333333333334 -0.008666666666666666 0.0013333333333333333 -0.12333333333333332 0.062 -0.006666666666666666 0.006666666666666666 -0.002 0.007333333333333332 0.011999999999999999 0.2866666666666666 0.003333333333333333 0.008 0.005333333333333333 0.004666666666666666 0.134 0.062c0.008 0.0026666666666666666 0.015333333333333332 0 0.019333333333333334 -0.005333333333333333l0.0026666666666666666 -0.009333333333333332 -0.02266666666666667 -0.4093333333333333c-0.002 -0.008 -0.006666666666666666 -0.013333333333333332 -0.013333333333333332 -0.014666666666666665Zm-0.4766666666666666 0.0013333333333333333a0.015333333333333332 0.015333333333333332 0 0 0 -0.018 0.004l-0.004 0.009333333333333332 -0.02266666666666667 0.4093333333333333c0 0.008 0.004666666666666666 0.013333333333333332 0.011333333333333334 0.016l0.009999999999999998 -0.0013333333333333333 0.134 -0.062 0.006666666666666666 -0.005333333333333333 0.0026666666666666666 -0.007333333333333332 0.011333333333333334 -0.2866666666666666 -0.002 -0.008 -0.006666666666666666 -0.006666666666666666 -0.12266666666666666 -0.06133333333333333Z",
|
|
1687
|
+
strokeWidth: "0.6667"
|
|
1688
|
+
}
|
|
1689
|
+
), /* @__PURE__ */ React2.createElement(
|
|
1690
|
+
"path",
|
|
1691
|
+
{
|
|
1692
|
+
fill: "#000000",
|
|
1693
|
+
d: "M4.862 2.862A0.6666666666666666 0.6666666666666666 0 0 1 5.333333333333333 2.6666666666666665h8.666666666666666a0.6666666666666666 0.6666666666666666 0 0 1 0.47133333333333327 1.138l-2 2A0.6666666666666666 0.6666666666666666 0 0 1 12 6H3.333333333333333a0.6666666666666666 0.6666666666666666 0 0 1 -0.47133333333333327 -1.138l2 -2Zm-2.1166666666666663 4.156666666666666A0.6666666666666666 0.6666666666666666 0 0 1 3.333333333333333 6.666666666666666h8.666666666666666a0.6666666666666666 0.6666666666666666 0 0 1 0.5546666666666666 0.29666666666666663l1.3333333333333333 2A0.6666666666666666 0.6666666666666666 0 0 1 13.333333333333332 10H4.666666666666666a0.6666666666666666 0.6666666666666666 0 0 1 -0.5546666666666666 -0.29666666666666663l-1.3333333333333333 -2a0.6666666666666666 0.6666666666666666 0 0 1 -0.03333333333333333 -0.6846666666666665Zm1.4499999999999997 3.843333333333333A0.6666666666666666 0.6666666666666666 0 0 1 4.666666666666666 10.666666666666666h8.666666666666666a0.6666666666666666 0.6666666666666666 0 0 1 0.47133333333333327 1.138l-2 2A0.6666666666666666 0.6666666666666666 0 0 1 11.333333333333332 14H2.6666666666666665a0.6666666666666666 0.6666666666666666 0 0 1 -0.47133333333333327 -1.138l2 -2Z",
|
|
1694
|
+
strokeWidth: "0.6667"
|
|
1695
|
+
}
|
|
1696
|
+
))
|
|
1697
|
+
);
|
|
1698
|
+
};
|
|
1699
|
+
var BaseIcon = ({ width = 24, height = 24, className, style }) => {
|
|
1700
|
+
return /* @__PURE__ */ React2.createElement(
|
|
1701
|
+
"svg",
|
|
1702
|
+
{
|
|
1703
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1704
|
+
viewBox: "0 0 24 24",
|
|
1705
|
+
fill: "none",
|
|
1706
|
+
stroke: "#000000",
|
|
1707
|
+
strokeLinecap: "round",
|
|
1708
|
+
strokeLinejoin: "round",
|
|
1709
|
+
width,
|
|
1710
|
+
height,
|
|
1711
|
+
className,
|
|
1712
|
+
style
|
|
1713
|
+
},
|
|
1714
|
+
/* @__PURE__ */ React2.createElement("desc", null, "Brand Coinbase Streamline Icon: https://streamlinehq.com"),
|
|
1715
|
+
/* @__PURE__ */ React2.createElement(
|
|
1716
|
+
"path",
|
|
1717
|
+
{
|
|
1718
|
+
d: "M12.95 22c-4.503 0 -8.445 -3.04 -9.61 -7.413 -1.165 -4.373 0.737 -8.988 4.638 -11.25a9.906 9.906 0 0 1 12.008 1.598l-3.335 3.367a5.185 5.185 0 0 0 -7.354 0.013 5.252 5.252 0 0 0 0 7.393 5.185 5.185 0 0 0 7.354 0.013L20 19.088A9.887 9.887 0 0 1 12.95 22z",
|
|
1719
|
+
strokeWidth: "2"
|
|
1720
|
+
}
|
|
1721
|
+
)
|
|
1722
|
+
);
|
|
1723
|
+
};
|
|
1724
|
+
var getNetworkIcon = (network) => {
|
|
1725
|
+
const networkLower = network.toLowerCase();
|
|
1726
|
+
if (networkLower.includes("solana")) {
|
|
1727
|
+
return SolanaIcon;
|
|
1728
|
+
}
|
|
1729
|
+
if (networkLower.includes("base")) {
|
|
1730
|
+
return BaseIcon;
|
|
1731
|
+
}
|
|
1732
|
+
return BaseIcon;
|
|
1733
|
+
};
|
|
1734
|
+
|
|
1735
|
+
// src/react/components/V402Checkout.tsx
|
|
1736
|
+
var { Title, Text } = Typography;
|
|
1737
|
+
var notify = {
|
|
1738
|
+
success: (title, msg) => {
|
|
1739
|
+
message.success(`${title}: ${msg}`);
|
|
1740
|
+
},
|
|
1741
|
+
error: (title, msg) => {
|
|
1742
|
+
message.error(`${title}: ${msg}`);
|
|
1743
|
+
},
|
|
1744
|
+
info: (title, msg) => {
|
|
1745
|
+
message.info(`${title}: ${msg}`);
|
|
1746
|
+
}
|
|
1747
|
+
};
|
|
1748
|
+
function V402Checkout({
|
|
1749
|
+
checkoutId,
|
|
1750
|
+
headerInfo = {},
|
|
1751
|
+
isModal = false,
|
|
1752
|
+
onPaymentComplete,
|
|
1753
|
+
additionalParams = {},
|
|
1754
|
+
expectedNetwork
|
|
1755
|
+
}) {
|
|
1756
|
+
const {
|
|
1757
|
+
title = "V402Pay - Make x402Pay Easier",
|
|
1758
|
+
subtitle = "onvoyage.ai",
|
|
1759
|
+
tooltipText = "V402Pay - Accept Crypto Payments Easier"
|
|
1760
|
+
} = headerInfo;
|
|
1761
|
+
let endpoint = DEV_BACK_URL;
|
|
1762
|
+
const {
|
|
1763
|
+
supportedNetworks,
|
|
1764
|
+
isLoading: fetchingPaymentInfo,
|
|
1765
|
+
paymentInfo
|
|
1766
|
+
} = usePaymentInfo(checkoutId, endpoint, additionalParams);
|
|
1767
|
+
const targetNetwork = expectedNetwork || supportedNetworks[0];
|
|
1768
|
+
const { address, networkType, disconnect, ensureNetwork } = usePageNetwork(
|
|
1769
|
+
targetNetwork,
|
|
1770
|
+
{ autoSwitch: !!targetNetwork, switchOnMount: true }
|
|
1771
|
+
);
|
|
1772
|
+
const { isProcessing, setIsProcessing, result, setResult, error, setError } = usePayment();
|
|
1773
|
+
const [paymentDetails, setPaymentDetails] = useState4(null);
|
|
1774
|
+
const handleDisconnect = () => {
|
|
1775
|
+
disconnect();
|
|
1776
|
+
setResult(null);
|
|
1777
|
+
setError(null);
|
|
1778
|
+
notify.info("Wallet Disconnected", "Your wallet has been disconnected successfully.");
|
|
1779
|
+
};
|
|
1780
|
+
useEffect3(() => {
|
|
1781
|
+
if (paymentInfo && paymentInfo.length > 0) {
|
|
1782
|
+
const firstPayment = paymentInfo[0];
|
|
1783
|
+
const rawAmount = firstPayment.maxAmountRequired?.toString() || "0";
|
|
1784
|
+
const decimals = 6;
|
|
1785
|
+
const humanReadableAmount = (Number(rawAmount) / Math.pow(10, decimals)).toFixed(2);
|
|
1786
|
+
const network = firstPayment.network || "Unknown";
|
|
1787
|
+
const currency = "USDC";
|
|
1788
|
+
setPaymentDetails({
|
|
1789
|
+
amount: humanReadableAmount,
|
|
1790
|
+
currency,
|
|
1791
|
+
network
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
}, [paymentInfo]);
|
|
1795
|
+
useEffect3(() => {
|
|
1796
|
+
if (targetNetwork && !fetchingPaymentInfo && ensureNetwork) {
|
|
1797
|
+
ensureNetwork(targetNetwork).catch((err) => {
|
|
1798
|
+
console.error("Failed to ensure network:", err);
|
|
1799
|
+
});
|
|
1800
|
+
}
|
|
1801
|
+
}, [targetNetwork, fetchingPaymentInfo]);
|
|
1802
|
+
const handlePayment = async () => {
|
|
1803
|
+
if (!networkType) {
|
|
1804
|
+
notify.error("Wallet Not Connected", "Please connect your wallet first.");
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
setResult(null);
|
|
1808
|
+
setError(null);
|
|
1809
|
+
setIsProcessing(true);
|
|
1810
|
+
try {
|
|
1811
|
+
const response = await makePayment(networkType, checkoutId, endpoint, additionalParams);
|
|
1812
|
+
const data = await response.json();
|
|
1813
|
+
setResult(data);
|
|
1814
|
+
notify.success("Payment Successful!", "Your payment has been processed successfully.");
|
|
1815
|
+
if (onPaymentComplete) {
|
|
1816
|
+
onPaymentComplete(data);
|
|
1817
|
+
}
|
|
1818
|
+
} catch (err) {
|
|
1819
|
+
const errorMessage = err.message || "Payment failed";
|
|
1820
|
+
setError(errorMessage);
|
|
1821
|
+
notify.error("Payment Failed", errorMessage);
|
|
1822
|
+
} finally {
|
|
1823
|
+
setIsProcessing(false);
|
|
1824
|
+
}
|
|
1825
|
+
};
|
|
1826
|
+
const getNetworkColor = (network) => {
|
|
1827
|
+
if (network.toLowerCase().includes("solana")) return "#14F195";
|
|
1828
|
+
if (network.toLowerCase().includes("evm") || network.toLowerCase().includes("base")) return "#0052FF";
|
|
1829
|
+
return "#8c8c8c";
|
|
1830
|
+
};
|
|
1831
|
+
const NetworkIcon = paymentDetails ? getNetworkIcon(paymentDetails.network) : null;
|
|
1832
|
+
const networkColor = paymentDetails ? getNetworkColor(paymentDetails.network) : "#8c8c8c";
|
|
1833
|
+
const loadingColor = "#8c8c8c";
|
|
1834
|
+
const hasInvalidCheckoutId = !fetchingPaymentInfo && (!paymentInfo || paymentInfo.length === 0);
|
|
1835
|
+
return /* @__PURE__ */ React3.createElement(
|
|
1836
|
+
"div",
|
|
1837
|
+
{
|
|
1838
|
+
className: isModal ? "bg-white" : "h-screen bg-white flex items-center justify-center p-4 overflow-hidden"
|
|
1839
|
+
},
|
|
1840
|
+
/* @__PURE__ */ React3.createElement(
|
|
1841
|
+
"div",
|
|
1842
|
+
{
|
|
1843
|
+
className: "flex gap-4 items-center justify-center",
|
|
1844
|
+
style: {
|
|
1845
|
+
maxWidth: isProcessing || result || error ? "1200px" : "480px",
|
|
1846
|
+
transition: "max-width 0.4s ease-in-out",
|
|
1847
|
+
width: "100%"
|
|
1848
|
+
}
|
|
1849
|
+
},
|
|
1850
|
+
/* @__PURE__ */ React3.createElement(
|
|
1851
|
+
Card,
|
|
1852
|
+
{
|
|
1853
|
+
className: "flex-shrink-0",
|
|
1854
|
+
style: {
|
|
1855
|
+
border: isModal ? "none" : "1px solid #e8e8e8",
|
|
1856
|
+
borderRadius: isModal ? "0" : "16px",
|
|
1857
|
+
boxShadow: isModal ? "none" : "0 4px 24px rgba(0, 0, 0, 0.06)",
|
|
1858
|
+
maxHeight: isModal ? "calc(100vh - 100px)" : "calc(100vh - 32px)",
|
|
1859
|
+
overflow: "auto",
|
|
1860
|
+
width: isModal ? "100%" : "480px",
|
|
1861
|
+
transition: "all 0.4s ease-in-out",
|
|
1862
|
+
transform: result || error ? "translateX(0)" : "translateX(0)"
|
|
1863
|
+
},
|
|
1864
|
+
styles: { body: { padding: isModal ? "0px" : "32px 24px" } }
|
|
1865
|
+
},
|
|
1866
|
+
/* @__PURE__ */ React3.createElement("div", { className: "flex items-center gap-3 mb-4" }, /* @__PURE__ */ React3.createElement(
|
|
1867
|
+
"div",
|
|
1868
|
+
{
|
|
1869
|
+
className: "w-12 h-12 rounded-xl flex items-center justify-center",
|
|
1870
|
+
style: {
|
|
1871
|
+
background: hasInvalidCheckoutId ? "#ff4d4f" : paymentDetails ? networkColor : loadingColor,
|
|
1872
|
+
transition: "background 0.3s ease"
|
|
1873
|
+
}
|
|
1874
|
+
},
|
|
1875
|
+
hasInvalidCheckoutId ? /* @__PURE__ */ React3.createElement("span", { style: { fontSize: "20px", color: "white", fontWeight: "bold" } }, "\u2717") : paymentDetails && NetworkIcon ? /* @__PURE__ */ React3.createElement(NetworkIcon, { width: 24, height: 24 }) : /* @__PURE__ */ React3.createElement(LoadingOutlined, { style: { fontSize: "20px", color: "white" }, spin: true })
|
|
1876
|
+
), /* @__PURE__ */ React3.createElement("div", { className: "flex-1" }, /* @__PURE__ */ React3.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React3.createElement(Title, { level: 4, style: { margin: 0, fontSize: "18px", fontWeight: 600 } }, title || "Echo Payment OnVoyage"), !hasInvalidCheckoutId && /* @__PURE__ */ React3.createElement(
|
|
1877
|
+
Tooltip,
|
|
1878
|
+
{
|
|
1879
|
+
title: tooltipText,
|
|
1880
|
+
placement: "top"
|
|
1881
|
+
},
|
|
1882
|
+
/* @__PURE__ */ React3.createElement(
|
|
1883
|
+
InfoCircleOutlined,
|
|
1884
|
+
{
|
|
1885
|
+
style: { fontSize: "14px", color: "#8c8c8c", cursor: "help" }
|
|
1886
|
+
}
|
|
1887
|
+
)
|
|
1888
|
+
)), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#8c8c8c" } }, subtitle))),
|
|
1889
|
+
/* @__PURE__ */ React3.createElement("div", { className: "text-center mb-5" }, /* @__PURE__ */ React3.createElement("div", { className: "inline-flex items-center justify-center w-12 h-12 rounded-full bg-gray-50 mb-3" }, /* @__PURE__ */ React3.createElement(LockOutlined, { style: { fontSize: "20px", color: "#595959" } })), /* @__PURE__ */ React3.createElement(Title, { level: 3, style: { margin: "0 0 6px 0", fontSize: "20px", fontWeight: 600 } }, "Payment Required"), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#8c8c8c" } }, "Pay ", paymentDetails ? `$${paymentDetails.amount} ${paymentDetails.currency}` : "the required amount", " to access")),
|
|
1890
|
+
hasInvalidCheckoutId && /* @__PURE__ */ React3.createElement("div", { className: "text-center py-6" }, /* @__PURE__ */ React3.createElement(
|
|
1891
|
+
"div",
|
|
1892
|
+
{
|
|
1893
|
+
className: "inline-flex items-center justify-center w-16 h-16 rounded-full mb-4",
|
|
1894
|
+
style: {
|
|
1895
|
+
background: "linear-gradient(135deg, #ef4444 0%, #f87171 100%)",
|
|
1896
|
+
boxShadow: "0 4px 20px rgba(239, 68, 68, 0.3)"
|
|
1897
|
+
}
|
|
1898
|
+
},
|
|
1899
|
+
/* @__PURE__ */ React3.createElement("span", { style: { fontSize: "32px", color: "white" } }, "!")
|
|
1900
|
+
), /* @__PURE__ */ React3.createElement(
|
|
1901
|
+
Title,
|
|
1902
|
+
{
|
|
1903
|
+
level: 4,
|
|
1904
|
+
style: { margin: "0 0 12px 0", fontSize: "18px", fontWeight: 600, color: "#262626" }
|
|
1905
|
+
},
|
|
1906
|
+
"Invalid Checkout ID"
|
|
1907
|
+
), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "14px", color: "#8c8c8c", display: "block", marginBottom: "16px" } }, "The checkout ID you provided is invalid or has expired."), /* @__PURE__ */ React3.createElement(
|
|
1908
|
+
"div",
|
|
1909
|
+
{
|
|
1910
|
+
style: {
|
|
1911
|
+
background: "#fef2f2",
|
|
1912
|
+
padding: "16px",
|
|
1913
|
+
borderRadius: "12px",
|
|
1914
|
+
border: "1px solid #fee2e2",
|
|
1915
|
+
marginTop: "16px"
|
|
1916
|
+
}
|
|
1917
|
+
},
|
|
1918
|
+
/* @__PURE__ */ React3.createElement(Text, { style: {
|
|
1919
|
+
fontSize: "13px",
|
|
1920
|
+
color: "#dc2626",
|
|
1921
|
+
lineHeight: "1.6",
|
|
1922
|
+
fontWeight: 500
|
|
1923
|
+
} }, "Failed to load payment information. Please check your checkout ID.")
|
|
1924
|
+
)),
|
|
1925
|
+
!hasInvalidCheckoutId && fetchingPaymentInfo && /* @__PURE__ */ React3.createElement("div", { className: "text-center py-6" }, /* @__PURE__ */ React3.createElement(Text, { style: { color: "#8c8c8c" } }, "Loading payment information...")),
|
|
1926
|
+
!hasInvalidCheckoutId && !fetchingPaymentInfo && !address && /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement(WalletConnect, { supportedNetworks })),
|
|
1927
|
+
!hasInvalidCheckoutId && address && /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(
|
|
1928
|
+
"div",
|
|
1929
|
+
{
|
|
1930
|
+
className: "bg-gray-50 rounded-lg p-3 mb-4",
|
|
1931
|
+
style: { border: "1px solid #f0f0f0" }
|
|
1932
|
+
},
|
|
1933
|
+
/* @__PURE__ */ React3.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React3.createElement("div", { className: "flex items-center gap-3 flex-1" }, /* @__PURE__ */ React3.createElement(
|
|
1934
|
+
"div",
|
|
1935
|
+
{
|
|
1936
|
+
className: "w-10 h-10 rounded-full bg-black flex items-center justify-center text-white text-sm font-semibold"
|
|
1937
|
+
},
|
|
1938
|
+
address.slice(0, 2).toUpperCase()
|
|
1939
|
+
), /* @__PURE__ */ React3.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React3.createElement(Text, { style: {
|
|
1940
|
+
display: "block",
|
|
1941
|
+
fontSize: "12px",
|
|
1942
|
+
color: "#8c8c8c",
|
|
1943
|
+
marginBottom: "2px"
|
|
1944
|
+
} }, "Connected Wallet"), /* @__PURE__ */ React3.createElement(
|
|
1945
|
+
Text,
|
|
1946
|
+
{
|
|
1947
|
+
style: {
|
|
1948
|
+
fontSize: "13px",
|
|
1949
|
+
fontWeight: 600,
|
|
1950
|
+
fontFamily: "Monaco, monospace"
|
|
1951
|
+
}
|
|
1952
|
+
},
|
|
1953
|
+
formatAddress(address)
|
|
1954
|
+
))), /* @__PURE__ */ React3.createElement(
|
|
1955
|
+
Button,
|
|
1956
|
+
{
|
|
1957
|
+
type: "text",
|
|
1958
|
+
size: "small",
|
|
1959
|
+
icon: /* @__PURE__ */ React3.createElement(DisconnectOutlined, null),
|
|
1960
|
+
onClick: handleDisconnect,
|
|
1961
|
+
style: { color: "#ff4d4f" }
|
|
1962
|
+
}
|
|
1963
|
+
))
|
|
1964
|
+
), paymentDetails && /* @__PURE__ */ React3.createElement("div", { className: "bg-gray-50 rounded-lg p-3 mb-4", style: { border: "1px solid #f0f0f0" } }, /* @__PURE__ */ React3.createElement("div", { className: "flex justify-between items-center mb-2" }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#8c8c8c" } }, "Payment Amount"), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "18px", fontWeight: 600 } }, "$", paymentDetails.amount)), /* @__PURE__ */ React3.createElement(Divider, { style: { margin: "6px 0" } }), /* @__PURE__ */ React3.createElement("div", { className: "flex justify-between items-center mb-2" }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#8c8c8c" } }, "Currency"), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "14px", fontWeight: 500 } }, paymentDetails.currency)), /* @__PURE__ */ React3.createElement(Divider, { style: { margin: "6px 0" } }), /* @__PURE__ */ React3.createElement("div", { className: "flex justify-between items-center mb-2" }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#8c8c8c" } }, "Network"), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "14px", fontWeight: 500 } }, paymentDetails.network)), /* @__PURE__ */ React3.createElement(Divider, { style: { margin: "6px 0" } }), /* @__PURE__ */ React3.createElement("div", { className: "flex justify-between items-start" }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#8c8c8c" } }, "Wallet Address"), /* @__PURE__ */ React3.createElement(Text, { style: {
|
|
1965
|
+
fontSize: "11px",
|
|
1966
|
+
fontWeight: 500,
|
|
1967
|
+
fontFamily: "Monaco, monospace",
|
|
1968
|
+
wordBreak: "break-all",
|
|
1969
|
+
textAlign: "right",
|
|
1970
|
+
maxWidth: "60%",
|
|
1971
|
+
lineHeight: 1.4
|
|
1972
|
+
} }, address))), /* @__PURE__ */ React3.createElement(
|
|
1973
|
+
"div",
|
|
1974
|
+
{
|
|
1975
|
+
className: "flex items-center justify-center gap-2 mb-3 p-2 rounded-lg",
|
|
1976
|
+
style: { background: "#f6ffed", border: "1px solid #d9f7be" }
|
|
1977
|
+
},
|
|
1978
|
+
/* @__PURE__ */ React3.createElement(SafetyOutlined, { style: { color: "#52c41a", fontSize: "13px" } }),
|
|
1979
|
+
/* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "12px", color: "#52c41a", fontWeight: 500 } }, "Secure payment powered by v402pay")
|
|
1980
|
+
), /* @__PURE__ */ React3.createElement(
|
|
1981
|
+
Button,
|
|
1982
|
+
{
|
|
1983
|
+
type: "primary",
|
|
1984
|
+
size: "large",
|
|
1985
|
+
onClick: handlePayment,
|
|
1986
|
+
disabled: isProcessing || !paymentDetails,
|
|
1987
|
+
loading: isProcessing,
|
|
1988
|
+
block: true,
|
|
1989
|
+
style: {
|
|
1990
|
+
height: "44px",
|
|
1991
|
+
fontSize: "14px",
|
|
1992
|
+
fontWeight: 600,
|
|
1993
|
+
borderRadius: "8px",
|
|
1994
|
+
...!isProcessing && paymentDetails && {
|
|
1995
|
+
background: "#1a1a1a",
|
|
1996
|
+
borderColor: "#1a1a1a"
|
|
1997
|
+
},
|
|
1998
|
+
marginBottom: "10px"
|
|
1999
|
+
}
|
|
2000
|
+
},
|
|
2001
|
+
isProcessing ? "Processing..." : !paymentDetails ? "Loading..." : `Pay $${paymentDetails.amount} ${paymentDetails.currency}`
|
|
2002
|
+
), /* @__PURE__ */ React3.createElement("div", { className: "text-center" }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#8c8c8c" } }, "Don't have USDC?", " "), /* @__PURE__ */ React3.createElement(
|
|
2003
|
+
"a",
|
|
2004
|
+
{
|
|
2005
|
+
href: "https://faucet.circle.com/",
|
|
2006
|
+
target: "_blank",
|
|
2007
|
+
rel: "noopener noreferrer",
|
|
2008
|
+
className: "text-blue-600 hover:text-blue-700 text-sm font-medium inline-flex items-center gap-1"
|
|
2009
|
+
},
|
|
2010
|
+
"Get it here ",
|
|
2011
|
+
/* @__PURE__ */ React3.createElement(LinkOutlined, { style: { fontSize: "12px" } })
|
|
2012
|
+
)), isModal && result && /* @__PURE__ */ React3.createElement(
|
|
2013
|
+
"div",
|
|
2014
|
+
{
|
|
2015
|
+
className: "mt-4 p-4 rounded-lg",
|
|
2016
|
+
style: { background: "#f6ffed", border: "1px solid #b7eb8f" }
|
|
2017
|
+
},
|
|
2018
|
+
/* @__PURE__ */ React3.createElement("div", { className: "text-center" }, /* @__PURE__ */ React3.createElement("span", { style: { fontSize: "20px" } }, "\u2713"), /* @__PURE__ */ React3.createElement(Text, { style: {
|
|
2019
|
+
fontSize: "14px",
|
|
2020
|
+
color: "#52c41a",
|
|
2021
|
+
fontWeight: 600,
|
|
2022
|
+
marginLeft: "8px"
|
|
2023
|
+
} }, "Payment Successful!"))
|
|
2024
|
+
), isModal && error && /* @__PURE__ */ React3.createElement(
|
|
2025
|
+
"div",
|
|
2026
|
+
{
|
|
2027
|
+
className: "mt-4 p-4 rounded-lg",
|
|
2028
|
+
style: { background: "#fff2f0", border: "1px solid #ffccc7" }
|
|
2029
|
+
},
|
|
2030
|
+
/* @__PURE__ */ React3.createElement("div", { className: "text-center mb-3" }, /* @__PURE__ */ React3.createElement("span", { style: { fontSize: "20px" } }, "\u2717"), /* @__PURE__ */ React3.createElement(Text, { style: {
|
|
2031
|
+
fontSize: "14px",
|
|
2032
|
+
color: "#ff4d4f",
|
|
2033
|
+
fontWeight: 600,
|
|
2034
|
+
marginLeft: "8px",
|
|
2035
|
+
display: "block",
|
|
2036
|
+
marginTop: "4px"
|
|
2037
|
+
} }, "Payment Failed")),
|
|
2038
|
+
/* @__PURE__ */ React3.createElement(Text, { style: {
|
|
2039
|
+
fontSize: "13px",
|
|
2040
|
+
color: "#ff4d4f",
|
|
2041
|
+
display: "block",
|
|
2042
|
+
textAlign: "center"
|
|
2043
|
+
} }, error)
|
|
2044
|
+
))
|
|
2045
|
+
),
|
|
2046
|
+
!isModal && (isProcessing || result || error) && /* @__PURE__ */ React3.createElement(
|
|
2047
|
+
Card,
|
|
2048
|
+
{
|
|
2049
|
+
title: /* @__PURE__ */ React3.createElement("div", { className: "flex items-center gap-2" }, isProcessing && !result && !error ? /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(LoadingOutlined, { style: { color: "#14b8a6", fontSize: "16px" } }), /* @__PURE__ */ React3.createElement(Text, { strong: true, style: { fontSize: "16px", color: "#262626" } }, "Processing Payment")) : result ? /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("span", { style: { color: "#52c41a", fontSize: "18px" } }, "\u2713"), /* @__PURE__ */ React3.createElement(Text, { strong: true, style: { fontSize: "16px", color: "#262626" } }, "Payment Successful")) : /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("span", { style: { color: "#ff4d4f", fontSize: "18px" } }, "\u2717"), /* @__PURE__ */ React3.createElement(Text, { strong: true, style: { fontSize: "16px", color: "#262626" } }, "Payment Failed"))),
|
|
2050
|
+
extra: !isProcessing && /* @__PURE__ */ React3.createElement(
|
|
2051
|
+
Button,
|
|
2052
|
+
{
|
|
2053
|
+
type: "text",
|
|
2054
|
+
size: "small",
|
|
2055
|
+
onClick: () => {
|
|
2056
|
+
setResult(null);
|
|
2057
|
+
setError(null);
|
|
2058
|
+
}
|
|
2059
|
+
},
|
|
2060
|
+
"Close"
|
|
2061
|
+
),
|
|
2062
|
+
style: {
|
|
2063
|
+
border: "1px solid #e8e8e8",
|
|
2064
|
+
borderRadius: "16px",
|
|
2065
|
+
boxShadow: "0 4px 24px rgba(0, 0, 0, 0.06)",
|
|
2066
|
+
maxHeight: "calc(100vh - 32px)",
|
|
2067
|
+
width: "480px",
|
|
2068
|
+
animation: "slideInRight 0.4s ease-out"
|
|
2069
|
+
},
|
|
2070
|
+
styles: {
|
|
2071
|
+
body: {
|
|
2072
|
+
padding: "24px",
|
|
2073
|
+
maxHeight: "calc(100vh - 120px)",
|
|
2074
|
+
overflow: "auto"
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
},
|
|
2078
|
+
isProcessing && !result && !error && /* @__PURE__ */ React3.createElement("div", { className: "text-center py-10" }, /* @__PURE__ */ React3.createElement("div", { className: "relative inline-block" }, /* @__PURE__ */ React3.createElement(
|
|
2079
|
+
"div",
|
|
2080
|
+
{
|
|
2081
|
+
className: "absolute inset-0 rounded-full blur-xl opacity-40",
|
|
2082
|
+
style: {
|
|
2083
|
+
background: "linear-gradient(135deg, #14b8a6 0%, #06b6d4 100%)",
|
|
2084
|
+
animation: "pulse 2s ease-in-out infinite"
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
), /* @__PURE__ */ React3.createElement(
|
|
2088
|
+
Spin,
|
|
2089
|
+
{
|
|
2090
|
+
indicator: /* @__PURE__ */ React3.createElement(LoadingOutlined, { style: { fontSize: 56, color: "#14b8a6" } })
|
|
2091
|
+
}
|
|
2092
|
+
)), /* @__PURE__ */ React3.createElement("div", { className: "mt-6" }, /* @__PURE__ */ React3.createElement(Text, { strong: true, style: { fontSize: "18px", color: "#262626", letterSpacing: "-0.02em" } }, "Verifying Payment")), /* @__PURE__ */ React3.createElement("div", { className: "mt-2 mb-6" }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "14px", color: "#8c8c8c", lineHeight: "1.6" } }, "Please wait while we confirm your transaction")), /* @__PURE__ */ React3.createElement(
|
|
2093
|
+
"div",
|
|
2094
|
+
{
|
|
2095
|
+
className: "mt-4 p-4 rounded-xl",
|
|
2096
|
+
style: {
|
|
2097
|
+
background: "linear-gradient(135deg, #f0fdfa 0%, #ecfeff 100%)",
|
|
2098
|
+
border: "1px solid #ccfbf1"
|
|
2099
|
+
}
|
|
2100
|
+
},
|
|
2101
|
+
/* @__PURE__ */ React3.createElement("div", { className: "flex items-center justify-center gap-2" }, /* @__PURE__ */ React3.createElement("span", { style: { fontSize: "16px" } }, "\u23F1\uFE0F"), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#0f766e", fontWeight: 500 } }, "This may take a few moments"))
|
|
2102
|
+
)),
|
|
2103
|
+
result && /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("div", { className: "text-center mb-6" }, /* @__PURE__ */ React3.createElement(
|
|
2104
|
+
"div",
|
|
2105
|
+
{
|
|
2106
|
+
className: "inline-flex items-center justify-center w-16 h-16 rounded-full mb-4",
|
|
2107
|
+
style: {
|
|
2108
|
+
background: "linear-gradient(135deg, #10b981 0%, #34d399 100%)",
|
|
2109
|
+
boxShadow: "0 4px 20px rgba(16, 185, 129, 0.3)"
|
|
2110
|
+
}
|
|
2111
|
+
},
|
|
2112
|
+
/* @__PURE__ */ React3.createElement("span", { style: { fontSize: "32px", color: "white" } }, "\u2713")
|
|
2113
|
+
), /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement(Text, { strong: true, style: {
|
|
2114
|
+
fontSize: "20px",
|
|
2115
|
+
color: "#262626",
|
|
2116
|
+
display: "block",
|
|
2117
|
+
marginBottom: "8px"
|
|
2118
|
+
} }, "Payment Successful!"), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "14px", color: "#8c8c8c" } }, "Your transaction has been confirmed"))), /* @__PURE__ */ React3.createElement(Divider, { style: { margin: "20px 0", borderColor: "#f0f0f0" } }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "12px", color: "#8c8c8c", fontWeight: 500 } }, "RESPONSE DATA")), /* @__PURE__ */ React3.createElement(
|
|
2119
|
+
"pre",
|
|
2120
|
+
{
|
|
2121
|
+
style: {
|
|
2122
|
+
background: "#fafafa",
|
|
2123
|
+
padding: "20px",
|
|
2124
|
+
borderRadius: "12px",
|
|
2125
|
+
fontSize: "12px",
|
|
2126
|
+
lineHeight: "1.8",
|
|
2127
|
+
overflow: "auto",
|
|
2128
|
+
margin: 0,
|
|
2129
|
+
fontFamily: "Monaco, Courier New, monospace",
|
|
2130
|
+
whiteSpace: "pre-wrap",
|
|
2131
|
+
wordBreak: "break-word",
|
|
2132
|
+
border: "1px solid #e8e8e8",
|
|
2133
|
+
color: "#262626"
|
|
2134
|
+
}
|
|
2135
|
+
},
|
|
2136
|
+
JSON.stringify(result, null, 2)
|
|
2137
|
+
)),
|
|
2138
|
+
error && /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("div", { className: "text-center mb-6" }, /* @__PURE__ */ React3.createElement(
|
|
2139
|
+
"div",
|
|
2140
|
+
{
|
|
2141
|
+
className: "inline-flex items-center justify-center w-16 h-16 rounded-full mb-4",
|
|
2142
|
+
style: {
|
|
2143
|
+
background: "linear-gradient(135deg, #ef4444 0%, #f87171 100%)",
|
|
2144
|
+
boxShadow: "0 4px 20px rgba(239, 68, 68, 0.3)"
|
|
2145
|
+
}
|
|
2146
|
+
},
|
|
2147
|
+
/* @__PURE__ */ React3.createElement("span", { style: { fontSize: "32px", color: "white" } }, "\u2717")
|
|
2148
|
+
), /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement(Text, { strong: true, style: {
|
|
2149
|
+
fontSize: "20px",
|
|
2150
|
+
color: "#262626",
|
|
2151
|
+
display: "block",
|
|
2152
|
+
marginBottom: "8px"
|
|
2153
|
+
} }, "Payment Failed"), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "14px", color: "#8c8c8c" } }, "Something went wrong with your transaction"))), /* @__PURE__ */ React3.createElement(Divider, { style: { margin: "20px 0", borderColor: "#f0f0f0" } }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "12px", color: "#8c8c8c", fontWeight: 500 } }, "ERROR DETAILS")), /* @__PURE__ */ React3.createElement(
|
|
2154
|
+
"div",
|
|
2155
|
+
{
|
|
2156
|
+
style: {
|
|
2157
|
+
background: "#fef2f2",
|
|
2158
|
+
padding: "20px",
|
|
2159
|
+
borderRadius: "12px",
|
|
2160
|
+
border: "1px solid #fee2e2"
|
|
2161
|
+
}
|
|
2162
|
+
},
|
|
2163
|
+
/* @__PURE__ */ React3.createElement(Text, { style: {
|
|
2164
|
+
fontSize: "14px",
|
|
2165
|
+
color: "#dc2626",
|
|
2166
|
+
lineHeight: "1.6",
|
|
2167
|
+
fontWeight: 500
|
|
2168
|
+
} }, error)
|
|
2169
|
+
), /* @__PURE__ */ React3.createElement("div", { className: "mt-4 text-center" }, /* @__PURE__ */ React3.createElement(
|
|
2170
|
+
Button,
|
|
2171
|
+
{
|
|
2172
|
+
size: "large",
|
|
2173
|
+
onClick: handlePayment,
|
|
2174
|
+
style: {
|
|
2175
|
+
height: "44px",
|
|
2176
|
+
fontSize: "14px",
|
|
2177
|
+
fontWeight: 600,
|
|
2178
|
+
borderRadius: "8px",
|
|
2179
|
+
background: "#1a1a1a",
|
|
2180
|
+
borderColor: "#1a1a1a",
|
|
2181
|
+
color: "white",
|
|
2182
|
+
paddingLeft: "32px",
|
|
2183
|
+
paddingRight: "32px"
|
|
2184
|
+
}
|
|
2185
|
+
},
|
|
2186
|
+
"Try Again"
|
|
2187
|
+
)))
|
|
2188
|
+
)
|
|
2189
|
+
),
|
|
2190
|
+
/* @__PURE__ */ React3.createElement("style", { dangerouslySetInnerHTML: {
|
|
2191
|
+
__html: `
|
|
2192
|
+
@keyframes slideInRight {
|
|
2193
|
+
from {
|
|
2194
|
+
opacity: 0;
|
|
2195
|
+
transform: translateX(100px);
|
|
2196
|
+
}
|
|
2197
|
+
to {
|
|
2198
|
+
opacity: 1;
|
|
2199
|
+
transform: translateX(0);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
@keyframes pulse {
|
|
2204
|
+
0%, 100% {
|
|
2205
|
+
transform: scale(1);
|
|
2206
|
+
opacity: 0.4;
|
|
2207
|
+
}
|
|
2208
|
+
50% {
|
|
2209
|
+
transform: scale(1.1);
|
|
2210
|
+
opacity: 0.6;
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
`
|
|
2214
|
+
} })
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
790
2217
|
export {
|
|
2218
|
+
V402Checkout,
|
|
791
2219
|
WalletConnect,
|
|
2220
|
+
usePageNetwork,
|
|
792
2221
|
usePayment,
|
|
793
2222
|
usePaymentInfo,
|
|
794
2223
|
useWallet
|