@dexterai/x402 1.6.0 → 1.6.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.
@@ -424,7 +424,246 @@ function x402Middleware(config) {
424
424
  }
425
425
 
426
426
  // src/server/browser-support.ts
427
- function generatePaywallHtml(paymentRequiredHeader, requestUrl, method, config) {
427
+ var USDC_ICON_SVG = `<svg width="18" height="18" viewBox="0 0 2000 2000" xmlns="http://www.w3.org/2000/svg"><path d="M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z" fill="#2775ca"/><path d="M1275 1158.33c0-145.83-87.5-195.83-262.5-216.66-125-16.67-150-50-150-108.34s41.67-95.83 125-95.83c75 0 116.67 25 137.5 87.5 4.17 12.5 16.67 20.83 29.17 20.83h66.66c16.67 0 29.17-12.5 29.17-29.16v-4.17c-16.67-91.67-91.67-162.5-187.5-170.83v-100c0-16.67-12.5-29.17-33.33-33.34h-62.5c-16.67 0-29.17 12.5-33.34 33.34v95.83c-125 16.67-204.16 100-204.16 204.17 0 137.5 83.33 191.66 258.33 212.5 116.67 20.83 154.17 45.83 154.17 112.5s-58.34 112.5-137.5 112.5c-108.34 0-145.84-45.84-158.34-108.34-4.16-16.66-16.66-25-29.16-25h-70.84c-16.66 0-29.16 12.5-29.16 29.17v4.17c16.66 104.16 83.33 179.16 220.83 200v100c0 16.66 12.5 29.16 33.33 33.33h62.5c16.67 0 29.17-12.5 33.34-33.33v-100c125-20.84 208.33-108.34 208.33-220.84z" fill="#fff"/><path d="M787.5 1595.83c-325-116.66-491.67-479.16-370.83-800 62.5-175 200-308.33 370.83-370.83 16.67-8.33 25-20.83 25-41.67V325c0-16.67-8.33-29.17-25-33.33-4.17 0-12.5 0-16.67 4.16-395.83 125-612.5 545.84-487.5 941.67 75 233.33 254.17 412.5 487.5 487.5 16.67 8.33 33.34 0 37.5-16.67 4.17-4.16 4.17-8.33 4.17-16.66v-58.34c0-12.5-12.5-29.16-25-37.5zM1229.17 295.83c-16.67-8.33-33.34 0-37.5 16.67-4.17 4.17-4.17 8.33-4.17 16.67v58.33c0 16.67 12.5 33.33 25 41.67 325 116.66 491.67 479.16 370.83 800-62.5 175-200 308.33-370.83 370.83-16.67 8.33-25 20.83-25 41.67V1700c0 16.67 8.33 29.17 25 33.33 4.17 0 12.5 0 16.67-4.16 395.83-125 612.5-545.84 487.5-941.67-75-237.5-258.34-416.67-487.5-491.67z" fill="#fff"/></svg>`;
428
+ var DEXTER_CREST_SVG = `<svg width="36" height="36" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg"><g><path fill="#F2681A" d="m324.93,313.11c-115.5,0-231,0-350,0l350,0z"/><path fill="#FDFAF5" d="m230.43,50.62c1.1.85 2.19 1.7 3.32 2.57 6.02 4.8 11.77 9.88 17.46 15.07.92.84.92.84 1.86 1.69 1.82 1.69 3.59 3.42 5.35 5.16.61.56 1.22 1.13 1.84 1.71 5.66 5.76 6.18 10.43 6.13 18.3.02 1.16.04 2.32.06 3.52.06 3.83.06 7.65.07 11.48.02 2.68.05 5.35.08 8.03.05 5.6.09 11.21.1 16.81.02 7.15.09 14.31.17 21.46.06 5.53.1 11.05.13 16.58.02 2.64.04 5.27.07 7.91.18 17.58.12 32.82-11.24 47.32-7.35 7.27-16.54 12.06-25.42 17.22-1.97 1.16-3.94 2.33-5.91 3.49-7.16 4.24-14.34 8.44-21.53 12.62-4.8 2.79-9.59 5.6-14.38 8.42-1.25.73-2.5 1.47-3.79 2.23-2.32 1.36-4.64 2.73-6.96 4.1-27.47 16.09-27.47 16.09-42.16 12.93-8.06-2.28-14.94-5.82-22.16-10.02-1.17-.67-2.34-1.34-3.54-2.04-24.55-14.25-43.58-27.03-51.9-55.58-1.07-4.58-1.54-8.92-1.52-13.61.28-9.5.28-9.5-3.3-17.97-1.81-1.49-3.68-2.92-5.59-4.28-9.19-7.06-12.7-20.03-14.18-31.06-.54-5.77-.55-11.56-.6-17.35-.03-1.32-.07-2.63-.1-3.99-.01-1.26-.02-2.53-.03-3.83-.02-1.15-.03-2.29-.05-3.47.72-4.02 1.94-5.36 5.21-7.74 2.89-.53 2.89-.53 6.07-.46 1.71.02 1.71.02 3.46.05 1.19.04 2.37.08 3.59.12 1.2.02 2.41.04 3.65.06 2.97.05 5.93.13 8.9.23.14-1.35.29-2.7.43-4.08.63-5 1.78-9.74 3.14-14.58.22-.79.43-1.59.66-2.4.53-1.92 1.06-3.84 1.6-5.76-1.55-.45-1.55-.45-3.13-.9-9.52-3.52-17.1-10.95-21.37-20.1-3.81-9.26-3.87-20.34-.29-29.68 6.49-13.99 16.36-23.23 30.66-29.01 49.81-17.69 115.79 8.35 155.13 38.85z"/><path fill="#F2671A" d="m142.93,22.62c.86.19 1.73.39 2.62.59 36.12 8.21 68.79 24.98 95.38 50.75 1.02.98 2.03 1.97 3.08 2.98 10.84 10.66 10.84 10.66 11.05 14.62-2.06 3.55-5.44 4.18-9.17 5.3-.79.25-1.59.49-2.41.75-28.13 8.43-60.95 6.37-87.13-7.16-.86-.49-1.71-.97-2.6-1.48-7.37-4.05-12.59-3.36-20.59-1.54-22.76 4-48.47 1.53-68.69-9.74-4.88-3.88-8.23-8.29-10.21-14.22-.93-10.38-.67-18.44 5.83-26.83 19.57-23.38 55.99-20.36 82.83-14z"/><path fill="#F16619" d="m44.93,129.12c27.36-.03 54.72-.05 82.08-.06 12.7-.01 25.41-.01 38.11-.03 11.07-.01 22.14-.02 33.2-.02 5.86 0 11.73-.01 17.59-.01 5.51-.01 11.03-.01 16.54-.01 2.03 0 4.06 0 6.09-.01 2.76-.01 5.52 0 8.28 0 .81 0 1.63-.01 2.47-.01 5.51.02 5.51.02 6.81 1.32.22 3.43.22 3.43 0 7-2.75 2.75-3.42 2.66-7.15 2.82-1.41.07-1.41.07-2.85.14-1.47.05-1.47.05-2.98.11-1.49.07-1.49.07-3 .14-2.45.11-4.9.21-7.35.3-.2 1.3-.4 2.59-.6 3.93-2.57 16.08-5.93 29.89-18.89 40.86-10.35 7.28-21.87 8.49-34.17 7.71-13.11-2.33-22.52-9.19-30.33-19.83-4.49-7.64-4.8-17.05-5.83-25.67-4.24.39-8.47.77-12.83 1.17-.28 1.84-.28 1.84-.56 3.71-2.32 14.39-5.63 23.35-16.95 33.11-2.32 1.67-2.32 1.67-4.65 1.67 4 4.67 9.06 6.59 14.87 8.24 3.79 1.09 3.79 1.09 6.12 3.43-.65 5.31-.65 5.31-2.33 7-8.42-.27-15.13-2.29-22.17-7-1.09-1.21-2.17-2.43-3.25-3.65-2.72-2.81-4.45-3.84-8.36-4.16-1.67-.02-3.34-.02-5.01.01-1.77-.04-3.54-.09-5.3-.15-1.27-.04-1.27-.04-2.56-.08-9.26-.54-17.6-4.56-24.51-10.64-9.58-11.11-11.03-22.56-10.72-36.82.02-1.4.03-2.8.05-4.24.04-3.42.1-6.85.17-10.27z"/><path fill="#F26117" d="m172.68,203.08c7.27.09 13.23 1.97 18.87 6.65 2.88 3.07 3.86 5.12 4.25 9.32-.12 1.01-.24 2.02-.36 3.06-2.55.95-2.55.95-5.83 1.17-3.28-2.84-3.28-2.84-5.83-5.83-.36.58-.71 1.16-1.08 1.75-7.6 11.29-20.06 17.74-33.05 21.09-20.36 3.1-36.81-1.66-53.37-13.73-2.33-2.11-2.33-2.11-4.67-5.61.42-3.45.99-4.49 3.5-7 4.07.37 5.95 2.13 8.75 4.96 9.81 8.93 22.53 11.87 35.51 11.69 11.74-1.05 22.38-5.85 31.57-13.15 2.06-2.45 2.06-2.45 3.5-4.67-1.66.07-1.66.07-3.35.15-3.65-.15-3.65-.15-5.98-2.48.75-6.18 1.46-7.19 7.58-7.36z"/></g></svg>`;
429
+ var DEXTER_STYLES = `
430
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Orbitron:wght@500;700&display=swap');
431
+ *{margin:0;padding:0;box-sizing:border-box}
432
+ body{font-family:'Inter',system-ui,-apple-system,sans-serif;background:#0a0a0a;color:#e2e8f0;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:1rem}
433
+ .card{max-width:460px;width:100%;background:rgba(20,20,20,.85);border:1px solid rgba(242,107,26,.12);border-radius:8px;padding:2rem 2rem 1.75rem;text-align:center;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}
434
+ .crest{margin:0 auto .75rem}
435
+ h1{font-family:'Orbitron',sans-serif;font-size:1.15rem;font-weight:700;color:#f1f5f9;letter-spacing:.04em;margin-bottom:.35rem}
436
+ .desc{color:#94a3b8;font-size:.9rem;margin-bottom:1.25rem;line-height:1.5}
437
+ .price{font-family:'Orbitron',sans-serif;font-size:1.6rem;font-weight:700;color:#F26B1A;margin:.75rem 0 .25rem;display:inline-flex;align-items:center;gap:.35rem}
438
+ .price svg{width:1.3em;height:1.3em;flex-shrink:0}
439
+ .chain{color:#525252;font-size:.75rem;margin-bottom:1.25rem;letter-spacing:.03em}
440
+ .endpoint{background:rgba(242,107,26,.06);border:1px solid rgba(242,107,26,.12);border-radius:6px;padding:.5rem .75rem;margin-bottom:1.25rem}
441
+ .endpoint code{font-family:'SF Mono',Monaco,Consolas,monospace;font-size:.8rem;color:#F26B1A}
442
+ .info{background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.06);border-radius:6px;padding:.85rem 1rem;font-size:.82rem;color:#737373;line-height:1.6;text-align:left}
443
+ .info strong{color:#a3a3a3}
444
+ .info code{background:rgba(242,107,26,.08);padding:2px 5px;border-radius:3px;font-size:.78rem;color:#F26B1A;font-family:'SF Mono',Monaco,Consolas,monospace}
445
+ .info a{color:#F26B1A;text-decoration:none;font-weight:600}
446
+ .info a:hover{text-decoration:underline}
447
+ .footer{margin-top:1.25rem;display:flex;align-items:center;justify-content:center;gap:.75rem;font-size:.7rem;color:#404040}
448
+ .footer a{color:#525252;text-decoration:none}
449
+ .footer a:hover{color:#737373}
450
+ .sep{width:3px;height:3px;border-radius:50%;background:#333}
451
+ `;
452
+ var PAY_BUTTON_STYLES = `
453
+ .pay-section{margin:1.25rem 0}
454
+ .pay-btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;background:linear-gradient(135deg,#F26B1A,#D13F00);color:#fff;border:none;padding:.65rem 2rem;border-radius:6px;font-family:'Inter',sans-serif;font-size:.95rem;font-weight:600;cursor:pointer;transition:opacity .15s,transform .1s;min-width:180px}
455
+ .pay-btn:hover:not(:disabled){opacity:.9;transform:translateY(-1px)}
456
+ .pay-btn:active:not(:disabled){transform:translateY(0)}
457
+ .pay-btn:disabled{opacity:.6;cursor:not-allowed}
458
+ .pay-btn .spinner{width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite}
459
+ @keyframes spin{to{transform:rotate(360deg)}}
460
+ .pay-status{font-size:.8rem;color:#737373;margin-top:.5rem;min-height:1.2em}
461
+ .pay-status.error{color:#ef4444}
462
+ .pay-status.success{color:#22c55e}
463
+ .pay-alt{font-size:.78rem;color:#404040;margin-top:.75rem}
464
+ .pay-alt a{color:#F26B1A;text-decoration:none}
465
+ .result-box{background:rgba(34,197,94,.06);border:1px solid rgba(34,197,94,.15);border-radius:6px;padding:.75rem;margin-top:.75rem;text-align:left;font-size:.78rem;max-height:200px;overflow:auto}
466
+ .result-box pre{color:#94a3b8;font-family:'SF Mono',Monaco,Consolas,monospace;white-space:pre-wrap;word-break:break-all}
467
+ .no-wallet{font-size:.82rem;color:#737373;margin:1rem 0}
468
+ `;
469
+ var PAY_SCRIPT = `
470
+ <script type="module">
471
+ // Payment data is embedded in #x402-data attributes
472
+ const dataEl = document.getElementById('x402-data');
473
+ if (!dataEl) throw new Error('Missing payment data');
474
+
475
+ const requirements = JSON.parse(atob(dataEl.dataset.requirements));
476
+ const requestMethod = dataEl.dataset.method;
477
+ const requestUrl = dataEl.dataset.url;
478
+ const rpcUrl = dataEl.dataset.rpc || 'https://api.dexter.cash/api/solana/rpc';
479
+
480
+ // Detect wallet provider
481
+ function getWalletProvider() {
482
+ if (window.phantom?.solana?.isPhantom) return { name: 'Phantom', provider: window.phantom.solana };
483
+ if (window.solflare?.isSolflare) return { name: 'Solflare', provider: window.solflare };
484
+ if (window.backpack) return { name: 'Backpack', provider: window.backpack };
485
+ // Generic wallet-standard fallback
486
+ if (window.solana) return { name: 'Wallet', provider: window.solana };
487
+ return null;
488
+ }
489
+
490
+ const walletInfo = getWalletProvider();
491
+ const btn = document.getElementById('pay-btn');
492
+ const status = document.getElementById('pay-status');
493
+ const section = document.getElementById('pay-section');
494
+ const noWallet = document.getElementById('no-wallet');
495
+
496
+ if (walletInfo && btn) {
497
+ section.style.display = 'block';
498
+ if (noWallet) noWallet.style.display = 'none';
499
+ } else if (noWallet) {
500
+ noWallet.style.display = 'block';
501
+ if (section) section.style.display = 'none';
502
+ }
503
+
504
+ // Preload Solana libraries in background
505
+ let solanaLibs = null;
506
+ const preload = (async () => {
507
+ try {
508
+ const [web3, spl] = await Promise.all([
509
+ import('https://esm.sh/@solana/web3.js@1.98.0'),
510
+ import('https://esm.sh/@solana/spl-token@0.4.9'),
511
+ ]);
512
+ solanaLibs = { web3, spl };
513
+ } catch (e) {
514
+ console.warn('[x402] Failed to preload Solana libraries:', e);
515
+ }
516
+ })();
517
+
518
+ function setStatus(msg, type) {
519
+ if (!status) return;
520
+ status.textContent = msg;
521
+ status.className = 'pay-status' + (type ? ' ' + type : '');
522
+ }
523
+
524
+ function setBtnState(text, disabled, loading) {
525
+ if (!btn) return;
526
+ btn.disabled = disabled;
527
+ btn.innerHTML = loading
528
+ ? '<span class="spinner"></span>' + text
529
+ : text;
530
+ }
531
+
532
+ if (btn) {
533
+ btn.addEventListener('click', async () => {
534
+ if (!walletInfo) return;
535
+ const { provider } = walletInfo;
536
+
537
+ try {
538
+ // 1. Connect wallet
539
+ setBtnState('Connecting...', true, true);
540
+ setStatus('');
541
+ await provider.connect();
542
+
543
+ if (!provider.publicKey) {
544
+ throw new Error('Wallet did not provide a public key');
545
+ }
546
+
547
+ // 2. Load Solana libraries (should already be cached from preload)
548
+ setBtnState('Preparing...', true, true);
549
+ await preload;
550
+ if (!solanaLibs) {
551
+ // Retry once
552
+ const [web3, spl] = await Promise.all([
553
+ import('https://esm.sh/@solana/web3.js@1.98.0'),
554
+ import('https://esm.sh/@solana/spl-token@0.4.9'),
555
+ ]);
556
+ solanaLibs = { web3, spl };
557
+ }
558
+
559
+ const { web3, spl } = solanaLibs;
560
+ const { PublicKey, Connection, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } = web3;
561
+ const { getAssociatedTokenAddress, createTransferCheckedInstruction, getMint, TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID } = spl;
562
+
563
+ // 3. Parse payment requirements
564
+ const accept = requirements.accepts[0];
565
+ if (!accept) throw new Error('No payment method available');
566
+
567
+ const payTo = new PublicKey(accept.payTo);
568
+ const amount = BigInt(accept.amount || accept.maxAmountRequired);
569
+ const mintPubkey = new PublicKey(accept.asset);
570
+ const feePayer = accept.extra?.feePayer ? new PublicKey(accept.extra.feePayer) : provider.publicKey;
571
+ const userPubkey = provider.publicKey;
572
+
573
+ // 4. Build transaction
574
+ setBtnState('Building tx...', true, true);
575
+ const connection = new Connection(rpcUrl, 'confirmed');
576
+
577
+ const instructions = [];
578
+
579
+ // ComputeBudget
580
+ instructions.push(ComputeBudgetProgram.setComputeUnitLimit({ units: 12000 }));
581
+ instructions.push(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1 }));
582
+
583
+ // Determine token program
584
+ const mintInfo = await connection.getAccountInfo(mintPubkey, 'confirmed');
585
+ if (!mintInfo) throw new Error('Token mint not found');
586
+ const programId = mintInfo.owner.toBase58() === TOKEN_2022_PROGRAM_ID.toBase58() ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
587
+
588
+ const mint = await getMint(connection, mintPubkey, undefined, programId);
589
+
590
+ // ATAs
591
+ const sourceAta = await getAssociatedTokenAddress(mintPubkey, userPubkey, false, programId);
592
+ const destAta = await getAssociatedTokenAddress(mintPubkey, payTo, false, programId);
593
+
594
+ // Verify source exists
595
+ const sourceInfo = await connection.getAccountInfo(sourceAta, 'confirmed');
596
+ if (!sourceInfo) throw new Error('No USDC token account found. Make sure you have USDC in your wallet.');
597
+
598
+ // TransferChecked
599
+ instructions.push(createTransferCheckedInstruction(sourceAta, mintPubkey, destAta, userPubkey, amount, mint.decimals, [], programId));
600
+
601
+ const { blockhash } = await connection.getLatestBlockhash('confirmed');
602
+ const message = new TransactionMessage({ payerKey: feePayer, recentBlockhash: blockhash, instructions }).compileToV0Message();
603
+ const transaction = new VersionedTransaction(message);
604
+
605
+ // 5. Sign
606
+ setBtnState('Sign in wallet...', true, true);
607
+ setStatus('Approve the transaction in your wallet');
608
+ const signed = await provider.signTransaction(transaction);
609
+ const serialized = signed.serialize();
610
+
611
+ // Convert Uint8Array to base64
612
+ let payload = '';
613
+ const bytes = new Uint8Array(serialized);
614
+ const chunk = 8192;
615
+ for (let i = 0; i < bytes.length; i += chunk) {
616
+ payload += String.fromCharCode.apply(null, bytes.slice(i, i + chunk));
617
+ }
618
+ payload = btoa(payload);
619
+
620
+ // 6. Build payment-signature header (x402 v2 format)
621
+ const paymentSignature = {
622
+ x402Version: accept.x402Version ?? 2,
623
+ resource: requirements.resource,
624
+ accepted: accept,
625
+ payload,
626
+ };
627
+ const paymentHeader = btoa(JSON.stringify(paymentSignature));
628
+
629
+ // 7. Submit payment
630
+ setBtnState('Verifying...', true, true);
631
+ setStatus('Payment submitted, verifying...');
632
+
633
+ // Use the original request body if available
634
+ const originalBody = dataEl.dataset.body ? atob(dataEl.dataset.body) : '{}';
635
+ const response = await fetch(requestUrl, {
636
+ method: requestMethod,
637
+ headers: {
638
+ 'Content-Type': 'application/json',
639
+ 'PAYMENT-SIGNATURE': paymentHeader,
640
+ },
641
+ body: requestMethod !== 'GET' ? originalBody : undefined,
642
+ });
643
+
644
+ if (response.ok) {
645
+ const data = await response.json();
646
+ setBtnState('Paid', true, false);
647
+ setStatus('Payment successful', 'success');
648
+ // Show response
649
+ const resultBox = document.createElement('div');
650
+ resultBox.className = 'result-box';
651
+ resultBox.innerHTML = '<pre>' + JSON.stringify(data, null, 2).replace(/</g, '&lt;') + '</pre>';
652
+ section.appendChild(resultBox);
653
+ } else {
654
+ const err = await response.json().catch(() => ({ error: 'Payment verification failed' }));
655
+ throw new Error(err.error || err.reason || 'Payment failed');
656
+ }
657
+ } catch (err) {
658
+ console.error('[x402] Payment error:', err);
659
+ setBtnState('Pay ' + document.getElementById('price-value').textContent, false, false);
660
+ setStatus(err.message || 'Payment failed', 'error');
661
+ }
662
+ });
663
+ }
664
+ </script>
665
+ `;
666
+ function generatePaywallHtml(paymentRequiredHeader, requestUrl, method, config, rpcUrl, requestBody) {
428
667
  let price = "?";
429
668
  let description = "This resource requires payment";
430
669
  let network = "";
@@ -442,72 +681,86 @@ function generatePaywallHtml(paymentRequiredHeader, requestUrl, method, config)
442
681
  }
443
682
  } catch {
444
683
  }
