@piprail/sdk 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,28 @@ 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.0] — 2026-06-10
8
+
9
+ ### Added — gate `discovery` option (one flag → x402scan-listable)
10
+ - **`createPaymentGate`/`requirePayment` now take an opt-in `discovery` option** that emits an
11
+ `extensions.bazaar` block **in the 402 challenge itself** — so the gate alone satisfies x402scan's
12
+ mandatory input-schema check (no separately-served file needed). `discovery: true` for a no-input
13
+ GET, or a `DiscoveryDescriptor` (`{ method, queryParams, output }`). Omitting it leaves the challenge
14
+ byte-identical. New export `buildBazaarExtension` + the `DiscoveryDescriptor`/`BazaarExtension` types.
15
+ - `DomainClaim` now also surfaces `verificationToken`, and `verificationHash` is **always** populated —
16
+ from the API, or computed as `sha256(verificationToken)` — so an agent always has the exact bytes to serve.
17
+
18
+ ### Fixed — conformance bug hunt (20-agent audit, every finding verified against the live API)
19
+ - **`hostOf` returned `''` for a bare `host:port`** (`new URL('x.com:8080')` parses the host as a scheme) —
20
+ `claimDomain`/`verifyDomain` now extract the host correctly.
21
+ - **`indexes.ts` base64 was Latin1-only** (`btoa`) — now UTF-8-safe, matching `x402.ts` (a non-ASCII SIWX
22
+ field could have thrown in the browser).
23
+ - **SIWX message** now reads `chainId` from `supportedChains[]` as a fallback (not only `info.chainId`),
24
+ so it always signs the correct `Chain ID`.
25
+ - Removed an invented `x-payment-info.bazaar:{discoverable:true}` marker from `buildOpenApi` (no index read it).
26
+ - Documented the caveat that the open indexes' agents are standard `exact` clients — advertise an `exact`
27
+ rail to be *payable*, not just listed (README + the `piprail_register` MCP tool).
28
+
7
29
  ## [1.12.0] — 2026-06-09
8
30
 
9
31
  ### Added — One-call domain verification (pending-review → searchable)
@@ -593,6 +615,7 @@ straight into your wallet. The API is small and self-contained.
593
615
  to your wallet; PipRail never holds funds.
594
616
  - `viem ^2.21` is a peer dependency. Node 20+ or a modern browser.
595
617
 
618
+ [1.13.0]: https://www.npmjs.com/package/@piprail/sdk
596
619
  [1.12.0]: https://www.npmjs.com/package/@piprail/sdk
597
620
  [1.11.0]: https://www.npmjs.com/package/@piprail/sdk
