@inversealtruism/cairn-sdk 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/board.cjs +33 -2
  2. package/dist/board.d.cts +1 -1
  3. package/dist/board.d.ts +1 -1
  4. package/dist/board.js +2 -2
  5. package/dist/chain.cjs +29 -0
  6. package/dist/chain.d.cts +24 -3
  7. package/dist/chain.d.ts +24 -3
  8. package/dist/chain.js +5 -1
  9. package/dist/{chunk-G7FABIF4.js → chunk-BGJZF7S2.js} +11 -2
  10. package/dist/{chunk-6VWLWF7R.js → chunk-C452UODV.js} +31 -2
  11. package/dist/{chunk-OGP2W7DL.js → chunk-FSQZVNEM.js} +1 -1
  12. package/dist/chunk-K7SJOW3O.js +213 -0
  13. package/dist/chunk-MBSPHWZG.js +96 -0
  14. package/dist/{chunk-BZVJVL4Q.js → chunk-MVX7VQT4.js} +4 -3
  15. package/dist/{chunk-FNBE2FPE.js → chunk-OG4KA3LS.js} +49 -17
  16. package/dist/chunk-RELP23CH.js +99 -0
  17. package/dist/connect.cjs +145 -18
  18. package/dist/connect.d.cts +140 -2
  19. package/dist/connect.d.ts +140 -2
  20. package/dist/connect.js +4 -2
  21. package/dist/content.cjs +37 -5
  22. package/dist/content.d.cts +1 -1
  23. package/dist/content.d.ts +1 -1
  24. package/dist/content.js +2 -2
  25. package/dist/controller.cjs +355 -0
  26. package/dist/controller.d.cts +37 -0
  27. package/dist/controller.d.ts +37 -0
  28. package/dist/controller.js +8 -0
  29. package/dist/{http-D2CzfOsm.d.cts → http-CQsNL8w9.d.cts} +5 -0
  30. package/dist/{http-D2CzfOsm.d.ts → http-CQsNL8w9.d.ts} +5 -0
  31. package/dist/index.cjs +386 -48
  32. package/dist/index.d.cts +39 -6
  33. package/dist/index.d.ts +39 -6
  34. package/dist/index.js +75 -20
  35. package/dist/indexer.cjs +49 -17
  36. package/dist/indexer.d.cts +15 -4
  37. package/dist/indexer.d.ts +15 -4
  38. package/dist/indexer.js +1 -1
  39. package/dist/react.cjs +371 -0
  40. package/dist/react.d.cts +30 -0
  41. package/dist/react.d.ts +30 -0
  42. package/dist/react.js +23 -0
  43. package/dist/registry.cjs +11 -2
  44. package/dist/registry.d.cts +12 -3
  45. package/dist/registry.d.ts +12 -3
  46. package/dist/registry.js +1 -1
  47. package/package.json +21 -10
  48. package/dist/chunk-PHMU3CG5.js +0 -63
  49. package/dist/chunk-SMAPCKVG.js +0 -118
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FetchLike } from './http-D2CzfOsm.cjs';
2
- export { H as Http, a as HttpOptions } from './http-D2CzfOsm.cjs';
1
+ import { F as FetchLike } from './http-CQsNL8w9.cjs';
2
+ export { H as Http, a as HttpOptions } from './http-CQsNL8w9.cjs';
3
3
  import { Chain } from './chain.cjs';
4
4
  import { BoardClient } from './board.cjs';
5
5
  export { BoardItemContent, BoardWindow, ProposeInput, ProposeResult, RankedItem, SupportInput } from './board.cjs';
@@ -9,7 +9,8 @@ import { ContentClient } from './content.cjs';
9
9
  export { ContentClientOptions, PreparedContent } from './content.cjs';
10
10
  import { RegistryClient } from './registry.cjs';
11
11
  import { WalletConnection, DetectOptions } from './connect.cjs';
12
- export { AttestParams, CairnProvider, ConnectResult, ProposeParams, SealClaimParams, SendParams, SignInResult, TxResult, connect, detectProvider, getWallet, isInstalled } from './connect.cjs';
12
+ export { AttestParams, CairnProvider, ConnectResult, FillParams, ProposeParams, ProviderDetail, ProviderInfo, SealClaimParams, SendParams, SignInResult, SiwcParams, SiwcResult, TxResult, WalletCapabilities, WalletPermission, connect, detectProvider, discoverProviders, getWallet, isInstalled } from './connect.cjs';
13
+ export { CairnController, CairnControllerOptions, CairnState } from './controller.cjs';
13
14
  import '@inversealtruism/csd-client';
14
15
  import '@inversealtruism/csd-tx';
15
16
  import '@inversealtruism/csd-codec';
