@dexterai/x402 6.0.0-rc.2 → 6.0.0-rc.3

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/CHANGELOG.md ADDED
@@ -0,0 +1,740 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [6.0.0-rc.3] - 2026-08-17
11
+
12
+ ### Added
13
+
14
+ - Seller-owned delivery accounting is now restart- and takeover-safe. Every
15
+ channel lease carries an unguessable owner token plus a monotonically
16
+ increasing fence, and every ledger write, delete, renewal, and release is
17
+ conditional on that exact lease generation. The SSE meter writes each
18
+ charge to the fenced durable ledger before the corresponding service may be
19
+ sent; a crash can conservatively over-account an unsent unit, but cannot
20
+ erase delivered service and re-grant the same signed budget after restart.
21
+ - `RedisChannelLedger` declares explicit restart/multi-instance capabilities,
22
+ performs authoritative reads on the Redis primary, renews live leases, and
23
+ fails closed on store or lease loss. Its `restartSafe` capability requires
24
+ both the exact AOF-always/no-data-loss/noeviction/dedicated-instance
25
+ durability attestation and
26
+ `writerCutover: 'all-legacy-writers-stopped'`.
27
+
28
+ ### Changed
29
+
30
+ - Seller admission revalidates the current durable voucher registration,
31
+ cumulative amount, and reservation proof after acquiring the channel lease.
32
+ Delayed requests therefore cannot overwrite a newer process's accepted
33
+ state. A response that disconnects during proof work cannot later acquire or
34
+ indefinitely renew a lease.
35
+ - Channel IDs are canonical lowercase 64-character hex at seller admission.
36
+ Every bundled ledger adapter also rejects noncanonical IDs before acquiring
37
+ a lock or touching storage, so direct adapter calls cannot recreate aliases.
38
+ - `ledgerSafetyMode` must be explicit unless `NODE_ENV` is exactly `test` or
39
+ `development`. `production-single-instance` requires durable restart-safe
40
+ state plus the explicit channel-alias cutover; `production-multi-instance`
41
+ additionally requires cross-process atomic fencing. There is no production
42
+ fallback to memory or inferred single-process topology.
43
+
44
+ ### Breaking
45
+
46
+ - Custom `ChannelLedger` adapters must expose `capabilities`, use the new
47
+ `ChannelLease { ownerToken, fence, heldUntilUnixMs }`, accept that lease on
48
+ every `set`, `update`, and `delete`, and implement conditional
49
+ `tryAcquireLease`, `renewLease`, and `releaseLease`. Adapters must reject
50
+ stale owner/fence mutations instead of silently applying them, reject every
51
+ non-lowercase/non-64-hex channel ID before locking or storage access, and
52
+ declare `canonicalChannelIds` only after historical aliases are migrated or
53
+ the durable store is proven empty.
54
+ - `SseMeter.charge()` is asynchronous write-ahead accounting and must be
55
+ awaited before `send()`. Concurrent charges are serialized against the
56
+ signed cap; `send()` fails while a charge commit is pending.
57
+
58
+ ### Seller-ledger upgrade and Redis keyspace migration
59
+
60
+ - **Canonical channel-ID cutover is mandatory for every durable adapter.** The
61
+ voucher signature covers the decoded 32 channel bytes, so historical SDKs
62
+ could persist the same signed channel under multiple case spellings. Before
63
+ setting `channelIdCutover: 'legacy-case-aliases-migrated-or-empty'`, stop all
64
+ seller writers and enumerate every ledger, lease, and fence key/file. Group
65
+ records by lowercase channel ID; wait for and remove every alias lease;
66
+ require exact session/public-key/registration compatibility (otherwise
67
+ quarantine the entire group for manual review); retain the highest valid
68
+ signed voucher by cumulative then sequence; **sum** delivered cumulative
69
+ across distinct aliases; take the maximum crystallized and gate-refused
70
+ watermarks; and publish the lowercase fence at least one greater than the
71
+ maximum alias fence. Overcounting a copied duplicate is safer than re-granting
72
+ delivered service. Verify the canonical record before deleting every alias
73
+ ledger/lease/fence. A brand-new proven-empty store may acknowledge the same
74
+ cutover without migration. Production middleware refuses File, Redis, or a
75
+ custom adapter until `canonicalChannelIds` attests this invariant.
76
+
77
+ - The default Redis layout remains `legacy-v0` (`<prefix>ledger:<channelId>`,
78
+ `<prefix>lease:<channelId>`, `<prefix>fence:<channelId>`) so upgrading does
79
+ not hide existing ledger state or split leases during rollout. Before
80
+ asserting `writerCutover`, stop every pre-fencing seller process. A raw UUID
81
+ lease written by an older process is respected until it expires; the new
82
+ writer never renews or releases that foreign lease.
83
+ - `cluster-v1` is opt-in and never supports a rolling mixed-layout deploy. Stop
84
+ all seller writers; wait for every legacy lease to expire; copy every legacy
85
+ canonical lowercase ledger record and fence counter to the corresponding hash-tagged
86
+ `<prefix>{<channelId>}:ledger|fence` keys (preserving or increasing each
87
+ fence); verify the copies; then remove all legacy ledger, lease, and fence
88
+ keys. Only after that stop-the-world procedure may the service start with
89
+ `keyLayout: 'cluster-v1'`,
90
+ `keyspaceCutover: 'legacy-state-migrated-or-empty'`, the writer cutover, and
91
+ the channel-ID cutover acknowledgements. Runtime admission rejects any
92
+ legacy ledger, lease, or fence remnant rather than treating the new keyspace
93
+ as empty.
94
+
95
+ ## [6.0.0-rc.2] - 2026-08-16
96
+
97
+ ### Fixed
98
+
99
+ - Native Tab V2 buyer and seller admission now completes at Solana `confirmed` after independently fetching the exact successful reservation transaction, verifying its Dexter-authority-signed voucher-binding Memo, and reading coherent Vault/SessionAccount state anchored to that transaction slot. Paid requests no longer wait for finalization; processed-only evidence still fails closed.
100
+ - V2 seller channel binding can no longer be poisoned by a scope-rejected first request, a concurrent competing registration, lease refusal, or a downstream durable-ledger failure.
101
+
102
+ ### Unchanged safety boundaries
103
+
104
+ - Rollback, failed-transaction retirement, blockhash-expiry adjudication, close, and revoke retain their finalized/background boundaries. A later finalization preserves the already-issued confirmed receipt byte-for-byte.
105
+
106
+ ## [6.0.0-rc.1] - 2026-08-16
107
+
108
+ ### Fixed
109
+
110
+ - Native Tab V2 sellers now require the complete reservation receipt in the existing `X-Tab-Voucher` envelope and independently prove the exact finalized `settle_voucher` transaction plus its Dexter-authority-signed voucher-binding Memo before delivery. Missing, malformed, confirmed-only, replayed, consumed, wrong-version, stale-registration, and same-amount substituted proofs fail closed.
111
+ - V2 seller admission re-reads the exact finalized SessionAccount PDA for every voucher and requires `currentOutstanding` to equal only the uncovered voucher increment above the authoritative settled/crystallized frontier.
112
+ - V2 terminal settlement sends the exact final reservation increment as `attemptedAmount`, rather than reconstructing it from the tab's lifetime cumulative amount.
113
+ - The CommonJS `@dexterai/x402/server` entrypoint now bundles the ESM-only sponsored-access runtime constant, so a clean `require()` consumer no longer fails during module loading.
114
+
115
+ ### Changed
116
+
117
+ - The complete verified `FinalVoucherV2ReservationReceipt` now survives every buyer route (`openTab`, `tabFromGrant`, `stream`, and `payAndFetch`) inside the existing voucher envelope. Provider-local lifecycle metadata remains non-authoritative; finalized transaction, Memo, and coherent post-state evidence authorize seller delivery.
118
+ - `@dexterai/vault` is pinned exactly to `0.43.2` for both peer and development use, including its corrected optional-account sentinel encoding.
119
+
120
+ ## [6.0.0-rc.0] - 2026-08-16
121
+
122
+ ### Added
123
+
124
+ - Context-bound FINAL V2 vouchers bind the Vault program, Vault, SessionAccount, seller, session generation, channel, cumulative amount, and sequence into the signed voucher.
125
+ - FINAL V2 release now requires a complete versioned reservation receipt, finality for the exact Solana reservation transaction, and an independent finalized post-state verification. A merely confirmed transaction and the former `{ armed: true }` acknowledgement are rejected.
126
+ - Live-session replacement uses one guard-bound V7 passkey ceremony and one atomic replacement transaction.
127
+ - Tab voucher signing and close share one fail-fast local operation gate, preventing two concurrent calls from reserving the same sequence/cumulative frontier.
128
+
129
+ ### Changed
130
+
131
+ - `@dexterai/vault` is pinned to the published `0.43.1` release. Close/revoke now signs the 252-byte V3 message over one coherent, authoritative Vault + SessionAccount snapshot, including all live meters and `pendingVoucherCount`.
132
+ - `VaultAdapter.sessionVoucherVersion` and `Tab.voucherVersion` are required. Buyer-side V1 open and reconstruction now fail with `HistoricalV1MigrationRequiredError`; seller-side verification remains available only for already-issued V1 vouchers. Missing, invalid, or unsupported generations fail closed.
133
+ - The Node P-256 signer no longer guesses a revoke authorization nonce. Revoke requires an authoritative `resolveAuthorizationContext`; unknown operations sign nothing unless the caller explicitly opts into the deprecated legacy operation hash.
134
+
135
+ ### Breaking
136
+
137
+ - V2 reservation callbacks now return the full `FinalVoucherV2ReservationReceipt`; boolean acknowledgements are invalid.
138
+ - V2 adapters must implement `verifyFinalVoucherV2Reservation`.
139
+ - Node.js 22 or newer is required. Node 18 and Node 20 are end-of-life, and the current Solana dependency graph relies on modern ESM loading unavailable on Node 18.
140
+ - This release is a major-version candidate because its reservation, revocation, and adapter contracts are intentionally incompatible with the abandoned 5.4.3 candidate.
141
+
142
+ ### Compatibility notice
143
+
144
+ - Public `@dexterai/x402@5.4.2` contains the historical V1 Native Tab rail. Its broad Vault peer range admits `0.43.1`, but its close wire predates Vault 0.43.x exact-state revocation. Existing 5.4.2 consumers should not upgrade Vault to 0.43.x until they move to this v6 line.
145
+
146
+ ### Added (buyer, K-T4e)
147
+ - **`openTab` / `authorizeSession` are now live-session-aware.** The session PDA is keyed by (vault, counterparty), so re-opening a tab against a seller you already hold a LIVE session with resolves to the same PDA. A live target either throws the typed `LiveSessionExistsError` (default — carrying spent, crystallized, outstanding, and frontier evidence) or, with `onLiveSession: 'replace'`, uses one guard-nonce-bound V7 passkey ceremony and one atomic `[secp(replace), replace_session_key_v1]` transaction. The buyer is never left sessionless mid-flow. The v0 + address-lookup-table transport manages priority fees, expiry rebroadcast, and ALT deactivation.
148
+ - **BEHAVIOR CHANGE:** pre-5.3.1, `openTab` against a live same-seller session silently overwrote it on-chain — stranding any of the old session's signed-but-unsettled vouchers. That silent path is gone: you get the typed error (settle the old tab first, or acknowledge with `onLiveSession: 'replace'`).
149
+ - `@dexterai/vault` peer/dev dependency raised `>=0.20.0` → `>=0.34.0` (the atomic compose primitive lives there).
150
+
151
+ ### Changed (seller, K-T3)
152
+ - **`lockCadence` demotes to advisory.** The crystallization cadence is becoming facilitator-owned: a server-side engine that fires locks at the operator's on-chain intent knob for every seller, whatever the client-side threshold says. This setting defers to that engine as it rolls out — until it is live on your facilitator, `lockCadence` is still your mid-stream protection, so don't loosen it on the strength of this note. It remains the seller's own lock-more-aggressively dial either way.
153
+ - **`below_lock_cadence` gate refusals are handled first-class.** A cadence-gated facilitator may refuse sub-threshold seller-initiated `/tab/lock` posts with `below_lock_cadence`. The SDK classifies this as benign (console.warn, never console.error — a facilitator only gates spans its own cadence engine owns) and records a **gate-refused watermark** (`ChannelLedgerEntry.gateRefusedCumulativeAtomic`, persisted) so the identical refused span is never re-attempted on subsequent deliveries or response closes — previously each would have retry-stormed the gate. A new signed voucher (higher cumulative) always re-attempts; a landed lock clears the watermark.
154
+
155
+ ## [4.0.0] - 2026-06-17
156
+
157
+ ### Removed (BREAKING)
158
+
159
+ The v1-era public exports are gone, completing the 3.9.0 deprecation. The payment engine is unchanged — `payAndFetch` (client) and `x402Middleware` (server) have been the canonical paths since 3.x and cover everything the removed helpers did.
160
+
161
+ - **client:** `createX402Client`, `wrapFetch` (and the `X402Client`, `X402ClientConfig`, `WrapFetchOptions` types). → `payAndFetch(url, init, wallets)`.
162
+ - **server:** `x402AccessPass`, `x402BrowserSupport`, `escapeHtml`. → `x402Middleware`.
163
+ - **server:** `createDynamicPricing`, `formatPricing`. → compute the price per request in your handler and pass it to `x402Middleware`.
164
+ - **server:** the entire token/model pricing surface — `createTokenPricing`, `countTokens`, `getAvailableModels`, `isValidModel`, `formatTokenPricing`, `MODEL_PRICING`, `MODEL_REGISTRY`, `MODEL_PRICING_MAP`, `getModel`, `findModel`, `isValidModelId`, `getAvailableModelIds`, `getModelsByTier`, `getModelsByFamily`, `getActiveModels`, `getTextModels`, `getCheapestModel`, `estimateCost`, `formatModelPricing` (and their types). This wrapped a hardcoded January-2026 OpenAI snapshot that goes stale fast; price requests with your model provider's live API and pass the amount to `x402Middleware`.
165
+ - **server:** `stripePayTo`. → a `PayToProvider` map on `x402Middleware`. (`getStripeProviderNetwork` stays internal.)
166
+ - **react:** `useAccessPass`. → `useX402Payment`.
167
+
168
+ ### Notes
169
+
170
+ - **EVM one-shot pay-per-call is unchanged.** `payAndFetch` remains the live EVM path until Tabs reaches EVM. The `createX402Client` / `wrapFetch` implementations are retained internally — they still power `payAndFetch`, the v2 strategy, and `useX402Payment`; only their public *exports* were removed.
171
+ - To stay on the old surface, pin `@dexterai/x402@^3`.
172
+
173
+ ## [3.9.0] - 2026-05-21
174
+
175
+ ### Documentation
176
+ - **README restructured to lead with the canonical paths.** The Quick Start now opens with `payAndFetch` (the version-agnostic 2026+ client) instead of the deprecated `wrapFetch`. New "Discovery (bazaar extension)" section documents the 3.8.0 `bazaarExtension` / `declareDiscoveryExtension` API, which had shipped but was undocumented. Sponsored Access reframed around the MCP-agent reality it already serves. The v1-era pricing helpers (Dynamic Pricing, Token Pricing, Access Pass, Stripe) collapsed from ~300 lines of feature sections into a single "Legacy capabilities" table with migration targets. Stale marketplace counts removed. README is ~40% shorter.
177
+
178
+ ### Fixed
179
+ - **`PayResult` no longer reports a phantom network on unpaid responses.** When `payAndFetch` hits an endpoint that returns 200 (or any non-402) directly, the result is now `{ ok: true, paid: false, response }` instead of `{ ok: true, response, amountPaid: '0', network: { caip2: '', bare: '', family: 'evm' } }`. The old placeholder poisoned any downstream analytics that grouped by network — every free-endpoint hit registered as an empty-CAIP-2 EVM payment. The `ok: true` variant is now discriminated by `paid: true | false`; callers should narrow on `paid` before reading `network` / `amountPaid` / `txSignature`. Strategy implementations (v1, v2) also set `paid: true` on their success path. (Source: `payment/dispatcher.ts:104` had a `// not-applicable placeholder` self-flag that finally got the type-level fix it was waiting for.)
180
+ - **`payAndFetch` no longer reports `timeout` on a payment that may have settled.** Previously a single 15s deadline governed the whole paid call — both the on-chain settlement and the wait for the merchant's response. A merchant slower than 15s (research / scout / agent endpoints routinely are) had its payment settled, then the abort fired and `payAndFetch` returned `{ ok: false, reason: 'timeout' }` — which reads as "safe to retry" and caused a silent double-charge. The timeout is now two-phase: a short pre-payment deadline (`timeoutMs`, default 15000) covers the unpaid probe and build/sign; a long post-payment deadline (new `responseTimeoutMs`, default 120000) covers the wait for the merchant's response. A pre-payment abort still yields `reason: 'timeout'` (no money moved, safe to retry). A post-payment abort yields the new `reason: 'payment_unconfirmed'` — the payment authorization was sent and may have settled on-chain, so a consumer must NOT blind-retry; the `detail` field spells this out. The `ok: true; paid: true` variant's `response` is now `Response | undefined`. Both v1 and v2 strategies are fixed. (Reported via a bake-off audit — see `FINDINGS-pay-timeout-double-charge-2026-05-21.md`.)
181
+ - **`payAndFetch` confirms settlement on-chain after a post-payment timeout.** When the merchant never responds, the SDK now asks the chain directly whether the payment settled, rather than always reporting `payment_unconfirmed`. EVM EIP-3009 payments (the default `exact` scheme) are confirmed via the token contract's `authorizationState(authorizer, nonce)` view — the nonce is the exact value the SDK generated, so this is a definitive yes/no. EVM Permit2 payments check `Permit2.nonceBitmap`. Solana payments scan recent signatures on the merchant's destination token account for a matching transfer, bounded by the transaction's blockhash validity. When the chain confirms settlement, the result is upgraded to `{ ok: true, paid: true, response: undefined, txSignature }` — a confirmed-but-unanswered payment, so the caller knows it paid and does not retry. When confirmation cannot be performed (RPC failure, or a scheme with no on-chain check such as EVM `exact-approval`), the result stays `payment_unconfirmed` with an explanatory `detail`. New `ChainAdapter.confirmSettlement` method and `SettlementProbe` type.
182
+
183
+ ### Deprecated
184
+
185
+ Internal hygiene pass ahead of 4.0 and 5.0. No runtime behavior changes — every symbol below still works exactly as before. JSDoc `@deprecated` markers now surface editor warnings so consumers can migrate ahead of the removal releases.
186
+
187
+ **Slated for removal in 4.0:**
188
+
189
+ - `x402AccessPass`, `X402AccessPassConfig`, `X402AccessPassRequest` (`@dexterai/x402/server`) — use per-request `x402Middleware` with `payAndFetch` clients.
190
+ - `useAccessPass`, `UseAccessPassConfig`, `UseAccessPassReturn` (`@dexterai/x402/react`) — use `useX402Payment` for per-request payments.
191
+ - `createDynamicPricing`, `formatPricing`, `DynamicPricingConfig`, `DynamicPricing`, `PriceQuote` (`@dexterai/x402/server`) — compute the price per request in your handler and pass it to `x402Middleware`.
192
+ - `createTokenPricing`, `countTokens`, `getAvailableModels`, `isValidModel`, `formatTokenPricing`, `MODEL_PRICING`, `TokenPricingConfig`, `TokenPricing`, `TokenPriceQuote`, `ModelPricing` (`@dexterai/x402/server`) — price requests with your model provider's live API and pass the amount to `x402Middleware`.
193
+ - `MODEL_REGISTRY`, `MODEL_PRICING_MAP`, `getModel`, `findModel`, `isValidModelId`, `getAvailableModelIds`, `getModelsByTier`, `getModelsByFamily`, `getActiveModels`, `getTextModels`, `getCheapestModel`, `estimateCost`, `formatModelPricing`, and the related types (`@dexterai/x402/server`) — January 2026 hardcoded snapshot; goes stale fast.
194
+ - `x402BrowserSupport`, `X402BrowserSupportConfig` (`@dexterai/x402/server`) — no replacement; build a custom paywall page if needed. (`escapeHtml` stays.)
195
+ - `stripePayTo`, `StripePayToConfig`, `getStripeProviderNetwork` (`@dexterai/x402/server`) — integrate Stripe at the application layer if still needed. The Stripe-network check in `x402Middleware` is removed alongside this in 4.0.
196
+
197
+ **Slated for removal in 5.0** (longer migration window because of real consumers):
198
+
199
+ - `createX402Client`, `X402Client`, `X402ClientConfig` (`@dexterai/x402/client`) — use `payAndFetch` instead. Migration: `client.fetch(url)` → `(await payAndFetch(url, undefined, wallets)).response`.
200
+ - `wrapFetch`, `WrapFetchOptions` (`@dexterai/x402/client`) — use `payAndFetch` with a wallet from `createKeypairWallet` / `createEvmKeypairWallet`.
201
+
202
+ `getPaymentReceipt` and `PaymentReceipt` (`@dexterai/x402/client`) are NOT deprecated.
203
+
204
+ ## [3.8.1] - 2026-05-20
205
+
206
+ ### Fixed
207
+ - **Bazaar `routeTemplate` now includes the mount path.** When `x402Middleware` is mounted on a sub-router via `app.use('/v1/agent', router)`, the bazaar extension previously emitted `routeTemplate: "/campaigns/:id"` instead of the full external-visible `"/v1/agent/campaigns/:id"`. The middleware now prepends `req.baseUrl` to `req.route.path`, so the emitted template matches the URL clients actually call. Without mounting, `req.baseUrl` is empty and behavior is unchanged.
208
+
209
+ ## [3.8.0] - 2026-05-20
210
+
211
+ Adds a resource-server extension system and the **bazaar discovery extension**, making `x402Middleware`-built 402 responses discoverable via the official x402 bazaar standard (`extensions.bazaar`). Fully backward compatible — calls without the new config fields emit a 402 byte-identical to today.
212
+
213
+ ### Added
214
+ - **`ResourceServerExtension` contract** (`@dexterai/x402/server`) — generic interface ported from the upstream `@x402/core` extension model. An extension namespaces its output under its own `key` inside `PaymentRequired.extensions`. Future extensions (offer-receipt, payment-identifier, etc.) plug in the same way.
215
+ - **Extension registry with failure isolation.** A throwing extension is caught, logged, and skipped — the 402 still goes out, just without that key. Never propagates, never 500s the payment path.
216
+ - **`bazaarExtension()` factory** — the first concrete extension. When configured, the 402 response carries the spec-compliant `extensions.bazaar` block: `{ info, schema, routeTemplate? }`, with `info.input` discriminated by HTTP method (GET/HEAD/DELETE → `queryParams`; POST/PUT/PATCH → `bodyType` + `body`) and `schema` as a JSON Schema (Draft 2020-12) validating `info`. Path-parameterized routes carry `info.input.pathParams` and a top-level `routeTemplate`.
217
+ - **`declareDiscoveryExtension(config)` helper** — wrap a route's discovery config as `{ bazaar: <config> }` for `x402Middleware`'s `declarations` map. Method may be omitted; the extension stamps the actual request method at 402 time.
218
+ - **`X402MiddlewareConfig.extensions`** + **`X402MiddlewareConfig.declarations`** — new optional fields. Pair them to opt routes into the registry. Example:
219
+ ```ts
220
+ x402Middleware({
221
+ payTo, network, amount, facilitatorUrl,
222
+ extensions: [bazaarExtension()],
223
+ declarations: {
224
+ ...declareDiscoveryExtension({
225
+ method: 'POST',
226
+ bodyType: 'json',
227
+ inputSchema: { properties: { amount: { type: 'string' } }, required: ['amount'] },
228
+ output: { example: { campaign: {} } },
229
+ }),
230
+ },
231
+ });
232
+ ```
233
+ - **`routeTemplate` validator** — enforces the bazaar spec's rules (non-empty, starts with `/`, allowed chars, no `..`/`://` even after percent-decoding). Invalid templates are dropped (`routeTemplate` is omitted), the 402 still ships.
234
+ - **End-to-end test** (`bazaar-middleware.test.ts`) — drives the full middleware with a mocked facilitator and asserts the 402 body carries `extensions.bazaar` when configured, and is `extensions`-free when not.
235
+ - **Public exports** from `@dexterai/x402/server`: `bazaarExtension`, `declareDiscoveryExtension`, and the types `ResourceServerExtension`, `PaymentRequiredContext`, `DiscoveryConfig`, `QueryDiscoveryConfig`, `BodyDiscoveryConfig`, `DiscoveryExtension`, `DeclareDiscoveryConfig`.
236
+
237
+ ### Notes
238
+ - **Backward compatible.** A `x402Middleware` call with no `extensions`/`declarations` emits a 402 byte-identical to 3.7.8.
239
+ - **HTTP only in v1.** MCP-tool discovery (`input.type: "mcp"`) is intentionally out of scope — a clean discriminated-union add-on later.
240
+ - **Spec-conformant.** The emitted `extensions.bazaar` was cross-checked against `/tmp/x402-spec/specs/extensions/bazaar.md` (the official x402 bazaar spec) and the upstream test oracle — field-for-field match on `info.input`, `info.output`, `schema.$schema`, `schema.properties.input.required`, `schema.required`, and `routeTemplate`.
241
+ - **272 tests passing.**
242
+
243
+ ## [3.7.4] - 2026-05-18
244
+
245
+ ### Fixed
246
+ - **v1 `exact`-scheme EVM payments no longer crash on Node 18.** `buildV1PaymentHeader` generated its EIP-3009 replay nonce via `globalThis.crypto.getRandomValues`, which is undefined on Node 18 (`crypto` is only a global in browsers and Node 19+). The SDK supports Node 18 (`engines: >=18`). The nonce generator now resolves `globalThis.crypto` with a `node:crypto` `webcrypto` fallback, the same fix applied to the batch-settlement salt generator in 3.7.3.
247
+
248
+ ## [3.7.3] - 2026-05-18
249
+
250
+ ### Fixed
251
+ - **`openBatchChannel` no longer crashes on Node 18.** 3.7.2's channel-salt generator called `crypto.getRandomValues` on the global `crypto`, which is only defined in browsers and Node 19+. The SDK supports Node 18 (`engines: >=18`), so on Node 18 `openBatchChannel` threw `ReferenceError: crypto is not defined`. The salt generator now resolves `globalThis.crypto` with a `node:crypto` `webcrypto` fallback, matching the pattern used elsewhere in the SDK.
252
+
253
+ ## [3.7.2] - 2026-05-18
254
+
255
+ Fixes a batch-settlement channel-identity bug: every channel between the same
256
+ buyer, seller, and token collided on a single deterministic id, so a buyer
257
+ could never open a second channel with a seller they had already used —
258
+ `openBatchChannel` would silently reopen the first, exhausted channel.
259
+
260
+ ### Fixed
261
+ - **Batch-settlement channels now have unique identities.** `openBatchChannel` generates a fresh random channel-config salt for each channel, so the deterministic `channelId` differs per channel. A buyer can hold multiple independent channels with the same seller over time. Previously the SDK never passed a salt to the upstream scheme, so every channel fell back to the zero `DEFAULT_SALT` and collided.
262
+
263
+ ### Added
264
+ - **`channel.salt`** — the 32-byte channel-config salt a channel was opened with, exposed on the `BatchSettlementChannel` handle. Persist it to later resume that exact channel.
265
+ - **`OpenBatchChannelOptions.salt`** (optional) — pass an explicit salt to deterministically reopen a specific channel; omit it for a fresh random one.
266
+
267
+ ### Breaking
268
+ - **`resumeBatchChannel` now requires `salt`.** Resuming a channel needs the exact salt it was opened with — a `channelId` cannot be reversed to a salt. Persist `channel.salt` at open time and pass it to `resumeBatchChannel`. (Resuming previously relied on the zero `DEFAULT_SALT`, which is the collision bug above; it could not correctly resume a distinct channel.)
269
+
270
+ ## [3.4.0] - 2026-05-17
271
+
272
+ Batch settlement is now functional end-to-end. 3.3.0 shipped a batch-settlement
273
+ buyer but no seller runtime, so a seller had no way to collect the vouchers a
274
+ buyer signed. This release adds the seller runtime and corrects the buyer's
275
+ `channel.close()`.
276
+
277
+ ### Added
278
+ - **Batch-settlement seller runtime** — `createBatchSettlementSeller(config)`, exported from `@dexterai/x402/batch-settlement/seller`. Returns a callable object that **is** an Express request handler (mount it directly) and also exposes `.closeChannel(channelId)`, `.closeAll()`, and `.stop()`. It accepts batch-settlement payments — incoming vouchers are verified and persisted to channel storage — and collects them (claim → settle → refund), automatically via a background loop (on by default) and on explicit demand via `closeChannel` / `closeAll`. Config: `{ payTo, network, price, facilitatorUrl?, route?, channelStore?, autoSettle?, verbose? }`.
279
+ - **`x402Middleware({ scheme: 'batch-settlement', ... })` now returns the callable seller object** — a seller that mounts the batch-settlement scheme via `x402Middleware` still gets a `.stop()` / `.closeAll()` / `.closeChannel()` handle.
280
+ - **Buyer escape hatch** — `channel.forceWithdraw()` followed (after the channel's withdraw delay) by `channel.finalizeWithdraw()` reclaims unspent escrow directly via the contract's timed withdrawal if the seller never settles. This is a last-resort safety net; normal operation does not need it. Unlike every other batch-settlement step, the escape hatch costs the buyer gas — the buyer's wallet must be transaction-capable (it must expose a `sendTransaction` method). A signature-only wallet cannot use it.
281
+
282
+ ### Breaking
283
+ - **`channel.close()` no longer returns a `CloseReceipt`.** It now returns `{ closed: true }` and is an intent signal that the buyer is finished with the channel — it is not a settlement and does not move funds. The buyer's unspent escrow returns via the seller's refund on the normal settlement path. (3.3.0's `close()` threw and never worked; this corrects it.)
284
+
285
+ ### Fixed
286
+ - **Batch settlement is now functional end-to-end** — a seller can collect the payments a buyer makes, which 3.3.0 could not (it shipped a buyer with no seller runtime).
287
+
288
+ ## [3.2.0] - 2026-05-03
289
+
290
+ Multi-chain coverage parity. The facilitator has supported Polygon, Optimism, Avalanche, BSC, and SKALE Base for a while; the client adapters and helper functions now declare every one of those chains explicitly instead of relying on `eip155:` substring fallbacks. This closes the gap that caused downstream consumers (e.g. the Dexter resource verifier) to display "Insufficient balance on Base" for resources that actually accepted payment on a different EVM chain.
291
+
292
+ ### Added
293
+ - `EvmAdapter.networks` now declares Polygon, Optimism, Avalanche, SKALE Base, and SKALE Base Sepolia in addition to Base mainnet, Base Sepolia, Ethereum, Arbitrum, and BSC. Behaviour for unknown `eip155:N` strings is unchanged (still accepted via prefix match) — the difference is that supported chains are now first-class enumerated entries.
294
+ - `getChainName()` extended with mappings for BSC, Polygon, Optimism, Avalanche, SKALE Base, and SKALE Base Sepolia. Legacy short-form aliases (`'polygon'`, `'avalanche'`, `'bsc'`, `'skale-base'`, etc.) are accepted alongside the canonical CAIP-2 form.
295
+ - `getExplorerUrl()` extended with explorer URL templates for the same set of chains (Polygonscan, Optimistic Etherscan, Snowtrace, BscScan, SKALE Base + SKALE Base Sepolia explorers).
296
+ - `getChainDisplayName(network, family)` exported from `@dexterai/x402/utils` — same mapping as `getChainName()` but falls back to the adapter family name (`'Solana'` / `'EVM'`) instead of the raw CAIP-2 string. Use this in user-facing error messages and UI badges.
297
+ - New test suite `evm-chain-coverage.test.ts` (71 cases) locks in the adapter declaration / canHandle / RPC / USDC / chain ID matrix per chain so future contributors can't add a constant without wiring the adapter.
298
+
299
+ ### Fixed
300
+ - The pre-payment `insufficient_balance` X402Error in `createX402Client` now resolves the chain name from the canonical registry, so the message reads "Insufficient balance on Polygon" instead of the previous hardcoded "Insufficient balance on Base" for non-Base EVM chains.
301
+ - Access-pass purchase flow's `insufficient_balance` error now includes the chain name. Previously it omitted the chain entirely, which made the diagnosis ambiguous when the same wallet was authorized on multiple chains.
302
+
303
+ ### Changed
304
+ - Consolidated network identifiers, token addresses, RPC URLs, chain IDs, Permit2 addresses, and protocol defaults into a single `src/constants.ts` module. Types, adapters, server middleware, access pass, and Stripe PayTo now import from it instead of carrying their own duplicates. Public export surface is unchanged. ([`4d3e881`])
305
+ - `getChainName('eip155:42161')` now returns `'Arbitrum'` (was `'Arbitrum One'`). The shorter form reads better in error messages and matches every other chain's display convention. The CAIP-2 identifier is unchanged.
306
+
307
+ ### Removed
308
+ - `X402ErrorCode` member `no_solana_accept` — zero callers anywhere in the ecosystem. ([`58c7eea`])
309
+ - `KeypairWallet.keypair` — the deprecated field that shadowed `KEYPAIR_SYMBOL`. `isKeypairWallet()` now checks the symbol directly. No external callers were found. ([`58c7eea`])
310
+ - Implicit `(response as any)._x402 = receipt` mutation after payment settlement. The typed `getPaymentReceipt(response)` + `PaymentReceipt` WeakMap path has been the public API since 1.8.0; the legacy mutation no longer had any readers. ([`58c7eea`])
311
+
312
+ ## [3.1.1] - 2026-04-18
313
+
314
+ ### Changed
315
+ - `capabilitySearch` implementation moved to `@dexterai/x402-core` — this SDK now re-exports from the shared core package so discovery logic stays consistent across Dexter surfaces (SDK, OpenDexter, MCP servers, widgets). Public API unchanged.
316
+ - Build now minifies output and targets ES2022.
317
+ - Bumped `@dexterai/x402-ads-types` from `^0.1.0` to `^0.2.0`.
318
+
319
+ ### Added
320
+ - `@dexterai/x402-core` as a runtime dependency.
321
+
322
+ ### Removed
323
+ - Stale `tweet-thread-v2.md` draft marketing content from the repo root.
324
+
325
+ ## [3.0.0] - 2026-04-15
326
+
327
+ ### Breaking
328
+ - **`searchAPIs()` is gone. Replaced by `capabilitySearch()`.** The legacy substring ranker at `/api/facilitator/marketplace/resources` was retired — discovery now goes through the semantic capability search pipeline at `/api/x402gle/capability` (vector search + similarity floor + tiering + cross-encoder LLM rerank).
329
+ - **`DiscoveredAPI` type removed. Replaced by `CapabilityAPI`.** The new shape carries `tier: 'strong' | 'related'`, a raw `similarity` score (0–1), a `why` string explaining the ranking factors, and a final combined `score`. It also nests gaming-flag signals (`gamingFlags`, `gamingSuspicious`) and drops `sellerReputation`, `totalVolume` (formatted), `lastActive`, and `authRequired`.
330
+ - **Hard-filter params are gone.** `category`, `network`, `maxPrice`, `verifiedOnly`, and `sort` were removed from the search options. They were the source of silent false-empties (e.g. `{ query: 'ETH price', network: 'ethereum' }` returned zero results because every ETH-price resource accepts payment on Base). The ranker handles these semantically; payment rail is a checkout-time concern the caller handles separately. The new options are: `query` (required), `limit`, `unverified`, `testnets`, `rerank`, and `endpoint`.
331
+ - **Response is tiered.** `capabilitySearch()` returns `{ strongResults, relatedResults, strongCount, relatedCount, topSimilarity, noMatchReason, rerank, intent, durationMs }` instead of a flat array. `strongResults` are high-confidence matches that cleared the strong similarity threshold; `relatedResults` are adjacent candidates that cleared the floor but not the strong threshold.
332
+
333
+ ### Added
334
+ - **`capabilitySearch(options: CapabilitySearchOptions): Promise<CapabilitySearchResult>`** — semantic search with synonym expansion at the intent parse layer, similarity floor filtering, strong/related tiering, and cross-encoder LLM rerank on the top strong results.
335
+ - **`NoMatchReason` type** — `'below_similarity_threshold' | 'below_strong_threshold' | null`. Callers can distinguish "corpus has zero candidates" from "candidates exist but none are high-confidence".
336
+ - **Intent telemetry on every response** — `result.intent` exposes the parsed `capabilityText` and the synonym-expanded `expandedCapabilityText` that was actually embedded for the vector search. Useful for debugging why a query ranked a particular way.
337
+ - **Rerank telemetry on every response** — `result.rerank.applied` tells you whether the LLM cross-encoder actually reordered the top strong results, and `result.rerank.reason` explains any skip.
338
+
339
+ ### Migration
340
+ Replace the search call:
341
+ ```ts
342
+ // Before (2.x)
343
+ const results = await searchAPIs({ query: 'ETH price', category: 'data', maxPrice: 0.10 });
344
+ for (const api of results) { console.log(api.name, api.price); }
345
+
346
+ // After (3.0)
347
+ const result = await capabilitySearch({ query: 'ETH price' });
348
+ for (const api of result.strongResults) { console.log(api.name, api.price, api.why); }
349
+ if (result.strongCount === 0 && result.relatedCount > 0) {
350
+ // Fall back to related matches when nothing cleared the strong threshold
351
+ for (const api of result.relatedResults) { console.log('related:', api.name); }
352
+ }
353
+ ```
354
+
355
+ Filter semantically via the query text, not parameters:
356
+ - `searchAPIs({ category: 'defi' })` → `capabilitySearch({ query: 'DeFi tools' })`
357
+ - `searchAPIs({ network: 'solana' })` → `capabilitySearch({ query: 'on Solana' })` (or filter client-side via `pricing.network`)
358
+ - `searchAPIs({ maxPrice: 0.10 })` → filter the result array: `result.strongResults.filter(r => r.priceUsdc != null && r.priceUsdc <= 0.10)`
359
+
360
+ ## [2.0.0] - 2026-03-15
361
+
362
+ ### Breaking
363
+ - **`PaymentAccept.amount` is now required** — v2 spec field. `maxAmountRequired` is deprecated (optional alias for v1 compat).
364
+ - **`PaymentAccept.extra` is now optional** — per v2 spec.
365
+ - **`TokenPricing` methods are async** — `calculate()`, `validateQuote()`, `countTokens()` return Promises. `tiktoken` is now an optional peer dependency (lazy-loaded on first call).
366
+
367
+ ### Added
368
+ - **Budget Accounts** — `createBudgetAccount()` wraps fetch with spending controls: total budget, per-request cap, hourly rate limit, and domain allowlist. Tracks cumulative spend with a full payment ledger. Give your agent $50 and let it spend autonomously.
369
+ - **API Discovery** — `searchAPIs()` searches the Dexter marketplace for x402 paid APIs by query, category, network, price range, and quality score. Returns typed `DiscoveredAPI[]` that can be called directly with `wrapFetch`.
370
+ - **Retry with exponential backoff** — `maxRetries` and `retryDelayMs` in client config. Retries on network errors and 502/503/504. Safe for payments — EIP-3009 nonces prevent double-spend.
371
+ - **First-class Sponsored Access (Ads for Agents)** — `getSponsoredRecommendations()`, `getSponsoredAccessInfo()`, `fireImpressionBeacon()` client helpers. React hook `sponsoredRecommendations`. Server `onMatch` callback. `@dexterai/x402-ads-types` promoted to direct dependency.
372
+ - **Pre-payment inspection** — `onPaymentRequired` callback on client and `wrapFetch`. Return `false` to reject a payment before signing.
373
+ - **Settlement webhooks** — `onSettlement` and `onVerifyFailed` callbacks in middleware config.
374
+ - **CSP headers** on browser paywall page.
375
+ - **`KEYPAIR_SYMBOL`** for safe access to the Solana Keypair (Symbol-keyed, hidden from serialization).
376
+ - **`escapeHtml()`** exported from server for safe HTML rendering of payment data.
377
+ - **`isSolanaNetwork()` / `isEvmNetwork()`** utility functions.
378
+ - **New error codes** — `wallet_disconnected`, `user_rejected_signature`, `rpc_timeout`, `facilitator_timeout`.
379
+ - **Typed `WalletSet`** — `solana` and `evm` fields are now typed as `SolanaWallet` and `EvmWallet` instead of `unknown`.
380
+ - **52 unit tests** covering dynamic pricing, XSS escaping, USDC detection, type compliance, amount conversion, sponsored access.
381
+ - **CI/CD** — GitHub Actions: typecheck + build + test on Node 18/20/22. Publish workflow gated on tests, auto-creates GitHub releases.
382
+ - **`extensions` field** on `PaymentRequired` and `PaymentSignature` per v2 spec.
383
+ - **Auto GitHub releases** from tag pushes with changelog extraction.
384
+
385
+ ### Fixed
386
+ - **EVM nonce security** — `Math.random()` replaced with `crypto.getRandomValues()`.
387
+ - **Dynamic pricing security** — FNV-1a replaced with HMAC-SHA256 with timestamp-bounded quotes (5-min TTL).
388
+ - **Balance checks** throw on RPC errors instead of silently returning 0.
389
+ - **Resource URL validation** — blocks `javascript:`, `data:`, `file:` schemes.
390
+ - **Internal errors** no longer leaked to clients.
391
+ - **Stripe guard** uses WeakMap instead of fragile `as any` property.
392
+ - **Source maps removed** from production build (62% smaller package).
393
+ - **Client JWT cache** capped to 24h regardless of decoded `exp`.
394
+
395
+ ## [1.9.4] - 2026-03-15
396
+
397
+ ### Fixed
398
+ - **Balance checks no longer silently swallow RPC errors** — Solana adapter now only returns 0 for `TokenAccountNotFoundError` (new wallets). EVM adapter throws on HTTP errors and RPC errors. The client gracefully skips the pre-check on RPC failure and lets the chain reject if balance is actually insufficient.
399
+ - **Resource URLs validated for scheme** — client rejects `javascript:`, `data:`, and `file:` URLs from payment requirement headers. Only `http:` and `https:` are accepted.
400
+ - **Internal error details no longer leaked to clients** — middleware 500 responses now return generic `"Payment processing error"` without the underlying error message.
401
+ - **CSP headers on browser paywall** — the generated HTML paywall page now sets `Content-Security-Policy` and `X-Content-Type-Options: nosniff` headers.
402
+ - **Stripe PayTo documentation** — JSDoc now explicitly documents the Base-only limitation with a multi-chain workaround example.
403
+ - **sessionStorage risk documented** — `useAccessPass` JSDoc warns about XSS exposure of stored JWTs.
404
+
405
+ ### Added
406
+ - **`onSettlement` callback** in middleware config — called after every successful payment settlement for logging, analytics, or webhooks.
407
+ - **`onVerifyFailed` callback** in middleware config — called when payment verification fails for monitoring suspicious activity.
408
+ - **`onPaymentRequired` callback** in client config — pre-payment inspection hook. Return `false` to reject a payment before signing. Critical for agent budget controls.
409
+ - **`KEYPAIR_SYMBOL`** — Symbol-keyed access to the underlying Solana Keypair, preventing accidental private key exposure via `console.log` or `JSON.stringify`. The `keypair` property is deprecated but kept for backwards compat.
410
+ - **New error codes** — `wallet_disconnected`, `user_rejected_signature`, `rpc_timeout`, `facilitator_timeout`.
411
+ - **`isSolanaNetwork()` and `isEvmNetwork()`** — exported utility functions for network detection, replacing duplicated `startsWith` checks.
412
+
413
+ ## [1.9.3] - 2026-03-15
414
+
415
+ ### Added
416
+ - **52 unit tests** (up from 6) covering dynamic pricing HMAC validation, amount conversion edge cases, network detection, XSS escaping, USDC detection across all chains, v2 type compliance, sponsored access helpers, and X402Error behavior.
417
+ - **`escapeHtml()` exported** from `@dexterai/x402/server` — the XSS escape function is now public and tested for consumers who render payment data in HTML.
418
+
419
+ ## [1.9.2] - 2026-03-15
420
+
421
+ ### Breaking
422
+ - **`TokenPricing.calculate()`, `validateQuote()`, and `countTokens()` are now async** — returns `Promise<TokenPriceQuote>`, `Promise<boolean>`, and `Promise<number>` respectively. This is required because tiktoken is now lazy-loaded. Add `await` to all calls.
423
+ - **`TokenPricingConfig.tokenizer` now accepts async functions** — type changed from `(text: string) => number` to `(text: string) => number | Promise<number>`. Existing sync tokenizers still work.
424
+
425
+ ### Fixed
426
+ - **tiktoken is no longer a hard dependency** — moved from `dependencies` to optional `peerDependencies`. The 5MB+ WASM binary is only loaded when `createTokenPricing()` or `countTokens()` is actually called. Consumers who don't use token pricing save the install cost entirely. Throws a helpful error if tiktoken is needed but not installed.
427
+ - **Stripe PayTo guard used fragile private property** — replaced `(provider as any)._stripeNetwork` with a `WeakMap` registry that survives wrapping, proxying, and `bind()`. Exported `getStripeProviderNetwork()` for external use.
428
+
429
+ ### Added
430
+ - **EVM wallet Quick Start in README** — `wrapFetch` with `evmPrivateKey` is now documented alongside the Solana example in the Quick Start section.
431
+
432
+ ## [1.9.1] - 2026-03-15
433
+
434
+ ### Added
435
+ - **First-class Sponsored Access (Ads for Agents)** — typed helpers for consuming sponsored recommendations from x402 payment receipts:
436
+ - `getSponsoredRecommendations(response)` — extract typed `SponsoredRecommendation[]` from a payment response
437
+ - `getSponsoredAccessInfo(response)` — extract the full `SponsoredAccessSettlementInfo` extension data
438
+ - `fireImpressionBeacon(response)` — fire-and-forget delivery confirmation to the ad network
439
+ - **React hook support** — `useX402Payment` now returns `sponsoredRecommendations` (auto-populated after payment, auto-fires impression beacon)
440
+ - **Server `onMatch` callback** — `sponsoredAccess: { onMatch: (recs, settlement) => ... }` for server-side logging/analytics when recommendations are delivered
441
+ - **Typed middleware injection** — `sponsoredAccess.inject` callback now receives typed `SponsoredRecommendation[]` instead of `unknown[]`
442
+ - **Re-exported types** — `SponsoredRecommendation`, `SponsoredAccessSettlementInfo`, `SponsoredAccessPaymentRequiredInfo`, `SponsoredAccessClientConsent`, and `SPONSORED_ACCESS_EXTENSION_KEY` are now exported from `@dexterai/x402/client`, `@dexterai/x402/server`, and `@dexterai/x402/react`
443
+
444
+ ### Changed
445
+ - **`@dexterai/x402-ads-types` is now a direct dependency** — moved from optional peer dependency to ensure types are always available. Package is 72KB (mostly type declarations, 12 lines of JS).
446
+
447
+ ## [1.9.0] - 2026-03-15
448
+
449
+ ### Breaking
450
+ - **`PaymentAccept.amount` is now required, `maxAmountRequired` is deprecated** — Aligns with the x402 v2 specification. `amount` is the v2 spec field; `maxAmountRequired` remains as an optional alias for backwards compatibility with v1 data. TypeScript consumers referencing `accept.maxAmountRequired` directly will get deprecation warnings. Server output includes both fields during the transition period.
451
+ - **`PaymentAccept.extra` is now optional** — Per v2 spec, `extra` is not required on all payment options. Existing code that accesses `accept.extra.feePayer` should use optional chaining: `accept.extra?.feePayer`.
452
+
453
+ ### Fixed
454
+ - **EVM nonce used `Math.random()`** — Replaced with `crypto.getRandomValues()` for cryptographically secure nonce generation in EIP-3009 authorizations. Falls back to Node.js `crypto.webcrypto` for older environments.
455
+ - **Dynamic pricing used non-cryptographic hash** — Replaced FNV-1a with HMAC-SHA256 for quote validation. Quotes now include a timestamp and are rejected after 5 minutes, preventing both hash collision attacks and stale quote reuse.
456
+ - **Client-side access pass cache didn't cap expiry** — JWT `exp` decoded from unverified tokens is now capped to 24 hours max cache TTL, preventing forged far-future timestamps from caching indefinitely. Server-side verification is unaffected (always enforces HMAC signature).
457
+
458
+ ### Added
459
+ - **`extensions` field on `PaymentRequired` and `PaymentSignature`** — Per v2 spec, both types now support optional `extensions: Record<string, unknown>` for protocol extensions like sponsored-access and bazaar.
460
+
461
+ ## [1.8.2] - 2026-03-11
462
+
463
+ ### Fixed
464
+ - **`getFeePayer()` throws on EVM networks** — `FacilitatorClient.getFeePayer()` required a `feePayer` field that only exists for SVM networks. Now returns `undefined` for EVM instead of throwing. Only throws if the network isn't supported at all.
465
+
466
+ ## [1.8.1] - 2026-03-11
467
+
468
+ ### Fixed
469
+ - **Server SDK crashes on EVM networks** — `createX402Server()` threw "Facilitator does not provide feePayer" when used with any EVM network (Base, Polygon, SKALE, etc.) because `getNetworkExtra()` unconditionally required a `feePayer` field from the facilitator's `/supported` response. That field only exists for Solana. The check is now SVM-only; EVM networks pass through `decimals` and EIP-712 fields without it.
470
+
471
+ ## [1.8.0] - 2026-03-10
472
+
473
+ ### Breaking
474
+ - **`createKeypairWallet` is now async** — Returns `Promise<KeypairWallet>` instead of `KeypairWallet`. You must `await` the result: `const wallet = await createKeypairWallet(key)`. This change was required for ESM compatibility (`require('bs58')` → `await import('bs58')`). `wrapFetch` handles this automatically — only direct callers are affected.
475
+
476
+ ### Fixed
477
+ - **Verify/settle amount bug** — Server was passing `amountAtomic: '0'` to the facilitator when verifying or settling payments with dynamic payTo (e.g., Stripe). Added an in-memory requirements cache that preserves the correct amount between the initial 402 response and the retry with payment. Falls back to extracting the amount from the payment header if the cache misses.
478
+ - **ESM compatibility** — Replaced `require()` calls in `adapters/index.ts` and `keypair-wallet.ts` with ESM-compatible static imports and `await import()`. The package is `"type": "module"` and now works correctly in strict ESM environments.
479
+ - **XSS in browser paywall** — HTML-escape all interpolated values (description, price, requestUrl) in the browser paywall page to prevent injection from malicious payment requirement fields.
480
+ - **USDC decimal inference** — Client now recognizes USDC on all supported chains (Polygon, Arbitrum, Optimism, Avalanche, SKALE) for decimal inference, not just Solana and Base. Uses a shared `isKnownUSDC()` helper instead of hardcoded lists.
481
+ - **Wrong facilitator URL in JSDoc** — Fixed `@default` annotations in middleware, wrap-fetch, and access-pass that said `x402-facilitator.dexter.cash` (wrong) instead of `x402.dexter.cash` (correct).
482
+ - **Stripe type safety** — Stripe client, PaymentIntent response, and crypto options are now typed via `import('stripe')` instead of `any`.
483
+
484
+ ### Added
485
+ - **Multi-network middleware** — `x402Middleware` now accepts `network: string | string[]` and `payTo: Record<string, string | PayToProvider>` with glob matching (`eip155:*`, `solana:*`, `*`). Endpoints can accept payments on all chains simultaneously. The client picks whichever chain it has a wallet for.
486
+ - **Full chain parity with facilitator** — EVM adapter now supports all 10 networks from the Dexter facilitator: Base, Polygon, Arbitrum, Optimism, Avalanche, SKALE Base (mainnet + testnet), and Base Sepolia. Ethereum mainnet is deprecated (not in facilitator).
487
+ - **Resilient facilitator client** — `FacilitatorClient` now retries on 5xx and network errors with exponential backoff (3 attempts, 500ms/1s/2s). All requests have a 10s timeout. Both limits are configurable via `FacilitatorClientConfig`.
488
+ - **`getPaymentReceipt(response)`** — Typed helper (backed by `WeakMap`) replaces the `(response as any)._x402` pattern. Exported from `@dexterai/x402/client`.
489
+ - **`@dexterai/x402-ads-types`** — Added as an optional peer dependency for typed sponsored-access extensions. No inlining; single source of truth.
490
+ - **New chain constants** — Exported `POLYGON`, `OPTIMISM`, `AVALANCHE`, `SKALE_BASE`, `SKALE_BASE_SEPOLIA`, `USDC_ADDRESSES` from `@dexterai/x402/adapters`.
491
+
492
+ ## [1.7.2] - 2026-02-28
493
+
494
+ ### Added
495
+ - **Sponsored access support** — Server middleware accepts `sponsoredAccess: true` config. Reads `extensions["sponsored-access"]` from the facilitator's settlement response and injects `_x402_sponsored` into the JSON response body.
496
+ - `SettleResponse.extensions` field for protocol extensions
497
+
498
+ ## [1.7.1] - 2026-02-25
499
+
500
+ ### Fixed
501
+ - Added `@types/aws-lambda` to fix DTS build errors
502
+ - Updated npm metadata and package description
503
+
504
+ ## [1.7.0] - 2026-02-20
505
+
506
+ ### Added
507
+ - OpenDexter marketplace auto-discovery section in README
508
+ - Updated header with marketplace links
509
+
510
+ ## [1.6.6] - 2026-02-12
511
+
512
+ ### Fixed
513
+ - Unicode-safe base64 encoding for server-side `btoa`/`atob`
514
+
515
+ ## [1.6.5] - 2026-02-10
516
+
517
+ ### Fixed
518
+ - **`wrapFetch` + `createEvmKeypairWallet` ESM compatibility** — v1.6.4 used `require('viem/accounts')` which fails in ESM consumers because viem 2.x is ESM-only. Replaced with `await import('viem/accounts')` (dynamic import). `createEvmKeypairWallet` is now async; `wrapFetch` starts the import eagerly and awaits it before the first fetch call, keeping its own signature synchronous.
519
+
520
+ ## [1.6.4] - 2026-02-10
521
+
522
+ ### Fixed
523
+ - **`wrapFetch` EVM support** — `evmPrivateKey` option now works. Previously, passing an EVM private key to `wrapFetch` would log a warning and silently discard the key, causing all Base/EVM payments to fail with `no_matching_payment_option`. The key is now used to create a proper EVM wallet via viem's `privateKeyToAccount` (chain-agnostic EIP-712 signing).
524
+
525
+ ### Added
526
+ - **`createEvmKeypairWallet()`** — New helper (parallel to `createKeypairWallet` for Solana) that creates an `EvmWallet` from a hex private key. Exported from `@dexterai/x402/client`. Useful for Node.js scripts that need EVM payments without a browser wallet.
527
+
528
+ ## [1.5.0] - 2026-02-09
529
+
530
+ ### Added
531
+ - **Access Pass** — New payment pattern: pay once, get a time-limited JWT for unlimited API requests. Works with both SVM and EVM.
532
+ - **Server**: `x402AccessPass` middleware (`@dexterai/x402/server`) — drop-in Express middleware with tier-based and custom duration pricing. Issues JWTs after x402 payment settlement. Validates passes on subsequent requests without touching the facilitator.
533
+ - **Client**: `accessPass` option on `wrapFetch` and `createX402Client` (`@dexterai/x402/client`) — auto-detects servers that offer access passes, purchases one, caches the JWT, and includes it on all subsequent requests. Auto-renews expired passes.
534
+ - **React**: `useAccessPass` hook (`@dexterai/x402/react`) — dedicated hook for managing the access pass lifecycle: tier discovery, pass purchase, token caching, countdown timer, and auto-fetch with pass.
535
+ - New types: `AccessPassTier`, `AccessPassInfo`, `AccessPassClaims`, `AccessPassClientConfig`
536
+ - New error codes: `access_pass_expired`, `access_pass_invalid`, `access_pass_tier_not_found`, `access_pass_exceeds_max_spend`
537
+ - New HTTP headers: `X-ACCESS-PASS-TIERS` (server -> client on 402), `ACCESS-PASS` (server -> client on pass purchase)
538
+ - `test/access-pass.ts` — 8-assertion test suite covering the full access pass lifecycle
539
+
540
+ ## [1.4.1] - 2026-02-09
541
+
542
+ ### Fixed
543
+ - **PAYMENT-RESPONSE header** — Server middleware now sets `PAYMENT-RESPONSE` header (base64-encoded settlement data) on 200 OK responses after successful payment, per the x402 v2 HTTP transport spec. Previously, settlement data was only attached to `req.x402` but not surfaced as a response header.
544
+ - **`amount` field in 402 response** — The `accepts` array in payment requirements now includes both `amount` (v2 spec field) and `maxAmountRequired` (legacy field). Non-Dexter v2 clients that look for `amount` instead of `maxAmountRequired` will now find it.
545
+ - **`x402Version` in facilitator requests** — The `FacilitatorClient` now sends `x402Version: 2` at the top level of `/verify` and `/settle` request bodies, matching the Coinbase reference implementation format.
546
+
547
+ ### Added
548
+ - `test/v2-spec-compliance.ts` — Automated test suite validating all three v2 spec compliance fixes against a mock facilitator (6 assertions).
549
+
550
+ ## [1.4.0] - 2026-01-11
551
+
552
+ ### Added
553
+ - **Model Registry** - Comprehensive single source of truth for all OpenAI models (`model-registry.ts`)
554
+ - 25 models across 5 tiers: fast, standard, reasoning, premium, specialized
555
+ - Complete pricing data from OpenAI (January 2026)
556
+ - GPT-5 family: gpt-5-nano, gpt-5-mini, gpt-5, gpt-5.1, gpt-5.2, gpt-5-pro, gpt-5.2-pro
557
+ - Reasoning models: o1, o1-mini, o1-pro, o3, o3-mini, o3-pro, o4-mini
558
+ - Specialized: deep-research, computer-use-preview, realtime models
559
+ - **Registry API**:
560
+ - `MODEL_REGISTRY` - Full model definitions with pricing, capabilities, and API parameters
561
+ - `getModel(id)` - Get model by ID (throws if not found)
562
+ - `findModel(id)` - Get model by ID (returns undefined if not found)
563
+ - `getModelsByTier(tier)` - Get all models in a tier
564
+ - `getModelsByFamily(family)` - Get models by family (gpt-5, o3, etc.)
565
+ - `getTextModels()` - Get all text-capable models for chat completions
566
+ - `getActiveModels()` - Get all non-deprecated models
567
+ - `getCheapestModel(minTier?)` - Find cheapest model meeting requirements
568
+ - `estimateCost(modelId, inputTokens, outputTokens)` - Calculate request cost
569
+ - **Model Parameters** - Each model specifies API compatibility:
570
+ - `usesMaxCompletionTokens` - GPT-5/reasoning models require this instead of `max_tokens`
571
+ - `supportsTemperature` - GPT-5 models only support default (1)
572
+ - `supportsReasoningEffort` - For o-series models
573
+ - `supportsTools`, `supportsStructuredOutput`, `supportsStreaming`
574
+
575
+ ### Changed
576
+ - `token-pricing.ts` now uses `MODEL_REGISTRY` as its data source (no more duplicate pricing)
577
+ - `getAvailableModels()` returns models sorted by tier then price
578
+
579
+ ### Developer Tools
580
+ - **Model Evaluation Harness** (`test/model-eval/`) - CLI for testing models
581
+ - Test prompts across multiple models simultaneously
582
+ - Compare response quality, timing, and costs
583
+ - Context injection from files (`--context`)
584
+ - Full output logging with metrics
585
+
586
+ ## [1.3.1] - 2025-01-10
587
+
588
+ ### Fixed
589
+ - Minor type exports cleanup
590
+
591
+ ## [1.3.0] - 2025-01-09
592
+
593
+ ### Changed
594
+ - Internal refactoring for model pricing
595
+
596
+ ## [1.2.4] - 2024-12-30
597
+
598
+ ### Fixed
599
+ - **x402 v1 compatibility** - SDK now echoes the `x402Version` from the server's 402 response instead of hardcoding v2. This enables compatibility with v1-only facilitators.
600
+ - Added `x402Version` field to `PaymentAccept` type
601
+
602
+ ## [1.2.1] - 2024-12-28
603
+
604
+ ### Added
605
+ - **Custom model support** for `createTokenPricing()`:
606
+ - `inputRate` - Custom USD per 1M input tokens (for Anthropic, Gemini, Mistral, etc.)
607
+ - `outputRate` - Custom USD per 1M output tokens
608
+ - `maxTokens` - Custom max output tokens
609
+ - `tokenizer` - Custom tokenizer function for non-OpenAI models
610
+ - `'custom'` tier for user-defined pricing
611
+
612
+ ### Changed
613
+ - `createDynamicPricing()` documentation clarified: works for ANY pricing scenario, not just LLM
614
+ - README now shows examples for Anthropic Claude, Google Gemini, and local models
615
+
616
+ ## [1.2.0] - 2024-12-28
617
+
618
+ ### Added
619
+ - **Token Pricing** - `createTokenPricing()` for accurate LLM pricing using tiktoken
620
+ - Uses real OpenAI model rates (gpt-4o-mini, gpt-4o, o1, o3, etc.)
621
+ - `MODEL_PRICING` - Complete pricing table for 20+ models across fast/standard/reasoning/premium tiers
622
+ - `countTokens()` - Accurate token counting using OpenAI's tiktoken
623
+ - `getAvailableModels()` - List all models sorted by tier and price
624
+ - `isValidModel()` - Check if a model is supported
625
+ - `formatTokenPricing()` - Display helper (e.g., "$0.15 per 1M tokens")
626
+
627
+ ### Changed
628
+ - Dynamic pricing now has two variants:
629
+ - `createDynamicPricing()` - Character-based (generic, no deps)
630
+ - `createTokenPricing()` - Token-based (LLM-accurate, uses tiktoken)
631
+
632
+ ## [1.1.0] - 2024-12-27
633
+
634
+ ### Added
635
+ - **Dynamic Pricing** - `createDynamicPricing()` for LLM/AI endpoints where cost scales with input
636
+ - Quote hash validation prevents prompt manipulation (includes pricing config in hash)
637
+ - `formatPricing()` helper for display strings
638
+ - Client SDK now forwards `X-Quote-Hash` header on retry
639
+
640
+ ## [1.0.4] - 2024-12-27
641
+
642
+ ### Changed
643
+ - **README overhaul** - Professional documentation with live demo links, emoji formatting, and clear API reference
644
+ - Prominent link to [dexter.cash/sdk](https://dexter.cash/sdk) for live verification
645
+
646
+ ## [1.0.3] - 2024-12-27
647
+
648
+ ### Added
649
+ - **Utils export** - `toAtomicUnits()` and `fromAtomicUnits()` now available via `@dexterai/x402/utils`
650
+ - `getChainFamily()`, `getChainName()`, `getExplorerUrl()` helpers
651
+
652
+ ### Changed
653
+ - README now includes notice that server SDK is not yet battle-tested
654
+
655
+ ## [1.0.2] - 2024-12-27
656
+
657
+ ### Added
658
+ - **Pre-flight balance check** - SDK now checks USDC balance before signing transactions
659
+ - `insufficient_balance` error code with clear message: "Insufficient USDC balance on [Network]. Have $X, need $Y"
660
+
661
+ ### Fixed
662
+ - Prevents confusing "Payment was rejected by the server: {}" error when user has insufficient funds
663
+ - Users now see a clear, actionable error message before wallet popup appears
664
+
665
+ ## [1.0.1] - 2024-12-26
666
+
667
+ ### Fixed
668
+ - EVM adapter payload structure now correctly separates `authorization` and `signature` fields to match upstream `@x402/evm` format
669
+ - Removed unnecessary `feePayer` validation for EVM networks (users pay their own gas)
670
+
671
+ ## [1.0.0] - 2024-12-26
672
+
673
+ ### Added
674
+ - **Chain-agnostic architecture** - Support for multiple blockchains through adapter pattern
675
+ - **SolanaAdapter** - Full Solana mainnet/devnet support with sponsored fees
676
+ - **EvmAdapter** - Base, Ethereum, and Arbitrum support via EIP-712 TransferWithAuthorization
677
+ - **Client SDK** (`@dexterai/x402/client`)
678
+ - `createX402Client()` - Wrapped fetch that auto-handles 402 responses
679
+ - Multi-wallet support via `WalletSet`
680
+ - Automatic adapter selection based on payment network
681
+ - **Server SDK** (`@dexterai/x402/server`)
682
+ - `createX402Server()` - Generate 402 responses and verify/settle payments
683
+ - `buildRequirements()` - Build PaymentRequired payloads
684
+ - `verifyPayment()` / `settlePayment()` - Facilitator integration
685
+ - Auto-fetches feePayer and decimals from facilitator
686
+ - **React Hooks** (`@dexterai/x402/react`)
687
+ - `useX402Payment()` - Complete payment state management
688
+ - Multi-wallet balance tracking
689
+ - Real-time connection status per network
690
+ - **Adapters** (`@dexterai/x402/adapters`)
691
+ - `ChainAdapter` interface for extensibility
692
+ - `createSolanaAdapter()` / `createEvmAdapter()` factories
693
+ - Balance fetching for USDC across chains
694
+ - Dual ESM/CJS builds with full TypeScript definitions
695
+ - Comprehensive documentation and examples
696
+
697
+ ### Technical Details
698
+ - Uses Dexter's public facilitator at `https://x402.dexter.cash`
699
+ - Solana: Sponsored fees via ComputeBudget instructions (12k CU limit, 1 microlamport priority)
700
+ - EVM: EIP-3009 TransferWithAuthorization for gasless token transfers
701
+ - v2 protocol only (header-based flow with `PAYMENT-REQUIRED` / `PAYMENT-SIGNATURE`)
702
+
703
+ ---
704
+
705
+ [Unreleased]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v3.4.0...HEAD
706
+ [3.4.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v3.2.0...v3.4.0
707
+ [3.1.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v3.0.0...v3.1.1
708
+ [3.0.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v2.0.0...v3.0.0
709
+ [2.0.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.9.4...v2.0.0
710
+ [1.9.4]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.9.3...v1.9.4
711
+ [1.9.3]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.9.2...v1.9.3
712
+ [1.9.2]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.9.1...v1.9.2
713
+ [1.9.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.9.0...v1.9.1
714
+ [1.9.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.8.2...v1.9.0
715
+ [1.8.2]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.8.1...v1.8.2
716
+ [1.8.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.8.0...v1.8.1
717
+ [1.8.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.7.2...v1.8.0
718
+ [1.7.2]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.7.1...v1.7.2
719
+ [1.7.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.7.0...v1.7.1
720
+ [1.7.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.6.6...v1.7.0
721
+ [1.6.6]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.6.5...v1.6.6
722
+ [1.6.5]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.6.4...v1.6.5
723
+ [1.6.4]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.5.5...v1.6.4
724
+ [1.5.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.4.1...v1.5.0
725
+ [1.4.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.4.0...v1.4.1
726
+ [1.4.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.3.1...v1.4.0
727
+ [1.3.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.3.0...v1.3.1
728
+ [1.3.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.2.5...v1.3.0
729
+ [1.2.4]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.2.1...v1.2.4
730
+ [1.2.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.2.0...v1.2.1
731
+ [1.2.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.1.0...v1.2.0
732
+ [1.1.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.0.4...v1.1.0
733
+ [1.0.4]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.0.3...v1.0.4
734
+ [1.0.3]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.0.2...v1.0.3
735
+ [1.0.2]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.0.1...v1.0.2
736
+ [1.0.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.0.0...v1.0.1
737
+ [1.0.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/releases/tag/v1.0.0
738
+
739
+ [`4d3e881`]: https://github.com/Dexter-DAO/dexter-x402-sdk/commit/4d3e881
740
+ [`58c7eea`]: https://github.com/Dexter-DAO/dexter-x402-sdk/commit/58c7eea