@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
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ContentVerificationError
3
- } from "./chunk-PHMU3CG5.js";
3
+ } from "./chunk-RELP23CH.js";
4
4
 
5
5
  // src/content.ts
6
6
  import { payloadHash, canonicalJson, verifyContentBytes } from "@inversealtruism/csd-codec";
@@ -67,8 +67,9 @@ var ContentClient = class {
67
67
  }
68
68
  };
69
69
  function normalizeHash(hash) {
70
- const h = hash.startsWith("0x") || hash.startsWith("0X") ? hash : `0x${hash}`;
71
- return h.toLowerCase();
70
+ const h = (hash.startsWith("0x") || hash.startsWith("0X") ? hash : `0x${hash}`).toLowerCase();
71
+ if (!/^0x[0-9a-f]{64}$/.test(h)) throw new Error(`invalid content hash: ${hash}`);
72
+ return h;
72
73
  }
73
74
 
74
75
  export {
@@ -1,5 +1,12 @@
1
1
  // src/indexer.ts
2
2
  import { verifyMerkleProof } from "@inversealtruism/csd-codec";
3
+ var HEX64 = /^0x[0-9a-fA-F]{64}$/;
4
+ var MAX_SSE_BUF = 1024 * 1024;
5
+ function isValidMerkleProof(p) {
6
+ if (!p || typeof p !== "object") return false;
7
+ const m = p;
8
+ 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));
9
+ }
3
10
  var IndexerClient = class {
4
11
  http;
5
12
  opts;
@@ -20,32 +27,35 @@ var IndexerClient = class {
20
27
  blockHashAtHeight(h) {
21
28
  return this.http.getJson(`/block-height/${h}`);
22
29
  }
30
+ // CAIRNSDK-CONTENT-HASH-PATH-1: encode every caller-supplied id segment. Raw interpolation let an id like
31
+ // `../../api/rpc/tip` walk `new URL` to a DIFFERENT same-origin endpoint whose body was then returned
32
+ // verbatim as the requested object (worse than the content case, which discards the walked-to response).
23
33
  block(hash) {
24
- return this.http.getJson(`/block/${hash}`);
34
+ return this.http.getJson(`/block/${encodeURIComponent(hash)}`);
25
35
  }
26
36
  blockTxids(hash) {
27
- return this.http.getJson(`/block/${hash}/txids`);
37
+ return this.http.getJson(`/block/${encodeURIComponent(hash)}/txids`);
28
38
  }
29
39
  tx(id) {
30
- return this.http.getJson(`/tx/${id}`);
40
+ return this.http.getJson(`/tx/${encodeURIComponent(id)}`);
31
41
  }
32
42
  txStatus(id) {
33
- return this.http.getJson(`/tx/${id}/status`);
43
+ return this.http.getJson(`/tx/${encodeURIComponent(id)}/status`);
34
44
  }
35
45
  txMerkleProof(id) {
36
- return this.http.getJson(`/tx/${id}/merkle-proof`);
46
+ return this.http.getJson(`/tx/${encodeURIComponent(id)}/merkle-proof`);
37
47
  }
38
48
  address(a) {
39
- return this.http.getJson(`/address/${a}`);
49
+ return this.http.getJson(`/address/${encodeURIComponent(a)}`);
40
50
  }
41
51
  addressTxs(a) {
42
- return this.http.getJson(`/address/${a}/txs`);
52
+ return this.http.getJson(`/address/${encodeURIComponent(a)}/txs`);
43
53
  }
44
54
  addressUtxo(a) {
45
- return this.http.getJson(`/address/${a}/utxo`);
55
+ return this.http.getJson(`/address/${encodeURIComponent(a)}/utxo`);
46
56
  }
47
57
  reputation(a) {
48
- return this.http.getJson(`/address/${a}/reputation`);
58
+ return this.http.getJson(`/address/${encodeURIComponent(a)}/reputation`);
49
59
  }
50
60
  domains() {
51
61
  return this.http.getJson("/domains");
@@ -54,10 +64,10 @@ var IndexerClient = class {
54
64
  return this.http.getJson(`/domain/${encodeURIComponent(d)}/proposals`);
55
65
  }
56
66
  proposal(id) {
57
- return this.http.getJson(`/proposal/${id}`);
67
+ return this.http.getJson(`/proposal/${encodeURIComponent(id)}`);
58
68
  }
59
69
  attestations(id) {
60
- return this.http.getJson(`/proposal/${id}/attestations`);
70
+ return this.http.getJson(`/proposal/${encodeURIComponent(id)}/attestations`);
61
71
  }
62
72
  registryPeers() {
63
73
  return this.http.getJson("/registry/peers");
@@ -69,7 +79,7 @@ var IndexerClient = class {
69
79
  return this.http.getJson(`/identity/${encodeURIComponent(handle)}`);
70
80
  }
71
81
  reverseIdentity(addr) {
72
- return this.http.getJson(`/address/${addr}/identity`);
82
+ return this.http.getJson(`/address/${encodeURIComponent(addr)}/identity`);
73
83
  }
74
84
  // ---- trust-minimized inclusion ----------------------------------------
75
85
  /**
@@ -78,9 +88,11 @@ var IndexerClient = class {
78
88
  * proof's root is cross-checked against the header merkle at that height →
79
89
  * trustLevel "verified-inclusion" (only as trustworthy as that header source —
80
90
  * see headerMerkleAt). Otherwise the proof is only internally consistent →
81
- * "proof-consistent" (still useful, but the indexer is trusted). The Cairn
82
- * facade wires headerMerkleAt ONLY when the node RPC is an independent origin
83
- * from the indexer, so the default same-origin setup stays at "proof-consistent".
91
+ * "proof-consistent" (still useful, but the indexer is trusted). INDEXER-DOC-STALE-1: the Cairn facade now
92
+ * wires headerMerkleAt UNCONDITIONALLY via a PoW-verifying checkpoint light client (index.ts), so the
93
+ * default reaches "verified-inclusion" when the proof root matches the PoW-verified header — what makes a
94
+ * same-origin RPC safe is the forward PoW re-verification, not an origin gate (which no longer exists).
95
+ * Fails closed: a malformed proof or a chain-disagreement returns "not-found" (the latter with equivocation:true).
84
96
  */
85
97
  async verifyInclusion(txid) {
86
98
  let proof;
@@ -89,7 +101,15 @@ var IndexerClient = class {
89
101
  } catch {
90
102
  return { included: false, trustLevel: "not-found", reason: "no merkle proof (tx not indexed)" };
91
103
  }
92
- const folds = verifyMerkleProof(txid, proof.pos, proof.merkle, proof.merkle_root);
104
+ if (!isValidMerkleProof(proof)) {
105
+ return { included: false, trustLevel: "not-found", reason: "malformed merkle proof" };
106
+ }
107
+ let folds = false;
108
+ try {
109
+ folds = verifyMerkleProof(txid, proof.pos, proof.merkle, proof.merkle_root);
110
+ } catch {
111
+ return { included: false, trustLevel: "not-found", blockHeight: proof.block_height, reason: "malformed merkle proof" };
112
+ }
93
113
  if (!folds) {
94
114
  return { included: false, trustLevel: "not-found", blockHeight: proof.block_height, reason: "merkle branch does not fold to the claimed root" };
95
115
  }
@@ -97,7 +117,7 @@ var IndexerClient = class {
97
117
  try {
98
118
  const onchain = await this.opts.headerMerkleAt(proof.block_height);
99
119
  if (norm(onchain) !== norm(proof.merkle_root)) {
100
- return { included: false, trustLevel: "not-found", blockHeight: proof.block_height, reason: "proof root != on-chain header merkle (indexer disagrees with chain)" };
120
+ return { included: false, trustLevel: "not-found", equivocation: true, blockHeight: proof.block_height, reason: "proof root != on-chain header merkle (indexer disagrees with chain)" };
101
121
  }
102
122
  return { included: true, trustLevel: "verified-inclusion", blockHeight: proof.block_height, pos: proof.pos, merkleRoot: proof.merkle_root };
103
123
  } catch {
@@ -121,6 +141,11 @@ var IndexerClient = class {
121
141
  /**
122
142
  * Subscribe over WebSocket with selective tracking. Returns a handle with
123
143
  * `.close()`. `onEvent` receives every matching IndexEvent (incl. reorgs).
144
+ *
145
+ * WS-NORECONNECT-1: unlike the SSE feeds (streamAll/streamBlocks/streamDomain, which auto-reconnect with
146
+ * backoff), this WebSocket subscription does NOT auto-reconnect — it surfaces a terminal error via
147
+ * `onError` (when provided) and stops. A consumer tracking reorgs over WS MUST re-`subscribe()` on error
148
+ * to avoid a silently-stale view; for built-in reconnect prefer the SSE `stream*` methods.
124
149
  */
125
150
  subscribe(sub, onEvent, onError) {
126
151
  const WS = this.opts.WebSocketImpl ?? globalThis.WebSocket;
@@ -183,6 +208,13 @@ var IndexerClient = class {
183
208
  buf = buf.slice(idx + 2);
184
209
  dispatchFrame(frame, handlers);
185
210
  }
211
+ if (buf.length > MAX_SSE_BUF) {
212
+ try {
213
+ await reader.cancel();
214
+ } catch {
215
+ }
216
+ throw new Error(`SSE buffer exceeded ${MAX_SSE_BUF} bytes with no frame delimiter \u2014 aborting`);
217
+ }
186
218
  }
187
219
  } catch (err) {
188
220
  if (closed) return;
@@ -0,0 +1,99 @@
1
+ // src/errors.ts
2
+ var SDK_VERSION = "0.1.4";
3
+ var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
4
+ var CairnError = class extends Error {
5
+ /** Stable machine code for programmatic branching. */
6
+ code;
7
+ /** The human message without the appended docs/version footer. */
8
+ shortMessage;
9
+ /** Relative docs path (under the SDK docs base), if any. */
10
+ docsPath;
11
+ /** The cairn-sdk version that produced this error. */
12
+ version = SDK_VERSION;
13
+ constructor(shortMessage, opts = {}) {
14
+ const docs = opts.docsPath;
15
+ super(
16
+ [shortMessage, docs ? `Docs: ${DOCS_BASE}${docs}` : "", `Version: cairn-sdk@${SDK_VERSION}`].filter(Boolean).join("\n"),
17
+ { cause: opts.cause }
18
+ );
19
+ this.name = new.target.name;
20
+ this.code = opts.code ?? "UNKNOWN";
21
+ this.shortMessage = shortMessage;
22
+ this.docsPath = docs;
23
+ }
24
+ /**
25
+ * Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
26
+ * returns the DEEPEST cause (the root). Mirrors viem's `BaseError.walk()`.
27
+ */
28
+ walk(fn) {
29
+ let current = this;
30
+ let last = this;
31
+ while (current != null) {
32
+ if (fn && fn(current)) return current;
33
+ last = current;
34
+ current = current?.cause;
35
+ }
36
+ return fn ? null : last;
37
+ }
38
+ };
39
+ var NotInstalledError = class extends CairnError {
40
+ constructor(message = "Cairn Wallet not detected. Install it from cairn-substrate.com.") {
41
+ super(message, { code: "NOT_INSTALLED", docsPath: "/install" });
42
+ }
43
+ };
44
+ var UserRejectedError = class extends CairnError {
45
+ constructor(message = "Request rejected by user.") {
46
+ super(message, { code: "USER_REJECTED", docsPath: "/errors#user-rejected" });
47
+ }
48
+ };
49
+ var WalletLockedError = class extends CairnError {
50
+ constructor(message = "Wallet is locked.") {
51
+ super(message, { code: "WALLET_LOCKED", docsPath: "/errors#wallet-locked" });
52
+ }
53
+ };
54
+ var UnsupportedMethodError = class extends CairnError {
55
+ constructor(message = "Method not supported by the wallet.") {
56
+ super(message, { code: "UNSUPPORTED_METHOD", docsPath: "/errors#unsupported-method" });
57
+ }
58
+ };
59
+ var HttpError = class extends CairnError {
60
+ status;
61
+ url;
62
+ body;
63
+ constructor(status, url, body) {
64
+ super(`HTTP ${status} for ${url}${body ? `: ${String(body).slice(0, 200)}` : ""}`, { code: "HTTP_ERROR" });
65
+ this.status = status;
66
+ this.url = url;
67
+ this.body = body;
68
+ }
69
+ };
70
+ var ContentVerificationError = class extends CairnError {
71
+ expected;
72
+ constructor(expected) {
73
+ super(`Content does not match its payload hash ${expected} \u2014 bytes were tampered with in transit.`, { code: "CONTENT_VERIFICATION", docsPath: "/content#verification" });
74
+ this.expected = expected;
75
+ }
76
+ };
77
+ function mapProviderError(error) {
78
+ const e = String(error || "").toLowerCase();
79
+ if (e.includes("rejected by user")) return new UserRejectedError(error);
80
+ if (e.includes("wallet locked")) return new WalletLockedError(error);
81
+ if (e.includes("unsupported dapp method") || e.includes("predates") || e.includes("too old")) return new UnsupportedMethodError(error);
82
+ return new CairnError(error || "Unknown wallet error");
83
+ }
84
+ function errorCode(err) {
85
+ return err instanceof CairnError ? err.code : "UNKNOWN";
86
+ }
87
+
88
+ export {
89
+ SDK_VERSION,
90
+ CairnError,
91
+ NotInstalledError,
92
+ UserRejectedError,
93
+ WalletLockedError,
94
+ UnsupportedMethodError,
95
+ HttpError,
96
+ ContentVerificationError,
97
+ mapProviderError,
98
+ errorCode
99
+ };
package/dist/connect.cjs CHANGED
@@ -23,47 +23,99 @@ __export(connect_exports, {
23
23
  WalletConnection: () => WalletConnection,
24
24
  connect: () => connect,
25
25
  detectProvider: () => detectProvider,
26
+ discoverProviders: () => discoverProviders,
26
27
  getWallet: () => getWallet,
27
28
  isInstalled: () => isInstalled
28
29
  });
29
30
  module.exports = __toCommonJS(connect_exports);
30
31
 
31
32
  // src/errors.ts
33
+ var SDK_VERSION = "0.1.4";
34
+ var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
32
35
  var CairnError = class extends Error {
33
- constructor(message) {
34
- super(message);
36
+ /** Stable machine code for programmatic branching. */
37
+ code;
38
+ /** The human message without the appended docs/version footer. */
39
+ shortMessage;
40
+ /** Relative docs path (under the SDK docs base), if any. */
41
+ docsPath;
42
+ /** The cairn-sdk version that produced this error. */
43
+ version = SDK_VERSION;
44
+ constructor(shortMessage, opts = {}) {
45
+ const docs = opts.docsPath;
46
+ super(
47
+ [shortMessage, docs ? `Docs: ${DOCS_BASE}${docs}` : "", `Version: cairn-sdk@${SDK_VERSION}`].filter(Boolean).join("\n"),
48
+ { cause: opts.cause }
49
+ );
35
50
  this.name = new.target.name;
51
+ this.code = opts.code ?? "UNKNOWN";
52
+ this.shortMessage = shortMessage;
53
+ this.docsPath = docs;
54
+ }
55
+ /**
56
+ * Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
57
+ * returns the DEEPEST cause (the root). Mirrors viem's `BaseError.walk()`.
58
+ */
59
+ walk(fn) {
60
+ let current = this;
61
+ let last = this;
62
+ while (current != null) {
63
+ if (fn && fn(current)) return current;
64
+ last = current;
65
+ current = current?.cause;
66
+ }
67
+ return fn ? null : last;
36
68
  }
37
69
  };
38
70
  var NotInstalledError = class extends CairnError {
39
71
  constructor(message = "Cairn Wallet not detected. Install it from cairn-substrate.com.") {
40
- super(message);
72
+ super(message, { code: "NOT_INSTALLED", docsPath: "/install" });
41
73
  }
42
74
  };
43
75
  var UserRejectedError = class extends CairnError {
44
76
  constructor(message = "Request rejected by user.") {
45
- super(message);
77
+ super(message, { code: "USER_REJECTED", docsPath: "/errors#user-rejected" });
46
78
  }
47
79
  };
48
80
  var WalletLockedError = class extends CairnError {
49
81
  constructor(message = "Wallet is locked.") {
50
- super(message);
82
+ super(message, { code: "WALLET_LOCKED", docsPath: "/errors#wallet-locked" });
51
83
  }
52
84
  };
53
85
  var UnsupportedMethodError = class extends CairnError {
54
86
  constructor(message = "Method not supported by the wallet.") {
55
- super(message);
87
+ super(message, { code: "UNSUPPORTED_METHOD", docsPath: "/errors#unsupported-method" });
56
88
  }
57
89
  };
58
90
  function mapProviderError(error) {
59
91
  const e = String(error || "").toLowerCase();
60
92
  if (e.includes("rejected by user")) return new UserRejectedError(error);
61
93
  if (e.includes("wallet locked")) return new WalletLockedError(error);
62
- if (e.includes("unsupported dapp method")) return new UnsupportedMethodError(error);
94
+ if (e.includes("unsupported dapp method") || e.includes("predates") || e.includes("too old")) return new UnsupportedMethodError(error);
63
95
  return new CairnError(error || "Unknown wallet error");
64
96
  }
65
97
 
66
98
  // src/connect.ts
99
+ function discoverProviders(opts = {}) {
100
+ const timeoutMs = opts.timeoutMs ?? 1e3;
101
+ return new Promise((resolve) => {
102
+ if (typeof window === "undefined") {
103
+ resolve([]);
104
+ return;
105
+ }
106
+ const found = /* @__PURE__ */ new Map();
107
+ const onAnnounce = (ev) => {
108
+ const d = ev?.detail;
109
+ if (d?.info?.uuid && d?.provider?.isCairn) found.set(d.info.uuid, d);
110
+ };
111
+ window.addEventListener("csd:announceProvider", onAnnounce);
112
+ window.dispatchEvent(new CustomEvent("csd:requestProvider"));
113
+ setTimeout(() => {
114
+ window.removeEventListener("csd:announceProvider", onAnnounce);
115
+ resolve([...found.values()]);
116
+ }, timeoutMs);
117
+ });
118
+ }
67
119
  function detectProvider(opts = {}) {
68
120
  const timeoutMs = opts.timeoutMs ?? 3e3;
69
121
  return new Promise((resolve, reject) => {
@@ -71,18 +123,19 @@ function detectProvider(opts = {}) {
71
123
  reject(new NotInstalledError("Not running in a browser \u2014 `window.cairn` is unavailable."));
72
124
  return;
73
125
  }
74
- if (window.cairn?.isCairn) {
75
- resolve(window.cairn);
76
- return;
77
- }
78
126
  let done = false;
79
- const onInit = () => {
127
+ const finish = (p) => {
80
128
  if (done) return;
81
- if (window.cairn?.isCairn) {
82
- done = true;
83
- cleanup();
84
- resolve(window.cairn);
85
- }
129
+ done = true;
130
+ cleanup();
131
+ resolve(p);
132
+ };
133
+ const onInit = () => {
134
+ if (!done && window.cairn?.isCairn) finish(window.cairn);
135
+ };
136
+ const onAnnounce = (ev) => {
137
+ const p = ev?.detail?.provider;
138
+ if (p?.isCairn) finish(p);
86
139
  };
87
140
  const timer = setTimeout(() => {
88
141
  if (done) return;
@@ -93,8 +146,15 @@ function detectProvider(opts = {}) {
93
146
  const cleanup = () => {
94
147
  clearTimeout(timer);
95
148
  window.removeEventListener("cairn#initialized", onInit);
149
+ window.removeEventListener("csd:announceProvider", onAnnounce);
96
150
  };
97
151
  window.addEventListener("cairn#initialized", onInit);
152
+ window.addEventListener("csd:announceProvider", onAnnounce);
153
+ if (window.cairn?.isCairn) {
154
+ finish(window.cairn);
155
+ return;
156
+ }
157
+ window.dispatchEvent(new CustomEvent("csd:requestProvider"));
98
158
  onInit();
99
159
  });
100
160
  }
@@ -131,10 +191,76 @@ var WalletConnection = class {
131
191
  this._addr = addr;
132
192
  return addr;
133
193
  }
134
- /** Passwordless sign-in: a signature over a login nonce (always prompts; structurally cannot sign a tx). */
194
+ /**
195
+ * @deprecated First-party-only legacy login (it authenticates against the wallet's own configured
196
+ * server and is rejected from third-party origins). Use {@link signInWithCsd} for any site.
197
+ * Passwordless sign-in: a signature over a login nonce (always prompts; structurally cannot sign a tx).
198
+ */
135
199
  signIn() {
136
200
  return Promise.resolve(this.provider.signIn()).then(unwrap);
137
201
  }
202
+ /**
203
+ * Audience-bound "Sign in with CSD" (SIWC) — the secure, replay-resistant third-party login.
204
+ * The wallet binds the REAL requesting origin + your single-use server `nonce` into the signed
205
+ * message and returns the artifact; it never mints a session. VERIFY the result SERVER-SIDE with
206
+ * `verifySiwc` from `@inversealtruism/csd-siwc` (domain/nonce/chain/time/signature/account), then
207
+ * issue your OWN session. The signature is proof-of-control, NOT a bearer token. Always prompts.
208
+ */
209
+ signInWithCsd(params) {
210
+ if (!params || typeof params.nonce !== "string" || !params.nonce) {
211
+ return Promise.reject(new UnsupportedMethodError("signInWithCsd requires a server-issued { nonce }"));
212
+ }
213
+ if (typeof this.provider.signInWithCsd !== "function") {
214
+ return Promise.reject(new UnsupportedMethodError("this wallet predates Sign-in-with-CSD \u2014 ask the user to update the Cairn Wallet"));
215
+ }
216
+ return Promise.resolve(this.provider.signInWithCsd(params)).then(unwrap);
217
+ }
218
+ /** True if this wallet supports audience-bound SIWC (safe to call `signInWithCsd`). */
219
+ get supportsSiwc() {
220
+ return typeof this.provider.signInWithCsd === "function";
221
+ }
222
+ /** Feature/capability detection. Returns null if the wallet predates `getCapabilities`. */
223
+ async getCapabilities() {
224
+ if (typeof this.provider.getCapabilities !== "function") return null;
225
+ try {
226
+ return await this.provider.getCapabilities();
227
+ } catch {
228
+ return null;
229
+ }
230
+ }
231
+ /** Atomic fill (CairnX DvP): pay + attest in ONE tx. Always prompts with clear-signing. */
232
+ fillOffer(params) {
233
+ return Promise.resolve(this.provider.fillOffer(params)).then(unwrap);
234
+ }
235
+ /** This origin's current permission grant (EIP-2255-style). Silent; [] if none / unsupported. */
236
+ async getPermissions() {
237
+ if (typeof this.provider.getPermissions !== "function") return [];
238
+ return unwrap(await this.provider.getPermissions());
239
+ }
240
+ /** Explicitly request permission to see the address (prompts, like connect). */
241
+ requestPermissions() {
242
+ if (typeof this.provider.requestPermissions !== "function") {
243
+ return Promise.reject(new UnsupportedMethodError("this wallet predates requestPermissions \u2014 use connect()"));
244
+ }
245
+ return Promise.resolve(this.provider.requestPermissions()).then(unwrap);
246
+ }
247
+ /** Revoke THIS origin's own access (silent). Returns { revoked }. Safe no-op on older wallets. */
248
+ async revokePermissions() {
249
+ if (typeof this.provider.revokePermissions !== "function") return { revoked: false };
250
+ return unwrap(await this.provider.revokePermissions());
251
+ }
252
+ /**
253
+ * Subscribe to provider events. `accountsChanged` fires with `[]` when this site loses access
254
+ * (wallet lock / account switch / revoke) — re-call connect() to (re)share; `disconnect` likewise.
255
+ * No-op if the wallet predates events. Use the SAME handler reference with off() to unsubscribe.
256
+ */
257
+ on(event, handler) {
258
+ this.provider.on?.(event, handler);
259
+ }
260
+ /** Remove a previously-added event handler (must be the same reference passed to on()). */
261
+ off(event, handler) {
262
+ this.provider.removeListener?.(event, handler);
263
+ }
138
264
  /** Send CSD (always prompts with clear-signing; wallet picks inputs + returns change to itself). */
139
265
  send(params) {
140
266
  return Promise.resolve(this.provider.send(params)).then(unwrap);
@@ -172,6 +298,7 @@ async function connect(opts = {}) {
172
298
  WalletConnection,
173
299
  connect,
174
300
  detectProvider,
301
+ discoverProviders,
175
302
  getWallet,
176
303
  isInstalled
177
304
  });