@inversealtruism/cairn-sdk 0.1.2 → 0.1.3

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.
@@ -0,0 +1,78 @@
1
+ import {
2
+ getWallet
3
+ } from "./chunk-G4OZKZSJ.js";
4
+
5
+ // src/controller.ts
6
+ var DISCONNECTED = Object.freeze({ status: "disconnected", account: null, error: null });
7
+ var CairnController = class {
8
+ state = DISCONNECTED;
9
+ conn = null;
10
+ listeners = /* @__PURE__ */ new Set();
11
+ getWalletFn;
12
+ detectOpts;
13
+ constructor(opts = {}) {
14
+ const { getWallet: gw, ...detect } = opts;
15
+ this.getWalletFn = gw ?? getWallet;
16
+ this.detectOpts = detect;
17
+ }
18
+ /** useSyncExternalStore-compatible. Returns an unsubscribe fn. */
19
+ subscribe = (cb) => {
20
+ this.listeners.add(cb);
21
+ return () => {
22
+ this.listeners.delete(cb);
23
+ };
24
+ };
25
+ /** useSyncExternalStore-compatible snapshot — stable reference until the state actually changes. */
26
+ getSnapshot = () => this.state;
27
+ /** The underlying WalletConnection (for advanced use: send/propose/permissions/…). Null until connect(). */
28
+ get connection() {
29
+ return this.conn;
30
+ }
31
+ setState(patch) {
32
+ const next = { ...this.state, ...patch };
33
+ if (next.status === this.state.status && next.account === this.state.account && next.error === this.state.error) return;
34
+ this.state = Object.freeze(next);
35
+ for (const l of [...this.listeners]) l();
36
+ }
37
+ onAccounts = (accounts) => {
38
+ if (!Array.isArray(accounts) || accounts.length === 0) this.setState({ status: "disconnected", account: null });
39
+ else this.setState({ status: "connected", account: String(accounts[0]), error: null });
40
+ };
41
+ onDisconnect = () => {
42
+ this.setState({ status: "disconnected", account: null });
43
+ };
44
+ /** Detect + connect (prompts the user the first time per origin). Resolves the connected address. */
45
+ connect = async () => {
46
+ this.setState({ status: "connecting", error: null });
47
+ try {
48
+ if (!this.conn) {
49
+ this.conn = await this.getWalletFn(this.detectOpts);
50
+ this.conn.on("accountsChanged", this.onAccounts);
51
+ this.conn.on("disconnect", this.onDisconnect);
52
+ }
53
+ const addr = await this.conn.connect();
54
+ this.setState({ status: "connected", account: addr, error: null });
55
+ return addr;
56
+ } catch (e) {
57
+ this.setState({ status: "disconnected", account: null, error: e?.message ?? String(e) });
58
+ throw e;
59
+ }
60
+ };
61
+ /** Forget the connection locally AND revoke this origin's wallet-side permission (best-effort). */
62
+ disconnect = async () => {
63
+ try {
64
+ await this.conn?.revokePermissions();
65
+ } catch {
66
+ }
67
+ this.setState({ status: "disconnected", account: null, error: null });
68
+ };
69
+ /** Audience-bound sign-in (must connect() first). Returns the signed artifact to verify server-side. */
70
+ signInWithCsd = (params) => {
71
+ if (!this.conn) return Promise.reject(new Error("call connect() before signInWithCsd()"));
72
+ return this.conn.signInWithCsd(params);
73
+ };
74
+ };
75
+
76
+ export {
77
+ CairnController
78
+ };
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.2";
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
  });
@@ -49,18 +49,94 @@ interface SealClaimParams {
49
49
  claim: string;
50
50
  fee?: number;
51
51
  }