598
621
  [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
@@ -1593,11 +1593,14 @@ async function claim402IndexDomain(domainOrUrl, opts = {}) {
1593
1593
  if (!res.ok) {
1594
1594
  return { ok: false, domain, httpStatus: res.status, detail: _nullishCoalesce(pickString(body, "error", "detail", "message"), () => ( `402 Index claim returned HTTP ${res.status}.`)) };
1595
1595
  }
1596
+ const verificationToken = pickString(body, "verification_token");
1597
+ const verificationHash = await _asyncNullishCoalesce(pickString(body, "verification_hash"), async () => ( (verificationToken ? await sha256Hex(verificationToken) : void 0)));
1596
1598
  return {
1597
1599
  ok: true,
1598
1600
  domain,
1599
1601
  httpStatus: res.status,
1600
- ...optionalString("verificationHash", pickString(body, "verification_hash")),
1602
+ ...optionalString("verificationHash", verificationHash),
1603
+ ...optionalString("verificationToken", verificationToken),
1601
1604
  ...optionalString("verificationUrl", pickString(body, "verification_url")),
1602
1605
  ...optionalString("instructions", pickString(body, "instructions"))
1603
1606
  };
@@ -1605,6 +1608,10 @@ async function claim402IndexDomain(domainOrUrl, opts = {}) {
1605
1608
  return { ok: false, domain, detail: errMsg(err) };
1606
1609
  }
1607
1610
  }
1611
+ async function sha256Hex(input) {
1612
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
1613
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
1614
+ }
1608
1615
  async function verify402IndexDomain(domainOrUrl) {
1609
1616
  const domain = hostOf(domainOrUrl);
1610
1617
  try {
@@ -1634,6 +1641,12 @@ async function readSiwxInfo(res) {
1634
1641
  const ext = body.extensions;
1635
1642
  const siwx = _optionalChain([ext, 'optionalAccess', _8 => _8["sign-in-with-x"]]);
1636
1643
  const info = _nullishCoalesce(_optionalChain([siwx, 'optionalAccess', _9 => _9.info]), () => ( siwx));
1644
+ if (info && info.chainId == null && Array.isArray(_optionalChain([siwx, 'optionalAccess', _10 => _10.supportedChains]))) {
1645
+ const evm = siwx.supportedChains.find(
1646
+ (c) => typeof _optionalChain([c, 'optionalAccess', _11 => _11.chainId]) === "string" && c.chainId.startsWith("eip155:")
1647
+ );
1648
+ if (evm && typeof evm.chainId === "string") info.chainId = evm.chainId;
1649
+ }
1637
1650
  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
1651
  return info;
1639
1652
  }
@@ -1686,7 +1699,7 @@ function mapRails(accepts) {
1686
1699
  }
1687
1700
  function matchesQuery(r, query) {
1688
1701
  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)));
1702
+ return r.resource.toLowerCase().includes(q) || (_nullishCoalesce(_optionalChain([r, 'access', _12 => _12.name, 'optionalAccess', _13 => _13.toLowerCase, 'call', _14 => _14(), 'access', _15 => _15.includes, 'call', _16 => _16(q)]), () => ( false))) || (_nullishCoalesce(_optionalChain([r, 'access', _17 => _17.description, 'optionalAccess', _18 => _18.toLowerCase, 'call', _19 => _19(), 'access', _20 => _20.includes, 'call', _21 => _21(q)]), () => ( false)));
1690
1703
  }
1691
1704
  function pickString(o, ...keys) {
1692
1705
  for (const k of keys) {
@@ -1717,7 +1730,8 @@ function firstArray(o, ...keys) {
1717
1730
  }
1718
1731
  function hostOf(url) {
1719
1732
  try {
1720
- return new URL(url).hostname;
1733
+ const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(url) ? url : `https://${url}`;
1734
+ return new URL(withScheme).hostname || url;
1721
1735
  } catch (e20) {
1722
1736
  return url;
1723
1737
  }
@@ -1726,8 +1740,13 @@ function errMsg(err) {
1726
1740
  return err instanceof Error ? err.message : String(err);
1727
1741
  }
1728
1742
  function encodeBase642(str) {
1729
- if (typeof btoa === "function") return btoa(str);
1730
1743
  if (typeof Buffer !== "undefined") return Buffer.from(str, "utf8").toString("base64");
1744
+ if (typeof btoa === "function" && typeof TextEncoder !== "undefined") {
1745
+ const bytes = new TextEncoder().encode(str);
1746
+ let binary = "";
1747
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
1748
+ return btoa(binary);
1749
+ }
1731
1750
  throw new Error("No base64 encoder available in this runtime.");
1732
1751
  }
1733
1752
 
@@ -1824,7 +1843,7 @@ var SpendLedger = (_class = class {constructor() { _class.prototype.__init.call(
1824
1843
  }
1825
1844
  /** Running total (base units) already spent on this (network, asset). */
1826
1845
  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));
1846
+ return _nullishCoalesce(_optionalChain([this, 'access', _22 => _22.buckets, 'access', _23 => _23.get, 'call', _24 => _24(keyFor(network, asset)), 'optionalAccess', _25 => _25.total]), () => ( 0n));
1828
1847
  }
1829
1848
  /** An immutable snapshot of all spend so far. */
1830
1849
  summary() {
@@ -1898,7 +1917,7 @@ var PipRailClient = (_class2 = class {
1898
1917
  * as-is) or a plain object (serialised as JSON).
1899
1918
  */
1900
1919
  post(url, body, init) {
1901
- const headers = new Headers(_optionalChain([init, 'optionalAccess', _24 => _24.headers]));
1920
+ const headers = new Headers(_optionalChain([init, 'optionalAccess', _26 => _26.headers]));
1902
1921
  let payload;
1903
1922
  if (body === void 0 || body === null) {
1904
1923
  payload = void 0;
@@ -1929,7 +1948,7 @@ var PipRailClient = (_class2 = class {
1929
1948
  * "0.05 USDC on Base, within budget → pay it." No funds move.
1930
1949
  */
1931
1950
  async quote(url, init) {
1932
- const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _25 => _25.method]), () => ( "GET")) });
1951
+ const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _27 => _27.method]), () => ( "GET")) });
1933
1952
  if (res.status !== 402) return null;