@@ -17,8 +18,29 @@ import '@inversealtruism/csd-crypto';
17
18
  import '@inversealtruism/csd-light';
18
19
  import '@inversealtruism/csd-registry';
19
20
 
21
+ /** Stable machine-readable error codes (branch on these, not the message text). */
22
+ type CairnErrorCode = "NOT_INSTALLED" | "USER_REJECTED" | "WALLET_LOCKED" | "UNSUPPORTED_METHOD" | "HTTP_ERROR" | "CONTENT_VERIFICATION" | "UNKNOWN";
23
+ declare const SDK_VERSION = "0.1.4";
24
+ interface CairnErrorOptions {
25
+ code?: CairnErrorCode;
26
+ docsPath?: string;
27
+ cause?: unknown;
28
+ }
20
29
  declare class CairnError extends Error {
21
- constructor(message: string);
30
+ /** Stable machine code for programmatic branching. */
31
+ readonly code: CairnErrorCode;
32
+ /** The human message without the appended docs/version footer. */
33
+ readonly shortMessage: string;
34
+ /** Relative docs path (under the SDK docs base), if any. */
35
+ readonly docsPath?: string;
36
+ /** The cairn-sdk version that produced this error. */
37
+ readonly version = "0.1.4";
38
+ constructor(shortMessage: string, opts?: CairnErrorOptions);
39
+ /**
40
+ * Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
41
+ * returns the DEEPEST cause (the root). Mirrors viem's `BaseError.walk()`.
42
+ */
43
+ walk(fn?: (err: unknown) => boolean): unknown;
22
44
  }
23
45
  /** The Cairn Wallet extension is not installed (no `window.cairn` appeared). */
