@piprail/sdk 2.15.1 → 2.16.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.
@@ -0,0 +1,649 @@
1
+ // src/indexes.ts
2
+ var DIRECTORY_INFO = Object.freeze({
3
+ "402index": {
4
+ source: "402index",
5
+ review: "probe-sync",
6
+ auth: "none",
7
+ chains: null,
8
+ onSuccess: "pending-review",
9
+ readByDiscover: true,
10
+ caveat: "402 Index probes your URL on submit (rejecting anything that does not return a real 402), then lists it \u2014 a self-registered resource becomes searchable once it passes automated health + payment-validity checks, with NO domain verification required (observed live: searchable within ~2 days for a healthy endpoint). Verify your domain on 402index.io for instant, guaranteed approval + a verified badge, which also flips every pending listing on that domain live at once."
11
+ },
12
+ x402scan: {
13
+ source: "x402scan",
14
+ review: "probe-sync",
15
+ auth: "siwx",
16
+ chains: ["eip155:8453", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"],
17
+ onSuccess: "live",
18
+ readByDiscover: false,
19
+ caveat: "x402scan lists Base/Solana only, needs one wallet signature (SIWX), and requires a resolvable input schema (from /openapi.json or the bazaar extension in the 402 body). It goes live on x402scan.com immediately on success \u2014 but discover() does NOT read x402scan, so the listing won't appear in discover() results."
20
+ },
21
+ bazaar: {
22
+ source: "bazaar",
23
+ review: "settle-coupled",
24
+ auth: "facilitator-only",
25
+ chains: null,
26
+ onSuccess: "not-listable",
27
+ readByDiscover: true,
28
+ caveat: "CDP Bazaar has no register endpoint \u2014 it catalogs a resource only when its own facilitator settles a payment. PipRail verifies locally with no facilitator, so a PipRail resource cannot be listed here (you can still READ Bazaar to find others). List on 402 Index or x402scan instead."
29
+ }
30
+ });
31
+ function getDirectoryInfo(source) {
32
+ return DIRECTORY_INFO[source];
33
+ }
34
+ function decorateOutcome(o) {
35
+ const info = DIRECTORY_INFO[o.source];
36
+ return { ...o, visibility: o.visibility ?? (o.ok ? info.onSuccess : "not-listable"), note: info.caveat };
37
+ }
38
+ var BAZAAR_URL = "https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources";
39
+ var INDEX402_SEARCH = "https://402index.io/api/v1/services";
40
+ var INDEX402_REGISTER = "https://402index.io/api/v1/register";
41
+ var INDEX402_CLAIM = "https://402index.io/api/v1/claim";
42
+ var INDEX402_VERIFY = "https://402index.io/api/v1/claim/verify";
43
+ var X402SCAN_REGISTER = "https://www.x402scan.com/api/x402/registry/register";
44
+ var USER_AGENT = "@piprail/sdk (+https://piprail.com)";
45
+ function clientHeaders(extra = {}) {
46
+ return { "user-agent": USER_AGENT, ...extra };
47
+ }
48
+ var SLUG_TO_CAIP2 = {
49
+ // EVM — every chain we ship a preset for, because an x402 **v1** server names the network by
50
+ // slug and a slug we can't resolve never matches `net.supports()` (the EVM driver compares
51
+ // chain ids, so an unresolved 'celo' silently fails to match a Celo client). Chains we don't
52
+ // preset still fall through to net.supports unchanged.
53
+ ethereum: "eip155:1",
54
+ base: "eip155:8453",
55
+ polygon: "eip155:137",
56
+ arbitrum: "eip155:42161",
57
+ optimism: "eip155:10",
58
+ avalanche: "eip155:43114",
59
+ bnb: "eip155:56",
60
+ bsc: "eip155:56",
61
+ mantle: "eip155:5000",
62
+ sonic: "eip155:146",
63
+ linea: "eip155:59144",
64
+ scroll: "eip155:534352",
65
+ celo: "eip155:42220",
66
+ zksync: "eip155:324",
67
+ unichain: "eip155:130",
68
+ worldchain: "eip155:480",
69
+ world: "eip155:480",
70
+ sei: "eip155:1329",
71
+ injective: "eip155:1776",
72
+ hyperevm: "eip155:999",
73
+ monad: "eip155:143",
74
+ kaia: "eip155:8217",
75
+ // non-EVM families — values mirror each driver's bound caip2 exactly
76
+ solana: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
77
+ ton: "tvm:-239",
78
+ tron: "tron:mainnet",
79
+ near: "near:mainnet",
80
+ sui: "sui:mainnet",
81
+ aptos: "aptos:1",
82
+ algorand: "algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=",
83
+ stellar: "stellar:pubnet",
84
+ xrpl: "xrpl:0"
85
+ };
86
+ var ALGORAND_SPEC_CAIP2 = "algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73k";
87
+ var LEGACY_CAIP2_ALIAS = {
88
+ "ton:-239": "tvm:-239",
89
+ [ALGORAND_SPEC_CAIP2]: "algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="
90
+ };
91
+ function normalizeNetwork(network) {
92
+ const legacy = LEGACY_CAIP2_ALIAS[network];
93
+ if (legacy) return legacy;
94
+ if (network.includes(":")) return network;
95
+ return SLUG_TO_CAIP2[network.toLowerCase()] ?? network;
96
+ }
97
+ async function searchOpenIndexes(opts = {}) {
98
+ const sources = opts.sources ?? ["bazaar", "402index"];
99
+ const limit = opts.limit ?? 20;
100
+ const filters = {
101
+ ...optionalRaw("category", opts.category),
102
+ ...optionalRaw("asset", opts.asset),
103
+ ...optionalRaw("maxPrice", opts.maxPrice),
104
+ ...optionalRaw("verified", opts.verified),
105
+ ...optionalRaw("paymentValid", opts.paymentValid),
106
+ ...optionalRaw("sort", opts.sort),
107
+ ...optionalRaw("order", opts.order)
108
+ };
109
+ const results = await Promise.all(
110
+ sources.map((source) => {
111
+ if (source === "bazaar") return safeSearch(() => searchBazaar(opts.query, limit, opts.signal));
112
+ if (source === "402index") return safeSearch(() => search402Index(opts.query, limit, filters, opts.signal));
113
+ return Promise.resolve([]);
114
+ })
115
+ );
116
+ const merged = applyClientFilters(dedupeByResource(results.flat()), opts);
117
+ const wantRelevance = opts.query !== void 0 && (opts.sort ?? "relevance") === "relevance";
118
+ if (wantRelevance) return rankResources(merged, opts.query);
119
+ if (opts.sort && opts.sort !== "relevance") return sortResources(merged, opts.sort, opts.order ?? "desc");
120
+ return merged;
121
+ }
122
+ function optionalRaw(field, value) {
123
+ return value !== void 0 ? { [field]: value } : {};
124
+ }
125
+ function applyClientFilters(items, opts) {
126
+ let out = items;
127
+ if (opts.category) {
128
+ const want = opts.category.toLowerCase();
129
+ out = out.filter((r) => r.category !== void 0 && r.category.toLowerCase().startsWith(want));
130
+ }
131
+ if (opts.maxPrice !== void 0) {
132
+ const max = opts.maxPrice;
133
+ out = out.filter((r) => r.priceUsd === void 0 || r.priceUsd <= max);
134
+ }
135
+ if (opts.asset) {
136
+ const want = opts.asset.toLowerCase();
137
+ out = out.filter((r) => {
138
+ const known = r.rails.map((x) => x.symbol ?? x.asset).filter((a) => !!a);
139
+ return known.length === 0 || known.some((a) => a.toLowerCase() === want);
140
+ });
141
+ }
142
+ if (opts.minReliability !== void 0) {
143
+ const min = opts.minReliability;
144
+ out = out.filter((r) => r.reliabilityScore === void 0 || r.reliabilityScore >= min);
145
+ }
146
+ return out;
147
+ }
148
+ function tokenize(s) {
149
+ return (s ?? "").toLowerCase().match(/[a-z0-9]+/g) ?? [];
150
+ }
151
+ var FIELD_WEIGHTS = { name: 6, category: 4, tags: 4, path: 3, description: 2 };
152
+ function fieldTokens(r) {
153
+ let pathText = r.resource;
154
+ try {
155
+ const u = new URL(r.resource);
156
+ pathText = `${u.hostname} ${u.pathname}`;
157
+ } catch {
158
+ }
159
+ return [
160
+ [tokenize(r.name), FIELD_WEIGHTS.name],
161
+ [tokenize(r.category), FIELD_WEIGHTS.category],
162
+ [tokenize((r.tags ?? []).join(" ")), FIELD_WEIGHTS.tags],
163
+ [tokenize(pathText), FIELD_WEIGHTS.path],
164
+ [tokenize(r.description), FIELD_WEIGHTS.description]
165
+ ];
166
+ }
167
+ function scoreResource(r, queryTokens) {
168
+ if (queryTokens.length === 0) return 0;
169
+ const fields = fieldTokens(r);
170
+ let score = 0;
171
+ let matched = 0;
172
+ for (const qt of queryTokens) {
173
+ let hit = false;
174
+ for (const [toks, w] of fields) {
175
+ if (toks.includes(qt)) {
176
+ score += w;
177
+ hit = true;
178
+ } else if (qt.length >= 4 && toks.some((t) => t.startsWith(qt) || t.length >= 4 && qt.startsWith(t))) {
179
+ score += w * 0.4;
180
+ hit = true;
181
+ }
182
+ }
183
+ if (hit) matched++;
184
+ }
185
+ if (matched === 0) return 0;
186
+ if (matched === queryTokens.length) score += 8;
187
+ if (r.reliabilityScore !== void 0) score += r.reliabilityScore / 1e3;
188
+ return score;
189
+ }
190
+ function rankResources(items, query) {
191
+ const qTokens = tokenize(query);
192
+ if (qTokens.length === 0) return items;
193
+ return items.map((r, i) => ({ r, i, s: scoreResource(r, qTokens) })).filter((x) => x.s > 0).sort((a, b) => b.s - a.s || a.i - b.i).map((x) => ({ ...x.r, score: x.s }));
194
+ }
195
+ function sortResources(items, sort, order) {
196
+ const dir = order === "asc" ? 1 : -1;
197
+ const key = (r) => sort === "reliability" || sort === "uptime" ? r.reliabilityScore : sort === "price" ? r.priceUsd : sort === "name" ? (r.name ?? r.resource).toLowerCase() : void 0;
198
+ return items.map((r, i) => ({ r, i })).sort((a, b) => {
199
+ const ka = key(a.r);
200
+ const kb = key(b.r);
201
+ if (ka === void 0 && kb === void 0) return a.i - b.i;
202
+ if (ka === void 0) return 1;
203
+ if (kb === void 0) return -1;
204
+ if (ka < kb) return -1 * dir;
205
+ if (ka > kb) return 1 * dir;
206
+ return a.i - b.i;
207
+ }).map((x) => x.r);
208
+ }
209
+ async function safeSearch(run) {
210
+ try {
211
+ return await run();
212
+ } catch {
213
+ return [];
214
+ }
215
+ }
216
+ function dedupeByResource(items) {
217
+ const seen = /* @__PURE__ */ new Set();
218
+ const out = [];
219
+ for (const it of items) {
220
+ const key = it.resource;
221
+ if (!key || seen.has(key)) continue;
222
+ seen.add(key);
223
+ out.push(it);
224
+ }
225
+ return out;
226
+ }
227
+ async function searchBazaar(query, limit, signal) {
228
+ const res = await fetch(`${BAZAAR_URL}?limit=${encodeURIComponent(String(limit))}`, {
229
+ headers: clientHeaders({ accept: "application/json" }),
230
+ ...signal ? { signal } : {}
231
+ });
232
+ if (!res.ok) return [];
233
+ const body = await res.json();
234
+ const items = Array.isArray(body.items) ? body.items : [];
235
+ const mapped = items.map(mapBazaarItem).filter((r) => r !== null);
236
+ return query ? mapped.filter((r) => matchesQuery(r, query)) : mapped;
237
+ }
238
+ function mapBazaarItem(raw) {
239
+ if (!raw || typeof raw !== "object") return null;
240
+ const o = raw;
241
+ const resource = pickString(o, "resource", "url", "endpoint");
242
+ if (!resource) return null;
243
+ const meta = o.metadata && typeof o.metadata === "object" ? o.metadata : {};
244
+ return {
245
+ resource,
246
+ source: "bazaar",
247
+ rails: mapRails(o.accepts),
248
+ ...optionalString("name", pickString(meta, "name", "title")),
249
+ ...optionalString("description", pickString(meta, "description") ?? pickString(o, "description")),
250
+ ...optionalString("category", pickString(meta, "category"))
251
+ };
252
+ }
253
+ async function search402Index(query, limit, filters, signal) {
254
+ const tokens = tokenize(query);
255
+ const queries = query && tokens.length > 1 ? [.../* @__PURE__ */ new Set([query, ...tokens])].slice(0, 5) : [query];
256
+ const pages = await Promise.all(queries.map((q) => safeSearch(() => fetch402Page(q, limit, filters, signal))));
257
+ return dedupeByResource(pages.flat());
258
+ }
259
+ async function fetch402Page(query, limit, filters, signal) {
260
+ const qs = new URLSearchParams({ limit: String(limit) });
261
+ if (query) qs.set("q", query);
262
+ if (filters.category) qs.set("category", filters.category);
263
+ if (filters.asset) qs.set("payment_asset", filters.asset);
264
+ if (filters.maxPrice !== void 0) qs.set("max_price_usd", String(filters.maxPrice));
265
+ if (filters.verified) qs.set("verified", "true");
266
+ if (filters.paymentValid) qs.set("payment_valid", "true");
267
+ if (filters.sort && filters.sort !== "relevance") {
268
+ qs.set("sort", filters.sort);
269
+ qs.set("order", filters.order ?? "desc");
270
+ }
271
+ const res = await fetch(`${INDEX402_SEARCH}?${qs.toString()}`, {
272
+ headers: clientHeaders({ accept: "application/json" }),
273
+ ...signal ? { signal } : {}
274
+ });
275
+ if (!res.ok) return [];
276
+ const body = await res.json();
277
+ const list = firstArray(body, "services", "results", "items", "data");
278
+ return list.map(map402IndexItem).filter((r) => r !== null).filter((r) => r.rails.length > 0);
279
+ }
280
+ function map402IndexItem(raw) {
281
+ if (!raw || typeof raw !== "object") return null;
282
+ const o = raw;
283
+ const resource = pickString(o, "url", "resource", "endpoint");
284
+ if (!resource) return null;
285
+ const protocol = (pickString(o, "protocol") ?? "x402").toLowerCase();
286
+ if (protocol !== "x402") return null;
287
+ const rails = Array.isArray(o.accepts) ? mapRails(o.accepts) : railFrom402IndexFields(o);
288
+ const priceUsd = pickNumber(o, "price_usd", "priceUsd", "price");
289
+ const reliabilityScore = pickNumber(o, "reliability_score", "reliabilityScore");
290
+ const tags = pickStringArray(o, "tags", "keywords");
291
+ return {
292
+ resource,
293
+ source: "402index",
294
+ rails,
295
+ ...priceUsd !== void 0 ? { priceUsd } : {},
296
+ ...reliabilityScore !== void 0 ? { reliabilityScore } : {},
297
+ ...tags ? { tags } : {},
298
+ ...optionalString("name", pickString(o, "name", "title")),
299
+ ...optionalString("description", pickString(o, "description")),
300
+ ...optionalString("category", pickString(o, "category", "tag")),
301
+ ...optionalString("health", pickString(o, "health_status", "health")),
302
+ // 402 Index reports domain_verified as 0/1; surface a boolean only when the field is present.
303
+ ...o.domain_verified !== void 0 || o.verified !== void 0 ? { verified: isTruthyFlag(o.domain_verified) || isTruthyFlag(o.verified) } : {}
304
+ };
305
+ }
306
+ function isTruthyFlag(v) {
307
+ return v === 1 || v === true || v === "1" || v === "true";
308
+ }
309
+ function railFrom402IndexFields(o) {
310
+ const network = pickString(o, "payment_network", "network");
311
+ const asset = pickString(o, "payment_asset", "asset", "token");
312
+ if (!network && !asset) return [];
313
+ return [
314
+ {
315
+ scheme: "exact",
316
+ network: network ?? "unknown",
317
+ ...asset ? { asset } : {},
318
+ ...optionalString("symbol", asset)
319
+ }
320
+ ];
321
+ }
322
+ async function register402Index(input) {
323
+ try {
324
+ const attributionOn = input.attribution !== false;
325
+ const withTags = appendKeywords(input.description, input.tags);
326
+ const description = attributionOn ? appendAttribution(withTags) : withTags;
327
+ const payload = {
328
+ url: input.url,
329
+ name: input.name ?? hostOf(input.url),
330
+ protocol: "x402",
331
+ ...description ? { description } : {},
332
+ ...typeof input.priceUsd === "number" ? { price_usd: input.priceUsd } : {},
333
+ ...input.asset ? { payment_asset: input.asset } : {},
334
+ ...input.network ? { payment_network: input.network } : {},
335
+ ...input.method ? { http_method: input.method.toUpperCase() } : {},
336
+ ...input.category ? { category: input.category } : {},
337
+ ...input.tags && input.tags.length > 0 ? { tags: input.tags } : {},
338
+ ...input.provider ? { provider: input.provider } : {},
339
+ ...input.contactEmail ? { contact_email: input.contactEmail } : {},
340
+ ...input.probeBody !== void 0 ? { probe_body: input.probeBody } : {},
341
+ ...attributionOn ? { via: "@piprail/sdk" } : {}
342
+ };
343
+ const res = await fetch(INDEX402_REGISTER, {
344
+ method: "POST",
345
+ headers: clientHeaders({ "content-type": "application/json", accept: "application/json" }),
346
+ body: JSON.stringify(payload)
347
+ });
348
+ if (res.ok) {
349
+ const body = await res.json().catch(() => ({}));
350
+ const msg = typeof body.message === "string" && body.message.length > 0 ? body.message : void 0;
351
+ const live = body.service?.status === "active";
352
+ return {
353
+ source: "402index",
354
+ ok: true,
355
+ status: res.status,
356
+ ...live ? { visibility: "live" } : {},
357
+ detail: msg ?? (live ? "Registered + live on 402 Index (domain verified)." : "Registered on 402 Index \u2014 probed on submit, then searchable once it passes automated health + payment checks (verify your domain on 402index.io for instant approval + a verified badge).")
358
+ };
359
+ }
360
+ const why = await readIndexError(res);
361
+ return {
362
+ source: "402index",
363
+ ok: false,
364
+ status: res.status,
365
+ detail: why ? `402 Index rejected it (HTTP ${res.status}): ${why}` : `402 Index returned HTTP ${res.status}.`
366
+ };
367
+ } catch (err) {
368
+ return { source: "402index", ok: false, detail: errMsg(err) };
369
+ }
370
+ }
371
+ async function readIndexError(res) {
372
+ try {
373
+ const body = await res.json();
374
+ const parts = [body.error, body.detail, body.message].filter(
375
+ (p) => typeof p === "string" && p.length > 0
376
+ );
377
+ return parts.length ? [...new Set(parts)].join(" \u2014 ") : void 0;
378
+ } catch {
379
+ return void 0;
380
+ }
381
+ }
382
+ async function registerX402Scan(input, signer) {
383
+ try {
384
+ const challengeRes = await fetch(X402SCAN_REGISTER, {
385
+ method: "POST",
386
+ headers: clientHeaders({ "content-type": "application/json", accept: "application/json" }),
387
+ body: JSON.stringify({ url: input.url })
388
+ });
389
+ if (challengeRes.status !== 402) {
390
+ return {
391
+ source: "x402scan",
392
+ ok: challengeRes.ok,
393
+ status: challengeRes.status,
394
+ detail: challengeRes.ok ? "Listed on x402scan." : `x402scan returned HTTP ${challengeRes.status} (expected a SIWX 402 challenge).`
395
+ };
396
+ }
397
+ const info = await readSiwxInfo(challengeRes);
398
+ if (!info) {
399
+ return { source: "x402scan", ok: false, status: 402, detail: "x402scan SIWX challenge was unparseable." };
400
+ }
401
+ const resolvedInfo = { ...info, issuedAt: info.issuedAt ?? (/* @__PURE__ */ new Date()).toISOString() };
402
+ const message = formatSiweMessage(resolvedInfo, signer.address);
403
+ const signature = await signer.signMessage(message);
404
+ const header = encodeBase64(
405
+ JSON.stringify({ ...resolvedInfo, address: signer.address, type: "eip191", message, signature })
406
+ );
407
+ const res = await fetch(X402SCAN_REGISTER, {
408
+ method: "POST",
409
+ headers: clientHeaders({
410
+ "content-type": "application/json",
411
+ accept: "application/json",
412
+ "sign-in-with-x": header
413
+ }),
414
+ body: JSON.stringify({ url: input.url })
415
+ });
416
+ if (res.ok) {
417
+ return { source: "x402scan", ok: true, status: res.status, detail: "Listed on x402scan (SIWX)." };
418
+ }
419
+ const why = await readIndexError(res);
420
+ return {
421
+ source: "x402scan",
422
+ ok: false,
423
+ status: res.status,
424
+ detail: why ? `x402scan rejected it (HTTP ${res.status}): ${why}` : `x402scan returned HTTP ${res.status} after signing.`
425
+ };
426
+ } catch (err) {
427
+ return { source: "x402scan", ok: false, detail: errMsg(err) };
428
+ }
429
+ }
430
+ async function claim402IndexDomain(domainOrUrl, opts = {}) {
431
+ const domain = hostOf(domainOrUrl);
432
+ try {
433
+ const res = await fetch(INDEX402_CLAIM, {
434
+ method: "POST",
435
+ headers: clientHeaders({ "content-type": "application/json", accept: "application/json" }),
436
+ body: JSON.stringify({ domain, ...opts.contactEmail ? { contact_email: opts.contactEmail } : {} })
437
+ });
438
+ const body = await res.json().catch(() => ({}));
439
+ if (!res.ok) {
440
+ return { ok: false, domain, httpStatus: res.status, detail: pickString(body, "error", "detail", "message") ?? `402 Index claim returned HTTP ${res.status}.` };
441
+ }
442
+ const verificationToken = pickString(body, "verification_token");
443
+ const verificationHash = pickString(body, "verification_hash") ?? (verificationToken ? await sha256Hex(verificationToken) : void 0);
444
+ return {
445
+ ok: true,
446
+ domain,
447
+ httpStatus: res.status,
448
+ ...optionalString("verificationHash", verificationHash),
449
+ ...optionalString("verificationToken", verificationToken),
450
+ ...optionalString("verificationUrl", pickString(body, "verification_url")),
451
+ ...optionalString("instructions", pickString(body, "instructions"))
452
+ };
453
+ } catch (err) {
454
+ return { ok: false, domain, detail: errMsg(err) };
455
+ }
456
+ }
457
+ async function sha256Hex(input) {
458
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
459
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
460
+ }
461
+ async function verify402IndexDomain(domainOrUrl) {
462
+ const domain = hostOf(domainOrUrl);
463
+ try {
464
+ const res = await fetch(INDEX402_VERIFY, {
465
+ method: "POST",
466
+ headers: clientHeaders({ "content-type": "application/json", accept: "application/json" }),
467
+ body: JSON.stringify({ domain })
468
+ });
469
+ const body = await res.json().catch(() => ({}));
470
+ if (!res.ok) {
471
+ return { ok: false, domain, httpStatus: res.status, detail: pickString(body, "error", "detail", "message") ?? `402 Index verify returned HTTP ${res.status}.` };
472
+ }
473
+ return {
474
+ ok: true,
475
+ domain,
476
+ httpStatus: res.status,
477
+ ...optionalString("status", pickString(body, "status")),
478
+ ...typeof body.services_count === "number" ? { servicesCount: body.services_count } : {}
479
+ };
480
+ } catch (err) {
481
+ return { ok: false, domain, detail: errMsg(err) };
482
+ }
483
+ }
484
+ async function readSiwxInfo(res) {
485
+ try {
486
+ const body = await res.json();
487
+ const ext = body.extensions;
488
+ const siwx = ext?.["sign-in-with-x"];
489
+ const info = siwx?.info ?? siwx;
490
+ if (info && info.chainId == null && Array.isArray(siwx?.supportedChains)) {
491
+ const evm = siwx.supportedChains.find(
492
+ (c) => typeof c?.chainId === "string" && c.chainId.startsWith("eip155:")
493
+ );
494
+ if (evm && typeof evm.chainId === "string") info.chainId = evm.chainId;
495
+ }
496
+ 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) {
497
+ return info;
498
+ }
499
+ return null;
500
+ } catch {
501
+ return null;
502
+ }
503
+ }
504
+ function formatSiweMessage(info, address) {
505
+ const chainId = info.chainId ? caip2ToChainId(info.chainId) : 1;
506
+ const statement = info.statement && info.statement.trim() ? info.statement : void 0;
507
+ const lines = [
508
+ `${info.domain} wants you to sign in with your Ethereum account:`,
509
+ address,
510
+ "",
511
+ ...statement ? [statement, ""] : [""],
512
+ `URI: ${info.uri}`,
513
+ "Version: 1",
514
+ `Chain ID: ${chainId}`,
515
+ `Nonce: ${info.nonce}`,
516
+ `Issued At: ${info.issuedAt}`,
517
+ ...info.expirationTime ? [`Expiration Time: ${info.expirationTime}`] : []
518
+ ];
519
+ return lines.join("\n");
520
+ }
521
+ function caip2ToChainId(caip2) {
522
+ const m = /^eip155:(\d+)$/.exec(caip2);
523
+ const n = m ? Number(m[1]) : Number(caip2);
524
+ return Number.isSafeInteger(n) && n > 0 ? n : 1;
525
+ }
526
+ function mapRails(accepts) {
527
+ if (!Array.isArray(accepts)) return [];
528
+ const out = [];
529
+ for (const raw of accepts) {
530
+ if (!raw || typeof raw !== "object") continue;
531
+ const a = raw;
532
+ const network = pickString(a, "network");
533
+ if (!network) continue;
534
+ const extra = a.extra && typeof a.extra === "object" ? a.extra : {};
535
+ out.push({
536
+ scheme: pickString(a, "scheme") ?? "exact",
537
+ network,
538
+ ...optionalString("asset", pickString(a, "asset")),
539
+ ...optionalString("amount", pickString(a, "amount", "maxAmountRequired")),
540
+ ...optionalString("payTo", pickString(a, "payTo")),
541
+ ...optionalString("symbol", pickString(extra, "symbol"))
542
+ });
543
+ }
544
+ return out;
545
+ }
546
+ function matchesQuery(r, query) {
547
+ const haystack = [r.name, r.description, r.category, (r.tags ?? []).join(" "), r.resource].filter(Boolean).join(" ").toLowerCase();
548
+ if (haystack.includes(query.toLowerCase())) return true;
549
+ const qTokens = tokenize(query);
550
+ const hayTokens = new Set(tokenize(haystack));
551
+ return qTokens.some((t) => hayTokens.has(t));
552
+ }
553
+ function pickStringArray(o, ...keys) {
554
+ for (const k of keys) {
555
+ const v = o[k];
556
+ if (Array.isArray(v)) {
557
+ const arr = v.filter((x) => typeof x === "string" && x.length > 0);
558
+ if (arr.length) return arr;
559
+ } else if (typeof v === "string" && v.trim()) {
560
+ const arr = v.split(/[,\s]+/).filter((x) => x.length > 0);
561
+ if (arr.length) return arr;
562
+ }
563
+ }
564
+ return void 0;
565
+ }
566
+ function pickString(o, ...keys) {
567
+ for (const k of keys) {
568
+ const v = o[k];
569
+ if (typeof v === "string" && v.length > 0) return v;
570
+ }
571
+ return void 0;
572
+ }
573
+ function pickNumber(o, ...keys) {
574
+ for (const k of keys) {
575
+ const v = o[k];
576
+ if (typeof v === "number" && Number.isFinite(v)) return v;
577
+ if (typeof v === "string" && /^\d+(\.\d+)?$/.test(v.trim())) {
578
+ const n = Number(v.trim());
579
+ if (Number.isFinite(n)) return n;
580
+ }
581
+ }
582
+ return void 0;
583
+ }
584
+ function optionalString(field, value) {
585
+ return value !== void 0 ? { [field]: value } : {};
586
+ }
587
+ function firstArray(o, ...keys) {
588
+ for (const k of keys) {
589
+ if (Array.isArray(o[k])) return o[k];
590
+ }
591
+ return Array.isArray(o) ? o : [];
592
+ }
593
+ function hostOf(url) {
594
+ try {
595
+ const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(url) ? url : `https://${url}`;
596
+ return new URL(withScheme).hostname || url;
597
+ } catch {
598
+ return url;
599
+ }
600
+ }
601
+ function errMsg(err) {
602
+ return err instanceof Error ? err.message : String(err);
603
+ }
604
+ var REGISTER_ATTRIBUTION = "\xB7 Built with @piprail/sdk";
605
+ function appendAttribution(description) {
606
+ if (!description) return description;
607
+ if (/piprail/i.test(description)) return description;
608
+ const next = `${description.trimEnd()} ${REGISTER_ATTRIBUTION}`;
609
+ return next.length <= 500 ? next : description;
610
+ }
611
+ function appendKeywords(description, tags) {
612
+ const clean = (tags ?? []).map((t) => t.trim()).filter((t) => t.length > 0);
613
+ if (clean.length === 0) return description;
614
+ const have = (description ?? "").toLowerCase();
615
+ const fresh = [...new Set(clean.filter((t) => !have.includes(t.toLowerCase())))];
616
+ if (fresh.length === 0) return description;
617
+ const tail = `Keywords: ${fresh.join(", ")}`;
618
+ if (!description) return tail;
619
+ const next = `${description.trimEnd()} \xB7 ${tail}`;
620
+ return next.length <= 500 ? next : description;
621
+ }
622
+ function encodeBase64(str) {
623
+ if (typeof Buffer !== "undefined") return Buffer.from(str, "utf8").toString("base64");
624
+ if (typeof btoa === "function" && typeof TextEncoder !== "undefined") {
625
+ const bytes = new TextEncoder().encode(str);
626
+ let binary = "";
627
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
628
+ return btoa(binary);
629
+ }
630
+ throw new Error("No base64 encoder available in this runtime.");
631
+ }
632
+
633
+ export {
634
+ DIRECTORY_INFO,
635
+ getDirectoryInfo,
636
+ decorateOutcome,
637
+ ALGORAND_SPEC_CAIP2,
638
+ normalizeNetwork,
639
+ searchOpenIndexes,
640
+ scoreResource,
641
+ rankResources,
642
+ register402Index,
643
+ registerX402Scan,
644
+ claim402IndexDomain,
645
+ verify402IndexDomain,
646
+ REGISTER_ATTRIBUTION,
647
+ appendAttribution,
648
+ appendKeywords
649
+ };