@inversealtruism/cairn-sdk 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs CHANGED
@@ -26,7 +26,7 @@ __export(react_exports, {
26
26
  module.exports = __toCommonJS(react_exports);
27
27
 
28
28
  // src/errors.ts
29
- var SDK_VERSION = "0.3.2";
29
+ var SDK_VERSION = "0.4.1";
30
30
  var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
31
31
  var CairnError = class extends Error {
32
32
  /** Stable machine code for programmatic branching. */
@@ -100,7 +100,7 @@ var SubmitInFlightError = class extends CairnError {
100
100
  this.txid = txid;
101
101
  }
102
102
  };
103
- var NATIVE_CODES = /* @__PURE__ */ new Set(["ACCOUNT_CHANGED", "FIRST_PARTY_ONLY", "RATE_LIMITED", "APPROVAL_CLOSED", "FORBIDDEN", "INTERNAL"]);
103
+ var NATIVE_CODES = /* @__PURE__ */ new Set(["FIRST_PARTY_ONLY", "RATE_LIMITED", "FORBIDDEN", "INTERNAL"]);
104
104
  var NESTED_TRANSIENT = /* @__PURE__ */ new Set(["VERIFY_UNAVAILABLE", "OFFER_UNKNOWN"]);
105
105
  var NESTED_TERMINAL = /* @__PURE__ */ new Set([
106
106
  "FILL_UNSAFE",
@@ -144,6 +144,7 @@ function mapSubmitResultError(result) {
144
144
  }
145
145
 
146
146
  // src/connect.ts
147
+ var DEFAULT_PROVIDER_TIMEOUT_MS = 10 * 6e4;
147
148
  function detectProvider(opts = {}) {
148
149
  const timeoutMs = opts.timeoutMs ?? 3e3;
149
150
  return new Promise((resolve, reject) => {
@@ -200,8 +201,27 @@ function unwrapWrite(reply) {
200
201
  var WalletConnection = class {
201
202
  provider;
202
203
  _addr = null;
203
- constructor(provider) {
204
+ /** B7c: per-call provider timeout (ms). GENEROUS by default so it never rejects an in-flight human
205
+ * approval; 0 disables it. Every provider call (writes especially) races against it. */
206
+ _timeoutMs;
207
+ constructor(provider, opts = {}) {
204
208
  this.provider = provider;
209
+ this._timeoutMs = opts.timeoutMs ?? DEFAULT_PROVIDER_TIMEOUT_MS;
210
+ }
211
+ // B7c: race a provider call against the per-call timeout. On timeout, throw a fail-closed CairnError
212
+ // (retryable:false: a write may already have reached the wallet, so never invite a blind auto-retry). The
213
+ // genuine wallet resolves quickly once the user acts; this only fires on a truly hung/absent response.
214
+ call(op, label) {
215
+ const ms = this._timeoutMs;
216
+ const invoke = () => new Promise((resolve) => {
217
+ resolve(op());
218
+ });
219
+ if (!Number.isFinite(ms) || ms <= 0) return invoke();
220
+ let timer;
221
+ const timeout = new Promise((_, reject) => {
222
+ timer = setTimeout(() => reject(new CairnError(`the wallet did not respond to ${label} within ${ms}ms`, { retryable: false })), ms);
223
+ });
224
+ return Promise.race([invoke(), timeout]).finally(() => clearTimeout(timer));
205
225
  }
206
226
  /** The wallet's version string (e.g. "0.2.x"). */
207
227
  get version() {
@@ -213,13 +233,13 @@ var WalletConnection = class {
213
233
  }
214
234
  /** Request connection. First time per origin prompts the user; afterwards may be silent. */
215
235
  async connect() {
216
- const { addr } = unwrap(await this.provider.connect());
236
+ const { addr } = unwrap(await this.call(() => this.provider.connect(), "connect"));
217
237
  this._addr = addr;
218
238
  return addr;
219
239
  }
220
240
  /** Get the connected address (does not force a new connection prompt if already consented). */
221
241
  async getAddress() {
222
- const { addr } = unwrap(await this.provider.getAddress());
242
+ const { addr } = unwrap(await this.call(() => this.provider.getAddress(), "getAddress"));
223
243
  this._addr = addr;
224
244
  return addr;
225
245
  }
@@ -232,7 +252,7 @@ var WalletConnection = class {
232
252
  if (typeof this.provider.signIn !== "function") {
233
253
  return Promise.reject(new UnsupportedMethodError("this wallet does not expose signIn()"));
234
254
  }
235
- return Promise.resolve(this.provider.signIn()).then(unwrap);
255
+ return this.call(() => this.provider.signIn(), "signIn").then(unwrap);
236
256
  }
237
257
  /**
238
258
  * Audience-bound "Sign in with CSD" (SIWC) — the secure, replay-resistant third-party login.
@@ -248,7 +268,7 @@ var WalletConnection = class {
248
268
  if (typeof this.provider.signInWithCsd !== "function") {
249
269
  return Promise.reject(new UnsupportedMethodError("this wallet predates Sign-in-with-CSD \u2014 ask the user to update the Cairn Wallet"));
250
270
  }
251
- return Promise.resolve(this.provider.signInWithCsd(params)).then(unwrap);
271
+ return this.call(() => this.provider.signInWithCsd(params), "signInWithCsd").then(unwrap);
252
272
  }
253
273
  /** True if this wallet supports audience-bound SIWC (safe to call `signInWithCsd`). */
254
274
  get supportsSiwc() {
@@ -258,7 +278,7 @@ var WalletConnection = class {
258
278
  async getCapabilities() {
259
279
  if (typeof this.provider.getCapabilities !== "function") return null;
260
280
  try {
261
- return await this.provider.getCapabilities();
281
+ return await this.call(() => this.provider.getCapabilities(), "getCapabilities");
262
282
  } catch {
263
283
  return null;
264
284
  }
@@ -274,24 +294,40 @@ var WalletConnection = class {
274
294
  if (typeof this.provider.fillOffer !== "function") {
275
295
  return Promise.reject(new UnsupportedMethodError("this wallet predates fillOffer() \u2014 ask the user to update the Cairn Wallet"));
276
296
  }
277
- return Promise.resolve(this.provider.fillOffer(params)).then(unwrapWrite);
297
+ this.adviseFillSpvCapability();
298
+ return this.call(() => this.provider.fillOffer(params), "fillOffer").then(unwrapWrite);
299
+ }
300
+ // P75-5 MF-10: ONE capability advisory per connection. The wallet's on-device fill-SPV (0.2.60+) is the
301
+ // payment-grade fill boundary; when the provider SELF-REPORTS an older version, warn the integrator once
302
+ // and STILL RELAY. WARN, NEVER BLOCK: a hard version gate would decline honest fills (version strings are
303
+ // spoofable and unparseable forks exist), and the wallet itself is the enforcement point. Reads only the
304
+ // sync `provider.version`; never an awaited getCapabilities() round-trip on the fill hot path. An
305
+ // unparseable version never warns (no false alarms).
306
+ _fillSpvAdvised = false;
307
+ adviseFillSpvCapability() {
308
+ if (this._fillSpvAdvised) return;
309
+ this._fillSpvAdvised = true;
310
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(String(this.provider?.version ?? ""));
311
+ if (!m) return;
312
+ const below = Number(m[1]) === 0 && (Number(m[2]) < 2 || Number(m[2]) === 2 && Number(m[3]) < 60);
313
+ if (below) console.warn(`[cairn-sdk] this Cairn Wallet (v${m[0]}) predates the on-device fill-SPV boundary (cairn-wallet 0.2.60+). The fill proceeds; suggest a wallet update for payment-grade offer verification.`);
278
314
  }
279
315
  /** This origin's current permission grant (EIP-2255-style). Silent; [] if none / unsupported. */
280
316
  async getPermissions() {
281
317
  if (typeof this.provider.getPermissions !== "function") return [];
282
- return unwrap(await this.provider.getPermissions());
318
+ return unwrap(await this.call(() => this.provider.getPermissions(), "getPermissions"));
283
319
  }
284
320
  /** Explicitly request permission to see the address (prompts, like connect). */
285
321
  requestPermissions() {
286
322
  if (typeof this.provider.requestPermissions !== "function") {
287
323
  return Promise.reject(new UnsupportedMethodError("this wallet predates requestPermissions \u2014 use connect()"));
288
324
  }
289
- return Promise.resolve(this.provider.requestPermissions()).then(unwrap);
325
+ return this.call(() => this.provider.requestPermissions(), "requestPermissions").then(unwrap);
290
326
  }
291
327
  /** Revoke THIS origin's own access (silent). Returns { revoked }. Safe no-op on older wallets. */
292
328
  async revokePermissions() {
293
329
  if (typeof this.provider.revokePermissions !== "function") return { revoked: false };
294
- return unwrap(await this.provider.revokePermissions());
330
+ return unwrap(await this.call(() => this.provider.revokePermissions(), "revokePermissions"));
295
331
  }
296
332
  /**
297
333
  * Subscribe to provider events. `accountsChanged` fires with `[]` when this site loses access
@@ -307,26 +343,26 @@ var WalletConnection = class {
307
343
  }
308
344
  /** Send CSD (always prompts with clear-signing; wallet picks inputs + returns change to itself). */
309
345
  send(params) {
310
- return Promise.resolve(this.provider.send(params)).then(unwrapWrite);
346
+ return this.call(() => this.provider.send(params), "send").then(unwrapWrite);
311
347
  }
312
348
  /** Low-level Propose (always prompts). Prefer `cairn.board.propose()` which computes the hash/uri/expiry. */
313
349
  propose(params) {
314
- return Promise.resolve(this.provider.propose(params)).then(unwrapWrite);
350
+ return this.call(() => this.provider.propose(params), "propose").then(unwrapWrite);
315
351
  }
316
352
  /** Attest/support a proposal (always prompts). */
317
353
  attest(params) {
318
- return Promise.resolve(this.provider.attest(params)).then(unwrapWrite);
354
+ return this.call(() => this.provider.attest(params), "attest").then(unwrapWrite);
319
355
  }
320
356
  /** Seal a commit-reveal claim (always prompts; salt stays inside the wallet). */
321
357
  sealClaim(params) {
322
- return Promise.resolve(this.provider.sealClaim(params)).then(unwrapWrite);
358
+ return this.call(() => this.provider.sealClaim(params), "sealClaim").then(unwrapWrite);
323
359
  }
324
360
  /** Reveal a previously sealed claim by its sealing txid (always prompts). */
325
361
  revealClaim(txid) {
326
362
  if (typeof txid !== "string" || !txid) {
327
363
  return Promise.reject(new UnsupportedMethodError("revealClaim requires the sealing txid"));
328
364
  }
329
- return Promise.resolve(this.provider.revealClaim(txid)).then(unwrapWrite);
365
+ return this.call(() => this.provider.revealClaim(txid), "revealClaim").then(unwrapWrite);
330
366
  }
331
367
  };
332
368
  async function getWallet(opts = {}) {
@@ -338,6 +374,9 @@ var DISCONNECTED = Object.freeze({ status: "disconnected", account: null, error:
338
374
  var CairnController = class {
339
375
  state = DISCONNECTED;
340
376
  conn = null;
377
+ // MF-15: connect/disconnect generation token. disconnect() bumps it; a connect() whose await straddled
378
+ // the bump must never commit "connected" over the torn-down session (a zombie null-connection session).
379
+ epoch = 0;
341
380
  listeners = /* @__PURE__ */ new Set();
342
381
  getWalletFn;
343
382
  detectOpts;
@@ -390,34 +429,43 @@ var CairnController = class {
390
429
  }
391
430
  /** Detect + connect (prompts the user the first time per origin). Resolves the connected address. */
392
431
  connect = async () => {
432
+ const epoch = this.epoch;
393
433
  this.setState({ status: "connecting", error: null });
394
434
  try {
395
- if (!this.conn) {
396
- this.conn = await this.getWalletFn(this.detectOpts);
397
- this.conn.on("accountsChanged", this.onAccounts);
398
- this.conn.on("disconnect", this.onDisconnect);
435
+ let conn = this.conn;
436
+ if (!conn) {
437
+ conn = await this.getWalletFn(this.detectOpts);
438
+ if (epoch !== this.epoch) throw new Error("connection was torn down while connecting - call connect() again");
439
+ conn.on("accountsChanged", this.onAccounts);
440
+ conn.on("disconnect", this.onDisconnect);
441
+ this.conn = conn;
399
442
  }
400
- const addr = await this.conn.connect();
443
+ const addr = await conn.connect();
444
+ if (epoch !== this.epoch) throw new Error("connection was torn down while awaiting approval - call connect() again");
401
445
  this.setState({ status: "connected", account: addr, error: null });
402
446
  return addr;
403
447
  } catch (e) {
404
- this.setState({ status: "disconnected", account: null, error: e?.message ?? String(e) });
448
+ if (epoch === this.epoch) this.setState({ status: "disconnected", account: null, error: e?.message ?? String(e) });
405
449
  throw e;
406
450
  }
407
451
  };
408
- /** Forget the connection locally AND revoke this origin's wallet-side permission (best-effort). */
452
+ /** Forget the connection locally AND revoke this origin's wallet-side permission (best-effort).
453
+ * Local teardown is IMMEDIATE and unconditional; the wallet-side revoke happens after, so a hung
454
+ * provider can never hold a torn-down session in "connected". */
409
455
  disconnect = async () => {
410
- try {
411
- await this.conn?.revokePermissions();
412
- } catch {
413
- }
456
+ this.epoch++;
457
+ const conn = this.conn;
414
458
  this.detach();
415
459
  this.conn = null;
416
460
  this.setState({ status: "disconnected", account: null, error: null });
461
+ try {
462
+ await conn?.revokePermissions();
463
+ } catch {
464
+ }
417
465
  };
418
466
  /** Audience-bound sign-in (must connect() first). Returns the signed artifact to verify server-side. */
419
467
  signInWithCsd = (params) => {
420
- if (!this.conn) return Promise.reject(new Error("call connect() before signInWithCsd()"));
468
+ if (!this.conn || this.state.status !== "connected") return Promise.reject(new Error("call connect() before signInWithCsd()"));
421
469
  return this.conn.signInWithCsd(params);
422
470
  };
423
471
  };
package/dist/react.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  CairnController
3
- } from "./chunk-AGU6K5EZ.js";
4
- import "./chunk-BN3PFJDY.js";
5
- import "./chunk-FMXNJQWL.js";
3
+ } from "./chunk-QATE6AIP.js";
4
+ import "./chunk-PIPN6FSA.js";
5
+ import "./chunk-ERBT4DFD.js";
6
6
 
7
7
  // src/react.ts
8
8
  function createCairnHooks(React, controllerOrOpts) {
package/dist/registry.cjs CHANGED
@@ -34,19 +34,221 @@ __export(registry_exports, {
34
34
  });
35
35
  module.exports = __toCommonJS(registry_exports);
36
36
  var import_csd_registry = require("@inversealtruism/csd-registry");
37
+
38
+ // src/errors.ts
39
+ var SDK_VERSION = "0.4.1";
40
+ var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
41
+ var CairnError = class extends Error {
42
+ /** Stable machine code for programmatic branching. */
43
+ code;
44
+ /** The human message without the appended docs/version footer. */
45
+ shortMessage;
46
+ /** Relative docs path (under the SDK docs base), if any. */
47
+ docsPath;
48
+ /** The cairn-sdk version that produced this error. */
49
+ version = SDK_VERSION;
50
+ /**
51
+ * Whether a dApp may safely AUTO-retry this failure. `false` for fund-safety refusals and user-intent
52
+ * declines (the default); `true` only for provably transient infra faults where nothing was signed.
53
+ * A `SubmitInFlightError` is `false` too: reconcile its `txid`, never blind-retry.
54
+ */
55
+ retryable;
56
+ constructor(shortMessage, opts = {}) {
57
+ const docs = opts.docsPath;
58
+ super(
59
+ [shortMessage, docs ? `Docs: ${DOCS_BASE}${docs}` : "", `Version: cairn-sdk@${SDK_VERSION}`].filter(Boolean).join("\n"),
60
+ { cause: opts.cause }
61
+ );
62
+ this.name = new.target.name;
63
+ this.code = opts.code ?? "UNKNOWN";
64
+ this.shortMessage = shortMessage;
65
+ this.docsPath = docs;
66
+ this.retryable = opts.retryable ?? false;
67
+ }
68
+ /**
69
+ * Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
70
+ * returns the DEEPEST cause (the root). Mirrors viem's `BaseError.walk()`.
71
+ */
72
+ walk(fn) {
73
+ let current = this;
74
+ let last = this;
75
+ while (current != null) {
76
+ if (fn && fn(current)) return current;
77
+ last = current;
78
+ current = current?.cause;
79
+ }
80
+ return fn ? null : last;
81
+ }
82
+ };
83
+ var HttpError = class extends CairnError {
84
+ status;
85
+ url;
86
+ body;
87
+ constructor(status, url, body) {
88
+ super(`HTTP ${status} for ${url}${body ? `: ${String(body).slice(0, 200)}` : ""}`, { code: "HTTP_ERROR" });
89
+ this.status = status;
90
+ this.url = url;
91
+ this.body = body;
92
+ }
93
+ };
94
+
95
+ // src/http.ts
96
+ var DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
97
+ var ERR_TEXT_CAP = 8 * 1024;
98
+ async function readCapped(res, maxBytes) {
99
+ const cl = Number(res.headers.get("content-length"));
100
+ if (Number.isFinite(cl) && cl > maxBytes) throw new HttpError(res.status, res.url, `response too large (content-length ${cl} > ${maxBytes})`);
101
+ const body = res.body;
102
+ if (!body || typeof body.getReader !== "function") {
103
+ const buf = new Uint8Array(await res.arrayBuffer());
104
+ if (buf.byteLength > maxBytes) throw new HttpError(res.status, res.url, `response too large (${buf.byteLength} > ${maxBytes})`);
105
+ return buf;
106
+ }
107
+ const reader = body.getReader();
108
+ const chunks = [];
109
+ let total = 0;
110
+ for (; ; ) {
111
+ const { done, value } = await reader.read();
112
+ if (done) break;
113
+ if (value) {
114
+ total += value.byteLength;
115
+ if (total > maxBytes) {
116
+ try {
117
+ await reader.cancel();
118
+ } catch {
119
+ }
120
+ throw new HttpError(res.status, res.url, `response exceeded ${maxBytes} bytes`);
121
+ }
122
+ chunks.push(value);
123
+ }
124
+ }
125
+ const out = new Uint8Array(total);
126
+ let off = 0;
127
+ for (const c of chunks) {
128
+ out.set(c, off);
129
+ off += c.byteLength;
130
+ }
131
+ return out;
132
+ }
133
+ var Http = class {
134
+ baseUrl;
135
+ _fetch;
136
+ timeoutMs;
137
+ maxBytes;
138
+ headers;
139
+ retries;
140
+ constructor(opts) {
141
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
142
+ const f = opts.fetch ?? globalThis.fetch;
143
+ if (!f) throw new Error("No fetch available \u2014 pass `fetch` in the SDK options.");
144
+ this._fetch = opts.fetch ? opts.fetch : f.bind(globalThis);
145
+ this.timeoutMs = opts.timeoutMs ?? 15e3;
146
+ this.maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
147
+ this.headers = opts.headers ?? {};
148
+ this.retries = Math.min(10, Math.max(0, Math.floor(opts.retries ?? 0)));
149
+ }
150
+ /** Build an absolute URL from a path + optional query params. */
151
+ url(path, query) {
152
+ const p = path.startsWith("/") ? path : `/${path}`;
153
+ const u = new URL(this.baseUrl + p);
154
+ const basePath = new URL(this.baseUrl + "/").pathname;
155
+ if (basePath !== "/" && !(u.pathname + "/").startsWith(basePath)) {
156
+ throw new Error(`illegal path '${path}' \u2014 escapes base ${basePath}`);
157
+ }
158
+ if (query) {
159
+ for (const [k, v] of Object.entries(query)) {
160
+ if (v !== void 0 && v !== null) u.searchParams.set(k, String(v));
161
+ }
162
+ }
163
+ return u.toString();
164
+ }
165
+ async raw(method, path, opts) {
166
+ const url = this.url(path, opts?.query);
167
+ const ctrl = new AbortController();
168
+ const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
169
+ try {
170
+ const res = await this._fetch(url, {
171
+ method,
172
+ signal: ctrl.signal,
173
+ headers: {
174
+ ...opts?.body !== void 0 ? { "content-type": "application/json" } : {},
175
+ ...this.headers,
176
+ ...opts?.headers ?? {}
177
+ },
178
+ body: opts?.body !== void 0 ? JSON.stringify(opts.body) : void 0
179
+ });
180
+ return res;
181
+ } finally {
182
+ clearTimeout(timer);
183
+ }
184
+ }
185
+ // A malformed 2xx body must surface as a TYPED HttpError, not a leaked SyntaxError (B10: dApp
186
+ // code branches on `instanceof CairnError`; a raw parse error escaped that contract).
187
+ parseJson(res, text, url) {
188
+ try {
189
+ return JSON.parse(text);
190
+ } catch (e) {
191
+ throw new HttpError(res.status, url, `invalid JSON body: ${text.slice(0, 120)}`);
192
+ }
193
+ }
194
+ async getJson(path, query) {
195
+ const url = this.url(path, query);
196
+ for (let attempt = 0; ; attempt++) {
197
+ try {
198
+ const res = await this.raw("GET", path, { query });
199
+ if (!res.ok) {
200
+ if (!(res.status >= 500 && attempt < this.retries)) throw new HttpError(res.status, url, await safeText(res));
201
+ res.body?.cancel().catch(() => {
202
+ });
203
+ } else {
204
+ return this.parseJson(res, new TextDecoder().decode(await readCapped(res, this.maxBytes)), url);
205
+ }
206
+ } catch (e) {
207
+ if (e instanceof HttpError && e.status < 500) throw e;
208
+ if (attempt >= this.retries) throw e;
209
+ }
210
+ await new Promise((r) => setTimeout(r, Math.floor(Math.random() * Math.min(4e3, 250 * 2 ** attempt))));
211
+ }
212
+ }
213
+ async postJson(path, body, headers) {
214
+ const url = this.url(path);
215
+ const res = await this.raw("POST", path, { body, headers });
216
+ if (!res.ok) throw new HttpError(res.status, url, await safeText(res));
217
+ return this.parseJson(res, new TextDecoder().decode(await readCapped(res, this.maxBytes)), url);
218
+ }
219
+ /** GET raw bytes (for content retrieval). Returns null on 404. */
220
+ async getBytes(path) {
221
+ const res = await this.raw("GET", path);
222
+ if (res.status === 404) return null;
223
+ if (!res.ok) throw new HttpError(res.status, this.url(path), await safeText(res));
224
+ return { bytes: await readCapped(res, this.maxBytes), headers: res.headers };
225
+ }
226
+ };
227
+ async function safeText(res) {
228
+ try {
229
+ return new TextDecoder().decode(await readCapped(res, ERR_TEXT_CAP));
230
+ } catch {
231
+ return "";
232
+ }
233
+ }
234
+
235
+ // src/registry.ts
37
236
  var import_csd_registry2 = require("@inversealtruism/csd-registry");
38
237
  var RegistryClient = class {
39
- src;
40
- constructor(opts) {
41
- this.src = { baseUrl: opts.baseUrl.replace(/\/+$/, ""), fetch: opts.fetch };
238
+ http;
239
+ /** Accepts the SDK's hardened Http directly (the Cairn facade path) or the legacy
240
+ * { baseUrl, fetch } options (kept for direct constructors; they now get the same
241
+ * hardened transport, built internally). */
242
+ constructor(src) {
243
+ this.http = src instanceof Http ? src : new Http({ baseUrl: src.baseUrl, fetch: src.fetch });
42
244
  }
43
245
  /** Discover ranked content/pin gateways (GET /registry/gateways). */
44
246
  gateways() {
45
- return (0, import_csd_registry.discoverGateways)(this.src);
247
+ return this.http.getJson("/registry/gateways");
46
248
  }
47
249
  /** Discover ranked libp2p peers (GET /registry/peers). */
48
250
  peers() {
49
- return (0, import_csd_registry.discoverPeers)(this.src);
251
+ return this.http.getJson("/registry/peers");
50
252
  }
51
253
  /**
52
254
  * Resolve a handle → identity (GET /identity/:handle). Returns null if unresolved.
@@ -58,12 +260,22 @@ var RegistryClient = class {
58
260
  * source (the wallet's namespv union cure). Treat `verified` here as a hint, not a guarantee.
59
261
  */
60
262
  resolveName(handle) {
61
- return (0, import_csd_registry.resolveName)(this.src, handle);
263
+ return this.getOr404Null(`/identity/${encodeURIComponent(handle)}`);
62
264
  }
63
265
  /** Reverse-resolve an address → identity (GET /address/:addr/identity).
64
266
  * ⚠ Same indexer-trust caveat as resolveName — `verified` is the indexer's claim, not a client proof. */
65
267
  reverseName(address) {
66
- return (0, import_csd_registry.reverseName)(this.src, address);
268
+ return this.getOr404Null(`/address/${encodeURIComponent(address)}/identity`);
269
+ }
270
+ // MF-12: 404 → null (the preserved csd-registry contract for both name resolvers); every other
271
+ // failure rethrows the typed HttpError/CairnError the hardened Http raises.
272
+ async getOr404Null(path) {
273
+ try {
274
+ return await this.http.getJson(path);
275
+ } catch (e) {
276
+ if (e instanceof HttpError && e.status === 404) return null;
277
+ return Promise.reject(e);
278
+ }
67
279
  }
68
280
  /** The deterministic, client-side resolvers (recompute from raw ChainRecord[]). */
69
281
  get fromRecords() {
@@ -1,6 +1,7 @@
1
1
  import * as _inversealtruism_csd_registry from '@inversealtruism/csd-registry';
2
+ import { RankedGateway, RankedPeer, ResolvedIdentity } from '@inversealtruism/csd-registry';
2
3
  export { BuiltRecord, ChainRecord, DOMAINS, RankedGateway, RankedPeer, ResolveOpts, ResolvedIdentity, buildGatewayRecord, buildIdentityCommit, buildIdentityReveal, buildPeerRecord, epochOf, fromRecords, verifyGateway, verifyIdentitySig, verifyPeer } from '@inversealtruism/csd-registry';
3
- import { F as FetchLike } from './http-C9C4L4hz.cjs';
4
+ import { H as Http, F as FetchLike } from './http-C9C4L4hz.cjs';
4
5
 
5
6
  interface RegistryClientOptions {
6
7
  /** Indexer base URL exposing /registry/* + /identity/* (e.g. https://cairn-substrate.com/indexer). */
@@ -8,12 +9,15 @@ interface RegistryClientOptions {
8
9
  fetch?: FetchLike;
9
10
  }
10
11
  declare class RegistryClient {
11
- private readonly src;
12
- constructor(opts: RegistryClientOptions);
12
+ private readonly http;
13
+ /** Accepts the SDK's hardened Http directly (the Cairn facade path) or the legacy
14
+ * { baseUrl, fetch } options (kept for direct constructors; they now get the same
15
+ * hardened transport, built internally). */
16
+ constructor(src: Http | RegistryClientOptions);
13
17
  /** Discover ranked content/pin gateways (GET /registry/gateways). */
14
- gateways(): Promise<_inversealtruism_csd_registry.RankedGateway[]>;
18
+ gateways(): Promise<RankedGateway[]>;
15
19
  /** Discover ranked libp2p peers (GET /registry/peers). */
16
- peers(): Promise<_inversealtruism_csd_registry.RankedPeer[]>;
20
+ peers(): Promise<RankedPeer[]>;
17
21
  /**
18
22
  * Resolve a handle → identity (GET /identity/:handle). Returns null if unresolved.
19
23
  *
@@ -23,16 +27,17 @@ declare class RegistryClient {
23
27
  * recomputing from raw records via `fromRecords.name(...)`, or (b) cross-checking a second independent
24
28
  * source (the wallet's namespv union cure). Treat `verified` here as a hint, not a guarantee.
25
29
  */
26
- resolveName(handle: string): Promise<_inversealtruism_csd_registry.ResolvedIdentity | null>;
30
+ resolveName(handle: string): Promise<ResolvedIdentity | null>;
27
31
  /** Reverse-resolve an address → identity (GET /address/:addr/identity).
28
32
  * ⚠ Same indexer-trust caveat as resolveName — `verified` is the indexer's claim, not a client proof. */
29
- reverseName(address: string): Promise<_inversealtruism_csd_registry.ResolvedIdentity | null>;
33
+ reverseName(address: string): Promise<ResolvedIdentity | null>;
34
+ private getOr404Null;
30
35
  /** The deterministic, client-side resolvers (recompute from raw ChainRecord[]). */
31
36
  get fromRecords(): {
32
- peers: (records: _inversealtruism_csd_registry.ChainRecord[], opts: _inversealtruism_csd_registry.ResolveOpts) => _inversealtruism_csd_registry.RankedPeer[];
33
- gateways: (records: _inversealtruism_csd_registry.ChainRecord[], opts: _inversealtruism_csd_registry.ResolveOpts) => _inversealtruism_csd_registry.RankedGateway[];
34
- name: (records: _inversealtruism_csd_registry.ChainRecord[], handle: string, opts: _inversealtruism_csd_registry.ResolveOpts) => _inversealtruism_csd_registry.ResolvedIdentity | null;
35
- reverse: (records: _inversealtruism_csd_registry.ChainRecord[], address: string, opts: _inversealtruism_csd_registry.ResolveOpts) => _inversealtruism_csd_registry.ResolvedIdentity | null;
37
+ peers: (records: _inversealtruism_csd_registry.ChainRecord[], opts: _inversealtruism_csd_registry.ResolveOpts) => RankedPeer[];
38
+ gateways: (records: _inversealtruism_csd_registry.ChainRecord[], opts: _inversealtruism_csd_registry.ResolveOpts) => RankedGateway[];
39
+ name: (records: _inversealtruism_csd_registry.ChainRecord[], handle: string, opts: _inversealtruism_csd_registry.ResolveOpts) => ResolvedIdentity | null;
40
+ reverse: (records: _inversealtruism_csd_registry.ChainRecord[], address: string, opts: _inversealtruism_csd_registry.ResolveOpts) => ResolvedIdentity | null;
36
41
  };
37
42
  }
38
43
 
@@ -1,6 +1,7 @@
1
1
  import * as _inversealtruism_csd_registry from '@inversealtruism/csd-registry';
2
+ import { RankedGateway, RankedPeer, ResolvedIdentity } from '@inversealtruism/csd-registry';
2
3
  export { BuiltRecord, ChainRecord, DOMAINS, RankedGateway, RankedPeer, ResolveOpts, ResolvedIdentity, buildGatewayRecord, buildIdentityCommit, buildIdentityReveal, buildPeerRecord, epochOf, fromRecords, verifyGateway, verifyIdentitySig, verifyPeer } from '@inversealtruism/csd-registry';
3
- import { F as FetchLike } from './http-C9C4L4hz.js';
4
+ import { H as Http, F as FetchLike } from './http-C9C4L4hz.js';
4
5
 
5
6
  interface RegistryClientOptions {
6
7
  /** Indexer base URL exposing /registry/* + /identity/* (e.g. https://cairn-substrate.com/indexer). */
@@ -8,12 +9,15 @@ interface RegistryClientOptions {
8
9
  fetch?: FetchLike;
9
10
  }
10
11
  declare class RegistryClient {
11
- private readonly src;
12
- constructor(opts: RegistryClientOptions);
12
+ private readonly http;
13
+ /** Accepts the SDK's hardened Http directly (the Cairn facade path) or the legacy
14
+ * { baseUrl, fetch } options (kept for direct constructors; they now get the same
15
+ * hardened transport, built internally). */
16
+ constructor(src: Http | RegistryClientOptions);
13
17
  /** Discover ranked content/pin gateways (GET /registry/gateways). */
14
- gateways(): Promise<_inversealtruism_csd_registry.RankedGateway[]>;
18
+ gateways(): Promise<RankedGateway[]>;
15
19
  /** Discover ranked libp2p peers (GET /registry/peers). */
16
- peers(): Promise<_inversealtruism_csd_registry.RankedPeer[]>;
20
+ peers(): Promise<RankedPeer[]>;
17
21
  /**
18
22
  * Resolve a handle → identity (GET /identity/:handle). Returns null if unresolved.
19
23
  *
@@ -23,16 +27,17 @@ declare class RegistryClient {
23
27
  * recomputing from raw records via `fromRecords.name(...)`, or (b) cross-checking a second independent
24
28
  * source (the wallet's namespv union cure). Treat `verified` here as a hint, not a guarantee.
25
29
  */
26
- resolveName(handle: string): Promise<_inversealtruism_csd_registry.ResolvedIdentity | null>;
30
+ resolveName(handle: string): Promise<ResolvedIdentity | null>;
27
31
  /** Reverse-resolve an address → identity (GET /address/:addr/identity).
28
32
  * ⚠ Same indexer-trust caveat as resolveName — `verified` is the indexer's claim, not a client proof. */
29
- reverseName(address: string): Promise<_inversealtruism_csd_registry.ResolvedIdentity | null>;
33
+ reverseName(address: string): Promise<ResolvedIdentity | null>;
34
+ private getOr404Null;
30
35
  /** The deterministic, client-side resolvers (recompute from raw ChainRecord[]). */
31
36
  get fromRecords(): {
32
- peers: (records: _inversealtruism_csd_registry.ChainRecord[], opts: _inversealtruism_csd_registry.ResolveOpts) => _inversealtruism_csd_registry.RankedPeer[];
33
- gateways: (records: _inversealtruism_csd_registry.ChainRecord[], opts: _inversealtruism_csd_registry.ResolveOpts) => _inversealtruism_csd_registry.RankedGateway[];
34
- name: (records: _inversealtruism_csd_registry.ChainRecord[], handle: string, opts: _inversealtruism_csd_registry.ResolveOpts) => _inversealtruism_csd_registry.ResolvedIdentity | null;
35
- reverse: (records: _inversealtruism_csd_registry.ChainRecord[], address: string, opts: _inversealtruism_csd_registry.ResolveOpts) => _inversealtruism_csd_registry.ResolvedIdentity | null;
37
+ peers: (records: _inversealtruism_csd_registry.ChainRecord[], opts: _inversealtruism_csd_registry.ResolveOpts) => RankedPeer[];
38
+ gateways: (records: _inversealtruism_csd_registry.ChainRecord[], opts: _inversealtruism_csd_registry.ResolveOpts) => RankedGateway[];
39
+ name: (records: _inversealtruism_csd_registry.ChainRecord[], handle: string, opts: _inversealtruism_csd_registry.ResolveOpts) => ResolvedIdentity | null;
40
+ reverse: (records: _inversealtruism_csd_registry.ChainRecord[], address: string, opts: _inversealtruism_csd_registry.ResolveOpts) => ResolvedIdentity | null;
36
41
  };
37
42
  }
38
43
 
package/dist/registry.js CHANGED
@@ -10,7 +10,8 @@ import {
10
10
  verifyGateway,
11
11
  verifyIdentitySig,
12
12
  verifyPeer
13
- } from "./chunk-BGJZF7S2.js";
13
+ } from "./chunk-7UOTIAYK.js";
14
+ import "./chunk-ERBT4DFD.js";
14
15
  export {
15
16
  DOMAINS,
16
17
  RegistryClient,