@inversealtruism/cairn-sdk 0.1.3 → 0.2.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.
Files changed (46) hide show
  1. package/README.md +16 -16
  2. package/dist/board.cjs +1 -1
  3. package/dist/board.d.cts +1 -1
  4. package/dist/board.d.ts +1 -1
  5. package/dist/board.js +2 -2
  6. package/dist/chain.cjs +1 -1
  7. package/dist/chain.d.cts +1 -1
  8. package/dist/chain.d.ts +1 -1
  9. package/dist/chain.js +1 -1
  10. package/dist/{chunk-G7FABIF4.js → chunk-BGJZF7S2.js} +11 -2
  11. package/dist/{chunk-5KAUQH72.js → chunk-C452UODV.js} +1 -1
  12. package/dist/{chunk-FNBE2FPE.js → chunk-J5J4JEJ6.js} +50 -18
  13. package/dist/{chunk-UMGWVOOT.js → chunk-JGXD3WIC.js} +7 -2
  14. package/dist/{chunk-ZAE2BIH5.js → chunk-PYREGAB3.js} +21 -3
  15. package/dist/{chunk-645CYBVN.js → chunk-RYZJDZLJ.js} +1 -1
  16. package/dist/{chunk-OSXS26RA.js → chunk-SIUUY6FZ.js} +4 -3
  17. package/dist/{chunk-G4OZKZSJ.js → chunk-UIEOBVFI.js} +8 -2
  18. package/dist/connect.cjs +14 -3
  19. package/dist/connect.d.cts +7 -0
  20. package/dist/connect.d.ts +7 -0
  21. package/dist/connect.js +2 -2
  22. package/dist/content.cjs +4 -3
  23. package/dist/content.d.cts +1 -1
  24. package/dist/content.d.ts +1 -1
  25. package/dist/content.js +2 -2
  26. package/dist/controller.cjs +34 -5
  27. package/dist/controller.d.cts +1 -0
  28. package/dist/controller.d.ts +1 -0
  29. package/dist/controller.js +3 -3
  30. package/dist/{http-D2CzfOsm.d.cts → http-C9C4L4hz.d.cts} +11 -0
  31. package/dist/{http-D2CzfOsm.d.ts → http-C9C4L4hz.d.ts} +11 -0
  32. package/dist/index.cjs +272 -37
  33. package/dist/index.d.cts +99 -11
  34. package/dist/index.d.ts +99 -11
  35. package/dist/index.js +179 -17
  36. package/dist/indexer.cjs +50 -18
  37. package/dist/indexer.d.cts +15 -4
  38. package/dist/indexer.d.ts +15 -4
  39. package/dist/indexer.js +1 -1
  40. package/dist/react.cjs +34 -5
  41. package/dist/react.js +3 -3
  42. package/dist/registry.cjs +11 -2
  43. package/dist/registry.d.cts +12 -3
  44. package/dist/registry.d.ts +12 -3
  45. package/dist/registry.js +1 -1
  46. package/package.json +32 -23
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 { H as Http, F as FetchLike } from './http-C9C4L4hz.cjs';
2
+ export { a as HttpOptions } from './http-C9C4L4hz.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';
@@ -18,9 +18,95 @@ import '@inversealtruism/csd-crypto';
18
18
  import '@inversealtruism/csd-light';
19
19
  import '@inversealtruism/csd-registry';
20
20
 
