@three-ws/x402-payment-modal 1.1.0 → 1.2.0

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 CHANGED
@@ -1,11 +1,20 @@
1
1
  # @three-ws/x402-payment-modal
2
2
 
3
- **A drop-in payment modal for any [x402](https://x402.org) paid endpoint.** One ES
4
- module, zero runtime dependencies. It turns an HTTP `402 Payment Required`
5
- challenge into a polished checkout: wallet connect (Phantom on Solana, MetaMask /
6
- any EVM wallet on Base via EIP-3009), the `402 → sign → settle` flow, optional
7
- SIWX re-entry, client-side spending caps, and a receipt — vanilla JS, no bundler
8
- required.
3
+ > The complete drop-in checkout for any [x402](https://x402.org) paid endpoint wallet connect, sign, settle, receipt.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@three-ws/x402-payment-modal.svg)](https://www.npmjs.com/package/@three-ws/x402-payment-modal)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@three-ws/x402-payment-modal.svg)](https://www.npmjs.com/package/@three-ws/x402-payment-modal)
7
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@three-ws/x402-payment-modal.svg)](https://bundlephobia.com/package/@three-ws/x402-payment-modal)
8
+ [![License: Proprietary](https://img.shields.io/badge/license-Proprietary-red.svg)](./LICENSE)
9
+
10
+ **A drop-in payment modal for any [x402](https://x402.org) paid endpoint.** The
11
+ browser client is one ES module with zero runtime dependencies; it turns an HTTP
12
+ `402 Payment Required` challenge into a polished checkout: wallet connect (Phantom
13
+ / Solflare / Backpack on Solana, MetaMask / any EVM wallet on Base via EIP-3009),
14
+ the `402 → sign → settle` flow, optional SIWX re-entry, client-side spending caps,
15
+ and a receipt. It ships a **server** checkout adapter for the Solana rail
16
+ (`./server`, `./server/express`, `./server/vercel`) and a **React** wrapper
17
+ (`./react`) — pick only the pieces you need. Vanilla JS, no bundler required.
9
18
 
10
19
  ```html
11
20
  <script type="module" src="https://unpkg.com/@three-ws/x402-payment-modal"></script>
@@ -41,17 +50,61 @@ throttle retries, and the receipt, so you ship a paid endpoint in minutes.
41
50
  - **Solana + EVM.** Phantom (Solana) and any injected EVM wallet (Base USDC
42
51
  via EIP-3009 `transferWithAuthorization`). The modal picks the right path from
43
52
  the 402 challenge.
44
- - **Pay in USDC _or_ THREE on Solana.** Offer both and the buyer gets a token
45
- picker pay in USDC, or in [$THREE](https://three.ws/three-token), the three.ws
46
- utility token, recognized on sight (symbol, decimals, branding). See
47
- [Accepting multiple Solana tokens](#accepting-multiple-solana-tokens-usdc--three).
53
+ - **USDC by default, multi-token if you want it.** USDC is the always-on default
54
+ on Solana. Optionally offer a second SPL token (the modal ships a built-in
55
+ `THREE` opt-in) and the buyer gets a token picker each token is recognized on
56
+ sight (symbol, decimals). See
57
+ [Accepting multiple Solana tokens](#accepting-multiple-solana-tokens).
48
58
  - **SIWX re-entry.** If a wallet already paid for a resource, it can sign back in
49
59
  instead of paying again. See [docs/siwx.md](docs/siwx.md).
50
60
  - **Spending caps.** Optional per-call / hourly / daily caps enforced in the
51
61
  browser. See [docs/spending-caps.md](docs/spending-caps.md).
52
- - **Themeable.** Light + automatic dark mode, all classes overridable. See
62
+ - **Themeable.** Light + automatic dark mode, a full `--x402-*` design-token
63
+ surface, runtime brand-matching, and a header logo. See
53
64
  [docs/theming.md](docs/theming.md).
54
- - **Accessible.** Focus management, `aria-modal`, keyboard `Esc` to close.
65
+ - **Accessible.** Focus trap + restore, `aria-modal`, `aria-live` step
66
+ announcements, `:focus-visible` rings, `prefers-reduced-motion`, keyboard `Esc`
67
+ to close.
68
+ - **Server + React adapters in the box.** Framework-agnostic Solana checkout
69
+ (`./server`) with Express and Vercel adapters, and a first-class React wrapper
70
+ (`./react`) — all behind separate export subpaths, all optional.
71
+
72
+ ---
73
+
74
+ ## Which x402 modal do I want?
75
+
76
+ This package has a lighter sibling, **[`@three-ws/x402-modal`](https://www.npmjs.com/package/@three-ws/x402-modal)**.
77
+ Both render a drop-in modal; choose by how much you need.
78
+
79
+ | | **`@three-ws/x402-payment-modal`** (this) | `@three-ws/x402-modal` |
80
+ | --- | --- | --- |
81
+ | Goal | Full checkout SDK: client **+ server + React** | Minimal client-only modal |
82
+ | Chains | Solana **and** EVM (Base/Arbitrum/Optimism) | Client modal, EVM-leaning |
83
+ | Solana server adapter | ✅ `./server` + Express + Vercel | ❌ bring your own |
84
+ | React wrapper | ✅ `./react` (`X402Button`, `useX402`) | ❌ |
85
+ | SIWX re-entry | ✅ | — |
86
+ | Client spending caps | ✅ per-call / hour / day | — |
87
+ | Multi-token picker (USDC + opt-in SPL) | ✅ | — |
88
+ | Builder-code (ERC-8021) echo | ✅ | — |
89
+ | Footprint | Larger (more surface) | Smaller, fewer knobs |
90
+
91
+ **Rule of thumb:** taking real Solana payments, want a React component, or need
92
+ caps/SIWX → use **this** package. Just need a tiny EVM paywall button and nothing
93
+ else → the lighter `@three-ws/x402-modal` is enough.
94
+
95
+ ## Package subpaths
96
+
97
+ Import only what you use — each subpath is independent and its peer deps are
98
+ optional.
99
+
100
+ | Subpath | Import | Needs |
101
+ | --- | --- | --- |
102
+ | `.` | `@three-ws/x402-payment-modal` | nothing (browser) |
103
+ | `./min` | `@three-ws/x402-payment-modal/min` | nothing — pre-minified bundle |
104
+ | `./server` | `@three-ws/x402-payment-modal/server` | `@solana/web3.js`, `@solana/spl-token` |
105
+ | `./server/express` | `@three-ws/x402-payment-modal/server/express` | + `express` |
106
+ | `./server/vercel` | `@three-ws/x402-payment-modal/server/vercel` | `@solana/web3.js`, `@solana/spl-token` |
107
+ | `./react` | `@three-ws/x402-payment-modal/react` | `react` |
55
108
 
56
109
  ---
57
110
 
@@ -73,15 +126,28 @@ npm install @three-ws/x402-payment-modal
73
126
  import { pay, configure } from '@three-ws/x402-payment-modal';
74
127
  ```
75
128
 
76
- For Solana payments you also run a tiny server endpoint — install the optional
77
- peer deps there:
129
+ ### Optional peer dependencies
130
+
131
+ The core client has **no runtime dependencies**. The other subpaths declare
132
+ *optional* peer deps — install them only when you use that subpath:
133
+
134
+ | You use… | Install |
135
+ | --- | --- |
136
+ | `./server` or `./server/vercel` (Solana checkout) | `@solana/web3.js @solana/spl-token` |
137
+ | `./server/express` | `@solana/web3.js @solana/spl-token express` |
138
+ | `./react` | `react` (you already have it) |
78
139
 
79
140
  ```bash
141
+ # Solana checkout server
80
142
  npm install @solana/web3.js @solana/spl-token
143
+ # …and express if you use the Express adapter
144
+ npm install express
81
145
  ```
82
146
 
83
147
  > EVM-only sites need **nothing** server-side: the wallet signs EIP-3009
84
- > typed-data entirely in the browser.
148
+ > typed-data entirely in the browser. The Solana/EVM crypto helpers used by the
149
+ > client are loaded lazily from a CDN **only** when a payment is attempted, so
150
+ > they are not in your bundle.
85
151
 
86
152
  ---
87
153
 
@@ -135,34 +201,34 @@ server.
135
201
 
136
202
  ---
137
203
 
138
- ## How it works
139
-
140
- ```
141
- click ──▶ GET/POST endpoint
142
-
143
- HTTP 402 { accepts: [ {network, amount, asset, payTo, ...} ] }
144
- ┌─────────────┐
145
- │ discover │ modal shows price + network
146
- └─────────────┘
147
-
148
-
149
- ┌─────────────┐
150
- │ connect │ Phantom (Solana) or EVM wallet (Base)
151
- └─────────────┘
152
-
153
- ┌────┴───────────────────────────────────────┐
154
- Solana ▼ EVM
155
- POST /api/x402-checkout?action=prepare sign EIP-3009
156
- Phantom signTransaction transferWithAuthorization
157
- POST /api/x402-checkout?action=encode (browser only — no server)
158
- └────┬───────────────────────────────────────┘
159
-
160
- ┌─────────────┐
161
- │ verify │ retry endpoint with `X-PAYMENT: <base64>`
162
- └─────────────┘ (auto-retries once on a 429 throttle)
163
-
164
- ▼ 200 + `X-PAYMENT-RESPONSE` settlement header
165
- receipt + result
204
+ ## Architecture
205
+
206
+ Three independent pieces, each optional except the client:
207
+
208
+ - **Client** (`.` / `./min`) — renders the modal, detects wallets, drives the
209
+ `402 sign settle` flow. Zero runtime deps.
210
+ - **Server adapter** (`./server*`) — Solana only. Builds the SPL transfer Phantom
211
+ signs and wraps it into the `X-PAYMENT` envelope. EVM never touches it.
212
+ - **React wrapper** (`./react`) — `X402Button` + `useX402`, SSR-safe.
213
+
214
+ ```mermaid
215
+ flowchart TD
216
+ A[User clicks pay] --> B[Client: GET/POST endpoint]
217
+ B --> C{HTTP 402 + accepts?}
218
+ C -->|no, 200| X[Error: not an x402 endpoint]
219
+ C -->|yes| D[discover: show price + network]
220
+ D --> E[connect: pick & connect wallet]
221
+ E --> F{network}
222
+ F -->|EVM| G[sign EIP-3009 typed-data\nbrowser only, no server]
223
+ F -->|Solana| H[POST ./server ?action=prepare]
224
+ H --> I[Phantom signs the v0 tx]
225
+ I --> J[POST ./server ?action=encode → X-PAYMENT]
226
+ G --> K[verify: retry with X-PAYMENT]
227
+ J --> K
228
+ K --> L{settled?}
229
+ L -->|429 throttle| K
230
+ L -->|2xx| M[receipt + result resolve PayResult]
231
+ L -->|4xx/5xx| N[error + roll back any cap reservation]
166
232
  ```
167
233
 
168
234
  Full walkthrough in [docs/architecture.md](docs/architecture.md).
@@ -235,12 +301,18 @@ configure({
235
301
  | `checkoutOrigin` | `data-x402-checkout-origin` | the script's own origin |
236
302
  | `checkoutPath` | `data-x402-checkout-path` | `/api/x402-checkout` |
237
303
  | `footerNote` | `data-x402-footer-note` | `x402 · onchain settled` |
238
- | `brand.name` / `brand.url` | `data-x402-brand-name` / `-brand-url` | `three.ws` |
239
- | `builderCode.wallet` / `.service` | `data-x402-builder-wallet` / `-builder-service` | three.ws codes |
304
+ | `brand.name` / `brand.url` | `data-x402-brand-name` / `-brand-url` | `null` (footer link hidden) |
305
+ | `brand.logo` | | `null` (no header logo) |
306
+ | `theme` | — | `'auto'` (follow the OS) |
307
+ | `cssVars` | — | `null` (use built-in tokens) |
308
+ | `builderCode.wallet` / `.service` | `data-x402-builder-wallet` / `-builder-service` | `''` (no self-attribution) |
240
309
  | `esm.solanaWeb3` / `esm.nobleHashesSha3` | — | pinned esm.sh URLs |
241
310
 
242
- Repoint `esm.*` at a self-hosted mirror if a strict Content-Security-Policy
243
- blocks `esm.sh`. (The EVM/Base path needs no third-party code at all.)
311
+ `theme` is `'auto' | 'light' | 'dark'`; `cssVars` is a flat map of `--x402-*`
312
+ design tokens (e.g. `{ '--x402-accent': '#ff5c00', '--x402-radius': '8px' }`) for
313
+ runtime brand-matching — see [docs/theming.md](docs/theming.md). Repoint `esm.*`
314
+ at a self-hosted mirror if a strict Content-Security-Policy blocks `esm.sh`. (The
315
+ EVM/Base path needs no third-party code at all.)
244
316
 
245
317
  ---
246
318
 
@@ -268,20 +340,62 @@ app.listen(3000);
268
340
  export { default } from '@three-ws/x402-payment-modal/server/vercel';
269
341
  ```
270
342
 
271
- Lower-level helpers (`prepareSolanaCheckout`, `encodeX402Payment`,
272
- `handleCheckout`, `CheckoutError`) are exported from
343
+ Pass `rpcUrls: [...]` (an ordered list, tried with failover) instead of a single
344
+ `rpcUrl` for production — the public RPC is rate-limited and warns once at
345
+ startup. Lower-level helpers (`prepareSolanaCheckout`, `encodeX402Payment`,
346
+ `handleCheckout`, `CheckoutError`, `solanaAccept`) are exported from
273
347
  `@three-ws/x402-payment-modal/server`. Full guide:
274
348
  [docs/server-setup.md](docs/server-setup.md).
275
349
 
276
350
  ---
277
351
 
278
- ## Accepting multiple Solana tokens (USDC + THREE)
352
+ ## React
353
+
354
+ The `./react` subpath ships a hook and a button. The browser-only core is
355
+ dynamically imported on first use, so both are **SSR-safe** — nothing runs during
356
+ render or on the server.
357
+
358
+ ```jsx
359
+ import { X402Button, useX402 } from '@three-ws/x402-payment-modal/react';
360
+
361
+ // Drop-in button:
362
+ <X402Button
363
+ endpoint="/api/paid/summarize"
364
+ method="POST"
365
+ body={{ url: 'https://en.wikipedia.org/wiki/x402' }}
366
+ merchant="Acme"
367
+ action="Summarize"
368
+ label="Summarize for $0.01"
369
+ onResult={(r) => console.log('paid', r.payment)}
370
+ onError={(e) => console.error(e)}
371
+ />;
372
+
373
+ // …or drive it yourself with the hook:
374
+ function Buy() {
375
+ const { pay, isPaying, result, error } = useX402({ merchant: 'Acme' });
376
+ return (
377
+ <button disabled={isPaying} onClick={() => pay({ endpoint: '/api/paid/summarize' })}>
378
+ {isPaying ? 'Processing…' : 'Pay'}
379
+ </button>
380
+ );
381
+ }
382
+ ```
383
+
384
+ `useX402(defaults)` returns `{ pay, status, result, error, reset, isPaying }`
385
+ where `status` is `idle | paying | done | error`. Full reference:
386
+ [docs/react.md](docs/react.md).
387
+
388
+ ---
279
389
 
280
- An x402 challenge can list more than one accepted payment. List a USDC accept
281
- **and** a [$THREE](https://three.ws/three-token) accept on Solana, and the modal
282
- renders a token picker: the buyer chooses which to pay in, the headline price and
283
- the transaction follow the choice. No client wiring the checkout endpoint
284
- already builds an SPL transfer for whatever mint the chosen accept names.
390
+ ## Accepting multiple Solana tokens
391
+
392
+ USDC is the default settlement asset, but an x402 challenge can list more than one
393
+ accepted payment. List a USDC accept **and** a second SPL-token accept on Solana,
394
+ and the modal renders a token picker: the buyer chooses which to pay in, the
395
+ headline price and the transaction follow the choice. No client wiring — the
396
+ checkout endpoint already builds an SPL transfer for whatever mint the chosen
397
+ accept names. The package ships a built-in `THREE` opt-in token as a ready
398
+ example, but any mint works.
285
399
 
286
400
  Build the accepts with the `solanaAccept` helper (no hardcoded mints):
287
401
 
@@ -304,9 +418,10 @@ and emits a spec-shaped `accept`. The constants `THREE_MINT`, `USDC_MINT_SOLANA`
304
418
  and `WELL_KNOWN_SOLANA_TOKENS` are exported too. On the client, the same mints
305
419
  are exposed at `window.X402.tokens` for inline merchants.
306
420
 
307
- > **THREE is a utility token, not a stablecoin.** Its price floats, so the modal
308
- > can't dollar-denominate it for browser-side spending caps — enforce caps for
309
- > THREE server-side. USDC caps work in the browser as usual.
421
+ > **Non-stable tokens float against the dollar.** The modal can't
422
+ > dollar-denominate a floating-price token for browser-side spending caps — so
423
+ > enforce caps for any non-stable token (like `THREE`) server-side. USDC and
424
+ > other stablecoin caps work in the browser as usual.
310
425
 
311
426
  ---
312
427
 
@@ -315,10 +430,13 @@ are exposed at `window.X402.tokens` for inline merchants.
315
430
  - [Architecture](docs/architecture.md) — the full payment lifecycle.
316
431
  - [Client API reference](docs/api-reference.md)
317
432
  - [Server setup](docs/server-setup.md)
433
+ - [React reference](docs/react.md) — `X402Button` + `useX402`.
318
434
  - [Theming](docs/theming.md)
319
435
  - [SIWX re-entry](docs/siwx.md)
320
436
  - [Spending caps](docs/spending-caps.md)
437
+ - [Examples](docs/EXAMPLES.md) — runnable plain-HTML, React, and Express samples.
321
438
  - [Tutorial](TUTORIAL.md) — build a paid endpoint end-to-end.
439
+ - [Contributing](CONTRIBUTING.md)
322
440
 
323
441
  ---
324
442
 
@@ -334,14 +452,71 @@ Modern evergreen browsers (ES2020, `BigInt`, dynamic `import()`, `fetch`,
334
452
  - Payments are signed in the user's wallet — this package never sees a private
335
453
  key. The Solana checkout server only builds an unsigned transaction and base64-
336
454
  wraps the user-signed one; it cannot move funds.
455
+ - **SIWX** re-entry is a gasless CAIP-122 signature, never a transfer. The client
456
+ detects the offer, signs, and submits the proof; the **server** must verify the
457
+ nonce/domain/expiry/signature and check the entitlement — see
458
+ [docs/siwx.md](docs/siwx.md).
337
459
  - Spending caps are a **client-side guardrail**, not a security boundary — a user
338
460
  can clear `localStorage`. Enforce real limits server-side.
339
- - Pin the script to a version or self-host it for production.
461
+ - Pin the script to a version or self-host it for production (the lazily-loaded
462
+ crypto helpers can be repointed at a mirror via `configure({ esm })` to satisfy
463
+ a strict CSP).
464
+
465
+ ## FAQ & troubleshooting
466
+
467
+ **The modal opens but says "Couldn't confirm the price."**
468
+ Discovery hit a response that wasn't an x402 challenge. The endpoint must answer
469
+ the *unpaid* request with HTTP `402` (or an MCP-style `401`) carrying an `accepts`
470
+ array — in the JSON body or a base64 `payment-required` header. A `200` means the
471
+ endpoint isn't paywalled; the modal surfaces that instead of silently succeeding.
472
+
473
+ **Do I need a server?**
474
+ Only for **Solana**. EVM (Base/Arbitrum/Optimism USDC) signs EIP-3009 typed-data
475
+ entirely in the browser. For Solana, mount `./server` so the modal can build the
476
+ SPL transfer Phantom signs — see [docs/server-setup.md](docs/server-setup.md).
477
+
478
+ **Solana payments fail with an RPC error under load.**
479
+ You're on the public RPC (it warns once at startup). Pass a dedicated endpoint —
480
+ `x402CheckoutRouter({ rpcUrls: ['https://your-helius-url', ...] })` — and the
481
+ adapter rotates across the list on transient failures.
482
+
483
+ **A Token-2022 mint (e.g. THREE) wouldn't settle in an older version.**
484
+ Fixed in 1.2.0 — the server now detects each mint's owning program (legacy SPL
485
+ Token vs Token-2022) and derives ATAs and `transferChecked` against the right one.
486
+
487
+ **`esm.sh` is blocked by my Content-Security-Policy.**
488
+ The client lazy-loads `@solana/web3.js` and `@noble/hashes` from a CDN only when a
489
+ payment is attempted. Repoint them at a self-hosted mirror:
490
+ `configure({ esm: { solanaWeb3: '/vendor/solana-web3.js', nobleHashesSha3: '/vendor/noble-sha3.js' } })`.
491
+ EVM-only sites load no third-party code at all.
492
+
493
+ **Why did `pay()` reject?**
494
+ A user dismissing the modal rejects with `err.code === 'cancelled'` — treat it as
495
+ a no-op, not a failure. Any other rejection is a real error. The declarative
496
+ auto-binder swallows cancellation and only fires `x402:error` for genuine failures.
497
+
498
+ **My spending caps don't apply to a non-stablecoin.**
499
+ Browser caps are denominated in micro-USD and the dependency-free client doesn't
500
+ fetch live prices, so caps only bind stablecoins (USDC/USDT/DAI). Enforce caps for
501
+ floating-price tokens server-side — see [docs/spending-caps.md](docs/spending-caps.md).
502
+
503
+ **Can the user pick which token to pay in?**
504
+ Yes — if the 402 challenge lists more than one Solana `accept`, the modal renders
505
+ a token picker. See [Accepting multiple Solana tokens](#accepting-multiple-solana-tokens).
506
+
507
+ ---
508
+
509
+ ## Related packages
510
+
511
+ - **[`@three-ws/x402-modal`](https://www.npmjs.com/package/@three-ws/x402-modal)** —
512
+ the lighter client-only sibling. See [the comparison above](#which-x402-modal-do-i-want).
513
+ - **[x402 protocol](https://x402.org)** — the open "HTTP 402 Payment Required"
514
+ micropayment standard this package implements (v2 envelope).
340
515
 
341
516
  ## License
342
517
 
343
- [Apache-2.0](LICENSE) © three.ws
518
+ Proprietary — Copyright (c) 2026 nirholas. All Rights Reserved. Unauthorized use, copying, modification, or distribution is prohibited. See [LICENSE](./LICENSE).
344
519
 
345
- > Maintained inside the [three.ws](https://three.ws) monorepo and published as a
346
- > standalone package. Issues and PRs:
347
- > <https://github.com/nirholas/three.ws/issues>.
520
+ > Standalone package. Browser client is dependency-free; server/react peer deps
521
+ > are optional. Issues and PRs welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) and
522
+ > <https://github.com/nirholas/x402-payment-modal/issues>.
package/dist/index.d.ts CHANGED
@@ -58,12 +58,17 @@ export interface PayResult {
58
58
  };
59
59
  }
60
60
 
61
- /** Branding shown in the modal footer. */
61
+ /** Branding shown in the modal header/footer. */
62
62
  export interface BrandConfig {
63
63
  name?: string;
64
64
  url?: string;
65
+ /** Logo shown in the modal header (URL). */
66
+ logo?: string;
65
67
  }
66
68
 
69
+ /** Forced color scheme. `auto` (default) follows the OS preference. */
70
+ export type X402Theme = 'auto' | 'light' | 'dark';
71
+
67
72
  /** ERC-8021 builder-code self-attribution echoed when the 402 challenge declares one. */
68
73
  export interface BuilderCodeConfig {
69
74
  wallet?: string;
@@ -84,6 +89,10 @@ export interface X402Config {
84
89
  footerNote?: string;
85
90
  builderCode?: BuilderCodeConfig;
86
91
  esm?: EsmConfig;
92
+ /** Force the modal's color scheme. Default `auto` (follow the OS). */
93
+ theme?: X402Theme;
94
+ /** Flat map of `--x402-*` design tokens to brand-match at runtime, e.g. `{ '--x402-accent': '#ff5c00' }`. */
95
+ cssVars?: Record<string, string> | null;
87
96
  }
88
97
 
89
98
  /**
@@ -107,8 +116,10 @@ export const version: string;
107
116
 
108
117
  /** Solana USDC mint (mainnet). */
109
118
  export const USDC_MINT_SOLANA: string;
110
- /** $THREE — the three.ws utility token mint. Recognized by the modal so a 402
111
- * `accept` using it renders as THREE without merchant-supplied metadata. */
119
+ /** THREE — an optional opt-in SPL token mint recognized by the modal so a 402
120
+ * `accept` using it renders as THREE without merchant-supplied metadata. USDC
121
+ * remains the always-on default; this token is used only when an endpoint
122
+ * chooses to accept it. */
112
123
  export const THREE_MINT: string;
113
124
 
114
125
  export interface KnownSolanaToken {