@inversealtruism/cairn-sdk 0.1.4 → 0.2.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @inversealtruism/cairn-sdk
2
2
 
3
- The **Compute Substrate dApp kit** — one cohesive toolkit for building apps on CSD. It composes everything in the Cairn ecosystem behind a single `Cairn` object:
3
+ The **Compute Substrate dApp kit:** one cohesive toolkit for building apps on CSD. It composes everything in the Cairn ecosystem behind a single `Cairn` object:
4
4
 
5
5
  | | |
6
6
  |---|---|
@@ -8,8 +8,9 @@ The **Compute Substrate dApp kit** — one cohesive toolkit for building apps on
8
8
  | `cairn.chain` | Node RPC + transaction builders + a verifying **light client** (re-exports the `csd-*` primitives). |
9
9
  | `cairn.board` | The signal **board** + work graph. Reads are public; writes are wallet-signed. |
10
10
  | `cairn.index` | The L2 **explorer/indexer**: REST + merkle proofs + live SSE/WS feeds. |
11
- | `cairn.content` | **Self-certifying content** — publish via the board, fetch with client-side verification. |
11
+ | `cairn.content` | **Self-certifying content:** publish via the board, fetch with client-side verification. |
12
12
  | `cairn.registry` | L3 **peer / gateway / identity** resolution. |
13
+ | `cairn.names` | **.csd names + CairnX reads:** name resolution, reverse/primary lookup, tokens, offers, bids, canonical state. |
13
14
 
14
15
  ```bash
15
16
  npm i @inversealtruism/cairn-sdk
@@ -26,7 +27,7 @@ const cairn = new Cairn({ network: "mainnet" }); // defaults to cairn-substrat
26
27
  const top = await cairn.board.top({ domain: "csd:apps" });
27
28
  const tip = await cairn.chain.tip();
28
29
 
29
- // connect the user's wallet (browser) — prompts once, then "connected"
30
+ // connect the user's wallet (browser): prompts once, then "connected"
30
31
  await cairn.connect();
31
32
 
32
33
  // publish a board item, signed by the user's wallet (clear-signed approval)
@@ -42,17 +43,17 @@ cairn.index.streamAll({ onProposal: (e) => console.log("new proposal", e) });
42
43
 
43
44
  ## How wallet connection works (and why it's safe)
44
45
 
45
- The SDK follows the exact model MetaMask uses — there is **no allowlist you maintain**:
46
+ The SDK follows the exact model MetaMask uses, there is **no allowlist you maintain**:
46
47
 
47
48
  - **`connect()` / `getAddress()`** grant *address visibility*. The first time a site connects, the user approves once; after that the wallet answers silently (the site shows up under **Settings → Connected sites**, revocable anytime). This is the only "silent" call.
48
- - **Every signing / fund-moving call** — `send`, `propose`, `attest`, `signIn`, `sealClaim`, `revealClaim` — **always** opens the wallet's clear-signing approval window, **every time**, no matter what. Being "connected" never pre-approves a signature. A connected site cannot move a satoshi without an explicit, fully-disclosed approval click.
49
+ - **Every signing / fund-moving call:** `send`, `propose`, `attest`, `signIn`, `sealClaim`, `revealClaim`, **always** opens the wallet's clear-signing approval window, **every time**, no matter what. Being "connected" never pre-approves a signature. A connected site cannot move a satoshi without an explicit, fully-disclosed approval click.
49
50
 
50
51
  ```ts
51
52
  import { connect, UserRejectedError, NotInstalledError } from "@inversealtruism/cairn-sdk";
52
53
 