21
+ /** `/cairnx/resolve/:name` — the display-path name→address resolution. */
22
+ interface NameResolution {
23
+ ok: boolean;
24
+ name: string;
25
+ /** Resolved recipient address (owner, or the nset target). ABSENT/undefined when not resolvable. */
26
+ addr?: string;
27
+ /** How the address was derived (e.g. "owner", "nset"). */
28
+ via?: string;
29
+ lapsed?: boolean;
30
+ owner?: string;
31
+ tipHeight?: number;
32
+ }
33
+ /** `/cairnx/name/:name` — full name detail (lease, lock, resting offer, recapture window). */
34
+ interface NameDetail {
35
+ name: string;
36
+ owner: string;
37
+ claimId: string;
38
+ height: number;
39
+ effectiveHeight: number;
40
+ locked: boolean;
41
+ paidThroughEpoch: number;
42
+ offer: unknown | null;
43
+ recapture: unknown | null;
44
+ tipHeight: number;
45
+ tipEpoch: number;
46
+ /** v2.5+: a revealed-but-unfinalized registration (no lease yet; not actionable). */
47
+ pending?: boolean;
48
+ lease: {
49
+ leased: boolean;
50
+ paidThroughEpoch: number;
51
+ graceEndEpoch: number;
52
+ epochsLeft: number;
53
+ graceEpochsLeft: number;
54
+ inGrace: boolean;
55
+ lapsed: boolean;
56
+ } | null;
57
+ }
58
+ /** One row of `/cairnx/tokens`. Amounts are DECIMAL STRINGS of base units (may exceed 2^53). */
59
+ interface TokenInfo {
60
+ ticker: string;
61
+ deployId: string;
62
+ deployer: string;
63
+ name?: string;
64
+ decimals: number;
65
+ supply: string;
66
+ minted: string;
67
+ mint?: string;
68
+ mintLimit?: string;
69
+ height: number;
70
+ }
71
+ interface PrimaryName {
72
+ ok: boolean;
73
+ address: string;
74
+ name?: string | null;
75
+ }
76
+ /**
77
+ * Read client for the CairnX convention (names + token market) over a Cairn front door.
78
+ * Construct via the main `Cairn` class (`cairn.names`); all methods are read-only.
79
+ */
80
+ declare class NamesClient {
81
+ private readonly http;
82
+ constructor(http: Http);
83
+ /** Resolve a .csd name for DISPLAY (server-trusted; see the module trust note). null = unknown/invalid name. */
84
+ resolve(name: string): Promise<NameResolution | null>;
85
+ /** Full detail for one name (lease, lock, resting offer, recapture). null = not registered/invalid. */
86
+ name(name: string): Promise<NameDetail | null>;
87
+ /** The event history of one name (registrations, transfers, sets, renewals). null = invalid name. */
88
+ nameHistory(name: string): Promise<unknown[] | null>;
89
+ /** Reverse lookup: an address's chosen primary .csd name (null name = none set). */
90
+ primary(addr: string): Promise<PrimaryName | null>;
91
+ /** All names an address owns (plus its primary), as served by `/cairnx/address/:addr`. */
92
+ namesOf(addr: string): Promise<unknown | null>;
93
+ /** All registered names (display list; short-edge-cached server-side). */
94
+ names(): Promise<unknown[]>;
95
+ /** All deployed tokens. */
96
+ tokens(): Promise<TokenInfo[]>;
97
+ /** One token by ticker. null = unknown/invalid ticker. */
98
+ token(ticker: string): Promise<TokenInfo | null>;
99
+ /** Open offers (the market's display list). */
100
+ offers(): Promise<unknown[]>;
101
+ /** Open bids. */
102
+ bids(): Promise<unknown[]>;
103
+ /** The full canonical CairnX state (large; prefer the scoped reads above). */
104
+ state(): Promise<unknown>;
105
+ }
106
+
21
107
  /** 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.2";
108
+ type CairnErrorCode = "NOT_INSTALLED" | "USER_REJECTED" | "WALLET_LOCKED" | "UNSUPPORTED_METHOD" | "HTTP_ERROR" | "CONTENT_VERIFICATION" | "ACCOUNT_CHANGED" | "FIRST_PARTY_ONLY" | "RATE_LIMITED" | "APPROVAL_CLOSED" | "FORBIDDEN" | "INTERNAL" | "UNKNOWN";
109
+ declare const SDK_VERSION = "0.2.0";
24
110
  interface CairnErrorOptions {
25
111
  code?: CairnErrorCode;
26
112
  docsPath?: string;
@@ -34,7 +120,7 @@ declare class CairnError extends Error {
34
120
  /** Relative docs path (under the SDK docs base), if any. */
35
121
  readonly docsPath?: string;
36
122
  /** The cairn-sdk version that produced this error. */
37
- readonly version = "0.1.2";
123
+ readonly version = "0.2.0";
38
124
  constructor(shortMessage: string, opts?: CairnErrorOptions);
39
125
  /**
40
126
  * Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
@@ -70,11 +156,7 @@ declare class ContentVerificationError extends CairnError {
70
156
  expected: string;
71
157
  constructor(expected: string);
72
158
  }
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;
159
+ declare function mapProviderError(error: string, code?: string): CairnError;
78
160
  /** Convenience: the stable code of any error (or "UNKNOWN" for a non-CairnError). */
