@inversealtruism/cairn-sdk 0.3.0 → 0.4.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/dist/board.cjs +1 -1
- package/dist/board.js +2 -2
- package/dist/{chunk-4RDXEW35.js → chunk-2L5QNDAE.js} +1 -1
- package/dist/{chunk-NLCF5T2F.js → chunk-2ZML7IKV.js} +1 -1
- package/dist/{chunk-UMXLWUEF.js → chunk-GTTLMWGY.js} +1 -1
- package/dist/{chunk-E2Z3XSCW.js → chunk-MREZG2HN.js} +2 -2
- package/dist/{chunk-QV2GIXO7.js → chunk-RKR3JMIA.js} +34 -16
- package/dist/connect.cjs +34 -17
- package/dist/connect.d.cts +7 -1
- package/dist/connect.d.ts +7 -1
- package/dist/connect.js +2 -2
- package/dist/content.cjs +1 -1
- package/dist/content.js +2 -2
- package/dist/controller.cjs +34 -17
- package/dist/controller.js +3 -3
- package/dist/index.cjs +164 -51
- package/dist/index.d.cts +40 -17
- package/dist/index.d.ts +40 -17
- package/dist/index.js +139 -37
- package/dist/react.cjs +34 -17
- package/dist/react.js +3 -3
- package/package.json +18 -25
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export { ContentClientOptions, PreparedContent } from './content.js';
|
|
|
10
10
|
import { RegistryClient } from './registry.js';
|
|
11
11
|
import { WalletConnection, DetectOptions } from './connect.js';
|
|
12
12
|
export { AttestParams, CairnProvider, ConnectResult, FillParams, ProposeParams, ProviderDetail, ProviderInfo, SealClaimParams, SendParams, SignInResult, SiwcParams, SiwcResult, TxResult, WalletCapabilities, WalletPermission, connect, detectProvider, discoverProviders, getWallet, isInstalled } from './connect.js';
|
|
13
|
+
import { ProvenOfferTerms } from '@inversealtruism/cairnx-core';
|
|
14
|
+
export { FillEndorsement, FillOutputPlan, FillPreview, FillSafety, ProvenOfferTerms, bindOfferTerms, feeBpsAt, fillEndorsement, fillIsSafe, fillOutputPlan, previewFill, requiredFillOutputs } from '@inversealtruism/cairnx-core';
|
|
13
15
|
import { InclusionResult } from '@inversealtruism/csd-light';
|
|
14
16
|
export { CairnController, CairnControllerOptions, CairnState } from './controller.js';
|
|
15
17
|
import '@inversealtruism/csd-client';
|
|
@@ -104,15 +106,6 @@ declare class NamesClient {
|
|
|
104
106
|
state(): Promise<unknown>;
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
/** The fee/rebate-relevant fields of an offer, derived from the MERKLE-PROVEN offer (never a served object). */
|
|
108
|
-
interface ProvenOfferTerms {
|
|
109
|
-
height: number;
|
|
110
|
-
feeBps: number;
|
|
111
|
-
value?: string;
|
|
112
|
-
taker?: string;
|
|
113
|
-
bid?: string;
|
|
114
|
-
min?: string;
|
|
115
|
-
}
|
|
116
109
|
/** The trust-labeled verdict. `ok:false` + `transient:true` = a retryable chain-catching-up soft-fail (NOT a
|
|
117
110
|
* proven mismatch). `payto`/`seller`/`terms` are populated whenever they could be merkle-derived. */
|
|
118
111
|
interface OfferFillCheck {
|
|
@@ -126,12 +119,17 @@ interface OfferFillCheck {
|
|
|
126
119
|
seller?: string;
|
|
127
120
|
terms?: ProvenOfferTerms;
|
|
128
121
|
blockHeight?: number;
|
|
122
|
+
/** B7b (REBIND W3/M1): the PROVEN CSD outputs to build for `pay` (a WHOLE fill of a non-partial offer),
|
|
123
|
+
* sized from the merkle-proven offer by the single-sourced `fillOutputPlan`. Present only when `pay` was
|
|
124
|
+
* passed and the offer could be sized. Build EXACTLY these; never the resolver-served outputs a lying
|
|
125
|
+
* resolver could redirect. A partial (min-bearing) offer is left unsized (its running paid/delivered is
|
|
126
|
+
* not provable at this corroboration layer; the wallet's fill-SPV is the payment-grade sizer). */
|
|
127
|
+
outputPlan?: {
|
|
128
|
+
to: string;
|
|
129
|
+
value: bigint;
|
|
130
|
+
}[];
|
|
129
131
|
}
|
|
130
|
-
|
|
131
|
-
declare const feeBpsAt: (height: number) => number;
|
|
132
|
-
/** true iff any fee/rebate/partial-sizing field of the SERVED offer diverges from the merkle-proven terms `t`
|
|
133
|
-
* (fail-closed: any divergence => the caller refuses). Byte-identical to cairnx-core `bindOfferTerms`. */
|
|
134
|
-
declare function bindOfferTerms(servedOffer: unknown, t: ProvenOfferTerms): boolean;
|
|
132
|
+
|
|
135
133
|
/**
|
|
136
134
|
* Pre-verify an offer on-chain before building a `fillOffer` payment. `light` is a PoW-verifying light client
|
|
137
135
|
* (its `verifyTxInclusion` surfaces the merkle-proven tx + committed appPayloadHash); `client` reads source txs
|
|
@@ -147,11 +145,20 @@ declare function preverifyOffer(opts: {
|
|
|
147
145
|
};
|
|
148
146
|
offerId: string;
|
|
149
147
|
servedOffer?: unknown;
|
|
148
|
+
/** B7b (REBIND W3/M1): the CSD base units you intend to pay. When given, the result carries `outputPlan`
|
|
149
|
+
* (the PROVEN CSD outputs to build for a WHOLE fill of a non-partial offer, sized by the single-sourced
|
|
150
|
+
* `fillOutputPlan`) and, if you also pass `plannedOutputs`, binds them EXACTLY to that proven plan.
|
|
151
|
+
* A partial (min-bearing) offer is left unsized here; the wallet's fill-SPV is its payment-grade sizer. */
|
|
152
|
+
pay?: bigint | string | number;
|
|
153
|
+
/** B7b: your planned per-address CSD sums (the outputs you WILL build), bound both ways to `outputPlan`.
|
|
154
|
+
* Requires `pay`. A missing/low leg is a doomed underpay; an extra/high leg is a W3 overpay or an N26
|
|
155
|
+
* smuggled output - both refused. */
|
|
156
|
+
plannedOutputs?: Record<string, bigint | string | number>;
|
|
150
157
|
}): Promise<OfferFillCheck>;
|
|
151
158
|
|
|
152
159
|
/** Stable machine-readable error codes (branch on these, not the message text). */
|
|
153
160
|
type CairnErrorCode = "NOT_INSTALLED" | "USER_REJECTED" | "WALLET_LOCKED" | "UNSUPPORTED_METHOD" | "HTTP_ERROR" | "CONTENT_VERIFICATION" | "ACCOUNT_CHANGED" | "FIRST_PARTY_ONLY" | "RATE_LIMITED" | "APPROVAL_CLOSED" | "FORBIDDEN" | "INTERNAL" | "GHOST_INPUTS_SKIPPED" | "VERIFY_UNAVAILABLE" | "VERIFY_TAMPER" | "INSUFFICIENT" | "TOO_MANY_INPUTS" | "FEE_TOO_LOW" | "FEE_CAP" | "BAD_FEE" | "ZERO_ADDR_REFUSED" | "BAD_OUTPUTS" | "NO_OUTPUTS" | "BAD_REQUEST" | "OFFER_UNKNOWN" | "FILL_UNSAFE" | "FILL_WRONG_TARGET" | "SOURCE_DIVERGENCE" | "SUBMIT_REJECTED" | "SUBMIT_MAYBE_INFLIGHT" | "SUBMIT_DUPLICATE" | "NOTHING_TO_CONSOLIDATE" | "UNKNOWN";
|
|
154
|
-
declare const SDK_VERSION = "0.
|
|
161
|
+
declare const SDK_VERSION = "0.4.0";
|
|
155
162
|
interface CairnErrorOptions {
|
|
156
163
|
code?: CairnErrorCode;
|
|
157
164
|
docsPath?: string;
|
|
@@ -171,7 +178,7 @@ declare class CairnError extends Error {
|
|
|
171
178
|
/** Relative docs path (under the SDK docs base), if any. */
|
|
172
179
|
readonly docsPath?: string;
|
|
173
180
|
/** The cairn-sdk version that produced this error. */
|
|
174
|
-
readonly version = "0.
|
|
181
|
+
readonly version = "0.4.0";
|
|
175
182
|
/**
|
|
176
183
|
* Whether a dApp may safely AUTO-retry this failure. `false` for fund-safety refusals and user-intent
|
|
177
184
|
* declines (the default); `true` only for provably transient infra faults where nothing was signed.
|
|
@@ -312,6 +319,7 @@ declare class Cairn {
|
|
|
312
319
|
private _spvLight;
|
|
313
320
|
private _spvSeeded;
|
|
314
321
|
private readonly _spvCp;
|
|
322
|
+
private readonly _cairnBase;
|
|
315
323
|
constructor(config?: CairnConfig);
|
|
316
324
|
/**
|
|
317
325
|
* PoW-VERIFIED header merkle at a height (audit M3 — the no-PoW half). Lazily seeds a light
|
|
@@ -325,6 +333,21 @@ declare class Cairn {
|
|
|
325
333
|
/** The PoW-verifying SPV light client, seeded ONCE from the pinned checkpoint (shared by the M3 header-merkle
|
|
326
334
|
* bind and the F13 offer pre-verify). Reused across calls; the first call near tip syncs the post-checkpoint span. */
|
|
327
335
|
private seededSpvLight;
|
|
336
|
+
/**
|
|
337
|
+
* W12 batch header transport for the checkpoint..tip forward sync, backed by the cairn server's
|
|
338
|
+
* `/api/headers/:from/:count`. Carries ZERO trust: the LightClient re-verifies every header's PoW /
|
|
339
|
+
* prev-link / LWMA, so a forged or stale row fails closed and retrying transport proves nothing new.
|
|
340
|
+
* Retry budget: a 429 (per-IP header budget) or 502/503 (a height past the lagging indexer, or the
|
|
341
|
+
* server's 10s deadline) is a transient data-availability hiccup; back off (honouring Retry-After) a
|
|
342
|
+
* bounded number of times, then THROW so `sync()` fails closed. A non-dense range is rejected.
|
|
343
|
+
*
|
|
344
|
+
* B7d / F6 DEPENDENCY: `/api/headers` serves no Access-Control-Allow-Origin today (`corsPublic` is
|
|
345
|
+
* mounted on `/explorer/api`, `/trade/api` and `/api/rpc`, not here). A SAME-ORIGIN dApp (hosted on the
|
|
346
|
+
* cairn base) reaches it now; a CROSS-ORIGIN third-party dApp cannot until B7d adds the CORS mount and
|
|
347
|
+
* restarts the cairn service. This code is correct as written; it becomes reachable cross-origin only
|
|
348
|
+
* once B7d ships. This is a recorded dependency, not a blocker for authoring/testing.
|
|
349
|
+
*/
|
|
350
|
+
private fetchHeadersBatch;
|
|
328
351
|
/**
|
|
329
352
|
* F13 — corroborate an offer ON-CHAIN before building a `fillOffer` payment's `outputs`. Merkle-proves the
|
|
330
353
|
* offer into the PoW-verified header chain (from the pinned SPV checkpoint), binds the record to its on-chain
|
|
@@ -349,4 +372,4 @@ declare class Cairn {
|
|
|
349
372
|
get connected(): boolean;
|
|
350
373
|
}
|
|
351
374
|
|
|
352
|
-
export { BoardClient, Cairn, type CairnBaseUrls, type CairnConfig, CairnError, type CairnErrorCode, type CairnErrorOptions, Chain, ContentClient, ContentVerificationError, DEFAULT_SPV_CHECKPOINT, DetectOptions, FetchLike, Http, HttpError, IndexerClient, type NameDetail, type NameResolution, NamesClient, NotInstalledError, type OfferFillCheck, type PrimaryName,
|
|
375
|
+
export { BoardClient, Cairn, type CairnBaseUrls, type CairnConfig, CairnError, type CairnErrorCode, type CairnErrorOptions, Chain, ContentClient, ContentVerificationError, DEFAULT_SPV_CHECKPOINT, DetectOptions, FetchLike, Http, HttpError, IndexerClient, type NameDetail, type NameResolution, NamesClient, NotInstalledError, type OfferFillCheck, type PrimaryName, RegistryClient, SDK_VERSION, SubmitInFlightError, type TokenInfo, UnsupportedMethodError, UserRejectedError, WalletConnection, WalletLockedError, errorCode, mapProviderError, mapSubmitResultError, preverifyOffer };
|
package/dist/index.js
CHANGED
|
@@ -3,20 +3,20 @@ import {
|
|
|
3
3
|
} from "./chunk-BGJZF7S2.js";
|
|
4
4
|
import {
|
|
5
5
|
BoardClient
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-2ZML7IKV.js";
|
|
7
7
|
import {
|
|
8
8
|
Chain,
|
|
9
9
|
LightClient
|
|
10
10
|
} from "./chunk-C452UODV.js";
|
|
11
11
|
import {
|
|
12
12
|
ContentClient
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-2L5QNDAE.js";
|
|
14
14
|
import {
|
|
15
15
|
IndexerClient
|
|
16
16
|
} from "./chunk-J5J4JEJ6.js";
|
|
17
17
|
import {
|
|
18
18
|
CairnController
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-GTTLMWGY.js";
|
|
20
20
|
import {
|
|
21
21
|
WalletConnection,
|
|
22
22
|
connect,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
discoverProviders,
|
|
25
25
|
getWallet,
|
|
26
26
|
isInstalled
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-RKR3JMIA.js";
|
|
28
28
|
import {
|
|
29
29
|
CairnError,
|
|
30
30
|
ContentVerificationError,
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
errorCode,
|
|
39
39
|
mapProviderError,
|
|
40
40
|
mapSubmitResultError
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-MREZG2HN.js";
|
|
42
42
|
|
|
43
43
|
// src/http.ts
|
|
44
44
|
var DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
|
|
@@ -145,8 +145,9 @@ var Http = class {
|
|
|
145
145
|
try {
|
|
146
146
|
const res = await this.raw("GET", path, { query });
|
|
147
147
|
if (!res.ok) {
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
if (!(res.status >= 500 && attempt < this.retries)) throw new HttpError(res.status, url, await safeText(res));
|
|
149
|
+
res.body?.cancel().catch(() => {
|
|
150
|
+
});
|
|
150
151
|
} else {
|
|
151
152
|
return this.parseJson(res, new TextDecoder().decode(await readCapped(res, this.maxBytes)), url);
|
|
152
153
|
}
|
|
@@ -191,7 +192,7 @@ var NamesClient = class {
|
|
|
191
192
|
async resolve(name) {
|
|
192
193
|
if (!nameOk(name)) return null;
|
|
193
194
|
try {
|
|
194
|
-
return await this.http.getJson(`/trade/api/cairnx/resolve/${name}`);
|
|
195
|
+
return await this.http.getJson(`/trade/api/cairnx/resolve/${encodeURIComponent(name)}`);
|
|
195
196
|
} catch (e) {
|
|
196
197
|
if (e?.status === 404) return null;
|
|
197
198
|
throw e;
|
|
@@ -201,7 +202,7 @@ var NamesClient = class {
|
|
|
201
202
|
async name(name) {
|
|
202
203
|
if (!nameOk(name)) return null;
|
|
203
204
|
try {
|
|
204
|
-
return await this.http.getJson(`/trade/api/cairnx/name/${name}`);
|
|
205
|
+
return await this.http.getJson(`/trade/api/cairnx/name/${encodeURIComponent(name)}`);
|
|
205
206
|
} catch (e) {
|
|
206
207
|
if (e?.status === 404) return null;
|
|
207
208
|
throw e;
|
|
@@ -211,7 +212,7 @@ var NamesClient = class {
|
|
|
211
212
|
async nameHistory(name) {
|
|
212
213
|
if (!nameOk(name)) return null;
|
|
213
214
|
try {
|
|
214
|
-
return await this.http.getJson(`/trade/api/cairnx/name-history/${name}`);
|
|
215
|
+
return await this.http.getJson(`/trade/api/cairnx/name-history/${encodeURIComponent(name)}`);
|
|
215
216
|
} catch (e) {
|
|
216
217
|
if (e?.status === 404) return null;
|
|
217
218
|
throw e;
|
|
@@ -272,23 +273,37 @@ var NamesClient = class {
|
|
|
272
273
|
// src/fillverify.ts
|
|
273
274
|
import { rpcTxToTx } from "@inversealtruism/csd-client";
|
|
274
275
|
import { txid, payloadHash } from "@inversealtruism/csd-codec";
|
|
275
|
-
import {
|
|
276
|
+
import {
|
|
277
|
+
parseRecord,
|
|
278
|
+
feeBpsAt,
|
|
279
|
+
bindOfferTerms,
|
|
280
|
+
provenOfferTerms,
|
|
281
|
+
fillOutputPlan,
|
|
282
|
+
isTokenWant,
|
|
283
|
+
fillEndorsement,
|
|
284
|
+
fillIsSafe,
|
|
285
|
+
requiredFillOutputs,
|
|
286
|
+
previewFill
|
|
287
|
+
} from "@inversealtruism/cairnx-core";
|
|
276
288
|
var ADDR = /^0x[0-9a-f]{40}$/;
|
|
277
289
|
var HASH = /^0x[0-9a-f]{64}$/;
|
|
278
290
|
var COINBASE = "0x" + "00".repeat(32);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
291
|
+
function bindPlannedSums(planned, proven) {
|
|
292
|
+
const provenMap = new Map(proven.map((o) => [o.to.toLowerCase(), o.value]));
|
|
293
|
+
const plannedMap = /* @__PURE__ */ new Map();
|
|
294
|
+
try {
|
|
295
|
+
for (const [a, v] of Object.entries(planned)) {
|
|
296
|
+
const k = String(a).toLowerCase();
|
|
297
|
+
plannedMap.set(k, (plannedMap.get(k) ?? 0n) + BigInt(v));
|
|
298
|
+
}
|
|
299
|
+
} catch {
|
|
300
|
+
return "invalid plannedOutputs (amounts must be base-unit integers)";
|
|
301
|
+
}
|
|
302
|
+
for (const k of /* @__PURE__ */ new Set([...provenMap.keys(), ...plannedMap.keys()])) {
|
|
303
|
+
if ((provenMap.get(k) ?? 0n) !== (plannedMap.get(k) ?? 0n))
|
|
304
|
+
return `your planned payment doesn't match the proven fill outputs (${k}: planned ${(plannedMap.get(k) ?? 0n).toString()}, proven ${(provenMap.get(k) ?? 0n).toString()}) - refusing (an overpay past the proven amount is unrecoverable, and a missing or low leg is a doomed underpay)`;
|
|
305
|
+
}
|
|
306
|
+
return void 0;
|
|
292
307
|
}
|
|
293
308
|
async function provenAuthor(client, provenTx) {
|
|
294
309
|
const in0 = provenTx.inputs?.[0];
|
|
@@ -345,17 +360,35 @@ async function preverifyOffer(opts) {
|
|
|
345
360
|
return { ok: false, trust: "unverified", reason: "the offer record doesn't match the on-chain (merkle-proven) commitment" };
|
|
346
361
|
const seller = await provenAuthor(opts.client, provenTx);
|
|
347
362
|
if (!seller) return { ok: false, trust: "verified", transient: true, blockHeight, reason: "couldn't prove the offer's on-chain author yet; the chain view may be catching up, try again" };
|
|
363
|
+
const offerRec = rec;
|
|
348
364
|
const w = rec.want ?? {};
|
|
349
365
|
const payto = w.payto && ADDR.test(String(w.payto).toLowerCase()) ? String(w.payto).toLowerCase() : seller;
|
|
350
|
-
const terms =
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
366
|
+
const terms = provenOfferTerms(offerRec, blockHeight);
|
|
367
|
+
let outputPlan;
|
|
368
|
+
let sumsMismatch;
|
|
369
|
+
if (opts.pay !== void 0 && opts.pay !== null && !isTokenWant(offerRec.want) && offerRec.min === void 0) {
|
|
370
|
+
const provenState = {
|
|
371
|
+
id,
|
|
372
|
+
seller,
|
|
373
|
+
give: offerRec.give,
|
|
374
|
+
want: offerRec.want,
|
|
375
|
+
taker: offerRec.taker,
|
|
376
|
+
bid: offerRec.bid,
|
|
377
|
+
status: "open",
|
|
378
|
+
expiresEpoch: 0,
|
|
379
|
+
height: blockHeight,
|
|
380
|
+
feeBps: terms.feeBps
|
|
381
|
+
};
|
|
382
|
+
const plan = fillOutputPlan(provenState, opts.pay);
|
|
383
|
+
if (plan.kind === "csd-outputs") {
|
|
384
|
+
outputPlan = plan.outputs;
|
|
385
|
+
if (opts.plannedOutputs !== void 0 && opts.plannedOutputs !== null)
|
|
386
|
+
sumsMismatch = bindPlannedSums(opts.plannedOutputs, plan.outputs);
|
|
387
|
+
} else if (plan.kind === "undeliverable") {
|
|
388
|
+
sumsMismatch = `this pay would not deliver against the proven offer (${plan.reason}) - refusing (the CSD would be lost)`;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
const base = { payto, seller, terms, blockHeight, ...outputPlan ? { outputPlan } : {} };
|
|
359
392
|
if (opts.servedOffer !== void 0 && opts.servedOffer !== null) {
|
|
360
393
|
const so = opts.servedOffer;
|
|
361
394
|
const servedPayto = so?.want?.payto !== void 0 && so?.want?.payto !== null ? String(so.want.payto).toLowerCase() : "";
|
|
@@ -363,15 +396,20 @@ async function preverifyOffer(opts) {
|
|
|
363
396
|
return { ok: false, trust: "verified", reason: "the served payment recipient doesn't match the offer's on-chain author/record", ...base };
|
|
364
397
|
if (so?.seller !== void 0 && so?.seller !== null && String(so.seller).toLowerCase() !== seller)
|
|
365
398
|
return { ok: false, trust: "verified", reason: "the served seller doesn't match the offer's on-chain author (a lying resolver may be redirecting your payment)", ...base };
|
|
366
|
-
if (bindOfferTerms(opts.servedOffer, terms))
|
|
367
|
-
return { ok: false, trust: "verified", reason: "the served
|
|
399
|
+
if (bindOfferTerms(opts.servedOffer, terms, { give: true, wantType: true }))
|
|
400
|
+
return { ok: false, trust: "verified", reason: "the served terms don't match the offer's on-chain record (fee, rebate, partial sizing, the give, or the want type - a lying resolver could mis-size or re-route the fill, redirecting or burning your payment)", ...base };
|
|
368
401
|
}
|
|
402
|
+
if (sumsMismatch) return { ok: false, trust: "verified", reason: sumsMismatch, ...base };
|
|
369
403
|
return { ok: true, trust: "verified", ...base };
|
|
370
404
|
}
|
|
371
405
|
|
|
372
406
|
// src/index.ts
|
|
373
407
|
var DEFAULT_SPV_CHECKPOINT = { height: 38142, hash: "0x00000000000140f023cc0ee1457a40833f2fcb4de44291b9d373e50f17b97232" };
|
|
374
408
|
var DEFAULT_CAIRN = "https://cairn-substrate.com";
|
|
409
|
+
var SPV_HTTP_TIMEOUT_MS = 15e3;
|
|
410
|
+
var SPV_HTTP_MAX_RETRIES = 4;
|
|
411
|
+
var spvSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
412
|
+
var spvRetryDelayMs = (attempt, retryAfterSecs) => Math.min(4e3, Math.max(700 * (attempt + 1), retryAfterSecs * 1e3));
|
|
375
413
|
var Cairn = class {
|
|
376
414
|
config;
|
|
377
415
|
chain;
|
|
@@ -387,6 +425,8 @@ var Cairn = class {
|
|
|
387
425
|
_spvLight = null;
|
|
388
426
|
_spvSeeded = false;
|
|
389
427
|
_spvCp;
|
|
428
|
+
// W12: the cairn base the SPV header-batch provider fetches /api/headers from (same base the rest of the SDK uses).
|
|
429
|
+
_cairnBase;
|
|
390
430
|
constructor(config = {}) {
|
|
391
431
|
this.config = config;
|
|
392
432
|
const fetch = config.fetch;
|
|
@@ -395,6 +435,7 @@ var Cairn = class {
|
|
|
395
435
|
const rpc = config.baseUrls?.rpc ?? `${cairn}/api/rpc`;
|
|
396
436
|
const indexer = (config.baseUrls?.indexer ?? `${cairn}/explorer/api`).replace(/\/+$/, "");
|
|
397
437
|
this.cairnHttp = new Http({ baseUrl: cairn, fetch, timeoutMs });
|
|
438
|
+
this._cairnBase = cairn;
|
|
398
439
|
const indexerHttp = new Http({ baseUrl: indexer, fetch, timeoutMs });
|
|
399
440
|
const swarmHttp = config.baseUrls?.swarm ? new Http({ baseUrl: config.baseUrls.swarm.replace(/\/+$/, ""), fetch, timeoutMs }) : void 0;
|
|
400
441
|
this._spvCp = config.spvCheckpoint ?? DEFAULT_SPV_CHECKPOINT;
|
|
@@ -431,13 +472,69 @@ var Cairn = class {
|
|
|
431
472
|
/** The PoW-verifying SPV light client, seeded ONCE from the pinned checkpoint (shared by the M3 header-merkle
|
|
432
473
|
* bind and the F13 offer pre-verify). Reused across calls; the first call near tip syncs the post-checkpoint span. */
|
|
433
474
|
async seededSpvLight() {
|
|
434
|
-
if (!this._spvLight) this._spvLight = new LightClient({
|
|
475
|
+
if (!this._spvLight) this._spvLight = new LightClient({
|
|
476
|
+
client: this.chain.client,
|
|
477
|
+
checkpoints: { [this._spvCp.height]: this._spvCp.hash },
|
|
478
|
+
headersBatchProvider: (from, count) => this.fetchHeadersBatch(from, count)
|
|
479
|
+
});
|
|
435
480
|
if (!this._spvSeeded) {
|
|
436
481
|
await this._spvLight.syncFromCheckpoint(this._spvCp.height, this._spvCp.hash);
|
|
482
|
+
try {
|
|
483
|
+
const tipHeight = Number((await this.chain.tip()).height);
|
|
484
|
+
const have = this._spvLight.baseHeight + this._spvLight.chain.length - 1;
|
|
485
|
+
if (Number.isFinite(tipHeight) && tipHeight > have) await this._spvLight.sync(tipHeight);
|
|
486
|
+
} catch {
|
|
487
|
+
}
|
|
437
488
|
this._spvSeeded = true;
|
|
438
489
|
}
|
|
439
490
|
return this._spvLight;
|
|
440
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* W12 batch header transport for the checkpoint..tip forward sync, backed by the cairn server's
|
|
494
|
+
* `/api/headers/:from/:count`. Carries ZERO trust: the LightClient re-verifies every header's PoW /
|
|
495
|
+
* prev-link / LWMA, so a forged or stale row fails closed and retrying transport proves nothing new.
|
|
496
|
+
* Retry budget: a 429 (per-IP header budget) or 502/503 (a height past the lagging indexer, or the
|
|
497
|
+
* server's 10s deadline) is a transient data-availability hiccup; back off (honouring Retry-After) a
|
|
498
|
+
* bounded number of times, then THROW so `sync()` fails closed. A non-dense range is rejected.
|
|
499
|
+
*
|
|
500
|
+
* B7d / F6 DEPENDENCY: `/api/headers` serves no Access-Control-Allow-Origin today (`corsPublic` is
|
|
501
|
+
* mounted on `/explorer/api`, `/trade/api` and `/api/rpc`, not here). A SAME-ORIGIN dApp (hosted on the
|
|
502
|
+
* cairn base) reaches it now; a CROSS-ORIGIN third-party dApp cannot until B7d adds the CORS mount and
|
|
503
|
+
* restarts the cairn service. This code is correct as written; it becomes reachable cross-origin only
|
|
504
|
+
* once B7d ships. This is a recorded dependency, not a blocker for authoring/testing.
|
|
505
|
+
*/
|
|
506
|
+
async fetchHeadersBatch(from, count) {
|
|
507
|
+
const g = this.config.fetch ?? globalThis.fetch;
|
|
508
|
+
if (!g) throw new Error("No fetch available for SPV header sync - pass `fetch` in the SDK options.");
|
|
509
|
+
const doFetch = this.config.fetch ? this.config.fetch : g.bind(globalThis);
|
|
510
|
+
const url = `${this._cairnBase}/api/headers/${from}/${count}`;
|
|
511
|
+
for (let attempt = 0; ; attempt++) {
|
|
512
|
+
let res;
|
|
513
|
+
const ctrl = new AbortController();
|
|
514
|
+
const timer = setTimeout(() => ctrl.abort(), SPV_HTTP_TIMEOUT_MS);
|
|
515
|
+
try {
|
|
516
|
+
res = await doFetch(url, { signal: ctrl.signal });
|
|
517
|
+
} catch (e) {
|
|
518
|
+
if (attempt >= SPV_HTTP_MAX_RETRIES) throw e;
|
|
519
|
+
await spvSleep(spvRetryDelayMs(attempt, 0));
|
|
520
|
+
continue;
|
|
521
|
+
} finally {
|
|
522
|
+
clearTimeout(timer);
|
|
523
|
+
}
|
|
524
|
+
if (res.ok) {
|
|
525
|
+
const rows = (await res.json())?.headers;
|
|
526
|
+
if (!Array.isArray(rows) || rows.length !== count) throw new Error(`/api/headers: non-dense range (${from}/${count})`);
|
|
527
|
+
return rows.map((r) => ({ header: r.header, hash: r.hash }));
|
|
528
|
+
}
|
|
529
|
+
if ((res.status === 429 || res.status === 502 || res.status === 503) && attempt < SPV_HTTP_MAX_RETRIES) {
|
|
530
|
+
res.body?.cancel?.().catch(() => {
|
|
531
|
+
});
|
|
532
|
+
await spvSleep(spvRetryDelayMs(attempt, Number(res.headers.get("retry-after")) || 0));
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
throw new Error(`/api/headers ${res.status}`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
441
538
|
/**
|
|
442
539
|
* F13 — corroborate an offer ON-CHAIN before building a `fillOffer` payment's `outputs`. Merkle-proves the
|
|
443
540
|
* offer into the PoW-verified header chain (from the pinned SPV checkpoint), binds the record to its on-chain
|
|
@@ -500,9 +597,14 @@ export {
|
|
|
500
597
|
discoverProviders,
|
|
501
598
|
errorCode,
|
|
502
599
|
feeBpsAt,
|
|
600
|
+
fillEndorsement,
|
|
601
|
+
fillIsSafe,
|
|
602
|
+
fillOutputPlan,
|
|
503
603
|
getWallet,
|
|
504
604
|
isInstalled,
|
|
505
605
|
mapProviderError,
|
|
506
606
|
mapSubmitResultError,
|
|
507
|
-
preverifyOffer
|
|
607
|
+
preverifyOffer,
|
|
608
|
+
previewFill,
|
|
609
|
+
requiredFillOutputs
|
|
508
610
|
};
|
package/dist/react.cjs
CHANGED
|
@@ -26,7 +26,7 @@ __export(react_exports, {
|
|
|
26
26
|
module.exports = __toCommonJS(react_exports);
|
|
27
27
|
|
|
28
28
|
// src/errors.ts
|
|
29
|
-
var SDK_VERSION = "0.
|
|
29
|
+
var SDK_VERSION = "0.4.0";
|
|
30
30
|
var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
|
|
31
31
|
var CairnError = class extends Error {
|
|
32
32
|
/** Stable machine code for programmatic branching. */
|
|
@@ -100,7 +100,7 @@ var SubmitInFlightError = class extends CairnError {
|
|
|
100
100
|
this.txid = txid;
|
|
101
101
|
}
|
|
102
102
|
};
|
|
103
|
-
var NATIVE_CODES = /* @__PURE__ */ new Set(["
|
|
103
|
+
var NATIVE_CODES = /* @__PURE__ */ new Set(["FIRST_PARTY_ONLY", "RATE_LIMITED", "FORBIDDEN", "INTERNAL"]);
|
|
104
104
|
var NESTED_TRANSIENT = /* @__PURE__ */ new Set(["VERIFY_UNAVAILABLE", "OFFER_UNKNOWN"]);
|
|
105
105
|
var NESTED_TERMINAL = /* @__PURE__ */ new Set([
|
|
106
106
|
"FILL_UNSAFE",
|
|
@@ -144,6 +144,7 @@ function mapSubmitResultError(result) {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// src/connect.ts
|
|
147
|
+
var DEFAULT_PROVIDER_TIMEOUT_MS = 10 * 6e4;
|
|
147
148
|
function detectProvider(opts = {}) {
|
|
148
149
|
const timeoutMs = opts.timeoutMs ?? 3e3;
|
|
149
150
|
return new Promise((resolve, reject) => {
|
|
@@ -200,8 +201,24 @@ function unwrapWrite(reply) {
|
|
|
200
201
|
var WalletConnection = class {
|
|
201
202
|
provider;
|
|
202
203
|
_addr = null;
|
|
203
|
-
|
|
204
|
+
/** B7c: per-call provider timeout (ms). GENEROUS by default so it never rejects an in-flight human
|
|
205
|
+
* approval; 0 disables it. Every provider call (writes especially) races against it. */
|
|
206
|
+
_timeoutMs;
|
|
207
|
+
constructor(provider, opts = {}) {
|
|
204
208
|
this.provider = provider;
|
|
209
|
+
this._timeoutMs = opts.timeoutMs ?? DEFAULT_PROVIDER_TIMEOUT_MS;
|
|
210
|
+
}
|
|
211
|
+
// B7c: race a provider call against the per-call timeout. On timeout, throw a fail-closed CairnError
|
|
212
|
+
// (retryable:false: a write may already have reached the wallet, so never invite a blind auto-retry). The
|
|
213
|
+
// genuine wallet resolves quickly once the user acts; this only fires on a truly hung/absent response.
|
|
214
|
+
call(op, label) {
|
|
215
|
+
const ms = this._timeoutMs;
|
|
216
|
+
if (!Number.isFinite(ms) || ms <= 0) return Promise.resolve(op());
|
|
217
|
+
let timer;
|
|
218
|
+
const timeout = new Promise((_, reject) => {
|
|
219
|
+
timer = setTimeout(() => reject(new CairnError(`the wallet did not respond to ${label} within ${ms}ms`, { retryable: false })), ms);
|
|
220
|
+
});
|
|
221
|
+
return Promise.race([Promise.resolve(op()), timeout]).finally(() => clearTimeout(timer));
|
|
205
222
|
}
|
|
206
223
|
/** The wallet's version string (e.g. "0.2.x"). */
|
|
207
224
|
get version() {
|
|
@@ -213,13 +230,13 @@ var WalletConnection = class {
|
|
|
213
230
|
}
|
|
214
231
|
/** Request connection. First time per origin prompts the user; afterwards may be silent. */
|
|
215
232
|
async connect() {
|
|
216
|
-
const { addr } = unwrap(await this.provider.connect());
|
|
233
|
+
const { addr } = unwrap(await this.call(() => this.provider.connect(), "connect"));
|
|
217
234
|
this._addr = addr;
|
|
218
235
|
return addr;
|
|
219
236
|
}
|
|
220
237
|
/** Get the connected address (does not force a new connection prompt if already consented). */
|
|
221
238
|
async getAddress() {
|
|
222
|
-
const { addr } = unwrap(await this.provider.getAddress());
|
|
239
|
+
const { addr } = unwrap(await this.call(() => this.provider.getAddress(), "getAddress"));
|
|
223
240
|
this._addr = addr;
|
|
224
241
|
return addr;
|
|
225
242
|
}
|
|
@@ -232,7 +249,7 @@ var WalletConnection = class {
|
|
|
232
249
|
if (typeof this.provider.signIn !== "function") {
|
|
233
250
|
return Promise.reject(new UnsupportedMethodError("this wallet does not expose signIn()"));
|
|
234
251
|
}
|
|
235
|
-
return
|
|
252
|
+
return this.call(() => this.provider.signIn(), "signIn").then(unwrap);
|
|
236
253
|
}
|
|
237
254
|
/**
|
|
238
255
|
* Audience-bound "Sign in with CSD" (SIWC) — the secure, replay-resistant third-party login.
|
|
@@ -248,7 +265,7 @@ var WalletConnection = class {
|
|
|
248
265
|
if (typeof this.provider.signInWithCsd !== "function") {
|
|
249
266
|
return Promise.reject(new UnsupportedMethodError("this wallet predates Sign-in-with-CSD \u2014 ask the user to update the Cairn Wallet"));
|
|
250
267
|
}
|
|
251
|
-
return
|
|
268
|
+
return this.call(() => this.provider.signInWithCsd(params), "signInWithCsd").then(unwrap);
|
|
252
269
|
}
|
|
253
270
|
/** True if this wallet supports audience-bound SIWC (safe to call `signInWithCsd`). */
|
|
254
271
|
get supportsSiwc() {
|
|
@@ -258,7 +275,7 @@ var WalletConnection = class {
|
|
|
258
275
|
async getCapabilities() {
|
|
259
276
|
if (typeof this.provider.getCapabilities !== "function") return null;
|
|
260
277
|
try {
|
|
261
|
-
return await this.provider.getCapabilities();
|
|
278
|
+
return await this.call(() => this.provider.getCapabilities(), "getCapabilities");
|
|
262
279
|
} catch {
|
|
263
280
|
return null;
|
|
264
281
|
}
|
|
@@ -274,24 +291,24 @@ var WalletConnection = class {
|
|
|
274
291
|
if (typeof this.provider.fillOffer !== "function") {
|
|
275
292
|
return Promise.reject(new UnsupportedMethodError("this wallet predates fillOffer() \u2014 ask the user to update the Cairn Wallet"));
|
|
276
293
|
}
|
|
277
|
-
return
|
|
294
|
+
return this.call(() => this.provider.fillOffer(params), "fillOffer").then(unwrapWrite);
|
|
278
295
|
}
|
|
279
296
|
/** This origin's current permission grant (EIP-2255-style). Silent; [] if none / unsupported. */
|
|
280
297
|
async getPermissions() {
|
|
281
298
|
if (typeof this.provider.getPermissions !== "function") return [];
|
|
282
|
-
return unwrap(await this.provider.getPermissions());
|
|
299
|
+
return unwrap(await this.call(() => this.provider.getPermissions(), "getPermissions"));
|
|
283
300
|
}
|
|
284
301
|
/** Explicitly request permission to see the address (prompts, like connect). */
|
|
285
302
|
requestPermissions() {
|
|
286
303
|
if (typeof this.provider.requestPermissions !== "function") {
|
|
287
304
|
return Promise.reject(new UnsupportedMethodError("this wallet predates requestPermissions \u2014 use connect()"));
|
|
288
305
|
}
|
|
289
|
-
return
|
|
306
|
+
return this.call(() => this.provider.requestPermissions(), "requestPermissions").then(unwrap);
|
|
290
307
|
}
|
|
291
308
|
/** Revoke THIS origin's own access (silent). Returns { revoked }. Safe no-op on older wallets. */
|
|
292
309
|
async revokePermissions() {
|
|
293
310
|
if (typeof this.provider.revokePermissions !== "function") return { revoked: false };
|
|
294
|
-
return unwrap(await this.provider.revokePermissions());
|
|
311
|
+
return unwrap(await this.call(() => this.provider.revokePermissions(), "revokePermissions"));
|
|
295
312
|
}
|
|
296
313
|
/**
|
|
297
314
|
* Subscribe to provider events. `accountsChanged` fires with `[]` when this site loses access
|
|
@@ -307,26 +324,26 @@ var WalletConnection = class {
|
|
|
307
324
|
}
|
|
308
325
|
/** Send CSD (always prompts with clear-signing; wallet picks inputs + returns change to itself). */
|
|
309
326
|
send(params) {
|
|
310
|
-
return
|
|
327
|
+
return this.call(() => this.provider.send(params), "send").then(unwrapWrite);
|
|
311
328
|
}
|
|
312
329
|
/** Low-level Propose (always prompts). Prefer `cairn.board.propose()` which computes the hash/uri/expiry. */
|
|
313
330
|
propose(params) {
|
|
314
|
-
return
|
|
331
|
+
return this.call(() => this.provider.propose(params), "propose").then(unwrapWrite);
|
|
315
332
|
}
|
|
316
333
|
/** Attest/support a proposal (always prompts). */
|
|
317
334
|
attest(params) {
|
|
318
|
-
return
|
|
335
|
+
return this.call(() => this.provider.attest(params), "attest").then(unwrapWrite);
|
|
319
336
|
}
|
|
320
337
|
/** Seal a commit-reveal claim (always prompts; salt stays inside the wallet). */
|
|
321
338
|
sealClaim(params) {
|
|
322
|
-
return
|
|
339
|
+
return this.call(() => this.provider.sealClaim(params), "sealClaim").then(unwrapWrite);
|
|
323
340
|
}
|
|
324
341
|
/** Reveal a previously sealed claim by its sealing txid (always prompts). */
|
|
325
342
|
revealClaim(txid) {
|
|
326
343
|
if (typeof txid !== "string" || !txid) {
|
|
327
344
|
return Promise.reject(new UnsupportedMethodError("revealClaim requires the sealing txid"));
|
|
328
345
|
}
|
|
329
|
-
return
|
|
346
|
+
return this.call(() => this.provider.revealClaim(txid), "revealClaim").then(unwrapWrite);
|
|
330
347
|
}
|
|
331
348
|
};
|
|
332
349
|
async function getWallet(opts = {}) {
|
package/dist/react.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CairnController
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-GTTLMWGY.js";
|
|
4
|
+
import "./chunk-RKR3JMIA.js";
|
|
5
|
+
import "./chunk-MREZG2HN.js";
|
|
6
6
|
|
|
7
7
|
// src/react.ts
|
|
8
8
|
function createCairnHooks(React, controllerOrOpts) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inversealtruism/cairn-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "The Compute Substrate dApp kit — one cohesive toolkit to connect the Cairn Wallet, read the chain/board, publish self-certifying content, and resolve registries. Composes the published csd-* primitives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -58,21 +58,8 @@
|
|
|
58
58
|
"LICENSE",
|
|
59
59
|
"README.md"
|
|
60
60
|
],
|
|
61
|
-
"scripts": {
|
|
62
|
-
"build": "tsup src/index.ts src/connect.ts src/board.ts src/indexer.ts src/content.ts src/registry.ts src/chain.ts src/controller.ts src/react.ts --format esm,cjs --dts --clean",
|
|
63
|
-
"test": "tsx test/sdk.test.ts && tsx test/connect.test.ts && tsx test/errors.test.ts && tsx test/controller.test.ts && tsx test/react.test.ts && tsx test/hardening.test.ts && tsx test/spv-checkpoint.test.ts && tsx test/names-b10.test.ts && tsx test/fillverify.test.ts",
|
|
64
|
-
"test:live": "node examples/read-only.mjs",
|
|
65
|
-
"build:example": "node scripts/build-example.mjs",
|
|
66
|
-
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
67
|
-
"test:e2e:read": "node test/e2e/e2e-read.mjs",
|
|
68
|
-
"test:e2e:wallet": "xvfb-run -a node test/e2e/e2e-wallet-connect.mjs",
|
|
69
|
-
"test:e2e:write": "node test/e2e/e2e-write.mjs",
|
|
70
|
-
"test:e2e": "node test/e2e/e2e-read.mjs && xvfb-run -a node test/e2e/e2e-wallet-connect.mjs",
|
|
71
|
-
"test:all": "pnpm test && pnpm test:e2e",
|
|
72
|
-
"prepublishOnly": "npm run build && npm test"
|
|
73
|
-
},
|
|
74
61
|
"dependencies": {
|
|
75
|
-
"@inversealtruism/cairnx-core": "0.1.
|
|
62
|
+
"@inversealtruism/cairnx-core": "0.1.40",
|
|
76
63
|
"@inversealtruism/csd-client": "0.1.15",
|
|
77
64
|
"@inversealtruism/csd-codec": "0.1.15",
|
|
78
65
|
"@inversealtruism/csd-crypto": "0.1.15",
|
|
@@ -81,20 +68,14 @@
|
|
|
81
68
|
"@inversealtruism/csd-tx": "0.1.17"
|
|
82
69
|
},
|
|
83
70
|
"devDependencies": {
|
|
71
|
+
"@inversealtruism/csd-siwc": "0.1.15",
|
|
84
72
|
"esbuild": "^0.27.0",
|
|
85
73
|
"playwright-core": "^1.49.0",
|
|
86
74
|
"tsup": "^8.5.1",
|
|
87
75
|
"tsx": "^4.19.2",
|
|
88
|
-
"typescript": "^5.6.0"
|
|
89
|
-
"@inversealtruism/csd-siwc": "0.1.15"
|
|
76
|
+
"typescript": "^5.6.0"
|
|
90
77
|
},
|
|
91
78
|
"sideEffects": false,
|
|
92
|
-
"packageManager": "pnpm@10.32.1",
|
|
93
|
-
"pnpm": {
|
|
94
|
-
"onlyBuiltDependencies": [
|
|
95
|
-
"esbuild"
|
|
96
|
-
]
|
|
97
|
-
},
|
|
98
79
|
"license": "MIT",
|
|
99
80
|
"publishConfig": {
|
|
100
81
|
"access": "public"
|
|
@@ -112,5 +93,17 @@
|
|
|
112
93
|
"dapp",
|
|
113
94
|
"sdk",
|
|
114
95
|
"blockchain"
|
|
115
|
-
]
|
|
116
|
-
|
|
96
|
+
],
|
|
97
|
+
"scripts": {
|
|
98
|
+
"build": "tsup src/index.ts src/connect.ts src/board.ts src/indexer.ts src/content.ts src/registry.ts src/chain.ts src/controller.ts src/react.ts --format esm,cjs --dts --clean",
|
|
99
|
+
"test": "node test/run.mjs",
|
|
100
|
+
"test:live": "node examples/read-only.mjs",
|
|
101
|
+
"build:example": "node scripts/build-example.mjs",
|
|
102
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
103
|
+
"test:e2e:read": "node test/e2e/e2e-read.mjs",
|
|
104
|
+
"test:e2e:wallet": "xvfb-run -a node test/e2e/e2e-wallet-connect.mjs",
|
|
105
|
+
"test:e2e:write": "node test/e2e/e2e-write.mjs",
|
|
106
|
+
"test:e2e": "node test/e2e/e2e-read.mjs && xvfb-run -a node test/e2e/e2e-wallet-connect.mjs",
|
|
107
|
+
"test:all": "pnpm test && pnpm test:e2e"
|
|
108
|
+
}
|
|
109
|
+
}
|