1934
1953
  const { quote } = await this.resolveChallenge(url, res);
1935
1954
  return quote;
@@ -1948,7 +1967,7 @@ var PipRailClient = (_class2 = class {
1948
1967
  * on Tron, where a USD₮ transfer can cost real TRX.
1949
1968
  */
1950
1969
  async estimateCost(url, init) {
1951
- const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _26 => _26.method]), () => ( "GET")) });
1970
+ const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _28 => _28.method]), () => ( "GET")) });
1952
1971
  if (res.status !== 402) return null;
1953
1972
  const { net, accept, quote } = await this.resolveChallenge(url, res);
1954
1973
  const cost = await net.estimateCost(accept);
@@ -1979,7 +1998,7 @@ var PipRailClient = (_class2 = class {
1979
1998
  * the plan yourself. No funds move.
1980
1999
  */
1981
2000
  async planPayment(url, init) {
1982
- const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _27 => _27.method]), () => ( "GET")) });
2001
+ const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _29 => _29.method]), () => ( "GET")) });
1983
2002
  if (res.status !== 402) return null;
1984
2003
  const challenge = await parseChallenge(res);
1985
2004
  if (!challenge) {
@@ -2146,7 +2165,7 @@ var PipRailClient = (_class2 = class {
2146
2165
  * streams throw `NonReplayableBodyError`.
2147
2166
  */
2148
2167
  async fetch(url, init) {
2149
- const body = _optionalChain([init, 'optionalAccess', _28 => _28.body]);
2168
+ const body = _optionalChain([init, 'optionalAccess', _30 => _30.body]);
2150
2169
  if (body !== void 0 && body !== null && !isReplayableBodyInit(body)) {
2151
2170
  throw new (0, _chunkMDLZJGLYcjs.NonReplayableBodyError)(
2152
2171
  "fetch(): init.body is not replayable. Pass a string, FormData, URLSearchParams, ArrayBuffer, or Blob \u2014 not a ReadableStream."
@@ -2158,7 +2177,7 @@ var PipRailClient = (_class2 = class {
2158
2177
  const { net, wallet, challenge } = resolved;
2159
2178
  let accept = resolved.accept;
2160
2179
  let quote = resolved.quote;
2161
- const autoRoute = _nullishCoalesce(_nullishCoalesce(_optionalChain([init, 'optionalAccess', _29 => _29.autoRoute]), () => ( this.opts.autoRoute)), () => ( false));
2180
+ const autoRoute = _nullishCoalesce(_nullishCoalesce(_optionalChain([init, 'optionalAccess', _31 => _31.autoRoute]), () => ( this.opts.autoRoute)), () => ( false));
2162
2181
  if (autoRoute) {
2163
2182
  const plan = await this.planFromChallenge(net, wallet, challenge, url);
2164
2183
  if (!plan.best) {
@@ -2320,8 +2339,8 @@ var PipRailClient = (_class2 = class {
2320
2339
  }
2321
2340
  const amountBase = BigInt(accept.amount);
2322
2341
  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));
2342
+ const decimals = _nullishCoalesce(_optionalChain([described, 'optionalAccess', _32 => _32.decimals]), () => ( accept.extra.decimals));
2343
+ const symbol = _nullishCoalesce(_optionalChain([described, 'optionalAccess', _33 => _33.symbol]), () => ( accept.extra.symbol));
2325
2344
  const amountFormatted = _chunkMDLZJGLYcjs.formatUnits.call(void 0, amountBase, decimals);
2326
2345
  const intent = {
2327
2346
  host: hostOf2(url),
@@ -2430,7 +2449,7 @@ var PipRailClient = (_class2 = class {
2430
2449
  accepted: accept,
2431
2450
  payload: { nonce: accept.extra.nonce, txHash: ref }
2432
2451
  };
2433
- const headers = new Headers(_optionalChain([originalInit, 'optionalAccess', _32 => _32.headers]));
2452
+ const headers = new Headers(_optionalChain([originalInit, 'optionalAccess', _34 => _34.headers]));
2434
2453
  headers.set(HEADER_SIGNATURE, buildSignatureHeader(signature));
2435
2454
  let lastResponse = null;
2436
2455
  let lastReason = null;
@@ -2445,7 +2464,7 @@ var PipRailClient = (_class2 = class {
2445
2464
  () => timeoutController.abort(),
2446
2465
  this.retryTimeoutMs
2447
2466
  );
2448
- const signal = _optionalChain([originalInit, 'optionalAccess', _33 => _33.signal]) && typeof AbortSignal.any === "function" ? AbortSignal.any([timeoutController.signal, originalInit.signal]) : timeoutController.signal;
2467
+ const signal = _optionalChain([originalInit, 'optionalAccess', _35 => _35.signal]) && typeof AbortSignal.any === "function" ? AbortSignal.any([timeoutController.signal, originalInit.signal]) : timeoutController.signal;
2449
2468
  try {
2450
2469
  lastResponse = await fetch(url, {
2451
2470
  ..._nullishCoalesce(originalInit, () => ( {})),
@@ -2518,10 +2537,10 @@ function buildFundingHint(options, chainLabel) {
2518
2537
  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
2538
  }
2520
2539
  const parts = [];
2521
- if (target.blockers.includes("INSUFFICIENT_TOKEN") && _optionalChain([target, 'access', _34 => _34.shortfall, 'optionalAccess', _35 => _35.token])) {
2540
+ if (target.blockers.includes("INSUFFICIENT_TOKEN") && _optionalChain([target, 'access', _36 => _36.shortfall, 'optionalAccess', _37 => _37.token])) {
2522
2541
  parts.push(`top up ${target.shortfall.token} ${sym}`);
2523
2542
  }
2524
- if (target.blockers.includes("INSUFFICIENT_GAS") && _optionalChain([target, 'access', _36 => _36.shortfall, 'optionalAccess', _37 => _37.native])) {
2543
+ if (target.blockers.includes("INSUFFICIENT_GAS") && _optionalChain([target, 'access', _38 => _38.shortfall, 'optionalAccess', _39 => _39.native])) {
2525
2544
  parts.push(`add ~${target.shortfall.native} ${target.cost.feeSymbol} for gas`);
2526
2545
  }
2527
2546
  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 +2554,7 @@ async function planAcross(clients, url, init) {
2535
2554
  const status = best ? "ready" : options.some((o) => o.state === "unknown") ? "unknown" : "blocked";
2536
2555
  return {
2537
2556
  url,
2538
- network: _nullishCoalesce(_optionalChain([best, 'optionalAccess', _38 => _38.accept, 'access', _39 => _39.network]), () => ( live[0].network)),
2557
+ network: _nullishCoalesce(_optionalChain([best, 'optionalAccess', _40 => _40.accept, 'access', _41 => _41.network]), () => ( live[0].network)),
2539
2558
  status,
2540
2559
  payable: best !== null,
2541
2560
  best,
@@ -2568,8 +2587,8 @@ function isReplayableBodyInit(value) {
2568
2587
  async function readInvalidReason(response) {
2569
2588
  try {
2570
2589
  const body = await response.clone().json();
2571
- const ext = _optionalChain([body, 'optionalAccess', _40 => _40.extensions]);
2572
- const piprail = _optionalChain([ext, 'optionalAccess', _41 => _41.piprail]);
2590
+ const ext = _optionalChain([body, 'optionalAccess', _42 => _42.extensions]);
2591
+ const piprail = _optionalChain([ext, 'optionalAccess', _43 => _43.piprail]);
2573
2592
  if (piprail && typeof piprail.code === "string") {
2574
2593
  return {
2575
2594
  error: piprail.code,
@@ -2773,7 +2792,7 @@ function paymentTools(client) {
2773
2792
  },
2774
2793
  {
2775
2794
  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.",
2795
+ 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
2796
  annotations: {
2778
2797
  title: "Register an x402 endpoint",
2779
2798
  readOnlyHint: false,
@@ -2807,6 +2826,87 @@ function paymentTools(client) {
2807
2826
  ];
2808
2827
  }
2809
2828
 
2829
+ // src/discovery.ts
2830
+ var GENERATOR = "@piprail/sdk \xB7 https://piprail.com";
2831
+ function buildBazaarExtension(descriptor = {}) {
2832
+ const method = (_nullishCoalesce(descriptor.method, () => ( "GET"))).toUpperCase();
2833
+ const queryParams = _nullishCoalesce(descriptor.queryParams, () => ( {}));
2834
+ return {
2835
+ info: {
2836
+ input: { type: "http", method, queryParams },
2837
+ output: _nullishCoalesce(descriptor.output, () => ( { type: "json" }))
2838
+ },
2839
+ schema: {
2840
+ $schema: "https://json-schema.org/draft/2020-12/schema",
2841
+ type: "object",
2842
+ properties: {
2843
+ input: {
2844
+ type: "object",
2845
+ properties: {
2846
+ type: { type: "string", const: "http" },
2847
+ method: { type: "string", enum: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"] },
2848
+ queryParams: { type: "object", properties: queryParams, additionalProperties: false }
2849
+ },
2850
+ required: ["type", "method"],
2851
+ additionalProperties: false
2852
+ }
2853
+ },
2854
+ required: ["input"]
2855
+ }
2856
+ };
2857
+ }
2858
+ function pathOf(url) {
2859
+ try {
2860
+ return new URL(url).pathname || "/";
2861
+ } catch (e26) {
2862
+ return url.startsWith("/") ? url : `/${url}`;
2863
+ }
2864
+ }
2865
+ function buildOpenApi(input) {
2866
+ const paths = {};
2867
+ for (const r of input.resources) {
2868
+ const path = pathOf(r.url);
2869
+ const method = (_nullishCoalesce(r.method, () => ( "GET"))).toLowerCase();
2870
+ const op = {
2871
+ ...r.description ? { summary: r.description } : {},
2872
+ responses: {
2873
+ "200": { description: "Paid \u2014 the resource." },
2874
+ "402": { description: "Payment required (x402)." }
2875
+ },
2876
+ "x-payment-info": {
2877
+ x402Version: 2,
2878
+ accepts: r.accepts
2879
+ }
2880
+ };
2881
+ paths[path] = { ..._nullishCoalesce(paths[path], () => ( {})), [method]: op };
2882
+ }
2883
+ return {
2884
+ openapi: "3.1.0",
2885
+ info: { title: _nullishCoalesce(input.title, () => ( "PipRail x402 resources")), version: _nullishCoalesce(input.version, () => ( "1.0.0")) },
2886
+ servers: [{ url: input.origin }],
2887
+ paths,
2888
+ // "Built with @piprail/sdk" — default on (opt out with attribution:false). At the
2889
+ // document ROOT so `info` stays exactly { title, version }.
2890
+ ...input.attribution === false ? {} : { "x-generator": GENERATOR },
2891
+ ...input.ownershipProofs && input.ownershipProofs.length > 0 ? { "x-agentcash-provenance": { ownershipProofs: input.ownershipProofs } } : {}
2892
+ };
2893
+ }
2894
+ function buildWellKnownX402(input) {
2895
+ return {
2896
+ version: 1,
2897
+ resources: input.resources.map((r) => r.url),
2898
+ ...input.ownershipProofs && input.ownershipProofs.length > 0 ? { ownershipProofs: input.ownershipProofs } : {}
2899
+ };
2900
+ }
2901
+ function buildX402DnsTxt(input) {
2902
+ const descriptor = input.descriptor ? `descriptor=${input.descriptor};` : "";
2903
+ return {
2904
+ name: `_x402.${input.host}`,
2905
+ type: "TXT",
2906
+ value: `v=x4021;${descriptor}url=${input.discoveryUrl}`
2907
+ };
2908
+ }
2909
+
2810
2910
  // src/facilitator.ts
2811
2911
  function safeStringify(value) {
2812
2912
  return JSON.stringify(value, (_k, v) => typeof v === "bigint" ? v.toString() : v);
@@ -2831,7 +2931,7 @@ async function post(url, body, headers) {
2831
2931
  let json = null;
2832
2932
  try {
2833
2933
  json = await res.json();
2834
- } catch (e26) {
2934
+ } catch (e27) {
2835
2935
  }
2836
2936
  return { status: res.status, json };
2837
2937
  }
@@ -3055,6 +3155,8 @@ function createPaymentGate(options) {
3055
3155
  async function makeChallenge(resourceUrl, opts) {
3056
3156
  const specs = await ready();
3057
3157
  const nonce = genNonce();
3158
+ const bazaar = options.discovery ? { bazaar: buildBazaarExtension(options.discovery === true ? {} : options.discovery) } : void 0;
3159
+ const extensions = { ...bazaar, ..._optionalChain([opts, 'optionalAccess', _44 => _44.extensions]) };
3058
3160
  const challenge2 = {
3059
3161
  x402Version: 2,
3060
3162
  resource: {
@@ -3062,8 +3164,8 @@ function createPaymentGate(options) {
3062
3164
  ...options.description ? { description: options.description } : {}
3063
3165
  },
3064
3166
  accepts: buildAccepts(specs, nonce),
3065
- ..._optionalChain([opts, 'optionalAccess', _42 => _42.error]) ? { error: opts.error } : {},
3066
- ..._optionalChain([opts, 'optionalAccess', _43 => _43.extensions]) ? { extensions: opts.extensions } : {}
3167
+ ..._optionalChain([opts, 'optionalAccess', _45 => _45.error]) ? { error: opts.error } : {},
3168
+ ...Object.keys(extensions).length > 0 ? { extensions } : {}
3067
3169
  };
3068
3170
  return { challenge: challenge2, requiredHeader: buildChallengeHeader(challenge2) };
3069
3171
  }
@@ -3085,7 +3187,7 @@ function createPaymentGate(options) {
3085
3187
  if (options.onPaid) {
3086
3188
  try {
3087
3189
  options.onPaid(receipt);
3088
- } catch (e27) {
3190
+ } catch (e28) {
3089
3191
  }
3090
3192
  }
3091
3193
  }
@@ -3250,60 +3352,6 @@ function normaliseHeader(value) {
3250
3352
  return value;
3251
3353
  }
3252
3354
 
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
3355
 
3308
3356
 
3309
3357
 
@@ -3367,4 +3415,4 @@ function buildX402DnsTxt(input) {
3367
3415
 
3368
3416
 
3369
3417
 
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;
3418
+ 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
@@ -1593,11 +1593,14 @@ async function claim402IndexDomain(domainOrUrl, opts = {}) {
1593
1593
  if (!res.ok) {
1594
1594
  return { ok: false, domain, httpStatus: res.status, detail: pickString(body, "error", "detail", "message") ?? `402 Index claim returned HTTP ${res.status}.` };
1595
1595
  }
1596
+ const verificationToken = pickString(body, "verification_token");
1597
+ const verificationHash = pickString(body, "verification_hash") ?? (verificationToken ? await sha256Hex(verificationToken) : void 0);
1596
1598
  return {
1597
1599
  ok: true,
1598
1600
  domain,
1599
1601
  httpStatus: res.status,
1600
- ...optionalString("verificationHash", pickString(body, "verification_hash")),
1602
+ ...optionalString("verificationHash", verificationHash),
1603
+ ...optionalString("verificationToken", verificationToken),
1601
1604
  ...optionalString("verificationUrl", pickString(body, "verification_url")),
1602
1605
  ...optionalString("instructions", pickString(body, "instructions"))
1603
1606
  };
@@ -1605,6 +1608,10 @@ async function claim402IndexDomain(domainOrUrl, opts = {}) {
1605
1608
  return { ok: false, domain, detail: errMsg(err) };
1606
1609
  }
1607
1610
  }
1611
+ async function sha256Hex(input) {
1612
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
1613
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
1614
+ }
1608
1615
  async function verify402IndexDomain(domainOrUrl) {
1609
1616
  const domain = hostOf(domainOrUrl);
1610
1617
  try {
@@ -1634,6 +1641,12 @@ async function readSiwxInfo(res) {
1634
1641
  const ext = body.extensions;
1635
1642
  const siwx = ext?.["sign-in-with-x"];
1636
1643
  const info = siwx?.info ?? siwx;
1644
+ if (info && info.chainId == null && Array.isArray(siwx?.supportedChains)) {
1645
+ const evm = siwx.supportedChains.find(
1646
+ (c) => typeof c?.chainId === "string" && c.chainId.startsWith("eip155:")
1647
+ );
1648
+ if (evm && typeof evm.chainId === "string") info.chainId = evm.chainId;
1649
+ }
1637
1650
  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
1651
  return info;
1639
1652
  }
@@ -1717,7 +1730,8 @@ function firstArray(o, ...keys) {
1717
1730
  }
1718
1731
  function hostOf(url) {
1719
1732
  try {
1720
- return new URL(url).hostname;
1733
+ const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(url) ? url : `https://${url}`;
1734
+ return new URL(withScheme).hostname || url;
1721
1735
  } catch {
1722
1736
  return url;
1723
1737
  }
@@ -1726,8 +1740,13 @@ function errMsg(err) {
1726
1740
  return err instanceof Error ? err.message : String(err);
1727
1741
  }
1728
1742
  function encodeBase642(str) {
1729
- if (typeof btoa === "function") return btoa(str);
1730
1743
  if (typeof Buffer !== "undefined") return Buffer.from(str, "utf8").toString("base64");
1744
+ if (typeof btoa === "function" && typeof TextEncoder !== "undefined") {
1745
+ const bytes = new TextEncoder().encode(str);
1746
+ let binary = "";
1747
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
1748
+ return btoa(binary);
1749
+ }
1731
1750
  throw new Error("No base64 encoder available in this runtime.");
1732
1751
  }
1733
1752
 
@@ -2773,7 +2792,7 @@ function paymentTools(client) {
2773
2792
  },
2774
2793
  {
2775
2794
  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.",
2795
+ 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
2796
  annotations: {
2778
2797
  title: "Register an x402 endpoint",
2779
2798
  readOnlyHint: false,
@@ -2807,6 +2826,87 @@ function paymentTools(client) {
2807
2826
  ];
2808
2827
  }
2809
2828
 
2829
+ // src/discovery.ts
2830
+ var GENERATOR = "@piprail/sdk \xB7 https://piprail.com";
2831
+ function buildBazaarExtension(descriptor = {}) {
2832
+ const method = (descriptor.method ?? "GET").toUpperCase();
2833
+ const queryParams = descriptor.queryParams ?? {};
2834
+ return {
2835
+ info: {
2836
+ input: { type: "http", method, queryParams },
2837
+ output: descriptor.output ?? { type: "json" }
2838
+ },
2839
+ schema: {
2840
+ $schema: "https://json-schema.org/draft/2020-12/schema",
2841
+ type: "object",
2842
+ properties: {
2843
+ input: {
2844
+ type: "object",
2845
+ properties: {
2846
+ type: { type: "string", const: "http" },
2847
+ method: { type: "string", enum: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"] },
2848
+ queryParams: { type: "object", properties: queryParams, additionalProperties: false }
2849
+ },
2850
+ required: ["type", "method"],
2851
+ additionalProperties: false
2852
+ }
2853
+ },
2854
+ required: ["input"]
2855
+ }
2856
+ };
2857
+ }
2858
+ function pathOf(url) {
2859
+ try {
2860
+ return new URL(url).pathname || "/";
2861
+ } catch {
2862
+ return url.startsWith("/") ? url : `/${url}`;
2863
+ }
2864
+ }
2865
+ function buildOpenApi(input) {
2866
+ const paths = {};
2867
+ for (const r of input.resources) {
2868
+ const path = pathOf(r.url);
2869
+ const method = (r.method ?? "GET").toLowerCase();
2870
+ const op = {
2871
+ ...r.description ? { summary: r.description } : {},
2872
+ responses: {
2873
+ "200": { description: "Paid \u2014 the resource." },
2874
+ "402": { description: "Payment required (x402)." }
2875
+ },
2876
+ "x-payment-info": {
2877
+ x402Version: 2,
2878
+ accepts: r.accepts
2879
+ }
2880
+ };
2881
+ paths[path] = { ...paths[path] ?? {}, [method]: op };
2882
+ }
2883
+ return {
2884
+ openapi: "3.1.0",
2885
+ info: { title: input.title ?? "PipRail x402 resources", version: input.version ?? "1.0.0" },
2886
+ servers: [{ url: input.origin }],
2887
+ paths,
2888
+ // "Built with @piprail/sdk" — default on (opt out with attribution:false). At the
2889
+ // document ROOT so `info` stays exactly { title, version }.
2890
+ ...input.attribution === false ? {} : { "x-generator": GENERATOR },
2891
+ ...input.ownershipProofs && input.ownershipProofs.length > 0 ? { "x-agentcash-provenance": { ownershipProofs: input.ownershipProofs } } : {}
2892
+ };
2893
+ }
2894
+ function buildWellKnownX402(input) {
2895
+ return {
2896
+ version: 1,
2897
+ resources: input.resources.map((r) => r.url),
2898
+ ...input.ownershipProofs && input.ownershipProofs.length > 0 ? { ownershipProofs: input.ownershipProofs } : {}
2899
+ };
2900
+ }
2901
+ function buildX402DnsTxt(input) {
2902
+ const descriptor = input.descriptor ? `descriptor=${input.descriptor};` : "";
2903
+ return {
2904
+ name: `_x402.${input.host}`,
2905
+ type: "TXT",
2906
+ value: `v=x4021;${descriptor}url=${input.discoveryUrl}`
2907
+ };
2908
+ }
2909
+
2810
2910
  // src/facilitator.ts
2811
2911
  function safeStringify(value) {
2812
2912
  return JSON.stringify(value, (_k, v) => typeof v === "bigint" ? v.toString() : v);
@@ -3055,6 +3155,8 @@ function createPaymentGate(options) {
3055
3155
  async function makeChallenge(resourceUrl, opts) {
3056
3156
  const specs = await ready();
3057
3157
  const nonce = genNonce();
3158
+ const bazaar = options.discovery ? { bazaar: buildBazaarExtension(options.discovery === true ? {} : options.discovery) } : void 0;
3159
+ const extensions = { ...bazaar, ...opts?.extensions };
3058
3160
  const challenge2 = {
3059
3161
  x402Version: 2,
3060
3162
  resource: {
@@ -3063,7 +3165,7 @@ function createPaymentGate(options) {
3063
3165
  },
3064
3166
  accepts: buildAccepts(specs, nonce),
3065
3167
  ...opts?.error ? { error: opts.error } : {},
3066
- ...opts?.extensions ? { extensions: opts.extensions } : {}
3168
+ ...Object.keys(extensions).length > 0 ? { extensions } : {}
3067
3169
  };
3068
3170
  return { challenge: challenge2, requiredHeader: buildChallengeHeader(challenge2) };
3069
3171
  }
@@ -3249,61 +3351,6 @@ function normaliseHeader(value) {
3249
3351
  if (Array.isArray(value)) return value[0];
3250
3352
  return value;
3251
3353
  }
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
3354
  export {
3308
3355
  CHAINS,
3309
3356
  ConfirmationTimeoutError,
@@ -3332,6 +3379,7 @@ export {
3332
3379
  UnsupportedNetworkError,
3333
3380
  WrongChainError,
3334
3381
  WrongFamilyError,
3382
+ buildBazaarExtension,
3335
3383
  buildChallengeHeader,
3336
3384
  buildExactAuthorization,
3337
3385
  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.0",
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",