@piprail/sdk 1.12.0 → 1.13.1

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 CHANGED
@@ -4,6 +4,37 @@ All notable changes to `@piprail/sdk` are documented here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
5
5
  versions follow [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [1.13.1] — 2026-06-10
8
+
9
+ ### Fixed
10
+ - **`register()` visibility is now accurate for a verified domain.** 402 Index returns a structured
11
+ `service.status` — a register from a domain you've verified comes back `'active'`, so the outcome now
12
+ reports `visibility:'live'` instead of the conservative `'pending-review'` default (`decorateOutcome`
13
+ honours a visibility the adapter already set). The `detail` already surfaced 402 Index's own message;
14
+ now `visibility` and `detail` agree.
15
+
16
+ ## [1.13.0] — 2026-06-10
17
+
18
+ ### Added — gate `discovery` option (one flag → x402scan-listable)
19
+ - **`createPaymentGate`/`requirePayment` now take an opt-in `discovery` option** that emits an
20
+ `extensions.bazaar` block **in the 402 challenge itself** — so the gate alone satisfies x402scan's
21
+ mandatory input-schema check (no separately-served file needed). `discovery: true` for a no-input
22
+ GET, or a `DiscoveryDescriptor` (`{ method, queryParams, output }`). Omitting it leaves the challenge
23
+ byte-identical. New export `buildBazaarExtension` + the `DiscoveryDescriptor`/`BazaarExtension` types.
24
+ - `DomainClaim` now also surfaces `verificationToken`, and `verificationHash` is **always** populated —
25
+ from the API, or computed as `sha256(verificationToken)` — so an agent always has the exact bytes to serve.
26
+
27
+ ### Fixed — conformance bug hunt (20-agent audit, every finding verified against the live API)
28
+ - **`hostOf` returned `''` for a bare `host:port`** (`new URL('x.com:8080')` parses the host as a scheme) —
29
+ `claimDomain`/`verifyDomain` now extract the host correctly.
30
+ - **`indexes.ts` base64 was Latin1-only** (`btoa`) — now UTF-8-safe, matching `x402.ts` (a non-ASCII SIWX
31
+ field could have thrown in the browser).
32
+ - **SIWX message** now reads `chainId` from `supportedChains[]` as a fallback (not only `info.chainId`),
33
+ so it always signs the correct `Chain ID`.
34
+ - Removed an invented `x-payment-info.bazaar:{discoverable:true}` marker from `buildOpenApi` (no index read it).
35
+ - Documented the caveat that the open indexes' agents are standard `exact` clients — advertise an `exact`
36
+ rail to be *payable*, not just listed (README + the `piprail_register` MCP tool).
37
+
7
38
  ## [1.12.0] — 2026-06-09
8
39
 
9
40
  ### Added — One-call domain verification (pending-review → searchable)
@@ -593,6 +624,8 @@ straight into your wallet. The API is small and self-contained.
593
624
  to your wallet; PipRail never holds funds.
594
625
  - `viem ^2.21` is a peer dependency. Node 20+ or a modern browser.
595
626
 
627
+ [1.13.1]: https://www.npmjs.com/package/@piprail/sdk
628
+ [1.13.0]: https://www.npmjs.com/package/@piprail/sdk
596
629
  [1.12.0]: https://www.npmjs.com/package/@piprail/sdk
597
630
  [1.11.0]: https://www.npmjs.com/package/@piprail/sdk
598
631
  [1.10.0]: https://www.npmjs.com/package/@piprail/sdk
package/README.md CHANGED
@@ -206,22 +206,34 @@ listing won't appear here, so don't read that absence as failure. `network` defa
206
206
  chain); pass `'any'` for every chain, or a CAIP-2 id (`'eip155:8453'`). Slugs map to CAIP-2 via
207
207
  `SLUG_TO_CAIP2`; an unresolved network is kept, never hidden.
208
208
 
209
- **4) Make your endpoint self-describing** turn your gate's config into the artifacts a crawler reads
210
- (pure, no I/O); serve them on **your own** origin. **x402scan REQUIRES** a resolvable input schema (your
211
- `/openapi.json` or an `extensions.bazaar` block in the 402 body), so this is what makes an x402scan
212
- listing accepted:
209
+ **4) Make your endpoint self-describing.** **x402scan REQUIRES an input schema or it won't list you.**
210
+ The simplest path: set the gate's `discovery` option it emits an `extensions.bazaar` block **in the 402
211
+ itself**, so the challenge alone is x402scan-listable (no extra file to serve):
213
212
 
214
213
  ```ts
215
- import { createPaymentGate, buildOpenApi, buildWellKnownX402, buildX402DnsTxt } from '@piprail/sdk'
214
+ // `discovery: true` for a no-input GET, or describe the request:
215
+ const gate = createPaymentGate({
216
+ chain: 'base', token: 'USDC', amount: '0.05', payTo,
217
+ exact: { settle: { facilitator: 'https://facilitator.payai.network' } }, // be payable by exact clients (see caveat)
218
+ discovery: { method: 'GET', output: { type: 'json', example: { ok: true } } },
219
+ })
220
+ ```
216
221
 
217
- const gate = createPaymentGate({ chain: 'base', token: 'USDC', amount: '0.05', payTo })
222
+ Optionally also serve the static discovery files (richer listings; OpenAPI carries the `x-generator`
223
+ attribution stamp) from your own origin — all pure, no I/O:
224
+
225
+ ```ts
226
+ import { buildOpenApi, buildWellKnownX402, buildX402DnsTxt } from '@piprail/sdk'
218
227
  const desc = await gate.describe('https://api.example.com/report')
219
- const openapi = buildOpenApi({ origin: 'https://api.example.com', resources: [desc] })
220
- // serve at /openapi.json each priced op carries an `x-payment-info` block + a root `x-generator` stamp.
221
- const wellKnown = buildWellKnownX402({ resources: [desc] }) // serve at /.well-known/x402
228
+ const openapi = buildOpenApi({ origin: 'https://api.example.com', resources: [desc] }) // → /openapi.json
229
+ const wellKnown = buildWellKnownX402({ resources: [desc] }) // /.well-known/x402
222
230
  // buildX402DnsTxt(...) emits the _x402 DNS line too.
223
231
  ```
224
232
 
233
+ > **Caveat — be *payable*, not just listed.** The open indexes' agents are overwhelmingly standard
234
+ > `exact` clients; a default `onchain-proof`-only gate gets listed but they can't pay it. Advertise an
235
+ > `exact` rail (above) so a discovered resource is actually payable.
236
+
225
237
  **Know each index before you call** — the facts are one import, `DIRECTORY_INFO`, and `register()`
226
238
  projects them onto every outcome (`visibility` + `note`), so an agent never has to guess:
227
239
 
package/dist/index.cjs CHANGED
@@ -1339,7 +1339,7 @@ function getDirectoryInfo(source) {
1339
1339
  }
1340
1340
  function decorateOutcome(o) {
1341
1341
  const info = DIRECTORY_INFO[o.source];
1342
- return { ...o, visibility: o.ok ? info.onSuccess : "not-listable", note: info.caveat };
1342
+ return { ...o, visibility: _nullishCoalesce(o.visibility, () => ( (o.ok ? info.onSuccess : "not-listable"))), note: info.caveat };
1343
1343
  }
1344
1344
  var BAZAAR_URL = "https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources";
1345
1345
  var INDEX402_SEARCH = "https://402index.io/api/v1/services";
@@ -1495,12 +1495,15 @@ async function register402Index(input) {
1495
1495
  body: JSON.stringify(payload)
1496
1496
  });
1497
1497
  if (res.ok) {
1498
- const msg = await readIndexMessage(res);
1498
+ const body = await res.json().catch(() => ({}));
1499
+ const msg = typeof body.message === "string" && body.message.length > 0 ? body.message : void 0;
1500
+ const live = _optionalChain([body, 'access', _8 => _8.service, 'optionalAccess', _9 => _9.status]) === "active";
1499
1501
  return {
1500
1502
  source: "402index",
1501
1503
  ok: true,
1502
1504
  status: res.status,
1503
- detail: _nullishCoalesce(msg, () => ( "Registered on 402 Index \u2014 pending review (verify your domain on 402index.io for instant approval)."))
1505
+ ...live ? { visibility: "live" } : {},
1506
+ detail: _nullishCoalesce(msg, () => ( (live ? "Registered + live on 402 Index (domain verified)." : "Registered on 402 Index \u2014 pending review (verify your domain on 402index.io for instant approval).")))
1504
1507
  };
1505
1508
  }
1506
1509
  const why = await readIndexError(res);
@@ -1514,14 +1517,6 @@ async function register402Index(input) {
1514
1517
  return { source: "402index", ok: false, detail: errMsg(err) };
1515
1518
  }
1516
1519
  }
1517
- async function readIndexMessage(res) {
1518
- try {
1519
- const body = await res.json();
1520
- return typeof body.message === "string" && body.message.length > 0 ? body.message : void 0;
1521
- } catch (e17) {
1522
- return void 0;
1523
- }
1524
- }
1525
1520
  async function readIndexError(res) {
1526
1521
  try {
1527
1522
  const body = await res.json();
@@ -1529,7 +1524,7 @@ async function readIndexError(res) {
1529
1524
  (p) => typeof p === "string" && p.length > 0
1530
1525
  );
1531
1526
  return parts.length ? [...new Set(parts)].join(" \u2014 ") : void 0;
1532
- } catch (e18) {
1527
+ } catch (e17) {
1533
1528
  return void 0;
1534
1529
  }
1535
1530
  }
@@ -1593,11 +1588,14 @@ async function claim402IndexDomain(domainOrUrl, opts = {}) {
1593
1588
  if (!res.ok) {
1594
1589
  return { ok: false, domain, httpStatus: res.status, detail: _nullishCoalesce(pickString(body, "error", "detail", "message"), () => ( `402 Index claim returned HTTP ${res.status}.`)) };
1595
1590
  }
1591
+ const verificationToken = pickString(body, "verification_token");
1592
+ const verificationHash = await _asyncNullishCoalesce(pickString(body, "verification_hash"), async () => ( (verificationToken ? await sha256Hex(verificationToken) : void 0)));
1596
1593
  return {
1597
1594
  ok: true,
1598
1595
  domain,
1599
1596
  httpStatus: res.status,
1600
- ...optionalString("verificationHash", pickString(body, "verification_hash")),
1597
+ ...optionalString("verificationHash", verificationHash),
1598
+ ...optionalString("verificationToken", verificationToken),
1601
1599
  ...optionalString("verificationUrl", pickString(body, "verification_url")),
1602
1600
  ...optionalString("instructions", pickString(body, "instructions"))
1603
1601
  };
@@ -1605,6 +1603,10 @@ async function claim402IndexDomain(domainOrUrl, opts = {}) {
1605
1603
  return { ok: false, domain, detail: errMsg(err) };
1606
1604
  }
1607
1605
  }