52
+ /** Atomic fill (CairnX delivery-vs-payment): an Attest + payment outputs in ONE tx. */
53
+ interface FillParams {
54
+ proposalId: string;
55
+ outputs: {
56
+ to: string;
57
+ value: number;
58
+ }[];
59
+ score?: number;
60
+ confidence?: number;
61
+ fee?: number;
62
+ }
63
+ /**
64
+ * Audience-bound "Sign in with CSD" (SIWC) params — the secure third-party login. The wallet binds
65
+ * the REAL requesting origin as the message `domain` (from the unforgeable sender.origin, NEVER this
66
+ * field) plus your server `nonce`, chain id, and a validity window, and returns only the signed
67
+ * artifact. The relying party MUST verify it server-side (see below).
68
+ */
69
+ interface SiwcParams {
70
+ /** Server-issued, single-use, >=8 alphanumeric (use `generateNonce()` from @inversealtruism/csd-siwc). */
71
+ nonce: string;
72
+ /** Optional one-line human statement shown in the wallet (no newline). */
73
+ statement?: string;
74
+ /** Subject URI; defaults to the origin root. If given, MUST be on the requesting site. */
75
+ uri?: string;
76
+ /** Validity window in seconds (wallet clamps 60..3600, default 600). */
77
+ expirationSecs?: number;
78
+ notBeforeSecs?: number;
79
+ requestId?: string;
80
+ resources?: string[];
81
+ /** Optional cross-check only — if set it MUST equal the requesting origin's host, else refused. */
82
+ domain?: string;
83
+ }
84
+ /**
85
+ * SIWC result — the signed artifact. VERIFY SERVER-SIDE with `verifySiwc` from
86
+ * `@inversealtruism/csd-siwc` (check domain==your-origin, nonce==your-issued-single-use-nonce,
87
+ * chainId, time window, signature, hash160(pub33)==account), THEN issue your OWN session. The
88
+ * signature is proof-of-control, NEVER a bearer token.
89
+ */
90
+ interface SiwcResult {
91
+ account: string;
92
+ pub33: string;
93
+ sig64: string;
94
+ /** The exact canonical SIWC message that was signed (verify the signature over this). */
95
+ message: string;
96
+ /** CAIP-2 chain id the sign-in is bound to. */
97
+ chainId: string;
98
+ }
99
+ /** Wallet feature/capability descriptor (feature-detection; older wallets may not expose it). */
100
+ interface WalletCapabilities {
101
+ version: string;
102
+ /** SIWC byte-contract version, e.g. "1", when audience-bound sign-in is supported. */
103
+ siwc?: string;
104
+ methods?: string[];
105
+ [k: string]: unknown;
106
+ }
107
+ /** An EIP-2255-style per-origin permission grant. */
108
+ interface WalletPermission {
109
+ invoker: string;
110
+ accounts: string[];
111
+ grantedAt: number;
112
+ }
52
113
  /** The raw provider object the extension injects at `window.cairn`. */
53
114
  interface CairnProvider {
54
115
  isCairn: true;
55
116
  version: string;
56
117
  connect(): Promise<ProviderReply<ConnectResult>>;
57
118
  getAddress(): Promise<ProviderReply<ConnectResult>>;
119
+ /** @deprecated First-party-only legacy login. Third parties MUST use `signInWithCsd`. */
58
120
  signIn(): Promise<ProviderReply<SignInResult>>;
121
+ /** Audience-bound "Sign in with CSD" — the secure third-party login. */
122
+ signInWithCsd(p: SiwcParams): Promise<ProviderReply<SiwcResult>>;
59
123
  propose(p: ProposeParams): Promise<ProviderReply<TxResult>>;
60
124
  attest(p: AttestParams): Promise<ProviderReply<TxResult>>;
61
125
  send(p: SendParams): Promise<ProviderReply<TxResult>>;
126
+ fillOffer(p: FillParams): Promise<ProviderReply<TxResult>>;
62
127
  sealClaim(p: SealClaimParams): Promise<ProviderReply<TxResult>>;
63
128
  revealClaim(txid: string): Promise<ProviderReply<TxResult>>;
129
+ /** Feature/capability detection (optional; absent on older wallets). */
130
+ getCapabilities?(): Promise<WalletCapabilities>;
131
+ /** EIP-2255-style per-origin permissions (optional; absent on older wallets). */
132
+ getPermissions?(): Promise<ProviderReply<WalletPermission[]>>;
133
+ requestPermissions?(): Promise<ProviderReply<WalletPermission[]>>;
134
+ revokePermissions?(): Promise<ProviderReply<{
135
+ revoked: boolean;
136
+ }>>;
137
+ /** Event subscription (accountsChanged / disconnect) (optional; absent on older wallets). */
138
+ on?(event: string, handler: (data: unknown) => void): void;
139
+ removeListener?(event: string, handler: (data: unknown) => void): void;
64
140
  }
