@inversealtruism/cairn-sdk 0.1.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.
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/dist/board.cjs +193 -0
- package/dist/board.d.cts +151 -0
- package/dist/board.d.ts +151 -0
- package/dist/board.js +7 -0
- package/dist/chain.cjs +144 -0
- package/dist/chain.d.cts +36 -0
- package/dist/chain.d.ts +36 -0
- package/dist/chain.js +86 -0
- package/dist/chunk-6VWLWF7R.js +121 -0
- package/dist/chunk-BZVJVL4Q.js +76 -0
- package/dist/chunk-ETZYBDFC.js +232 -0
- package/dist/chunk-G7FABIF4.js +60 -0
- package/dist/chunk-OGP2W7DL.js +163 -0
- package/dist/chunk-PHMU3CG5.js +63 -0
- package/dist/chunk-SMAPCKVG.js +118 -0
- package/dist/connect.cjs +177 -0
- package/dist/connect.d.cts +127 -0
- package/dist/connect.d.ts +127 -0
- package/dist/connect.js +15 -0
- package/dist/content.cjs +113 -0
- package/dist/content.d.cts +44 -0
- package/dist/content.d.ts +44 -0
- package/dist/content.js +7 -0
- package/dist/http-D2CzfOsm.d.cts +30 -0
- package/dist/http-D2CzfOsm.d.ts +30 -0
- package/dist/index.cjs +873 -0
- package/dist/index.d.cts +105 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +181 -0
- package/dist/indexer.cjs +256 -0
- package/dist/indexer.d.cts +103 -0
- package/dist/indexer.d.ts +103 -0
- package/dist/indexer.js +6 -0
- package/dist/registry.cjs +77 -0
- package/dist/registry.d.cts +30 -0
- package/dist/registry.d.ts +30 -0
- package/dist/registry.js +26 -0
- package/package.json +97 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,873 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BoardClient: () => BoardClient,
|
|
24
|
+
Cairn: () => Cairn,
|
|
25
|
+
CairnError: () => CairnError,
|
|
26
|
+
Chain: () => Chain,
|
|
27
|
+
ContentClient: () => ContentClient,
|
|
28
|
+
ContentVerificationError: () => ContentVerificationError,
|
|
29
|
+
Http: () => Http,
|
|
30
|
+
HttpError: () => HttpError,
|
|
31
|
+
IndexerClient: () => IndexerClient,
|
|
32
|
+
NotInstalledError: () => NotInstalledError,
|
|
33
|
+
RegistryClient: () => RegistryClient,
|
|
34
|
+
UnsupportedMethodError: () => UnsupportedMethodError,
|
|
35
|
+
UserRejectedError: () => UserRejectedError,
|
|
36
|
+
WalletConnection: () => WalletConnection,
|
|
37
|
+
WalletLockedError: () => WalletLockedError,
|
|
38
|
+
connect: () => connect,
|
|
39
|
+
detectProvider: () => detectProvider,
|
|
40
|
+
getWallet: () => getWallet,
|
|
41
|
+
isInstalled: () => isInstalled
|
|
42
|
+
});
|
|
43
|
+
module.exports = __toCommonJS(index_exports);
|
|
44
|
+
|
|
45
|
+
// src/errors.ts
|
|
46
|
+
var CairnError = class extends Error {
|
|
47
|
+
constructor(message) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = new.target.name;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var NotInstalledError = class extends CairnError {
|
|
53
|
+
constructor(message = "Cairn Wallet not detected. Install it from cairn-substrate.com.") {
|
|
54
|
+
super(message);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var UserRejectedError = class extends CairnError {
|
|
58
|
+
constructor(message = "Request rejected by user.") {
|
|
59
|
+
super(message);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var WalletLockedError = class extends CairnError {
|
|
63
|
+
constructor(message = "Wallet is locked.") {
|
|
64
|
+
super(message);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var UnsupportedMethodError = class extends CairnError {
|
|
68
|
+
constructor(message = "Method not supported by the wallet.") {
|
|
69
|
+
super(message);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var HttpError = class extends CairnError {
|
|
73
|
+
status;
|
|
74
|
+
url;
|
|
75
|
+
body;
|
|
76
|
+
constructor(status, url, body) {
|
|
77
|
+
super(`HTTP ${status} for ${url}${body ? `: ${String(body).slice(0, 200)}` : ""}`);
|
|
78
|
+
this.status = status;
|
|
79
|
+
this.url = url;
|
|
80
|
+
this.body = body;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var ContentVerificationError = class extends CairnError {
|
|
84
|
+
expected;
|
|
85
|
+
constructor(expected) {
|
|
86
|
+
super(`Content does not match its payload hash ${expected} \u2014 bytes were tampered with in transit.`);
|
|
87
|
+
this.expected = expected;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
function mapProviderError(error) {
|
|
91
|
+
const e = String(error || "").toLowerCase();
|
|
92
|
+
if (e.includes("rejected by user")) return new UserRejectedError(error);
|
|
93
|
+
if (e.includes("wallet locked")) return new WalletLockedError(error);
|
|
94
|
+
if (e.includes("unsupported dapp method")) return new UnsupportedMethodError(error);
|
|
95
|
+
return new CairnError(error || "Unknown wallet error");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/http.ts
|
|
99
|
+
var Http = class {
|
|
100
|
+
baseUrl;
|
|
101
|
+
_fetch;
|
|
102
|
+
timeoutMs;
|
|
103
|
+
headers;
|
|
104
|
+
constructor(opts) {
|
|
105
|
+
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
106
|
+
const f = opts.fetch ?? globalThis.fetch;
|
|
107
|
+
if (!f) throw new Error("No fetch available \u2014 pass `fetch` in the SDK options.");
|
|
108
|
+
this._fetch = opts.fetch ? opts.fetch : f.bind(globalThis);
|
|
109
|
+
this.timeoutMs = opts.timeoutMs ?? 15e3;
|
|
110
|
+
this.headers = opts.headers ?? {};
|
|
111
|
+
}
|
|
112
|
+
/** Build an absolute URL from a path + optional query params. */
|
|
113
|
+
url(path, query) {
|
|
114
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
115
|
+
const u = new URL(this.baseUrl + p);
|
|
116
|
+
if (query) {
|
|
117
|
+
for (const [k, v] of Object.entries(query)) {
|
|
118
|
+
if (v !== void 0 && v !== null) u.searchParams.set(k, String(v));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return u.toString();
|
|
122
|
+
}
|
|
123
|
+
async raw(method, path, opts) {
|
|
124
|
+
const url = this.url(path, opts?.query);
|
|
125
|
+
const ctrl = new AbortController();
|
|
126
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
127
|
+
try {
|
|
128
|
+
const res = await this._fetch(url, {
|
|
129
|
+
method,
|
|
130
|
+
signal: ctrl.signal,
|
|
131
|
+
headers: {
|
|
132
|
+
...opts?.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
133
|
+
...this.headers,
|
|
134
|
+
...opts?.headers ?? {}
|
|
135
|
+
},
|
|
136
|
+
body: opts?.body !== void 0 ? JSON.stringify(opts.body) : void 0
|
|
137
|
+
});
|
|
138
|
+
return res;
|
|
139
|
+
} finally {
|
|
140
|
+
clearTimeout(timer);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async getJson(path, query) {
|
|
144
|
+
const res = await this.raw("GET", path, { query });
|
|
145
|
+
if (!res.ok) throw new HttpError(res.status, this.url(path, query), await safeText(res));
|
|
146
|
+
return await res.json();
|
|
147
|
+
}
|
|
148
|
+
async postJson(path, body, headers) {
|
|
149
|
+
const res = await this.raw("POST", path, { body, headers });
|
|
150
|
+
if (!res.ok) throw new HttpError(res.status, this.url(path), await safeText(res));
|
|
151
|
+
return await res.json();
|
|
152
|
+
}
|
|
153
|
+
/** GET raw bytes (for content retrieval). Returns null on 404. */
|
|
154
|
+
async getBytes(path) {
|
|
155
|
+
const res = await this.raw("GET", path);
|
|
156
|
+
if (res.status === 404) return null;
|
|
157
|
+
if (!res.ok) throw new HttpError(res.status, this.url(path), await safeText(res));
|
|
158
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
159
|
+
return { bytes: buf, headers: res.headers };
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
async function safeText(res) {
|
|
163
|
+
try {
|
|
164
|
+
return await res.text();
|
|
165
|
+
} catch {
|
|
166
|
+
return "";
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/chain.ts
|
|
171
|
+
var import_csd_client = require("@inversealtruism/csd-client");
|
|
172
|
+
var import_csd_tx = require("@inversealtruism/csd-tx");
|
|
173
|
+
var import_csd_codec = require("@inversealtruism/csd-codec");
|
|
174
|
+
var import_csd_crypto = require("@inversealtruism/csd-crypto");
|
|
175
|
+
var import_csd_light = require("@inversealtruism/csd-light");
|
|
176
|
+
var import_csd_client2 = require("@inversealtruism/csd-client");
|
|
177
|
+
var import_csd_light2 = require("@inversealtruism/csd-light");
|
|
178
|
+
var Chain = class {
|
|
179
|
+
client;
|
|
180
|
+
opts;
|
|
181
|
+
_light = null;
|
|
182
|
+
constructor(opts) {
|
|
183
|
+
this.opts = opts;
|
|
184
|
+
this.client = new import_csd_client2.CsdClient({ baseUrl: opts.rpcUrl, fetch: opts.fetch, timeoutMs: opts.timeoutMs });
|
|
185
|
+
}
|
|
186
|
+
/** Current chain tip ({ tip, height, chainwork }). */
|
|
187
|
+
tip() {
|
|
188
|
+
return this.client.tip();
|
|
189
|
+
}
|
|
190
|
+
/** Spendable UTXOs + confirmed balance for an address. */
|
|
191
|
+
utxos(addr) {
|
|
192
|
+
return this.client.utxos(addr);
|
|
193
|
+
}
|
|
194
|
+
/** Submit a signed transaction (node-shaped JSON from `signTx().nodeJson`). */
|
|
195
|
+
submit(nodeJsonTx) {
|
|
196
|
+
return this.client.submit(nodeJsonTx);
|
|
197
|
+
}
|
|
198
|
+
/** A verifying light client over the same RPC (built once, reused). */
|
|
199
|
+
light() {
|
|
200
|
+
if (!this._light) {
|
|
201
|
+
this._light = new import_csd_light2.LightClient({ client: this.client });
|
|
202
|
+
}
|
|
203
|
+
return this._light;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// src/board.ts
|
|
208
|
+
var import_csd_codec2 = require("@inversealtruism/csd-codec");
|
|
209
|
+
var import_csd_codec3 = require("@inversealtruism/csd-codec");
|
|
210
|
+
var BoardClient = class {
|
|
211
|
+
http;
|
|
212
|
+
wallet;
|
|
213
|
+
constructor(http, wallet) {
|
|
214
|
+
this.http = http;
|
|
215
|
+
this.wallet = wallet;
|
|
216
|
+
}
|
|
217
|
+
requireWallet() {
|
|
218
|
+
if (!this.wallet) {
|
|
219
|
+
throw new CairnError("This action needs a connected wallet. Construct Cairn with a wallet, or call cairn.connect() first.");
|
|
220
|
+
}
|
|
221
|
+
return this.wallet;
|
|
222
|
+
}
|
|
223
|
+
// ---- reads (public) ----------------------------------------------------
|
|
224
|
+
/** Ranked board for a domain + time window. */
|
|
225
|
+
board(opts = {}) {
|
|
226
|
+
return this.http.getJson("/api/board", { domain: opts.domain ?? "all", window: opts.window ?? "all" });
|
|
227
|
+
}
|
|
228
|
+
/** Alias for `board()` — the top items. */
|
|
229
|
+
top(opts = {}) {
|
|
230
|
+
return this.board(opts);
|
|
231
|
+
}
|
|
232
|
+
/** A single item with its supports + pledges + integrity flag. */
|
|
233
|
+
item(id) {
|
|
234
|
+
return this.http.getJson(`/api/item/${encodeURIComponent(id)}`);
|
|
235
|
+
}
|
|
236
|
+
/** Known + discovered domains. */
|
|
237
|
+
domains() {
|
|
238
|
+
return this.http.getJson("/api/domains");
|
|
239
|
+
}
|
|
240
|
+
/** All quests (work-graph items). */
|
|
241
|
+
quests() {
|
|
242
|
+
return this.http.getJson("/api/quests");
|
|
243
|
+
}
|
|
244
|
+
/** A single quest with claims/submissions/payouts. */
|
|
245
|
+
quest(id) {
|
|
246
|
+
return this.http.getJson(`/api/quest/${encodeURIComponent(id)}`);
|
|
247
|
+
}
|
|
248
|
+
/** An address's on-chain-derived profile + identity + reputation. */
|
|
249
|
+
profile(addr, opts = {}) {
|
|
250
|
+
return this.http.getJson(`/api/profile/${encodeURIComponent(addr)}`, opts.live ? { live: 1 } : void 0);
|
|
251
|
+
}
|
|
252
|
+
/** Reputation leaderboard (top 50). */
|
|
253
|
+
leaderboard() {
|
|
254
|
+
return this.http.getJson("/api/leaderboard");
|
|
255
|
+
}
|
|
256
|
+
/** Network dashboard snapshot. */
|
|
257
|
+
network() {
|
|
258
|
+
return this.http.getJson("/api/network");
|
|
259
|
+
}
|
|
260
|
+
/** Network time-series for charts. */
|
|
261
|
+
networkSeries(range = "24h") {
|
|
262
|
+
return this.http.getJson("/api/network/series", { range });
|
|
263
|
+
}
|
|
264
|
+
/** Recent blocks with miner/reward. */
|
|
265
|
+
networkBlocks(limit = 15) {
|
|
266
|
+
return this.http.getJson("/api/network/blocks", { limit });
|
|
267
|
+
}
|
|
268
|
+
/** Per-miner hashrate estimate. */
|
|
269
|
+
miner(addr) {
|
|
270
|
+
return this.http.getJson(`/api/miner/${encodeURIComponent(addr)}`);
|
|
271
|
+
}
|
|
272
|
+
/** Recent activity feed. */
|
|
273
|
+
activity() {
|
|
274
|
+
return this.http.getJson("/api/activity");
|
|
275
|
+
}
|
|
276
|
+
/** The gamified cairn wall. */
|
|
277
|
+
wall() {
|
|
278
|
+
return this.http.getJson("/api/wall");
|
|
279
|
+
}
|
|
280
|
+
/** Server health + chain reachability. */
|
|
281
|
+
health() {
|
|
282
|
+
return this.http.getJson("/api/health");
|
|
283
|
+
}
|
|
284
|
+
// ---- writes (wallet-signed) -------------------------------------------
|
|
285
|
+
/**
|
|
286
|
+
* Publish a board item: compute the canonical payload hash, have the WALLET
|
|
287
|
+
* build/sign/submit the Propose (clear-signed approval), then register the
|
|
288
|
+
* off-chain content. The payload hash is byte-identical to what the cairn
|
|
289
|
+
* server verifies (csd-codec.payloadHash over { v:1, domain, title, body, links }).
|
|
290
|
+
*/
|
|
291
|
+
async propose(input) {
|
|
292
|
+
const wallet = this.requireWallet();
|
|
293
|
+
const content = {
|
|
294
|
+
v: 1,
|
|
295
|
+
domain: input.domain,
|
|
296
|
+
title: input.title,
|
|
297
|
+
body: input.body ?? "",
|
|
298
|
+
links: input.links ?? []
|
|
299
|
+
};
|
|
300
|
+
const ph = (0, import_csd_codec2.payloadHash)(content);
|
|
301
|
+
const uri = `cairn:v1:${ph.slice(2, 14)}`;
|
|
302
|
+
const lifetime = input.lifetimeEpochs ?? 720;
|
|
303
|
+
const height = await this.tipHeight();
|
|
304
|
+
const expiresEpoch = Math.floor(height / import_csd_codec3.EPOCH_LEN) + lifetime;
|
|
305
|
+
const fee = input.fee ?? import_csd_codec3.MIN_FEE_PROPOSE;
|
|
306
|
+
const r = await wallet.propose({ domain: content.domain, payloadHash: ph, uri, expiresEpoch, fee });
|
|
307
|
+
const txid2 = String(r.txid ?? "");
|
|
308
|
+
if (!txid2) throw new CairnError("Propose submitted but no txid was returned by the wallet.");
|
|
309
|
+
const reg = await this.tryRegister(content, txid2);
|
|
310
|
+
return { txid: txid2, payloadHash: ph, content, registered: reg.ok, registerError: reg.error };
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Register off-chain content for an already-submitted proposal txid. Idempotent.
|
|
314
|
+
* Returns { ok } — the cairn server verifies sha256(canonical) == on-chain payload_hash.
|
|
315
|
+
*/
|
|
316
|
+
async registerContent(content, txid2) {
|
|
317
|
+
try {
|
|
318
|
+
const res = await this.http.postJson("/api/content", {
|
|
319
|
+
domain: content.domain,
|
|
320
|
+
title: content.title,
|
|
321
|
+
body: content.body,
|
|
322
|
+
links: content.links,
|
|
323
|
+
txid: txid2
|
|
324
|
+
});
|
|
325
|
+
return { ok: !!res.ok, error: res.ok ? void 0 : res.error };
|
|
326
|
+
} catch (e) {
|
|
327
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/** Support/attest a proposal (wallet-signed; always clear-signed). */
|
|
331
|
+
async support(proposalId, opts = {}) {
|
|
332
|
+
const wallet = this.requireWallet();
|
|
333
|
+
return wallet.attest({
|
|
334
|
+
proposalId,
|
|
335
|
+
score: opts.score ?? 80,
|
|
336
|
+
confidence: opts.confidence ?? 70,
|
|
337
|
+
fee: opts.fee ?? import_csd_codec3.MIN_FEE_ATTEST
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
// ---- internal ----------------------------------------------------------
|
|
341
|
+
async tipHeight() {
|
|
342
|
+
const tip = await this.http.getJson("/api/rpc/tip");
|
|
343
|
+
const h = Number(tip?.height);
|
|
344
|
+
if (!Number.isFinite(h)) throw new CairnError("Could not read chain tip height for expiry computation.");
|
|
345
|
+
return h;
|
|
346
|
+
}
|
|
347
|
+
async tryRegister(content, txid2) {
|
|
348
|
+
const delays = [0, 1500, 3e3, 5e3];
|
|
349
|
+
let last = { ok: false, error: "not attempted" };
|
|
350
|
+
for (const d of delays) {
|
|
351
|
+
if (d) await sleep(d);
|
|
352
|
+
last = await this.registerContent(content, txid2);
|
|
353
|
+
if (last.ok) return last;
|
|
354
|
+
if (!/not found yet|not found/i.test(last.error ?? "")) return last;
|
|
355
|
+
}
|
|
356
|
+
return last;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
function sleep(ms) {
|
|
360
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// src/indexer.ts
|
|
364
|
+
var import_csd_codec4 = require("@inversealtruism/csd-codec");
|
|
365
|
+
var IndexerClient = class {
|
|
366
|
+
http;
|
|
367
|
+
opts;
|
|
368
|
+
constructor(http, opts = {}) {
|
|
369
|
+
this.http = http;
|
|
370
|
+
this.opts = opts;
|
|
371
|
+
}
|
|
372
|
+
// ---- REST --------------------------------------------------------------
|
|
373
|
+
health() {
|
|
374
|
+
return this.http.getJson("/health");
|
|
375
|
+
}
|
|
376
|
+
tipHeight() {
|
|
377
|
+
return this.http.getJson("/blocks/tip/height");
|
|
378
|
+
}
|
|
379
|
+
tipHash() {
|
|
380
|
+
return this.http.getJson("/blocks/tip/hash");
|
|
381
|
+
}
|
|
382
|
+
blockHashAtHeight(h) {
|
|
383
|
+
return this.http.getJson(`/block-height/${h}`);
|
|
384
|
+
}
|
|
385
|
+
block(hash) {
|
|
386
|
+
return this.http.getJson(`/block/${hash}`);
|
|
387
|
+
}
|
|
388
|
+
blockTxids(hash) {
|
|
389
|
+
return this.http.getJson(`/block/${hash}/txids`);
|
|
390
|
+
}
|
|
391
|
+
tx(id) {
|
|
392
|
+
return this.http.getJson(`/tx/${id}`);
|
|
393
|
+
}
|
|
394
|
+
txStatus(id) {
|
|
395
|
+
return this.http.getJson(`/tx/${id}/status`);
|
|
396
|
+
}
|
|
397
|
+
txMerkleProof(id) {
|
|
398
|
+
return this.http.getJson(`/tx/${id}/merkle-proof`);
|
|
399
|
+
}
|
|
400
|
+
address(a) {
|
|
401
|
+
return this.http.getJson(`/address/${a}`);
|
|
402
|
+
}
|
|
403
|
+
addressTxs(a) {
|
|
404
|
+
return this.http.getJson(`/address/${a}/txs`);
|
|
405
|
+
}
|
|
406
|
+
addressUtxo(a) {
|
|
407
|
+
return this.http.getJson(`/address/${a}/utxo`);
|
|
408
|
+
}
|
|
409
|
+
reputation(a) {
|
|
410
|
+
return this.http.getJson(`/address/${a}/reputation`);
|
|
411
|
+
}
|
|
412
|
+
domains() {
|
|
413
|
+
return this.http.getJson("/domains");
|
|
414
|
+
}
|
|
415
|
+
domainProposals(d) {
|
|
416
|
+
return this.http.getJson(`/domain/${encodeURIComponent(d)}/proposals`);
|
|
417
|
+
}
|
|
418
|
+
proposal(id) {
|
|
419
|
+
return this.http.getJson(`/proposal/${id}`);
|
|
420
|
+
}
|
|
421
|
+
attestations(id) {
|
|
422
|
+
return this.http.getJson(`/proposal/${id}/attestations`);
|
|
423
|
+
}
|
|
424
|
+
registryPeers() {
|
|
425
|
+
return this.http.getJson("/registry/peers");
|
|
426
|
+
}
|
|
427
|
+
registryGateways() {
|
|
428
|
+
return this.http.getJson("/registry/gateways");
|
|
429
|
+
}
|
|
430
|
+
identity(handle) {
|
|
431
|
+
return this.http.getJson(`/identity/${encodeURIComponent(handle)}`);
|
|
432
|
+
}
|
|
433
|
+
reverseIdentity(addr) {
|
|
434
|
+
return this.http.getJson(`/address/${addr}/identity`);
|
|
435
|
+
}
|
|
436
|
+
// ---- trust-minimized inclusion ----------------------------------------
|
|
437
|
+
/**
|
|
438
|
+
* Verify a tx is included in a block by folding its merkle branch to the root
|
|
439
|
+
* with csd-codec.verifyMerkleProof. If `headerMerkleAt` was provided, the
|
|
440
|
+
* proof's root is cross-checked against the ON-CHAIN header merkle at that
|
|
441
|
+
* height → trustLevel "verified-inclusion". Otherwise the proof is only
|
|
442
|
+
* internally consistent → "proof-consistent" (still useful, but the indexer is trusted).
|
|
443
|
+
*/
|
|
444
|
+
async verifyInclusion(txid2) {
|
|
445
|
+
let proof;
|
|
446
|
+
try {
|
|
447
|
+
proof = await this.txMerkleProof(txid2);
|
|
448
|
+
} catch {
|
|
449
|
+
return { included: false, trustLevel: "not-found", reason: "no merkle proof (tx not indexed)" };
|
|
450
|
+
}
|
|
451
|
+
const folds = (0, import_csd_codec4.verifyMerkleProof)(txid2, proof.pos, proof.merkle, proof.merkle_root);
|
|
452
|
+
if (!folds) {
|
|
453
|
+
return { included: false, trustLevel: "not-found", blockHeight: proof.block_height, reason: "merkle branch does not fold to the claimed root" };
|
|
454
|
+
}
|
|
455
|
+
if (this.opts.headerMerkleAt) {
|
|
456
|
+
try {
|
|
457
|
+
const onchain = await this.opts.headerMerkleAt(proof.block_height);
|
|
458
|
+
if (norm(onchain) !== norm(proof.merkle_root)) {
|
|
459
|
+
return { included: false, trustLevel: "not-found", blockHeight: proof.block_height, reason: "proof root != on-chain header merkle (indexer disagrees with chain)" };
|
|
460
|
+
}
|
|
461
|
+
return { included: true, trustLevel: "verified-inclusion", blockHeight: proof.block_height, pos: proof.pos, merkleRoot: proof.merkle_root };
|
|
462
|
+
} catch {
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return { included: true, trustLevel: "proof-consistent", blockHeight: proof.block_height, pos: proof.pos, merkleRoot: proof.merkle_root };
|
|
466
|
+
}
|
|
467
|
+
// ---- live streams ------------------------------------------------------
|
|
468
|
+
/** Stream all events (blocks, proposals, attestations, reorgs) via SSE. */
|
|
469
|
+
streamAll(handlers) {
|
|
470
|
+
return this.sse("/stream/all", handlers);
|
|
471
|
+
}
|
|
472
|
+
/** Stream blocks + reorgs only. */
|
|
473
|
+
streamBlocks(handlers) {
|
|
474
|
+
return this.sse("/stream/blocks", handlers);
|
|
475
|
+
}
|
|
476
|
+
/** Stream proposals (+ reorgs) for a domain. */
|
|
477
|
+
streamDomain(domain, handlers) {
|
|
478
|
+
return this.sse(`/stream/domain/${encodeURIComponent(domain)}`, handlers);
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Subscribe over WebSocket with selective tracking. Returns a handle with
|
|
482
|
+
* `.close()`. `onEvent` receives every matching IndexEvent (incl. reorgs).
|
|
483
|
+
*/
|
|
484
|
+
subscribe(sub, onEvent, onError) {
|
|
485
|
+
const WS = this.opts.WebSocketImpl ?? globalThis.WebSocket;
|
|
486
|
+
if (!WS) {
|
|
487
|
+
throw new Error("No WebSocket available \u2014 pass WebSocketImpl in the indexer options (e.g. in older Node).");
|
|
488
|
+
}
|
|
489
|
+
const wsUrl = this.http.url("/ws").replace(/^http/, "ws");
|
|
490
|
+
let closed = false;
|
|
491
|
+
const ws = new WS(wsUrl);
|
|
492
|
+
ws.addEventListener("open", () => {
|
|
493
|
+
const msg = {};
|
|
494
|
+
if (sub.all) msg["track-all"] = true;
|
|
495
|
+
if (sub.domains?.length) msg["track-domain"] = sub.domains;
|
|
496
|
+
if (sub.addresses?.length) msg["track-address"] = sub.addresses;
|
|
497
|
+
if (sub.proposals?.length) msg["track-proposal"] = sub.proposals;
|
|
498
|
+
ws.send(JSON.stringify(Object.keys(msg).length ? msg : { "track-all": true }));
|
|
499
|
+
});
|
|
500
|
+
ws.addEventListener("message", (ev) => {
|
|
501
|
+
try {
|
|
502
|
+
const data = JSON.parse(typeof ev.data === "string" ? ev.data : String(ev.data));
|
|
503
|
+
if (data && data.kind) onEvent(data);
|
|
504
|
+
} catch {
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
if (onError) ws.addEventListener("error", (e) => onError(e));
|
|
508
|
+
return {
|
|
509
|
+
close() {
|
|
510
|
+
if (closed) return;
|
|
511
|
+
closed = true;
|
|
512
|
+
try {
|
|
513
|
+
ws.close();
|
|
514
|
+
} catch {
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
// ---- internal SSE over fetch (portable + auto-reconnect) ---------------
|
|
520
|
+
sse(path, handlers) {
|
|
521
|
+
const fetchImpl = this.opts.fetch ?? globalThis.fetch;
|
|
522
|
+
const url = this.http.url(path);
|
|
523
|
+
let closed = false;
|
|
524
|
+
let ctrl = null;
|
|
525
|
+
const run = async () => {
|
|
526
|
+
while (!closed) {
|
|
527
|
+
ctrl = new AbortController();
|
|
528
|
+
try {
|
|
529
|
+
const res = await fetchImpl(url, { signal: ctrl.signal, headers: { accept: "text/event-stream" } });
|
|
530
|
+
if (!res.ok || !res.body) throw new Error(`SSE ${res.status}`);
|
|
531
|
+
handlers.onOpen?.();
|
|
532
|
+
const reader = res.body.getReader();
|
|
533
|
+
const decoder = new TextDecoder();
|
|
534
|
+
let buf = "";
|
|
535
|
+
for (; ; ) {
|
|
536
|
+
const { value, done } = await reader.read();
|
|
537
|
+
if (done) break;
|
|
538
|
+
buf += decoder.decode(value, { stream: true });
|
|
539
|
+
let idx;
|
|
540
|
+
while ((idx = buf.indexOf("\n\n")) >= 0) {
|
|
541
|
+
const frame = buf.slice(0, idx);
|
|
542
|
+
buf = buf.slice(idx + 2);
|
|
543
|
+
dispatchFrame(frame, handlers);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
} catch (err) {
|
|
547
|
+
if (closed) return;
|
|
548
|
+
handlers.onError?.(err);
|
|
549
|
+
}
|
|
550
|
+
if (closed) return;
|
|
551
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
void run();
|
|
555
|
+
return {
|
|
556
|
+
close() {
|
|
557
|
+
if (closed) return;
|
|
558
|
+
closed = true;
|
|
559
|
+
try {
|
|
560
|
+
ctrl?.abort();
|
|
561
|
+
} catch {
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
function dispatchFrame(frame, h) {
|
|
568
|
+
let event = "message";
|
|
569
|
+
const dataLines = [];
|
|
570
|
+
for (const line of frame.split("\n")) {
|
|
571
|
+
if (line.startsWith(":")) continue;
|
|
572
|
+
if (line.startsWith("event:")) event = line.slice(6).trim();
|
|
573
|
+
else if (line.startsWith("data:")) dataLines.push(line.slice(5).trim());
|
|
574
|
+
}
|
|
575
|
+
if (event === "hello" || dataLines.length === 0) return;
|
|
576
|
+
let parsed;
|
|
577
|
+
try {
|
|
578
|
+
parsed = JSON.parse(dataLines.join("\n"));
|
|
579
|
+
} catch {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
h.onEvent?.(parsed);
|
|
583
|
+
if (parsed.kind === "block") h.onBlock?.(parsed);
|
|
584
|
+
else if (parsed.kind === "proposal") h.onProposal?.(parsed);
|
|
585
|
+
else if (parsed.kind === "attestation") h.onAttestation?.(parsed);
|
|
586
|
+
else if (parsed.kind === "reorg") h.onReorg?.(parsed);
|
|
587
|
+
}
|
|
588
|
+
function norm(h) {
|
|
589
|
+
return String(h).toLowerCase().replace(/^0x/, "");
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// src/content.ts
|
|
593
|
+
var import_csd_codec5 = require("@inversealtruism/csd-codec");
|
|
594
|
+
var ContentClient = class {
|
|
595
|
+
sources;
|
|
596
|
+
constructor(opts) {
|
|
597
|
+
this.sources = [];
|
|
598
|
+
if (opts.swarm) this.sources.push({ name: "swarm", http: opts.swarm, path: (h) => `/content/${h}` });
|
|
599
|
+
if (opts.indexer) this.sources.push({ name: "indexer", http: opts.indexer, path: (h) => `/content/${h}` });
|
|
600
|
+
if (opts.cairn) this.sources.push({ name: "cairn", http: opts.cairn, path: (h) => `/content/${h}` });
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Compute the canonical hash + bytes for an object, ready to anchor on-chain.
|
|
604
|
+
* This is byte-identical to what the cairn server and wallet compute.
|
|
605
|
+
*/
|
|
606
|
+
prepare(obj) {
|
|
607
|
+
const canonical = (0, import_csd_codec5.canonicalJson)(obj);
|
|
608
|
+
return {
|
|
609
|
+
payloadHash: (0, import_csd_codec5.payloadHash)(obj),
|
|
610
|
+
canonical,
|
|
611
|
+
bytes: new TextEncoder().encode(canonical)
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
/** Alias for `prepare()` — emphasises this does not itself publish (anchor via board.propose). */
|
|
615
|
+
put(obj) {
|
|
616
|
+
return this.prepare(obj);
|
|
617
|
+
}
|
|
618
|
+
/** Just the payload hash for an object. */
|
|
619
|
+
hash(obj) {
|
|
620
|
+
return (0, import_csd_codec5.payloadHash)(obj);
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Fetch content by hash and VERIFY it self-certifies (sha256(bytes) == hash).
|
|
624
|
+
* Tries each configured source in order. Returns raw bytes, or null if no
|
|
625
|
+
* source holds it. Throws ContentVerificationError if a source returns bytes
|
|
626
|
+
* that don't match the hash (tampering / wrong object).
|
|
627
|
+
*/
|
|
628
|
+
async getBytes(hash) {
|
|
629
|
+
const h = normalizeHash(hash);
|
|
630
|
+
for (const src of this.sources) {
|
|
631
|
+
let got = null;
|
|
632
|
+
try {
|
|
633
|
+
got = await src.http.getBytes(src.path(h));
|
|
634
|
+
} catch {
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
if (!got) continue;
|
|
638
|
+
if (!(0, import_csd_codec5.verifyContentBytes)(got.bytes, h)) {
|
|
639
|
+
throw new ContentVerificationError(h);
|
|
640
|
+
}
|
|
641
|
+
return got.bytes;
|
|
642
|
+
}
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
/** Fetch + verify + parse as UTF-8 JSON. Returns null if not found. */
|
|
646
|
+
async get(hash) {
|
|
647
|
+
const bytes = await this.getBytes(hash);
|
|
648
|
+
if (!bytes) return null;
|
|
649
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
650
|
+
}
|
|
651
|
+
/** Verify already-fetched bytes against a hash (no network). */
|
|
652
|
+
verify(bytes, hash) {
|
|
653
|
+
return (0, import_csd_codec5.verifyContentBytes)(bytes, normalizeHash(hash));
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
function normalizeHash(hash) {
|
|
657
|
+
const h = hash.startsWith("0x") || hash.startsWith("0X") ? hash : `0x${hash}`;
|
|
658
|
+
return h.toLowerCase();
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// src/registry.ts
|
|
662
|
+
var import_csd_registry = require("@inversealtruism/csd-registry");
|
|
663
|
+
var import_csd_registry2 = require("@inversealtruism/csd-registry");
|
|
664
|
+
var RegistryClient = class {
|
|
665
|
+
src;
|
|
666
|
+
constructor(opts) {
|
|
667
|
+
this.src = { baseUrl: opts.baseUrl.replace(/\/+$/, ""), fetch: opts.fetch };
|
|
668
|
+
}
|
|
669
|
+
/** Discover ranked content/pin gateways (GET /registry/gateways). */
|
|
670
|
+
gateways() {
|
|
671
|
+
return (0, import_csd_registry.discoverGateways)(this.src);
|
|
672
|
+
}
|
|
673
|
+
/** Discover ranked libp2p peers (GET /registry/peers). */
|
|
674
|
+
peers() {
|
|
675
|
+
return (0, import_csd_registry.discoverPeers)(this.src);
|
|
676
|
+
}
|
|
677
|
+
/** Resolve a handle → identity (GET /identity/:handle). Returns null if unresolved. */
|
|
678
|
+
resolveName(handle) {
|
|
679
|
+
return (0, import_csd_registry.resolveName)(this.src, handle);
|
|
680
|
+
}
|
|
681
|
+
/** Reverse-resolve an address → identity (GET /address/:addr/identity). */
|
|
682
|
+
reverseName(address) {
|
|
683
|
+
return (0, import_csd_registry.reverseName)(this.src, address);
|
|
684
|
+
}
|
|
685
|
+
/** The deterministic, client-side resolvers (recompute from raw ChainRecord[]). */
|
|
686
|
+
get fromRecords() {
|
|
687
|
+
return import_csd_registry.fromRecords;
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
// src/connect.ts
|
|
692
|
+
function detectProvider(opts = {}) {
|
|
693
|
+
const timeoutMs = opts.timeoutMs ?? 3e3;
|
|
694
|
+
return new Promise((resolve, reject) => {
|
|
695
|
+
if (typeof window === "undefined") {
|
|
696
|
+
reject(new NotInstalledError("Not running in a browser \u2014 `window.cairn` is unavailable."));
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
if (window.cairn?.isCairn) {
|
|
700
|
+
resolve(window.cairn);
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
let done = false;
|
|
704
|
+
const onInit = () => {
|
|
705
|
+
if (done) return;
|
|
706
|
+
if (window.cairn?.isCairn) {
|
|
707
|
+
done = true;
|
|
708
|
+
cleanup();
|
|
709
|
+
resolve(window.cairn);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
const timer = setTimeout(() => {
|
|
713
|
+
if (done) return;
|
|
714
|
+
done = true;
|
|
715
|
+
cleanup();
|
|
716
|
+
reject(new NotInstalledError());
|
|
717
|
+
}, timeoutMs);
|
|
718
|
+
const cleanup = () => {
|
|
719
|
+
clearTimeout(timer);
|
|
720
|
+
window.removeEventListener("cairn#initialized", onInit);
|
|
721
|
+
};
|
|
722
|
+
window.addEventListener("cairn#initialized", onInit);
|
|
723
|
+
onInit();
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
function isInstalled() {
|
|
727
|
+
return typeof window !== "undefined" && !!window.cairn?.isCairn;
|
|
728
|
+
}
|
|
729
|
+
function unwrap(reply) {
|
|
730
|
+
if (reply && reply.ok) return reply.result;
|
|
731
|
+
throw mapProviderError(reply?.error ?? "Unknown wallet error");
|
|
732
|
+
}
|
|
733
|
+
var WalletConnection = class {
|
|
734
|
+
provider;
|
|
735
|
+
_addr = null;
|
|
736
|
+
constructor(provider) {
|
|
737
|
+
this.provider = provider;
|
|
738
|
+
}
|
|
739
|
+
/** The wallet's version string (e.g. "0.2.x"). */
|
|
740
|
+
get version() {
|
|
741
|
+
return this.provider.version;
|
|
742
|
+
}
|
|
743
|
+
/** The connected address, if `connect()`/`getAddress()` has resolved. */
|
|
744
|
+
get address() {
|
|
745
|
+
return this._addr;
|
|
746
|
+
}
|
|
747
|
+
/** Request connection. First time per origin prompts the user; afterwards may be silent. */
|
|
748
|
+
async connect() {
|
|
749
|
+
const { addr } = unwrap(await this.provider.connect());
|
|
750
|
+
this._addr = addr;
|
|
751
|
+
return addr;
|
|
752
|
+
}
|
|
753
|
+
/** Get the connected address (does not force a new connection prompt if already consented). */
|
|
754
|
+
async getAddress() {
|
|
755
|
+
const { addr } = unwrap(await this.provider.getAddress());
|
|
756
|
+
this._addr = addr;
|
|
757
|
+
return addr;
|
|
758
|
+
}
|
|
759
|
+
/** Passwordless sign-in: a signature over a login nonce (always prompts; structurally cannot sign a tx). */
|
|
760
|
+
signIn() {
|
|
761
|
+
return Promise.resolve(this.provider.signIn()).then(unwrap);
|
|
762
|
+
}
|
|
763
|
+
/** Send CSD (always prompts with clear-signing; wallet picks inputs + returns change to itself). */
|
|
764
|
+
send(params) {
|
|
765
|
+
return Promise.resolve(this.provider.send(params)).then(unwrap);
|
|
766
|
+
}
|
|
767
|
+
/** Low-level Propose (always prompts). Prefer `cairn.board.propose()` which computes the hash/uri/expiry. */
|
|
768
|
+
propose(params) {
|
|
769
|
+
return Promise.resolve(this.provider.propose(params)).then(unwrap);
|
|
770
|
+
}
|
|
771
|
+
/** Attest/support a proposal (always prompts). */
|
|
772
|
+
attest(params) {
|
|
773
|
+
return Promise.resolve(this.provider.attest(params)).then(unwrap);
|
|
774
|
+
}
|
|
775
|
+
/** Seal a commit-reveal claim (always prompts; salt stays inside the wallet). */
|
|
776
|
+
sealClaim(params) {
|
|
777
|
+
return Promise.resolve(this.provider.sealClaim(params)).then(unwrap);
|
|
778
|
+
}
|
|
779
|
+
/** Reveal a previously sealed claim by its sealing txid (always prompts). */
|
|
780
|
+
revealClaim(txid2) {
|
|
781
|
+
if (typeof txid2 !== "string" || !txid2) {
|
|
782
|
+
return Promise.reject(new UnsupportedMethodError("revealClaim requires the sealing txid"));
|
|
783
|
+
}
|
|
784
|
+
return Promise.resolve(this.provider.revealClaim(txid2)).then(unwrap);
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
async function getWallet(opts = {}) {
|
|
788
|
+
return new WalletConnection(await detectProvider(opts));
|
|
789
|
+
}
|
|
790
|
+
async function connect(opts = {}) {
|
|
791
|
+
const wallet = await getWallet(opts);
|
|
792
|
+
const address = await wallet.connect();
|
|
793
|
+
return { wallet, address };
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// src/index.ts
|
|
797
|
+
var DEFAULT_CAIRN = "https://cairn-substrate.com";
|
|
798
|
+
var Cairn = class {
|
|
799
|
+
config;
|
|
800
|
+
chain;
|
|
801
|
+
index;
|
|
802
|
+
content;
|
|
803
|
+
registry;
|
|
804
|
+
board;
|
|
805
|
+
wallet;
|
|
806
|
+
cairnHttp;
|
|
807
|
+
constructor(config = {}) {
|
|
808
|
+
this.config = config;
|
|
809
|
+
const fetch = config.fetch;
|
|
810
|
+
const timeoutMs = config.timeoutMs;
|
|
811
|
+
const cairn = (config.baseUrls?.cairn ?? DEFAULT_CAIRN).replace(/\/+$/, "");
|
|
812
|
+
const rpc = config.baseUrls?.rpc ?? `${cairn}/api/rpc`;
|
|
813
|
+
const indexer = (config.baseUrls?.indexer ?? `${cairn}/explorer/api`).replace(/\/+$/, "");
|
|
814
|
+
this.cairnHttp = new Http({ baseUrl: cairn, fetch, timeoutMs });
|
|
815
|
+
const indexerHttp = new Http({ baseUrl: indexer, fetch, timeoutMs });
|
|
816
|
+
const swarmHttp = config.baseUrls?.swarm ? new Http({ baseUrl: config.baseUrls.swarm.replace(/\/+$/, ""), fetch, timeoutMs }) : void 0;
|
|
817
|
+
this.chain = new Chain({ rpcUrl: rpc, fetch, timeoutMs });
|
|
818
|
+
this.wallet = config.wallet;
|
|
819
|
+
this.board = new BoardClient(this.cairnHttp, this.wallet);
|
|
820
|
+
this.index = new IndexerClient(indexerHttp, {
|
|
821
|
+
fetch,
|
|
822
|
+
WebSocketImpl: config.WebSocketImpl,
|
|
823
|
+
// Upgrade inclusion proofs to fully trust-minimized by cross-checking the
|
|
824
|
+
// proof root against the on-chain header merkle at that height.
|
|
825
|
+
headerMerkleAt: async (h) => {
|
|
826
|
+
const block = await this.chain.client.blockByHeight(h);
|
|
827
|
+
return String(block.header.merkle);
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
this.content = new ContentClient({ swarm: swarmHttp, indexer: indexerHttp, cairn: this.cairnHttp });
|
|
831
|
+
this.registry = new RegistryClient({ baseUrl: indexer, fetch });
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Detect + connect the Cairn Wallet (browser only). Wires the wallet into
|
|
835
|
+
* `cairn.board` for signed writes. Resolves to the connected address.
|
|
836
|
+
*/
|
|
837
|
+
async connect(opts = {}) {
|
|
838
|
+
const { wallet, address } = await connect(opts);
|
|
839
|
+
this.attachWallet(wallet);
|
|
840
|
+
return address;
|
|
841
|
+
}
|
|
842
|
+
/** Attach an already-connected wallet (e.g. one you constructed yourself). */
|
|
843
|
+
attachWallet(wallet) {
|
|
844
|
+
this.wallet = wallet;
|
|
845
|
+
this.board = new BoardClient(this.cairnHttp, wallet);
|
|
846
|
+
}
|
|
847
|
+
/** True if a wallet is attached. */
|
|
848
|
+
get connected() {
|
|
849
|
+
return !!this.wallet;
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
853
|
+
0 && (module.exports = {
|
|
854
|
+
BoardClient,
|
|
855
|
+
Cairn,
|
|
856
|
+
CairnError,
|
|
857
|
+
Chain,
|
|
858
|
+
ContentClient,
|
|
859
|
+
ContentVerificationError,
|
|
860
|
+
Http,
|
|
861
|
+
HttpError,
|
|
862
|
+
IndexerClient,
|
|
863
|
+
NotInstalledError,
|
|
864
|
+
RegistryClient,
|
|
865
|
+
UnsupportedMethodError,
|
|
866
|
+
UserRejectedError,
|
|
867
|
+
WalletConnection,
|
|
868
|
+
WalletLockedError,
|
|
869
|
+
connect,
|
|
870
|
+
detectProvider,
|
|
871
|
+
getWallet,
|
|
872
|
+
isInstalled
|
|
873
|
+
});
|