1606
+ async function sha256Hex(input) {
1607
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
1608
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
1609
+ }
1608
1610
  async function verify402IndexDomain(domainOrUrl) {
1609
1611
  const domain = hostOf(domainOrUrl);
1610
1612
  try {
@@ -1632,13 +1634,19 @@ async function readSiwxInfo(res) {
1632
1634
  try {
1633
1635
  const body = await res.json();
1634
1636
  const ext = body.extensions;
1635
- const siwx = _optionalChain([ext, 'optionalAccess', _8 => _8["sign-in-with-x"]]);
1636
- const info = _nullishCoalesce(_optionalChain([siwx, 'optionalAccess', _9 => _9.info]), () => ( siwx));
1637
+ const siwx = _optionalChain([ext, 'optionalAccess', _10 => _10["sign-in-with-x"]]);
1638
+ const info = _nullishCoalesce(_optionalChain([siwx, 'optionalAccess', _11 => _11.info]), () => ( siwx));
1639
+ if (info && info.chainId == null && Array.isArray(_optionalChain([siwx, 'optionalAccess', _12 => _12.supportedChains]))) {
1640
+ const evm = siwx.supportedChains.find(
1641
+ (c) => typeof _optionalChain([c, 'optionalAccess', _13 => _13.chainId]) === "string" && c.chainId.startsWith("eip155:")
1642
+ );
1643
+ if (evm && typeof evm.chainId === "string") info.chainId = evm.chainId;
1644
+ }
1637
1645
  if (info && typeof info.domain === "string" && info.domain.length > 0 && typeof info.nonce === "string" && info.nonce.length > 0 && typeof info.uri === "string" && info.uri.length > 0) {
1638
1646
  return info;
1639
1647
  }
1640
1648
  return null;
1641
- } catch (e19) {
1649
+ } catch (e18) {
1642
1650
  return null;
1643
1651
  }
1644
1652
  }
@@ -1686,7 +1694,7 @@ function mapRails(accepts) {
1686
1694
  }
1687
1695
  function matchesQuery(r, query) {
1688
1696
  const q = query.toLowerCase();
1689
- return r.resource.toLowerCase().includes(q) || (_nullishCoalesce(_optionalChain([r, 'access', _10 => _10.name, 'optionalAccess', _11 => _11.toLowerCase, 'call', _12 => _12(), 'access', _13 => _13.includes, 'call', _14 => _14(q)]), () => ( false))) || (_nullishCoalesce(_optionalChain([r, 'access', _15 => _15.description, 'optionalAccess', _16 => _16.toLowerCase, 'call', _17 => _17(), 'access', _18 => _18.includes, 'call', _19 => _19(q)]), () => ( false)));
1697
+ return r.resource.toLowerCase().includes(q) || (_nullishCoalesce(_optionalChain([r, 'access', _14 => _14.name, 'optionalAccess', _15 => _15.toLowerCase, 'call', _16 => _16(), 'access', _17 => _17.includes, 'call', _18 => _18(q)]), () => ( false))) || (_nullishCoalesce(_optionalChain([r, 'access', _19 => _19.description, 'optionalAccess', _20 => _20.toLowerCase, 'call', _21 => _21(), 'access', _22 => _22.includes, 'call', _23 => _23(q)]), () => ( false)));
1690
1698
  }
1691
1699
  function pickString(o, ...keys) {
1692
1700
  for (const k of keys) {
@@ -1717,8 +1725,9 @@ function firstArray(o, ...keys) {
1717
1725
  }
1718
1726
  function hostOf(url) {
1719
1727
  try {
1720
- return new URL(url).hostname;
1721
- } catch (e20) {
1728
+ const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(url) ? url : `https://${url}`;
1729
+ return new URL(withScheme).hostname || url;
1730
+ } catch (e19) {
1722
1731
  return url;
1723
1732
  }
1724
1733
  }
@@ -1726,8 +1735,13 @@ function errMsg(err) {
1726
1735
  return err instanceof Error ? err.message : String(err);
1727
1736
  }
1728
1737
  function encodeBase642(str) {
1729
- if (typeof btoa === "function") return btoa(str);
1730
1738
  if (typeof Buffer !== "undefined") return Buffer.from(str, "utf8").toString("base64");
1739
+ if (typeof btoa === "function" && typeof TextEncoder !== "undefined") {
1740
+ const bytes = new TextEncoder().encode(str);
1741
+ let binary = "";
1742
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
1743
+ return btoa(binary);
1744
+ }
1731
1745
  throw new Error("No base64 encoder available in this runtime.");
1732
1746
  }
1733
1747
 
@@ -1824,7 +1838,7 @@ var SpendLedger = (_class = class {constructor() { _class.prototype.__init.call(
1824
1838
  }
1825
1839
  /** Running total (base units) already spent on this (network, asset). */
1826
1840
  totalFor(network, asset) {
1827
- return _nullishCoalesce(_optionalChain([this, 'access', _20 => _20.buckets, 'access', _21 => _21.get, 'call', _22 => _22(keyFor(network, asset)), 'optionalAccess', _23 => _23.total]), () => ( 0n));
1841
+ return _nullishCoalesce(_optionalChain([this, 'access', _24 => _24.buckets, 'access', _25 => _25.get, 'call', _26 => _26(keyFor(network, asset)), 'optionalAccess', _27 => _27.total]), () => ( 0n));
1828
1842
  }
1829
1843
  /** An immutable snapshot of all spend so far. */
1830
1844
  summary() {
@@ -1873,7 +1887,7 @@ var PipRailClient = (_class2 = class {
1873
1887
  safeEmit(event) {
1874
1888
  try {
1875
1889
  this.onEvent(event);
1876
- } catch (e21) {
1890
+ } catch (e20) {
1877
1891
  }
1878
1892
  }
1879
1893
  /** Auto-mount the chain's driver, resolve the network, and bind the wallet — once. */
@@ -1898,7 +1912,7 @@ var PipRailClient = (_class2 = class {
1898
1912
  * as-is) or a plain object (serialised as JSON).
1899
1913
  */
1900
1914
  post(url, body, init) {
1901
- const headers = new Headers(_optionalChain([init, 'optionalAccess', _24 => _24.headers]));
1915
+ const headers = new Headers(_optionalChain([init, 'optionalAccess', _28 => _28.headers]));
1902
1916
  let payload;
1903
1917
  if (body === void 0 || body === null) {
1904
1918
  payload = void 0;
@@ -1929,7 +1943,7 @@ var PipRailClient = (_class2 = class {
1929
1943
  * "0.05 USDC on Base, within budget → pay it." No funds move.
1930
1944
  */
1931
1945
  async quote(url, init) {
1932
- const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _25 => _25.method]), () => ( "GET")) });
1946
+ const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _29 => _29.method]), () => ( "GET")) });
1933
1947
  if (res.status !== 402) return null;
1934
1948
  const { quote } = await this.resolveChallenge(url, res);
1935
1949
  return quote;
@@ -1948,7 +1962,7 @@ var PipRailClient = (_class2 = class {
1948
1962
  * on Tron, where a USD₮ transfer can cost real TRX.
1949
1963
  */
1950
1964
  async estimateCost(url, init) {
1951
- const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _26 => _26.method]), () => ( "GET")) });
1965
+ const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _30 => _30.method]), () => ( "GET")) });
1952
1966
  if (res.status !== 402) return null;
1953
1967
  const { net, accept, quote } = await this.resolveChallenge(url, res);
1954
1968
  const cost = await net.estimateCost(accept);
@@ -1979,7 +1993,7 @@ var PipRailClient = (_class2 = class {
1979
1993
  * the plan yourself. No funds move.
1980
1994
  */
1981
1995
  async planPayment(url, init) {
1982
- const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _27 => _27.method]), () => ( "GET")) });
1996
+ const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _31 => _31.method]), () => ( "GET")) });
1983
1997
  if (res.status !== 402) return null;
1984
1998
  const challenge = await parseChallenge(res);