79
161
  declare function errorCode(err: unknown): CairnErrorCode;
80
162
 
@@ -122,12 +204,18 @@ interface CairnConfig {
122
204
  hash: string;
123
205
  };
124
206
  }
207
+ declare const DEFAULT_SPV_CHECKPOINT: {
208
+ height: number;
209
+ hash: string;
210
+ };
125
211
  declare class Cairn {
126
212
  readonly config: CairnConfig;
127
213
  readonly chain: Chain;
128
214
  readonly index: IndexerClient;
129
215
  readonly content: ContentClient;
130
216
  readonly registry: RegistryClient;
217
+ /** .csd names + CairnX market READS (server-trusted display reads; see names.ts trust note). */
218
+ readonly names: NamesClient;
131
219
  board: BoardClient;
132
220
  wallet?: WalletConnection;
133
221
  private readonly cairnHttp;
@@ -155,4 +243,4 @@ declare class Cairn {
155
243
  get connected(): boolean;
156
244
  }
157
245
 
158
- export { BoardClient, Cairn, type CairnBaseUrls, type CairnConfig, CairnError, type CairnErrorCode, type CairnErrorOptions, Chain, ContentClient, ContentVerificationError, DetectOptions, FetchLike, HttpError, IndexerClient, NotInstalledError, RegistryClient, SDK_VERSION, UnsupportedMethodError, UserRejectedError, WalletConnection, WalletLockedError, errorCode, mapProviderError };
246
+ export { BoardClient, Cairn, type CairnBaseUrls, type CairnConfig, CairnError, type CairnErrorCode, type CairnErrorOptions, Chain, ContentClient, ContentVerificationError, DEFAULT_SPV_CHECKPOINT, DetectOptions, FetchLike, Http, HttpError, IndexerClient, type NameDetail, type NameResolution, NamesClient, NotInstalledError, type PrimaryName, RegistryClient, SDK_VERSION, type TokenInfo, 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 { H as Http, F as FetchLike } from './http-C9C4L4hz.js';
2
+ export { a as HttpOptions } from './http-C9C4L4hz.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';
@@ -18,9 +18,95 @@ import '@inversealtruism/csd-crypto';
18
18
  import '@inversealtruism/csd-light';
19
19
  import '@inversealtruism/csd-registry';
20
20
 
21
+ /** `/cairnx/resolve/:name` — the display-path name→address resolution. */
22
+ interface NameResolution {
23
+ ok: boolean;
24
+ name: string;
25
+ /** Resolved recipient address (owner, or the nset target). ABSENT/undefined when not resolvable. */
26
+ addr?: string;
27
+ /** How the address was derived (e.g. "owner", "nset"). */
28
+ via?: string;
29
+ lapsed?: boolean;
30
+ owner?: string;
31
+ tipHeight?: number;
32
+ }
33
+ /** `/cairnx/name/:name` — full name detail (lease, lock, resting offer, recapture window). */
34
+ interface NameDetail {
35
+ name: string;
36
+ owner: string;
37
+ claimId: string;
38
+ height: number;
39
+ effectiveHeight: number;
40
+ locked: boolean;
41
+ paidThroughEpoch: number;
42
+ offer: unknown | null;
43
+ recapture: unknown | null;
44
+ tipHeight: number;
45
+ tipEpoch: number;
46
+ /** v2.5+: a revealed-but-unfinalized registration (no lease yet; not actionable). */
47
+ pending?: boolean;
48
+ lease: {
49
+ leased: boolean;
50
+ paidThroughEpoch: number;
51
+ graceEndEpoch: number;
52
+ epochsLeft: number;
53
+ graceEpochsLeft: number;
54
+ inGrace: boolean;
55
+ lapsed: boolean;
56
+ } | null;
57
+ }
58
+ /** One row of `/cairnx/tokens`. Amounts are DECIMAL STRINGS of base units (may exceed 2^53). */
59
+ interface TokenInfo {
60
+ ticker: string;
61
+ deployId: string;
62
+ deployer: string;
63
+ name?: string;
64
+ decimals: number;
65
+ supply: string;
66
+ minted: string;
67
+ mint?: string;
68
+ mintLimit?: string;
69
+ height: number;
70
+ }
71
+ interface PrimaryName {
72
+ ok: boolean;
73
+ address: string;
74
+ name?: string | null;
75
+ }
76
+ /**
77
+ * Read client for the CairnX convention (names + token market) over a Cairn front door.
78
+ * Construct via the main `Cairn` class (`cairn.names`); all methods are read-only.
79
+ */
80
+ declare class NamesClient {
81
+ private readonly http;
82
+ constructor(http: Http);
83
+ /** Resolve a .csd name for DISPLAY (server-trusted; see the module trust note). null = unknown/invalid name. */
84
+ resolve(name: string): Promise<NameResolution | null>;
85
+ /** Full detail for one name (lease, lock, resting offer, recapture). null = not registered/invalid. */
86
+ name(name: string): Promise<NameDetail | null>;
87
+ /** The event history of one name (registrations, transfers, sets, renewals). null = invalid name. */
88
+ nameHistory(name: string): Promise<unknown[] | null>;
89
+ /** Reverse lookup: an address's chosen primary .csd name (null name = none set). */
90
+ primary(addr: string): Promise<PrimaryName | null>;
91
+ /** All names an address owns (plus its primary), as served by `/cairnx/address/:addr`. */
92
+ namesOf(addr: string): Promise<unknown | null>;
93
+ /** All registered names (display list; short-edge-cached server-side). */
94
+ names(): Promise<unknown[]>;
95
+ /** All deployed tokens. */
96
+ tokens(): Promise<TokenInfo[]>;
97
+ /** One token by ticker. null = unknown/invalid ticker. */
98
+ token(ticker: string): Promise<TokenInfo | null>;
99
+ /** Open offers (the market's display list). */
100
+ offers(): Promise<unknown[]>;
101
+ /** Open bids. */
102
+ bids(): Promise<unknown[]>;
103
+ /** The full canonical CairnX state (large; prefer the scoped reads above). */
104
+ state(): Promise<unknown>;
105
+ }
106
+
21
107
  /** 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.2";
108
+ type CairnErrorCode = "NOT_INSTALLED" | "USER_REJECTED" | "WALLET_LOCKED" | "UNSUPPORTED_METHOD" | "HTTP_ERROR" | "CONTENT_VERIFICATION" | "ACCOUNT_CHANGED" | "FIRST_PARTY_ONLY" | "RATE_LIMITED" | "APPROVAL_CLOSED" | "FORBIDDEN" | "INTERNAL" | "UNKNOWN";
109
+ declare const SDK_VERSION = "0.2.0";
24
110
  interface CairnErrorOptions {
25
111
  code?: CairnErrorCode;
26
112
  docsPath?: string;
@@ -34,7 +120,7 @@ declare class CairnError extends Error {
34
120
  /** Relative docs path (under the SDK docs base), if any. */
35
121
  readonly docsPath?: string;
36
122
  /** The cairn-sdk version that produced this error. */
37
- readonly version = "0.1.2";
123
+ readonly version = "0.2.0";
38
124
  constructor(shortMessage: string, opts?: CairnErrorOptions);
39
125
  /**
40
126
  * Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
@@ -70,11 +156,7 @@ declare class ContentVerificationError extends CairnError {
70
156
  expected: string;
71
157
  constructor(expected: string);
72
158
  }
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;
159
+ declare function mapProviderError(error: string, code?: string): CairnError;
78
160
  /** Convenience: the stable code of any error (or "UNKNOWN" for a non-CairnError). */
79
161
  declare function errorCode(err: unknown): CairnErrorCode;
80
162
 
@@ -122,12 +204,18 @@ interface CairnConfig {
122
204
  hash: string;
123
205
  };
124
206
  }
207
+ declare const DEFAULT_SPV_CHECKPOINT: {
208
+ height: number;
209
+ hash: string;
210
+ };
125
211
  declare class Cairn {
126
212
  readonly config: CairnConfig;
127
213
  readonly chain: Chain;
128
214
  readonly index: IndexerClient;
129
215
  readonly content: ContentClient;
130
216
  readonly registry: RegistryClient;
217
+ /** .csd names + CairnX market READS (server-trusted display reads; see names.ts trust note). */
218
+ readonly names: NamesClient;
131
219
  board: BoardClient;
132
220
  wallet?: WalletConnection;
133
221
  private readonly cairnHttp;
@@ -155,4 +243,4 @@ declare class Cairn {
155
243
  get connected(): boolean;
156
244
  }
157
245
 
158
- export { BoardClient, Cairn, type CairnBaseUrls, type CairnConfig, CairnError, type CairnErrorCode, type CairnErrorOptions, Chain, ContentClient, ContentVerificationError, DetectOptions, FetchLike, HttpError, IndexerClient, NotInstalledError, RegistryClient, SDK_VERSION, UnsupportedMethodError, UserRejectedError, WalletConnection, WalletLockedError, errorCode, mapProviderError };
246
+ export { BoardClient, Cairn, type CairnBaseUrls, type CairnConfig, CairnError, type CairnErrorCode, type CairnErrorOptions, Chain, ContentClient, ContentVerificationError, DEFAULT_SPV_CHECKPOINT, DetectOptions, FetchLike, Http, HttpError, IndexerClient, type NameDetail, type NameResolution, NamesClient, NotInstalledError, type PrimaryName, RegistryClient, SDK_VERSION, type TokenInfo, UnsupportedMethodError, UserRejectedError, WalletConnection, WalletLockedError, errorCode, mapProviderError };
package/dist/index.js CHANGED
@@ -1,22 +1,22 @@
1
1
  import {
2
2
  RegistryClient
3
- } from "./chunk-G7FABIF4.js";
3
+ } from "./chunk-BGJZF7S2.js";
4
4
  import {
5
5
  BoardClient
6
- } from "./chunk-645CYBVN.js";
6
+ } from "./chunk-RYZJDZLJ.js";
7
7
  import {
8
8
  Chain,
9
9
  LightClient
10
- } from "./chunk-5KAUQH72.js";
10
+ } from "./chunk-C452UODV.js";
11
11
  import {
12
12
  ContentClient
13
- } from "./chunk-OSXS26RA.js";
13
+ } from "./chunk-SIUUY6FZ.js";
14
14
  import {
15
15
  IndexerClient
16
- } from "./chunk-FNBE2FPE.js";
16
+ } from "./chunk-J5J4JEJ6.js";
17
17
  import {
18
18
  CairnController
19
- } from "./chunk-ZAE2BIH5.js";
19
+ } from "./chunk-PYREGAB3.js";
20
20
  import {
21
21
  WalletConnection,
22
22
  connect,
@@ -24,7 +24,7 @@ import {
24
24
  discoverProviders,
25
25
  getWallet,
26
26
  isInstalled
27
- } from "./chunk-G4OZKZSJ.js";
27
+ } from "./chunk-UIEOBVFI.js";
28
28
  import {
29
29
  CairnError,
30
30
  ContentVerificationError,
@@ -36,26 +36,71 @@ import {
36
36
  WalletLockedError,
37
37
  errorCode,
38
38
  mapProviderError
39
- } from "./chunk-UMGWVOOT.js";
39
+ } from "./chunk-JGXD3WIC.js";
40
40
 