445
- const chainName = network.includes("solana") ? "Solana" : network.includes("eip155") ? "Base" : "Unknown";
684
+ const chainName = network.includes("solana") ? "Solana" : network.includes("eip155") ? "Base" : "";
446
685
  const endpointSection = config.showEndpoint ? `<div class="endpoint"><code>${method} ${requestUrl}</code></div>` : "";
447
686
  return `<!DOCTYPE html>
448
687
  <html lang="en">
449
688
  <head>
450
689
  <meta charset="utf-8">
451
690
  <meta name="viewport" content="width=device-width,initial-scale=1">
452
- <title>${config.title} \u2014 $${price} USDC</title>
453
- <style>
454
- *{margin:0;padding:0;box-sizing:border-box}
455
- body{font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0a0a0a;color:#e2e8f0;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:1rem}
456
- .paywall{max-width:460px;width:100%;background:#141414;border:1px solid #2a2a2a;border-radius:16px;padding:2.5rem;text-align:center}
457
- .paywall h1{font-size:1.35rem;margin-bottom:.35rem;color:#f1f5f9;font-weight:600}
458
- .desc{color:#94a3b8;font-size:.95rem;margin-bottom:1.5rem;line-height:1.5}
459
- .price-badge{display:inline-block;background:linear-gradient(135deg,#22c55e,#16a34a);color:#fff;padding:.6rem 2rem;border-radius:999px;font-size:1.75rem;font-weight:700;margin:1rem 0;letter-spacing:-.02em}
460
- .chain{color:#64748b;font-size:.8rem;margin-bottom:1.5rem}
461
- .endpoint{background:#1e1e1e;border:1px solid #333;border-radius:8px;padding:.6rem 1rem;margin-bottom:1.5rem}
462
- .endpoint code{font-family:'SF Mono',Monaco,Consolas,monospace;font-size:.85rem;color:#60a5fa}
463
- .info{background:#1a1a2e;border:1px solid #2d2d5e;border-radius:10px;padding:1rem 1.25rem;font-size:.85rem;color:#a0aec0;line-height:1.6;text-align:left}
464
- .info strong{color:#e2e8f0}
465
- .info code{background:#2a2a3e;padding:2px 6px;border-radius:4px;font-size:.8rem;color:#818cf8;font-family:'SF Mono',Monaco,Consolas,monospace}
466
- .info a{color:#60a5fa;text-decoration:none;font-weight:600}
467
- .info a:hover{text-decoration:underline}
468
- .powered{margin-top:1.5rem;font-size:.75rem;color:#475569}
469
- .powered a{color:#64748b;text-decoration:none}
470
- .powered a:hover{color:#94a3b8}
471
- .x402-badge{display:inline-flex;align-items:center;gap:.35rem;background:#1a1a2e;border:1px solid #2d2d5e;padding:.25rem .75rem;border-radius:999px;font-size:.7rem;color:#818cf8;margin-top:.75rem;font-weight:500}
472
- </style>
691
+ <title>${config.title} \u2014 ${price} USDC</title>
692
+ <style>${DEXTER_STYLES}${PAY_BUTTON_STYLES}</style>
473
693
  </head>
474
694
  <body>
475
- <div class="paywall">
695
+ <div class="card">
696
+ <div class="crest">${DEXTER_CREST_SVG}</div>
476
697
  <h1>${config.title}</h1>
477
698
  <p class="desc">${description}</p>
478
- <div class="price-badge">$${price} USDC</div>
479
- <div class="chain">${chainName} network</div>
699
+ <div class="price">${USDC_ICON_SVG}<span id="price-value">${price}</span></div>
700
+ <div class="chain">${chainName}${chainName ? " network" : ""}</div>
480
701
  ${endpointSection}
481
- <div class="info">
482
- <strong>How to access this endpoint:</strong><br><br>
483
- Use any x402-compatible client or SDK. The client handles wallet connection and payment automatically.<br><br>
484
- <code>npm install @dexterai/x402</code><br><br>
485
- <a href="${config.sdkUrl}">Learn more about x402 &rarr;</a>
702
+
703
+ <div id="pay-section" class="pay-section" style="display:none">
704
+ <button id="pay-btn" class="pay-btn">Pay ${price}</button>
705
+ <div id="pay-status" class="pay-status"></div>
706
+ <div class="pay-alt">or use <a href="${config.sdkUrl}">x402 SDK</a> for programmatic access</div>
707
+ </div>
708
+
709
+ <div id="no-wallet" class="no-wallet" style="display:none">
710
+ <div class="info">
711
+ <strong>Access this endpoint:</strong><br><br>
712
+ Use any x402-compatible client or a browser with a Solana wallet extension (Phantom, Solflare, Backpack).<br><br>
713
+ <code>npm install @dexterai/x402</code><br><br>
714
+ <a href="${config.sdkUrl}">x402 SDK docs &rarr;</a>
715
+ </div>
716
+ </div>
717
+
718
+ <div class="footer">
719
+ <a href="https://docs.dexter.cash/docs/sdk/">x402</a>
720
+ <span class="sep"></span>
721
+ <a href="https://dexter.cash">Dexter</a>
486
722
  </div>
487
- <div class="powered">${config.branding}</div>
488
- <div class="x402-badge">x402 protocol</div>
489
723
  </div>
724
+
725
+ <div id="x402-data" style="display:none"
726
+ data-requirements="${paymentRequiredHeader}"
727
+ data-method="${method}"
728
+ data-url="${requestUrl}"
729
+ data-rpc="${rpcUrl}"
730
+ data-body="${requestBody ? Buffer.from(requestBody).toString("base64") : ""}"
731
+ ></div>
732
+ ${PAY_SCRIPT}
490
733
  </body>
491
734
  </html>`;
