@inversealtruism/cairn-sdk 0.8.0 → 0.8.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/README.md +3 -3
- package/dist/chain.cjs +83 -3
- package/dist/chain.d.cts +9 -4
- package/dist/chain.d.ts +9 -4
- package/dist/chain.js +6 -1
- package/dist/{chunk-DTPADO7N.js → chunk-2RR4ZJPQ.js} +1 -1
- package/dist/{chunk-UAPWZZQK.js → chunk-37OHCH7R.js} +20 -8
- package/dist/{chunk-JXBO4LV6.js → chunk-AHPXOYCC.js} +1 -1
- package/dist/{chunk-WWO3FXHG.js → chunk-EVEBQSDK.js} +19 -14
- package/dist/{chunk-C452UODV.js → chunk-IPBMMJ3Z.js} +26 -4
- package/dist/{chunk-CI7YODPP.js → chunk-NRCTL7OI.js} +43 -3
- package/dist/connect.cjs +34 -20
- package/dist/connect.js +2 -2
- package/dist/content.cjs +1 -1
- package/dist/content.js +2 -2
- package/dist/controller.cjs +34 -20
- package/dist/controller.js +3 -3
- package/dist/index.cjs +268 -99
- package/dist/index.d.cts +77 -32
- package/dist/index.d.ts +77 -32
- package/dist/index.js +190 -82
- package/dist/indexer.cjs +98 -3
- package/dist/indexer.d.cts +5 -1
- package/dist/indexer.d.ts +5 -1
- package/dist/indexer.js +2 -1
- package/dist/react.cjs +34 -20
- package/dist/react.js +3 -3
- package/package.json +10 -7
package/dist/index.cjs
CHANGED
|
@@ -58,7 +58,7 @@ __export(index_exports, {
|
|
|
58
58
|
module.exports = __toCommonJS(index_exports);
|
|
59
59
|
|
|
60
60
|
// src/errors.ts
|
|
61
|
-
var SDK_VERSION = "0.8.
|
|
61
|
+
var SDK_VERSION = "0.8.2";
|
|
62
62
|
var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
|
|
63
63
|
var CairnError = class extends Error {
|
|
64
64
|
/** Stable machine code for programmatic branching. */
|
|
@@ -141,17 +141,22 @@ var ContentVerificationError = class extends CairnError {
|
|
|
141
141
|
}
|
|
142
142
|
};
|
|
143
143
|
var SubmitInFlightError = class extends CairnError {
|
|
144
|
-
/** The locally-computed consensus txid to reconcile against the chain (
|
|
144
|
+
/** The locally-computed consensus txid to reconcile against the chain (undefined when it is not known). */
|
|
145
145
|
txid;
|
|
146
|
-
/**
|
|
146
|
+
/** The signed transaction, set only when `Chain.send` threw this (a wallet write keeps it in the wallet).
|
|
147
|
+
* `chain.submit(nodeJson)` resends it unchanged: the same coins under the same txid, so it cannot pay twice,
|
|
148
|
+
* where a new send may select other coins. */
|
|
149
|
+
nodeJson;
|
|
150
|
+
/** Always true: the tx MAY be in the mempool. Reconcile via `txid` or the balance; never blind-retry. */
|
|
147
151
|
maybeSent = true;
|
|
148
|
-
constructor(message, code, txid3) {
|
|
149
|
-
super(message, { code, docsPath: "/errors#submit-inflight", retryable: false });
|
|
152
|
+
constructor(message, code, txid3, cause, nodeJson) {
|
|
153
|
+
super(message, { code, docsPath: "/errors#submit-inflight", retryable: false, cause });
|
|
150
154
|
this.txid = txid3;
|
|
155
|
+
this.nodeJson = nodeJson;
|
|
151
156
|
}
|
|
152
157
|
};
|
|
153
158
|
var NATIVE_CODES = /* @__PURE__ */ new Set(["FIRST_PARTY_ONLY", "RATE_LIMITED", "FORBIDDEN", "INTERNAL"]);
|
|
154
|
-
var NESTED_TRANSIENT = /* @__PURE__ */ new Set(["VERIFY_UNAVAILABLE", "OFFER_UNKNOWN", "WALLET_UNAVAILABLE", "REVIEW_REQUIRED"]);
|
|
159
|
+
var NESTED_TRANSIENT = /* @__PURE__ */ new Set(["VERIFY_UNAVAILABLE", "OFFER_UNKNOWN", "WALLET_UNAVAILABLE", "REVIEW_REQUIRED", "SEAL_NOT_MINED"]);
|
|
155
160
|
var NESTED_TERMINAL = /* @__PURE__ */ new Set([
|
|
156
161
|
"FILL_UNSAFE",
|
|
157
162
|
"FILL_WRONG_TARGET",
|
|
@@ -173,7 +178,9 @@ var NESTED_TERMINAL = /* @__PURE__ */ new Set([
|
|
|
173
178
|
"OFFER_SOURCE_CONFLICT",
|
|
174
179
|
"FILL_LEGACY_SUNSET",
|
|
175
180
|
"VERIFY_HORIZON",
|
|
176
|
-
"VERIFY_UNREPRESENTABLE"
|
|
181
|
+
"VERIFY_UNREPRESENTABLE",
|
|
182
|
+
"RESERVATION_DENIED",
|
|
183
|
+
"RPC_BLOCK_MISMATCH"
|
|
177
184
|
]);
|
|
178
185
|
function mapProviderError(error, code) {
|
|
179
186
|
if (code === "USER_REJECTED" || code === "APPROVAL_CLOSED") return new UserRejectedError(error);
|
|
@@ -191,11 +198,15 @@ function mapProviderError(error, code) {
|
|
|
191
198
|
function mapSubmitResultError(result) {
|
|
192
199
|
const code = result?.code;
|
|
193
200
|
const msg = typeof result?.error === "string" && result.error || code || "The wallet refused the transaction.";
|
|
194
|
-
if (code === "SUBMIT_MAYBE_INFLIGHT" || code === "SUBMIT_DUPLICATE") {
|
|
201
|
+
if (code === "SUBMIT_MAYBE_INFLIGHT" || code === "SUBMIT_DUPLICATE" || code === "WALLET_UNAVAILABLE") {
|
|
195
202
|
return new SubmitInFlightError(msg, code, typeof result?.txid === "string" ? result.txid : void 0);
|
|
196
203
|
}
|
|
197
204
|
return mapProviderError(msg, code);
|
|
198
205
|
}
|
|
206
|
+
function mapWriteProviderError(error, code) {
|
|
207
|
+
if (code === "WALLET_UNAVAILABLE") return new SubmitInFlightError(error || code, code);
|
|
208
|
+
return mapProviderError(error, code);
|
|
209
|
+
}
|
|
199
210
|
function errorCode(err) {
|
|
200
211
|
return err instanceof CairnError ? err.code : "UNKNOWN";
|
|
201
212
|
}
|
|
@@ -404,7 +415,11 @@ var Chain = class {
|
|
|
404
415
|
* chain (`verifyInputValues` recomputes each source tx's txid, so a lying RPC can't under-report) and
|
|
405
416
|
* computes change from the VERIFIED total — closing the UTXO-VALUE-1 implicit-fee-burn. Fail-closed:
|
|
406
417
|
* if any source can't be verified it signs nothing. Prefer this over the pure `buildSend`. Pass
|
|
407
|
-
* `broadcast:false` to get the signed tx back without submitting.
|
|
418
|
+
* `broadcast:false` to get the signed tx back without submitting. A submit request that fails after signing
|
|
419
|
+
* throws a `SubmitInFlightError` carrying the signed `txid` and `nodeJson`: the node may have taken it, so check
|
|
420
|
+
* before resending, and resend that `nodeJson` with `submit` rather than a new send. A 4xx answer (a proxy's
|
|
421
|
+
* body cap or rate limit, or the node refusing the request) broadcast nothing and throws a final `CairnError`
|
|
422
|
+
* with code `SUBMIT_REJECTED`.
|
|
408
423
|
*/
|
|
409
424
|
async send(p) {
|
|
410
425
|
const from = (0, import_csd_crypto2.addrFromPriv)(p.priv);
|
|
@@ -418,10 +433,19 @@ var Chain = class {
|
|
|
418
433
|
verify: (inputs) => (0, import_csd_client2.verifyInputValues)(this.client, inputs)
|
|
419
434
|
});
|
|
420
435
|
if (!built.ok || p.broadcast === false) return built;
|
|
421
|
-
|
|
436
|
+
let submitResult;
|
|
437
|
+
try {
|
|
438
|
+
submitResult = await this.client.submit(built.nodeJson);
|
|
439
|
+
} catch (e) {
|
|
440
|
+
const what = e?.message ?? String(e);
|
|
441
|
+
if (e?.terminal === true)
|
|
442
|
+
throw new CairnError(`the submit request was refused (${what}), so nothing was broadcast; a 400 or 413 from a proxy is usually its request body cap (a transaction with many inputs), a 429 its rate limit`, { code: "SUBMIT_REJECTED", cause: e });
|
|
443
|
+
throw new SubmitInFlightError(`the submit request failed after signing (${what}); the node may have taken the transaction, so check ${built.txid} before sending again`, "SUBMIT_MAYBE_INFLIGHT", built.txid, e, built.nodeJson);
|
|
444
|
+
}
|
|
422
445
|
return { ...built, ok: built.ok && submitResult.ok !== false, submitResult };
|
|
423
446
|
}
|
|
424
|
-
/** A
|
|
447
|
+
/** A light client over the same RPC (built once, reused), with no checkpoint and no batch header provider: it proves
|
|
448
|
+
* nothing until seeded. The facade's seeded client backs `cairn.index.verifyInclusion` and `verifyOfferForFill`. */
|
|
425
449
|
light() {
|
|
426
450
|
if (!this._light) {
|
|
427
451
|
this._light = new import_csd_light2.LightClient({ client: this.client });
|
|
@@ -434,6 +458,20 @@ var Chain = class {
|
|
|
434
458
|
var import_csd_codec2 = require("@inversealtruism/csd-codec");
|
|
435
459
|
var HEX64 = /^0x[0-9a-fA-F]{64}$/;
|
|
436
460
|
var MAX_SSE_BUF = 1024 * 1024;
|
|
461
|
+
var UTXO_PAGE = 1e3;
|
|
462
|
+
var UTXO_MAX_PAGES = 100;
|
|
463
|
+
var UTXO_CURSOR = /^\d{1,16}:0x[0-9a-fA-F]{64}:\d{1,10}$/;
|
|
464
|
+
function utxoCursor(row) {
|
|
465
|
+
const r = row;
|
|
466
|
+
const c = `${String(r?.status?.block_height)}:${String(r?.txid)}:${String(r?.vout)}`;
|
|
467
|
+
return UTXO_CURSOR.test(c) ? c : null;
|
|
468
|
+
}
|
|
469
|
+
function cursorAfter(a, b) {
|
|
470
|
+
const [ha, ta, va] = a.split(":"), [hb2, tb, vb] = b.split(":");
|
|
471
|
+
if (Number(ha) !== Number(hb2)) return Number(ha) > Number(hb2);
|
|
472
|
+
if (ta.toLowerCase() !== tb.toLowerCase()) return ta.toLowerCase() > tb.toLowerCase();
|
|
473
|
+
return Number(va) > Number(vb);
|
|
474
|
+
}
|
|
437
475
|
function isValidMerkleProof(p) {
|
|
438
476
|
if (!p || typeof p !== "object") return false;
|
|
439
477
|
const m = p;
|
|
@@ -483,8 +521,29 @@ var IndexerClient = class {
|
|
|
483
521
|
addressTxs(a) {
|
|
484
522
|
return this.http.getJson(`/address/${encodeURIComponent(a)}/txs`);
|
|
485
523
|
}
|
|
486
|
-
|
|
487
|
-
|
|
524
|
+
/** Every unspent output of an address. The indexer answers in pages of at most 1,000 rows; this follows its
|
|
525
|
+
* `?after=` cursor until a short page, reading at most 100 pages: when the 100th is full (100,000 or more
|
|
526
|
+
* outputs) it throws HttpError instead. An indexer that predates paging answers the whole set at once. */
|
|
527
|
+
async addressUtxo(a) {
|
|
528
|
+
const path = `/address/${encodeURIComponent(a)}/utxo`;
|
|
529
|
+
const first = await this.http.getJson(path);
|
|
530
|
+
if (!Array.isArray(first) || first.length !== UTXO_PAGE) return first;
|
|
531
|
+
const out = [...first];
|
|
532
|
+
let page = first;
|
|
533
|
+
let prev = null;
|
|
534
|
+
for (let pages = 1; page.length === UTXO_PAGE; pages++) {
|
|
535
|
+
if (pages === UTXO_MAX_PAGES)
|
|
536
|
+
throw new HttpError(200, this.http.url(path), `too many unspent outputs: addressUtxo reads at most ${UTXO_MAX_PAGES} pages of ${UTXO_PAGE} rows, and page ${UTXO_MAX_PAGES} is full`);
|
|
537
|
+
const after = utxoCursor(page[UTXO_PAGE - 1]);
|
|
538
|
+
if (!after) throw new HttpError(200, this.http.url(path), "malformed response: a utxo row has no block_height, txid and vout to page from");
|
|
539
|
+
if (prev !== null && !cursorAfter(after, prev)) throw new HttpError(200, this.http.url(path, { after }), "malformed response: the utxo pages do not advance");
|
|
540
|
+
page = await this.http.getJson(path, { after });
|
|
541
|
+
if (!Array.isArray(page)) throw new HttpError(200, this.http.url(path, { after }), "malformed response: a utxo page is not an array");
|
|
542
|
+
if (prev === null && page.length > 0 && utxoCursor(page[0]) === utxoCursor(first[0])) return first;
|
|
543
|
+
prev = after;
|
|
544
|
+
out.push(...page);
|
|
545
|
+
}
|
|
546
|
+
return out;
|
|
488
547
|
}
|
|
489
548
|
reputation(a) {
|
|
490
549
|
return this.http.getJson(`/address/${encodeURIComponent(a)}/reputation`);
|
|
@@ -503,7 +562,8 @@ var IndexerClient = class {
|
|
|
503
562
|
}
|
|
504
563
|
// ---- trust-minimized inclusion ----------------------------------------
|
|
505
564
|
/**
|
|
506
|
-
* Verify
|
|
565
|
+
* Verify that `txid` is a node of a block's merkle tree (an inner node or the root folds too, so pass an id you
|
|
566
|
+
* hashed from a transaction body you hold) by folding its merkle branch to the root
|
|
507
567
|
* with csd-codec.verifyMerkleProof. If `headerMerkleAt` was provided, the
|
|
508
568
|
* proof's root is cross-checked against the header merkle at that height →
|
|
509
569
|
* trustLevel "verified-inclusion" (only as trustworthy as that header source —
|
|
@@ -987,7 +1047,8 @@ function unwrap(reply) {
|
|
|
987
1047
|
throw mapProviderError(reply?.error ?? "Unknown wallet error", reply?.code);
|
|
988
1048
|
}
|
|
989
1049
|
function unwrapWrite(reply) {
|
|
990
|
-
|
|
1050
|
+
if (!reply || !reply.ok) throw mapWriteProviderError(reply?.error ?? "Unknown wallet error", reply?.code);
|
|
1051
|
+
const result = reply.result;
|
|
991
1052
|
if (result && typeof result === "object" && result.ok === false) {
|
|
992
1053
|
throw mapSubmitResultError(result);
|
|
993
1054
|
}
|
|
@@ -1003,10 +1064,12 @@ var WalletConnection = class {
|
|
|
1003
1064
|
this.provider = provider;
|
|
1004
1065
|
this._timeoutMs = opts.timeoutMs ?? DEFAULT_PROVIDER_TIMEOUT_MS;
|
|
1005
1066
|
}
|
|
1006
|
-
// B7c: race a provider call against the per-call timeout. On timeout, throw a fail-closed
|
|
1007
|
-
// (retryable:false
|
|
1008
|
-
//
|
|
1009
|
-
|
|
1067
|
+
// B7c: race a provider call against the per-call timeout. On timeout, throw a fail-closed error
|
|
1068
|
+
// (retryable:false). A write's request stays open in the wallet and may still be approved and sent, so its
|
|
1069
|
+
// timeout is a maybe-sent SubmitInFlightError (WALLET_TIMEOUT, no txid); while that request stays open, a resend
|
|
1070
|
+
// opens a second one, and approving both sends twice. The genuine wallet resolves quickly
|
|
1071
|
+
// once the user acts; this only fires on a truly hung/absent response.
|
|
1072
|
+
call(op, label, write = false) {
|
|
1010
1073
|
const ms = this._timeoutMs;
|
|
1011
1074
|
const invoke = () => new Promise((resolve) => {
|
|
1012
1075
|
resolve(op());
|
|
@@ -1014,7 +1077,7 @@ var WalletConnection = class {
|
|
|
1014
1077
|
if (!Number.isFinite(ms) || ms <= 0) return invoke();
|
|
1015
1078
|
let timer;
|
|
1016
1079
|
const timeout = new Promise((_, reject) => {
|
|
1017
|
-
timer = setTimeout(() => reject(new CairnError(`the wallet did not respond to ${label} within ${ms}ms`, { retryable: false })), ms);
|
|
1080
|
+
timer = setTimeout(() => reject(write ? new SubmitInFlightError(`the wallet did not respond to ${label} within ${ms}ms; the request may still be open in the wallet and be sent if approved, so reject it in the wallet and check the chain before sending it again`, "WALLET_TIMEOUT") : new CairnError(`the wallet did not respond to ${label} within ${ms}ms`, { retryable: false })), ms);
|
|
1018
1081
|
});
|
|
1019
1082
|
return Promise.race([invoke(), timeout]).finally(() => clearTimeout(timer));
|
|
1020
1083
|
}
|
|
@@ -1090,7 +1153,7 @@ var WalletConnection = class {
|
|
|
1090
1153
|
return Promise.reject(new UnsupportedMethodError("this wallet predates fillOffer() \u2014 ask the user to update the Cairn Wallet"));
|
|
1091
1154
|
}
|
|
1092
1155
|
this.adviseFillSpvCapability();
|
|
1093
|
-
return this.call(() => this.provider.fillOffer(params), "fillOffer").then(unwrapWrite);
|
|
1156
|
+
return this.call(() => this.provider.fillOffer(params), "fillOffer", true).then(unwrapWrite);
|
|
1094
1157
|
}
|
|
1095
1158
|
// P75-5 MF-10: ONE capability advisory per connection. The wallet's on-device fill-SPV (0.2.60+) is the
|
|
1096
1159
|
// payment-grade fill boundary; when the provider SELF-REPORTS an older version, warn the integrator once
|
|
@@ -1138,26 +1201,26 @@ var WalletConnection = class {
|
|
|
1138
1201
|
}
|
|
1139
1202
|
/** Send CSD (always prompts with clear-signing; wallet picks inputs + returns change to itself). */
|
|
1140
1203
|
send(params) {
|
|
1141
|
-
return this.call(() => this.provider.send(params), "send").then(unwrapWrite);
|
|
1204
|
+
return this.call(() => this.provider.send(params), "send", true).then(unwrapWrite);
|
|
1142
1205
|
}
|
|
1143
1206
|
/** Low-level Propose (always prompts). The caller computes the payload hash, uri and expiry epoch. */
|
|
1144
1207
|
propose(params) {
|
|
1145
|
-
return this.call(() => this.provider.propose(params), "propose").then(unwrapWrite);
|
|
1208
|
+
return this.call(() => this.provider.propose(params), "propose", true).then(unwrapWrite);
|
|
1146
1209
|
}
|
|
1147
1210
|
/** Attest/support a proposal (always prompts). */
|
|
1148
1211
|
attest(params) {
|
|
1149
|
-
return this.call(() => this.provider.attest(params), "attest").then(unwrapWrite);
|
|
1212
|
+
return this.call(() => this.provider.attest(params), "attest", true).then(unwrapWrite);
|
|
1150
1213
|
}
|
|
1151
1214
|
/** Seal a commit-reveal claim (always prompts; salt stays inside the wallet). */
|
|
1152
1215
|
sealClaim(params) {
|
|
1153
|
-
return this.call(() => this.provider.sealClaim(params), "sealClaim").then(unwrapWrite);
|
|
1216
|
+
return this.call(() => this.provider.sealClaim(params), "sealClaim", true).then(unwrapWrite);
|
|
1154
1217
|
}
|
|
1155
1218
|
/** Reveal a previously sealed claim by its sealing txid (always prompts). */
|
|
1156
1219
|
revealClaim(txid3) {
|
|
1157
1220
|
if (typeof txid3 !== "string" || !txid3) {
|
|
1158
1221
|
return Promise.reject(new UnsupportedMethodError("revealClaim requires the sealing txid"));
|
|
1159
1222
|
}
|
|
1160
|
-
return this.call(() => this.provider.revealClaim(txid3), "revealClaim").then(unwrapWrite);
|
|
1223
|
+
return this.call(() => this.provider.revealClaim(txid3), "revealClaim", true).then(unwrapWrite);
|
|
1161
1224
|
}
|
|
1162
1225
|
};
|
|
1163
1226
|
async function getWallet(opts = {}) {
|
|
@@ -1172,6 +1235,7 @@ async function connect(opts = {}) {
|
|
|
1172
1235
|
// src/fillverify.ts
|
|
1173
1236
|
var import_csd_client3 = require("@inversealtruism/csd-client");
|
|
1174
1237
|
var import_csd_codec4 = require("@inversealtruism/csd-codec");
|
|
1238
|
+
var import_csd_crypto3 = require("@inversealtruism/csd-crypto");
|
|
1175
1239
|
var import_cairnx_core2 = require("@inversealtruism/cairnx-core");
|
|
1176
1240
|
var ADDR = /^0x[0-9a-f]{40}$/;
|
|
1177
1241
|
var BELOW_BASE = /^tx below the synced base \((\d+)\)/;
|
|
@@ -1212,13 +1276,116 @@ async function provenAuthor(client, provenTx) {
|
|
|
1212
1276
|
return null;
|
|
1213
1277
|
}
|
|
1214
1278
|
if (String((0, import_csd_codec4.txid)(src)).toLowerCase() !== String(in0.prevTxid).toLowerCase()) return null;
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1279
|
+
return (0, import_csd_crypto3.authorOf)(provenTx, src.outputs?.[Number(in0.vout)]?.scriptPubkey);
|
|
1280
|
+
}
|
|
1281
|
+
var has = (v) => v !== void 0 && v !== null;
|
|
1282
|
+
function fillInputs(o) {
|
|
1283
|
+
const base = { pay: o.pay, plannedOutputs: o.plannedOutputs };
|
|
1284
|
+
if (!has(o.me) && !has(o.tip) && !has(o.fillTargetId)) return base;
|
|
1285
|
+
const me = typeof o.me === "string" ? o.me.toLowerCase() : "";
|
|
1286
|
+
const target = has(o.fillTargetId) ? String(o.fillTargetId).toLowerCase() : void 0;
|
|
1287
|
+
if (!ADDR.test(me) || !Number.isSafeInteger(o.tip) || o.tip < 0 || target !== void 0 && !HASH.test(target) || !has(o.pay))
|
|
1288
|
+
return "invalid fill inputs: pass me (your 0x address), tip (the chain tip height) and pay together; fillTargetId, when given, is a 0x txid";
|
|
1289
|
+
return { ...base, me, tip: o.tip, fillTargetId: target };
|
|
1290
|
+
}
|
|
1291
|
+
function judgeFill(rec, proven, served, f) {
|
|
1292
|
+
const str = (v) => typeof v === "string" ? v : void 0;
|
|
1293
|
+
const status = served?.status;
|
|
1294
|
+
const terms = proven.terms;
|
|
1295
|
+
const state = {
|
|
1296
|
+
id: proven.id,
|
|
1297
|
+
seller: proven.seller,
|
|
1298
|
+
give: rec.give,
|
|
1299
|
+
want: { ...rec.want, payto: proven.payto },
|
|
1300
|
+
taker: rec.taker,
|
|
1301
|
+
bid: rec.bid,
|
|
1302
|
+
min: rec.min,
|
|
1303
|
+
status,
|
|
1304
|
+
expiresEpoch: proven.expiresEpoch,
|
|
1305
|
+
height: terms.height,
|
|
1306
|
+
feeBps: terms.feeBps,
|
|
1307
|
+
paid: str(served?.paid),
|
|
1308
|
+
delivered: str(served?.delivered),
|
|
1309
|
+
claimedBy: str(served?.claimedBy),
|
|
1310
|
+
claimUntilHeight: typeof served?.claimUntilHeight === "number" ? served.claimUntilHeight : void 0,
|
|
1311
|
+
claimTxid: str(served?.claimTxid)
|
|
1312
|
+
};
|
|
1313
|
+
const j = { planBound: false };
|
|
1314
|
+
const err = (e) => e?.message ?? String(e);
|
|
1315
|
+
const token = (0, import_cairnx_core2.isTokenWant)(rec.want);
|
|
1316
|
+
const progress = state.paid !== void 0 && state.delivered !== void 0;
|
|
1317
|
+
if (typeof status === "string" && status !== "open")
|
|
1318
|
+
j.refusal = `the resolver serves this offer as ${status}, not open - refusing (CairnX does not apply a fill of it)`;
|
|
1319
|
+
if (token && has(f.plannedOutputs)) {
|
|
1320
|
+
const legs = bindPlannedSums(f.plannedOutputs, []);
|
|
1321
|
+
if (legs) j.refusal ??= `the offer is token-priced, so a fill moves no CSD: ${legs}`;
|
|
1322
|
+
}
|
|
1323
|
+
if (!has(f.pay)) j.planReason = "no `pay` was given, so no output plan was sized";
|
|
1324
|
+
else if (j.refusal) j.planReason = "the fill is refused, so no output plan was sized";
|
|
1325
|
+
else if (token) j.planReason = "the offer is token-priced: the payment is a token transfer, not CSD outputs";
|
|
1326
|
+
else if (rec.min !== void 0) {
|
|
1327
|
+
j.planReason = "the offer is partially fillable: its running paid/delivered is not provable here (the wallet's fill-SPV sizes it)";
|
|
1328
|
+
if (typeof status === "string" && progress) {
|
|
1329
|
+
try {
|
|
1330
|
+
const p = (0, import_cairnx_core2.previewFill)(state, f.pay);
|
|
1331
|
+
if (!p.deliverable) j.refusal = `this pay would not deliver against the offer's served progress (${p.reason}) - refusing (the CSD would be lost)`;
|
|
1332
|
+
} catch (e) {
|
|
1333
|
+
j.refusal = `couldn't size the fill against the offer (${err(e)}) - refusing`;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
} else if (typeof status !== "string") j.planReason = "no served offer status: liveness is not provable here, so no output plan was sized (pass the resolver's offer object)";
|
|
1337
|
+
else {
|
|
1338
|
+
try {
|
|
1339
|
+
const plan = (0, import_cairnx_core2.fillOutputPlan)(state, f.pay);
|
|
1340
|
+
if (plan.kind === "csd-outputs") {
|
|
1341
|
+
j.outputPlan = plan.outputs;
|
|
1342
|
+
if (has(f.plannedOutputs)) {
|
|
1343
|
+
j.refusal = bindPlannedSums(f.plannedOutputs, plan.outputs);
|
|
1344
|
+
j.planBound = true;
|
|
1345
|
+
}
|
|
1346
|
+
} else if (plan.kind === "undeliverable") {
|
|
1347
|
+
j.refusal = `this pay would not deliver against the proven offer (${plan.reason}) - refusing (the CSD would be lost)`;
|
|
1348
|
+
} else {
|
|
1349
|
+
j.planReason = `the proven offer settles without CSD outputs (${plan.kind})`;
|
|
1350
|
+
}
|
|
1351
|
+
} catch (e) {
|
|
1352
|
+
j.refusal = `couldn't size the fill against the proven offer (${err(e)}) - refusing rather than sizing from served fields`;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
if (f.me !== void 0 && f.tip !== void 0 && has(f.pay)) {
|
|
1356
|
+
const unplan = () => {
|
|
1357
|
+
delete j.outputPlan;
|
|
1358
|
+
j.planBound = false;
|
|
1359
|
+
j.planReason = "the fill is refused, so no output plan was sized";
|
|
1360
|
+
};
|
|
1361
|
+
if ((0, import_cairnx_core2.offerFillTooLate)(proven.expiresAt, f.tip)) {
|
|
1362
|
+
j.refusal ??= `the offer's last live block ${proven.expiresAt - 1} leaves less than the ${import_cairnx_core2.FILL_TIP_MARGIN}-block cushion above tip ${f.tip}, so the fill is too close to the offer's expiry - do not sign it`;
|
|
1363
|
+
unplan();
|
|
1364
|
+
}
|
|
1365
|
+
if (typeof status !== "string") j.refusal ??= "the fill endorsement needs the resolver's offer object (its status, claim and progress) - pass servedOffer";
|
|
1366
|
+
else if (rec.min !== void 0 && !progress) j.refusal ??= "the served offer is missing paid or delivered (a partial offer's progress) - pass the resolver's full offer object";
|
|
1367
|
+
else {
|
|
1368
|
+
try {
|
|
1369
|
+
const e = (0, import_cairnx_core2.fillEndorsement)(state, f.me, f.pay, f.tip, f.fillTargetId !== void 0 ? { fillTargetId: f.fillTargetId } : void 0);
|
|
1370
|
+
j.endorsement = { verdict: e.verdict, reason: e.reason };
|
|
1371
|
+
j.fillTarget = (0, import_cairnx_core2.fillTargetId)(state, f.tip);
|
|
1372
|
+
if (e.verdict === "refused") {
|
|
1373
|
+
j.refusal ??= `CairnX would refuse this fill (${e.reason}) - do not sign it`;
|
|
1374
|
+
unplan();
|
|
1375
|
+
}
|
|
1376
|
+
} catch (e) {
|
|
1377
|
+
j.refusal ??= `couldn't judge the fill against the offer (${err(e)}) - refusing`;
|
|
1378
|
+
unplan();
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
return j;
|
|
1218
1383
|
}
|
|
1219
1384
|
async function preverifyOffer(opts) {
|
|
1220
1385
|
const id = String(opts.offerId).toLowerCase();
|
|
1221
1386
|
if (!HASH.test(id)) return { ok: false, trust: "unverified", reason: "invalid offer id" };
|
|
1387
|
+
const fill = fillInputs(opts);
|
|
1388
|
+
if (typeof fill === "string") return { ok: false, trust: "unverified", reason: fill };
|
|
1222
1389
|
let incl;
|
|
1223
1390
|
try {
|
|
1224
1391
|
incl = await opts.light.verifyTxInclusion(id);
|
|
@@ -1242,8 +1409,6 @@ async function preverifyOffer(opts) {
|
|
|
1242
1409
|
if (String(provenId).toLowerCase() !== id) return { ok: false, trust: "unverified", reason: "the merkle-proven tx doesn't match the offer id" };
|
|
1243
1410
|
const app = incl.tx.app;
|
|
1244
1411
|
if (!app || app.type !== "Propose") return { ok: false, trust: "unverified", reason: "the offer tx is not a Propose" };
|
|
1245
|
-
if (String(app.domain) !== import_cairnx_core2.DOMAIN)
|
|
1246
|
-
return { ok: false, trust: "unverified", reason: `the offer tx is not a ${import_cairnx_core2.DOMAIN} Propose (wrong settlement domain)` };
|
|
1247
1412
|
let rec;
|
|
1248
1413
|
try {
|
|
1249
1414
|
rec = (0, import_cairnx_core2.parseRecord)(app.uri, incl.appPayloadHash);
|
|
@@ -1256,60 +1421,23 @@ async function preverifyOffer(opts) {
|
|
|
1256
1421
|
const seller = await provenAuthor(opts.client, provenTx);
|
|
1257
1422
|
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" };
|
|
1258
1423
|
const offerRec = rec;
|
|
1424
|
+
const ee = app.expires_epoch;
|
|
1425
|
+
const refusal = (0, import_cairnx_core2.provenOfferRefusal)(offerRec, { domain: String(app.domain), height: blockHeight, expiresEpoch: typeof ee === "bigint" ? ee : Number(ee), seller });
|
|
1426
|
+
if (refusal) return { ok: false, trust: "verified", blockHeight, reason: `CairnX rejects this offer (${refusal}) - a fill would move CSD for nothing` };
|
|
1259
1427
|
const w = rec.want ?? {};
|
|
1260
1428
|
const payto = w.payto && ADDR.test(String(w.payto).toLowerCase()) ? String(w.payto).toLowerCase() : seller;
|
|
1261
1429
|
const terms = (0, import_cairnx_core2.provenOfferTerms)(offerRec, blockHeight);
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
const
|
|
1265
|
-
if (!Number.isSafeInteger(provenExpiresEpoch) || (0, import_cairnx_core2.epochOf)(blockHeight) > provenExpiresEpoch)
|
|
1266
|
-
return { ok: false, trust: "verified", blockHeight, reason: "the offer was already expired when it was mined - the chain rejects it, and a fill would move CSD for nothing" };
|
|
1267
|
-
let outputPlan;
|
|
1268
|
-
let sumsMismatch;
|
|
1269
|
-
let planReason;
|
|
1270
|
-
let planBound = false;
|
|
1271
|
-
const has = (v) => v !== void 0 && v !== null;
|
|
1272
|
-
const servedStatus = opts.servedOffer?.status;
|
|
1273
|
-
if (!has(opts.pay)) planReason = "no `pay` was given, so no output plan was sized";
|
|
1274
|
-
else if ((0, import_cairnx_core2.isTokenWant)(offerRec.want)) planReason = "the offer is token-priced: the payment is a token transfer, not CSD outputs";
|
|
1275
|
-
else if (offerRec.min !== void 0) planReason = "the offer is partially fillable: its running paid/delivered is not provable here (the wallet's fill-SPV sizes it)";
|
|
1276
|
-
else if (typeof servedStatus !== "string") planReason = "no served offer status: liveness is not provable here, so no output plan was sized (pass the resolver's offer object)";
|
|
1277
|
-
else {
|
|
1278
|
-
const provenState = {
|
|
1279
|
-
id,
|
|
1280
|
-
seller,
|
|
1281
|
-
give: offerRec.give,
|
|
1282
|
-
want: { ...offerRec.want, payto },
|
|
1283
|
-
taker: offerRec.taker,
|
|
1284
|
-
bid: offerRec.bid,
|
|
1285
|
-
status: servedStatus,
|
|
1286
|
-
expiresEpoch: 0,
|
|
1287
|
-
height: blockHeight,
|
|
1288
|
-
feeBps: terms.feeBps
|
|
1289
|
-
};
|
|
1290
|
-
try {
|
|
1291
|
-
const plan = (0, import_cairnx_core2.fillOutputPlan)(provenState, opts.pay);
|
|
1292
|
-
if (plan.kind === "csd-outputs") {
|
|
1293
|
-
outputPlan = plan.outputs;
|
|
1294
|
-
if (has(opts.plannedOutputs)) {
|
|
1295
|
-
sumsMismatch = bindPlannedSums(opts.plannedOutputs, plan.outputs);
|
|
1296
|
-
planBound = true;
|
|
1297
|
-
}
|
|
1298
|
-
} else if (plan.kind === "undeliverable") {
|
|
1299
|
-
sumsMismatch = `this pay would not deliver against the proven offer (${plan.reason}) - refusing (the CSD would be lost)`;
|
|
1300
|
-
} else {
|
|
1301
|
-
planReason = `the proven offer settles without CSD outputs (${plan.kind})`;
|
|
1302
|
-
}
|
|
1303
|
-
} catch (e) {
|
|
1304
|
-
sumsMismatch = `couldn't size the fill against the proven offer (${e?.message ?? e}) - refusing rather than sizing from served fields`;
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1430
|
+
const expiresEpoch = Number(ee);
|
|
1431
|
+
const expiresAt = (0, import_cairnx_core2.offerExpiryHeightOf)(expiresEpoch, blockHeight);
|
|
1432
|
+
const j = judgeFill(offerRec, { id, seller, payto, terms, expiresEpoch, expiresAt }, opts.servedOffer, fill);
|
|
1307
1433
|
const planFields = {
|
|
1308
|
-
...outputPlan ? { outputPlan } : {},
|
|
1309
|
-
...has(opts.
|
|
1310
|
-
|
|
1434
|
+
...j.outputPlan ? { outputPlan: j.outputPlan } : {},
|
|
1435
|
+
...has(opts.pay) ? { sized: j.outputPlan !== void 0 } : {},
|
|
1436
|
+
...has(opts.plannedOutputs) ? { planBound: j.planBound } : {},
|
|
1437
|
+
...!j.outputPlan && j.planReason && (has(opts.pay) || has(opts.plannedOutputs)) ? { planReason: j.planReason } : {},
|
|
1438
|
+
...j.endorsement ? { endorsement: j.endorsement, fillTarget: j.fillTarget } : {}
|
|
1311
1439
|
};
|
|
1312
|
-
const base = { payto, seller, terms, blockHeight, ...planFields };
|
|
1440
|
+
const base = { payto, seller, terms, blockHeight, expiresEpoch, expiresAt, ...planFields };
|
|
1313
1441
|
if (opts.servedOffer !== void 0 && opts.servedOffer !== null) {
|
|
1314
1442
|
const so = opts.servedOffer;
|
|
1315
1443
|
if (so?.want?.payto === void 0 || so?.want?.payto === null)
|
|
@@ -1320,12 +1448,10 @@ async function preverifyOffer(opts) {
|
|
|
1320
1448
|
return { ok: false, trust: "verified", reason: "the served offer is missing seller (the rebate recipient) - pass the resolver's full offer object, or omit servedOffer", ...base };
|
|
1321
1449
|
if (String(so.seller).toLowerCase() !== seller)
|
|
1322
1450
|
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 };
|
|
1323
|
-
if ((0, import_cairnx_core2.isTokenWant)(offerRec.want) && (String(so?.want?.ticker) !== String(offerRec.want.ticker) || String(so?.want?.amount) !== String(offerRec.want.amount)))
|
|
1324
|
-
return { ok: false, trust: "verified", reason: "the served want ticker/amount don't match the offer's on-chain record (a lying resolver could bait-and-switch the token price)", ...base };
|
|
1325
1451
|
if ((0, import_cairnx_core2.bindOfferTerms)(opts.servedOffer, terms, { give: true, wantType: true }))
|
|
1326
|
-
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 };
|
|
1452
|
+
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's type or token price - a lying resolver could mis-size, re-price or re-route the fill, redirecting or burning your payment)", ...base };
|
|
1327
1453
|
}
|
|
1328
|
-
if (
|
|
1454
|
+
if (j.refusal) return { ok: false, trust: "verified", reason: j.refusal, ...base };
|
|
1329
1455
|
return { ok: true, trust: "verified", ...base };
|
|
1330
1456
|
}
|
|
1331
1457
|
|
|
@@ -1450,6 +1576,20 @@ var spvBackoffMs = (attempt) => Math.min(4e3, 700 * (attempt + 1));
|
|
|
1450
1576
|
var SPV_RETRY_WAIT_BUDGET_MS = 16e3;
|
|
1451
1577
|
var SPV_RECHECK_DEPTH = 12;
|
|
1452
1578
|
var spvTipOf = (lc) => lc.baseHeight + lc.chain.length - 1;
|
|
1579
|
+
var SPV_BEYOND_TIP = /^tx at \d+ is \d+ blocks beyond tip; sync\(\d+\) first$/;
|
|
1580
|
+
var SpvLightClient = class extends import_csd_light.LightClient {
|
|
1581
|
+
syncInFlight = null;
|
|
1582
|
+
async sync(to, from) {
|
|
1583
|
+
while (this.syncInFlight) await this.syncInFlight;
|
|
1584
|
+
const run = super.sync(to, from);
|
|
1585
|
+
this.syncInFlight = run;
|
|
1586
|
+
try {
|
|
1587
|
+
return await run;
|
|
1588
|
+
} finally {
|
|
1589
|
+
if (this.syncInFlight === run) this.syncInFlight = null;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1453
1593
|
var Cairn = class {
|
|
1454
1594
|
config;
|
|
1455
1595
|
chain;
|
|
@@ -1465,6 +1605,9 @@ var Cairn = class {
|
|
|
1465
1605
|
// syncFromCheckpoint calls onto the same client (the loser would hit "seedTrusted must be called on a
|
|
1466
1606
|
// fresh client"). Cleared when the seed settles; a failure leaves no cached state, so the next call retries.
|
|
1467
1607
|
_spvSeedInflight = null;
|
|
1608
|
+
// The in-flight reseed of withSpvReseed, shared by callers whose structural failures overlap. Cleared when it
|
|
1609
|
+
// settles; each caller still commits the fresh client only after its own retry succeeds.
|
|
1610
|
+
_spvReseedInflight = null;
|
|
1468
1611
|
_spvCp;
|
|
1469
1612
|
// W12: the cairn base the SPV header-batch provider fetches /api/headers from (same base the rest of the SDK uses).
|
|
1470
1613
|
_cairnBase;
|
|
@@ -1520,14 +1663,18 @@ var Cairn = class {
|
|
|
1520
1663
|
// (rate limit / gateway / timeout / non-dense / CF 200-HTML) keeps the cache and rethrows: wiping
|
|
1521
1664
|
// on those turns one /api/headers 429 into a reseed storm (DOS-HDR-3). A second structural
|
|
1522
1665
|
// failure propagates fail-closed; there is no loop. recheckCachedHeader feeds this path: a near-tip
|
|
1523
|
-
// cached header that the served chain no longer carries is a structural failure.
|
|
1666
|
+
// cached header that the served chain no longer carries is a structural failure. Callers whose failures overlap
|
|
1667
|
+
// (waiters on one shared sync) share one reseed.
|
|
1524
1668
|
async withSpvReseed(op) {
|
|
1525
1669
|
const lc = await this.seededSpvLight();
|
|
1526
1670
|
try {
|
|
1527
1671
|
return await op(lc);
|
|
1528
1672
|
} catch (e) {
|
|
1529
1673
|
if (spvIsTransient(e)) throw e;
|
|
1530
|
-
|
|
1674
|
+
this._spvReseedInflight ??= this.seedFreshSpvLight().finally(() => {
|
|
1675
|
+
this._spvReseedInflight = null;
|
|
1676
|
+
});
|
|
1677
|
+
const fresh = await this._spvReseedInflight;
|
|
1531
1678
|
const out = await op(fresh);
|
|
1532
1679
|
this._spvLight = fresh;
|
|
1533
1680
|
return out;
|
|
@@ -1547,7 +1694,7 @@ var Cairn = class {
|
|
|
1547
1694
|
// the caller commits the result only on success (MF-14; the wallet G8 posture - never null or
|
|
1548
1695
|
// dirty the shared field while an attempt is in flight).
|
|
1549
1696
|
async seedFreshSpvLight() {
|
|
1550
|
-
const lc = new
|
|
1697
|
+
const lc = new SpvLightClient({
|
|
1551
1698
|
client: this.chain.client,
|
|
1552
1699
|
checkpoints: { [this._spvCp.height]: this._spvCp.hash },
|
|
1553
1700
|
headersBatchProvider: (from, count) => this.fetchHeadersBatch(from, count)
|
|
@@ -1589,11 +1736,7 @@ var Cairn = class {
|
|
|
1589
1736
|
* data-availability hiccup; back off (honouring Retry-After) a bounded number of times, then THROW so
|
|
1590
1737
|
* `sync()` fails closed. A non-dense range is rejected terminally (never retried).
|
|
1591
1738
|
*
|
|
1592
|
-
*
|
|
1593
|
-
* mounted on `/explorer/api`, `/trade/api` and `/api/rpc`, not here). A SAME-ORIGIN dApp (hosted on the
|
|
1594
|
-
* cairn base) reaches it now; a CROSS-ORIGIN third-party dApp cannot until B7d adds the CORS mount and
|
|
1595
|
-
* restarts the cairn service. This code is correct as written; it becomes reachable cross-origin only
|
|
1596
|
-
* once B7d ships. This is a recorded dependency, not a blocker for authoring/testing.
|
|
1739
|
+
* Cross-origin reach depends on the server's CORS mount on `/api/headers` (the cairn server mounts one).
|
|
1597
1740
|
*/
|
|
1598
1741
|
async fetchHeadersBatch(from, count) {
|
|
1599
1742
|
const g = this.config.fetch ?? globalThis.fetch;
|
|
@@ -1641,7 +1784,8 @@ var Cairn = class {
|
|
|
1641
1784
|
* commitment, derives the payment recipient + seller from the offer's on-chain AUTHOR (the funding input's
|
|
1642
1785
|
* prevout owner, txid-committed, NOT the malleable scriptSig), and, if you pass the resolver-SERVED `offer`
|
|
1643
1786
|
* object, binds its payto/seller + fee/rebate/partial terms to the proven ones. Returns a TRUST-LABELED result
|
|
1644
|
-
* (fail-closed on a positive mismatch
|
|
1787
|
+
* (fail-closed on a positive mismatch; `transient` on a lagging chain view; final, naming the anchor, for an
|
|
1788
|
+
* offer mined below the SPV checkpoint).
|
|
1645
1789
|
*
|
|
1646
1790
|
* This is best-effort corroboration, NOT the payment-grade boundary: the Cairn Wallet's OWN on-device
|
|
1647
1791
|
* fill-SPV (0.2.60+, fails-closed before signing) is that. A dApp settling real value should still clear this
|
|
@@ -1649,19 +1793,44 @@ var Cairn = class {
|
|
|
1649
1793
|
*
|
|
1650
1794
|
* Pass opts.pay (the CSD base units you intend to pay) to receive the PROVEN outputPlan for a whole fill of a
|
|
1651
1795
|
* non-partial offer, and opts.plannedOutputs to bind your planned per-address sums to it before signing; build
|
|
1652
|
-
* EXACTLY the outputPlan, never resolver-served outputs.
|
|
1796
|
+
* EXACTLY the outputPlan, never resolver-served outputs. Pass opts.me (your address) and opts.tip (the chain tip
|
|
1797
|
+
* height) with pay to have cairnx-core's fillEndorsement and offerFillTooLate judge the fill for you at that tip; without them `ok`
|
|
1798
|
+
* says the offer record is proven, not that you can fill it now.
|
|
1653
1799
|
*/
|
|
1654
1800
|
async verifyOfferForFill(offerId, servedOffer, opts) {
|
|
1655
1801
|
const light = {
|
|
1656
1802
|
verifyTxInclusion: (txidHex) => this.withSpvReseed(async (lc) => {
|
|
1657
1803
|
const haveTo = spvTipOf(lc);
|
|
1658
|
-
|
|
1659
|
-
if (r.trustLevel === "
|
|
1660
|
-
|
|
1661
|
-
|
|
1804
|
+
let r = await lc.verifyTxInclusion(txidHex);
|
|
1805
|
+
if (r.trustLevel === "rpc-trusted" && SPV_BEYOND_TIP.test(r.reason ?? "")) {
|
|
1806
|
+
let tipHeight;
|
|
1807
|
+
try {
|
|
1808
|
+
tipHeight = Number((await this.chain.tip()).height);
|
|
1809
|
+
} catch (e) {
|
|
1810
|
+
return { ...r, reason: `${r.reason} (the node tip read failed: ${e?.message ?? e})` };
|
|
1811
|
+
}
|
|
1812
|
+
if (Number.isSafeInteger(tipHeight) && tipHeight > spvTipOf(lc)) await lc.sync(tipHeight);
|
|
1813
|
+
r = await lc.verifyTxInclusion(txidHex);
|
|
1814
|
+
}
|
|
1815
|
+
if (r.trustLevel !== "verified-inclusion") return r;
|
|
1816
|
+
const height = Number(r.blockHeight);
|
|
1817
|
+
if (!Number.isSafeInteger(height))
|
|
1818
|
+
return { trustLevel: "rpc-trusted", included: false, reason: `the node's containing height for this tx is not a block height (${String(r.blockHeight)})` };
|
|
1819
|
+
if (height <= haveTo) await this.recheckCachedHeader(lc, height, haveTo);
|
|
1820
|
+
return { ...r, blockHeight: height };
|
|
1662
1821
|
})
|
|
1663
1822
|
};
|
|
1664
|
-
return preverifyOffer({
|
|
1823
|
+
return preverifyOffer({
|
|
1824
|
+
light,
|
|
1825
|
+
client: this.chain.client,
|
|
1826
|
+
offerId,
|
|
1827
|
+
servedOffer,
|
|
1828
|
+
pay: opts?.pay,
|
|
1829
|
+
plannedOutputs: opts?.plannedOutputs,
|
|
1830
|
+
me: opts?.me,
|
|
1831
|
+
tip: opts?.tip,
|
|
1832
|
+
fillTargetId: opts?.fillTargetId
|
|
1833
|
+
});
|
|
1665
1834
|
}
|
|
1666
1835
|
/**
|
|
1667
1836
|
* Detect + connect the Cairn Wallet (browser only) and attach it as `cairn.wallet`.
|