@inversealtruism/cairn-sdk 0.2.3 → 0.3.2
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 +8 -1
- package/dist/board.js +2 -2
- package/dist/{chunk-72YSLTLO.js → chunk-AGU6K5EZ.js} +1 -1
- package/dist/{chunk-TKZXUBDX.js → chunk-BN3PFJDY.js} +23 -9
- package/dist/{chunk-SVW7FUBJ.js → chunk-CZWDA3A5.js} +1 -1
- package/dist/{chunk-M6OSWJVC.js → chunk-FMXNJQWL.js} +50 -1
- package/dist/{chunk-AAOETAUE.js → chunk-JKC5K253.js} +1 -1
- package/dist/connect.cjs +68 -8
- package/dist/connect.d.cts +17 -2
- package/dist/connect.d.ts +17 -2
- package/dist/connect.js +2 -2
- package/dist/content.cjs +8 -1
- package/dist/content.js +2 -2
- package/dist/controller.cjs +68 -8
- package/dist/controller.js +3 -3
- package/dist/index.cjs +212 -34
- package/dist/index.d.cts +108 -5
- package/dist/index.d.ts +108 -5
- package/dist/index.js +133 -18
- package/dist/react.cjs +68 -8
- package/dist/react.js +3 -3
- package/package.json +5 -5
package/dist/controller.cjs
CHANGED
|
@@ -25,7 +25,7 @@ __export(controller_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(controller_exports);
|
|
26
26
|
|
|
27
27
|
// src/errors.ts
|
|
28
|
-
var SDK_VERSION = "0.2
|
|
28
|
+
var SDK_VERSION = "0.3.2";
|
|
29
29
|
var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
|
|
30
30
|
var CairnError = class extends Error {
|
|
31
31
|
/** Stable machine code for programmatic branching. */
|
|
@@ -36,6 +36,12 @@ var CairnError = class extends Error {
|
|
|
36
36
|
docsPath;
|
|
37
37
|
/** The cairn-sdk version that produced this error. */
|
|
38
38
|
version = SDK_VERSION;
|
|
39
|
+
/**
|
|
40
|
+
* Whether a dApp may safely AUTO-retry this failure. `false` for fund-safety refusals and user-intent
|
|
41
|
+
* declines (the default); `true` only for provably transient infra faults where nothing was signed.
|
|
42
|
+
* A `SubmitInFlightError` is `false` too: reconcile its `txid`, never blind-retry.
|
|
43
|
+
*/
|
|
44
|
+
retryable;
|
|
39
45
|
constructor(shortMessage, opts = {}) {
|
|
40
46
|
const docs = opts.docsPath;
|
|
41
47
|
super(
|
|
@@ -46,6 +52,7 @@ var CairnError = class extends Error {
|
|
|
46
52
|
this.code = opts.code ?? "UNKNOWN";
|
|
47
53
|
this.shortMessage = shortMessage;
|
|
48
54
|
this.docsPath = docs;
|
|
55
|
+
this.retryable = opts.retryable ?? false;
|
|
49
56
|
}
|
|
50
57
|
/**
|
|
51
58
|
* Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
|
|
@@ -82,11 +89,43 @@ var UnsupportedMethodError = class extends CairnError {
|
|
|
82
89
|
super(message, { code: "UNSUPPORTED_METHOD", docsPath: "/errors#unsupported-method" });
|
|
83
90
|
}
|
|
84
91
|
};
|
|
92
|
+
var SubmitInFlightError = class extends CairnError {
|
|
93
|
+
/** The locally-computed consensus txid to reconcile against the chain (may be undefined if the wallet omitted it). */
|
|
94
|
+
txid;
|
|
95
|
+
/** Always true: the tx MAY be in the mempool. Reconcile via `txid`; never blind-retry. */
|
|
96
|
+
maybeSent = true;
|
|
97
|
+
constructor(message, code, txid) {
|
|
98
|
+
super(message, { code, docsPath: "/errors#submit-inflight", retryable: false });
|
|
99
|
+
this.txid = txid;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
85
102
|
var NATIVE_CODES = /* @__PURE__ */ new Set(["ACCOUNT_CHANGED", "FIRST_PARTY_ONLY", "RATE_LIMITED", "APPROVAL_CLOSED", "FORBIDDEN", "INTERNAL"]);
|
|
103
|
+
var NESTED_TRANSIENT = /* @__PURE__ */ new Set(["VERIFY_UNAVAILABLE", "OFFER_UNKNOWN"]);
|
|
104
|
+
var NESTED_TERMINAL = /* @__PURE__ */ new Set([
|
|
105
|
+
"FILL_UNSAFE",
|
|
106
|
+
"FILL_WRONG_TARGET",
|
|
107
|
+
"SOURCE_DIVERGENCE",
|
|
108
|
+
"VERIFY_TAMPER",
|
|
109
|
+
"SUBMIT_REJECTED",
|
|
110
|
+
"BAD_REQUEST",
|
|
111
|
+
"BAD_FEE",
|
|
112
|
+
"BAD_OUTPUTS",
|
|
113
|
+
"NO_OUTPUTS",
|
|
114
|
+
"ZERO_ADDR_REFUSED",
|
|
115
|
+
"TOO_MANY_INPUTS",
|
|
116
|
+
"INSUFFICIENT",
|
|
117
|
+
"FEE_CAP",
|
|
118
|
+
"FEE_TOO_LOW",
|
|
119
|
+
"ACCOUNT_CHANGED",
|
|
120
|
+
"GHOST_INPUTS_SKIPPED",
|
|
121
|
+
"NOTHING_TO_CONSOLIDATE"
|
|
122
|
+
]);
|
|
86
123
|
function mapProviderError(error, code) {
|
|
87
124
|
if (code === "USER_REJECTED" || code === "APPROVAL_CLOSED") return new UserRejectedError(error);
|
|
88
125
|
if (code === "WALLET_LOCKED") return new WalletLockedError(error);
|
|
89
126
|
if (code === "UNSUPPORTED_METHOD" || code === "UNKNOWN_KIND") return new UnsupportedMethodError(error);
|
|
127
|
+
if (code && NESTED_TRANSIENT.has(code)) return new CairnError(error || code, { code, retryable: true });
|
|
128
|
+
if (code && NESTED_TERMINAL.has(code)) return new CairnError(error || code, { code, retryable: false });
|
|
90
129
|
if (code && NATIVE_CODES.has(code)) return new CairnError(error || code, { code });
|
|
91
130
|
const e = String(error || "").toLowerCase();
|
|
92
131
|
if (e.includes("rejected by user")) return new UserRejectedError(error);
|
|
@@ -94,6 +133,14 @@ function mapProviderError(error, code) {
|
|
|
94
133
|
if (e.includes("unsupported dapp method") || e.includes("predates") || e.includes("too old")) return new UnsupportedMethodError(error);
|
|
95
134
|
return new CairnError(error || "Unknown wallet error");
|
|
96
135
|
}
|
|
136
|
+
function mapSubmitResultError(result) {
|
|
137
|
+
const code = result?.code;
|
|
138
|
+
const msg = typeof result?.error === "string" && result.error || code || "The wallet refused the transaction.";
|
|
139
|
+
if (code === "SUBMIT_MAYBE_INFLIGHT" || code === "SUBMIT_DUPLICATE") {
|
|
140
|
+
return new SubmitInFlightError(msg, code, typeof result?.txid === "string" ? result.txid : void 0);
|
|
141
|
+
}
|
|
142
|
+
return mapProviderError(msg, code);
|
|
143
|
+
}
|
|
97
144
|
|
|
98
145
|
// src/connect.ts
|
|
99
146
|
function detectProvider(opts = {}) {
|
|
@@ -142,6 +189,13 @@ function unwrap(reply) {
|
|
|
142
189
|
if (reply && reply.ok) return reply.result;
|
|
143
190
|
throw mapProviderError(reply?.error ?? "Unknown wallet error", reply?.code);
|
|
144
191
|
}
|
|
192
|
+
function unwrapWrite(reply) {
|
|
193
|
+
const result = unwrap(reply);
|
|
194
|
+
if (result && typeof result === "object" && result.ok === false) {
|
|
195
|
+
throw mapSubmitResultError(result);
|
|
196
|
+
}
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
145
199
|
var WalletConnection = class {
|
|
146
200
|
provider;
|
|
147
201
|
_addr = null;
|
|
@@ -208,12 +262,18 @@ var WalletConnection = class {
|
|
|
208
262
|
return null;
|
|
209
263
|
}
|
|
210
264
|
}
|
|
211
|
-
/**
|
|
265
|
+
/**
|
|
266
|
+
* CairnX fill: pay + attest in ONE tx (atomic SETTLEMENT SHAPE, both revert together). Always prompts with
|
|
267
|
+
* clear-signing. The payment recipient in `params.outputs` is RESOLVER-TRUSTED here: this SDK does NOT
|
|
268
|
+
* SPV-verify it, so a lying resolver can redirect the payment. A dApp settling real value MUST corroborate
|
|
269
|
+
* the offer on-chain before building `outputs` (F13). The wallet's own fill-SPV boundary (cairn-wallet
|
|
270
|
+
* 0.2.60+, which binds payment/rebate/fee to the merkle-proven offer) is the payment-grade check; see FillParams.
|
|
271
|
+
*/
|
|
212
272
|
fillOffer(params) {
|
|
213
273
|
if (typeof this.provider.fillOffer !== "function") {
|
|
214
274
|
return Promise.reject(new UnsupportedMethodError("this wallet predates fillOffer() \u2014 ask the user to update the Cairn Wallet"));
|
|
215
275
|
}
|
|
216
|
-
return Promise.resolve(this.provider.fillOffer(params)).then(
|
|
276
|
+
return Promise.resolve(this.provider.fillOffer(params)).then(unwrapWrite);
|
|
217
277
|
}
|
|
218
278
|
/** This origin's current permission grant (EIP-2255-style). Silent; [] if none / unsupported. */
|
|
219
279
|
async getPermissions() {
|
|
@@ -246,26 +306,26 @@ var WalletConnection = class {
|
|
|
246
306
|
}
|
|
247
307
|
/** Send CSD (always prompts with clear-signing; wallet picks inputs + returns change to itself). */
|
|
248
308
|
send(params) {
|
|
249
|
-
return Promise.resolve(this.provider.send(params)).then(
|
|
309
|
+
return Promise.resolve(this.provider.send(params)).then(unwrapWrite);
|
|
250
310
|
}
|
|
251
311
|
/** Low-level Propose (always prompts). Prefer `cairn.board.propose()` which computes the hash/uri/expiry. */
|
|
252
312
|
propose(params) {
|
|
253
|
-
return Promise.resolve(this.provider.propose(params)).then(
|
|
313
|
+
return Promise.resolve(this.provider.propose(params)).then(unwrapWrite);
|
|
254
314
|
}
|
|
255
315
|
/** Attest/support a proposal (always prompts). */
|
|
256
316
|
attest(params) {
|
|
257
|
-
return Promise.resolve(this.provider.attest(params)).then(
|
|
317
|
+
return Promise.resolve(this.provider.attest(params)).then(unwrapWrite);
|
|
258
318
|
}
|
|
259
319
|
/** Seal a commit-reveal claim (always prompts; salt stays inside the wallet). */
|
|
260
320
|
sealClaim(params) {
|
|
261
|
-
return Promise.resolve(this.provider.sealClaim(params)).then(
|
|
321
|
+
return Promise.resolve(this.provider.sealClaim(params)).then(unwrapWrite);
|
|
262
322
|
}
|
|
263
323
|
/** Reveal a previously sealed claim by its sealing txid (always prompts). */
|
|
264
324
|
revealClaim(txid) {
|
|
265
325
|
if (typeof txid !== "string" || !txid) {
|
|
266
326
|
return Promise.reject(new UnsupportedMethodError("revealClaim requires the sealing txid"));
|
|
267
327
|
}
|
|
268
|
-
return Promise.resolve(this.provider.revealClaim(txid)).then(
|
|
328
|
+
return Promise.resolve(this.provider.revealClaim(txid)).then(unwrapWrite);
|
|
269
329
|
}
|
|
270
330
|
};
|
|
271
331
|
async function getWallet(opts = {}) {
|
package/dist/controller.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -35,22 +35,27 @@ __export(index_exports, {
|
|
|
35
35
|
NotInstalledError: () => NotInstalledError,
|
|
36
36
|
RegistryClient: () => RegistryClient,
|
|
37
37
|
SDK_VERSION: () => SDK_VERSION,
|
|
38
|
+
SubmitInFlightError: () => SubmitInFlightError,
|
|
38
39
|
UnsupportedMethodError: () => UnsupportedMethodError,
|
|
39
40
|
UserRejectedError: () => UserRejectedError,
|
|
40
41
|
WalletConnection: () => WalletConnection,
|
|
41
42
|
WalletLockedError: () => WalletLockedError,
|
|
43
|
+
bindOfferTerms: () => import_cairnx_core2.bindOfferTerms,
|
|
42
44
|
connect: () => connect,
|
|
43
45
|
detectProvider: () => detectProvider,
|
|
44
46
|
discoverProviders: () => discoverProviders,
|
|
45
47
|
errorCode: () => errorCode,
|
|
48
|
+
feeBpsAt: () => import_cairnx_core2.feeBpsAt,
|
|
46
49
|
getWallet: () => getWallet,
|
|
47
50
|
isInstalled: () => isInstalled,
|
|
48
|
-
mapProviderError: () => mapProviderError
|
|
51
|
+
mapProviderError: () => mapProviderError,
|
|
52
|
+
mapSubmitResultError: () => mapSubmitResultError,
|
|
53
|
+
preverifyOffer: () => preverifyOffer
|
|
49
54
|
});
|
|
50
55
|
module.exports = __toCommonJS(index_exports);
|
|
51
56
|
|
|
52
57
|
// src/errors.ts
|
|
53
|
-
var SDK_VERSION = "0.2
|
|
58
|
+
var SDK_VERSION = "0.3.2";
|
|
54
59
|
var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
|
|
55
60
|
var CairnError = class extends Error {
|
|
56
61
|
/** Stable machine code for programmatic branching. */
|
|
@@ -61,6 +66,12 @@ var CairnError = class extends Error {
|
|
|
61
66
|
docsPath;
|
|
62
67
|
/** The cairn-sdk version that produced this error. */
|
|
63
68
|
version = SDK_VERSION;
|
|
69
|
+
/**
|
|
70
|
+
* Whether a dApp may safely AUTO-retry this failure. `false` for fund-safety refusals and user-intent
|
|
71
|
+
* declines (the default); `true` only for provably transient infra faults where nothing was signed.
|
|
72
|
+
* A `SubmitInFlightError` is `false` too: reconcile its `txid`, never blind-retry.
|
|
73
|
+
*/
|
|
74
|
+
retryable;
|
|
64
75
|
constructor(shortMessage, opts = {}) {
|
|
65
76
|
const docs = opts.docsPath;
|
|
66
77
|
super(
|
|
@@ -71,6 +82,7 @@ var CairnError = class extends Error {
|
|
|
71
82
|
this.code = opts.code ?? "UNKNOWN";
|
|
72
83
|
this.shortMessage = shortMessage;
|
|
73
84
|
this.docsPath = docs;
|
|
85
|
+
this.retryable = opts.retryable ?? false;
|
|
74
86
|
}
|
|
75
87
|
/**
|
|
76
88
|
* Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
|
|
@@ -125,11 +137,43 @@ var ContentVerificationError = class extends CairnError {
|
|
|
125
137
|
this.expected = expected;
|
|
126
138
|
}
|
|
127
139
|
};
|
|
140
|
+
var SubmitInFlightError = class extends CairnError {
|
|
141
|
+
/** The locally-computed consensus txid to reconcile against the chain (may be undefined if the wallet omitted it). */
|
|
142
|
+
txid;
|
|
143
|
+
/** Always true: the tx MAY be in the mempool. Reconcile via `txid`; never blind-retry. */
|
|
144
|
+
maybeSent = true;
|
|
145
|
+
constructor(message, code, txid3) {
|
|
146
|
+
super(message, { code, docsPath: "/errors#submit-inflight", retryable: false });
|
|
147
|
+
this.txid = txid3;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
128
150
|
var NATIVE_CODES = /* @__PURE__ */ new Set(["ACCOUNT_CHANGED", "FIRST_PARTY_ONLY", "RATE_LIMITED", "APPROVAL_CLOSED", "FORBIDDEN", "INTERNAL"]);
|
|
151
|
+
var NESTED_TRANSIENT = /* @__PURE__ */ new Set(["VERIFY_UNAVAILABLE", "OFFER_UNKNOWN"]);
|
|
152
|
+
var NESTED_TERMINAL = /* @__PURE__ */ new Set([
|
|
153
|
+
"FILL_UNSAFE",
|
|
154
|
+
"FILL_WRONG_TARGET",
|
|
155
|
+
"SOURCE_DIVERGENCE",
|
|
156
|
+
"VERIFY_TAMPER",
|
|
157
|
+
"SUBMIT_REJECTED",
|
|
158
|
+
"BAD_REQUEST",
|
|
159
|
+
"BAD_FEE",
|
|
160
|
+
"BAD_OUTPUTS",
|
|
161
|
+
"NO_OUTPUTS",
|
|
162
|
+
"ZERO_ADDR_REFUSED",
|
|
163
|
+
"TOO_MANY_INPUTS",
|
|
164
|
+
"INSUFFICIENT",
|
|
165
|
+
"FEE_CAP",
|
|
166
|
+
"FEE_TOO_LOW",
|
|
167
|
+
"ACCOUNT_CHANGED",
|
|
168
|
+
"GHOST_INPUTS_SKIPPED",
|
|
169
|
+
"NOTHING_TO_CONSOLIDATE"
|
|
170
|
+
]);
|
|
129
171
|
function mapProviderError(error, code) {
|
|
130
172
|
if (code === "USER_REJECTED" || code === "APPROVAL_CLOSED") return new UserRejectedError(error);
|
|
131
173
|
if (code === "WALLET_LOCKED") return new WalletLockedError(error);
|
|
132
174
|
if (code === "UNSUPPORTED_METHOD" || code === "UNKNOWN_KIND") return new UnsupportedMethodError(error);
|
|
175
|
+
if (code && NESTED_TRANSIENT.has(code)) return new CairnError(error || code, { code, retryable: true });
|
|
176
|
+
if (code && NESTED_TERMINAL.has(code)) return new CairnError(error || code, { code, retryable: false });
|
|
133
177
|
if (code && NATIVE_CODES.has(code)) return new CairnError(error || code, { code });
|
|
134
178
|
const e = String(error || "").toLowerCase();
|
|
135
179
|
if (e.includes("rejected by user")) return new UserRejectedError(error);
|
|
@@ -137,6 +181,14 @@ function mapProviderError(error, code) {
|
|
|
137
181
|
if (e.includes("unsupported dapp method") || e.includes("predates") || e.includes("too old")) return new UnsupportedMethodError(error);
|
|
138
182
|
return new CairnError(error || "Unknown wallet error");
|
|
139
183
|
}
|
|
184
|
+
function mapSubmitResultError(result) {
|
|
185
|
+
const code = result?.code;
|
|
186
|
+
const msg = typeof result?.error === "string" && result.error || code || "The wallet refused the transaction.";
|
|
187
|
+
if (code === "SUBMIT_MAYBE_INFLIGHT" || code === "SUBMIT_DUPLICATE") {
|
|
188
|
+
return new SubmitInFlightError(msg, code, typeof result?.txid === "string" ? result.txid : void 0);
|
|
189
|
+
}
|
|
190
|
+
return mapProviderError(msg, code);
|
|
191
|
+
}
|
|
140
192
|
function errorCode(err) {
|
|
141
193
|
return err instanceof CairnError ? err.code : "UNKNOWN";
|
|
142
194
|
}
|
|
@@ -246,8 +298,9 @@ var Http = class {
|
|
|
246
298
|
try {
|
|
247
299
|
const res = await this.raw("GET", path, { query });
|
|
248
300
|
if (!res.ok) {
|
|
249
|
-
|
|
250
|
-
|
|
301
|
+
if (!(res.status >= 500 && attempt < this.retries)) throw new HttpError(res.status, url, await safeText(res));
|
|
302
|
+
res.body?.cancel().catch(() => {
|
|
303
|
+
});
|
|
251
304
|
} else {
|
|
252
305
|
return this.parseJson(res, new TextDecoder().decode(await readCapped(res, this.maxBytes)), url);
|
|
253
306
|
}
|
|
@@ -442,23 +495,23 @@ var BoardClient = class {
|
|
|
442
495
|
const expiresEpoch = Math.floor(height / import_csd_codec3.EPOCH_LEN) + lifetime;
|
|
443
496
|
const fee = input.fee ?? import_csd_codec3.MIN_FEE_PROPOSE;
|
|
444
497
|
const r = await wallet.propose({ domain: content.domain, payloadHash: ph, uri, expiresEpoch, fee });
|
|
445
|
-
const
|
|
446
|
-
if (!
|
|
447
|
-
const reg = await this.tryRegister(content,
|
|
448
|
-
return { txid:
|
|
498
|
+
const txid3 = String(r.txid ?? "");
|
|
499
|
+
if (!txid3) throw new CairnError("Propose submitted but no txid was returned by the wallet.");
|
|
500
|
+
const reg = await this.tryRegister(content, txid3);
|
|
501
|
+
return { txid: txid3, payloadHash: ph, content, registered: reg.ok, registerError: reg.error };
|
|
449
502
|
}
|
|
450
503
|
/**
|
|
451
504
|
* Register off-chain content for an already-submitted proposal txid. Idempotent.
|
|
452
505
|
* Returns { ok } — the cairn server verifies sha256(canonical) == on-chain payload_hash.
|
|
453
506
|
*/
|
|
454
|
-
async registerContent(content,
|
|
507
|
+
async registerContent(content, txid3) {
|
|
455
508
|
try {
|
|
456
509
|
const res = await this.http.postJson("/api/content", {
|
|
457
510
|
domain: content.domain,
|
|
458
511
|
title: content.title,
|
|
459
512
|
body: content.body,
|
|
460
513
|
links: content.links,
|
|
461
|
-
txid:
|
|
514
|
+
txid: txid3
|
|
462
515
|
});
|
|
463
516
|
return { ok: !!res.ok, error: res.ok ? void 0 : res.error };
|
|
464
517
|
} catch (e) {
|
|
@@ -482,12 +535,12 @@ var BoardClient = class {
|
|
|
482
535
|
if (!Number.isFinite(h)) throw new CairnError("Could not read chain tip height for expiry computation.");
|
|
483
536
|
return h;
|
|
484
537
|
}
|
|
485
|
-
async tryRegister(content,
|
|
538
|
+
async tryRegister(content, txid3) {
|
|
486
539
|
const delays = [0, 1500, 3e3, 5e3];
|
|
487
540
|
let last = { ok: false, error: "not attempted" };
|
|
488
541
|
for (const d of delays) {
|
|
489
542
|
if (d) await sleep(d);
|
|
490
|
-
last = await this.registerContent(content,
|
|
543
|
+
last = await this.registerContent(content, txid3);
|
|
491
544
|
if (last.ok) return last;
|
|
492
545
|
if (!/not found yet|not found/i.test(last.error ?? "")) return last;
|
|
493
546
|
}
|
|
@@ -594,10 +647,10 @@ var IndexerClient = class {
|
|
|
594
647
|
* same-origin RPC safe is the forward PoW re-verification, not an origin gate (which no longer exists).
|
|
595
648
|
* Fails closed: a malformed proof or a chain-disagreement returns "not-found" (the latter with equivocation:true).
|
|
596
649
|
*/
|
|
597
|
-
async verifyInclusion(
|
|
650
|
+
async verifyInclusion(txid3) {
|
|
598
651
|
let proof;
|
|
599
652
|
try {
|
|
600
|
-
proof = await this.txMerkleProof(
|
|
653
|
+
proof = await this.txMerkleProof(txid3);
|
|
601
654
|
} catch {
|
|
602
655
|
return { included: false, trustLevel: "not-found", reason: "no merkle proof (tx not indexed)" };
|
|
603
656
|
}
|
|
@@ -606,7 +659,7 @@ var IndexerClient = class {
|
|
|
606
659
|
}
|
|
607
660
|
let folds = false;
|
|
608
661
|
try {
|
|
609
|
-
folds = (0, import_csd_codec4.verifyMerkleProof)(
|
|
662
|
+
folds = (0, import_csd_codec4.verifyMerkleProof)(txid3, proof.pos, proof.merkle, proof.merkle_root);
|
|
610
663
|
} catch {
|
|
611
664
|
return { included: false, trustLevel: "not-found", blockHeight: proof.block_height, reason: "malformed merkle proof" };
|
|
612
665
|
}
|
|
@@ -883,7 +936,7 @@ var NamesClient = class {
|
|
|
883
936
|
async resolve(name) {
|
|
884
937
|
if (!nameOk(name)) return null;
|
|
885
938
|
try {
|
|
886
|
-
return await this.http.getJson(`/trade/api/cairnx/resolve/${name}`);
|
|
939
|
+
return await this.http.getJson(`/trade/api/cairnx/resolve/${encodeURIComponent(name)}`);
|
|
887
940
|
} catch (e) {
|
|
888
941
|
if (e?.status === 404) return null;
|
|
889
942
|
throw e;
|
|
@@ -893,7 +946,7 @@ var NamesClient = class {
|
|
|
893
946
|
async name(name) {
|
|
894
947
|
if (!nameOk(name)) return null;
|
|
895
948
|
try {
|
|
896
|
-
return await this.http.getJson(`/trade/api/cairnx/name/${name}`);
|
|
949
|
+
return await this.http.getJson(`/trade/api/cairnx/name/${encodeURIComponent(name)}`);
|
|
897
950
|
} catch (e) {
|
|
898
951
|
if (e?.status === 404) return null;
|
|
899
952
|
throw e;
|
|
@@ -903,7 +956,7 @@ var NamesClient = class {
|
|
|
903
956
|
async nameHistory(name) {
|
|
904
957
|
if (!nameOk(name)) return null;
|
|
905
958
|
try {
|
|
906
|
-
return await this.http.getJson(`/trade/api/cairnx/name-history/${name}`);
|
|
959
|
+
return await this.http.getJson(`/trade/api/cairnx/name-history/${encodeURIComponent(name)}`);
|
|
907
960
|
} catch (e) {
|
|
908
961
|
if (e?.status === 404) return null;
|
|
909
962
|
throw e;
|
|
@@ -1031,6 +1084,13 @@ function unwrap(reply) {
|
|
|
1031
1084
|
if (reply && reply.ok) return reply.result;
|
|
1032
1085
|
throw mapProviderError(reply?.error ?? "Unknown wallet error", reply?.code);
|
|
1033
1086
|
}
|
|
1087
|
+
function unwrapWrite(reply) {
|
|
1088
|
+
const result = unwrap(reply);
|
|
1089
|
+
if (result && typeof result === "object" && result.ok === false) {
|
|
1090
|
+
throw mapSubmitResultError(result);
|
|
1091
|
+
}
|
|
1092
|
+
return result;
|
|
1093
|
+
}
|
|
1034
1094
|
var WalletConnection = class {
|
|
1035
1095
|
provider;
|
|
1036
1096
|
_addr = null;
|
|
@@ -1097,12 +1157,18 @@ var WalletConnection = class {
|
|
|
1097
1157
|
return null;
|
|
1098
1158
|
}
|
|
1099
1159
|
}
|
|
1100
|
-
/**
|
|
1160
|
+
/**
|
|
1161
|
+
* CairnX fill: pay + attest in ONE tx (atomic SETTLEMENT SHAPE, both revert together). Always prompts with
|
|
1162
|
+
* clear-signing. The payment recipient in `params.outputs` is RESOLVER-TRUSTED here: this SDK does NOT
|
|
1163
|
+
* SPV-verify it, so a lying resolver can redirect the payment. A dApp settling real value MUST corroborate
|
|
1164
|
+
* the offer on-chain before building `outputs` (F13). The wallet's own fill-SPV boundary (cairn-wallet
|
|
1165
|
+
* 0.2.60+, which binds payment/rebate/fee to the merkle-proven offer) is the payment-grade check; see FillParams.
|
|
1166
|
+
*/
|
|
1101
1167
|
fillOffer(params) {
|
|
1102
1168
|
if (typeof this.provider.fillOffer !== "function") {
|
|
1103
1169
|
return Promise.reject(new UnsupportedMethodError("this wallet predates fillOffer() \u2014 ask the user to update the Cairn Wallet"));
|
|
1104
1170
|
}
|
|
1105
|
-
return Promise.resolve(this.provider.fillOffer(params)).then(
|
|
1171
|
+
return Promise.resolve(this.provider.fillOffer(params)).then(unwrapWrite);
|
|
1106
1172
|
}
|
|
1107
1173
|
/** This origin's current permission grant (EIP-2255-style). Silent; [] if none / unsupported. */
|
|
1108
1174
|
async getPermissions() {
|
|
@@ -1135,26 +1201,26 @@ var WalletConnection = class {
|
|
|
1135
1201
|
}
|
|
1136
1202
|
/** Send CSD (always prompts with clear-signing; wallet picks inputs + returns change to itself). */
|
|
1137
1203
|
send(params) {
|
|
1138
|
-
return Promise.resolve(this.provider.send(params)).then(
|
|
1204
|
+
return Promise.resolve(this.provider.send(params)).then(unwrapWrite);
|
|
1139
1205
|
}
|
|
1140
1206
|
/** Low-level Propose (always prompts). Prefer `cairn.board.propose()` which computes the hash/uri/expiry. */
|
|
1141
1207
|
propose(params) {
|
|
1142
|
-
return Promise.resolve(this.provider.propose(params)).then(
|
|
1208
|
+
return Promise.resolve(this.provider.propose(params)).then(unwrapWrite);
|
|
1143
1209
|
}
|
|
1144
1210
|
/** Attest/support a proposal (always prompts). */
|
|
1145
1211
|
attest(params) {
|
|
1146
|
-
return Promise.resolve(this.provider.attest(params)).then(
|
|
1212
|
+
return Promise.resolve(this.provider.attest(params)).then(unwrapWrite);
|
|
1147
1213
|
}
|
|
1148
1214
|
/** Seal a commit-reveal claim (always prompts; salt stays inside the wallet). */
|
|
1149
1215
|
sealClaim(params) {
|
|
1150
|
-
return Promise.resolve(this.provider.sealClaim(params)).then(
|
|
1216
|
+
return Promise.resolve(this.provider.sealClaim(params)).then(unwrapWrite);
|
|
1151
1217
|
}
|
|
1152
1218
|
/** Reveal a previously sealed claim by its sealing txid (always prompts). */
|
|
1153
|
-
revealClaim(
|
|
1154
|
-
if (typeof
|
|
1219
|
+
revealClaim(txid3) {
|
|
1220
|
+
if (typeof txid3 !== "string" || !txid3) {
|
|
1155
1221
|
return Promise.reject(new UnsupportedMethodError("revealClaim requires the sealing txid"));
|
|
1156
1222
|
}
|
|
1157
|
-
return Promise.resolve(this.provider.revealClaim(
|
|
1223
|
+
return Promise.resolve(this.provider.revealClaim(txid3)).then(unwrapWrite);
|
|
1158
1224
|
}
|
|
1159
1225
|
};
|
|
1160
1226
|
async function getWallet(opts = {}) {
|
|
@@ -1166,6 +1232,92 @@ async function connect(opts = {}) {
|
|
|
1166
1232
|
return { wallet, address };
|
|
1167
1233
|
}
|
|
1168
1234
|
|
|
1235
|
+
// src/fillverify.ts
|
|
1236
|
+
var import_csd_client3 = require("@inversealtruism/csd-client");
|
|
1237
|
+
var import_csd_codec6 = require("@inversealtruism/csd-codec");
|
|
1238
|
+
var import_cairnx_core2 = require("@inversealtruism/cairnx-core");
|
|
1239
|
+
var ADDR = /^0x[0-9a-f]{40}$/;
|
|
1240
|
+
var HASH = /^0x[0-9a-f]{64}$/;
|
|
1241
|
+
var COINBASE = "0x" + "00".repeat(32);
|
|
1242
|
+
async function provenAuthor(client, provenTx) {
|
|
1243
|
+
const in0 = provenTx.inputs?.[0];
|
|
1244
|
+
if (!in0 || String(in0.prevTxid).toLowerCase() === COINBASE && Number(in0.vout) === 4294967295) return null;
|
|
1245
|
+
let info;
|
|
1246
|
+
try {
|
|
1247
|
+
info = await client.tx(String(in0.prevTxid));
|
|
1248
|
+
} catch {
|
|
1249
|
+
return null;
|
|
1250
|
+
}
|
|
1251
|
+
const body = info?.tx ?? info;
|
|
1252
|
+
if (!body || !Array.isArray(body.outputs) || !Array.isArray(body.inputs)) return null;
|
|
1253
|
+
let src;
|
|
1254
|
+
try {
|
|
1255
|
+
src = (0, import_csd_client3.rpcTxToTx)(body);
|
|
1256
|
+
} catch {
|
|
1257
|
+
return null;
|
|
1258
|
+
}
|
|
1259
|
+
if (String((0, import_csd_codec6.txid)(src)).toLowerCase() !== String(in0.prevTxid).toLowerCase()) return null;
|
|
1260
|
+
const out = src.outputs?.[Number(in0.vout)];
|
|
1261
|
+
const spk = out?.scriptPubkey ? String(out.scriptPubkey).toLowerCase() : null;
|
|
1262
|
+
return spk && ADDR.test(spk) ? spk : null;
|
|
1263
|
+
}
|
|
1264
|
+
async function preverifyOffer(opts) {
|
|
1265
|
+
const id = String(opts.offerId).toLowerCase();
|
|
1266
|
+
if (!HASH.test(id)) return { ok: false, trust: "unverified", reason: "invalid offer id" };
|
|
1267
|
+
let incl;
|
|
1268
|
+
try {
|
|
1269
|
+
incl = await opts.light.verifyTxInclusion(id);
|
|
1270
|
+
} catch (e) {
|
|
1271
|
+
return { ok: false, trust: "unverified", transient: true, reason: `couldn't reach the chain to verify the offer (${e?.message ?? e})` };
|
|
1272
|
+
}
|
|
1273
|
+
if (!incl.included || incl.trustLevel !== "verified-inclusion" || !incl.tx || !incl.appPayloadHash)
|
|
1274
|
+
return { ok: false, trust: "unverified", transient: true, blockHeight: incl.blockHeight, reason: incl.reason ?? `offer not merkle-proven on-chain (${incl.trustLevel})` };
|
|
1275
|
+
const blockHeight = Number(incl.blockHeight);
|
|
1276
|
+
let provenTx, provenId;
|
|
1277
|
+
try {
|
|
1278
|
+
provenTx = (0, import_csd_client3.rpcTxToTx)(incl.tx);
|
|
1279
|
+
provenId = (0, import_csd_codec6.txid)(provenTx);
|
|
1280
|
+
} catch {
|
|
1281
|
+
return { ok: false, trust: "unverified", reason: "the on-chain offer tx couldn't be re-derived" };
|
|
1282
|
+
}
|
|
1283
|
+
if (String(provenId).toLowerCase() !== id) return { ok: false, trust: "unverified", reason: "the merkle-proven tx doesn't match the offer id" };
|
|
1284
|
+
const app = incl.tx.app;
|
|
1285
|
+
if (!app || app.type !== "Propose") return { ok: false, trust: "unverified", reason: "the offer tx is not a Propose" };
|
|
1286
|
+
let rec;
|
|
1287
|
+
try {
|
|
1288
|
+
rec = (0, import_cairnx_core2.parseRecord)(app.uri, incl.appPayloadHash);
|
|
1289
|
+
} catch {
|
|
1290
|
+
rec = null;
|
|
1291
|
+
}
|
|
1292
|
+
if (!rec || rec.t !== "offer") return { ok: false, trust: "unverified", reason: "the offer record doesn't bind to its on-chain commitment" };
|
|
1293
|
+
if (String((0, import_csd_codec6.payloadHash)(rec)).toLowerCase() !== String(incl.appPayloadHash).toLowerCase())
|
|
1294
|
+
return { ok: false, trust: "unverified", reason: "the offer record doesn't match the on-chain (merkle-proven) commitment" };
|
|
1295
|
+
const seller = await provenAuthor(opts.client, provenTx);
|
|
1296
|
+
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" };
|
|
1297
|
+
const w = rec.want ?? {};
|
|
1298
|
+
const payto = w.payto && ADDR.test(String(w.payto).toLowerCase()) ? String(w.payto).toLowerCase() : seller;
|
|
1299
|
+
const terms = {
|
|
1300
|
+
height: blockHeight,
|
|
1301
|
+
feeBps: (0, import_cairnx_core2.feeBpsAt)(blockHeight),
|
|
1302
|
+
value: w.value !== void 0 ? String(w.value) : void 0,
|
|
1303
|
+
taker: rec.taker !== void 0 ? String(rec.taker).toLowerCase() : void 0,
|
|
1304
|
+
bid: rec.bid !== void 0 ? String(rec.bid).toLowerCase() : void 0,
|
|
1305
|
+
min: rec.min !== void 0 ? String(rec.min) : void 0
|
|
1306
|
+
};
|
|
1307
|
+
const base = { payto, seller, terms, blockHeight };
|
|
1308
|
+
if (opts.servedOffer !== void 0 && opts.servedOffer !== null) {
|
|
1309
|
+
const so = opts.servedOffer;
|
|
1310
|
+
const servedPayto = so?.want?.payto !== void 0 && so?.want?.payto !== null ? String(so.want.payto).toLowerCase() : "";
|
|
1311
|
+
if (servedPayto && servedPayto !== payto)
|
|
1312
|
+
return { ok: false, trust: "verified", reason: "the served payment recipient doesn't match the offer's on-chain author/record", ...base };
|
|
1313
|
+
if (so?.seller !== void 0 && so?.seller !== null && String(so.seller).toLowerCase() !== seller)
|
|
1314
|
+
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 };
|
|
1315
|
+
if ((0, import_cairnx_core2.bindOfferTerms)(opts.servedOffer, terms))
|
|
1316
|
+
return { ok: false, trust: "verified", reason: "the served fee/rebate/partial terms don't match the offer's on-chain record (a lying resolver could mis-size the fill, causing a rejected fill after your payment)", ...base };
|
|
1317
|
+
}
|
|
1318
|
+
return { ok: true, trust: "verified", ...base };
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1169
1321
|
// src/controller.ts
|
|
1170
1322
|
var DISCONNECTED = Object.freeze({ status: "disconnected", account: null, error: null });
|
|
1171
1323
|
var CairnController = class {
|
|
@@ -1307,18 +1459,39 @@ var Cairn = class {
|
|
|
1307
1459
|
async verifiedHeaderMerkleAt(height) {
|
|
1308
1460
|
if (!Number.isInteger(height) || height < this._spvCp.height)
|
|
1309
1461
|
throw new Error(`height ${height} is below the SPV checkpoint ${this._spvCp.height} (cannot PoW-verify backward)`);
|
|
1310
|
-
|
|
1311
|
-
const lc = this._spvLight;
|
|
1312
|
-
if (!this._spvSeeded) {
|
|
1313
|
-
await lc.syncFromCheckpoint(this._spvCp.height, this._spvCp.hash);
|
|
1314
|
-
this._spvSeeded = true;
|
|
1315
|
-
}
|
|
1462
|
+
const lc = await this.seededSpvLight();
|
|
1316
1463
|
const haveTo = lc.baseHeight + lc.chain.length - 1;
|
|
1317
1464
|
if (haveTo < height) await lc.sync(height);
|
|
1318
1465
|
const vh = lc.chain[height - lc.baseHeight];
|
|
1319
1466
|
if (!vh) throw new Error(`light client has no verified header at ${height}`);
|
|
1320
1467
|
return String(vh.header.merkle);
|
|
1321
1468
|
}
|
|
1469
|
+
/** The PoW-verifying SPV light client, seeded ONCE from the pinned checkpoint (shared by the M3 header-merkle
|
|
1470
|
+
* bind and the F13 offer pre-verify). Reused across calls; the first call near tip syncs the post-checkpoint span. */
|
|
1471
|
+
async seededSpvLight() {
|
|
1472
|
+
if (!this._spvLight) this._spvLight = new import_csd_light.LightClient({ client: this.chain.client, checkpoints: { [this._spvCp.height]: this._spvCp.hash } });
|
|
1473
|
+
if (!this._spvSeeded) {
|
|
1474
|
+
await this._spvLight.syncFromCheckpoint(this._spvCp.height, this._spvCp.hash);
|
|
1475
|
+
this._spvSeeded = true;
|
|
1476
|
+
}
|
|
1477
|
+
return this._spvLight;
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* F13 — corroborate an offer ON-CHAIN before building a `fillOffer` payment's `outputs`. Merkle-proves the
|
|
1481
|
+
* offer into the PoW-verified header chain (from the pinned SPV checkpoint), binds the record to its on-chain
|
|
1482
|
+
* commitment, derives the payment recipient + seller from the offer's on-chain AUTHOR (the funding input's
|
|
1483
|
+
* prevout owner, txid-committed, NOT the malleable scriptSig), and, if you pass the resolver-SERVED `offer`
|
|
1484
|
+
* object, binds its payto/seller + fee/rebate/partial terms to the proven ones. Returns a TRUST-LABELED result
|
|
1485
|
+
* (fail-closed on a positive mismatch, fail-soft `transient` on a lagging / below-checkpoint chain view).
|
|
1486
|
+
*
|
|
1487
|
+
* This is best-effort corroboration, NOT the payment-grade boundary: the Cairn Wallet's OWN on-device
|
|
1488
|
+
* fill-SPV (0.2.60+, fails-closed before signing) is that. A dApp settling real value should still clear this
|
|
1489
|
+
* before building `outputs`, so it never hands the wallet a payment a lying resolver would redirect.
|
|
1490
|
+
*/
|
|
1491
|
+
async verifyOfferForFill(offerId, servedOffer) {
|
|
1492
|
+
const light = await this.seededSpvLight();
|
|
1493
|
+
return preverifyOffer({ light, client: this.chain.client, offerId, servedOffer });
|
|
1494
|
+
}
|
|
1322
1495
|
/**
|
|
1323
1496
|
* Detect + connect the Cairn Wallet (browser only). Wires the wallet into
|
|
1324
1497
|
* `cairn.board` for signed writes. Resolves to the connected address.
|
|
@@ -1355,15 +1528,20 @@ var Cairn = class {
|
|
|
1355
1528
|
NotInstalledError,
|
|
1356
1529
|
RegistryClient,
|
|
1357
1530
|
SDK_VERSION,
|
|
1531
|
+
SubmitInFlightError,
|
|
1358
1532
|
UnsupportedMethodError,
|
|
1359
1533
|
UserRejectedError,
|
|
1360
1534
|
WalletConnection,
|
|
1361
1535
|
WalletLockedError,
|
|
1536
|
+
bindOfferTerms,
|
|
1362
1537
|
connect,
|
|
1363
1538
|
detectProvider,
|
|
1364
1539
|
discoverProviders,
|
|
1365
1540
|
errorCode,
|
|
1541
|
+
feeBpsAt,
|
|
1366
1542
|
getWallet,
|
|
1367
1543
|
isInstalled,
|
|
1368
|
-
mapProviderError
|
|
1544
|
+
mapProviderError,
|
|
1545
|
+
mapSubmitResultError,
|
|
1546
|
+
preverifyOffer
|
|
1369
1547
|
});
|