41
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
+ }
42
79
  var Http = class {
43
80
  baseUrl;
44
81
  _fetch;
45
82
  timeoutMs;
83
+ maxBytes;
46
84
  headers;
85
+ retries;
47
86
  constructor(opts) {
48
87
  this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
49
88
  const f = opts.fetch ?? globalThis.fetch;
50
89
  if (!f) throw new Error("No fetch available \u2014 pass `fetch` in the SDK options.");
51
90
  this._fetch = opts.fetch ? opts.fetch : f.bind(globalThis);
52
91
  this.timeoutMs = opts.timeoutMs ?? 15e3;
92
+ this.maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
53
93
  this.headers = opts.headers ?? {};
94
+ this.retries = Math.min(10, Math.max(0, Math.floor(opts.retries ?? 0)));
54
95
  }
55
96
  /** Build an absolute URL from a path + optional query params. */
56
97
  url(path, query) {
57
98
  const p = path.startsWith("/") ? path : `/${path}`;
58
99
  const u = new URL(this.baseUrl + p);
100
+ const basePath = new URL(this.baseUrl + "/").pathname;
101
+ if (basePath !== "/" && !(u.pathname + "/").startsWith(basePath)) {
102
+ throw new Error(`illegal path '${path}' \u2014 escapes base ${basePath}`);
103
+ }
59
104
  if (query) {
60
105
  for (const [k, v] of Object.entries(query)) {
61
106
  if (v !== void 0 && v !== null) u.searchParams.set(k, String(v));
@@ -83,35 +128,147 @@ var Http = class {
83
128
  clearTimeout(timer);
84
129
  }
85
130
  }
131
+ // A malformed 2xx body must surface as a TYPED HttpError, not a leaked SyntaxError (B10: dApp
132
+ // code branches on `instanceof CairnError`; a raw parse error escaped that contract).
133
+ parseJson(res, text, url) {
134
+ try {
135
+ return JSON.parse(text);
136
+ } catch (e) {
137
+ throw new HttpError(res.status, url, `invalid JSON body: ${text.slice(0, 120)}`);
138
+ }
139
+ }
86
140
  async getJson(path, query) {
87
- const res = await this.raw("GET", path, { query });
88
- if (!res.ok) throw new HttpError(res.status, this.url(path, query), await safeText(res));
89
- return await res.json();
141
+ const url = this.url(path, query);
142
+ for (let attempt = 0; ; attempt++) {
143
+ try {
144
+ const res = await this.raw("GET", path, { query });
145
+ if (!res.ok) {
146
+ const err = new HttpError(res.status, url, await safeText(res));
147
+ if (!(res.status >= 500 && attempt < this.retries)) throw err;
148
+ } else {
149
+ return this.parseJson(res, new TextDecoder().decode(await readCapped(res, this.maxBytes)), url);
150
+ }
151
+ } catch (e) {
152
+ if (e instanceof HttpError && e.status < 500) throw e;
153
+ if (attempt >= this.retries) throw e;
154
+ }
155
+ await new Promise((r) => setTimeout(r, Math.floor(Math.random() * Math.min(4e3, 250 * 2 ** attempt))));
156
+ }
90
157
  }
91
158
  async postJson(path, body, headers) {
159
+ const url = this.url(path);
92
160
  const res = await this.raw("POST", path, { body, headers });
93
- if (!res.ok) throw new HttpError(res.status, this.url(path), await safeText(res));
94
- return await res.json();
161
+ if (!res.ok) throw new HttpError(res.status, url, await safeText(res));
162
+ return this.parseJson(res, new TextDecoder().decode(await readCapped(res, this.maxBytes)), url);
95
163
  }
96
164
  /** GET raw bytes (for content retrieval). Returns null on 404. */
97
165
  async getBytes(path) {
98
166
  const res = await this.raw("GET", path);
99
167
  if (res.status === 404) return null;
100
168
  if (!res.ok) throw new HttpError(res.status, this.url(path), await safeText(res));
101
- const buf = new Uint8Array(await res.arrayBuffer());
102
- return { bytes: buf, headers: res.headers };
169
+ return { bytes: await readCapped(res, this.maxBytes), headers: res.headers };
103
170
  }
104
171
  };
105
172
  async function safeText(res) {
106
173
  try {
107
- return await res.text();
174
+ return new TextDecoder().decode(await readCapped(res, ERR_TEXT_CAP));
108
175
  } catch {
109
176
  return "";
110
177
  }
111
178
  }
112
179
 
180
+ // src/names.ts
181
+ import { NAME_RE, RESERVED_NAMES } from "@inversealtruism/cairnx-core";
182
+ var nameOk = (n) => typeof n === "string" && NAME_RE.test(n) && !RESERVED_NAMES.has(n);
183
+ var NamesClient = class {
184
+ constructor(http) {
185
+ this.http = http;
186
+ }
187
+ http;
188
+ /** Resolve a .csd name for DISPLAY (server-trusted; see the module trust note). null = unknown/invalid name. */
189
+ async resolve(name) {
190
+ if (!nameOk(name)) return null;
191
+ try {
192
+ return await this.http.getJson(`/trade/api/cairnx/resolve/${name}`);
193
+ } catch (e) {
194
+ if (e?.status === 404) return null;
195
+ throw e;
196
+ }
197
+ }
198
+ /** Full detail for one name (lease, lock, resting offer, recapture). null = not registered/invalid. */
199
+ async name(name) {
200
+ if (!nameOk(name)) return null;
201
+ try {
202
+ return await this.http.getJson(`/trade/api/cairnx/name/${name}`);
203
+ } catch (e) {
204
+ if (e?.status === 404) return null;
205
+ throw e;
206
+ }
207
+ }
208
+ /** The event history of one name (registrations, transfers, sets, renewals). null = invalid name. */
209
+ async nameHistory(name) {
210
+ if (!nameOk(name)) return null;
211
+ try {
212
+ return await this.http.getJson(`/trade/api/cairnx/name-history/${name}`);
213
+ } catch (e) {
214
+ if (e?.status === 404) return null;
215
+ throw e;
216
+ }
217
+ }
218
+ /** Reverse lookup: an address's chosen primary .csd name (null name = none set). */
219
+ async primary(addr) {
220
+ if (typeof addr !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(addr)) return null;
221
+ try {
222
+ return await this.http.getJson(`/trade/api/cairnx/primary/${addr.toLowerCase()}`);
223
+ } catch (e) {
224
+ if (e?.status === 404) return null;
225
+ throw e;
226
+ }
227
+ }
228
+ /** All names an address owns (plus its primary), as served by `/cairnx/address/:addr`. */
229
+ async namesOf(addr) {
230
+ if (typeof addr !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(addr)) return null;
231
+ try {
232
+ return await this.http.getJson(`/trade/api/cairnx/address/${addr.toLowerCase()}`);
233
+ } catch (e) {
234
+ if (e?.status === 404) return null;
235
+ throw e;
236
+ }
237
+ }
238
+ /** All registered names (display list; short-edge-cached server-side). */
239
+ names() {
240
+ return this.http.getJson(`/trade/api/cairnx/names`);
241
+ }
242
+ /** All deployed tokens. */
243
+ tokens() {
244
+ return this.http.getJson(`/trade/api/cairnx/tokens`);
245
+ }
246
+ /** One token by ticker. null = unknown/invalid ticker. */
247
+ async token(ticker) {
248
+ if (typeof ticker !== "string" || !/^[A-Z][A-Z0-9]{2,11}$/.test(ticker)) return null;
249
+ try {
250
+ return await this.http.getJson(`/trade/api/cairnx/token/${ticker}`);
251
+ } catch (e) {
252
+ if (e?.status === 404) return null;
253
+ throw e;
254
+ }
255
+ }
256
+ /** Open offers (the market's display list). */
257
+ offers() {
258
+ return this.http.getJson(`/trade/api/cairnx/offers`);
259
+ }
260
+ /** Open bids. */
261
+ bids() {
262
+ return this.http.getJson(`/trade/api/cairnx/bids`);
263
+ }
264
+ /** The full canonical CairnX state (large; prefer the scoped reads above). */
265
+ state() {
266
+ return this.http.getJson(`/trade/api/cairnx/state`);
267
+ }
268
+ };
269
+
113
270
  // src/index.ts
114
- var DEFAULT_SPV_CHECKPOINT = { height: 31310, hash: "0x000000000000138bd3eee4b8d2fbacb8d5433ac3040ddbf1c45a5e3e0cc9e814" };
271
+ var DEFAULT_SPV_CHECKPOINT = { height: 38142, hash: "0x00000000000140f023cc0ee1457a40833f2fcb4de44291b9d373e50f17b97232" };
115
272
  var DEFAULT_CAIRN = "https://cairn-substrate.com";
116
273
  var Cairn = class {
117
274
  config;
@@ -119,6 +276,8 @@ var Cairn = class {
119
276
  index;
120
277
  content;
121
278
  registry;
279
+ /** .csd names + CairnX market READS (server-trusted display reads; see names.ts trust note). */
280
+ names;
122
281
  board;
123
282
  wallet;
124
283
  cairnHttp;
@@ -147,6 +306,7 @@ var Cairn = class {
147
306
  });
148
307
  this.content = new ContentClient({ swarm: swarmHttp, indexer: indexerHttp, cairn: this.cairnHttp });
149
308
  this.registry = new RegistryClient({ baseUrl: indexer, fetch });
309
+ this.names = new NamesClient(this.cairnHttp);
150
310
  }
151
311
  /**
152
312
  * PoW-VERIFIED header merkle at a height (audit M3 — the no-PoW half). Lazily seeds a light
@@ -198,9 +358,11 @@ export {
198
358
  Chain,
199
359
  ContentClient,
200
360
  ContentVerificationError,
361
+ DEFAULT_SPV_CHECKPOINT,
201
362
  Http,
202
363
  HttpError,
203
364
  IndexerClient,
365
+ NamesClient,
204
366
  NotInstalledError,
205
367
  RegistryClient,
206
368
  SDK_VERSION,