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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,749 @@
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.4] - 2026-08-17
11
+
12
+ ### Changed
13
+
14
+ - Rebound the exact Vault peer and development dependency to
15
+ `@dexterai/vault@0.43.3-rc.1`. This is a package-metadata-only successor to
16
+ rc.3 so the canonical archive-mode Vault release resolves as one dependency
17
+ graph; x402 runtime source and declarations are unchanged.
18
+
19
+ ## [6.0.0-rc.3] - 2026-08-17
20
+
21
+ ### Added
22
+
23
+ - Seller-owned delivery accounting is now restart- and takeover-safe. Every
24
+ channel lease carries an unguessable owner token plus a monotonically
25
+ increasing fence, and every ledger write, delete, renewal, and release is
26
+ conditional on that exact lease generation. The SSE meter writes each
27
+ charge to the fenced durable ledger before the corresponding service may be
28
+ sent; a crash can conservatively over-account an unsent unit, but cannot
29
+ erase delivered service and re-grant the same signed budget after restart.
30
+ - `RedisChannelLedger` declares explicit restart/multi-instance capabilities,
31
+ performs authoritative reads on the Redis primary, renews live leases, and
32
+ fails closed on store or lease loss. Its `restartSafe` capability requires
33
+ both the exact AOF-always/no-data-loss/noeviction/dedicated-instance
34
+ durability attestation and
35
+ `writerCutover: 'all-legacy-writers-stopped'`.
36
+
37
+ ### Changed
38
+
39
+ - Seller admission revalidates the current durable voucher registration,
40
+ cumulative amount, and reservation proof after acquiring the channel lease.
41
+ Delayed requests therefore cannot overwrite a newer process's accepted
42
+ state. A response that disconnects during proof work cannot later acquire or
43
+ indefinitely renew a lease.
44
+ - Channel IDs are canonical lowercase 64-character hex at seller admission.
45
+ Every bundled ledger adapter also rejects noncanonical IDs before acquiring
46
+ a lock or touching storage, so direct adapter calls cannot recreate aliases.
47
+ - `ledgerSafetyMode` must be explicit unless `NODE_ENV` is exactly `test` or
48
+ `development`. `production-single-instance` requires durable restart-safe
49
+ state plus the explicit channel-alias cutover; `production-multi-instance`
50
+ additionally requires cross-process atomic fencing. There is no production
51
+ fallback to memory or inferred single-process topology.
52
+
53
+ ### Breaking
54
+
55
+ - Custom `ChannelLedger` adapters must expose `capabilities`, use the new
56
+ `ChannelLease { ownerToken, fence, heldUntilUnixMs }`, accept that lease on
57
+ every `set`, `update`, and `delete`, and implement conditional
58
+ `tryAcquireLease`, `renewLease`, and `releaseLease`. Adapters must reject
59
+ stale owner/fence mutations instead of silently applying them, reject every
60
+ non-lowercase/non-64-hex channel ID before locking or storage access, and
61
+ declare `canonicalChannelIds` only after historical aliases are migrated or
62
+ the durable store is proven empty.
63
+ - `SseMeter.charge()` is asynchronous write-ahead accounting and must be
64
+ awaited before `send()`. Concurrent charges are serialized against the
65
+ signed cap; `send()` fails while a charge commit is pending.
66
+
67
+ ### Seller-ledger upgrade and Redis keyspace migration
68
+
69
+ - **Canonical channel-ID cutover is mandatory for every durable adapter.** The
70
+ voucher signature covers the decoded 32 channel bytes, so historical SDKs
71
+ could persist the same signed channel under multiple case spellings. Before
72
+ setting `channelIdCutover: 'legacy-case-aliases-migrated-or-empty'`, stop all
73
+ seller writers and enumerate every ledger, lease, and fence key/file. Group
74
+ records by lowercase channel ID; wait for and remove every alias lease;
75
+ require exact session/public-key/registration compatibility (otherwise
76
+ quarantine the entire group for manual review); retain the highest valid
77
+ signed voucher by cumulative then sequence; **sum** delivered cumulative
78
+ across distinct aliases; take the maximum crystallized and gate-refused
79
+ watermarks; and publish the lowercase fence at least one greater than the
80
+ maximum alias fence. Overcounting a copied duplicate is safer than re-granting
81
+ delivered service. Verify the canonical record before deleting every alias
82
+ ledger/lease/fence. A brand-new proven-empty store may acknowledge the same
83
+ cutover without migration. Production middleware refuses File, Redis, or a
84
+ custom adapter until `canonicalChannelIds` attests this invariant.
85
+
86
+ - The default Redis layout remains `legacy-v0` (`<prefix>ledger:<channelId>`,
87
+ `<prefix>lease:<channelId>`, `<prefix>fence:<channelId>`) so upgrading does
88
+ not hide existing ledger state or split leases during rollout. Before
89
+ asserting `writerCutover`, stop every pre-fencing seller process. A raw UUID
90
+ lease written by an older process is respected until it expires; the new
91
+ writer never renews or releases that foreign lease.
92
+ - `cluster-v1` is opt-in and never supports a rolling mixed-layout deploy. Stop
93
+ all seller writers; wait for every legacy lease to expire; copy every legacy
94
+ canonical lowercase ledger record and fence counter to the corresponding hash-tagged
95
+ `<prefix>{<channelId>}:ledger|fence` keys (preserving or increasing each
96
+ fence); verify the copies; then remove all legacy ledger, lease, and fence
97
+ keys. Only after that stop-the-world procedure may the service start with
98
+ `keyLayout: 'cluster-v1'`,
99
+ `keyspaceCutover: 'legacy-state-migrated-or-empty'`, the writer cutover, and
100
+ the channel-ID cutover acknowledgements. Runtime admission rejects any
101
+ legacy ledger, lease, or fence remnant rather than treating the new keyspace
102
+ as empty.
103
+
104
+ ## [6.0.0-rc.2] - 2026-08-16
105
+
106
+ ### Fixed
107
+
108
+ - 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.
109
+ - 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.
110
+
111
+ ### Unchanged safety boundaries
112
+
113
+ - 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.
114
+
115
+ ## [6.0.0-rc.1] - 2026-08-16
116
+
117
+ ### Fixed
118
+
119
+ - 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.
120
+ - 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.
121
+ - V2 terminal settlement sends the exact final reservation increment as `attemptedAmount`, rather than reconstructing it from the tab's lifetime cumulative amount.
122
+ - 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.
123
+
124
+ ### Changed
125
+
126
+ - 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.
127
+ - `@dexterai/vault` is pinned exactly to `0.43.2` for both peer and development use, including its corrected optional-account sentinel encoding.
128
+
129
+ ## [6.0.0-rc.0] - 2026-08-16
130
+
131
+ ### Added
132
+
133
+ - Context-bound FINAL V2 vouchers bind the Vault program, Vault, SessionAccount, seller, session generation, channel, cumulative amount, and sequence into the signed voucher.
134
+ - 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.
135
+ - Live-session replacement uses one guard-bound V7 passkey ceremony and one atomic replacement transaction.
136
+ - Tab voucher signing and close share one fail-fast local operation gate, preventing two concurrent calls from reserving the same sequence/cumulative frontier.
137
+
138
+ ### Changed
139
+
140
+ - `@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`.
141
+ - `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.
142
+ - 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.
143
+
144
+ ### Breaking
145
+
146
+ - V2 reservation callbacks now return the full `FinalVoucherV2ReservationReceipt`; boolean acknowledgements are invalid.
147
+ - V2 adapters must implement `verifyFinalVoucherV2Reservation`.
148
+ - 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.
149
+ - This release is a major-version candidate because its reservation, revocation, and adapter contracts are intentionally incompatible with the abandoned 5.4.3 candidate.
150
+
151
+ ### Compatibility notice
152
+
153
+ - 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.
154
+
155
+ ### Added (buyer, K-T4e)
156
+ - **`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.
157
+ - **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'`).
158
+ - `@dexterai/vault` peer/dev dependency raised `>=0.20.0` → `>=0.34.0` (the atomic compose primitive lives there).
159
+
160
+ ### Changed (seller, K-T3)
161
+ - **`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.
162
+ - **`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.
163
+
164
+ ## [4.0.0] - 2026-06-17
165
+
166
+ ### Removed (BREAKING)
167
+
168
+ 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.
169
+
170
+ - **client:** `createX402Client`, `wrapFetch` (and the `X402Client`, `X402ClientConfig`, `WrapFetchOptions` types). → `payAndFetch(url, init, wallets)`.
171
+ - **server:** `x402AccessPass`, `x402BrowserSupport`, `escapeHtml`. → `x402Middleware`.
172
+ - **server:** `createDynamicPricing`, `formatPricing`. → compute the price per request in your handler and pass it to `x402Middleware`.
173
+ - **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`.
174
+ - **server:** `stripePayTo`. → a `PayToProvider` map on `x402Middleware`. (`getStripeProviderNetwork` stays internal.)
175
+ - **react:** `useAccessPass`. → `useX402Payment`.
176
+
177
+ ### Notes
178
+
179
+ - **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.
180
+ - To stay on the old surface, pin `@dexterai/x402@^3`.
181
+
182
+ ## [3.9.0] - 2026-05-21
183
+
184
+ ### Documentation
185
+ - **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.
186
+
187
+ ### Fixed
188
+ - **`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.)
189
+ - **`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`.)
190
+ - **`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.
191
+
192
+ ### Deprecated
193
+
194
+ 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.
195
+
196
+ **Slated for removal in 4.0:**
197
+
198
+ - `x402AccessPass`, `X402AccessPassConfig`, `X402AccessPassRequest` (`@dexterai/x402/server`) — use per-request `x402Middleware` with `payAndFetch` clients.
199
+ - `useAccessPass`, `UseAccessPassConfig`, `UseAccessPassReturn` (`@dexterai/x402/react`) — use `useX402Payment` for per-request payments.
200
+ - `createDynamicPricing`, `formatPricing`, `DynamicPricingConfig`, `DynamicPricing`, `PriceQuote` (`@dexterai/x402/server`) — compute the price per request in your handler and pass it to `x402Middleware`.
201
+ - `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`.
202
+ - `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.
203
+ - `x402BrowserSupport`, `X402BrowserSupportConfig` (`@dexterai/x402/server`) — no replacement; build a custom paywall page if needed. (`escapeHtml` stays.)
204
+ - `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.
205
+
206
+ **Slated for removal in 5.0** (longer migration window because of real consumers):
207
+
208
+ - `createX402Client`, `X402Client`, `X402ClientConfig` (`@dexterai/x402/client`) — use `payAndFetch` instead. Migration: `client.fetch(url)` → `(await payAndFetch(url, undefined, wallets)).response`.
209
+ - `wrapFetch`, `WrapFetchOptions` (`@dexterai/x402/client`) — use `payAndFetch` with a wallet from `createKeypairWallet` / `createEvmKeypairWallet`.
210
+
211
+ `getPaymentReceipt` and `PaymentReceipt` (`@dexterai/x402/client`) are NOT deprecated.
212
+
213
+ ## [3.8.1] - 2026-05-20
214
+
215
+ ### Fixed
216
+ - **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.
217
+
218
+ ## [3.8.0] - 2026-05-20
219
+
220
+ 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.
221
+
222
+ ### Added
223
+ - **`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.
224
+ - **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.
225
+ - **`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`.
226
+ - **`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.
227
+ - **`X402MiddlewareConfig.extensions`** + **`X402MiddlewareConfig.declarations`** — new optional fields. Pair them to opt routes into the registry. Example:
228
+ ```ts
229
+ x402Middleware({
230
+ payTo, network, amount, facilitatorUrl,
231
+ extensions: [bazaarExtension()],
232
+ declarations: {
233
+ ...declareDiscoveryExtension({
234
+ method: 'POST',
235
+ bodyType: 'json',
236
+ inputSchema: { properties: { amount: { type: 'string' } }, required: ['amount'] },
237
+ output: { example: { campaign: {} } },
238
+ }),
239
+ },
240
+ });
241
+ ```
242
+ - **`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.
243
+ - **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.
244
+ - **Public exports** from `@dexterai/x402/server`: `bazaarExtension`, `declareDiscoveryExtension`, and the types `ResourceServerExtension`, `PaymentRequiredContext`, `DiscoveryConfig`, `QueryDiscoveryConfig`, `BodyDiscoveryConfig`, `DiscoveryExtension`, `DeclareDiscoveryConfig`.
245
+
246
+ ### Notes
247
+ - **Backward compatible.** A `x402Middleware` call with no `extensions`/`declarations` emits a 402 byte-identical to 3.7.8.
248
+ - **HTTP only in v1.** MCP-tool discovery (`input.type: "mcp"`) is intentionally out of scope — a clean discriminated-union add-on later.
249
+ - **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`.
250
+ - **272 tests passing.**
251
+
252
+ ## [3.7.4] - 2026-05-18
253
+
254
+ ### Fixed
255
+ - **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.
256
+
257
+ ## [3.7.3] - 2026-05-18
258
+
259
+ ### Fixed
260
+ - **`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.
261
+
262
+ ## [3.7.2] - 2026-05-18
263
+
264
+ Fixes a batch-settlement channel-identity bug: every channel between the same
265
+ buyer, seller, and token collided on a single deterministic id, so a buyer
266
+ could never open a second channel with a seller they had already used —
267
+ `openBatchChannel` would silently reopen the first, exhausted channel.
268
+
269
+ ### Fixed
270
+ - **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.
271
+
272
+ ### Added
273
+ - **`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.
274
+ - **`OpenBatchChannelOptions.salt`** (optional) — pass an explicit salt to deterministically reopen a specific channel; omit it for a fresh random one.
275
+
276
+ ### Breaking
277
+ - **`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.)
278
+
279
+ ## [3.4.0] - 2026-05-17
280
+
281
+ Batch settlement is now functional end-to-end. 3.3.0 shipped a batch-settlement
282
+ buyer but no seller runtime, so a seller had no way to collect the vouchers a
283
+ buyer signed. This release adds the seller runtime and corrects the buyer's
284
+ `channel.close()`.
285
+
286
+ ### Added
287
+ - **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? }`.
288
+ - **`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.
289
+ - **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.
290
+
291
+ ### Breaking
292
+ - **`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.)
293
+
294
+ ### Fixed
295
+ - **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).
296
+
297
+ ## [3.2.0] - 2026-05-03
298
+
299
+ 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.
300
+
301
+ ### Added
302
+ - `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.
303
+ - `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.
304
+ - `getExplorerUrl()` extended with explorer URL templates for the same set of chains (Polygonscan, Optimistic Etherscan, Snowtrace, BscScan, SKALE Base + SKALE Base Sepolia explorers).
305
+ - `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.
306
+ - 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.
307
+
308
+ ### Fixed
309
+ - 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.
310
+ - 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.
311
+
312
+ ### Changed
313
+ - 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`])
314
+ - `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.
315
+
316
+ ### Removed
317
+ - `X402ErrorCode` member `no_solana_accept` — zero callers anywhere in the ecosystem. ([`58c7eea`])
318
+ - `KeypairWallet.keypair` — the deprecated field that shadowed `KEYPAIR_SYMBOL`. `isKeypairWallet()` now checks the symbol directly. No external callers were found. ([`58c7eea`])
319
+ - 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`])
320
+
321
+ ## [3.1.1] - 2026-04-18
322
+
323
+ ### Changed
324
+ - `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.
325
+ - Build now minifies output and targets ES2022.
326
+ - Bumped `@dexterai/x402-ads-types` from `^0.1.0` to `^0.2.0`.
327
+
328
+ ### Added
329
+ - `@dexterai/x402-core` as a runtime dependency.
330
+
331
+ ### Removed
332
+ - Stale `tweet-thread-v2.md` draft marketing content from the repo root.
333
+
334
+ ## [3.0.0] - 2026-04-15
335
+
336
+ ### Breaking
337
+ - **`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).
338
+ - **`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`.
339
+ - **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`.
340
+ - **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.
341
+
342
+ ### Added
343
+ - **`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.
344
+ - **`NoMatchReason` type** — `'below_similarity_threshold' | 'below_strong_threshold' | null`. Callers can distinguish "corpus has zero candidates" from "candidates exist but none are high-confidence".
345
+ - **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.
346
+ - **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.
347
+
348
+ ### Migration
349
+ Replace the search call:
350
+ ```ts
351
+ // Before (2.x)
352
+ const results = await searchAPIs({ query: 'ETH price', category: 'data', maxPrice: 0.10 });
353
+ for (const api of results) { console.log(api.name, api.price); }
354
+
355
+ // After (3.0)
356
+ const result = await capabilitySearch({ query: 'ETH price' });
357
+ for (const api of result.strongResults) { console.log(api.name, api.price, api.why); }
358
+ if (result.strongCount === 0 && result.relatedCount > 0) {
359
+ // Fall back to related matches when nothing cleared the strong threshold
360
+ for (const api of result.relatedResults) { console.log('related:', api.name); }
361
+ }
362
+ ```
363
+
364
+ Filter semantically via the query text, not parameters:
365
+ - `searchAPIs({ category: 'defi' })` → `capabilitySearch({ query: 'DeFi tools' })`
366
+ - `searchAPIs({ network: 'solana' })` → `capabilitySearch({ query: 'on Solana' })` (or filter client-side via `pricing.network`)
367
+ - `searchAPIs({ maxPrice: 0.10 })` → filter the result array: `result.strongResults.filter(r => r.priceUsdc != null && r.priceUsdc <= 0.10)`
368
+
369
+ ## [2.0.0] - 2026-03-15
370
+
371
+ ### Breaking
372
+ - **`PaymentAccept.amount` is now required** — v2 spec field. `maxAmountRequired` is deprecated (optional alias for v1 compat).
373
+ - **`PaymentAccept.extra` is now optional** — per v2 spec.
374
+ - **`TokenPricing` methods are async** — `calculate()`, `validateQuote()`, `countTokens()` return Promises. `tiktoken` is now an optional peer dependency (lazy-loaded on first call).
375
+
376
+ ### Added
377
+ - **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.
378
+ - **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`.
379
+ - **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.
380
+ - **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.
381
+ - **Pre-payment inspection** — `onPaymentRequired` callback on client and `wrapFetch`. Return `false` to reject a payment before signing.
382
+ - **Settlement webhooks** — `onSettlement` and `onVerifyFailed` callbacks in middleware config.
383
+ - **CSP headers** on browser paywall page.
384
+ - **`KEYPAIR_SYMBOL`** for safe access to the Solana Keypair (Symbol-keyed, hidden from serialization).
385
+ - **`escapeHtml()`** exported from server for safe HTML rendering of payment data.
386
+ - **`isSolanaNetwork()` / `isEvmNetwork()`** utility functions.
387
+ - **New error codes** — `wallet_disconnected`, `user_rejected_signature`, `rpc_timeout`, `facilitator_timeout`.
388
+ - **Typed `WalletSet`** — `solana` and `evm` fields are now typed as `SolanaWallet` and `EvmWallet` instead of `unknown`.
389
+ - **52 unit tests** covering dynamic pricing, XSS escaping, USDC detection, type compliance, amount conversion, sponsored access.
390
+ - **CI/CD** — GitHub Actions: typecheck + build + test on Node 18/20/22. Publish workflow gated on tests, auto-creates GitHub releases.
391
+ - **`extensions` field** on `PaymentRequired` and `PaymentSignature` per v2 spec.
392
+ - **Auto GitHub releases** from tag pushes with changelog extraction.
393
+
394
+ ### Fixed
395
+ - **EVM nonce security** — `Math.random()` replaced with `crypto.getRandomValues()`.
396
+ - **Dynamic pricing security** — FNV-1a replaced with HMAC-SHA256 with timestamp-bounded quotes (5-min TTL).
397
+ - **Balance checks** throw on RPC errors instead of silently returning 0.
398
+ - **Resource URL validation** — blocks `javascript:`, `data:`, `file:` schemes.
399
+ - **Internal errors** no longer leaked to clients.
400
+ - **Stripe guard** uses WeakMap instead of fragile `as any` property.
401
+ - **Source maps removed** from production build (62% smaller package).
402
+ - **Client JWT cache** capped to 24h regardless of decoded `exp`.
403
+
404
+ ## [1.9.4] - 2026-03-15
405
+
406
+ ### Fixed
407
+ - **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.
408
+ - **Resource URLs validated for scheme** — client rejects `javascript:`, `data:`, and `file:` URLs from payment requirement headers. Only `http:` and `https:` are accepted.
409
+ - **Internal error details no longer leaked to clients** — middleware 500 responses now return generic `"Payment processing error"` without the underlying error message.
410
+ - **CSP headers on browser paywall** — the generated HTML paywall page now sets `Content-Security-Policy` and `X-Content-Type-Options: nosniff` headers.
411
+ - **Stripe PayTo documentation** — JSDoc now explicitly documents the Base-only limitation with a multi-chain workaround example.
412
+ - **sessionStorage risk documented** — `useAccessPass` JSDoc warns about XSS exposure of stored JWTs.
413
+
414
+ ### Added
415
+ - **`onSettlement` callback** in middleware config — called after every successful payment settlement for logging, analytics, or webhooks.
416
+ - **`onVerifyFailed` callback** in middleware config — called when payment verification fails for monitoring suspicious activity.
417
+ - **`onPaymentRequired` callback** in client config — pre-payment inspection hook. Return `false` to reject a payment before signing. Critical for agent budget controls.
418
+ - **`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.
419
+ - **New error codes** — `wallet_disconnected`, `user_rejected_signature`, `rpc_timeout`, `facilitator_timeout`.
420
+ - **`isSolanaNetwork()` and `isEvmNetwork()`** — exported utility functions for network detection, replacing duplicated `startsWith` checks.
421
+
422
+ ## [1.9.3] - 2026-03-15
423
+
424
+ ### Added
425
+ - **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.
426
+ - **`escapeHtml()` exported** from `@dexterai/x402/server` — the XSS escape function is now public and tested for consumers who render payment data in HTML.
427
+
428
+ ## [1.9.2] - 2026-03-15
429
+
430
+ ### Breaking
431
+ - **`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.
432
+ - **`TokenPricingConfig.tokenizer` now accepts async functions** — type changed from `(text: string) => number` to `(text: string) => number | Promise<number>`. Existing sync tokenizers still work.
433
+
434
+ ### Fixed
435
+ - **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.
436
+ - **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.
437
+
438
+ ### Added
439
+ - **EVM wallet Quick Start in README** — `wrapFetch` with `evmPrivateKey` is now documented alongside the Solana example in the Quick Start section.
440
+
441
+ ## [1.9.1] - 2026-03-15
442
+
443
+ ### Added
444
+ - **First-class Sponsored Access (Ads for Agents)** — typed helpers for consuming sponsored recommendations from x402 payment receipts:
445
+ - `getSponsoredRecommendations(response)` — extract typed `SponsoredRecommendation[]` from a payment response
446
+ - `getSponsoredAccessInfo(response)` — extract the full `SponsoredAccessSettlementInfo` extension data
447
+ - `fireImpressionBeacon(response)` — fire-and-forget delivery confirmation to the ad network
448
+ - **React hook support** — `useX402Payment` now returns `sponsoredRecommendations` (auto-populated after payment, auto-fires impression beacon)
449
+ - **Server `onMatch` callback** — `sponsoredAccess: { onMatch: (recs, settlement) => ... }` for server-side logging/analytics when recommendations are delivered
450
+ - **Typed middleware injection** — `sponsoredAccess.inject` callback now receives typed `SponsoredRecommendation[]` instead of `unknown[]`
451
+ - **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`
452
+
453
+ ### Changed
454
+ - **`@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).
455
+
456
+ ## [1.9.0] - 2026-03-15
457
+
458
+ ### Breaking
459
+ - **`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.
460
+ - **`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`.
461
+
462
+ ### Fixed
463
+ - **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.
464
+ - **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.
465
+ - **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).
466
+
467
+ ### Added
468
+ - **`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.
469
+
470
+ ## [1.8.2] - 2026-03-11
471
+
472
+ ### Fixed
473
+ - **`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.
474
+
475
+ ## [1.8.1] - 2026-03-11
476
+
477
+ ### Fixed
478
+ - **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.
479
+
480
+ ## [1.8.0] - 2026-03-10
481
+
482
+ ### Breaking
483
+ - **`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.
484
+
485
+ ### Fixed
486
+ - **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.
487
+ - **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.
488
+ - **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.
489
+ - **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.
490
+ - **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).
491
+ - **Stripe type safety** — Stripe client, PaymentIntent response, and crypto options are now typed via `import('stripe')` instead of `any`.
492
+
493
+ ### Added
494
+ - **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.
495
+ - **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).
496
+ - **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`.
497
+ - **`getPaymentReceipt(response)`** — Typed helper (backed by `WeakMap`) replaces the `(response as any)._x402` pattern. Exported from `@dexterai/x402/client`.
498
+ - **`@dexterai/x402-ads-types`** — Added as an optional peer dependency for typed sponsored-access extensions. No inlining; single source of truth.
499
+ - **New chain constants** — Exported `POLYGON`, `OPTIMISM`, `AVALANCHE`, `SKALE_BASE`, `SKALE_BASE_SEPOLIA`, `USDC_ADDRESSES` from `@dexterai/x402/adapters`.
500
+
501
+ ## [1.7.2] - 2026-02-28
502
+
503
+ ### Added
504
+ - **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.
505
+ - `SettleResponse.extensions` field for protocol extensions
506
+
507
+ ## [1.7.1] - 2026-02-25
508
+
509
+ ### Fixed
510
+ - Added `@types/aws-lambda` to fix DTS build errors
511
+ - Updated npm metadata and package description
512
+
513
+ ## [1.7.0] - 2026-02-20
514
+
515
+ ### Added
516
+ - OpenDexter marketplace auto-discovery section in README
517
+ - Updated header with marketplace links
518
+
519
+ ## [1.6.6] - 2026-02-12
520
+
521
+ ### Fixed
522
+ - Unicode-safe base64 encoding for server-side `btoa`/`atob`
523
+
524
+ ## [1.6.5] - 2026-02-10
525
+
526
+ ### Fixed
527
+ - **`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.
528
+
529
+ ## [1.6.4] - 2026-02-10
530
+
531
+ ### Fixed
532
+ - **`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).
533
+
534
+ ### Added
535
+ - **`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.
536
+
537
+ ## [1.5.0] - 2026-02-09
538
+
539
+ ### Added
540
+ - **Access Pass** — New payment pattern: pay once, get a time-limited JWT for unlimited API requests. Works with both SVM and EVM.
541
+ - **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.
542
+ - **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.
543
+ - **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.
544
+ - New types: `AccessPassTier`, `AccessPassInfo`, `AccessPassClaims`, `AccessPassClientConfig`
545
+ - New error codes: `access_pass_expired`, `access_pass_invalid`, `access_pass_tier_not_found`, `access_pass_exceeds_max_spend`
546
+ - New HTTP headers: `X-ACCESS-PASS-TIERS` (server -> client on 402), `ACCESS-PASS` (server -> client on pass purchase)
547
+ - `test/access-pass.ts` — 8-assertion test suite covering the full access pass lifecycle
548
+
549
+ ## [1.4.1] - 2026-02-09
550
+
551
+ ### Fixed
552
+ - **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.
553
+ - **`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.
554
+ - **`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.
555
+
556
+ ### Added
557
+ - `test/v2-spec-compliance.ts` — Automated test suite validating all three v2 spec compliance fixes against a mock facilitator (6 assertions).
558
+
559
+ ## [1.4.0] - 2026-01-11
560
+
561
+ ### Added
562
+ - **Model Registry** - Comprehensive single source of truth for all OpenAI models (`model-registry.ts`)
563
+ - 25 models across 5 tiers: fast, standard, reasoning, premium, specialized
564
+ - Complete pricing data from OpenAI (January 2026)
565
+ - GPT-5 family: gpt-5-nano, gpt-5-mini, gpt-5, gpt-5.1, gpt-5.2, gpt-5-pro, gpt-5.2-pro
566
+ - Reasoning models: o1, o1-mini, o1-pro, o3, o3-mini, o3-pro, o4-mini
567
+ - Specialized: deep-research, computer-use-preview, realtime models
568
+ - **Registry API**:
569
+ - `MODEL_REGISTRY` - Full model definitions with pricing, capabilities, and API parameters
570
+ - `getModel(id)` - Get model by ID (throws if not found)
571
+ - `findModel(id)` - Get model by ID (returns undefined if not found)
572
+ - `getModelsByTier(tier)` - Get all models in a tier
573
+ - `getModelsByFamily(family)` - Get models by family (gpt-5, o3, etc.)
574
+ - `getTextModels()` - Get all text-capable models for chat completions
575
+ - `getActiveModels()` - Get all non-deprecated models
576
+ - `getCheapestModel(minTier?)` - Find cheapest model meeting requirements
577
+ - `estimateCost(modelId, inputTokens, outputTokens)` - Calculate request cost
578
+ - **Model Parameters** - Each model specifies API compatibility:
579
+ - `usesMaxCompletionTokens` - GPT-5/reasoning models require this instead of `max_tokens`
580
+ - `supportsTemperature` - GPT-5 models only support default (1)
581
+ - `supportsReasoningEffort` - For o-series models
582
+ - `supportsTools`, `supportsStructuredOutput`, `supportsStreaming`
583
+
584
+ ### Changed
585
+ - `token-pricing.ts` now uses `MODEL_REGISTRY` as its data source (no more duplicate pricing)
586
+ - `getAvailableModels()` returns models sorted by tier then price
587
+
588
+ ### Developer Tools
589
+ - **Model Evaluation Harness** (`test/model-eval/`) - CLI for testing models
590
+ - Test prompts across multiple models simultaneously
591
+ - Compare response quality, timing, and costs
592
+ - Context injection from files (`--context`)
593
+ - Full output logging with metrics
594
+
595
+ ## [1.3.1] - 2025-01-10
596
+
597
+ ### Fixed
598
+ - Minor type exports cleanup
599
+
600
+ ## [1.3.0] - 2025-01-09
601
+
602
+ ### Changed
603
+ - Internal refactoring for model pricing
604
+
605
+ ## [1.2.4] - 2024-12-30
606
+
607
+ ### Fixed
608
+ - **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.
609
+ - Added `x402Version` field to `PaymentAccept` type
610
+
611
+ ## [1.2.1] - 2024-12-28
612
+
613
+ ### Added
614
+ - **Custom model support** for `createTokenPricing()`:
615
+ - `inputRate` - Custom USD per 1M input tokens (for Anthropic, Gemini, Mistral, etc.)
616
+ - `outputRate` - Custom USD per 1M output tokens
617
+ - `maxTokens` - Custom max output tokens
618
+ - `tokenizer` - Custom tokenizer function for non-OpenAI models
619
+ - `'custom'` tier for user-defined pricing
620
+
621
+ ### Changed
622
+ - `createDynamicPricing()` documentation clarified: works for ANY pricing scenario, not just LLM
623
+ - README now shows examples for Anthropic Claude, Google Gemini, and local models
624
+
625
+ ## [1.2.0] - 2024-12-28
626
+
627
+ ### Added
628
+ - **Token Pricing** - `createTokenPricing()` for accurate LLM pricing using tiktoken
629
+ - Uses real OpenAI model rates (gpt-4o-mini, gpt-4o, o1, o3, etc.)
630
+ - `MODEL_PRICING` - Complete pricing table for 20+ models across fast/standard/reasoning/premium tiers
631
+ - `countTokens()` - Accurate token counting using OpenAI's tiktoken
632
+ - `getAvailableModels()` - List all models sorted by tier and price
633
+ - `isValidModel()` - Check if a model is supported
634
+ - `formatTokenPricing()` - Display helper (e.g., "$0.15 per 1M tokens")
635
+
636
+ ### Changed
637
+ - Dynamic pricing now has two variants:
638
+ - `createDynamicPricing()` - Character-based (generic, no deps)
639
+ - `createTokenPricing()` - Token-based (LLM-accurate, uses tiktoken)
640
+
641
+ ## [1.1.0] - 2024-12-27
642
+
643
+ ### Added
644
+ - **Dynamic Pricing** - `createDynamicPricing()` for LLM/AI endpoints where cost scales with input
645
+ - Quote hash validation prevents prompt manipulation (includes pricing config in hash)
646
+ - `formatPricing()` helper for display strings
647
+ - Client SDK now forwards `X-Quote-Hash` header on retry
648
+
649
+ ## [1.0.4] - 2024-12-27
650
+
651
+ ### Changed
652
+ - **README overhaul** - Professional documentation with live demo links, emoji formatting, and clear API reference
653
+ - Prominent link to [dexter.cash/sdk](https://dexter.cash/sdk) for live verification
654
+
655
+ ## [1.0.3] - 2024-12-27
656
+
657
+ ### Added
658
+ - **Utils export** - `toAtomicUnits()` and `fromAtomicUnits()` now available via `@dexterai/x402/utils`
659
+ - `getChainFamily()`, `getChainName()`, `getExplorerUrl()` helpers
660
+
661
+ ### Changed
662
+ - README now includes notice that server SDK is not yet battle-tested
663
+
664
+ ## [1.0.2] - 2024-12-27
665
+
666
+ ### Added
667
+ - **Pre-flight balance check** - SDK now checks USDC balance before signing transactions
668
+ - `insufficient_balance` error code with clear message: "Insufficient USDC balance on [Network]. Have $X, need $Y"
669
+
670
+ ### Fixed
671
+ - Prevents confusing "Payment was rejected by the server: {}" error when user has insufficient funds
672
+ - Users now see a clear, actionable error message before wallet popup appears
673
+
674
+ ## [1.0.1] - 2024-12-26
675
+
676
+ ### Fixed
677
+ - EVM adapter payload structure now correctly separates `authorization` and `signature` fields to match upstream `@x402/evm` format
678
+ - Removed unnecessary `feePayer` validation for EVM networks (users pay their own gas)
679
+
680
+ ## [1.0.0] - 2024-12-26
681
+
682
+ ### Added
683
+ - **Chain-agnostic architecture** - Support for multiple blockchains through adapter pattern
684
+ - **SolanaAdapter** - Full Solana mainnet/devnet support with sponsored fees
685
+ - **EvmAdapter** - Base, Ethereum, and Arbitrum support via EIP-712 TransferWithAuthorization
686
+ - **Client SDK** (`@dexterai/x402/client`)
687
+ - `createX402Client()` - Wrapped fetch that auto-handles 402 responses
688
+ - Multi-wallet support via `WalletSet`
689
+ - Automatic adapter selection based on payment network
690
+ - **Server SDK** (`@dexterai/x402/server`)
691
+ - `createX402Server()` - Generate 402 responses and verify/settle payments
692
+ - `buildRequirements()` - Build PaymentRequired payloads
693
+ - `verifyPayment()` / `settlePayment()` - Facilitator integration
694
+ - Auto-fetches feePayer and decimals from facilitator
695
+ - **React Hooks** (`@dexterai/x402/react`)
696
+ - `useX402Payment()` - Complete payment state management
697
+ - Multi-wallet balance tracking
698
+ - Real-time connection status per network
699
+ - **Adapters** (`@dexterai/x402/adapters`)
700
+ - `ChainAdapter` interface for extensibility
701
+ - `createSolanaAdapter()` / `createEvmAdapter()` factories
702
+ - Balance fetching for USDC across chains
703
+ - Dual ESM/CJS builds with full TypeScript definitions
704
+ - Comprehensive documentation and examples
705
+
706
+ ### Technical Details
707
+ - Uses Dexter's public facilitator at `https://x402.dexter.cash`
708
+ - Solana: Sponsored fees via ComputeBudget instructions (12k CU limit, 1 microlamport priority)
709
+ - EVM: EIP-3009 TransferWithAuthorization for gasless token transfers
710
+ - v2 protocol only (header-based flow with `PAYMENT-REQUIRED` / `PAYMENT-SIGNATURE`)
711
+
712
+ ---
713
+
714
+ [Unreleased]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v3.4.0...HEAD
715
+ [3.4.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v3.2.0...v3.4.0
716
+ [3.1.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v3.0.0...v3.1.1
717
+ [3.0.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v2.0.0...v3.0.0
718
+ [2.0.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.9.4...v2.0.0
719
+ [1.9.4]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.9.3...v1.9.4
720
+ [1.9.3]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.9.2...v1.9.3
721
+ [1.9.2]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.9.1...v1.9.2
722
+ [1.9.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.9.0...v1.9.1
723
+ [1.9.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.8.2...v1.9.0
724
+ [1.8.2]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.8.1...v1.8.2
725
+ [1.8.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.8.0...v1.8.1
726
+ [1.8.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.7.2...v1.8.0
727
+ [1.7.2]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.7.1...v1.7.2
728
+ [1.7.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.7.0...v1.7.1
729
+ [1.7.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.6.6...v1.7.0
730
+ [1.6.6]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.6.5...v1.6.6
731
+ [1.6.5]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.6.4...v1.6.5
732
+ [1.6.4]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.5.5...v1.6.4
733
+ [1.5.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.4.1...v1.5.0
734
+ [1.4.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.4.0...v1.4.1
735
+ [1.4.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.3.1...v1.4.0
736
+ [1.3.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.3.0...v1.3.1
737
+ [1.3.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.2.5...v1.3.0
738
+ [1.2.4]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.2.1...v1.2.4
739
+ [1.2.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.2.0...v1.2.1
740
+ [1.2.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.1.0...v1.2.0
741
+ [1.1.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.0.4...v1.1.0
742
+ [1.0.4]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.0.3...v1.0.4
743
+ [1.0.3]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.0.2...v1.0.3
744
+ [1.0.2]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.0.1...v1.0.2
745
+ [1.0.1]: https://github.com/Dexter-DAO/dexter-x402-sdk/compare/v1.0.0...v1.0.1
746
+ [1.0.0]: https://github.com/Dexter-DAO/dexter-x402-sdk/releases/tag/v1.0.0
747
+
748
+ [`4d3e881`]: https://github.com/Dexter-DAO/dexter-x402-sdk/commit/4d3e881
749
+ [`58c7eea`]: https://github.com/Dexter-DAO/dexter-x402-sdk/commit/58c7eea