@inversealtruism/cairn-sdk 0.3.2 → 0.4.1
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-7UOTIAYK.js +224 -0
- package/dist/{chunk-CZWDA3A5.js → chunk-DUTVUIQF.js} +1 -1
- package/dist/{chunk-FMXNJQWL.js → chunk-ERBT4DFD.js} +2 -2
- package/dist/{chunk-JKC5K253.js → chunk-OCEHXK6A.js} +1 -1
- package/dist/{chunk-BN3PFJDY.js → chunk-PIPN6FSA.js} +53 -16
- package/dist/{chunk-AGU6K5EZ.js → chunk-QATE6AIP.js} +25 -13
- package/dist/connect.cjs +53 -17
- package/dist/connect.d.cts +11 -2
- package/dist/connect.d.ts +11 -2
- package/dist/connect.js +2 -2
- package/dist/content.cjs +1 -1
- package/dist/content.js +2 -2
- package/dist/controller.cjs +77 -29
- package/dist/controller.d.cts +4 -1
- package/dist/controller.d.ts +4 -1
- package/dist/controller.js +3 -3
- package/dist/index.cjs +317 -71
- package/dist/index.d.cts +65 -18
- package/dist/index.d.ts +65 -18
- package/dist/index.js +235 -181
- package/dist/react.cjs +77 -29
- package/dist/react.js +3 -3
- package/dist/registry.cjs +219 -7
- package/dist/registry.d.cts +16 -11
- package/dist/registry.d.ts +16 -11
- package/dist/registry.js +2 -1
- package/package.json +21 -28
- package/dist/chunk-BGJZF7S2.js +0 -69
package/dist/index.cjs
CHANGED
|
@@ -46,16 +46,21 @@ __export(index_exports, {
|
|
|
46
46
|
discoverProviders: () => discoverProviders,
|
|
47
47
|
errorCode: () => errorCode,
|
|
48
48
|
feeBpsAt: () => import_cairnx_core2.feeBpsAt,
|
|
49
|
+
fillEndorsement: () => import_cairnx_core2.fillEndorsement,
|
|
50
|
+
fillIsSafe: () => import_cairnx_core2.fillIsSafe,
|
|
51
|
+
fillOutputPlan: () => import_cairnx_core2.fillOutputPlan,
|
|
49
52
|
getWallet: () => getWallet,
|
|
50
53
|
isInstalled: () => isInstalled,
|
|
51
54
|
mapProviderError: () => mapProviderError,
|
|
52
55
|
mapSubmitResultError: () => mapSubmitResultError,
|
|
53
|
-
preverifyOffer: () => preverifyOffer
|
|
56
|
+
preverifyOffer: () => preverifyOffer,
|
|
57
|
+
previewFill: () => import_cairnx_core2.previewFill,
|
|
58
|
+
requiredFillOutputs: () => import_cairnx_core2.requiredFillOutputs
|
|
54
59
|
});
|
|
55
60
|
module.exports = __toCommonJS(index_exports);
|
|
56
61
|
|
|
57
62
|
// src/errors.ts
|
|
58
|
-
var SDK_VERSION = "0.
|
|
63
|
+
var SDK_VERSION = "0.4.1";
|
|
59
64
|
var DOCS_BASE = "https://cairn-substrate.com/docs/sdk";
|
|
60
65
|
var CairnError = class extends Error {
|
|
61
66
|
/** Stable machine code for programmatic branching. */
|
|
@@ -147,7 +152,7 @@ var SubmitInFlightError = class extends CairnError {
|
|
|
147
152
|
this.txid = txid3;
|
|
148
153
|
}
|
|
149
154
|
};
|
|
150
|
-
var NATIVE_CODES = /* @__PURE__ */ new Set(["
|
|
155
|
+
var NATIVE_CODES = /* @__PURE__ */ new Set(["FIRST_PARTY_ONLY", "RATE_LIMITED", "FORBIDDEN", "INTERNAL"]);
|
|
151
156
|
var NESTED_TRANSIENT = /* @__PURE__ */ new Set(["VERIFY_UNAVAILABLE", "OFFER_UNKNOWN"]);
|
|
152
157
|
var NESTED_TERMINAL = /* @__PURE__ */ new Set([
|
|
153
158
|
"FILL_UNSAFE",
|
|
@@ -889,17 +894,20 @@ function normalizeHash(hash) {
|
|
|
889
894
|
var import_csd_registry = require("@inversealtruism/csd-registry");
|
|
890
895
|
var import_csd_registry2 = require("@inversealtruism/csd-registry");
|
|
891
896
|
var RegistryClient = class {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
897
|
+
http;
|
|
898
|
+
/** Accepts the SDK's hardened Http directly (the Cairn facade path) or the legacy
|
|
899
|
+
* { baseUrl, fetch } options (kept for direct constructors; they now get the same
|
|
900
|
+
* hardened transport, built internally). */
|
|
901
|
+
constructor(src) {
|
|
902
|
+
this.http = src instanceof Http ? src : new Http({ baseUrl: src.baseUrl, fetch: src.fetch });
|
|
895
903
|
}
|
|
896
904
|
/** Discover ranked content/pin gateways (GET /registry/gateways). */
|
|
897
905
|
gateways() {
|
|
898
|
-
return
|
|
906
|
+
return this.http.getJson("/registry/gateways");
|
|
899
907
|
}
|
|
900
908
|
/** Discover ranked libp2p peers (GET /registry/peers). */
|
|
901
909
|
peers() {
|
|
902
|
-
return
|
|
910
|
+
return this.http.getJson("/registry/peers");
|
|
903
911
|
}
|
|
904
912
|
/**
|
|
905
913
|
* Resolve a handle → identity (GET /identity/:handle). Returns null if unresolved.
|
|
@@ -911,12 +919,22 @@ var RegistryClient = class {
|
|
|
911
919
|
* source (the wallet's namespv union cure). Treat `verified` here as a hint, not a guarantee.
|
|
912
920
|
*/
|
|
913
921
|
resolveName(handle) {
|
|
914
|
-
return
|
|
922
|
+
return this.getOr404Null(`/identity/${encodeURIComponent(handle)}`);
|
|
915
923
|
}
|
|
916
924
|
/** Reverse-resolve an address → identity (GET /address/:addr/identity).
|
|
917
925
|
* ⚠ Same indexer-trust caveat as resolveName — `verified` is the indexer's claim, not a client proof. */
|
|
918
926
|
reverseName(address) {
|
|
919
|
-
return
|
|
927
|
+
return this.getOr404Null(`/address/${encodeURIComponent(address)}/identity`);
|
|
928
|
+
}
|
|
929
|
+
// MF-12: 404 → null (the preserved csd-registry contract for both name resolvers); every other
|
|
930
|
+
// failure rethrows the typed HttpError/CairnError the hardened Http raises.
|
|
931
|
+
async getOr404Null(path) {
|
|
932
|
+
try {
|
|
933
|
+
return await this.http.getJson(path);
|
|
934
|
+
} catch (e) {
|
|
935
|
+
if (e instanceof HttpError && e.status === 404) return null;
|
|
936
|
+
return Promise.reject(e);
|
|
937
|
+
}
|
|
920
938
|
}
|
|
921
939
|
/** The deterministic, client-side resolvers (recompute from raw ChainRecord[]). */
|
|
922
940
|
get fromRecords() {
|
|
@@ -1015,6 +1033,7 @@ var NamesClient = class {
|
|
|
1015
1033
|
};
|
|
1016
1034
|
|
|
1017
1035
|
// src/connect.ts
|
|
1036
|
+
var DEFAULT_PROVIDER_TIMEOUT_MS = 10 * 6e4;
|
|
1018
1037
|
function discoverProviders(opts = {}) {
|
|
1019
1038
|
const timeoutMs = opts.timeoutMs ?? 1e3;
|
|
1020
1039
|
return new Promise((resolve) => {
|
|
@@ -1094,8 +1113,27 @@ function unwrapWrite(reply) {
|
|
|
1094
1113
|
var WalletConnection = class {
|
|
1095
1114
|
provider;
|
|
1096
1115
|
_addr = null;
|
|
1097
|
-
|
|
1116
|
+
/** B7c: per-call provider timeout (ms). GENEROUS by default so it never rejects an in-flight human
|
|
1117
|
+
* approval; 0 disables it. Every provider call (writes especially) races against it. */
|
|
1118
|
+
_timeoutMs;
|
|
1119
|
+
constructor(provider, opts = {}) {
|
|
1098
1120
|
this.provider = provider;
|
|
1121
|
+
this._timeoutMs = opts.timeoutMs ?? DEFAULT_PROVIDER_TIMEOUT_MS;
|
|
1122
|
+
}
|
|
1123
|
+
// B7c: race a provider call against the per-call timeout. On timeout, throw a fail-closed CairnError
|
|
1124
|
+
// (retryable:false: a write may already have reached the wallet, so never invite a blind auto-retry). The
|
|
1125
|
+
// genuine wallet resolves quickly once the user acts; this only fires on a truly hung/absent response.
|
|
1126
|
+
call(op, label) {
|
|
1127
|
+
const ms = this._timeoutMs;
|
|
1128
|
+
const invoke = () => new Promise((resolve) => {
|
|
1129
|
+
resolve(op());
|
|
1130
|
+
});
|
|
1131
|
+
if (!Number.isFinite(ms) || ms <= 0) return invoke();
|
|
1132
|
+
let timer;
|
|
1133
|
+
const timeout = new Promise((_, reject) => {
|
|
1134
|
+
timer = setTimeout(() => reject(new CairnError(`the wallet did not respond to ${label} within ${ms}ms`, { retryable: false })), ms);
|
|
1135
|
+
});
|
|
1136
|
+
return Promise.race([invoke(), timeout]).finally(() => clearTimeout(timer));
|
|
1099
1137
|
}
|
|
1100
1138
|
/** The wallet's version string (e.g. "0.2.x"). */
|
|
1101
1139
|
get version() {
|
|
@@ -1107,13 +1145,13 @@ var WalletConnection = class {
|
|
|
1107
1145
|
}
|
|
1108
1146
|
/** Request connection. First time per origin prompts the user; afterwards may be silent. */
|
|
1109
1147
|
async connect() {
|
|
1110
|
-
const { addr } = unwrap(await this.provider.connect());
|
|
1148
|
+
const { addr } = unwrap(await this.call(() => this.provider.connect(), "connect"));
|
|
1111
1149
|
this._addr = addr;
|
|
1112
1150
|
return addr;
|
|
1113
1151
|
}
|
|
1114
1152
|
/** Get the connected address (does not force a new connection prompt if already consented). */
|
|
1115
1153
|
async getAddress() {
|
|
1116
|
-
const { addr } = unwrap(await this.provider.getAddress());
|
|
1154
|
+
const { addr } = unwrap(await this.call(() => this.provider.getAddress(), "getAddress"));
|
|
1117
1155
|
this._addr = addr;
|
|
1118
1156
|
return addr;
|
|
1119
1157
|
}
|
|
@@ -1126,7 +1164,7 @@ var WalletConnection = class {
|
|
|
1126
1164
|
if (typeof this.provider.signIn !== "function") {
|
|
1127
1165
|
return Promise.reject(new UnsupportedMethodError("this wallet does not expose signIn()"));
|
|
1128
1166
|
}
|
|
1129
|
-
return
|
|
1167
|
+
return this.call(() => this.provider.signIn(), "signIn").then(unwrap);
|
|
1130
1168
|
}
|
|
1131
1169
|
/**
|
|
1132
1170
|
* Audience-bound "Sign in with CSD" (SIWC) — the secure, replay-resistant third-party login.
|
|
@@ -1142,7 +1180,7 @@ var WalletConnection = class {
|
|
|
1142
1180
|
if (typeof this.provider.signInWithCsd !== "function") {
|
|
1143
1181
|
return Promise.reject(new UnsupportedMethodError("this wallet predates Sign-in-with-CSD \u2014 ask the user to update the Cairn Wallet"));
|
|
1144
1182
|
}
|
|
1145
|
-
return
|
|
1183
|
+
return this.call(() => this.provider.signInWithCsd(params), "signInWithCsd").then(unwrap);
|
|
1146
1184
|
}
|
|
1147
1185
|
/** True if this wallet supports audience-bound SIWC (safe to call `signInWithCsd`). */
|
|
1148
1186
|
get supportsSiwc() {
|
|
@@ -1152,7 +1190,7 @@ var WalletConnection = class {
|
|
|
1152
1190
|
async getCapabilities() {
|
|
1153
1191
|
if (typeof this.provider.getCapabilities !== "function") return null;
|
|
1154
1192
|
try {
|
|
1155
|
-
return await this.provider.getCapabilities();
|
|
1193
|
+
return await this.call(() => this.provider.getCapabilities(), "getCapabilities");
|
|
1156
1194
|
} catch {
|
|
1157
1195
|
return null;
|
|
1158
1196
|
}
|
|
@@ -1168,24 +1206,40 @@ var WalletConnection = class {
|
|
|
1168
1206
|
if (typeof this.provider.fillOffer !== "function") {
|
|
1169
1207
|
return Promise.reject(new UnsupportedMethodError("this wallet predates fillOffer() \u2014 ask the user to update the Cairn Wallet"));
|
|
1170
1208
|
}
|
|
1171
|
-
|
|
1209
|
+
this.adviseFillSpvCapability();
|
|
1210
|
+
return this.call(() => this.provider.fillOffer(params), "fillOffer").then(unwrapWrite);
|
|
1211
|
+
}
|
|
1212
|
+
// P75-5 MF-10: ONE capability advisory per connection. The wallet's on-device fill-SPV (0.2.60+) is the
|
|
1213
|
+
// payment-grade fill boundary; when the provider SELF-REPORTS an older version, warn the integrator once
|
|
1214
|
+
// and STILL RELAY. WARN, NEVER BLOCK: a hard version gate would decline honest fills (version strings are
|
|
1215
|
+
// spoofable and unparseable forks exist), and the wallet itself is the enforcement point. Reads only the
|
|
1216
|
+
// sync `provider.version`; never an awaited getCapabilities() round-trip on the fill hot path. An
|
|
1217
|
+
// unparseable version never warns (no false alarms).
|
|
1218
|
+
_fillSpvAdvised = false;
|
|
1219
|
+
adviseFillSpvCapability() {
|
|
1220
|
+
if (this._fillSpvAdvised) return;
|
|
1221
|
+
this._fillSpvAdvised = true;
|
|
1222
|
+
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(String(this.provider?.version ?? ""));
|
|
1223
|
+
if (!m) return;
|
|
1224
|
+
const below = Number(m[1]) === 0 && (Number(m[2]) < 2 || Number(m[2]) === 2 && Number(m[3]) < 60);
|
|
1225
|
+
if (below) console.warn(`[cairn-sdk] this Cairn Wallet (v${m[0]}) predates the on-device fill-SPV boundary (cairn-wallet 0.2.60+). The fill proceeds; suggest a wallet update for payment-grade offer verification.`);
|
|
1172
1226
|
}
|
|
1173
1227
|
/** This origin's current permission grant (EIP-2255-style). Silent; [] if none / unsupported. */
|
|
1174
1228
|
async getPermissions() {
|
|
1175
1229
|
if (typeof this.provider.getPermissions !== "function") return [];
|
|
1176
|
-
return unwrap(await this.provider.getPermissions());
|
|
1230
|
+
return unwrap(await this.call(() => this.provider.getPermissions(), "getPermissions"));
|
|
1177
1231
|
}
|
|
1178
1232
|
/** Explicitly request permission to see the address (prompts, like connect). */
|
|
1179
1233
|
requestPermissions() {
|
|
1180
1234
|
if (typeof this.provider.requestPermissions !== "function") {
|
|
1181
1235
|
return Promise.reject(new UnsupportedMethodError("this wallet predates requestPermissions \u2014 use connect()"));
|
|
1182
1236
|
}
|
|
1183
|
-
return
|
|
1237
|
+
return this.call(() => this.provider.requestPermissions(), "requestPermissions").then(unwrap);
|
|
1184
1238
|
}
|
|
1185
1239
|
/** Revoke THIS origin's own access (silent). Returns { revoked }. Safe no-op on older wallets. */
|
|
1186
1240
|
async revokePermissions() {
|
|
1187
1241
|
if (typeof this.provider.revokePermissions !== "function") return { revoked: false };
|
|
1188
|
-
return unwrap(await this.provider.revokePermissions());
|
|
1242
|
+
return unwrap(await this.call(() => this.provider.revokePermissions(), "revokePermissions"));
|
|
1189
1243
|
}
|
|
1190
1244
|
/**
|
|
1191
1245
|
* Subscribe to provider events. `accountsChanged` fires with `[]` when this site loses access
|
|
@@ -1201,26 +1255,26 @@ var WalletConnection = class {
|
|
|
1201
1255
|
}
|
|
1202
1256
|
/** Send CSD (always prompts with clear-signing; wallet picks inputs + returns change to itself). */
|
|
1203
1257
|
send(params) {
|
|
1204
|
-
return
|
|
1258
|
+
return this.call(() => this.provider.send(params), "send").then(unwrapWrite);
|
|
1205
1259
|
}
|
|
1206
1260
|
/** Low-level Propose (always prompts). Prefer `cairn.board.propose()` which computes the hash/uri/expiry. */
|
|
1207
1261
|
propose(params) {
|
|
1208
|
-
return
|
|
1262
|
+
return this.call(() => this.provider.propose(params), "propose").then(unwrapWrite);
|
|
1209
1263
|
}
|
|
1210
1264
|
/** Attest/support a proposal (always prompts). */
|
|
1211
1265
|
attest(params) {
|
|
1212
|
-
return
|
|
1266
|
+
return this.call(() => this.provider.attest(params), "attest").then(unwrapWrite);
|
|
1213
1267
|
}
|
|
1214
1268
|
/** Seal a commit-reveal claim (always prompts; salt stays inside the wallet). */
|
|
1215
1269
|
sealClaim(params) {
|
|
1216
|
-
return
|
|
1270
|
+
return this.call(() => this.provider.sealClaim(params), "sealClaim").then(unwrapWrite);
|
|
1217
1271
|
}
|
|
1218
1272
|
/** Reveal a previously sealed claim by its sealing txid (always prompts). */
|
|
1219
1273
|
revealClaim(txid3) {
|
|
1220
1274
|
if (typeof txid3 !== "string" || !txid3) {
|
|
1221
1275
|
return Promise.reject(new UnsupportedMethodError("revealClaim requires the sealing txid"));
|
|
1222
1276
|
}
|
|
1223
|
-
return
|
|
1277
|
+
return this.call(() => this.provider.revealClaim(txid3), "revealClaim").then(unwrapWrite);
|
|
1224
1278
|
}
|
|
1225
1279
|
};
|
|
1226
1280
|
async function getWallet(opts = {}) {
|
|
@@ -1239,6 +1293,23 @@ var import_cairnx_core2 = require("@inversealtruism/cairnx-core");
|
|
|
1239
1293
|
var ADDR = /^0x[0-9a-f]{40}$/;
|
|
1240
1294
|
var HASH = /^0x[0-9a-f]{64}$/;
|
|
1241
1295
|
var COINBASE = "0x" + "00".repeat(32);
|
|
1296
|
+
function bindPlannedSums(planned, proven) {
|
|
1297
|
+
const provenMap = new Map(proven.map((o) => [o.to.toLowerCase(), o.value]));
|
|
1298
|
+
const plannedMap = /* @__PURE__ */ new Map();
|
|
1299
|
+
try {
|
|
1300
|
+
for (const [a, v] of Object.entries(planned)) {
|
|
1301
|
+
const k = String(a).toLowerCase();
|
|
1302
|
+
plannedMap.set(k, (plannedMap.get(k) ?? 0n) + BigInt(v));
|
|
1303
|
+
}
|
|
1304
|
+
} catch {
|
|
1305
|
+
return "invalid plannedOutputs (amounts must be base-unit integers)";
|
|
1306
|
+
}
|
|
1307
|
+
for (const k of /* @__PURE__ */ new Set([...provenMap.keys(), ...plannedMap.keys()])) {
|
|
1308
|
+
if ((provenMap.get(k) ?? 0n) !== (plannedMap.get(k) ?? 0n))
|
|
1309
|
+
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)`;
|
|
1310
|
+
}
|
|
1311
|
+
return void 0;
|
|
1312
|
+
}
|
|
1242
1313
|
async function provenAuthor(client, provenTx) {
|
|
1243
1314
|
const in0 = provenTx.inputs?.[0];
|
|
1244
1315
|
if (!in0 || String(in0.prevTxid).toLowerCase() === COINBASE && Number(in0.vout) === 4294967295) return null;
|
|
@@ -1283,6 +1354,8 @@ async function preverifyOffer(opts) {
|
|
|
1283
1354
|
if (String(provenId).toLowerCase() !== id) return { ok: false, trust: "unverified", reason: "the merkle-proven tx doesn't match the offer id" };
|
|
1284
1355
|
const app = incl.tx.app;
|
|
1285
1356
|
if (!app || app.type !== "Propose") return { ok: false, trust: "unverified", reason: "the offer tx is not a Propose" };
|
|
1357
|
+
if (String(app.domain) !== import_cairnx_core2.DOMAIN)
|
|
1358
|
+
return { ok: false, trust: "unverified", reason: `the offer tx is not a ${import_cairnx_core2.DOMAIN} Propose (wrong settlement domain)` };
|
|
1286
1359
|
let rec;
|
|
1287
1360
|
try {
|
|
1288
1361
|
rec = (0, import_cairnx_core2.parseRecord)(app.uri, incl.appPayloadHash);
|
|
@@ -1294,27 +1367,56 @@ async function preverifyOffer(opts) {
|
|
|
1294
1367
|
return { ok: false, trust: "unverified", reason: "the offer record doesn't match the on-chain (merkle-proven) commitment" };
|
|
1295
1368
|
const seller = await provenAuthor(opts.client, provenTx);
|
|
1296
1369
|
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" };
|
|
1370
|
+
const offerRec = rec;
|
|
1297
1371
|
const w = rec.want ?? {};
|
|
1298
1372
|
const payto = w.payto && ADDR.test(String(w.payto).toLowerCase()) ? String(w.payto).toLowerCase() : seller;
|
|
1299
|
-
const terms =
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1373
|
+
const terms = (0, import_cairnx_core2.provenOfferTerms)(offerRec, blockHeight);
|
|
1374
|
+
let outputPlan;
|
|
1375
|
+
let sumsMismatch;
|
|
1376
|
+
if (opts.pay !== void 0 && opts.pay !== null && !(0, import_cairnx_core2.isTokenWant)(offerRec.want) && offerRec.min === void 0) {
|
|
1377
|
+
const servedStatus = opts.servedOffer?.status;
|
|
1378
|
+
const provenState = {
|
|
1379
|
+
id,
|
|
1380
|
+
seller,
|
|
1381
|
+
give: offerRec.give,
|
|
1382
|
+
want: { ...offerRec.want, payto },
|
|
1383
|
+
taker: offerRec.taker,
|
|
1384
|
+
bid: offerRec.bid,
|
|
1385
|
+
status: typeof servedStatus === "string" ? servedStatus : "open",
|
|
1386
|
+
expiresEpoch: 0,
|
|
1387
|
+
height: blockHeight,
|
|
1388
|
+
feeBps: terms.feeBps
|
|
1389
|
+
};
|
|
1390
|
+
try {
|
|
1391
|
+
const plan = (0, import_cairnx_core2.fillOutputPlan)(provenState, opts.pay);
|
|
1392
|
+
if (plan.kind === "csd-outputs") {
|
|
1393
|
+
outputPlan = plan.outputs;
|
|
1394
|
+
if (opts.plannedOutputs !== void 0 && opts.plannedOutputs !== null)
|
|
1395
|
+
sumsMismatch = bindPlannedSums(opts.plannedOutputs, plan.outputs);
|
|
1396
|
+
} else if (plan.kind === "undeliverable") {
|
|
1397
|
+
sumsMismatch = `this pay would not deliver against the proven offer (${plan.reason}) - refusing (the CSD would be lost)`;
|
|
1398
|
+
}
|
|
1399
|
+
} catch (e) {
|
|
1400
|
+
sumsMismatch = `couldn't size the fill against the proven offer (${e?.message ?? e}) - refusing rather than sizing from served fields`;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
const base = { payto, seller, terms, blockHeight, ...outputPlan ? { outputPlan } : {} };
|
|
1308
1404
|
if (opts.servedOffer !== void 0 && opts.servedOffer !== null) {
|
|
1309
1405
|
const so = opts.servedOffer;
|
|
1310
|
-
|
|
1311
|
-
|
|
1406
|
+
if (so?.want?.payto === void 0 || so?.want?.payto === null)
|
|
1407
|
+
return { ok: false, trust: "verified", reason: "the served offer is missing want.payto (the payment recipient) - pass the resolver's full offer object, or omit servedOffer", ...base };
|
|
1408
|
+
if (String(so.want.payto).toLowerCase() !== payto)
|
|
1312
1409
|
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
|
|
1410
|
+
if (so?.seller === void 0 || so?.seller === null)
|
|
1411
|
+
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 };
|
|
1412
|
+
if (String(so.seller).toLowerCase() !== seller)
|
|
1314
1413
|
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
|
|
1414
|
+
if ((0, import_cairnx_core2.bindOfferTerms)(opts.servedOffer, terms, { give: true, wantType: true }))
|
|
1415
|
+
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 };
|
|
1416
|
+
if ((0, import_cairnx_core2.isTokenWant)(offerRec.want) && (String(so?.want?.ticker) !== String(offerRec.want.ticker) || String(so?.want?.amount) !== String(offerRec.want.amount)))
|
|
1417
|
+
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 };
|
|
1317
1418
|
}
|
|
1419
|
+
if (sumsMismatch) return { ok: false, trust: "verified", reason: sumsMismatch, ...base };
|
|
1318
1420
|
return { ok: true, trust: "verified", ...base };
|
|
1319
1421
|
}
|
|
1320
1422
|
|
|
@@ -1323,6 +1425,9 @@ var DISCONNECTED = Object.freeze({ status: "disconnected", account: null, error:
|
|
|
1323
1425
|
var CairnController = class {
|
|
1324
1426
|
state = DISCONNECTED;
|
|
1325
1427
|
conn = null;
|
|
1428
|
+
// MF-15: connect/disconnect generation token. disconnect() bumps it; a connect() whose await straddled
|
|
1429
|
+
// the bump must never commit "connected" over the torn-down session (a zombie null-connection session).
|
|
1430
|
+
epoch = 0;
|
|
1326
1431
|
listeners = /* @__PURE__ */ new Set();
|
|
1327
1432
|
getWalletFn;
|
|
1328
1433
|
detectOpts;
|
|
@@ -1375,34 +1480,43 @@ var CairnController = class {
|
|
|
1375
1480
|
}
|
|
1376
1481
|
/** Detect + connect (prompts the user the first time per origin). Resolves the connected address. */
|
|
1377
1482
|
connect = async () => {
|
|
1483
|
+
const epoch = this.epoch;
|
|
1378
1484
|
this.setState({ status: "connecting", error: null });
|
|
1379
1485
|
try {
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
this.
|
|
1383
|
-
this.
|
|
1486
|
+
let conn = this.conn;
|
|
1487
|
+
if (!conn) {
|
|
1488
|
+
conn = await this.getWalletFn(this.detectOpts);
|
|
1489
|
+
if (epoch !== this.epoch) throw new Error("connection was torn down while connecting - call connect() again");
|
|
1490
|
+
conn.on("accountsChanged", this.onAccounts);
|
|
1491
|
+
conn.on("disconnect", this.onDisconnect);
|
|
1492
|
+
this.conn = conn;
|
|
1384
1493
|
}
|
|
1385
|
-
const addr = await
|
|
1494
|
+
const addr = await conn.connect();
|
|
1495
|
+
if (epoch !== this.epoch) throw new Error("connection was torn down while awaiting approval - call connect() again");
|
|
1386
1496
|
this.setState({ status: "connected", account: addr, error: null });
|
|
1387
1497
|
return addr;
|
|
1388
1498
|
} catch (e) {
|
|
1389
|
-
this.setState({ status: "disconnected", account: null, error: e?.message ?? String(e) });
|
|
1499
|
+
if (epoch === this.epoch) this.setState({ status: "disconnected", account: null, error: e?.message ?? String(e) });
|
|
1390
1500
|
throw e;
|
|
1391
1501
|
}
|
|
1392
1502
|
};
|
|
1393
|
-
/** Forget the connection locally AND revoke this origin's wallet-side permission (best-effort).
|
|
1503
|
+
/** Forget the connection locally AND revoke this origin's wallet-side permission (best-effort).
|
|
1504
|
+
* Local teardown is IMMEDIATE and unconditional; the wallet-side revoke happens after, so a hung
|
|
1505
|
+
* provider can never hold a torn-down session in "connected". */
|
|
1394
1506
|
disconnect = async () => {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
} catch {
|
|
1398
|
-
}
|
|
1507
|
+
this.epoch++;
|
|
1508
|
+
const conn = this.conn;
|
|
1399
1509
|
this.detach();
|
|
1400
1510
|
this.conn = null;
|
|
1401
1511
|
this.setState({ status: "disconnected", account: null, error: null });
|
|
1512
|
+
try {
|
|
1513
|
+
await conn?.revokePermissions();
|
|
1514
|
+
} catch {
|
|
1515
|
+
}
|
|
1402
1516
|
};
|
|
1403
1517
|
/** Audience-bound sign-in (must connect() first). Returns the signed artifact to verify server-side. */
|
|
1404
1518
|
signInWithCsd = (params) => {
|
|
1405
|
-
if (!this.conn) return Promise.reject(new Error("call connect() before signInWithCsd()"));
|
|
1519
|
+
if (!this.conn || this.state.status !== "connected") return Promise.reject(new Error("call connect() before signInWithCsd()"));
|
|
1406
1520
|
return this.conn.signInWithCsd(params);
|
|
1407
1521
|
};
|
|
1408
1522
|
};
|
|
@@ -1410,6 +1524,17 @@ var CairnController = class {
|
|
|
1410
1524
|
// src/index.ts
|
|
1411
1525
|
var DEFAULT_SPV_CHECKPOINT = { height: 38142, hash: "0x00000000000140f023cc0ee1457a40833f2fcb4de44291b9d373e50f17b97232" };
|
|
1412
1526
|
var DEFAULT_CAIRN = "https://cairn-substrate.com";
|
|
1527
|
+
var SPV_HTTP_TIMEOUT_MS = 15e3;
|
|
1528
|
+
var SPV_HTTP_MAX_RETRIES = 4;
|
|
1529
|
+
var SPV_MAX_BATCH_BYTES = 2 * 1024 * 1024;
|
|
1530
|
+
var SPV_TRANSIENT_RE = /\b(429|50[0-9]|timeout|timed out|abort|aborted|headers|non-dense|failed to fetch|networkerror|load failed)\b/i;
|
|
1531
|
+
var SPV_TRANSIENT_JSON_RE = /unexpected (token|end)|json/i;
|
|
1532
|
+
var spvIsTransient = (e) => {
|
|
1533
|
+
const msg = String(e?.message || e);
|
|
1534
|
+
return SPV_TRANSIENT_RE.test(msg) || SPV_TRANSIENT_JSON_RE.test(msg);
|
|
1535
|
+
};
|
|
1536
|
+
var spvSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1537
|
+
var spvRetryDelayMs = (attempt, retryAfterSecs) => Math.min(4e3, Math.max(700 * (attempt + 1), retryAfterSecs * 1e3));
|
|
1413
1538
|
var Cairn = class {
|
|
1414
1539
|
config;
|
|
1415
1540
|
chain;
|
|
@@ -1423,8 +1548,13 @@ var Cairn = class {
|
|
|
1423
1548
|
cairnHttp;
|
|
1424
1549
|
// Lazily-seeded SPV light client + its checkpoint (audit M3 — PoW-verified header source).
|
|
1425
1550
|
_spvLight = null;
|
|
1426
|
-
|
|
1551
|
+
// MF-14: the in-flight seed promise. Concurrent first callers share ONE seed instead of racing two
|
|
1552
|
+
// syncFromCheckpoint calls onto the same client (the loser would hit "seedTrusted must be called on a
|
|
1553
|
+
// fresh client"). Cleared when the seed settles; a failure leaves no cached state, so the next call retries.
|
|
1554
|
+
_spvSeedInflight = null;
|
|
1427
1555
|
_spvCp;
|
|
1556
|
+
// W12: the cairn base the SPV header-batch provider fetches /api/headers from (same base the rest of the SDK uses).
|
|
1557
|
+
_cairnBase;
|
|
1428
1558
|
constructor(config = {}) {
|
|
1429
1559
|
this.config = config;
|
|
1430
1560
|
const fetch = config.fetch;
|
|
@@ -1433,6 +1563,7 @@ var Cairn = class {
|
|
|
1433
1563
|
const rpc = config.baseUrls?.rpc ?? `${cairn}/api/rpc`;
|
|
1434
1564
|
const indexer = (config.baseUrls?.indexer ?? `${cairn}/explorer/api`).replace(/\/+$/, "");
|
|
1435
1565
|
this.cairnHttp = new Http({ baseUrl: cairn, fetch, timeoutMs });
|
|
1566
|
+
this._cairnBase = cairn;
|
|
1436
1567
|
const indexerHttp = new Http({ baseUrl: indexer, fetch, timeoutMs });
|
|
1437
1568
|
const swarmHttp = config.baseUrls?.swarm ? new Http({ baseUrl: config.baseUrls.swarm.replace(/\/+$/, ""), fetch, timeoutMs }) : void 0;
|
|
1438
1569
|
this._spvCp = config.spvCheckpoint ?? DEFAULT_SPV_CHECKPOINT;
|
|
@@ -1445,7 +1576,7 @@ var Cairn = class {
|
|
|
1445
1576
|
headerMerkleAt: (h) => this.verifiedHeaderMerkleAt(h)
|
|
1446
1577
|
});
|
|
1447
1578
|
this.content = new ContentClient({ swarm: swarmHttp, indexer: indexerHttp, cairn: this.cairnHttp });
|
|
1448
|
-
this.registry = new RegistryClient(
|
|
1579
|
+
this.registry = new RegistryClient(indexerHttp);
|
|
1449
1580
|
this.names = new NamesClient(this.cairnHttp);
|
|
1450
1581
|
}
|
|
1451
1582
|
/**
|
|
@@ -1455,26 +1586,132 @@ var Cairn = class {
|
|
|
1455
1586
|
* checkpoint (can't verify backward) or if verification fails — the IndexerClient then degrades
|
|
1456
1587
|
* to the honest "proof-consistent" instead of over-claiming "verified-inclusion". The verified
|
|
1457
1588
|
* header chain is cached across calls; the first call near tip syncs the post-checkpoint span.
|
|
1589
|
+
* MF-03: a STRUCTURAL forward-sync break (a reorg orphaned the cached tip) reseeds ONCE via
|
|
1590
|
+
* withSpvReseed. RESIDUAL: a height at or below the cached tip answers from the cached branch with
|
|
1591
|
+
* NO network touch, so a tx in a reorg-orphaned block can still read "verified-inclusion" until a
|
|
1592
|
+
* read PAST the tip trips the reseed; this closes the permanent post-reorg brick, not reorg-blindness.
|
|
1458
1593
|
*/
|
|
1459
1594
|
async verifiedHeaderMerkleAt(height) {
|
|
1460
1595
|
if (!Number.isInteger(height) || height < this._spvCp.height)
|
|
1461
1596
|
throw new Error(`height ${height} is below the SPV checkpoint ${this._spvCp.height} (cannot PoW-verify backward)`);
|
|
1597
|
+
return this.withSpvReseed(async (lc) => {
|
|
1598
|
+
const haveTo = lc.baseHeight + lc.chain.length - 1;
|
|
1599
|
+
if (haveTo < height) await lc.sync(height);
|
|
1600
|
+
const vh = lc.chain[height - lc.baseHeight];
|
|
1601
|
+
if (!vh) throw new Error(`light client has no verified header at ${height}`);
|
|
1602
|
+
return String(vh.header.merkle);
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
// MF-03: run an SPV read; on a STRUCTURAL failure (a reorg orphaned our cached tip: broken
|
|
1606
|
+
// prev-link / bad PoW / hash mismatch) reseed ONCE from the pinned checkpoint into a LOCAL fresh
|
|
1607
|
+
// client, retry the read on it, and commit the fresh client ONLY when both succeed (never null
|
|
1608
|
+
// this._spvLight first - the wallet G8 lesson, same shape as the MF-14 seed). A TRANSIENT failure
|
|
1609
|
+
// (rate limit / gateway / timeout / non-dense / CF 200-HTML) keeps the cache and rethrows: wiping
|
|
1610
|
+
// on those turns one /api/headers 429 into a reseed storm (DOS-HDR-3). A second structural
|
|
1611
|
+
// failure propagates fail-closed; there is no loop.
|
|
1612
|
+
// RESIDUAL (recorded, not closed): heights at or below the cached tip answer from the cached
|
|
1613
|
+
// branch with NO network touch, so after a reorg "verified-inclusion" can still be over-claimed
|
|
1614
|
+
// for a tx in an orphaned block until some read past the tip trips this reseed. This fix closes
|
|
1615
|
+
// the permanent-brick leg only.
|
|
1616
|
+
async withSpvReseed(op) {
|
|
1462
1617
|
const lc = await this.seededSpvLight();
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1618
|
+
try {
|
|
1619
|
+
return await op(lc);
|
|
1620
|
+
} catch (e) {
|
|
1621
|
+
if (spvIsTransient(e)) throw e;
|
|
1622
|
+
const fresh = await this.seedFreshSpvLight();
|
|
1623
|
+
const out = await op(fresh);
|
|
1624
|
+
this._spvLight = fresh;
|
|
1625
|
+
return out;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
// Build + seed + tip-advance a FRESH light client into a LOCAL. Never touches this._spvLight:
|
|
1629
|
+
// the caller commits the result only on success (MF-14; the wallet G8 posture - never null or
|
|
1630
|
+
// dirty the shared field while an attempt is in flight).
|
|
1631
|
+
async seedFreshSpvLight() {
|
|
1632
|
+
const lc = new import_csd_light.LightClient({
|
|
1633
|
+
client: this.chain.client,
|
|
1634
|
+
checkpoints: { [this._spvCp.height]: this._spvCp.hash },
|
|
1635
|
+
headersBatchProvider: (from, count) => this.fetchHeadersBatch(from, count)
|
|
1636
|
+
});
|
|
1637
|
+
await lc.syncFromCheckpoint(this._spvCp.height, this._spvCp.hash);
|
|
1638
|
+
try {
|
|
1639
|
+
const tipHeight = Number((await this.chain.tip()).height);
|
|
1640
|
+
const have = lc.baseHeight + lc.chain.length - 1;
|
|
1641
|
+
if (Number.isFinite(tipHeight) && tipHeight > have) await lc.sync(tipHeight);
|
|
1642
|
+
} catch {
|
|
1643
|
+
}
|
|
1644
|
+
return lc;
|
|
1645
|
+
}
|
|
1646
|
+
/** The PoW-verifying SPV light client, seeded from the pinned checkpoint and cached across calls
|
|
1647
|
+
* (shared by the M3 header-merkle bind and the F13 offer pre-verify). The client is committed to
|
|
1648
|
+
* the field ONLY after a fully successful seed (MF-14): a failed seed leaves NO cached state, so
|
|
1649
|
+
* the next call retries on a fresh client instead of wedging on "seedTrusted must be called on a
|
|
1650
|
+
* fresh client". Concurrent first callers share ONE in-flight seed. Re-seeded only when a
|
|
1651
|
+
* structural (reorg) break is detected, see withSpvReseed. */
|
|
1652
|
+
seededSpvLight() {
|
|
1653
|
+
if (this._spvLight) return Promise.resolve(this._spvLight);
|
|
1654
|
+
if (!this._spvSeedInflight) {
|
|
1655
|
+
this._spvSeedInflight = this.seedFreshSpvLight().then((lc) => {
|
|
1656
|
+
this._spvLight = lc;
|
|
1657
|
+
return lc;
|
|
1658
|
+
}).finally(() => {
|
|
1659
|
+
this._spvSeedInflight = null;
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
return this._spvSeedInflight;
|
|
1663
|
+
}
|
|
1664
|
+
/**
|
|
1665
|
+
* W12 batch header transport for the checkpoint..tip forward sync, backed by the cairn server's
|
|
1666
|
+
* `/api/headers/:from/:count`. Carries ZERO trust: the LightClient re-verifies every header's PoW /
|
|
1667
|
+
* prev-link / LWMA, so a forged or stale row fails closed and retrying transport proves nothing new.
|
|
1668
|
+
* Retry budget: a 429 (per-IP header budget) or 502/503 (a height past the lagging indexer, or the
|
|
1669
|
+
* server's 10s deadline), a transport/timeout error, or a body-read fault (an oversize stream past the
|
|
1670
|
+
* SPV_MAX_BATCH_BYTES cap, or a non-JSON 200 body such as a CF interstitial) is a transient
|
|
1671
|
+
* data-availability hiccup; back off (honouring Retry-After) a bounded number of times, then THROW so
|
|
1672
|
+
* `sync()` fails closed. A non-dense range is rejected terminally (never retried).
|
|
1673
|
+
*
|
|
1674
|
+
* B7d / F6 DEPENDENCY: `/api/headers` serves no Access-Control-Allow-Origin today (`corsPublic` is
|
|
1675
|
+
* mounted on `/explorer/api`, `/trade/api` and `/api/rpc`, not here). A SAME-ORIGIN dApp (hosted on the
|
|
1676
|
+
* cairn base) reaches it now; a CROSS-ORIGIN third-party dApp cannot until B7d adds the CORS mount and
|
|
1677
|
+
* restarts the cairn service. This code is correct as written; it becomes reachable cross-origin only
|
|
1678
|
+
* once B7d ships. This is a recorded dependency, not a blocker for authoring/testing.
|
|
1679
|
+
*/
|
|
1680
|
+
async fetchHeadersBatch(from, count) {
|
|
1681
|
+
const g = this.config.fetch ?? globalThis.fetch;
|
|
1682
|
+
if (!g) throw new Error("No fetch available for SPV header sync - pass `fetch` in the SDK options.");
|
|
1683
|
+
const doFetch = this.config.fetch ? this.config.fetch : g.bind(globalThis);
|
|
1684
|
+
const url = `${this._cairnBase}/api/headers/${from}/${count}`;
|
|
1685
|
+
for (let attempt = 0; ; attempt++) {
|
|
1686
|
+
let res;
|
|
1687
|
+
let parsed;
|
|
1688
|
+
const ctrl = new AbortController();
|
|
1689
|
+
const timer = setTimeout(() => ctrl.abort(), SPV_HTTP_TIMEOUT_MS);
|
|
1690
|
+
try {
|
|
1691
|
+
res = await doFetch(url, { signal: ctrl.signal });
|
|
1692
|
+
if (res.ok) {
|
|
1693
|
+
parsed = JSON.parse(new TextDecoder().decode(await readCapped(res, SPV_MAX_BATCH_BYTES)));
|
|
1694
|
+
}
|
|
1695
|
+
} catch (e) {
|
|
1696
|
+
if (attempt >= SPV_HTTP_MAX_RETRIES) throw e;
|
|
1697
|
+
await spvSleep(spvRetryDelayMs(attempt, 0));
|
|
1698
|
+
continue;
|
|
1699
|
+
} finally {
|
|
1700
|
+
clearTimeout(timer);
|
|
1701
|
+
}
|
|
1702
|
+
if (res.ok) {
|
|
1703
|
+
const rows = parsed?.headers;
|
|
1704
|
+
if (!Array.isArray(rows) || rows.length !== count) throw new Error(`/api/headers: non-dense range (${from}/${count})`);
|
|
1705
|
+
return rows.map((r) => ({ header: r.header, hash: r.hash }));
|
|
1706
|
+
}
|
|
1707
|
+
if ((res.status === 429 || res.status === 502 || res.status === 503) && attempt < SPV_HTTP_MAX_RETRIES) {
|
|
1708
|
+
res.body?.cancel?.().catch(() => {
|
|
1709
|
+
});
|
|
1710
|
+
await spvSleep(spvRetryDelayMs(attempt, Number(res.headers.get("retry-after")) || 0));
|
|
1711
|
+
continue;
|
|
1712
|
+
}
|
|
1713
|
+
throw new Error(`/api/headers ${res.status}`);
|
|
1476
1714
|
}
|
|
1477
|
-
return this._spvLight;
|
|
1478
1715
|
}
|
|
1479
1716
|
/**
|
|
1480
1717
|
* F13 — corroborate an offer ON-CHAIN before building a `fillOffer` payment's `outputs`. Merkle-proves the
|
|
@@ -1487,10 +1724,14 @@ var Cairn = class {
|
|
|
1487
1724
|
* This is best-effort corroboration, NOT the payment-grade boundary: the Cairn Wallet's OWN on-device
|
|
1488
1725
|
* fill-SPV (0.2.60+, fails-closed before signing) is that. A dApp settling real value should still clear this
|
|
1489
1726
|
* before building `outputs`, so it never hands the wallet a payment a lying resolver would redirect.
|
|
1727
|
+
*
|
|
1728
|
+
* Pass opts.pay (the CSD base units you intend to pay) to receive the PROVEN outputPlan for a whole fill of a
|
|
1729
|
+
* non-partial offer, and opts.plannedOutputs to bind your planned per-address sums to it before signing; build
|
|
1730
|
+
* EXACTLY the outputPlan, never resolver-served outputs.
|
|
1490
1731
|
*/
|
|
1491
|
-
async verifyOfferForFill(offerId, servedOffer) {
|
|
1492
|
-
const light =
|
|
1493
|
-
return preverifyOffer({ light, client: this.chain.client, offerId, servedOffer });
|
|
1732
|
+
async verifyOfferForFill(offerId, servedOffer, opts) {
|
|
1733
|
+
const light = { verifyTxInclusion: (txidHex) => this.withSpvReseed((lc) => lc.verifyTxInclusion(txidHex)) };
|
|
1734
|
+
return preverifyOffer({ light, client: this.chain.client, offerId, servedOffer, pay: opts?.pay, plannedOutputs: opts?.plannedOutputs });
|
|
1494
1735
|
}
|
|
1495
1736
|
/**
|
|
1496
1737
|
* Detect + connect the Cairn Wallet (browser only). Wires the wallet into
|
|
@@ -1539,9 +1780,14 @@ var Cairn = class {
|
|
|
1539
1780
|
discoverProviders,
|
|
1540
1781
|
errorCode,
|
|
1541
1782
|
feeBpsAt,
|
|
1783
|
+
fillEndorsement,
|
|
1784
|
+
fillIsSafe,
|
|
1785
|
+
fillOutputPlan,
|
|
1542
1786
|
getWallet,
|
|
1543
1787
|
isInstalled,
|
|
1544
1788
|
mapProviderError,
|
|
1545
1789
|
mapSubmitResultError,
|
|
1546
|
-
preverifyOffer
|
|
1790
|
+
preverifyOffer,
|
|
1791
|
+
previewFill,
|
|
1792
|
+
requiredFillOutputs
|
|
1547
1793
|
});
|