1985
1999
  if (!challenge) {
@@ -2146,7 +2160,7 @@ var PipRailClient = (_class2 = class {
2146
2160
  * streams throw `NonReplayableBodyError`.
2147
2161
  */
2148
2162
  async fetch(url, init) {
2149
- const body = _optionalChain([init, 'optionalAccess', _28 => _28.body]);
2163
+ const body = _optionalChain([init, 'optionalAccess', _32 => _32.body]);
2150
2164
  if (body !== void 0 && body !== null && !isReplayableBodyInit(body)) {
2151
2165
  throw new (0, _chunkMDLZJGLYcjs.NonReplayableBodyError)(
2152
2166
  "fetch(): init.body is not replayable. Pass a string, FormData, URLSearchParams, ArrayBuffer, or Blob \u2014 not a ReadableStream."
@@ -2158,7 +2172,7 @@ var PipRailClient = (_class2 = class {
2158
2172
  const { net, wallet, challenge } = resolved;
2159
2173
  let accept = resolved.accept;
2160
2174
  let quote = resolved.quote;
2161
- const autoRoute = _nullishCoalesce(_nullishCoalesce(_optionalChain([init, 'optionalAccess', _29 => _29.autoRoute]), () => ( this.opts.autoRoute)), () => ( false));
2175
+ const autoRoute = _nullishCoalesce(_nullishCoalesce(_optionalChain([init, 'optionalAccess', _33 => _33.autoRoute]), () => ( this.opts.autoRoute)), () => ( false));
2162
2176
  if (autoRoute) {
2163
2177
  const plan = await this.planFromChallenge(net, wallet, challenge, url);
2164
2178
  if (!plan.best) {
@@ -2320,8 +2334,8 @@ var PipRailClient = (_class2 = class {
2320
2334
  }
2321
2335
  const amountBase = BigInt(accept.amount);
2322
2336
  const described = net.describeAsset(accept.asset);
2323
- const decimals = _nullishCoalesce(_optionalChain([described, 'optionalAccess', _30 => _30.decimals]), () => ( accept.extra.decimals));
2324
- const symbol = _nullishCoalesce(_optionalChain([described, 'optionalAccess', _31 => _31.symbol]), () => ( accept.extra.symbol));
2337
+ const decimals = _nullishCoalesce(_optionalChain([described, 'optionalAccess', _34 => _34.decimals]), () => ( accept.extra.decimals));
2338
+ const symbol = _nullishCoalesce(_optionalChain([described, 'optionalAccess', _35 => _35.symbol]), () => ( accept.extra.symbol));
2325
2339
  const amountFormatted = _chunkMDLZJGLYcjs.formatUnits.call(void 0, amountBase, decimals);
2326
2340
  const intent = {
2327
2341
  host: hostOf2(url),
@@ -2430,7 +2444,7 @@ var PipRailClient = (_class2 = class {
2430
2444
  accepted: accept,
2431
2445
  payload: { nonce: accept.extra.nonce, txHash: ref }
2432
2446
  };
2433
- const headers = new Headers(_optionalChain([originalInit, 'optionalAccess', _32 => _32.headers]));
2447
+ const headers = new Headers(_optionalChain([originalInit, 'optionalAccess', _36 => _36.headers]));
2434
2448
  headers.set(HEADER_SIGNATURE, buildSignatureHeader(signature));
2435
2449
  let lastResponse = null;
2436
2450
  let lastReason = null;
@@ -2445,7 +2459,7 @@ var PipRailClient = (_class2 = class {
2445
2459
  () => timeoutController.abort(),
2446
2460
  this.retryTimeoutMs
2447
2461
  );
2448
- const signal = _optionalChain([originalInit, 'optionalAccess', _33 => _33.signal]) && typeof AbortSignal.any === "function" ? AbortSignal.any([timeoutController.signal, originalInit.signal]) : timeoutController.signal;
2462
+ const signal = _optionalChain([originalInit, 'optionalAccess', _37 => _37.signal]) && typeof AbortSignal.any === "function" ? AbortSignal.any([timeoutController.signal, originalInit.signal]) : timeoutController.signal;
2449
2463
  try {
2450
2464
  lastResponse = await fetch(url, {
2451
2465
  ..._nullishCoalesce(originalInit, () => ( {})),
@@ -2485,7 +2499,7 @@ var PipRailClient = (_class2 = class {
2485
2499
  function safeBig(s) {
2486
2500
  try {
2487
2501
  return BigInt(s);
2488
- } catch (e22) {
2502
+ } catch (e21) {
2489
2503
  return 0n;
2490
2504
  }
2491
2505
  }
@@ -2518,10 +2532,10 @@ function buildFundingHint(options, chainLabel) {
2518
2532
  return `Couldn't fully read your wallet on ${chainLabel} (RPC throttled) \u2014 retry; you may already be able to pay ${target.quote.amountFormatted} ${sym}.`;
2519
2533
  }
2520
2534
  const parts = [];
2521
- if (target.blockers.includes("INSUFFICIENT_TOKEN") && _optionalChain([target, 'access', _34 => _34.shortfall, 'optionalAccess', _35 => _35.token])) {
2535
+ if (target.blockers.includes("INSUFFICIENT_TOKEN") && _optionalChain([target, 'access', _38 => _38.shortfall, 'optionalAccess', _39 => _39.token])) {
2522
2536
  parts.push(`top up ${target.shortfall.token} ${sym}`);
2523
2537
  }
2524
- if (target.blockers.includes("INSUFFICIENT_GAS") && _optionalChain([target, 'access', _36 => _36.shortfall, 'optionalAccess', _37 => _37.native])) {
2538
+ if (target.blockers.includes("INSUFFICIENT_GAS") && _optionalChain([target, 'access', _40 => _40.shortfall, 'optionalAccess', _41 => _41.native])) {
2525
2539
  parts.push(`add ~${target.shortfall.native} ${target.cost.feeSymbol} for gas`);
2526
2540
  }
2527
2541
  return parts.length ? `Can't settle on ${chainLabel}: ${parts.join(" and ")} (to pay ${target.quote.amountFormatted} ${sym}).` : `Can't settle on ${chainLabel} for ${target.quote.amountFormatted} ${sym}.`;
@@ -2535,7 +2549,7 @@ async function planAcross(clients, url, init) {
2535
2549
  const status = best ? "ready" : options.some((o) => o.state === "unknown") ? "unknown" : "blocked";
2536
2550
  return {
2537
2551
  url,
2538
- network: _nullishCoalesce(_optionalChain([best, 'optionalAccess', _38 => _38.accept, 'access', _39 => _39.network]), () => ( live[0].network)),
2552
+ network: _nullishCoalesce(_optionalChain([best, 'optionalAccess', _42 => _42.accept, 'access', _43 => _43.network]), () => ( live[0].network)),
2539
2553
  status,
2540
2554
  payable: best !== null,
2541
2555
  best,
@@ -2551,7 +2565,7 @@ function railOnNetwork(rail, matches) {
2551
2565
  function hostOf2(url) {
2552
2566
  try {
2553
2567
  return new URL(url).hostname;
2554
- } catch (e23) {
2568
+ } catch (e22) {
2555
2569
  return url;
2556
2570
  }
2557
2571
  }
@@ -2568,8 +2582,8 @@ function isReplayableBodyInit(value) {
2568
2582
  async function readInvalidReason(response) {
2569
2583
  try {
2570
2584
  const body = await response.clone().json();
2571
- const ext = _optionalChain([body, 'optionalAccess', _40 => _40.extensions]);
2572
- const piprail = _optionalChain([ext, 'optionalAccess', _41 => _41.piprail]);
2585
+ const ext = _optionalChain([body, 'optionalAccess', _44 => _44.extensions]);
2586
+ const piprail = _optionalChain([ext, 'optionalAccess', _45 => _45.piprail]);
2573
2587
  if (piprail && typeof piprail.code === "string") {
2574
2588
  return {
2575
2589
  error: piprail.code,
@@ -2582,7 +2596,7 @@ async function readInvalidReason(response) {
2582
2596
  detail: typeof body.detail === "string" ? body.detail : ""
2583
2597
  };
2584
2598
  }
2585
- } catch (e24) {
2599
+ } catch (e23) {
2586
2600
  }
2587
2601
  return null;
2588
2602
  }
@@ -2593,7 +2607,7 @@ async function readBody(res) {
2593
2607
  if (!text) return null;
2594
2608
  try {
2595
2609
  return JSON.parse(text);
2596
- } catch (e25) {
2610
+ } catch (e24) {
2597
2611
  return text;
2598
2612
  }
2599
2613
  }
@@ -2773,7 +2787,7 @@ function paymentTools(client) {
2773
2787
  },
2774
2788
  {
2775
2789
  name: "piprail_register",
2776
- description: "List an x402 payment-gated resource YOU run on the open indexes so other agents can discover it. Default target is 402 Index \u2014 no auth, no signature, no payment; a self-registered listing is pending review (verify your domain on 402index.io for instant approval). Returns one outcome per index ({ source, ok, detail, visibility, note }); a step the chain can't satisfy comes back ok:false with the reason. Moves no funds; nothing is PipRail-hosted.",
2790
+ description: "List an x402 payment-gated resource YOU run on the open indexes so other agents can discover it. Default target is 402 Index \u2014 no auth, no signature, no payment; a self-registered listing is pending review (verify your domain on 402index.io for instant approval). Returns one outcome per index ({ source, ok, detail, visibility, note }); a step the chain can't satisfy comes back ok:false with the reason. Moves no funds; nothing is PipRail-hosted. NOTE: index/agent payers are overwhelmingly standard `exact` clients \u2014 a default onchain-proof-only gate gets listed but they cannot pay it, so add an `exact` rail (and set the gate's `discovery` option, required for x402scan) to be usefully discoverable AND payable.",
2777
2791
  annotations: {
2778
2792
  title: "Register an x402 endpoint",
2779
2793
  readOnlyHint: false,
@@ -2807,6 +2821,87 @@ function paymentTools(client) {
2807
2821
  ];
2808
2822
  }
2809
2823
 
2824
+ // src/discovery.ts
2825
+ var GENERATOR = "@piprail/sdk \xB7 https://piprail.com";
2826
+ function buildBazaarExtension(descriptor = {}) {
2827
+ const method = (_nullishCoalesce(descriptor.method, () => ( "GET"))).toUpperCase();
2828
+ const queryParams = _nullishCoalesce(descriptor.queryParams, () => ( {}));
2829
+ return {
2830
+ info: {
2831
+ input: { type: "http", method, queryParams },
2832
+ output: _nullishCoalesce(descriptor.output, () => ( { type: "json" }))
2833
+ },
2834
+ schema: {
2835
+ $schema: "https://json-schema.org/draft/2020-12/schema",
2836
+ type: "object",
2837
+ properties: {
2838
+ input: {
2839
+ type: "object",
2840
+ properties: {
2841
+ type: { type: "string", const: "http" },
2842
+ method: { type: "string", enum: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"] },
2843
+ queryParams: { type: "object", properties: queryParams, additionalProperties: false }
2844
+ },
2845
+ required: ["type", "method"],
2846
+ additionalProperties: false
2847
+ }
2848
+ },
2849
+ required: ["input"]
2850
+ }
2851
+ };
2852
+ }
2853
+ function pathOf(url) {
2854
+ try {
2855
+ return new URL(url).pathname || "/";
2856
+ } catch (e25) {
2857
+ return url.startsWith("/") ? url : `/${url}`;
2858
+ }
2859
+ }
2860
+ function buildOpenApi(input) {
2861
+ const paths = {};
2862
+ for (const r of input.resources) {
2863
+ const path = pathOf(r.url);
2864
+ const method = (_nullishCoalesce(r.method, () => ( "GET"))).toLowerCase();
2865
+ const op = {
2866
+ ...r.description ? { summary: r.description } : {},
2867
+ responses: {
2868
+ "200": { description: "Paid \u2014 the resource." },
2869
+ "402": { description: "Payment required (x402)." }
2870
+ },
2871
+ "x-payment-info": {
2872
+ x402Version: 2,
2873
+ accepts: r.accepts
2874
+ }
2875
+ };
2876
+ paths[path] = { ..._nullishCoalesce(paths[path], () => ( {})), [method]: op };
2877
+ }
2878
+ return {
2879
+ openapi: "3.1.0",
2880
+ info: { title: _nullishCoalesce(input.title, () => ( "PipRail x402 resources")), version: _nullishCoalesce(input.version, () => ( "1.0.0")) },
2881
+ servers: [{ url: input.origin }],
2882
+ paths,
2883
+ // "Built with @piprail/sdk" — default on (opt out with attribution:false). At the
2884
+ // document ROOT so `info` stays exactly { title, version }.
2885
+ ...input.attribution === false ? {} : { "x-generator": GENERATOR },
2886
+ ...input.ownershipProofs && input.ownershipProofs.length > 0 ? { "x-agentcash-provenance": { ownershipProofs: input.ownershipProofs } } : {}
2887
+ };
2888
+ }
2889
+ function buildWellKnownX402(input) {
2890
+ return {
2891
+ version: 1,
2892
+ resources: input.resources.map((r) => r.url),
2893
+ ...input.ownershipProofs && input.ownershipProofs.length > 0 ? { ownershipProofs: input.ownershipProofs } : {}
2894
+ };
2895
+ }
2896
+ function buildX402DnsTxt(input) {
2897
+ const descriptor = input.descriptor ? `descriptor=${input.descriptor};` : "";
2898
+ return {
2899
+ name: `_x402.${input.host}`,
2900
+ type: "TXT",
2901
+ value: `v=x4021;${descriptor}url=${input.discoveryUrl}`
2902
+ };
2903
+ }
2904
+
2810
2905
  // src/facilitator.ts
2811
2906
  function safeStringify(value) {
2812
2907
  return JSON.stringify(value, (_k, v) => typeof v === "bigint" ? v.toString() : v);
@@ -3055,6 +3150,8 @@ function createPaymentGate(options) {
3055
3150
  async function makeChallenge(resourceUrl, opts) {
3056
3151
  const specs = await ready();
3057
3152
  const nonce = genNonce();
3153
+ const bazaar = options.discovery ? { bazaar: buildBazaarExtension(options.discovery === true ? {} : options.discovery) } : void 0;
3154
+ const extensions = { ...bazaar, ..._optionalChain([opts, 'optionalAccess', _46 => _46.extensions]) };
3058
3155
  const challenge2 = {
3059
3156
  x402Version: 2,
3060
3157
  resource: {
@@ -3062,8 +3159,8 @@ function createPaymentGate(options) {
3062
3159
  ...options.description ? { description: options.description } : {}
3063
3160
  },
3064
3161
  accepts: buildAccepts(specs, nonce),
3065
- ..._optionalChain([opts, 'optionalAccess', _42 => _42.error]) ? { error: opts.error } : {},
3066
- ..._optionalChain([opts, 'optionalAccess', _43 => _43.extensions]) ? { extensions: opts.extensions } : {}
3162
+ ..._optionalChain([opts, 'optionalAccess', _47 => _47.error]) ? { error: opts.error } : {},
3163
+ ...Object.keys(extensions).length > 0 ? { extensions } : {}
3067
3164
  };
3068
3165
  return { challenge: challenge2, requiredHeader: buildChallengeHeader(challenge2) };
3069
3166
  }
@@ -3250,60 +3347,6 @@ function normaliseHeader(value) {
3250
3347
  return value;
3251
3348
  }
3252
3349
 
3253
- // src/discovery.ts
3254
- var GENERATOR = "@piprail/sdk \xB7 https://piprail.com";
3255
- function pathOf(url) {
3256
- try {
3257
- return new URL(url).pathname || "/";
3258
- } catch (e28) {
3259
- return url.startsWith("/") ? url : `/${url}`;
3260
- }
3261
- }
3262
- function buildOpenApi(input) {
3263
- const paths = {};
3264
- for (const r of input.resources) {
3265
- const path = pathOf(r.url);
3266
- const method = (_nullishCoalesce(r.method, () => ( "GET"))).toLowerCase();
3267
- const op = {
3268
- ...r.description ? { summary: r.description } : {},
3269
- responses: {
3270
- "200": { description: "Paid \u2014 the resource." },
3271
- "402": { description: "Payment required (x402)." }
3272
- },
3273
- "x-payment-info": {
3274
- x402Version: 2,
3275
- accepts: r.accepts,
3276
- bazaar: { discoverable: true }
3277
- }
3278
- };
3279
- paths[path] = { ..._nullishCoalesce(paths[path], () => ( {})), [method]: op };
3280
- }
3281
- return {
3282
- openapi: "3.1.0",
3283
- info: { title: _nullishCoalesce(input.title, () => ( "PipRail x402 resources")), version: _nullishCoalesce(input.version, () => ( "1.0.0")) },
3284
- servers: [{ url: input.origin }],
3285
- paths,
3286
- // "Built with @piprail/sdk" — default on (opt out with attribution:false). At the
3287
- // document ROOT so `info` stays exactly { title, version }.
3288
- ...input.attribution === false ? {} : { "x-generator": GENERATOR },
3289
- ...input.ownershipProofs && input.ownershipProofs.length > 0 ? { "x-agentcash-provenance": { ownershipProofs: input.ownershipProofs } } : {}
3290
- };
3291
- }
3292
- function buildWellKnownX402(input) {
3293
- return {
3294
- version: 1,
3295
- resources: input.resources.map((r) => r.url),
3296
- ...input.ownershipProofs && input.ownershipProofs.length > 0 ? { ownershipProofs: input.ownershipProofs } : {}
3297
- };
3298
- }
3299
- function buildX402DnsTxt(input) {
3300
- const descriptor = input.descriptor ? `descriptor=${input.descriptor};` : "";
3301
- return {
3302
- name: `_x402.${input.host}`,
3303
- type: "TXT",
3304
- value: `v=x4021;${descriptor}url=${input.discoveryUrl}`
3305
- };
3306
- }
3307
3350
 
3308
3351
 
3309
3352
 
@@ -3367,4 +3410,4 @@ function buildX402DnsTxt(input) {
3367
3410
 
3368
3411
 
3369
3412
 
3370
- exports.CHAINS = CHAINS; exports.ConfirmationTimeoutError = _chunkMDLZJGLYcjs.ConfirmationTimeoutError; exports.DIRECTORY_INFO = DIRECTORY_INFO; exports.EIP3009_TYPES = EIP3009_TYPES; exports.EXACT_NETWORK_SLUGS = EXACT_NETWORK_SLUGS; exports.GENERATOR = GENERATOR; exports.HEADER_REQUIRED = HEADER_REQUIRED; exports.HEADER_RESPONSE = HEADER_RESPONSE; exports.HEADER_RESPONSE_V1 = HEADER_RESPONSE_V1; exports.HEADER_SIGNATURE = HEADER_SIGNATURE; exports.HEADER_SIGNATURE_V1 = HEADER_SIGNATURE_V1; exports.InsufficientFundsError = _chunkMDLZJGLYcjs.InsufficientFundsError; exports.InvalidEnvelopeError = _chunkMDLZJGLYcjs.InvalidEnvelopeError; exports.MaxRetriesExceededError = _chunkMDLZJGLYcjs.MaxRetriesExceededError; exports.MissingDriverError = _chunkMDLZJGLYcjs.MissingDriverError; exports.NoCompatibleAcceptError = _chunkMDLZJGLYcjs.NoCompatibleAcceptError; exports.NonReplayableBodyError = _chunkMDLZJGLYcjs.NonReplayableBodyError; exports.PaymentDeclinedError = _chunkMDLZJGLYcjs.PaymentDeclinedError; exports.PaymentTimeoutError = _chunkMDLZJGLYcjs.PaymentTimeoutError; exports.PipRailClient = PipRailClient; exports.PipRailError = _chunkMDLZJGLYcjs.PipRailError; exports.RecipientNotReadyError = _chunkMDLZJGLYcjs.RecipientNotReadyError; exports.SettlementError = _chunkMDLZJGLYcjs.SettlementError; exports.UnknownTokenError = _chunkMDLZJGLYcjs.UnknownTokenError; exports.UnsupportedNetworkError = _chunkMDLZJGLYcjs.UnsupportedNetworkError; exports.WrongChainError = _chunkMDLZJGLYcjs.WrongChainError; exports.WrongFamilyError = _chunkMDLZJGLYcjs.WrongFamilyError; exports.buildChallengeHeader = buildChallengeHeader; exports.buildExactAuthorization = buildExactAuthorization; exports.buildOpenApi = buildOpenApi; exports.buildReceiptHeader = buildReceiptHeader; exports.buildSignatureHeader = buildSignatureHeader; exports.buildWellKnownX402 = buildWellKnownX402; exports.buildX402DnsTxt = buildX402DnsTxt; exports.chainIdForExactNetwork = chainIdForExactNetwork; exports.claim402IndexDomain = claim402IndexDomain; exports.createPaymentGate = createPaymentGate; exports.decorateOutcome = decorateOutcome; exports.eip3009Abi = eip3009Abi; exports.encodeXPaymentHeader = encodeXPaymentHeader; exports.evaluatePolicy = evaluatePolicy; exports.getDirectoryInfo = getDirectoryInfo; exports.normalizeNetwork = normalizeNetwork; exports.parseChallenge = parseChallenge; exports.parseExactPaymentHeader = parseExactPaymentHeader; exports.parseExactRequirements = parseExactRequirements; exports.parseReceipt = parseReceipt; exports.parseSignatureHeader = parseSignatureHeader; exports.paymentTools = paymentTools; exports.pickAccept = pickAccept; exports.planAcross = planAcross; exports.readExactDomain = readExactDomain; exports.register402Index = register402Index; exports.registerDriver = registerDriver; exports.registerX402Scan = registerX402Scan; exports.requirePayment = requirePayment; exports.resolveChain = resolveChain; exports.searchOpenIndexes = searchOpenIndexes; exports.settleViaFacilitator = settleViaFacilitator; exports.toInsufficientFundsError = _chunkMDLZJGLYcjs.toInsufficientFundsError; exports.toInvalidBody = toInvalidBody; exports.verify402IndexDomain = verify402IndexDomain;
3413
+ exports.CHAINS = CHAINS; exports.ConfirmationTimeoutError = _chunkMDLZJGLYcjs.ConfirmationTimeoutError; exports.DIRECTORY_INFO = DIRECTORY_INFO; exports.EIP3009_TYPES = EIP3009_TYPES; exports.EXACT_NETWORK_SLUGS = EXACT_NETWORK_SLUGS; exports.GENERATOR = GENERATOR; exports.HEADER_REQUIRED = HEADER_REQUIRED; exports.HEADER_RESPONSE = HEADER_RESPONSE; exports.HEADER_RESPONSE_V1 = HEADER_RESPONSE_V1; exports.HEADER_SIGNATURE = HEADER_SIGNATURE; exports.HEADER_SIGNATURE_V1 = HEADER_SIGNATURE_V1; exports.InsufficientFundsError = _chunkMDLZJGLYcjs.InsufficientFundsError; exports.InvalidEnvelopeError = _chunkMDLZJGLYcjs.InvalidEnvelopeError; exports.MaxRetriesExceededError = _chunkMDLZJGLYcjs.MaxRetriesExceededError; exports.MissingDriverError = _chunkMDLZJGLYcjs.MissingDriverError; exports.NoCompatibleAcceptError = _chunkMDLZJGLYcjs.NoCompatibleAcceptError; exports.NonReplayableBodyError = _chunkMDLZJGLYcjs.NonReplayableBodyError; exports.PaymentDeclinedError = _chunkMDLZJGLYcjs.PaymentDeclinedError; exports.PaymentTimeoutError = _chunkMDLZJGLYcjs.PaymentTimeoutError; exports.PipRailClient = PipRailClient; exports.PipRailError = _chunkMDLZJGLYcjs.PipRailError; exports.RecipientNotReadyError = _chunkMDLZJGLYcjs.RecipientNotReadyError; exports.SettlementError = _chunkMDLZJGLYcjs.SettlementError; exports.UnknownTokenError = _chunkMDLZJGLYcjs.UnknownTokenError; exports.UnsupportedNetworkError = _chunkMDLZJGLYcjs.UnsupportedNetworkError; exports.WrongChainError = _chunkMDLZJGLYcjs.WrongChainError; exports.WrongFamilyError = _chunkMDLZJGLYcjs.WrongFamilyError; exports.buildBazaarExtension = buildBazaarExtension; exports.buildChallengeHeader = buildChallengeHeader; exports.buildExactAuthorization = buildExactAuthorization; exports.buildOpenApi = buildOpenApi; exports.buildReceiptHeader = buildReceiptHeader; exports.buildSignatureHeader = buildSignatureHeader; exports.buildWellKnownX402 = buildWellKnownX402; exports.buildX402DnsTxt = buildX402DnsTxt; exports.chainIdForExactNetwork = chainIdForExactNetwork; exports.claim402IndexDomain = claim402IndexDomain; exports.createPaymentGate = createPaymentGate; exports.decorateOutcome = decorateOutcome; exports.eip3009Abi = eip3009Abi; exports.encodeXPaymentHeader = encodeXPaymentHeader; exports.evaluatePolicy = evaluatePolicy; exports.getDirectoryInfo = getDirectoryInfo; exports.normalizeNetwork = normalizeNetwork; exports.parseChallenge = parseChallenge; exports.parseExactPaymentHeader = parseExactPaymentHeader; exports.parseExactRequirements = parseExactRequirements; exports.parseReceipt = parseReceipt; exports.parseSignatureHeader = parseSignatureHeader; exports.paymentTools = paymentTools; exports.pickAccept = pickAccept; exports.planAcross = planAcross; exports.readExactDomain = readExactDomain; exports.register402Index = register402Index; exports.registerDriver = registerDriver; exports.registerX402Scan = registerX402Scan; exports.requirePayment = requirePayment; exports.resolveChain = resolveChain; exports.searchOpenIndexes = searchOpenIndexes; exports.settleViaFacilitator = settleViaFacilitator; exports.toInsufficientFundsError = _chunkMDLZJGLYcjs.toInsufficientFundsError; exports.toInvalidBody = toInvalidBody; exports.verify402IndexDomain = verify402IndexDomain;
package/dist/index.d.cts CHANGED
@@ -4370,9 +4370,14 @@ declare function registerX402Scan(input: {
4370
4370
  interface DomainClaim {
4371
4371
  ok: boolean;
4372
4372
  domain: string;
4373
- /** Serve THIS string as the entire body of `verificationUrl`. */
4373
+ /** The exact text to serve as the ENTIRE body of `verificationUrl` — this is what
4374
+ * 402 Index fetches and checks (the SHA-256 of the token). Always populated on
4375
+ * success: read from the response, or computed as `sha256(verificationToken)` if
4376
+ * the API returns only the token. Serve THIS. */
4374
4377
  verificationHash?: string;
4375
- /** Where to serve it your `https://<domain>/.well-known/402index-verify.txt`. */
4378
+ /** The raw 64-hex token 402 Index issued (the preimage of `verificationHash`). */
4379
+ verificationToken?: string;
4380
+ /** Where to serve `verificationHash` — your `https://<domain>/.well-known/402index-verify.txt`. */
4376
4381
  verificationUrl?: string;
4377
4382
  /** 402 Index's own human instructions. */
4378
4383
  instructions?: string;
@@ -5129,10 +5134,6 @@ interface OpenApiOperation {
5129
5134
  'x-payment-info': {
5130
5135
  x402Version: 2;
5131
5136
  accepts: PaymentRail[];
5132
- /** Bazaar-style input schema marker so a strict index doesn't "skip" the op. */
5133
- bazaar: {
5134
- discoverable: true;
5135
- };
5136
5137
  };
5137
5138
  }
5138
5139
  /** x402scan's legacy origin file (`/.well-known/x402`). */
@@ -5141,6 +5142,46 @@ interface WellKnownX402 {
5141
5142
  resources: string[];
5142
5143
  ownershipProofs?: string[];
5143
5144
  }
5145
+ /**
5146
+ * Describes a resource's INPUT for discovery. The open indexes that REQUIRE an
5147
+ * input schema (x402scan rejects a listing without one) read this from a
5148
+ * `extensions.bazaar` block. Pass it to a gate's `discovery` option (emits the
5149
+ * block in the 402 challenge) or build it directly with {@link buildBazaarExtension}.
5150
+ */
5151
+ interface DiscoveryDescriptor {
5152
+ /** HTTP method the resource answers. Default `'GET'`. */
5153
+ method?: string;
5154
+ /** Query params the resource reads, as a JSON-Schema `properties` object
5155
+ * (name → schema). Default `{}` — a no-input GET. */
5156
+ queryParams?: Record<string, unknown>;
5157
+ /** Optional output hint (shape/example) for a richer listing. */
5158
+ output?: {
5159
+ type?: string;
5160
+ example?: unknown;
5161
+ };
5162
+ }
5163
+ /** The `extensions.bazaar` discovery block (the x402 "bazaar" convention the open
5164
+ * indexes parse: `info.input` describes the request, `schema` is its JSON Schema). */
5165
+ interface BazaarExtension {
5166
+ info: {
5167
+ input: {
5168
+ type: 'http';
5169
+ method: string;
5170
+ queryParams: Record<string, unknown>;
5171
+ };
5172
+ output?: {
5173
+ type?: string;
5174
+ example?: unknown;
5175
+ };
5176
+ };
5177
+ schema: Record<string, unknown>;
5178
+ }
5179
+ /**
5180
+ * Build the `extensions.bazaar` block that satisfies x402scan's mandatory input-schema
5181
+ * check, from a {@link DiscoveryDescriptor}. Pure. Defaults to a no-input GET — the
5182
+ * minimal shape a live x402scan listing accepts.
5183
+ */
5184
+ declare function buildBazaarExtension(descriptor?: DiscoveryDescriptor): BazaarExtension;
5144
5185
  /** The `_x402` DNS TXT pointer record (experimental draft). */
5145
5186
  interface X402DnsRecord {
5146
5187
  name: string;
@@ -5292,6 +5333,14 @@ interface RequirePaymentOptions {
5292
5333
  * Omit to keep the gate exactly as today (`onchain-proof` only).
5293
5334
  */
5294
5335
  exact?: ExactRailOption;
5336
+ /**
5337
+ * Make this gate's 402 self-describing for the open indexes — **x402scan REQUIRES
5338
+ * an input schema or it won't list the resource.** Set `true` for a no-input GET,
5339
+ * or pass a {@link DiscoveryDescriptor} to describe the request. Emits an
5340
+ * `extensions.bazaar` block in the 402 challenge. Opt-in; omitting it leaves the
5341
+ * challenge byte-identical to before.
5342
+ */
5343
+ discovery?: boolean | DiscoveryDescriptor;
5295
5344
  }
5296
5345
  type VerifyPaymentResult = {
5297
5346
  kind: 'paid';
@@ -5898,4 +5947,4 @@ declare function readExactDomain(publicClient: PublicClient, asset: string): Pro
5898
5947
  version: string;
5899
5948
  } | null>;
5900
5949
 
5901
- export { type AcceptOption, type AddressId, type AgentTool, type AlgorandToken, type AptosToken, type AssetId, type BuildExactParams, CHAINS, type Caip2, type ChainFamily, type ChainInput, type ChainName, type ChainPreset, type ChainSelector, type ConfirmInfo, ConfirmationTimeoutError, type CostEstimate, DIRECTORY_INFO, type DirectoryInfo, type DiscoverOptions, type DiscoveredRail, type DiscoveredResource, type DiscoverySigner, type DiscoverySource, type DomainClaim, type DomainVerification, EIP3009_TYPES, EXACT_NETWORK_SLUGS, type EvmToken, type ExactAccept, type ExactAuthorization, type ExactAuthorizationWire, type ExactPaymentPayload, type ExactRailOption, type ExpressLikeMiddleware, type ExpressLikeNext, type ExpressLikeRequest, type ExpressLikeResponse, type FacilitatorConfig, type FacilitatorPaymentRequirements, GENERATOR, HEADER_REQUIRED, HEADER_RESPONSE, HEADER_RESPONSE_V1, HEADER_SIGNATURE, HEADER_SIGNATURE_V1, InsufficientFundsError, InvalidEnvelopeError, type ListingVisibility, type ManifestInput, MaxRetriesExceededError, MissingDriverError, type NearToken, NoCompatibleAcceptError, NonReplayableBodyError, type OpenApiDocument, type OpenApiOperation, type ParsedExactPayment, type PayBlocker, type PayOption, type PayWarning, PaymentDeclinedError, type PaymentDriver, type PaymentGate, type PaymentIntent, type PaymentPlan, type PaymentPolicy, type PaymentRail, PaymentTimeoutError, PipRailClient, type PipRailClientOptions, type PipRailCostQuote, PipRailError, type PipRailEvent, type PipRailQuote, type PolicyDecision, RecipientNotReadyError, type RecipientReason, type RegisterInput, type RegisterOptions, type RegisterOutcome, type RequirePaymentOptions, type ResolveOptions, type ResolvedChain, type ResolvedNetwork, type ResolvedToken, type ResourceDescription, type SearchOpenIndexesOptions, type SettleViaFacilitatorInput, SettlementError, type SolanaToken, type SpendAssetTotal, type SpendRecord, type SpendSummary, type StellarToken, type SuiToken, type TokenInfo, type TokenInput, type TonToken, type ToolAnnotations, type TronToken, UnknownTokenError, UnsupportedNetworkError, type VerifyErrorCode, type VerifyPaymentResult, type VerifyResult, type WalletBalance, type WalletHandle, type WalletInput, type WellKnownX402, WrongChainError, WrongFamilyError, type X402AcceptEntry, type X402AnyAccept, type X402Challenge, type X402DnsRecord, type X402ExactAcceptEntry, type X402InvalidBody, type X402PaymentSignature, type X402Receipt, type X402ResourceObject, type XrplToken, buildChallengeHeader, buildExactAuthorization, buildOpenApi, buildReceiptHeader, buildSignatureHeader, buildWellKnownX402, buildX402DnsTxt, chainIdForExactNetwork, claim402IndexDomain, createPaymentGate, decorateOutcome, eip3009Abi, encodeXPaymentHeader, evaluatePolicy, getDirectoryInfo, normalizeNetwork, parseChallenge, parseExactPaymentHeader, parseExactRequirements, parseReceipt, parseSignatureHeader, paymentTools, pickAccept, planAcross, readExactDomain, register402Index, registerDriver, registerX402Scan, requirePayment, resolveChain, searchOpenIndexes, settleViaFacilitator, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
5950
+ export { type AcceptOption, type AddressId, type AgentTool, type AlgorandToken, type AptosToken, type AssetId, type BazaarExtension, type BuildExactParams, CHAINS, type Caip2, type ChainFamily, type ChainInput, type ChainName, type ChainPreset, type ChainSelector, type ConfirmInfo, ConfirmationTimeoutError, type CostEstimate, DIRECTORY_INFO, type DirectoryInfo, type DiscoverOptions, type DiscoveredRail, type DiscoveredResource, type DiscoveryDescriptor, type DiscoverySigner, type DiscoverySource, type DomainClaim, type DomainVerification, EIP3009_TYPES, EXACT_NETWORK_SLUGS, type EvmToken, type ExactAccept, type ExactAuthorization, type ExactAuthorizationWire, type ExactPaymentPayload, type ExactRailOption, type ExpressLikeMiddleware, type ExpressLikeNext, type ExpressLikeRequest, type ExpressLikeResponse, type FacilitatorConfig, type FacilitatorPaymentRequirements, GENERATOR, HEADER_REQUIRED, HEADER_RESPONSE, HEADER_RESPONSE_V1, HEADER_SIGNATURE, HEADER_SIGNATURE_V1, InsufficientFundsError, InvalidEnvelopeError, type ListingVisibility, type ManifestInput, MaxRetriesExceededError, MissingDriverError, type NearToken, NoCompatibleAcceptError, NonReplayableBodyError, type OpenApiDocument, type OpenApiOperation, type ParsedExactPayment, type PayBlocker, type PayOption, type PayWarning, PaymentDeclinedError, type PaymentDriver, type PaymentGate, type PaymentIntent, type PaymentPlan, type PaymentPolicy, type PaymentRail, PaymentTimeoutError, PipRailClient, type PipRailClientOptions, type PipRailCostQuote, PipRailError, type PipRailEvent, type PipRailQuote, type PolicyDecision, RecipientNotReadyError, type RecipientReason, type RegisterInput, type RegisterOptions, type RegisterOutcome, type RequirePaymentOptions, type ResolveOptions, type ResolvedChain, type ResolvedNetwork, type ResolvedToken, type ResourceDescription, type SearchOpenIndexesOptions, type SettleViaFacilitatorInput, SettlementError, type SolanaToken, type SpendAssetTotal, type SpendRecord, type SpendSummary, type StellarToken, type SuiToken, type TokenInfo, type TokenInput, type TonToken, type ToolAnnotations, type TronToken, UnknownTokenError, UnsupportedNetworkError, type VerifyErrorCode, type VerifyPaymentResult, type VerifyResult, type WalletBalance, type WalletHandle, type WalletInput, type WellKnownX402, WrongChainError, WrongFamilyError, type X402AcceptEntry, type X402AnyAccept, type X402Challenge, type X402DnsRecord, type X402ExactAcceptEntry, type X402InvalidBody, type X402PaymentSignature, type X402Receipt, type X402ResourceObject, type XrplToken, buildBazaarExtension, buildChallengeHeader, buildExactAuthorization, buildOpenApi, buildReceiptHeader, buildSignatureHeader, buildWellKnownX402, buildX402DnsTxt, chainIdForExactNetwork, claim402IndexDomain, createPaymentGate, decorateOutcome, eip3009Abi, encodeXPaymentHeader, evaluatePolicy, getDirectoryInfo, normalizeNetwork, parseChallenge, parseExactPaymentHeader, parseExactRequirements, parseReceipt, parseSignatureHeader, paymentTools, pickAccept, planAcross, readExactDomain, register402Index, registerDriver, registerX402Scan, requirePayment, resolveChain, searchOpenIndexes, settleViaFacilitator, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
package/dist/index.d.ts CHANGED
@@ -4370,9 +4370,14 @@ declare function registerX402Scan(input: {
4370
4370
  interface DomainClaim {
4371
4371
  ok: boolean;
4372
4372
  domain: string;
4373
- /** Serve THIS string as the entire body of `verificationUrl`. */
4373
+ /** The exact text to serve as the ENTIRE body of `verificationUrl` — this is what
4374
+ * 402 Index fetches and checks (the SHA-256 of the token). Always populated on
4375
+ * success: read from the response, or computed as `sha256(verificationToken)` if
4376
+ * the API returns only the token. Serve THIS. */
4374
4377
  verificationHash?: string;
4375
- /** Where to serve it your `https://<domain>/.well-known/402index-verify.txt`. */
4378
+ /** The raw 64-hex token 402 Index issued (the preimage of `verificationHash`). */
4379
+ verificationToken?: string;
4380
+ /** Where to serve `verificationHash` — your `https://<domain>/.well-known/402index-verify.txt`. */
4376
4381
  verificationUrl?: string;
4377
4382
  /** 402 Index's own human instructions. */
4378
4383
  instructions?: string;
@@ -5129,10 +5134,6 @@ interface OpenApiOperation {
5129
5134
  'x-payment-info': {
5130
5135
  x402Version: 2;
5131
5136
  accepts: PaymentRail[];
5132
- /** Bazaar-style input schema marker so a strict index doesn't "skip" the op. */
5133
- bazaar: {
5134
- discoverable: true;
5135
- };
5136
5137
  };
5137
5138
  }
5138
5139
  /** x402scan's legacy origin file (`/.well-known/x402`). */
@@ -5141,6 +5142,46 @@ interface WellKnownX402 {
5141
5142
  resources: string[];
5142
5143
  ownershipProofs?: string[];
5143
5144
  }
5145
+ /**
5146
+ * Describes a resource's INPUT for discovery. The open indexes that REQUIRE an
5147
+ * input schema (x402scan rejects a listing without one) read this from a
5148
+ * `extensions.bazaar` block. Pass it to a gate's `discovery` option (emits the
5149
+ * block in the 402 challenge) or build it directly with {@link buildBazaarExtension}.
5150
+ */
5151
+ interface DiscoveryDescriptor {
5152
+ /** HTTP method the resource answers. Default `'GET'`. */
5153
+ method?: string;
5154
+ /** Query params the resource reads, as a JSON-Schema `properties` object
5155
+ * (name → schema). Default `{}` — a no-input GET. */
5156
+ queryParams?: Record<string, unknown>;
5157
+ /** Optional output hint (shape/example) for a richer listing. */
5158
+ output?: {
5159
+ type?: string;
5160
+ example?: unknown;
5161
+ };
5162
+ }
5163
+ /** The `extensions.bazaar` discovery block (the x402 "bazaar" convention the open
5164
+ * indexes parse: `info.input` describes the request, `schema` is its JSON Schema). */
5165
+ interface BazaarExtension {
5166
+ info: {
5167
+ input: {
5168
+ type: 'http';
5169
+ method: string;
5170
+ queryParams: Record<string, unknown>;
5171
+ };
5172
+ output?: {
5173
+ type?: string;
5174
+ example?: unknown;
5175
+ };
5176
+ };
5177
+ schema: Record<string, unknown>;
5178
+ }
5179
+ /**
5180
+ * Build the `extensions.bazaar` block that satisfies x402scan's mandatory input-schema
5181
+ * check, from a {@link DiscoveryDescriptor}. Pure. Defaults to a no-input GET — the
5182
+ * minimal shape a live x402scan listing accepts.
5183
+ */
5184
+ declare function buildBazaarExtension(descriptor?: DiscoveryDescriptor): BazaarExtension;
5144
5185
  /** The `_x402` DNS TXT pointer record (experimental draft). */
5145
5186
  interface X402DnsRecord {
5146
5187
  name: string;
@@ -5292,6 +5333,14 @@ interface RequirePaymentOptions {
5292
5333
  * Omit to keep the gate exactly as today (`onchain-proof` only).
5293
5334
  */
5294
5335
  exact?: ExactRailOption;
5336
+ /**
5337
+ * Make this gate's 402 self-describing for the open indexes — **x402scan REQUIRES
5338
+ * an input schema or it won't list the resource.** Set `true` for a no-input GET,
5339
+ * or pass a {@link DiscoveryDescriptor} to describe the request. Emits an
5340
+ * `extensions.bazaar` block in the 402 challenge. Opt-in; omitting it leaves the
5341
+ * challenge byte-identical to before.
5342
+ */
5343
+ discovery?: boolean | DiscoveryDescriptor;
5295
5344
  }
5296
5345
  type VerifyPaymentResult = {
5297
5346
  kind: 'paid';
@@ -5898,4 +5947,4 @@ declare function readExactDomain(publicClient: PublicClient, asset: string): Pro
5898
5947
  version: string;
5899
5948
  } | null>;
5900
5949
 
5901
- export { type AcceptOption, type AddressId, type AgentTool, type AlgorandToken, type AptosToken, type AssetId, type BuildExactParams, CHAINS, type Caip2, type ChainFamily, type ChainInput, type ChainName, type ChainPreset, type ChainSelector, type ConfirmInfo, ConfirmationTimeoutError, type CostEstimate, DIRECTORY_INFO, type DirectoryInfo, type DiscoverOptions, type DiscoveredRail, type DiscoveredResource, type DiscoverySigner, type DiscoverySource, type DomainClaim, type DomainVerification, EIP3009_TYPES, EXACT_NETWORK_SLUGS, type EvmToken, type ExactAccept, type ExactAuthorization, type ExactAuthorizationWire, type ExactPaymentPayload, type ExactRailOption, type ExpressLikeMiddleware, type ExpressLikeNext, type ExpressLikeRequest, type ExpressLikeResponse, type FacilitatorConfig, type FacilitatorPaymentRequirements, GENERATOR, HEADER_REQUIRED, HEADER_RESPONSE, HEADER_RESPONSE_V1, HEADER_SIGNATURE, HEADER_SIGNATURE_V1, InsufficientFundsError, InvalidEnvelopeError, type ListingVisibility, type ManifestInput, MaxRetriesExceededError, MissingDriverError, type NearToken, NoCompatibleAcceptError, NonReplayableBodyError, type OpenApiDocument, type OpenApiOperation, type ParsedExactPayment, type PayBlocker, type PayOption, type PayWarning, PaymentDeclinedError, type PaymentDriver, type PaymentGate, type PaymentIntent, type PaymentPlan, type PaymentPolicy, type PaymentRail, PaymentTimeoutError, PipRailClient, type PipRailClientOptions, type PipRailCostQuote, PipRailError, type PipRailEvent, type PipRailQuote, type PolicyDecision, RecipientNotReadyError, type RecipientReason, type RegisterInput, type RegisterOptions, type RegisterOutcome, type RequirePaymentOptions, type ResolveOptions, type ResolvedChain, type ResolvedNetwork, type ResolvedToken, type ResourceDescription, type SearchOpenIndexesOptions, type SettleViaFacilitatorInput, SettlementError, type SolanaToken, type SpendAssetTotal, type SpendRecord, type SpendSummary, type StellarToken, type SuiToken, type TokenInfo, type TokenInput, type TonToken, type ToolAnnotations, type TronToken, UnknownTokenError, UnsupportedNetworkError, type VerifyErrorCode, type VerifyPaymentResult, type VerifyResult, type WalletBalance, type WalletHandle, type WalletInput, type WellKnownX402, WrongChainError, WrongFamilyError, type X402AcceptEntry, type X402AnyAccept, type X402Challenge, type X402DnsRecord, type X402ExactAcceptEntry, type X402InvalidBody, type X402PaymentSignature, type X402Receipt, type X402ResourceObject, type XrplToken, buildChallengeHeader, buildExactAuthorization, buildOpenApi, buildReceiptHeader, buildSignatureHeader, buildWellKnownX402, buildX402DnsTxt, chainIdForExactNetwork, claim402IndexDomain, createPaymentGate, decorateOutcome, eip3009Abi, encodeXPaymentHeader, evaluatePolicy, getDirectoryInfo, normalizeNetwork, parseChallenge, parseExactPaymentHeader, parseExactRequirements, parseReceipt, parseSignatureHeader, paymentTools, pickAccept, planAcross, readExactDomain, register402Index, registerDriver, registerX402Scan, requirePayment, resolveChain, searchOpenIndexes, settleViaFacilitator, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
5950
+ export { type AcceptOption, type AddressId, type AgentTool, type AlgorandToken, type AptosToken, type AssetId, type BazaarExtension, type BuildExactParams, CHAINS, type Caip2, type ChainFamily, type ChainInput, type ChainName, type ChainPreset, type ChainSelector, type ConfirmInfo, ConfirmationTimeoutError, type CostEstimate, DIRECTORY_INFO, type DirectoryInfo, type DiscoverOptions, type DiscoveredRail, type DiscoveredResource, type DiscoveryDescriptor, type DiscoverySigner, type DiscoverySource, type DomainClaim, type DomainVerification, EIP3009_TYPES, EXACT_NETWORK_SLUGS, type EvmToken, type ExactAccept, type ExactAuthorization, type ExactAuthorizationWire, type ExactPaymentPayload, type ExactRailOption, type ExpressLikeMiddleware, type ExpressLikeNext, type ExpressLikeRequest, type ExpressLikeResponse, type FacilitatorConfig, type FacilitatorPaymentRequirements, GENERATOR, HEADER_REQUIRED, HEADER_RESPONSE, HEADER_RESPONSE_V1, HEADER_SIGNATURE, HEADER_SIGNATURE_V1, InsufficientFundsError, InvalidEnvelopeError, type ListingVisibility, type ManifestInput, MaxRetriesExceededError, MissingDriverError, type NearToken, NoCompatibleAcceptError, NonReplayableBodyError, type OpenApiDocument, type OpenApiOperation, type ParsedExactPayment, type PayBlocker, type PayOption, type PayWarning, PaymentDeclinedError, type PaymentDriver, type PaymentGate, type PaymentIntent, type PaymentPlan, type PaymentPolicy, type PaymentRail, PaymentTimeoutError, PipRailClient, type PipRailClientOptions, type PipRailCostQuote, PipRailError, type PipRailEvent, type PipRailQuote, type PolicyDecision, RecipientNotReadyError, type RecipientReason, type RegisterInput, type RegisterOptions, type RegisterOutcome, type RequirePaymentOptions, type ResolveOptions, type ResolvedChain, type ResolvedNetwork, type ResolvedToken, type ResourceDescription, type SearchOpenIndexesOptions, type SettleViaFacilitatorInput, SettlementError, type SolanaToken, type SpendAssetTotal, type SpendRecord, type SpendSummary, type StellarToken, type SuiToken, type TokenInfo, type TokenInput, type TonToken, type ToolAnnotations, type TronToken, UnknownTokenError, UnsupportedNetworkError, type VerifyErrorCode, type VerifyPaymentResult, type VerifyResult, type WalletBalance, type WalletHandle, type WalletInput, type WellKnownX402, WrongChainError, WrongFamilyError, type X402AcceptEntry, type X402AnyAccept, type X402Challenge, type X402DnsRecord, type X402ExactAcceptEntry, type X402InvalidBody, type X402PaymentSignature, type X402Receipt, type X402ResourceObject, type XrplToken, buildBazaarExtension, buildChallengeHeader, buildExactAuthorization, buildOpenApi, buildReceiptHeader, buildSignatureHeader, buildWellKnownX402, buildX402DnsTxt, chainIdForExactNetwork, claim402IndexDomain, createPaymentGate, decorateOutcome, eip3009Abi, encodeXPaymentHeader, evaluatePolicy, getDirectoryInfo, normalizeNetwork, parseChallenge, parseExactPaymentHeader, parseExactRequirements, parseReceipt, parseSignatureHeader, paymentTools, pickAccept, planAcross, readExactDomain, register402Index, registerDriver, registerX402Scan, requirePayment, resolveChain, searchOpenIndexes, settleViaFacilitator, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
package/dist/index.js CHANGED
@@ -1339,7 +1339,7 @@ function getDirectoryInfo(source) {
1339
1339
  }
1340
1340
  function decorateOutcome(o) {
1341
1341
  const info = DIRECTORY_INFO[o.source];
1342
- return { ...o, visibility: o.ok ? info.onSuccess : "not-listable", note: info.caveat };
1342
+ return { ...o, visibility: o.visibility ?? (o.ok ? info.onSuccess : "not-listable"), note: info.caveat };
1343
1343
  }
1344
1344
  var BAZAAR_URL = "https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources";
1345
1345
  var INDEX402_SEARCH = "https://402index.io/api/v1/services";
@@ -1495,12 +1495,15 @@ async function register402Index(input) {
1495
1495
  body: JSON.stringify(payload)
1496
1496
  });
1497
1497
  if (res.ok) {
1498
- const msg = await readIndexMessage(res);
1498
+ const body = await res.json().catch(() => ({}));
1499
+ const msg = typeof body.message === "string" && body.message.length > 0 ? body.message : void 0;
1500
+ const live = body.service?.status === "active";
1499
1501
  return {
1500
1502
  source: "402index",
1501
1503
  ok: true,
1502
1504
  status: res.status,
1503
- detail: msg ?? "Registered on 402 Index \u2014 pending review (verify your domain on 402index.io for instant approval)."
1505
+ ...live ? { visibility: "live" } : {},
1506
+ detail: msg ?? (live ? "Registered + live on 402 Index (domain verified)." : "Registered on 402 Index \u2014 pending review (verify your domain on 402index.io for instant approval).")
1504
1507
  };
1505
1508
  }
1506
1509
  const why = await readIndexError(res);
@@ -1514,14 +1517,6 @@ async function register402Index(input) {
1514
1517
  return { source: "402index", ok: false, detail: errMsg(err) };
1515
1518
  }
1516
1519
  }
1517
- async function readIndexMessage(res) {
1518
- try {
1519
- const body = await res.json();
1520
- return typeof body.message === "string" && body.message.length > 0 ? body.message : void 0;
1521
- } catch {
1522
- return void 0;
1523
- }
1524
- }
1525
1520
  async function readIndexError(res) {
1526
1521
  try {
1527
1522
  const body = await res.json();
@@ -1593,11 +1588,14 @@ async function claim402IndexDomain(domainOrUrl, opts = {}) {
1593
1588
  if (!res.ok) {
1594
1589
  return { ok: false, domain, httpStatus: res.status, detail: pickString(body, "error", "detail", "message") ?? `402 Index claim returned HTTP ${res.status}.` };
1595
1590
  }
1591
+ const verificationToken = pickString(body, "verification_token");
1592
+ const verificationHash = pickString(body, "verification_hash") ?? (verificationToken ? await sha256Hex(verificationToken) : void 0);
1596
1593
  return {
1597
1594
  ok: true,
1598
1595
  domain,
1599
1596
  httpStatus: res.status,
1600
- ...optionalString("verificationHash", pickString(body, "verification_hash")),
1597
+ ...optionalString("verificationHash", verificationHash),
1598
+ ...optionalString("verificationToken", verificationToken),
1601
1599
  ...optionalString("verificationUrl", pickString(body, "verification_url")),
1602
1600
  ...optionalString("instructions", pickString(body, "instructions"))
1603
1601
  };
@@ -1605,6 +1603,10 @@ async function claim402IndexDomain(domainOrUrl, opts = {}) {
1605
1603
  return { ok: false, domain, detail: errMsg(err) };
1606
1604
  }
1607
1605
  }
1606
+ async function sha256Hex(input) {
1607
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
1608
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
1609
+ }
1608
1610
  async function verify402IndexDomain(domainOrUrl) {
1609
1611
  const domain = hostOf(domainOrUrl);
1610
1612
  try {
@@ -1634,6 +1636,12 @@ async function readSiwxInfo(res) {
1634
1636
  const ext = body.extensions;
1635
1637
  const siwx = ext?.["sign-in-with-x"];
1636
1638
  const info = siwx?.info ?? siwx;
1639
+ if (info && info.chainId == null && Array.isArray(siwx?.supportedChains)) {
1640
+ const evm = siwx.supportedChains.find(
1641
+ (c) => typeof c?.chainId === "string" && c.chainId.startsWith("eip155:")
1642
+ );
1643
+ if (evm && typeof evm.chainId === "string") info.chainId = evm.chainId;
1644
+ }
1637
1645
  if (info && typeof info.domain === "string" && info.domain.length > 0 && typeof info.nonce === "string" && info.nonce.length > 0 && typeof info.uri === "string" && info.uri.length > 0) {
1638
1646
  return info;
1639
1647
  }
@@ -1717,7 +1725,8 @@ function firstArray(o, ...keys) {
1717
1725
  }
1718
1726
  function hostOf(url) {
1719
1727
  try {
1720
- return new URL(url).hostname;
1728
+ const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(url) ? url : `https://${url}`;
1729
+ return new URL(withScheme).hostname || url;
1721
1730
  } catch {
1722
1731
  return url;
1723
1732
  }
@@ -1726,8 +1735,13 @@ function errMsg(err) {
1726
1735
  return err instanceof Error ? err.message : String(err);
1727
1736
  }
1728
1737
  function encodeBase642(str) {
1729
- if (typeof btoa === "function") return btoa(str);
1730
1738
  if (typeof Buffer !== "undefined") return Buffer.from(str, "utf8").toString("base64");
1739
+ if (typeof btoa === "function" && typeof TextEncoder !== "undefined") {
1740
+ const bytes = new TextEncoder().encode(str);
1741
+ let binary = "";
1742
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
1743
+ return btoa(binary);
1744
+ }
1731
1745
  throw new Error("No base64 encoder available in this runtime.");
1732
1746
  }
1733
1747
 
@@ -2773,7 +2787,7 @@ function paymentTools(client) {
2773
2787
  },
2774
2788
  {
2775
2789
  name: "piprail_register",
2776
- description: "List an x402 payment-gated resource YOU run on the open indexes so other agents can discover it. Default target is 402 Index \u2014 no auth, no signature, no payment; a self-registered listing is pending review (verify your domain on 402index.io for instant approval). Returns one outcome per index ({ source, ok, detail, visibility, note }); a step the chain can't satisfy comes back ok:false with the reason. Moves no funds; nothing is PipRail-hosted.",
2790
+ description: "List an x402 payment-gated resource YOU run on the open indexes so other agents can discover it. Default target is 402 Index \u2014 no auth, no signature, no payment; a self-registered listing is pending review (verify your domain on 402index.io for instant approval). Returns one outcome per index ({ source, ok, detail, visibility, note }); a step the chain can't satisfy comes back ok:false with the reason. Moves no funds; nothing is PipRail-hosted. NOTE: index/agent payers are overwhelmingly standard `exact` clients \u2014 a default onchain-proof-only gate gets listed but they cannot pay it, so add an `exact` rail (and set the gate's `discovery` option, required for x402scan) to be usefully discoverable AND payable.",
2777
2791
  annotations: {
2778
2792
  title: "Register an x402 endpoint",
2779
2793
  readOnlyHint: false,
@@ -2807,6 +2821,87 @@ function paymentTools(client) {
2807
2821
  ];
2808
2822
  }
2809
2823
 
2824
+ // src/discovery.ts
2825
+ var GENERATOR = "@piprail/sdk \xB7 https://piprail.com";
2826
+ function buildBazaarExtension(descriptor = {}) {
2827
+ const method = (descriptor.method ?? "GET").toUpperCase();
2828
+ const queryParams = descriptor.queryParams ?? {};
2829
+ return {
2830
+ info: {
2831
+ input: { type: "http", method, queryParams },
2832
+ output: descriptor.output ?? { type: "json" }
2833
+ },
2834
+ schema: {
2835
+ $schema: "https://json-schema.org/draft/2020-12/schema",
2836
+ type: "object",
2837
+ properties: {
2838
+ input: {
2839
+ type: "object",
2840
+ properties: {
2841
+ type: { type: "string", const: "http" },
2842
+ method: { type: "string", enum: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"] },
2843
+ queryParams: { type: "object", properties: queryParams, additionalProperties: false }
2844
+ },
2845
+ required: ["type", "method"],
2846
+ additionalProperties: false
2847
+ }
2848
+ },
2849
+ required: ["input"]
2850
+ }
2851
+ };
2852
+ }
2853
+ function pathOf(url) {
2854
+ try {
2855
+ return new URL(url).pathname || "/";
2856
+ } catch {
2857
+ return url.startsWith("/") ? url : `/${url}`;
2858
+ }
2859
+ }
2860
+ function buildOpenApi(input) {
2861
+ const paths = {};
2862
+ for (const r of input.resources) {
2863
+ const path = pathOf(r.url);
2864
+ const method = (r.method ?? "GET").toLowerCase();
2865
+ const op = {
2866
+ ...r.description ? { summary: r.description } : {},
2867
+ responses: {
2868
+ "200": { description: "Paid \u2014 the resource." },
2869
+ "402": { description: "Payment required (x402)." }
2870
+ },
2871
+ "x-payment-info": {
2872
+ x402Version: 2,
2873
+ accepts: r.accepts
2874
+ }
2875
+ };
2876
+ paths[path] = { ...paths[path] ?? {}, [method]: op };
2877
+ }
2878
+ return {
2879
+ openapi: "3.1.0",
2880
+ info: { title: input.title ?? "PipRail x402 resources", version: input.version ?? "1.0.0" },
2881
+ servers: [{ url: input.origin }],
2882
+ paths,
2883
+ // "Built with @piprail/sdk" — default on (opt out with attribution:false). At the
2884
+ // document ROOT so `info` stays exactly { title, version }.
2885
+ ...input.attribution === false ? {} : { "x-generator": GENERATOR },
2886
+ ...input.ownershipProofs && input.ownershipProofs.length > 0 ? { "x-agentcash-provenance": { ownershipProofs: input.ownershipProofs } } : {}
2887
+ };
2888
+ }
2889
+ function buildWellKnownX402(input) {
2890
+ return {
2891
+ version: 1,
2892
+ resources: input.resources.map((r) => r.url),
2893
+ ...input.ownershipProofs && input.ownershipProofs.length > 0 ? { ownershipProofs: input.ownershipProofs } : {}
2894
+ };
2895
+ }
2896
+ function buildX402DnsTxt(input) {
2897
+ const descriptor = input.descriptor ? `descriptor=${input.descriptor};` : "";
2898
+ return {
2899
+ name: `_x402.${input.host}`,
2900
+ type: "TXT",
2901
+ value: `v=x4021;${descriptor}url=${input.discoveryUrl}`
2902
+ };
2903
+ }
2904
+
2810
2905
  // src/facilitator.ts
2811
2906
  function safeStringify(value) {
2812
2907
  return JSON.stringify(value, (_k, v) => typeof v === "bigint" ? v.toString() : v);
@@ -3055,6 +3150,8 @@ function createPaymentGate(options) {
3055
3150
  async function makeChallenge(resourceUrl, opts) {
3056
3151
  const specs = await ready();
3057
3152
  const nonce = genNonce();
3153
+ const bazaar = options.discovery ? { bazaar: buildBazaarExtension(options.discovery === true ? {} : options.discovery) } : void 0;
3154
+ const extensions = { ...bazaar, ...opts?.extensions };
3058
3155
  const challenge2 = {
3059
3156
  x402Version: 2,
3060
3157
  resource: {
@@ -3063,7 +3160,7 @@ function createPaymentGate(options) {
3063
3160
  },
3064
3161
  accepts: buildAccepts(specs, nonce),
3065
3162
  ...opts?.error ? { error: opts.error } : {},
3066
- ...opts?.extensions ? { extensions: opts.extensions } : {}
3163
+ ...Object.keys(extensions).length > 0 ? { extensions } : {}
3067
3164
  };
3068
3165
  return { challenge: challenge2, requiredHeader: buildChallengeHeader(challenge2) };
3069
3166
  }
@@ -3249,61 +3346,6 @@ function normaliseHeader(value) {
3249
3346
  if (Array.isArray(value)) return value[0];
3250
3347
  return value;
3251
3348
  }
3252
-
3253
- // src/discovery.ts
3254
- var GENERATOR = "@piprail/sdk \xB7 https://piprail.com";
3255
- function pathOf(url) {
3256
- try {
3257
- return new URL(url).pathname || "/";
3258
- } catch {
3259
- return url.startsWith("/") ? url : `/${url}`;
3260
- }
3261
- }
3262
- function buildOpenApi(input) {
3263
- const paths = {};
3264
- for (const r of input.resources) {
3265
- const path = pathOf(r.url);
3266
- const method = (r.method ?? "GET").toLowerCase();
3267
- const op = {
3268
- ...r.description ? { summary: r.description } : {},
3269
- responses: {
3270
- "200": { description: "Paid \u2014 the resource." },
3271
- "402": { description: "Payment required (x402)." }
3272
- },
3273
- "x-payment-info": {
3274
- x402Version: 2,
3275
- accepts: r.accepts,
3276
- bazaar: { discoverable: true }
3277
- }
3278
- };
3279
- paths[path] = { ...paths[path] ?? {}, [method]: op };
3280
- }
3281
- return {
3282
- openapi: "3.1.0",
3283
- info: { title: input.title ?? "PipRail x402 resources", version: input.version ?? "1.0.0" },
3284
- servers: [{ url: input.origin }],
3285
- paths,
3286
- // "Built with @piprail/sdk" — default on (opt out with attribution:false). At the
3287
- // document ROOT so `info` stays exactly { title, version }.
3288
- ...input.attribution === false ? {} : { "x-generator": GENERATOR },
3289
- ...input.ownershipProofs && input.ownershipProofs.length > 0 ? { "x-agentcash-provenance": { ownershipProofs: input.ownershipProofs } } : {}
3290
- };
3291
- }
3292
- function buildWellKnownX402(input) {
3293
- return {
3294
- version: 1,
3295
- resources: input.resources.map((r) => r.url),
3296
- ...input.ownershipProofs && input.ownershipProofs.length > 0 ? { ownershipProofs: input.ownershipProofs } : {}
3297
- };
3298
- }
3299
- function buildX402DnsTxt(input) {
3300
- const descriptor = input.descriptor ? `descriptor=${input.descriptor};` : "";
3301
- return {
3302
- name: `_x402.${input.host}`,
3303
- type: "TXT",
3304
- value: `v=x4021;${descriptor}url=${input.discoveryUrl}`
3305
- };
3306
- }
3307
3349
  export {
3308
3350
  CHAINS,
3309
3351
  ConfirmationTimeoutError,
@@ -3332,6 +3374,7 @@ export {
3332
3374
  UnsupportedNetworkError,
3333
3375
  WrongChainError,
3334
3376
  WrongFamilyError,
3377
+ buildBazaarExtension,
3335
3378
  buildChallengeHeader,
3336
3379
  buildExactAuthorization,
3337
3380
  buildOpenApi,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piprail/sdk",
3
- "version": "1.12.0",
3
+ "version": "1.13.1",
4
4
  "description": "Accept x402 crypto payments across 29 chains — every major EVM chain plus Solana, TON, Tron, NEAR, Sui, Aptos, Algorand, Stellar & XRPL — in a couple of lines. No backend, no database, no fee; payments settle straight to your wallet.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",