65
141
  /** Every provider call resolves to this discriminated reply (never rejects). */
66
142
  type ProviderReply<T> = {
@@ -84,6 +160,28 @@ interface DetectOptions {
84
160
  * content script hasn't injected yet. Throws `NotInstalledError` on timeout or
85
161
  * outside a browser.
86
162
  */
163
+ /** EIP-6963-style announced wallet info (Cairn's own `csd:` discovery namespace). */
164
+ interface ProviderInfo {
165
+ uuid: string;
166
+ name: string;
167
+ icon: string;
168
+ rdns: string;
169
+ }
170
+ /** A discovered provider + its display info. */
171
+ interface ProviderDetail {
172
+ info: ProviderInfo;
173
+ provider: CairnProvider;
174
+ }
175
+ /**
176
+ * Enumerate all wallets that announce via the `csd:` discovery handshake (EIP-6963 analog) — for a
177
+ * multi-wallet picker. Resolves with everything announced within `timeoutMs` (default 1000ms).
178
+ */
179
+ declare function discoverProviders(opts?: DetectOptions): Promise<ProviderDetail[]>;
180
+ /**
181
+ * Resolve `window.cairn`, via three paths that race: an immediate global, the legacy
182
+ * `cairn#initialized` event, or the `csd:announceProvider` discovery handshake (we dispatch
183
+ * `csd:requestProvider`). Throws `NotInstalledError` on timeout or outside a browser.
184
+ */
87
185
  declare function detectProvider(opts?: DetectOptions): Promise<CairnProvider>;
88
186
  /** True if the Cairn Wallet is already present (no waiting). */
89
187
  declare function isInstalled(): boolean;
@@ -103,8 +201,42 @@ declare class WalletConnection {
103
201
  connect(): Promise<string>;
104
202
  /** Get the connected address (does not force a new connection prompt if already consented). */
105
203
  getAddress(): Promise<string>;
106
- /** Passwordless sign-in: a signature over a login nonce (always prompts; structurally cannot sign a tx). */
204
+ /**
205
+ * @deprecated First-party-only legacy login (it authenticates against the wallet's own configured
206
+ * server and is rejected from third-party origins). Use {@link signInWithCsd} for any site.
207
+ * Passwordless sign-in: a signature over a login nonce (always prompts; structurally cannot sign a tx).
208
+ */
107
209
  signIn(): Promise<SignInResult>;
210
+ /**
211
+ * Audience-bound "Sign in with CSD" (SIWC) — the secure, replay-resistant third-party login.
212
+ * The wallet binds the REAL requesting origin + your single-use server `nonce` into the signed
213
+ * message and returns the artifact; it never mints a session. VERIFY the result SERVER-SIDE with
214
+ * `verifySiwc` from `@inversealtruism/csd-siwc` (domain/nonce/chain/time/signature/account), then
215
+ * issue your OWN session. The signature is proof-of-control, NOT a bearer token. Always prompts.
216
+ */
217
+ signInWithCsd(params: SiwcParams): Promise<SiwcResult>;
218
+ /** True if this wallet supports audience-bound SIWC (safe to call `signInWithCsd`). */
219
+ get supportsSiwc(): boolean;
220
+ /** Feature/capability detection. Returns null if the wallet predates `getCapabilities`. */
221
+ getCapabilities(): Promise<WalletCapabilities | null>;
222
+ /** Atomic fill (CairnX DvP): pay + attest in ONE tx. Always prompts with clear-signing. */
223
+ fillOffer(params: FillParams): Promise<TxResult>;
224
+ /** This origin's current permission grant (EIP-2255-style). Silent; [] if none / unsupported. */
225
+ getPermissions(): Promise<WalletPermission[]>;
226
+ /** Explicitly request permission to see the address (prompts, like connect). */
227
+ requestPermissions(): Promise<WalletPermission[]>;
228
+ /** Revoke THIS origin's own access (silent). Returns { revoked }. Safe no-op on older wallets. */
229
+ revokePermissions(): Promise<{
230
+ revoked: boolean;
231
+ }>;
232
+ /**
233
+ * Subscribe to provider events. `accountsChanged` fires with `[]` when this site loses access
234
+ * (wallet lock / account switch / revoke) — re-call connect() to (re)share; `disconnect` likewise.
235
+ * No-op if the wallet predates events. Use the SAME handler reference with off() to unsubscribe.
236
+ */
237
+ on(event: "accountsChanged" | "disconnect" | (string & {}), handler: (data: any) => void): void;
238
+ /** Remove a previously-added event handler (must be the same reference passed to on()). */
239
+ off(event: string, handler: (data: any) => void): void;
108
240
  /** Send CSD (always prompts with clear-signing; wallet picks inputs + returns change to itself). */
109
241
  send(params: SendParams): Promise<TxResult>;
110
242
  /** Low-level Propose (always prompts). Prefer `cairn.board.propose()` which computes the hash/uri/expiry. */
@@ -124,4 +256,4 @@ declare function connect(opts?: DetectOptions): Promise<{
124
256
  address: string;
125
257
  }>;
126
258
 
127
- export { type AttestParams, type CairnProvider, type ConnectResult, type DetectOptions, type ProposeParams, type ProviderReply, type SealClaimParams, type SendParams, type SignInResult, type TxResult, WalletConnection, connect, detectProvider, getWallet, isInstalled };
259
+ export { type AttestParams, type CairnProvider, type ConnectResult, type DetectOptions, type FillParams, type ProposeParams, type ProviderDetail, type ProviderInfo, type ProviderReply, type SealClaimParams, type SendParams, type SignInResult, type SiwcParams, type SiwcResult, type TxResult, type WalletCapabilities, WalletConnection, type WalletPermission, connect, detectProvider, discoverProviders, getWallet, isInstalled };
package/dist/connect.d.ts CHANGED
@@ -49,18 +49,94 @@ interface SealClaimParams {
49
49
  claim: string;
50
50
  fee?: number;
51
51
  }
52
+ /** Atomic fill (CairnX delivery-vs-payment): an Attest + payment outputs in ONE tx. */
53
+ interface FillParams {
54
+ proposalId: string;
55
+ outputs: {
56
+ to: string;
57
+ value: number;
58
+ }[];
59
+ score?: number;
60
+ confidence?: number;
61
+ fee?: number;
62
+ }
63
+ /**
64
+ * Audience-bound "Sign in with CSD" (SIWC) params — the secure third-party login. The wallet binds
65
+ * the REAL requesting origin as the message `domain` (from the unforgeable sender.origin, NEVER this
66
+ * field) plus your server `nonce`, chain id, and a validity window, and returns only the signed
67
+ * artifact. The relying party MUST verify it server-side (see below).
68
+ */
69
+ interface SiwcParams {
70
+ /** Server-issued, single-use, >=8 alphanumeric (use `generateNonce()` from @inversealtruism/csd-siwc). */
71
+ nonce: string;
72
+ /** Optional one-line human statement shown in the wallet (no newline). */
73
+ statement?: string;
74
+ /** Subject URI; defaults to the origin root. If given, MUST be on the requesting site. */
75
+ uri?: string;
76
+ /** Validity window in seconds (wallet clamps 60..3600, default 600). */
77
+ expirationSecs?: number;
78
+ notBeforeSecs?: number;
79
+ requestId?: string;
80
+ resources?: string[];
81
+ /** Optional cross-check only — if set it MUST equal the requesting origin's host, else refused. */
82
+ domain?: string;
83
+ }
84
+ /**
85
+ * SIWC result — the signed artifact. VERIFY SERVER-SIDE with `verifySiwc` from
86
+ * `@inversealtruism/csd-siwc` (check domain==your-origin, nonce==your-issued-single-use-nonce,
87
+ * chainId, time window, signature, hash160(pub33)==account), THEN issue your OWN session. The
88
+ * signature is proof-of-control, NEVER a bearer token.
89
+ */
90
+ interface SiwcResult {
91
+ account: string;
92
+ pub33: string;
93
+ sig64: string;
94
+ /** The exact canonical SIWC message that was signed (verify the signature over this). */
95
+ message: string;
96
+ /** CAIP-2 chain id the sign-in is bound to. */
97
+ chainId: string;
98
+ }
99
+ /** Wallet feature/capability descriptor (feature-detection; older wallets may not expose it). */
100
+ interface WalletCapabilities {
101
+ version: string;
102
+ /** SIWC byte-contract version, e.g. "1", when audience-bound sign-in is supported. */
103
+ siwc?: string;
104
+ methods?: string[];
105
+ [k: string]: unknown;
106
+ }
107
+ /** An EIP-2255-style per-origin permission grant. */
108
+ interface WalletPermission {
109
+ invoker: string;
110
+ accounts: string[];
111
+ grantedAt: number;
112
+ }
52
113
  /** The raw provider object the extension injects at `window.cairn`. */
53
114
  interface CairnProvider {
54
115
  isCairn: true;
55
116
  version: string;
56
117
  connect(): Promise<ProviderReply<ConnectResult>>;
57
118
  getAddress(): Promise<ProviderReply<ConnectResult>>;
119
+ /** @deprecated First-party-only legacy login. Third parties MUST use `signInWithCsd`. */
58
120
  signIn(): Promise<ProviderReply<SignInResult>>;
121
+ /** Audience-bound "Sign in with CSD" — the secure third-party login. */
122
+ signInWithCsd(p: SiwcParams): Promise<ProviderReply<SiwcResult>>;
59
123
  propose(p: ProposeParams): Promise<ProviderReply<TxResult>>;
60
124
  attest(p: AttestParams): Promise<ProviderReply<TxResult>>;
61
125
  send(p: SendParams): Promise<ProviderReply<TxResult>>;
126
+ fillOffer(p: FillParams): Promise<ProviderReply<TxResult>>;
62
127
  sealClaim(p: SealClaimParams): Promise<ProviderReply<TxResult>>;
63
128
  revealClaim(txid: string): Promise<ProviderReply<TxResult>>;
129
+ /** Feature/capability detection (optional; absent on older wallets). */
130
+ getCapabilities?(): Promise<WalletCapabilities>;
131
+ /** EIP-2255-style per-origin permissions (optional; absent on older wallets). */
132
+ getPermissions?(): Promise<ProviderReply<WalletPermission[]>>;
133
+ requestPermissions?(): Promise<ProviderReply<WalletPermission[]>>;
134
+ revokePermissions?(): Promise<ProviderReply<{
135
+ revoked: boolean;
136
+ }>>;
137
+ /** Event subscription (accountsChanged / disconnect) (optional; absent on older wallets). */
138
+ on?(event: string, handler: (data: unknown) => void): void;
139
+ removeListener?(event: string, handler: (data: unknown) => void): void;
64
140
  }
65
141
  /** Every provider call resolves to this discriminated reply (never rejects). */
66
142
  type ProviderReply<T> = {
@@ -84,6 +160,28 @@ interface DetectOptions {
84
160
  * content script hasn't injected yet. Throws `NotInstalledError` on timeout or
85
161
  * outside a browser.
86
162
  */
163
+ /** EIP-6963-style announced wallet info (Cairn's own `csd:` discovery namespace). */
164
+ interface ProviderInfo {
165
+ uuid: string;
166
+ name: string;
167
+ icon: string;
168
+ rdns: string;
169
+ }
170
+ /** A discovered provider + its display info. */
171
+ interface ProviderDetail {
172
+ info: ProviderInfo;
173
+ provider: CairnProvider;
174
+ }
175
+ /**
176
+ * Enumerate all wallets that announce via the `csd:` discovery handshake (EIP-6963 analog) — for a
177
+ * multi-wallet picker. Resolves with everything announced within `timeoutMs` (default 1000ms).
178
+ */
179
+ declare function discoverProviders(opts?: DetectOptions): Promise<ProviderDetail[]>;
180
+ /**
181
+ * Resolve `window.cairn`, via three paths that race: an immediate global, the legacy
182
+ * `cairn#initialized` event, or the `csd:announceProvider` discovery handshake (we dispatch
183
+ * `csd:requestProvider`). Throws `NotInstalledError` on timeout or outside a browser.
184
+ */
87
185
  declare function detectProvider(opts?: DetectOptions): Promise<CairnProvider>;
88
186
  /** True if the Cairn Wallet is already present (no waiting). */
89
187
  declare function isInstalled(): boolean;
@@ -103,8 +201,42 @@ declare class WalletConnection {
103
201
  connect(): Promise<string>;
104
202
  /** Get the connected address (does not force a new connection prompt if already consented). */
105
203
  getAddress(): Promise<string>;
106
- /** Passwordless sign-in: a signature over a login nonce (always prompts; structurally cannot sign a tx). */
204
+ /**
205
+ * @deprecated First-party-only legacy login (it authenticates against the wallet's own configured
206
+ * server and is rejected from third-party origins). Use {@link signInWithCsd} for any site.
207
+ * Passwordless sign-in: a signature over a login nonce (always prompts; structurally cannot sign a tx).
208
+ */
107
209
  signIn(): Promise<SignInResult>;
210
+ /**
211
+ * Audience-bound "Sign in with CSD" (SIWC) — the secure, replay-resistant third-party login.
212
+ * The wallet binds the REAL requesting origin + your single-use server `nonce` into the signed
213
+ * message and returns the artifact; it never mints a session. VERIFY the result SERVER-SIDE with
214
+ * `verifySiwc` from `@inversealtruism/csd-siwc` (domain/nonce/chain/time/signature/account), then
215
+ * issue your OWN session. The signature is proof-of-control, NOT a bearer token. Always prompts.
216
+ */
217
+ signInWithCsd(params: SiwcParams): Promise<SiwcResult>;
218
+ /** True if this wallet supports audience-bound SIWC (safe to call `signInWithCsd`). */
219
+ get supportsSiwc(): boolean;
220
+ /** Feature/capability detection. Returns null if the wallet predates `getCapabilities`. */
221
+ getCapabilities(): Promise<WalletCapabilities | null>;
222
+ /** Atomic fill (CairnX DvP): pay + attest in ONE tx. Always prompts with clear-signing. */
223
+ fillOffer(params: FillParams): Promise<TxResult>;
224
+ /** This origin's current permission grant (EIP-2255-style). Silent; [] if none / unsupported. */
225
+ getPermissions(): Promise<WalletPermission[]>;
226
+ /** Explicitly request permission to see the address (prompts, like connect). */
227
+ requestPermissions(): Promise<WalletPermission[]>;
228
+ /** Revoke THIS origin's own access (silent). Returns { revoked }. Safe no-op on older wallets. */
229
+ revokePermissions(): Promise<{
230
+ revoked: boolean;
231
+ }>;
232
+ /**
233
+ * Subscribe to provider events. `accountsChanged` fires with `[]` when this site loses access
234
+ * (wallet lock / account switch / revoke) — re-call connect() to (re)share; `disconnect` likewise.
235
+ * No-op if the wallet predates events. Use the SAME handler reference with off() to unsubscribe.
236
+ */
237
+ on(event: "accountsChanged" | "disconnect" | (string & {}), handler: (data: any) => void): void;
238
+ /** Remove a previously-added event handler (must be the same reference passed to on()). */
239
+ off(event: string, handler: (data: any) => void): void;
108
240
  /** Send CSD (always prompts with clear-signing; wallet picks inputs + returns change to itself). */
109
241
  send(params: SendParams): Promise<TxResult>;
110
242
  /** Low-level Propose (always prompts). Prefer `cairn.board.propose()` which computes the hash/uri/expiry. */
@@ -124,4 +256,4 @@ declare function connect(opts?: DetectOptions): Promise<{
124
256
  address: string;
125
257
  }>;
126
258
 
127
- export { type AttestParams, type CairnProvider, type ConnectResult, type DetectOptions, type ProposeParams, type ProviderReply, type SealClaimParams, type SendParams, type SignInResult, type TxResult, WalletConnection, connect, detectProvider, getWallet, isInstalled };
259
+ export { type AttestParams, type CairnProvider, type ConnectResult, type DetectOptions, type FillParams, type ProposeParams, type ProviderDetail, type ProviderInfo, type ProviderReply, type SealClaimParams, type SendParams, type SignInResult, type SiwcParams, type SiwcResult, type TxResult, type WalletCapabilities, WalletConnection, type WalletPermission, connect, detectProvider, discoverProviders, getWallet, isInstalled };