@voyage_ai/v402-web-ts 0.1.0 → 0.1.2
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/README.md +639 -1
- package/dist/index.d.mts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +600 -229
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +600 -229
- package/dist/index.mjs.map +1 -1
- package/dist/react/components/WalletConnect.tsx +47 -20
- package/dist/react/hooks/usePaymentInfo.ts +38 -4
- package/dist/react/hooks/useWallet.ts +35 -8
- package/dist/react/index.d.mts +16 -55
- package/dist/react/index.d.ts +16 -55
- package/dist/react/index.js +270 -399
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +272 -400
- package/dist/react/index.mjs.map +1 -1
- package/dist/react/index.ts +5 -9
- package/dist/react/store/walletStore.ts +29 -0
- package/dist/react/styles/inline-styles.ts +238 -0
- package/dist/react/styles.css +8 -1
- package/package.json +8 -6
- package/dist/react/components/PaymentButton.tsx +0 -112
package/dist/react/index.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from "x402/types";
|
|
17
17
|
|
|
18
18
|
// src/types/common.ts
|
|
19
|
-
var PROD_BACK_URL = "https://v402.onvoyage.ai/api";
|
|
19
|
+
var PROD_BACK_URL = "https://v402.onvoyage.ai/api/pay";
|
|
20
20
|
|
|
21
21
|
// src/types/svm.ts
|
|
22
22
|
import { z } from "zod";
|
|
@@ -205,6 +205,40 @@ function onAccountsChanged(callback) {
|
|
|
205
205
|
ethereum.removeListener?.("accountsChanged", handler);
|
|
206
206
|
};
|
|
207
207
|
}
|
|
208
|
+
function onChainChanged(callback) {
|
|
209
|
+
if (typeof window === "undefined" || !window.ethereum) {
|
|
210
|
+
return () => {
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const ethereum = window.ethereum;
|
|
214
|
+
const handler = (chainId) => {
|
|
215
|
+
console.log("\u{1F504} Chain changed to:", chainId);
|
|
216
|
+
callback(chainId);
|
|
217
|
+
};
|
|
218
|
+
ethereum.on("chainChanged", handler);
|
|
219
|
+
return () => {
|
|
220
|
+
ethereum.removeListener?.("chainChanged", handler);
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function onWalletDisconnect(callback) {
|
|
224
|
+
if (typeof window === "undefined") {
|
|
225
|
+
return () => {
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const solana = window.solana;
|
|
229
|
+
if (!solana) {
|
|
230
|
+
return () => {
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
const handler = () => {
|
|
234
|
+
console.log("\u{1F50C} Solana wallet disconnected");
|
|
235
|
+
callback();
|
|
236
|
+
};
|
|
237
|
+
solana.on("disconnect", handler);
|
|
238
|
+
return () => {
|
|
239
|
+
solana.removeListener?.("disconnect", handler);
|
|
240
|
+
};
|
|
241
|
+
}
|
|
208
242
|
|
|
209
243
|
// src/services/svm/payment-header.ts
|
|
210
244
|
import {
|
|
@@ -221,295 +255,9 @@ import {
|
|
|
221
255
|
TOKEN_2022_PROGRAM_ID,
|
|
222
256
|
TOKEN_PROGRAM_ID
|
|
223
257
|
} from "@solana/spl-token";
|
|
224
|
-
async function createSvmPaymentHeader(params) {
|
|
225
|
-
const { wallet, paymentRequirements, x402Version, rpcUrl } = params;
|
|
226
|
-
const connection = new Connection(rpcUrl, "confirmed");
|
|
227
|
-
const feePayer = paymentRequirements?.extra?.feePayer;
|
|
228
|
-
if (typeof feePayer !== "string" || !feePayer) {
|
|
229
|
-
throw new Error("Missing facilitator feePayer in payment requirements (extra.feePayer).");
|
|
230
|
-
}
|
|
231
|
-
const feePayerPubkey = new PublicKey(feePayer);
|
|
232
|
-
const walletAddress = wallet?.publicKey?.toString() || wallet?.address;
|
|
233
|
-
if (!walletAddress) {
|
|
234
|
-
throw new Error("Missing connected Solana wallet address or publicKey");
|
|
235
|
-
}
|
|
236
|
-
const userPubkey = new PublicKey(walletAddress);
|
|
237
|
-
if (!paymentRequirements?.payTo) {
|
|
238
|
-
throw new Error("Missing payTo in payment requirements");
|
|
239
|
-
}
|
|
240
|
-
const destination = new PublicKey(paymentRequirements.payTo);
|
|
241
|
-
const instructions = [];
|
|
242
|
-
instructions.push(
|
|
243
|
-
ComputeBudgetProgram.setComputeUnitLimit({
|
|
244
|
-
units: 7e3
|
|
245
|
-
// Sufficient for SPL token transfer
|
|
246
|
-
})
|
|
247
|
-
);
|
|
248
|
-
instructions.push(
|
|
249
|
-
ComputeBudgetProgram.setComputeUnitPrice({
|
|
250
|
-
microLamports: 1
|
|
251
|
-
// Minimal price
|
|
252
|
-
})
|
|
253
|
-
);
|
|
254
|
-
if (!paymentRequirements.asset) {
|
|
255
|
-
throw new Error("Missing token mint for SPL transfer");
|
|
256
|
-
}
|
|
257
|
-
const mintPubkey = new PublicKey(paymentRequirements.asset);
|
|
258
|
-
const mintInfo = await connection.getAccountInfo(mintPubkey, "confirmed");
|
|
259
|
-
const programId = mintInfo?.owner?.toBase58() === TOKEN_2022_PROGRAM_ID.toBase58() ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
|
|
260
|
-
const mint = await getMint(connection, mintPubkey, void 0, programId);
|
|
261
|
-
const sourceAta = await getAssociatedTokenAddress(
|
|
262
|
-
mintPubkey,
|
|
263
|
-
userPubkey,
|
|
264
|
-
false,
|
|
265
|
-
programId
|
|
266
|
-
);
|
|
267
|
-
const destinationAta = await getAssociatedTokenAddress(
|
|
268
|
-
mintPubkey,
|
|
269
|
-
destination,
|
|
270
|
-
false,
|
|
271
|
-
programId
|
|
272
|
-
);
|
|
273
|
-
const sourceAtaInfo = await connection.getAccountInfo(sourceAta, "confirmed");
|
|
274
|
-
if (!sourceAtaInfo) {
|
|
275
|
-
throw new Error(
|
|
276
|
-
`User does not have an Associated Token Account for ${paymentRequirements.asset}. Please create one first or ensure you have the required token.`
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
const destAtaInfo = await connection.getAccountInfo(destinationAta, "confirmed");
|
|
280
|
-
if (!destAtaInfo) {
|
|
281
|
-
throw new Error(
|
|
282
|
-
`Destination does not have an Associated Token Account for ${paymentRequirements.asset}. The receiver must create their token account before receiving payments.`
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
const amount = BigInt(paymentRequirements.maxAmountRequired);
|
|
286
|
-
instructions.push(
|
|
287
|
-
createTransferCheckedInstruction(
|
|
288
|
-
sourceAta,
|
|
289
|
-
mintPubkey,
|
|
290
|
-
destinationAta,
|
|
291
|
-
userPubkey,
|
|
292
|
-
amount,
|
|
293
|
-
mint.decimals,
|
|
294
|
-
[],
|
|
295
|
-
programId
|
|
296
|
-
)
|
|
297
|
-
);
|
|
298
|
-
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
299
|
-
const message = new TransactionMessage({
|
|
300
|
-
payerKey: feePayerPubkey,
|
|
301
|
-
recentBlockhash: blockhash,
|
|
302
|
-
instructions
|
|
303
|
-
}).compileToV0Message();
|
|
304
|
-
const transaction = new VersionedTransaction(message);
|
|
305
|
-
if (typeof wallet?.signTransaction !== "function") {
|
|
306
|
-
throw new Error("Connected wallet does not support signTransaction");
|
|
307
|
-
}
|
|
308
|
-
const userSignedTx = await wallet.signTransaction(transaction);
|
|
309
|
-
const serializedTransaction = Buffer.from(userSignedTx.serialize()).toString("base64");
|
|
310
|
-
const paymentPayload = {
|
|
311
|
-
x402Version,
|
|
312
|
-
scheme: paymentRequirements.scheme,
|
|
313
|
-
network: paymentRequirements.network,
|
|
314
|
-
payload: {
|
|
315
|
-
transaction: serializedTransaction
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
const paymentHeader = Buffer.from(JSON.stringify(paymentPayload)).toString("base64");
|
|
319
|
-
return paymentHeader;
|
|
320
|
-
}
|
|
321
|
-
function getDefaultSolanaRpcUrl(network) {
|
|
322
|
-
const normalized = network.toLowerCase();
|
|
323
|
-
if (normalized === "solana" || normalized === "solana-mainnet") {
|
|
324
|
-
return "https://api.mainnet-beta.solana.com";
|
|
325
|
-
} else if (normalized === "solana-devnet") {
|
|
326
|
-
return "https://api.devnet.solana.com";
|
|
327
|
-
}
|
|
328
|
-
throw new Error(`Unsupported Solana network: ${network}`);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// src/services/svm/payment-handler.ts
|
|
332
|
-
async function handleSvmPayment(endpoint, config, requestInit) {
|
|
333
|
-
const { wallet, network, rpcUrl, maxPaymentAmount } = config;
|
|
334
|
-
const initialResponse = await fetch(endpoint, {
|
|
335
|
-
...requestInit,
|
|
336
|
-
method: requestInit?.method || "POST"
|
|
337
|
-
});
|
|
338
|
-
if (initialResponse.status !== 402) {
|
|
339
|
-
return initialResponse;
|
|
340
|
-
}
|
|
341
|
-
const rawResponse = await initialResponse.json();
|
|
342
|
-
const x402Version = rawResponse.x402Version;
|
|
343
|
-
const parsedPaymentRequirements = rawResponse.accepts || [];
|
|
344
|
-
const selectedRequirements = parsedPaymentRequirements.find(
|
|
345
|
-
(req) => req.scheme === "exact" && SolanaNetworkSchema.safeParse(req.network.toLowerCase()).success
|
|
346
|
-
);
|
|
347
|
-
if (!selectedRequirements) {
|
|
348
|
-
console.error(
|
|
349
|
-
"\u274C No suitable Solana payment requirements found. Available networks:",
|
|
350
|
-
parsedPaymentRequirements.map((req) => req.network)
|
|
351
|
-
);
|
|
352
|
-
throw new Error("No suitable Solana payment requirements found");
|
|
353
|
-
}
|
|
354
|
-
if (maxPaymentAmount && maxPaymentAmount > BigInt(0)) {
|
|
355
|
-
if (BigInt(selectedRequirements.maxAmountRequired) > maxPaymentAmount) {
|
|
356
|
-
throw new Error(
|
|
357
|
-
`Payment amount ${selectedRequirements.maxAmountRequired} exceeds maximum allowed ${maxPaymentAmount}`
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
const effectiveRpcUrl = rpcUrl || getDefaultSolanaRpcUrl(network);
|
|
362
|
-
const paymentHeader = await createSvmPaymentHeader({
|
|
363
|
-
wallet,
|
|
364
|
-
paymentRequirements: selectedRequirements,
|
|
365
|
-
x402Version,
|
|
366
|
-
rpcUrl: effectiveRpcUrl
|
|
367
|
-
});
|
|
368
|
-
const newInit = {
|
|
369
|
-
...requestInit,
|
|
370
|
-
method: requestInit?.method || "POST",
|
|
371
|
-
headers: {
|
|
372
|
-
...requestInit?.headers || {},
|
|
373
|
-
"X-PAYMENT": paymentHeader,
|
|
374
|
-
"Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE"
|
|
375
|
-
}
|
|
376
|
-
};
|
|
377
|
-
return await fetch(endpoint, newInit);
|
|
378
|
-
}
|
|
379
258
|
|
|
380
259
|
// src/services/evm/payment-header.ts
|
|
381
260
|
import { ethers } from "ethers";
|
|
382
|
-
async function createEvmPaymentHeader(params) {
|
|
383
|
-
const { wallet, paymentRequirements, x402Version, chainId } = params;
|
|
384
|
-
if (!paymentRequirements?.payTo) {
|
|
385
|
-
throw new Error("Missing payTo in payment requirements");
|
|
386
|
-
}
|
|
387
|
-
if (!paymentRequirements?.asset) {
|
|
388
|
-
throw new Error("Missing asset (token contract) in payment requirements");
|
|
389
|
-
}
|
|
390
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
391
|
-
const nonceBytes = ethers.randomBytes(32);
|
|
392
|
-
const nonceBytes32 = ethers.hexlify(nonceBytes);
|
|
393
|
-
const domain = {
|
|
394
|
-
name: paymentRequirements.extra?.name || "USDC",
|
|
395
|
-
version: paymentRequirements.extra?.version || "2",
|
|
396
|
-
chainId,
|
|
397
|
-
verifyingContract: paymentRequirements.asset
|
|
398
|
-
};
|
|
399
|
-
const types = {
|
|
400
|
-
TransferWithAuthorization: [
|
|
401
|
-
{ name: "from", type: "address" },
|
|
402
|
-
{ name: "to", type: "address" },
|
|
403
|
-
{ name: "value", type: "uint256" },
|
|
404
|
-
{ name: "validAfter", type: "uint256" },
|
|
405
|
-
{ name: "validBefore", type: "uint256" },
|
|
406
|
-
{ name: "nonce", type: "bytes32" }
|
|
407
|
-
]
|
|
408
|
-
};
|
|
409
|
-
const authorization = {
|
|
410
|
-
from: wallet.address,
|
|
411
|
-
to: paymentRequirements.payTo,
|
|
412
|
-
value: paymentRequirements.maxAmountRequired,
|
|
413
|
-
validAfter: "0",
|
|
414
|
-
// Effective immediately
|
|
415
|
-
validBefore: String(now + (paymentRequirements.maxTimeoutSeconds || 3600)),
|
|
416
|
-
nonce: nonceBytes32
|
|
417
|
-
};
|
|
418
|
-
const signature = await wallet.signTypedData(domain, types, authorization);
|
|
419
|
-
const headerPayload = {
|
|
420
|
-
x402_version: x402Version,
|
|
421
|
-
x402Version,
|
|
422
|
-
scheme: paymentRequirements.scheme,
|
|
423
|
-
network: paymentRequirements.network,
|
|
424
|
-
payload: {
|
|
425
|
-
signature,
|
|
426
|
-
authorization: {
|
|
427
|
-
from: authorization.from,
|
|
428
|
-
to: authorization.to,
|
|
429
|
-
value: String(authorization.value),
|
|
430
|
-
valid_after: authorization.validAfter,
|
|
431
|
-
validAfter: authorization.validAfter,
|
|
432
|
-
valid_before: authorization.validBefore,
|
|
433
|
-
validBefore: authorization.validBefore,
|
|
434
|
-
nonce: authorization.nonce
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
};
|
|
438
|
-
const paymentHeader = btoa(JSON.stringify(headerPayload));
|
|
439
|
-
return paymentHeader;
|
|
440
|
-
}
|
|
441
|
-
function getChainIdFromNetwork(network) {
|
|
442
|
-
const chainIdMap = {
|
|
443
|
-
"ethereum": 1,
|
|
444
|
-
"sepolia": 11155111,
|
|
445
|
-
"base": 8453,
|
|
446
|
-
"base-sepolia": 84532,
|
|
447
|
-
"polygon": 137,
|
|
448
|
-
"arbitrum": 42161,
|
|
449
|
-
"optimism": 10
|
|
450
|
-
};
|
|
451
|
-
const chainId = chainIdMap[network.toLowerCase()];
|
|
452
|
-
if (!chainId) {
|
|
453
|
-
throw new Error(`Unknown network: ${network}`);
|
|
454
|
-
}
|
|
455
|
-
return chainId;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// src/services/evm/payment-handler.ts
|
|
459
|
-
async function handleEvmPayment(endpoint, config, requestInit) {
|
|
460
|
-
const { wallet, network, maxPaymentAmount } = config;
|
|
461
|
-
const initialResponse = await fetch(endpoint, {
|
|
462
|
-
...requestInit,
|
|
463
|
-
method: requestInit?.method || "POST"
|
|
464
|
-
});
|
|
465
|
-
if (initialResponse.status !== 402) {
|
|
466
|
-
return initialResponse;
|
|
467
|
-
}
|
|
468
|
-
const rawResponse = await initialResponse.json();
|
|
469
|
-
const x402Version = rawResponse.x402Version;
|
|
470
|
-
const parsedPaymentRequirements = rawResponse.accepts || [];
|
|
471
|
-
const selectedRequirements = parsedPaymentRequirements.find(
|
|
472
|
-
(req) => req.scheme === "exact" && EvmNetworkSchema.safeParse(req.network.toLowerCase()).success
|
|
473
|
-
);
|
|
474
|
-
if (!selectedRequirements) {
|
|
475
|
-
console.error(
|
|
476
|
-
"\u274C No suitable EVM payment requirements found. Available networks:",
|
|
477
|
-
parsedPaymentRequirements.map((req) => req.network)
|
|
478
|
-
);
|
|
479
|
-
throw new Error("No suitable EVM payment requirements found");
|
|
480
|
-
}
|
|
481
|
-
if (maxPaymentAmount && maxPaymentAmount > BigInt(0)) {
|
|
482
|
-
if (BigInt(selectedRequirements.maxAmountRequired) > maxPaymentAmount) {
|
|
483
|
-
throw new Error(
|
|
484
|
-
`Payment amount ${selectedRequirements.maxAmountRequired} exceeds maximum allowed ${maxPaymentAmount}`
|
|
485
|
-
);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
const targetChainId = getChainIdFromNetwork(selectedRequirements.network);
|
|
489
|
-
if (wallet.switchChain) {
|
|
490
|
-
try {
|
|
491
|
-
await wallet.switchChain(`0x${targetChainId.toString(16)}`);
|
|
492
|
-
} catch (error) {
|
|
493
|
-
console.warn("Failed to switch chain:", error);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
const paymentHeader = await createEvmPaymentHeader({
|
|
497
|
-
wallet,
|
|
498
|
-
paymentRequirements: selectedRequirements,
|
|
499
|
-
x402Version,
|
|
500
|
-
chainId: targetChainId
|
|
501
|
-
});
|
|
502
|
-
const newInit = {
|
|
503
|
-
...requestInit,
|
|
504
|
-
method: requestInit?.method || "POST",
|
|
505
|
-
headers: {
|
|
506
|
-
...requestInit?.headers || {},
|
|
507
|
-
"X-PAYMENT": paymentHeader,
|
|
508
|
-
"Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE"
|
|
509
|
-
}
|
|
510
|
-
};
|
|
511
|
-
return await fetch(endpoint, newInit);
|
|
512
|
-
}
|
|
513
261
|
|
|
514
262
|
// src/utils/payment-helpers.ts
|
|
515
263
|
import { ethers as ethers2 } from "ethers";
|
|
@@ -536,62 +284,6 @@ function getSupportedNetworkTypes(paymentRequirements) {
|
|
|
536
284
|
});
|
|
537
285
|
return Array.from(networkTypes);
|
|
538
286
|
}
|
|
539
|
-
async function makePayment(networkType, merchantId, endpoint = PROD_BACK_URL) {
|
|
540
|
-
endpoint = `${endpoint}/${merchantId}`;
|
|
541
|
-
let response;
|
|
542
|
-
if (networkType === "solana" /* SOLANA */ || networkType === "svm" /* SVM */) {
|
|
543
|
-
const solana = window.solana;
|
|
544
|
-
if (!solana) {
|
|
545
|
-
throw new Error("\u8BF7\u5B89\u88C5 Phantom \u94B1\u5305");
|
|
546
|
-
}
|
|
547
|
-
if (!solana.isConnected) {
|
|
548
|
-
await solana.connect();
|
|
549
|
-
}
|
|
550
|
-
response = await handleSvmPayment(endpoint, {
|
|
551
|
-
wallet: solana,
|
|
552
|
-
network: "solana-devnet"
|
|
553
|
-
});
|
|
554
|
-
} else if (networkType === "evm" /* EVM */) {
|
|
555
|
-
if (!window.ethereum) {
|
|
556
|
-
throw new Error("\u8BF7\u5B89\u88C5 MetaMask \u94B1\u5305");
|
|
557
|
-
}
|
|
558
|
-
const provider = new ethers2.BrowserProvider(window.ethereum);
|
|
559
|
-
const signer = await provider.getSigner();
|
|
560
|
-
const wallet = {
|
|
561
|
-
address: await signer.getAddress(),
|
|
562
|
-
signTypedData: async (domain, types, message) => {
|
|
563
|
-
return await signer.signTypedData(domain, types, message);
|
|
564
|
-
}
|
|
565
|
-
};
|
|
566
|
-
const network = endpoint.includes("sepolia") ? "base-sepolia" : "base";
|
|
567
|
-
response = await handleEvmPayment(endpoint, {
|
|
568
|
-
wallet,
|
|
569
|
-
network
|
|
570
|
-
});
|
|
571
|
-
} else {
|
|
572
|
-
throw new Error(`\u4E0D\u652F\u6301\u7684\u7F51\u7EDC\u7C7B\u578B: ${networkType}`);
|
|
573
|
-
}
|
|
574
|
-
return response;
|
|
575
|
-
}
|
|
576
|
-
async function handlePayment(endpoint, networkType, callbacks) {
|
|
577
|
-
try {
|
|
578
|
-
callbacks?.onStart?.();
|
|
579
|
-
const response = await makePayment(networkType, "", endpoint);
|
|
580
|
-
if (!response.ok) {
|
|
581
|
-
const errorText = await response.text();
|
|
582
|
-
throw new Error(`\u8BF7\u6C42\u5931\u8D25 (${response.status}): ${errorText}`);
|
|
583
|
-
}
|
|
584
|
-
const result = await response.json();
|
|
585
|
-
callbacks?.onSuccess?.(result);
|
|
586
|
-
return result;
|
|
587
|
-
} catch (err) {
|
|
588
|
-
const errorMessage = err.message || "\u652F\u4ED8\u5931\u8D25";
|
|
589
|
-
callbacks?.onError?.(errorMessage);
|
|
590
|
-
throw err;
|
|
591
|
-
} finally {
|
|
592
|
-
callbacks?.onFinish?.();
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
287
|
|
|
596
288
|
// src/utils/network.ts
|
|
597
289
|
var NETWORK_TYPE_MAP = {
|
|
@@ -615,6 +307,7 @@ var NETWORK_TYPE_MAP = {
|
|
|
615
307
|
};
|
|
616
308
|
function getNetworkDisplayName(network) {
|
|
617
309
|
const displayNames = {
|
|
310
|
+
"evm": "EVM",
|
|
618
311
|
"ethereum": "Ethereum",
|
|
619
312
|
"sepolia": "Sepolia Testnet",
|
|
620
313
|
"base": "Base",
|
|
@@ -660,6 +353,29 @@ var WalletStore = class {
|
|
|
660
353
|
}
|
|
661
354
|
}
|
|
662
355
|
});
|
|
356
|
+
onChainChanged(() => {
|
|
357
|
+
const connectedType = getConnectedNetworkType();
|
|
358
|
+
if (connectedType === "evm" /* EVM */) {
|
|
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
|
+
});
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
onWalletDisconnect(() => {
|
|
369
|
+
const connectedType = getConnectedNetworkType();
|
|
370
|
+
if (connectedType === "solana" /* SOLANA */ || connectedType === "svm" /* SVM */) {
|
|
371
|
+
console.log("\u26A0\uFE0F Solana wallet disconnected");
|
|
372
|
+
disconnectWallet();
|
|
373
|
+
this.setState({
|
|
374
|
+
address: null,
|
|
375
|
+
networkType: null
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
});
|
|
663
379
|
}
|
|
664
380
|
async autoReconnect() {
|
|
665
381
|
if (!isWalletManuallyDisconnected()) {
|
|
@@ -784,7 +500,7 @@ function usePayment() {
|
|
|
784
500
|
|
|
785
501
|
// src/react/hooks/usePaymentInfo.ts
|
|
786
502
|
import { useEffect, useState as useState2 } from "react";
|
|
787
|
-
function usePaymentInfo(merchantId, endpoint = PROD_BACK_URL) {
|
|
503
|
+
function usePaymentInfo(merchantId, endpoint = PROD_BACK_URL, additionalParams) {
|
|
788
504
|
const [paymentInfo, setPaymentInfo] = useState2(null);
|
|
789
505
|
const [supportedNetworks, setSupportedNetworks] = useState2([]);
|
|
790
506
|
const [isLoading, setIsLoading] = useState2(true);
|
|
@@ -793,8 +509,17 @@ function usePaymentInfo(merchantId, endpoint = PROD_BACK_URL) {
|
|
|
793
509
|
setIsLoading(true);
|
|
794
510
|
setError(null);
|
|
795
511
|
try {
|
|
796
|
-
|
|
797
|
-
const
|
|
512
|
+
const fullEndpoint = `${endpoint}/${merchantId}`;
|
|
513
|
+
const requestInit = {
|
|
514
|
+
method: "POST",
|
|
515
|
+
...additionalParams && Object.keys(additionalParams).length > 0 ? {
|
|
516
|
+
body: JSON.stringify(additionalParams),
|
|
517
|
+
headers: {
|
|
518
|
+
"Content-Type": "application/json"
|
|
519
|
+
}
|
|
520
|
+
} : {}
|
|
521
|
+
};
|
|
522
|
+
const response = await fetch(fullEndpoint, requestInit);
|
|
798
523
|
if (response.status === 402) {
|
|
799
524
|
const body = await response.json();
|
|
800
525
|
const payment = parsePaymentRequired(body);
|
|
@@ -815,7 +540,7 @@ function usePaymentInfo(merchantId, endpoint = PROD_BACK_URL) {
|
|
|
815
540
|
};
|
|
816
541
|
useEffect(() => {
|
|
817
542
|
fetchPaymentInfo();
|
|
818
|
-
}, [endpoint]);
|
|
543
|
+
}, [endpoint, merchantId]);
|
|
819
544
|
return {
|
|
820
545
|
paymentInfo,
|
|
821
546
|
supportedNetworks,
|
|
@@ -826,7 +551,188 @@ function usePaymentInfo(merchantId, endpoint = PROD_BACK_URL) {
|
|
|
826
551
|
}
|
|
827
552
|
|
|
828
553
|
// src/react/components/WalletConnect.tsx
|
|
829
|
-
import React from "react";
|
|
554
|
+
import React, { useState as useState3 } from "react";
|
|
555
|
+
|
|
556
|
+
// src/react/styles/inline-styles.ts
|
|
557
|
+
var isDarkMode = () => {
|
|
558
|
+
if (typeof window === "undefined") return false;
|
|
559
|
+
return window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
|
|
560
|
+
};
|
|
561
|
+
var colors = {
|
|
562
|
+
// Light mode
|
|
563
|
+
light: {
|
|
564
|
+
background: "#fafafa",
|
|
565
|
+
cardBg: "#ffffff",
|
|
566
|
+
text: "#0a0a0a",
|
|
567
|
+
textSecondary: "#737373",
|
|
568
|
+
primary: "#000000",
|
|
569
|
+
primaryHover: "#262626",
|
|
570
|
+
danger: "#ef4444",
|
|
571
|
+
dangerHover: "#dc2626",
|
|
572
|
+
success: "#10b981",
|
|
573
|
+
successHover: "#059669",
|
|
574
|
+
disabled: "#e5e5e5",
|
|
575
|
+
disabledText: "#a3a3a3",
|
|
576
|
+
errorBg: "#fef2f2",
|
|
577
|
+
errorText: "#dc2626"
|
|
578
|
+
},
|
|
579
|
+
// Dark mode
|
|
580
|
+
dark: {
|
|
581
|
+
background: "#0a0a0a",
|
|
582
|
+
cardBg: "#171717",
|
|
583
|
+
text: "#fafafa",
|
|
584
|
+
textSecondary: "#a3a3a3",
|
|
585
|
+
primary: "#ffffff",
|
|
586
|
+
primaryHover: "#e5e5e5",
|
|
587
|
+
danger: "#f87171",
|
|
588
|
+
dangerHover: "#ef4444",
|
|
589
|
+
success: "#34d399",
|
|
590
|
+
successHover: "#10b981",
|
|
591
|
+
disabled: "#262626",
|
|
592
|
+
disabledText: "#525252",
|
|
593
|
+
errorBg: "#1c1917",
|
|
594
|
+
errorText: "#f87171"
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
var getColors = () => {
|
|
598
|
+
return isDarkMode() ? colors.dark : colors.light;
|
|
599
|
+
};
|
|
600
|
+
var containerStyle = {
|
|
601
|
+
width: "100%",
|
|
602
|
+
maxWidth: "420px",
|
|
603
|
+
margin: "0 auto"
|
|
604
|
+
};
|
|
605
|
+
var getSectionStyle = () => {
|
|
606
|
+
const c = getColors();
|
|
607
|
+
return {
|
|
608
|
+
padding: "1.5rem",
|
|
609
|
+
background: c.cardBg,
|
|
610
|
+
borderRadius: "12px"
|
|
611
|
+
};
|
|
612
|
+
};
|
|
613
|
+
var getTitleStyle = () => {
|
|
614
|
+
const c = getColors();
|
|
615
|
+
return {
|
|
616
|
+
margin: "0 0 1.25rem 0",
|
|
617
|
+
fontSize: "1.125rem",
|
|
618
|
+
fontWeight: 600,
|
|
619
|
+
color: c.text,
|
|
620
|
+
letterSpacing: "-0.01em"
|
|
621
|
+
};
|
|
622
|
+
};
|
|
623
|
+
var buttonsContainerStyle = {
|
|
624
|
+
display: "flex",
|
|
625
|
+
flexDirection: "column",
|
|
626
|
+
gap: "0.75rem"
|
|
627
|
+
};
|
|
628
|
+
var walletOptionStyle = {
|
|
629
|
+
display: "flex",
|
|
630
|
+
flexDirection: "column",
|
|
631
|
+
gap: "0.5rem"
|
|
632
|
+
};
|
|
633
|
+
var baseButtonStyle = {
|
|
634
|
+
padding: "0.875rem 1.25rem",
|
|
635
|
+
fontSize: "0.9375rem",
|
|
636
|
+
fontWeight: 500,
|
|
637
|
+
border: "none",
|
|
638
|
+
borderRadius: "8px",
|
|
639
|
+
cursor: "pointer",
|
|
640
|
+
transition: "background-color 0.15s ease, opacity 0.15s ease",
|
|
641
|
+
outline: "none",
|
|
642
|
+
letterSpacing: "-0.01em"
|
|
643
|
+
};
|
|
644
|
+
var getConnectButtonStyle = (isDisabled, isHovered) => {
|
|
645
|
+
const c = getColors();
|
|
646
|
+
const darkMode = isDarkMode();
|
|
647
|
+
if (isDisabled) {
|
|
648
|
+
return {
|
|
649
|
+
...baseButtonStyle,
|
|
650
|
+
background: c.disabled,
|
|
651
|
+
color: c.disabledText,
|
|
652
|
+
cursor: "not-allowed",
|
|
653
|
+
border: darkMode ? "1px solid #404040" : "1px solid #d4d4d4"
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
return {
|
|
657
|
+
...baseButtonStyle,
|
|
658
|
+
background: isHovered ? c.primaryHover : c.primary,
|
|
659
|
+
color: darkMode ? "#000000" : "#ffffff",
|
|
660
|
+
cursor: "pointer"
|
|
661
|
+
};
|
|
662
|
+
};
|
|
663
|
+
var getDisconnectButtonStyle = (isHovered) => {
|
|
664
|
+
const c = getColors();
|
|
665
|
+
return {
|
|
666
|
+
...baseButtonStyle,
|
|
667
|
+
background: isHovered ? c.dangerHover : c.danger,
|
|
668
|
+
color: "#ffffff"
|
|
669
|
+
};
|
|
670
|
+
};
|
|
671
|
+
var getInstallLinkStyle = (isHovered) => {
|
|
672
|
+
const c = getColors();
|
|
673
|
+
return {
|
|
674
|
+
display: "inline-block",
|
|
675
|
+
padding: "0.5rem",
|
|
676
|
+
fontSize: "0.8125rem",
|
|
677
|
+
color: c.textSecondary,
|
|
678
|
+
textDecoration: isHovered ? "underline" : "none",
|
|
679
|
+
textAlign: "center",
|
|
680
|
+
fontWeight: 500
|
|
681
|
+
};
|
|
682
|
+
};
|
|
683
|
+
var walletAddressStyle = {
|
|
684
|
+
display: "flex",
|
|
685
|
+
flexDirection: "column",
|
|
686
|
+
gap: "0.5rem",
|
|
687
|
+
marginBottom: "1rem"
|
|
688
|
+
};
|
|
689
|
+
var getLabelStyle = () => {
|
|
690
|
+
const c = getColors();
|
|
691
|
+
return {
|
|
692
|
+
fontSize: "0.8125rem",
|
|
693
|
+
color: c.textSecondary,
|
|
694
|
+
fontWeight: 500,
|
|
695
|
+
textTransform: "uppercase",
|
|
696
|
+
letterSpacing: "0.05em"
|
|
697
|
+
};
|
|
698
|
+
};
|
|
699
|
+
var getAddressStyle = () => {
|
|
700
|
+
const c = getColors();
|
|
701
|
+
return {
|
|
702
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
703
|
+
fontSize: "0.9375rem",
|
|
704
|
+
fontWeight: 500,
|
|
705
|
+
color: c.text,
|
|
706
|
+
letterSpacing: "-0.01em"
|
|
707
|
+
};
|
|
708
|
+
};
|
|
709
|
+
var walletActionsStyle = {
|
|
710
|
+
margin: "1rem 0"
|
|
711
|
+
};
|
|
712
|
+
var getHintStyle = () => {
|
|
713
|
+
const c = getColors();
|
|
714
|
+
return {
|
|
715
|
+
marginTop: "1rem",
|
|
716
|
+
fontSize: "0.8125rem",
|
|
717
|
+
color: c.textSecondary,
|
|
718
|
+
textAlign: "center",
|
|
719
|
+
lineHeight: "1.5"
|
|
720
|
+
};
|
|
721
|
+
};
|
|
722
|
+
var getErrorStyle = () => {
|
|
723
|
+
const c = getColors();
|
|
724
|
+
return {
|
|
725
|
+
marginTop: "1rem",
|
|
726
|
+
padding: "0.75rem 1rem",
|
|
727
|
+
background: c.errorBg,
|
|
728
|
+
color: c.errorText,
|
|
729
|
+
borderRadius: "8px",
|
|
730
|
+
fontSize: "0.8125rem",
|
|
731
|
+
fontWeight: 500
|
|
732
|
+
};
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
// src/react/components/WalletConnect.tsx
|
|
830
736
|
function WalletConnect({
|
|
831
737
|
supportedNetworks = ["solana" /* SOLANA */, "evm" /* EVM */],
|
|
832
738
|
className = "",
|
|
@@ -834,6 +740,8 @@ function WalletConnect({
|
|
|
834
740
|
onDisconnect
|
|
835
741
|
}) {
|
|
836
742
|
const { address, networkType, isConnecting, error, connect, disconnect } = useWallet();
|
|
743
|
+
const [hoveredButton, setHoveredButton] = useState3(null);
|
|
744
|
+
const [hoveredLink, setHoveredLink] = useState3(null);
|
|
837
745
|
const handleConnect = async (network) => {
|
|
838
746
|
try {
|
|
839
747
|
await connect(network);
|
|
@@ -844,14 +752,16 @@ function WalletConnect({
|
|
|
844
752
|
disconnect();
|
|
845
753
|
onDisconnect?.();
|
|
846
754
|
};
|
|
847
|
-
return /* @__PURE__ */ React.createElement("div", {
|
|
755
|
+
return /* @__PURE__ */ React.createElement("div", { style: { ...containerStyle, ...className ? {} : {} }, className }, !address ? /* @__PURE__ */ React.createElement("div", { style: getSectionStyle() }, /* @__PURE__ */ React.createElement("h3", { style: getTitleStyle() }, "Connect Wallet"), supportedNetworks.length === 0 ? /* @__PURE__ */ React.createElement("p", { style: getHintStyle() }, "Please install a supported wallet extension") : /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, supportedNetworks.map((network) => {
|
|
848
756
|
const installed = isWalletInstalled(network);
|
|
849
|
-
return /* @__PURE__ */ React.createElement("div", { key: network,
|
|
757
|
+
return /* @__PURE__ */ React.createElement("div", { key: network, style: walletOptionStyle }, /* @__PURE__ */ React.createElement(
|
|
850
758
|
"button",
|
|
851
759
|
{
|
|
852
|
-
|
|
760
|
+
style: getConnectButtonStyle(isConnecting || !installed, hoveredButton === network),
|
|
853
761
|
onClick: () => handleConnect(network),
|
|
854
|
-
disabled: isConnecting || !installed
|
|
762
|
+
disabled: isConnecting || !installed,
|
|
763
|
+
onMouseEnter: () => setHoveredButton(network),
|
|
764
|
+
onMouseLeave: () => setHoveredButton(null)
|
|
855
765
|
},
|
|
856
766
|
isConnecting ? "Connecting..." : getNetworkDisplayName(network)
|
|
857
767
|
), !installed && /* @__PURE__ */ React.createElement(
|
|
@@ -860,62 +770,24 @@ function WalletConnect({
|
|
|
860
770
|
href: getWalletInstallUrl(network),
|
|
861
771
|
target: "_blank",
|
|
862
772
|
rel: "noopener noreferrer",
|
|
863
|
-
|
|
773
|
+
style: getInstallLinkStyle(hoveredLink === network),
|
|
774
|
+
onMouseEnter: () => setHoveredLink(network),
|
|
775
|
+
onMouseLeave: () => setHoveredLink(null)
|
|
864
776
|
},
|
|
865
777
|
"Install Wallet"
|
|
866
778
|
));
|
|
867
|
-
})), error && /* @__PURE__ */ React.createElement("p", {
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
// src/react/components/PaymentButton.tsx
|
|
871
|
-
import React2 from "react";
|
|
872
|
-
function PaymentButton({
|
|
873
|
-
endpoint,
|
|
874
|
-
className = "",
|
|
875
|
-
disabled = false,
|
|
876
|
-
onSuccess,
|
|
877
|
-
onError,
|
|
878
|
-
onStart,
|
|
879
|
-
onFinish,
|
|
880
|
-
children = "Pay Now"
|
|
881
|
-
}) {
|
|
882
|
-
const { networkType } = useWallet();
|
|
883
|
-
const { isProcessing, setIsProcessing, setResult, setError, error } = usePayment();
|
|
884
|
-
const handleClick = async () => {
|
|
885
|
-
if (!networkType) {
|
|
886
|
-
const errorMsg = "Please connect wallet first";
|
|
887
|
-
setError(errorMsg);
|
|
888
|
-
onError?.(errorMsg);
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
try {
|
|
892
|
-
onStart?.();
|
|
893
|
-
setIsProcessing(true);
|
|
894
|
-
setError(null);
|
|
895
|
-
const result = await handlePayment(endpoint, networkType);
|
|
896
|
-
setResult(result);
|
|
897
|
-
onSuccess?.(result);
|
|
898
|
-
} catch (err) {
|
|
899
|
-
const errorMsg = err.message || "Payment failed";
|
|
900
|
-
setError(errorMsg);
|
|
901
|
-
onError?.(errorMsg);
|
|
902
|
-
} finally {
|
|
903
|
-
setIsProcessing(false);
|
|
904
|
-
onFinish?.();
|
|
905
|
-
}
|
|
906
|
-
};
|
|
907
|
-
return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(
|
|
779
|
+
})), error && /* @__PURE__ */ React.createElement("p", { style: getErrorStyle() }, error), /* @__PURE__ */ React.createElement("p", { style: getHintStyle() }, "To switch accounts, please change it in your wallet extension")) : /* @__PURE__ */ React.createElement("div", { style: getSectionStyle() }, /* @__PURE__ */ React.createElement("div", { style: walletAddressStyle }, /* @__PURE__ */ React.createElement("span", { style: getLabelStyle() }, "Connected ", networkType && `(${getNetworkDisplayName(networkType)})`), /* @__PURE__ */ React.createElement("span", { style: getAddressStyle() }, formatAddress(address))), /* @__PURE__ */ React.createElement("div", { style: walletActionsStyle }, /* @__PURE__ */ React.createElement(
|
|
908
780
|
"button",
|
|
909
781
|
{
|
|
910
|
-
|
|
911
|
-
onClick:
|
|
912
|
-
|
|
782
|
+
style: getDisconnectButtonStyle(hoveredButton === "disconnect"),
|
|
783
|
+
onClick: handleDisconnect,
|
|
784
|
+
onMouseEnter: () => setHoveredButton("disconnect"),
|
|
785
|
+
onMouseLeave: () => setHoveredButton(null)
|
|
913
786
|
},
|
|
914
|
-
|
|
915
|
-
),
|
|
787
|
+
"Disconnect"
|
|
788
|
+
)), /* @__PURE__ */ React.createElement("p", { style: getHintStyle() }, "Switch account in your wallet to change address")));
|
|
916
789
|
}
|
|
917
790
|
export {
|
|
918
|
-
PaymentButton,
|
|
919
791
|
WalletConnect,
|
|
920
792
|
usePayment,
|
|
921
793
|
usePaymentInfo,
|