53
54
  try {
54
55
  const { wallet, address } = await connect();
55
- await wallet.send({ to: "0x…", amount: 100_000_000 }); // 1 CSD — still prompts
56
+ await wallet.send({ to: "0x…", amount: 100_000_000 }); // 1 CSD, still prompts
56
57
  } catch (e) {
57
58
  if (e instanceof NotInstalledError) { /* tell the user to install the wallet */ }
58
59
  if (e instanceof UserRejectedError) { /* they declined */ }
@@ -61,30 +62,32 @@ try {
61
62
 
62
63
  ## API by namespace
63
64
 
64
- **`cairn.board`** — `top()/board({domain,window})`, `item(id)`, `domains()`, `quests()/quest(id)`, `profile(addr)`, `leaderboard()`, `network()/networkSeries()/miner(addr)`, `activity()`, `wall()`; writes `propose({domain,title,body,links?,fee?})`, `support(id,{score?,confidence?,fee?})`.
65
+ **`cairn.board`:** `top()/board({domain,window})`, `item(id)`, `domains()`, `quests()/quest(id)`, `profile(addr)`, `leaderboard()`, `network()/networkSeries()/miner(addr)`, `activity()`, `wall()`; writes `propose({domain,title,body,links?,fee?})`, `support(id,{score?,confidence?,fee?})`.
65
66
 
66
- **`cairn.index`** — `tipHeight()`, `block()/blockTxids()`, `tx()/txStatus()/txMerkleProof()`, `address()/addressTxs()/addressUtxo()`, `proposal()/attestations()`, `reputation()`, `registryPeers()/registryGateways()/identity()`, **`verifyInclusion(txid)`** (trust-minimized), `streamAll()/streamBlocks()/streamDomain()` (SSE), `subscribe()` (WS).
67
+ **`cairn.index`:** `tipHeight()`, `block()/blockTxids()`, `tx()/txStatus()/txMerkleProof()`, `address()/addressTxs()/addressUtxo()`, `proposal()/attestations()`, `reputation()`, `registryPeers()/registryGateways()/identity()`, **`verifyInclusion(txid)`** (trust-minimized), `streamAll()/streamBlocks()/streamDomain()` (SSE), `subscribe()` (WS).
67
68
 
68
- **`cairn.content`** — `prepare(obj)`/`put(obj)` → `{ payloadHash, canonical, bytes }`; `get(hash)` / `getBytes(hash)` (fetch **and verify** `sha256(bytes) === hash`, throws `ContentVerificationError` on tamper); `hash(obj)`.
69
+ **`cairn.names`:** `resolve(name)` (display-path name to address), `name(name)`/`nameHistory(name)`, `primary(addr)` (reverse lookup)/`namesOf(addr)`, `names()`, `tokens()/token(ticker)`, `offers()`, `bids()`, `state()` (the canonical CairnX state). Reads go through the public `/trade/api/cairnx/*` proxy; for payment-grade name resolution verify through the wallet or a replaying resolver, not a bare read (see `src/names.ts` header).
69
70
 
70
- **`cairn.chain`** — `tip()`, `utxos(addr)`, `submit(nodeJsonTx)`, `.client` (full `CsdClient`), `.light()` (verifying light client). The package also re-exports the primitives under the `/chain` subpath: `buildSend/buildPropose/buildAttest`, `signTx`, `keygen`, `payloadHash`, `verifyMerkleProof`, `LightClient`, etc.
71
+ **`cairn.content`:** `prepare(obj)`/`put(obj)` → `{ payloadHash, canonical, bytes }`; `get(hash)` / `getBytes(hash)` (fetch **and verify** `sha256(bytes) === hash`, throws `ContentVerificationError` on tamper); `hash(obj)`.
71
72
 
72
- **`cairn.registry`** — `gateways()`, `peers()`, `resolveName(handle)`, `reverseName(addr)`, `.fromRecords` (deterministic local resolvers).
73
+ **`cairn.chain`:** `tip()`, `utxos(addr)`, `submit(nodeJsonTx)`, `.client` (full `CsdClient`), `.light()` (verifying light client). The package also re-exports the primitives under the `/chain` subpath: `buildSend/buildPropose/buildAttest`, `signTx`, `keygen`, `payloadHash`, `verifyMerkleProof`, `LightClient`, etc.
74
+
75
+ **`cairn.registry`:** `gateways()`, `peers()`, `resolveName(handle)`, `reverseName(addr)`, `.fromRecords` (deterministic local resolvers).
73
76
 
74
77
  Subpath imports are available for tree-shaking: `@inversealtruism/cairn-sdk/{connect,board,indexer,content,registry,chain}`.
75
78
 
76
79
  ## Examples
77
80
 
78
- - **`examples/read-only.mjs`** — runs today against live mainnet, no wallet, no build: `npm run test:live`. Demonstrates chain/board/indexer/registry reads + verified merkle inclusion + verified content fetch.
79
- - **`examples/hello-csd/`** — a no-framework browser dApp (connect → read board → publish → live feed). Build the bundle with `npm run build:example`, then serve the folder from **localhost** (the wallet injects there) with the Cairn Wallet installed.
81
+ - **`examples/read-only.mjs`:** runs today against live mainnet, no wallet, no build: `npm run test:live`. Demonstrates chain/board/indexer/registry reads + verified merkle inclusion + verified content fetch.
82
+ - **`examples/hello-csd/`:** a no-framework browser dApp (connect → read board → publish → live feed). Build the bundle with `npm run build:example`, then serve the folder from **localhost** (the wallet injects there) with the Cairn Wallet installed.
80
83
 
81
84
  ## Trust model & honest limits
82
85
 
83
- - **Content is always verified client-side.** `cairn.content.get()` rejects bytes that don't hash to the requested `payloadHash` — an untrusted gateway can't serve tampered content.
84
- - **Merkle inclusion is trust-minimized — to the strength of your header source.** `verifyInclusion()` folds the proof and, when a header-merkle source is wired, cross-checks the root against it → `trustLevel: "verified-inclusion"`; otherwise `"proof-consistent"` (indexer trusted). That cross-check only means something against an *independent* header, so the default `Cairn` wiring enables it ONLY when your node RPC is a different origin than the indexer (the default same-origin proxy stays at `"proof-consistent"` — a single compromised server can't be cross-checked against itself). For full trust-minimization against a lying node too, supply a PoW-verifying `headerMerkleAt` (e.g. backed by `cairn.chain.light()`).
86
+ - **Content is always verified client-side.** `cairn.content.get()` rejects bytes that don't hash to the requested `payloadHash`, an untrusted gateway can't serve tampered content.
87
+ - **Merkle inclusion is trust-minimized, to the strength of your header source.** `verifyInclusion()` folds the proof and, when a header-merkle source is wired, cross-checks the root against it → `trustLevel: "verified-inclusion"`; otherwise `"proof-consistent"` (indexer trusted). That cross-check only means something against an *independent* header, so the default `Cairn` wiring enables it ONLY when your node RPC is a different origin than the indexer (the default same-origin proxy stays at `"proof-consistent"`, a single compromised server can't be cross-checked against itself). For full trust-minimization against a lying node too, supply a PoW-verifying `headerMerkleAt` (e.g. backed by `cairn.chain.light()`).
85
88
  - **Balances are RPC-trusted.** Headers don't commit to the UTXO set; balance reads trust the node/proxy (same as every light client).
86
- - **Indexer/swarm run behind the cairn proxy.** By default `cairn.index` and content resolution use the cairn server's hardened, read-only, rate-limited `/explorer/api` reverse proxy (no separate public indexer needed). **SSE works through it; `subscribe()` (WebSocket) needs a direct indexer endpoint** — set `baseUrls.indexer` to a private/localhost indexer for WS.
87
- - **Today the wallet injects only on `cairn-substrate.com` + localhost.** Connecting from an arbitrary third-party origin requires the wallet's "connect anywhere" release (broadened injection), which ships after the per-origin consent layer is proven.
89
+ - **Indexer/swarm run behind the cairn proxy.** By default `cairn.index` and content resolution use the cairn server's hardened, read-only, rate-limited `/explorer/api` reverse proxy (no separate public indexer needed). **SSE works through it; `subscribe()` (WebSocket) needs a direct indexer endpoint:** set `baseUrls.indexer` to a private/localhost indexer for WS.
90
+ - **The wallet injects on every site** (connect-anywhere shipped in 0.2.4x) behind a per-origin consent gate: a site sees nothing until the user approves the connection, and some request kinds stay first-party-only (the `FIRST_PARTY_ONLY` error code).
88
91
 
89
92
  ## Configuration
90
93
 
package/dist/board.cjs CHANGED
@@ -27,7 +27,7 @@ var import_csd_codec = require("@inversealtruism/csd-codec");
27
27
  var import_csd_codec2 = require("@inversealtruism/csd-codec");
28
28
 
29
29
  // src/errors.ts
30
- var SDK_VERSION = "0.1.4";
30
+ var SDK_VERSION = "0.2.1";
31
31
  var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
32
32
  var CairnError = class extends Error {
33
33
  /** Stable machine code for programmatic branching. */
package/dist/board.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { H as Http } from './http-CQsNL8w9.cjs';
1
+ import { H as Http } from './http-C9C4L4hz.cjs';
2
2
  import { WalletConnection, TxResult } from './connect.cjs';
3
3
 
4
4
  type BoardWindow = "24h" | "7d" | "30d" | "all";
package/dist/board.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { H as Http } from './http-CQsNL8w9.js';
1
+ import { H as Http } from './http-C9C4L4hz.js';
2
2
  import { WalletConnection, TxResult } from './connect.js';
3
3
 
4
4
  type BoardWindow = "24h" | "7d" | "30d" | "all";
package/dist/board.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  BoardClient
3
- } from "./chunk-FSQZVNEM.js";
4
- import "./chunk-RELP23CH.js";
3
+ } from "./chunk-7LQKND43.js";
4
+ import "./chunk-IYQEEMV2.js";
5
5
  export {
6
6
  BoardClient
7
7
  };
package/dist/chain.d.cts CHANGED
@@ -7,7 +7,7 @@ export { App, BlockHeader, Tx, TxInput, TxOutput, canonicalJson, deserialize, hb
7
7
  export { ADDR_RE, addrFromPriv, addrFromPub, hash160, isValidAddr, isValidPriv, keygen, pubFromPriv, randomNonce, signDigest, verifyDigest } from '@inversealtruism/csd-crypto';
8
8
  import { LightClient } from '@inversealtruism/csd-light';
9
9
  export { InclusionResult, LightClient, ReorgResult, TrustLevel, VerifiedHeader, expectedBits, expectedBitsFromWindow } from '@inversealtruism/csd-light';
10
- import { F as FetchLike } from './http-CQsNL8w9.cjs';
10
+ import { F as FetchLike } from './http-C9C4L4hz.cjs';
11
11
 
12
12
  interface ChainOptions {
13
13
  /** Node RPC base URL — defaults to the public Cairn proxy (no local node needed). */
package/dist/chain.d.ts CHANGED
@@ -7,7 +7,7 @@ export { App, BlockHeader, Tx, TxInput, TxOutput, canonicalJson, deserialize, hb
7
7
  export { ADDR_RE, addrFromPriv, addrFromPub, hash160, isValidAddr, isValidPriv, keygen, pubFromPriv, randomNonce, signDigest, verifyDigest } from '@inversealtruism/csd-crypto';
8
8
  import { LightClient } from '@inversealtruism/csd-light';
9
9
  export { InclusionResult, LightClient, ReorgResult, TrustLevel, VerifiedHeader, expectedBits, expectedBitsFromWindow } from '@inversealtruism/csd-light';
10
- import { F as FetchLike } from './http-CQsNL8w9.js';
10
+ import { F as FetchLike } from './http-C9C4L4hz.js';
11
11
 
12
12
  interface ChainOptions {
13
13
  /** Node RPC base URL — defaults to the public Cairn proxy (no local node needed). */
@@ -2,7 +2,7 @@ import {
2
2
  NotInstalledError,
3
3
  UnsupportedMethodError,
4
4
  mapProviderError
5
- } from "./chunk-RELP23CH.js";
5
+ } from "./chunk-IYQEEMV2.js";
6
6
 
7
7
  // src/connect.ts
8
8
  function discoverProviders(opts = {}) {
@@ -72,7 +72,7 @@ function isInstalled() {
72
72
  }
73
73
  function unwrap(reply) {
74
74
  if (reply && reply.ok) return reply.result;
75
- throw mapProviderError(reply?.error ?? "Unknown wallet error");
75
+ throw mapProviderError(reply?.error ?? "Unknown wallet error", reply?.code);
76
76
  }
77
77
  var WalletConnection = class {
78
78
  provider;
@@ -106,6 +106,9 @@ var WalletConnection = class {
106
106
  * Passwordless sign-in: a signature over a login nonce (always prompts; structurally cannot sign a tx).
107
107
  */
108
108
  signIn() {
109
+ if (typeof this.provider.signIn !== "function") {
110
+ return Promise.reject(new UnsupportedMethodError("this wallet does not expose signIn()"));
111
+ }
109
112
  return Promise.resolve(this.provider.signIn()).then(unwrap);
110
113
  }
111
114
  /**
@@ -139,6 +142,9 @@ var WalletConnection = class {
139
142
  }
140
143
  /** Atomic fill (CairnX DvP): pay + attest in ONE tx. Always prompts with clear-signing. */
141
144
  fillOffer(params) {
145
+ if (typeof this.provider.fillOffer !== "function") {
146
+ return Promise.reject(new UnsupportedMethodError("this wallet predates fillOffer() \u2014 ask the user to update the Cairn Wallet"));
147
+ }
142
148
  return Promise.resolve(this.provider.fillOffer(params)).then(unwrap);
143
149
  }
144
150
  /** This origin's current permission grant (EIP-2255-style). Silent; [] if none / unsupported. */
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CairnError
3
- } from "./chunk-RELP23CH.js";
3
+ } from "./chunk-IYQEEMV2.js";
4
4
 
5
5
  // src/board.ts
6
6
  import { payloadHash } from "@inversealtruism/csd-codec";
@@ -1,5 +1,5 @@
1
1
  // src/errors.ts
2
- var SDK_VERSION = "0.1.4";
2
+ var SDK_VERSION = "0.2.1";
3
3
  var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
4
4
  var CairnError = class extends Error {
5
5
  /** Stable machine code for programmatic branching. */
@@ -74,7 +74,12 @@ var ContentVerificationError = class extends CairnError {
74
74
  this.expected = expected;
75
75
  }
76
76
  };
77
- function mapProviderError(error) {
77
+ var NATIVE_CODES = /* @__PURE__ */ new Set(["ACCOUNT_CHANGED", "FIRST_PARTY_ONLY", "RATE_LIMITED", "APPROVAL_CLOSED", "FORBIDDEN", "INTERNAL"]);
78
+ function mapProviderError(error, code) {
79
+ if (code === "USER_REJECTED" || code === "APPROVAL_CLOSED") return new UserRejectedError(error);
80
+ if (code === "WALLET_LOCKED") return new WalletLockedError(error);
81
+ if (code === "UNSUPPORTED_METHOD" || code === "UNKNOWN_KIND") return new UnsupportedMethodError(error);
82
+ if (code && NATIVE_CODES.has(code)) return new CairnError(error || code, { code });
78
83
  const e = String(error || "").toLowerCase();
79
84
  if (e.includes("rejected by user")) return new UserRejectedError(error);
80
85
  if (e.includes("wallet locked")) return new WalletLockedError(error);
@@ -221,7 +221,7 @@ var IndexerClient = class {
221
221
  handlers.onError?.(err);
222
222
  }
223
223
  if (closed) return;
224
- await new Promise((r) => setTimeout(r, 2e3));
224
+ await new Promise((r) => setTimeout(r, 1e3 + Math.floor(Math.random() * 2e3)));
225
225
  }
226
226
  };
227
227
  void run();
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ContentVerificationError
3
- } from "./chunk-RELP23CH.js";
3
+ } from "./chunk-IYQEEMV2.js";
4
4
 
5
5
  // src/content.ts
6
6
  import { payloadHash, canonicalJson, verifyContentBytes } from "@inversealtruism/csd-codec";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getWallet
3
- } from "./chunk-K7SJOW3O.js";
3
+ } from "./chunk-7IAFWBTW.js";
4
4
 
5
5
  // src/controller.ts
6
6
  var DISCONNECTED = Object.freeze({ status: "disconnected", account: null, error: null });
package/dist/connect.cjs CHANGED
@@ -30,7 +30,7 @@ __export(connect_exports, {
30
30
  module.exports = __toCommonJS(connect_exports);
31
31
 
32
32
  // src/errors.ts
33
- var SDK_VERSION = "0.1.4";
33
+ var SDK_VERSION = "0.2.1";
34
34
  var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
35
35
  var CairnError = class extends Error {
36
36
  /** Stable machine code for programmatic branching. */
@@ -87,7 +87,12 @@ var UnsupportedMethodError = class extends CairnError {
87
87
  super(message, { code: "UNSUPPORTED_METHOD", docsPath: "/errors#unsupported-method" });
88
88
  }
89
89
  };
90
- function mapProviderError(error) {
90
+ var NATIVE_CODES = /* @__PURE__ */ new Set(["ACCOUNT_CHANGED", "FIRST_PARTY_ONLY", "RATE_LIMITED", "APPROVAL_CLOSED", "FORBIDDEN", "INTERNAL"]);
91
+ function mapProviderError(error, code) {
92
+ if (code === "USER_REJECTED" || code === "APPROVAL_CLOSED") return new UserRejectedError(error);
93
+ if (code === "WALLET_LOCKED") return new WalletLockedError(error);
94
+ if (code === "UNSUPPORTED_METHOD" || code === "UNKNOWN_KIND") return new UnsupportedMethodError(error);
95
+ if (code && NATIVE_CODES.has(code)) return new CairnError(error || code, { code });
91
96
  const e = String(error || "").toLowerCase();
92
97
  if (e.includes("rejected by user")) return new UserRejectedError(error);
93
98
  if (e.includes("wallet locked")) return new WalletLockedError(error);
@@ -163,7 +168,7 @@ function isInstalled() {
163
168
  }
164
169
  function unwrap(reply) {
165
170
  if (reply && reply.ok) return reply.result;
166
- throw mapProviderError(reply?.error ?? "Unknown wallet error");
171
+ throw mapProviderError(reply?.error ?? "Unknown wallet error", reply?.code);
167
172
  }
168
173
  var WalletConnection = class {
169
174
  provider;
@@ -197,6 +202,9 @@ var WalletConnection = class {
197
202
  * Passwordless sign-in: a signature over a login nonce (always prompts; structurally cannot sign a tx).
198
203
  */
199
204
  signIn() {
205
+ if (typeof this.provider.signIn !== "function") {
206
+ return Promise.reject(new UnsupportedMethodError("this wallet does not expose signIn()"));
207
+ }
200
208
  return Promise.resolve(this.provider.signIn()).then(unwrap);
201
209
  }
202
210
  /**
@@ -230,6 +238,9 @@ var WalletConnection = class {
230
238
  }
231
239
  /** Atomic fill (CairnX DvP): pay + attest in ONE tx. Always prompts with clear-signing. */
232
240
  fillOffer(params) {
241
+ if (typeof this.provider.fillOffer !== "function") {
242
+ return Promise.reject(new UnsupportedMethodError("this wallet predates fillOffer() \u2014 ask the user to update the Cairn Wallet"));
243
+ }
233
244
  return Promise.resolve(this.provider.fillOffer(params)).then(unwrap);
234
245
  }
235
246
  /** This origin's current permission grant (EIP-2255-style). Silent; [] if none / unsupported. */
@@ -145,6 +145,7 @@ type ProviderReply<T> = {
145
145
  } | {
146
146
  ok: false;
147
147
  error: string;
148
+ code?: string;
148
149
  };
149
150
  declare global {
150
151
  interface Window {
package/dist/connect.d.ts CHANGED
@@ -145,6 +145,7 @@ type ProviderReply<T> = {
145
145
  } | {
146
146
  ok: false;
147
147
  error: string;
148
+ code?: string;
148
149
  };
149
150
  declare global {
150
151
  interface Window {
package/dist/connect.js CHANGED
@@ -5,8 +5,8 @@ import {
5
5
  discoverProviders,
6
6
  getWallet,
7
7
  isInstalled
8
- } from "./chunk-K7SJOW3O.js";
9
- import "./chunk-RELP23CH.js";
8
+ } from "./chunk-7IAFWBTW.js";
9
+ import "./chunk-IYQEEMV2.js";
10
10
  export {
11
11
  WalletConnection,
12
12
  connect,
package/dist/content.cjs CHANGED
@@ -26,7 +26,7 @@ module.exports = __toCommonJS(content_exports);
26
26
  var import_csd_codec = require("@inversealtruism/csd-codec");
27
27
 
28
28
  // src/errors.ts
29
- var SDK_VERSION = "0.1.4";
29
+ var SDK_VERSION = "0.2.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. */
@@ -1,4 +1,4 @@
1
- import { H as Http } from './http-CQsNL8w9.cjs';
1
+ import { H as Http } from './http-C9C4L4hz.cjs';
2
2
 
3
3
  interface ContentClientOptions {
4
4
  /** Swarm gateway HTTP client (e.g. https://cairn-substrate.com/swarm). */
package/dist/content.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { H as Http } from './http-CQsNL8w9.js';
1
+ import { H as Http } from './http-C9C4L4hz.js';
2
2
 
3
3
  interface ContentClientOptions {
4
4
  /** Swarm gateway HTTP client (e.g. https://cairn-substrate.com/swarm). */
package/dist/content.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ContentClient
3
- } from "./chunk-MVX7VQT4.js";
4
- import "./chunk-RELP23CH.js";
3
+ } from "./chunk-PQ4BSDPD.js";
4
+ import "./chunk-IYQEEMV2.js";
5
5
  export {
6
6
  ContentClient
7
7
  };
@@ -25,7 +25,7 @@ __export(controller_exports, {
25
25
  module.exports = __toCommonJS(controller_exports);
26
26
 
27
27
  // src/errors.ts
28
- var SDK_VERSION = "0.1.4";
28
+ var SDK_VERSION = "0.2.1";
29
29
  var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
30
30
  var CairnError = class extends Error {
31
31
  /** Stable machine code for programmatic branching. */
@@ -82,7 +82,12 @@ var UnsupportedMethodError = class extends CairnError {
82
82
  super(message, { code: "UNSUPPORTED_METHOD", docsPath: "/errors#unsupported-method" });
83
83
  }
84
84
  };
85
- function mapProviderError(error) {
85
+ var NATIVE_CODES = /* @__PURE__ */ new Set(["ACCOUNT_CHANGED", "FIRST_PARTY_ONLY", "RATE_LIMITED", "APPROVAL_CLOSED", "FORBIDDEN", "INTERNAL"]);
86
+ function mapProviderError(error, code) {
87
+ if (code === "USER_REJECTED" || code === "APPROVAL_CLOSED") return new UserRejectedError(error);
88
+ if (code === "WALLET_LOCKED") return new WalletLockedError(error);
89
+ if (code === "UNSUPPORTED_METHOD" || code === "UNKNOWN_KIND") return new UnsupportedMethodError(error);
90
+ if (code && NATIVE_CODES.has(code)) return new CairnError(error || code, { code });
86
91
  const e = String(error || "").toLowerCase();
87
92
  if (e.includes("rejected by user")) return new UserRejectedError(error);
88
93
  if (e.includes("wallet locked")) return new WalletLockedError(error);
@@ -135,7 +140,7 @@ function detectProvider(opts = {}) {
135
140
  }
136
141
  function unwrap(reply) {
137
142
  if (reply && reply.ok) return reply.result;
138
- throw mapProviderError(reply?.error ?? "Unknown wallet error");
143
+ throw mapProviderError(reply?.error ?? "Unknown wallet error", reply?.code);
139
144
  }
140
145
  var WalletConnection = class {
141
146
  provider;
@@ -169,6 +174,9 @@ var WalletConnection = class {
169
174
  * Passwordless sign-in: a signature over a login nonce (always prompts; structurally cannot sign a tx).
170
175
  */
171
176
  signIn() {
177
+ if (typeof this.provider.signIn !== "function") {
178
+ return Promise.reject(new UnsupportedMethodError("this wallet does not expose signIn()"));
179
+ }
172
180
  return Promise.resolve(this.provider.signIn()).then(unwrap);
173
181
  }
174
182
  /**
@@ -202,6 +210,9 @@ var WalletConnection = class {
202
210
  }
203
211
  /** Atomic fill (CairnX DvP): pay + attest in ONE tx. Always prompts with clear-signing. */
204
212
  fillOffer(params) {
213
+ if (typeof this.provider.fillOffer !== "function") {
214
+ return Promise.reject(new UnsupportedMethodError("this wallet predates fillOffer() \u2014 ask the user to update the Cairn Wallet"));
215
+ }
205
216
  return Promise.resolve(this.provider.fillOffer(params)).then(unwrap);
206
217
  }
207
218
  /** This origin's current permission grant (EIP-2255-style). Silent; [] if none / unsupported. */
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  CairnController
3
- } from "./chunk-MBSPHWZG.js";
4
- import "./chunk-K7SJOW3O.js";
5
- import "./chunk-RELP23CH.js";
3
+ } from "./chunk-XXOQC4R2.js";
4
+ import "./chunk-7IAFWBTW.js";
5
+ import "./chunk-IYQEEMV2.js";
6
6
  export {
7
7
  CairnController
8
8
  };
@@ -12,6 +12,10 @@ interface HttpOptions {
12
12
  maxBytes?: number;
13
13
  /** Extra headers sent on every request. */
14
14
  headers?: Record<string, string>;
15
+ /** Opt-in retry budget for IDEMPOTENT GETs only (default 0 = today's behavior). Retries fire on
16
+ * network errors/timeouts and 5xx, never on 4xx (mirrors csd-client's posture), with full-jitter
17
+ * backoff. POSTs are never retried (they may not be idempotent). */
18
+ retries?: number;
15
19
  }
16
20
  declare class Http {
17
21
  readonly baseUrl: string;
@@ -19,10 +23,12 @@ declare class Http {
19
23
  private readonly timeoutMs;
20
24
  private readonly maxBytes;
21
25
  private readonly headers;
26
+ private readonly retries;
22
27
  constructor(opts: HttpOptions);
23
28
  /** Build an absolute URL from a path + optional query params. */
24
29
  url(path: string, query?: Record<string, string | number | boolean | undefined>): string;
25
30
  private raw;
31
+ private parseJson;
26
32
  getJson<T = unknown>(path: string, query?: Record<string, string | number | boolean | undefined>): Promise<T>;
27
33
  postJson<T = unknown>(path: string, body: unknown, headers?: Record<string, string>): Promise<T>;
28
34
  /** GET raw bytes (for content retrieval). Returns null on 404. */
@@ -12,6 +12,10 @@ interface HttpOptions {
12
12
  maxBytes?: number;
13
13
  /** Extra headers sent on every request. */
14
14
  headers?: Record<string, string>;
15
+ /** Opt-in retry budget for IDEMPOTENT GETs only (default 0 = today's behavior). Retries fire on
16
+ * network errors/timeouts and 5xx, never on 4xx (mirrors csd-client's posture), with full-jitter
17
+ * backoff. POSTs are never retried (they may not be idempotent). */
18
+ retries?: number;
15
19
  }
16
20
  declare class Http {
17
21
  readonly baseUrl: string;
@@ -19,10 +23,12 @@ declare class Http {
19
23
  private readonly timeoutMs;
20
24
  private readonly maxBytes;
21
25
  private readonly headers;
26
+ private readonly retries;
22
27
  constructor(opts: HttpOptions);
23
28
  /** Build an absolute URL from a path + optional query params. */
24
29
  url(path: string, query?: Record<string, string | number | boolean | undefined>): string;
25
30
  private raw;
31
+ private parseJson;
26
32
  getJson<T = unknown>(path: string, query?: Record<string, string | number | boolean | undefined>): Promise<T>;
27
33
  postJson<T = unknown>(path: string, body: unknown, headers?: Record<string, string>): Promise<T>;
28
34
  /** GET raw bytes (for content retrieval). Returns null on 404. */