24
46
  declare class NotInstalledError extends CairnError {
@@ -32,7 +54,7 @@ declare class UserRejectedError extends CairnError {
32
54
  declare class WalletLockedError extends CairnError {
33
55
  constructor(message?: string);
34
56
  }
35
- /** The method is not exposed to dApps by the wallet boundary. */
57
+ /** The method is not exposed to dApps by the wallet boundary (or the wallet is too old for it). */
36
58
  declare class UnsupportedMethodError extends CairnError {
37
59
  constructor(message?: string);
38
60
  }
@@ -48,6 +70,13 @@ declare class ContentVerificationError extends CairnError {
48
70
  expected: string;
49
71
  constructor(expected: string);
50
72
  }
73
+ /**
74
+ * Map a provider error string (from the wallet's `{ ok:false, error }`) onto a typed error. The
75
+ * wallet's strings are stable: "rejected by user", "wallet locked", "unsupported dApp method: …".
76
+ */
77
+ declare function mapProviderError(error: string): CairnError;
78
+ /** Convenience: the stable code of any error (or "UNKNOWN" for a non-CairnError). */
79
+ declare function errorCode(err: unknown): CairnErrorCode;
51
80
 
52
81
  interface CairnBaseUrls {
53
82
  /** Cairn board server (board + /api/rpc proxy + /content origin). Default https://cairn-substrate.com */
@@ -93,6 +122,10 @@ interface CairnConfig {
93
122
  hash: string;
94
123
  };
95
124
  }
125
+ declare const DEFAULT_SPV_CHECKPOINT: {
126
+ height: number;
127
+ hash: string;
128
+ };
96
129
  declare class Cairn {
97
130
  readonly config: CairnConfig;
98
131
  readonly chain: Chain;
@@ -126,4 +159,4 @@ declare class Cairn {
126
159
  get connected(): boolean;
127
160
  }
128
161
 
129
- export { BoardClient, Cairn, type CairnBaseUrls, type CairnConfig, CairnError, Chain, ContentClient, ContentVerificationError, DetectOptions, FetchLike, HttpError, IndexerClient, NotInstalledError, RegistryClient, UnsupportedMethodError, UserRejectedError, WalletConnection, WalletLockedError };
162
+ export { BoardClient, Cairn, type CairnBaseUrls, type CairnConfig, CairnError, type CairnErrorCode, type CairnErrorOptions, Chain, ContentClient, ContentVerificationError, DEFAULT_SPV_CHECKPOINT, DetectOptions, FetchLike, HttpError, IndexerClient, NotInstalledError, RegistryClient, SDK_VERSION, UnsupportedMethodError, UserRejectedError, WalletConnection, WalletLockedError, errorCode, mapProviderError };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FetchLike } from './http-D2CzfOsm.js';
2
- export { H as Http, a as HttpOptions } from './http-D2CzfOsm.js';
1
+ import { F as FetchLike } from './http-CQsNL8w9.js';
2
+ export { H as Http, a as HttpOptions } from './http-CQsNL8w9.js';
3
3
  import { Chain } from './chain.js';
4
4
  import { BoardClient } from './board.js';
5
5
  export { BoardItemContent, BoardWindow, ProposeInput, ProposeResult, RankedItem, SupportInput } from './board.js';
@@ -9,7 +9,8 @@ import { ContentClient } from './content.js';
9
9
  export { ContentClientOptions, PreparedContent } from './content.js';
10
10
  import { RegistryClient } from './registry.js';
11
11
  import { WalletConnection, DetectOptions } from './connect.js';
12
- export { AttestParams, CairnProvider, ConnectResult, ProposeParams, SealClaimParams, SendParams, SignInResult, TxResult, connect, detectProvider, getWallet, isInstalled } from './connect.js';
12
+ export { AttestParams, CairnProvider, ConnectResult, FillParams, ProposeParams, ProviderDetail, ProviderInfo, SealClaimParams, SendParams, SignInResult, SiwcParams, SiwcResult, TxResult, WalletCapabilities, WalletPermission, connect, detectProvider, discoverProviders, getWallet, isInstalled } from './connect.js';
13
+ export { CairnController, CairnControllerOptions, CairnState } from './controller.js';
13
14
  import '@inversealtruism/csd-client';
14
15
  import '@inversealtruism/csd-tx';
15
16
  import '@inversealtruism/csd-codec';
@@ -17,8 +18,29 @@ import '@inversealtruism/csd-crypto';
17
18
  import '@inversealtruism/csd-light';
18
19
  import '@inversealtruism/csd-registry';
19
20
 
21
+ /** Stable machine-readable error codes (branch on these, not the message text). */
22
+ type CairnErrorCode = "NOT_INSTALLED" | "USER_REJECTED" | "WALLET_LOCKED" | "UNSUPPORTED_METHOD" | "HTTP_ERROR" | "CONTENT_VERIFICATION" | "UNKNOWN";
23
+ declare const SDK_VERSION = "0.1.4";
24
+ interface CairnErrorOptions {
25
+ code?: CairnErrorCode;
26
+ docsPath?: string;
27
+ cause?: unknown;
28
+ }
20
29
  declare class CairnError extends Error {
21
- constructor(message: string);
30
+ /** Stable machine code for programmatic branching. */
31
+ readonly code: CairnErrorCode;
32
+ /** The human message without the appended docs/version footer. */
33
+ readonly shortMessage: string;
34
+ /** Relative docs path (under the SDK docs base), if any. */
35
+ readonly docsPath?: string;
36
+ /** The cairn-sdk version that produced this error. */
37
+ readonly version = "0.1.4";
38
+ constructor(shortMessage: string, opts?: CairnErrorOptions);
39
+ /**
40
+ * Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
41
+ * returns the DEEPEST cause (the root). Mirrors viem's `BaseError.walk()`.
42
+ */
43
+ walk(fn?: (err: unknown) => boolean): unknown;
22
44
  }
23
45
  /** The Cairn Wallet extension is not installed (no `window.cairn` appeared). */
24
46
  declare class NotInstalledError extends CairnError {
@@ -32,7 +54,7 @@ declare class UserRejectedError extends CairnError {
32
54
  declare class WalletLockedError extends CairnError {
33
55
  constructor(message?: string);
34
56
  }
35
- /** The method is not exposed to dApps by the wallet boundary. */
57
+ /** The method is not exposed to dApps by the wallet boundary (or the wallet is too old for it). */
36
58
  declare class UnsupportedMethodError extends CairnError {
37
59
  constructor(message?: string);
38
60
  }
@@ -48,6 +70,13 @@ declare class ContentVerificationError extends CairnError {
48
70
  expected: string;
49
71
  constructor(expected: string);
50
72
  }
73
+ /**
74
+ * Map a provider error string (from the wallet's `{ ok:false, error }`) onto a typed error. The
75
+ * wallet's strings are stable: "rejected by user", "wallet locked", "unsupported dApp method: …".
76
+ */
77
+ declare function mapProviderError(error: string): CairnError;
78
+ /** Convenience: the stable code of any error (or "UNKNOWN" for a non-CairnError). */
79
+ declare function errorCode(err: unknown): CairnErrorCode;
51
80
 
52
81
  interface CairnBaseUrls {
53
82
  /** Cairn board server (board + /api/rpc proxy + /content origin). Default https://cairn-substrate.com */
@@ -93,6 +122,10 @@ interface CairnConfig {
93
122
  hash: string;
94
123
  };
95
124
  }
125
+ declare const DEFAULT_SPV_CHECKPOINT: {
126
+ height: number;
127
+ hash: string;
128
+ };
96
129
  declare class Cairn {
97
130
  readonly config: CairnConfig;
98
131
  readonly chain: Chain;
@@ -126,4 +159,4 @@ declare class Cairn {
126
159
  get connected(): boolean;
127
160
  }
128
161
 
129
- export { BoardClient, Cairn, type CairnBaseUrls, type CairnConfig, CairnError, Chain, ContentClient, ContentVerificationError, DetectOptions, FetchLike, HttpError, IndexerClient, NotInstalledError, RegistryClient, UnsupportedMethodError, UserRejectedError, WalletConnection, WalletLockedError };
162
+ export { BoardClient, Cairn, type CairnBaseUrls, type CairnConfig, CairnError, type CairnErrorCode, type CairnErrorOptions, Chain, ContentClient, ContentVerificationError, DEFAULT_SPV_CHECKPOINT, DetectOptions, FetchLike, HttpError, IndexerClient, NotInstalledError, RegistryClient, SDK_VERSION, UnsupportedMethodError, UserRejectedError, WalletConnection, WalletLockedError, errorCode, mapProviderError };
package/dist/index.js CHANGED
@@ -1,41 +1,86 @@
1
+ import {
2
+ RegistryClient
3
+ } from "./chunk-BGJZF7S2.js";
1
4
  import {
2
5
  BoardClient
3
- } from "./chunk-OGP2W7DL.js";
6
+ } from "./chunk-FSQZVNEM.js";
4
7
  import {
5
8
  Chain,
6
9
  LightClient
7
- } from "./chunk-6VWLWF7R.js";
10
+ } from "./chunk-C452UODV.js";
11
+ import {
12
+ ContentClient
13
+ } from "./chunk-MVX7VQT4.js";
14
+ import {
15
+ IndexerClient
16
+ } from "./chunk-OG4KA3LS.js";
17
+ import {
18
+ CairnController
19
+ } from "./chunk-MBSPHWZG.js";
8
20
  import {
9
21
  WalletConnection,
10
22
  connect,
11
23
  detectProvider,
24
+ discoverProviders,
12
25
  getWallet,
13
26
  isInstalled
14
- } from "./chunk-SMAPCKVG.js";
15
- import {
16
- ContentClient
17
- } from "./chunk-BZVJVL4Q.js";
27
+ } from "./chunk-K7SJOW3O.js";
18
28
  import {
19
29
  CairnError,
20
30
  ContentVerificationError,
21
31
  HttpError,
22
32
  NotInstalledError,
33
+ SDK_VERSION,
23
34
  UnsupportedMethodError,
24
35
  UserRejectedError,
25
- WalletLockedError
26
- } from "./chunk-PHMU3CG5.js";
27
- import {
28
- IndexerClient
29
- } from "./chunk-FNBE2FPE.js";
30
- import {
31
- RegistryClient
32
- } from "./chunk-G7FABIF4.js";
36
+ WalletLockedError,
37
+ errorCode,
38
+ mapProviderError
39
+ } from "./chunk-RELP23CH.js";
33
40
 
34
41
  // src/http.ts
42
+ var DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
43
+ var ERR_TEXT_CAP = 8 * 1024;
44
+ async function readCapped(res, maxBytes) {
45
+ const cl = Number(res.headers.get("content-length"));
46
+ if (Number.isFinite(cl) && cl > maxBytes) throw new HttpError(res.status, res.url, `response too large (content-length ${cl} > ${maxBytes})`);
47
+ const body = res.body;
48
+ if (!body || typeof body.getReader !== "function") {
49
+ const buf = new Uint8Array(await res.arrayBuffer());
50
+ if (buf.byteLength > maxBytes) throw new HttpError(res.status, res.url, `response too large (${buf.byteLength} > ${maxBytes})`);
51
+ return buf;
52
+ }
53
+ const reader = body.getReader();
54
+ const chunks = [];
55
+ let total = 0;
56
+ for (; ; ) {
57
+ const { done, value } = await reader.read();
58
+ if (done) break;
59
+ if (value) {
60
+ total += value.byteLength;
61
+ if (total > maxBytes) {
62
+ try {
63
+ await reader.cancel();
64
+ } catch {
65
+ }
66
+ throw new HttpError(res.status, res.url, `response exceeded ${maxBytes} bytes`);
67
+ }
68
+ chunks.push(value);
69
+ }
70
+ }
71
+ const out = new Uint8Array(total);
72
+ let off = 0;
73
+ for (const c of chunks) {
74
+ out.set(c, off);
75
+ off += c.byteLength;
76
+ }
77
+ return out;
78
+ }
35
79
  var Http = class {
36
80
  baseUrl;
37
81
  _fetch;
38
82
  timeoutMs;
83
+ maxBytes;
39
84
  headers;
40
85
  constructor(opts) {
41
86
  this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
@@ -43,12 +88,17 @@ var Http = class {
43
88
  if (!f) throw new Error("No fetch available \u2014 pass `fetch` in the SDK options.");
44
89
  this._fetch = opts.fetch ? opts.fetch : f.bind(globalThis);
45
90
  this.timeoutMs = opts.timeoutMs ?? 15e3;
91
+ this.maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
46
92
  this.headers = opts.headers ?? {};
47
93
  }
48
94
  /** Build an absolute URL from a path + optional query params. */
49
95
  url(path, query) {
50
96
  const p = path.startsWith("/") ? path : `/${path}`;
51
97
  const u = new URL(this.baseUrl + p);
98
+ const basePath = new URL(this.baseUrl + "/").pathname;
99
+ if (basePath !== "/" && !(u.pathname + "/").startsWith(basePath)) {
100
+ throw new Error(`illegal path '${path}' \u2014 escapes base ${basePath}`);
101
+ }
52
102
  if (query) {
53
103
  for (const [k, v] of Object.entries(query)) {
54
104
  if (v !== void 0 && v !== null) u.searchParams.set(k, String(v));
@@ -79,25 +129,24 @@ var Http = class {
79
129
  async getJson(path, query) {
80
130
  const res = await this.raw("GET", path, { query });
81
131
  if (!res.ok) throw new HttpError(res.status, this.url(path, query), await safeText(res));
82
- return await res.json();
132
+ return JSON.parse(new TextDecoder().decode(await readCapped(res, this.maxBytes)));
83
133
  }
84
134
  async postJson(path, body, headers) {
85
135
  const res = await this.raw("POST", path, { body, headers });
86
136
  if (!res.ok) throw new HttpError(res.status, this.url(path), await safeText(res));
87
- return await res.json();
137
+ return JSON.parse(new TextDecoder().decode(await readCapped(res, this.maxBytes)));
88
138
  }
89
139
  /** GET raw bytes (for content retrieval). Returns null on 404. */
90
140
  async getBytes(path) {
91
141
  const res = await this.raw("GET", path);
92
142
  if (res.status === 404) return null;
93
143
  if (!res.ok) throw new HttpError(res.status, this.url(path), await safeText(res));
94
- const buf = new Uint8Array(await res.arrayBuffer());
95
- return { bytes: buf, headers: res.headers };
144
+ return { bytes: await readCapped(res, this.maxBytes), headers: res.headers };
96
145
  }
97
146
  };
98
147
  async function safeText(res) {
99
148
  try {
100
- return await res.text();
149
+ return new TextDecoder().decode(await readCapped(res, ERR_TEXT_CAP));
101
150
  } catch {
102
151
  return "";
103
152
  }
@@ -186,21 +235,27 @@ var Cairn = class {
186
235
  export {
187
236
  BoardClient,
188
237
  Cairn,
238
+ CairnController,
189
239
  CairnError,
190
240
  Chain,
191
241
  ContentClient,
192
242
  ContentVerificationError,
243
+ DEFAULT_SPV_CHECKPOINT,
193
244
  Http,
194
245
  HttpError,
195
246
  IndexerClient,
196
247
  NotInstalledError,
197
248
  RegistryClient,
249
+ SDK_VERSION,
198
250
  UnsupportedMethodError,
199
251
  UserRejectedError,
200
252
  WalletConnection,
201
253
  WalletLockedError,
202
254
  connect,
203
255
  detectProvider,
256
+ discoverProviders,
257
+ errorCode,
204
258
  getWallet,
205
- isInstalled
259
+ isInstalled,
260
+ mapProviderError
206
261
  };
package/dist/indexer.cjs CHANGED
@@ -24,6 +24,13 @@ __export(indexer_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(indexer_exports);
26
26
  var import_csd_codec = require("@inversealtruism/csd-codec");
27
+ var HEX64 = /^0x[0-9a-fA-F]{64}$/;
28
+ var MAX_SSE_BUF = 1024 * 1024;
29
+ function isValidMerkleProof(p) {
30
+ if (!p || typeof p !== "object") return false;
31
+ const m = p;
32
+ return Number.isInteger(m.block_height) && m.block_height >= 0 && Number.isInteger(m.pos) && m.pos >= 0 && typeof m.merkle_root === "string" && HEX64.test(m.merkle_root) && Array.isArray(m.merkle) && m.merkle.every((x) => typeof x === "string" && HEX64.test(x));
33
+ }
27
34
  var IndexerClient = class {
28
35
  http;
29
36
  opts;
@@ -44,32 +51,35 @@ var IndexerClient = class {
44
51
  blockHashAtHeight(h) {
45
52
  return this.http.getJson(`/block-height/${h}`);
46
53
  }
54
+ // CAIRNSDK-CONTENT-HASH-PATH-1: encode every caller-supplied id segment. Raw interpolation let an id like
55
+ // `../../api/rpc/tip` walk `new URL` to a DIFFERENT same-origin endpoint whose body was then returned
56
+ // verbatim as the requested object (worse than the content case, which discards the walked-to response).
47
57
  block(hash) {
48
- return this.http.getJson(`/block/${hash}`);
58
+ return this.http.getJson(`/block/${encodeURIComponent(hash)}`);
49
59
  }
50
60
  blockTxids(hash) {
51
- return this.http.getJson(`/block/${hash}/txids`);
61
+ return this.http.getJson(`/block/${encodeURIComponent(hash)}/txids`);
52
62
  }
53
63
  tx(id) {
54
- return this.http.getJson(`/tx/${id}`);
64
+ return this.http.getJson(`/tx/${encodeURIComponent(id)}`);
55
65
  }
56
66
  txStatus(id) {
57
- return this.http.getJson(`/tx/${id}/status`);
67
+ return this.http.getJson(`/tx/${encodeURIComponent(id)}/status`);
58
68
  }
59
69
  txMerkleProof(id) {
60
- return this.http.getJson(`/tx/${id}/merkle-proof`);
70
+ return this.http.getJson(`/tx/${encodeURIComponent(id)}/merkle-proof`);
61
71
  }
62
72
  address(a) {
63
- return this.http.getJson(`/address/${a}`);
73
+ return this.http.getJson(`/address/${encodeURIComponent(a)}`);
64
74
  }
65
75
  addressTxs(a) {
66
- return this.http.getJson(`/address/${a}/txs`);
76
+ return this.http.getJson(`/address/${encodeURIComponent(a)}/txs`);
67
77
  }
68
78
  addressUtxo(a) {
69
- return this.http.getJson(`/address/${a}/utxo`);
79
+ return this.http.getJson(`/address/${encodeURIComponent(a)}/utxo`);
70
80
  }
71
81
  reputation(a) {
72
- return this.http.getJson(`/address/${a}/reputation`);
82
+ return this.http.getJson(`/address/${encodeURIComponent(a)}/reputation`);
73
83
  }
74
84
  domains() {
75
85
  return this.http.getJson("/domains");
@@ -78,10 +88,10 @@ var IndexerClient = class {
78
88
  return this.http.getJson(`/domain/${encodeURIComponent(d)}/proposals`);
79
89
  }
80
90
  proposal(id) {
81
- return this.http.getJson(`/proposal/${id}`);
91
+ return this.http.getJson(`/proposal/${encodeURIComponent(id)}`);
82
92
  }
83
93
  attestations(id) {
84
- return this.http.getJson(`/proposal/${id}/attestations`);
94
+ return this.http.getJson(`/proposal/${encodeURIComponent(id)}/attestations`);
85
95
  }
86
96
  registryPeers() {
87
97
  return this.http.getJson("/registry/peers");
@@ -93,7 +103,7 @@ var IndexerClient = class {
93
103
  return this.http.getJson(`/identity/${encodeURIComponent(handle)}`);
94
104
  }
95
105
  reverseIdentity(addr) {
96
- return this.http.getJson(`/address/${addr}/identity`);
106
+ return this.http.getJson(`/address/${encodeURIComponent(addr)}/identity`);
97
107
  }
98
108
  // ---- trust-minimized inclusion ----------------------------------------
99
109
  /**
@@ -102,9 +112,11 @@ var IndexerClient = class {
102
112
  * proof's root is cross-checked against the header merkle at that height →
103
113
  * trustLevel "verified-inclusion" (only as trustworthy as that header source —
104
114
  * see headerMerkleAt). Otherwise the proof is only internally consistent →
105
- * "proof-consistent" (still useful, but the indexer is trusted). The Cairn
106
- * facade wires headerMerkleAt ONLY when the node RPC is an independent origin
107
- * from the indexer, so the default same-origin setup stays at "proof-consistent".
115
+ * "proof-consistent" (still useful, but the indexer is trusted). INDEXER-DOC-STALE-1: the Cairn facade now
116
+ * wires headerMerkleAt UNCONDITIONALLY via a PoW-verifying checkpoint light client (index.ts), so the
117
+ * default reaches "verified-inclusion" when the proof root matches the PoW-verified header — what makes a
118
+ * same-origin RPC safe is the forward PoW re-verification, not an origin gate (which no longer exists).
119
+ * Fails closed: a malformed proof or a chain-disagreement returns "not-found" (the latter with equivocation:true).
108
120
  */
109
121
  async verifyInclusion(txid) {
110
122
  let proof;
@@ -113,7 +125,15 @@ var IndexerClient = class {
113
125
  } catch {
114
126
  return { included: false, trustLevel: "not-found", reason: "no merkle proof (tx not indexed)" };
115
127
  }
116
- const folds = (0, import_csd_codec.verifyMerkleProof)(txid, proof.pos, proof.merkle, proof.merkle_root);
128
+ if (!isValidMerkleProof(proof)) {
129
+ return { included: false, trustLevel: "not-found", reason: "malformed merkle proof" };
130
+ }
131
+ let folds = false;
132
+ try {
133
+ folds = (0, import_csd_codec.verifyMerkleProof)(txid, proof.pos, proof.merkle, proof.merkle_root);
134
+ } catch {
135
+ return { included: false, trustLevel: "not-found", blockHeight: proof.block_height, reason: "malformed merkle proof" };
136
+ }
117
137
  if (!folds) {
118
138
  return { included: false, trustLevel: "not-found", blockHeight: proof.block_height, reason: "merkle branch does not fold to the claimed root" };
119
139
  }
@@ -121,7 +141,7 @@ var IndexerClient = class {
121
141
  try {
122
142
  const onchain = await this.opts.headerMerkleAt(proof.block_height);
123
143
  if (norm(onchain) !== norm(proof.merkle_root)) {
124
- return { included: false, trustLevel: "not-found", blockHeight: proof.block_height, reason: "proof root != on-chain header merkle (indexer disagrees with chain)" };
144
+ return { included: false, trustLevel: "not-found", equivocation: true, blockHeight: proof.block_height, reason: "proof root != on-chain header merkle (indexer disagrees with chain)" };
125
145
  }
126
146
  return { included: true, trustLevel: "verified-inclusion", blockHeight: proof.block_height, pos: proof.pos, merkleRoot: proof.merkle_root };
127
147
  } catch {
@@ -145,6 +165,11 @@ var IndexerClient = class {
145
165
  /**
146
166
  * Subscribe over WebSocket with selective tracking. Returns a handle with
147
167
  * `.close()`. `onEvent` receives every matching IndexEvent (incl. reorgs).
168
+ *
169
+ * WS-NORECONNECT-1: unlike the SSE feeds (streamAll/streamBlocks/streamDomain, which auto-reconnect with
170
+ * backoff), this WebSocket subscription does NOT auto-reconnect — it surfaces a terminal error via
171
+ * `onError` (when provided) and stops. A consumer tracking reorgs over WS MUST re-`subscribe()` on error
172
+ * to avoid a silently-stale view; for built-in reconnect prefer the SSE `stream*` methods.
148
173
  */
149
174
  subscribe(sub, onEvent, onError) {
150
175
  const WS = this.opts.WebSocketImpl ?? globalThis.WebSocket;
@@ -207,6 +232,13 @@ var IndexerClient = class {
207
232
  buf = buf.slice(idx + 2);
208
233
  dispatchFrame(frame, handlers);
209
234
  }
235
+ if (buf.length > MAX_SSE_BUF) {
236
+ try {
237
+ await reader.cancel();
238
+ } catch {
239
+ }
240
+ throw new Error(`SSE buffer exceeded ${MAX_SSE_BUF} bytes with no frame delimiter \u2014 aborting`);
241
+ }
210
242
  }
211
243
  } catch (err) {
212
244
  if (closed) return;
@@ -1,4 +1,4 @@
1
- import { H as Http, F as FetchLike } from './http-D2CzfOsm.cjs';
1
+ import { H as Http, F as FetchLike } from './http-CQsNL8w9.cjs';
2
2
 
3
3
  interface MerkleProof {
4
4
  block_height: number;
@@ -14,6 +14,10 @@ interface InclusionResult {
14
14
  pos?: number;
15
15
  merkleRoot?: string;
16
16
  reason?: string;
17
+ /** CAIRN-SPV-3: set true when a folding proof's root conflicts with the PoW-verified on-chain header
18
+ * merkle (active indexer↔chain equivocation/tamper) — distinct from an honest tx-absence. Consumers that
19
+ * retry on `not-found` MUST check this and treat it as a detected attack, NOT a benign miss. */
20
+ equivocation?: boolean;
17
21
  }
18
22
  /** A live index event ({ kind: "block" | "proposal" | "attestation" | "reorg", ... }). */
19
23
  interface IndexEvent {
@@ -71,9 +75,11 @@ declare class IndexerClient {
71
75
  * proof's root is cross-checked against the header merkle at that height →
72
76
  * trustLevel "verified-inclusion" (only as trustworthy as that header source —
73
77
  * see headerMerkleAt). Otherwise the proof is only internally consistent →
74
- * "proof-consistent" (still useful, but the indexer is trusted). The Cairn
75
- * facade wires headerMerkleAt ONLY when the node RPC is an independent origin
76
- * from the indexer, so the default same-origin setup stays at "proof-consistent".
78
+ * "proof-consistent" (still useful, but the indexer is trusted). INDEXER-DOC-STALE-1: the Cairn facade now
79
+ * wires headerMerkleAt UNCONDITIONALLY via a PoW-verifying checkpoint light client (index.ts), so the
80
+ * default reaches "verified-inclusion" when the proof root matches the PoW-verified header — what makes a
81
+ * same-origin RPC safe is the forward PoW re-verification, not an origin gate (which no longer exists).
82
+ * Fails closed: a malformed proof or a chain-disagreement returns "not-found" (the latter with equivocation:true).
77
83
  */
78
84
  verifyInclusion(txid: string): Promise<InclusionResult>;
79
85
  /** Stream all events (blocks, proposals, attestations, reorgs) via SSE. */
@@ -85,6 +91,11 @@ declare class IndexerClient {
85
91
  /**
86
92
  * Subscribe over WebSocket with selective tracking. Returns a handle with
87
93
  * `.close()`. `onEvent` receives every matching IndexEvent (incl. reorgs).
94
+ *
95
+ * WS-NORECONNECT-1: unlike the SSE feeds (streamAll/streamBlocks/streamDomain, which auto-reconnect with
96
+ * backoff), this WebSocket subscription does NOT auto-reconnect — it surfaces a terminal error via
97
+ * `onError` (when provided) and stops. A consumer tracking reorgs over WS MUST re-`subscribe()` on error
98
+ * to avoid a silently-stale view; for built-in reconnect prefer the SSE `stream*` methods.
88
99
  */
89
100
  subscribe(sub: {
90
101
  all?: boolean;
package/dist/indexer.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { H as Http, F as FetchLike } from './http-D2CzfOsm.js';
1
+ import { H as Http, F as FetchLike } from './http-CQsNL8w9.js';
2
2
 
3
3
  interface MerkleProof {
4
4
  block_height: number;
@@ -14,6 +14,10 @@ interface InclusionResult {
14
14
  pos?: number;
15
15
  merkleRoot?: string;
16
16
  reason?: string;
17
+ /** CAIRN-SPV-3: set true when a folding proof's root conflicts with the PoW-verified on-chain header
18
+ * merkle (active indexer↔chain equivocation/tamper) — distinct from an honest tx-absence. Consumers that
19
+ * retry on `not-found` MUST check this and treat it as a detected attack, NOT a benign miss. */
20
+ equivocation?: boolean;
17
21
  }
18
22
  /** A live index event ({ kind: "block" | "proposal" | "attestation" | "reorg", ... }). */
19
23
  interface IndexEvent {
@@ -71,9 +75,11 @@ declare class IndexerClient {
71
75
  * proof's root is cross-checked against the header merkle at that height →
72
76
  * trustLevel "verified-inclusion" (only as trustworthy as that header source —
73
77
  * see headerMerkleAt). Otherwise the proof is only internally consistent →
74
- * "proof-consistent" (still useful, but the indexer is trusted). The Cairn
75
- * facade wires headerMerkleAt ONLY when the node RPC is an independent origin
76
- * from the indexer, so the default same-origin setup stays at "proof-consistent".
78
+ * "proof-consistent" (still useful, but the indexer is trusted). INDEXER-DOC-STALE-1: the Cairn facade now
79
+ * wires headerMerkleAt UNCONDITIONALLY via a PoW-verifying checkpoint light client (index.ts), so the
80
+ * default reaches "verified-inclusion" when the proof root matches the PoW-verified header — what makes a
81
+ * same-origin RPC safe is the forward PoW re-verification, not an origin gate (which no longer exists).
82
+ * Fails closed: a malformed proof or a chain-disagreement returns "not-found" (the latter with equivocation:true).
77
83
  */
78
84
  verifyInclusion(txid: string): Promise<InclusionResult>;
79
85
  /** Stream all events (blocks, proposals, attestations, reorgs) via SSE. */
@@ -85,6 +91,11 @@ declare class IndexerClient {
85
91
  /**
86
92
  * Subscribe over WebSocket with selective tracking. Returns a handle with
87
93
  * `.close()`. `onEvent` receives every matching IndexEvent (incl. reorgs).
94
+ *
95
+ * WS-NORECONNECT-1: unlike the SSE feeds (streamAll/streamBlocks/streamDomain, which auto-reconnect with
96
+ * backoff), this WebSocket subscription does NOT auto-reconnect — it surfaces a terminal error via
97
+ * `onError` (when provided) and stops. A consumer tracking reorgs over WS MUST re-`subscribe()` on error
98
+ * to avoid a silently-stale view; for built-in reconnect prefer the SSE `stream*` methods.
88
99
  */
89
100
  subscribe(sub: {
90
101
  all?: boolean;
package/dist/indexer.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  IndexerClient
3
- } from "./chunk-FNBE2FPE.js";
3
+ } from "./chunk-OG4KA3LS.js";
4
4
  export {
5
5
  IndexerClient
6
6
  };