492
735
  }
493
736
  function x402BrowserSupport(config = {}) {
494
737
  const resolvedConfig = {
495
738
  title: config.title ?? "Payment Required",
496
- branding: config.branding ?? 'Powered by <a href="https://x402.org">x402</a>',
497
- sdkUrl: config.sdkUrl ?? "https://x402.org",
739
+ branding: config.branding ?? 'Powered by <a href="https://docs.dexter.cash/docs/sdk/">Dexter x402</a>',
740
+ sdkUrl: config.sdkUrl ?? "https://docs.dexter.cash/docs/sdk/",
498
741
  showEndpoint: config.showEndpoint ?? true
499
742
  };
743
+ const rpcUrl = config.rpcUrl ?? "https://api.dexter.cash/api/solana/rpc";
500
744
  return (req, res, next) => {
501
745
  const originalJson = res.json.bind(res);
502
746
  res.json = function(body) {
503
747
  if (res.statusCode === 402 && req.accepts("html") && !req.headers["payment-signature"]) {
504
748
  const paymentRequired = res.getHeader("PAYMENT-REQUIRED") || res.getHeader("payment-required");
505
749
  if (paymentRequired && typeof paymentRequired === "string") {
750
+ let bodyStr;
751
+ if (req.body && typeof req.body === "object" && Object.keys(req.body).length > 0) {
752
+ try {
753
+ bodyStr = JSON.stringify(req.body);
754
+ } catch {
755
+ }
756
+ }
506
757
  const html = generatePaywallHtml(
507
758
  paymentRequired,
508
759
  req.originalUrl,
509
760
  req.method,
510
- resolvedConfig
761
+ resolvedConfig,
762
+ rpcUrl,
763
+ bodyStr
511
764
  );
512
765
  res.status(402).type("html").send(html);
513
766
  return res;