@inversealtruism/cairn-sdk 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/board.cjs +1 -1
- package/dist/board.js +2 -2
- package/dist/{chunk-4RDXEW35.js → chunk-2L5QNDAE.js} +1 -1
- package/dist/{chunk-NLCF5T2F.js → chunk-2ZML7IKV.js} +1 -1
- package/dist/{chunk-UMXLWUEF.js → chunk-GTTLMWGY.js} +1 -1
- package/dist/{chunk-E2Z3XSCW.js → chunk-MREZG2HN.js} +2 -2
- package/dist/{chunk-QV2GIXO7.js → chunk-RKR3JMIA.js} +34 -16
- package/dist/connect.cjs +34 -17
- package/dist/connect.d.cts +7 -1
- package/dist/connect.d.ts +7 -1
- package/dist/connect.js +2 -2
- package/dist/content.cjs +1 -1
- package/dist/content.js +2 -2
- package/dist/controller.cjs +34 -17
- package/dist/controller.js +3 -3
- package/dist/index.cjs +164 -51
- package/dist/index.d.cts +40 -17
- package/dist/index.d.ts +40 -17
- package/dist/index.js +139 -37
- package/dist/react.cjs +34 -17
- package/dist/react.js +3 -3
- package/package.json +18 -25
package/dist/index.cjs
CHANGED
|
@@ -40,22 +40,27 @@ __export(index_exports, {
|
|
|
40
40
|
UserRejectedError: () => UserRejectedError,
|
|
41
41
|
WalletConnection: () => WalletConnection,
|
|
42
42
|
WalletLockedError: () => WalletLockedError,
|
|
43
|
-
bindOfferTerms: () => bindOfferTerms,
|
|
43
|
+
bindOfferTerms: () => import_cairnx_core2.bindOfferTerms,
|
|
44
44
|
connect: () => connect,
|
|
45
45
|
detectProvider: () => detectProvider,
|
|
46
46
|
discoverProviders: () => discoverProviders,
|
|
47
47
|
errorCode: () => errorCode,
|
|
48
|
-
feeBpsAt: () => feeBpsAt,
|
|
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.0";
|
|
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",
|
|
@@ -298,8 +303,9 @@ var Http = class {
|
|
|
298
303
|
try {
|
|
299
304
|
const res = await this.raw("GET", path, { query });
|
|
300
305
|
if (!res.ok) {
|
|
301
|
-
|
|
302
|
-
|
|
306
|
+
if (!(res.status >= 500 && attempt < this.retries)) throw new HttpError(res.status, url, await safeText(res));
|
|
307
|
+
res.body?.cancel().catch(() => {
|
|
308
|
+
});
|
|
303
309
|
} else {
|
|
304
310
|
return this.parseJson(res, new TextDecoder().decode(await readCapped(res, this.maxBytes)), url);
|
|
305
311
|
}
|
|
@@ -935,7 +941,7 @@ var NamesClient = class {
|
|
|
935
941
|
async resolve(name) {
|
|
936
942
|
if (!nameOk(name)) return null;
|
|
937
943
|
try {
|
|
938
|
-
return await this.http.getJson(`/trade/api/cairnx/resolve/${name}`);
|
|
944
|
+
return await this.http.getJson(`/trade/api/cairnx/resolve/${encodeURIComponent(name)}`);
|
|
939
945
|
} catch (e) {
|
|
940
946
|
if (e?.status === 404) return null;
|
|
941
947
|
throw e;
|
|
@@ -945,7 +951,7 @@ var NamesClient = class {
|
|
|
945
951
|
async name(name) {
|
|
946
952
|
if (!nameOk(name)) return null;
|
|
947
953
|
try {
|
|
948
|
-
return await this.http.getJson(`/trade/api/cairnx/name/${name}`);
|
|
954
|
+
return await this.http.getJson(`/trade/api/cairnx/name/${encodeURIComponent(name)}`);
|
|
949
955
|
} catch (e) {
|
|
950
956
|
if (e?.status === 404) return null;
|
|
951
957
|
throw e;
|
|
@@ -955,7 +961,7 @@ var NamesClient = class {
|
|
|
955
961
|
async nameHistory(name) {
|
|
956
962
|
if (!nameOk(name)) return null;
|
|
957
963
|
try {
|
|
958
|
-
return await this.http.getJson(`/trade/api/cairnx/name-history/${name}`);
|
|
964
|
+
return await this.http.getJson(`/trade/api/cairnx/name-history/${encodeURIComponent(name)}`);
|
|
959
965
|
} catch (e) {
|
|
960
966
|
if (e?.status === 404) return null;
|
|
961
967
|
throw e;
|
|
@@ -1014,6 +1020,7 @@ var NamesClient = class {
|
|
|
1014
1020
|
};
|
|
1015
1021
|
|
|
1016
1022
|
// src/connect.ts
|
|
1023
|
+
var DEFAULT_PROVIDER_TIMEOUT_MS = 10 * 6e4;
|
|
1017
1024
|
function discoverProviders(opts = {}) {
|
|
1018
1025
|
const timeoutMs = opts.timeoutMs ?? 1e3;
|
|
1019
1026
|
return new Promise((resolve) => {
|
|
@@ -1093,8 +1100,24 @@ function unwrapWrite(reply) {
|
|
|
1093
1100
|
var WalletConnection = class {
|
|
1094
1101
|
provider;
|
|
1095
1102
|
_addr = null;
|
|
1096
|
-
|
|
1103
|
+
/** B7c: per-call provider timeout (ms). GENEROUS by default so it never rejects an in-flight human
|
|
1104
|
+
* approval; 0 disables it. Every provider call (writes especially) races against it. */
|
|
1105
|
+
_timeoutMs;
|
|
1106
|
+
constructor(provider, opts = {}) {
|
|
1097
1107
|
this.provider = provider;
|
|
1108
|
+
this._timeoutMs = opts.timeoutMs ?? DEFAULT_PROVIDER_TIMEOUT_MS;
|
|
1109
|
+
}
|
|
1110
|
+
// B7c: race a provider call against the per-call timeout. On timeout, throw a fail-closed CairnError
|
|
1111
|
+
// (retryable:false: a write may already have reached the wallet, so never invite a blind auto-retry). The
|
|
1112
|
+
// genuine wallet resolves quickly once the user acts; this only fires on a truly hung/absent response.
|
|
1113
|
+
call(op, label) {
|
|
1114
|
+
const ms = this._timeoutMs;
|
|
1115
|
+
if (!Number.isFinite(ms) || ms <= 0) return Promise.resolve(op());
|
|
1116
|
+
let timer;
|
|
1117
|
+
const timeout = new Promise((_, reject) => {
|
|
1118
|
+
timer = setTimeout(() => reject(new CairnError(`the wallet did not respond to ${label} within ${ms}ms`, { retryable: false })), ms);
|
|
1119
|
+
});
|
|
1120
|
+
return Promise.race([Promise.resolve(op()), timeout]).finally(() => clearTimeout(timer));
|
|
1098
1121
|
}
|
|
1099
1122
|
/** The wallet's version string (e.g. "0.2.x"). */
|
|
1100
1123
|
get version() {
|
|
@@ -1106,13 +1129,13 @@ var WalletConnection = class {
|
|
|
1106
1129
|
}
|
|
1107
1130
|
/** Request connection. First time per origin prompts the user; afterwards may be silent. */
|
|
1108
1131
|
async connect() {
|
|
1109
|
-
const { addr } = unwrap(await this.provider.connect());
|
|
1132
|
+
const { addr } = unwrap(await this.call(() => this.provider.connect(), "connect"));
|
|
1110
1133
|
this._addr = addr;
|
|
1111
1134
|
return addr;
|
|
1112
1135
|
}
|
|
1113
1136
|
/** Get the connected address (does not force a new connection prompt if already consented). */
|
|
1114
1137
|
async getAddress() {
|
|
1115
|
-
const { addr } = unwrap(await this.provider.getAddress());
|
|
1138
|
+
const { addr } = unwrap(await this.call(() => this.provider.getAddress(), "getAddress"));
|
|
1116
1139
|
this._addr = addr;
|
|
1117
1140
|
return addr;
|
|
1118
1141
|
}
|
|
@@ -1125,7 +1148,7 @@ var WalletConnection = class {
|
|
|
1125
1148
|
if (typeof this.provider.signIn !== "function") {
|
|
1126
1149
|
return Promise.reject(new UnsupportedMethodError("this wallet does not expose signIn()"));
|
|
1127
1150
|
}
|
|
1128
|
-
return
|
|
1151
|
+
return this.call(() => this.provider.signIn(), "signIn").then(unwrap);
|
|
1129
1152
|
}
|
|
1130
1153
|
/**
|
|
1131
1154
|
* Audience-bound "Sign in with CSD" (SIWC) — the secure, replay-resistant third-party login.
|
|
@@ -1141,7 +1164,7 @@ var WalletConnection = class {
|
|
|
1141
1164
|
if (typeof this.provider.signInWithCsd !== "function") {
|
|
1142
1165
|
return Promise.reject(new UnsupportedMethodError("this wallet predates Sign-in-with-CSD \u2014 ask the user to update the Cairn Wallet"));
|
|
1143
1166
|
}
|
|
1144
|
-
return
|
|
1167
|
+
return this.call(() => this.provider.signInWithCsd(params), "signInWithCsd").then(unwrap);
|
|
1145
1168
|
}
|
|
1146
1169
|
/** True if this wallet supports audience-bound SIWC (safe to call `signInWithCsd`). */
|
|
1147
1170
|
get supportsSiwc() {
|
|
@@ -1151,7 +1174,7 @@ var WalletConnection = class {
|
|
|
1151
1174
|
async getCapabilities() {
|
|
1152
1175
|
if (typeof this.provider.getCapabilities !== "function") return null;
|
|
1153
1176
|
try {
|
|
1154
|
-
return await this.provider.getCapabilities();
|
|
1177
|
+
return await this.call(() => this.provider.getCapabilities(), "getCapabilities");
|
|
1155
1178
|
} catch {
|
|
1156
1179
|
return null;
|
|
1157
1180
|
}
|
|
@@ -1167,24 +1190,24 @@ var WalletConnection = class {
|
|
|
1167
1190
|
if (typeof this.provider.fillOffer !== "function") {
|
|
1168
1191
|
return Promise.reject(new UnsupportedMethodError("this wallet predates fillOffer() \u2014 ask the user to update the Cairn Wallet"));
|
|
1169
1192
|
}
|
|
1170
|
-
return
|
|
1193
|
+
return this.call(() => this.provider.fillOffer(params), "fillOffer").then(unwrapWrite);
|
|
1171
1194
|
}
|
|
1172
1195
|
/** This origin's current permission grant (EIP-2255-style). Silent; [] if none / unsupported. */
|
|
1173
1196
|
async getPermissions() {
|
|
1174
1197
|
if (typeof this.provider.getPermissions !== "function") return [];
|
|
1175
|
-
return unwrap(await this.provider.getPermissions());
|
|
1198
|
+
return unwrap(await this.call(() => this.provider.getPermissions(), "getPermissions"));
|
|
1176
1199
|
}
|
|
1177
1200
|
/** Explicitly request permission to see the address (prompts, like connect). */
|
|
1178
1201
|
requestPermissions() {
|
|
1179
1202
|
if (typeof this.provider.requestPermissions !== "function") {
|
|
1180
1203
|
return Promise.reject(new UnsupportedMethodError("this wallet predates requestPermissions \u2014 use connect()"));
|
|
1181
1204
|
}
|
|
1182
|
-
return
|
|
1205
|
+
return this.call(() => this.provider.requestPermissions(), "requestPermissions").then(unwrap);
|
|
1183
1206
|
}
|
|
1184
1207
|
/** Revoke THIS origin's own access (silent). Returns { revoked }. Safe no-op on older wallets. */
|
|
1185
1208
|
async revokePermissions() {
|
|
1186
1209
|
if (typeof this.provider.revokePermissions !== "function") return { revoked: false };
|
|
1187
|
-
return unwrap(await this.provider.revokePermissions());
|
|
1210
|
+
return unwrap(await this.call(() => this.provider.revokePermissions(), "revokePermissions"));
|
|
1188
1211
|
}
|
|
1189
1212
|
/**
|
|
1190
1213
|
* Subscribe to provider events. `accountsChanged` fires with `[]` when this site loses access
|
|
@@ -1200,26 +1223,26 @@ var WalletConnection = class {
|
|
|
1200
1223
|
}
|
|
1201
1224
|
/** Send CSD (always prompts with clear-signing; wallet picks inputs + returns change to itself). */
|
|
1202
1225
|
send(params) {
|
|
1203
|
-
return
|
|
1226
|
+
return this.call(() => this.provider.send(params), "send").then(unwrapWrite);
|
|
1204
1227
|
}
|
|
1205
1228
|
/** Low-level Propose (always prompts). Prefer `cairn.board.propose()` which computes the hash/uri/expiry. */
|
|
1206
1229
|
propose(params) {
|
|
1207
|
-
return
|
|
1230
|
+
return this.call(() => this.provider.propose(params), "propose").then(unwrapWrite);
|
|
1208
1231
|
}
|
|
1209
1232
|
/** Attest/support a proposal (always prompts). */
|
|
1210
1233
|
attest(params) {
|
|
1211
|
-
return
|
|
1234
|
+
return this.call(() => this.provider.attest(params), "attest").then(unwrapWrite);
|
|
1212
1235
|
}
|
|
1213
1236
|
/** Seal a commit-reveal claim (always prompts; salt stays inside the wallet). */
|
|
1214
1237
|
sealClaim(params) {
|
|
1215
|
-
return
|
|
1238
|
+
return this.call(() => this.provider.sealClaim(params), "sealClaim").then(unwrapWrite);
|
|
1216
1239
|
}
|
|
1217
1240
|
/** Reveal a previously sealed claim by its sealing txid (always prompts). */
|
|
1218
1241
|
revealClaim(txid3) {
|
|
1219
1242
|
if (typeof txid3 !== "string" || !txid3) {
|
|
1220
1243
|
return Promise.reject(new UnsupportedMethodError("revealClaim requires the sealing txid"));
|
|
1221
1244
|
}
|
|
1222
|
-
return
|
|
1245
|
+
return this.call(() => this.provider.revealClaim(txid3), "revealClaim").then(unwrapWrite);
|
|
1223
1246
|
}
|
|
1224
1247
|
};
|
|
1225
1248
|
async function getWallet(opts = {}) {
|
|
@@ -1238,19 +1261,22 @@ var import_cairnx_core2 = require("@inversealtruism/cairnx-core");
|
|
|
1238
1261
|
var ADDR = /^0x[0-9a-f]{40}$/;
|
|
1239
1262
|
var HASH = /^0x[0-9a-f]{64}$/;
|
|
1240
1263
|
var COINBASE = "0x" + "00".repeat(32);
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
const
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1264
|
+
function bindPlannedSums(planned, proven) {
|
|
1265
|
+
const provenMap = new Map(proven.map((o) => [o.to.toLowerCase(), o.value]));
|
|
1266
|
+
const plannedMap = /* @__PURE__ */ new Map();
|
|
1267
|
+
try {
|
|
1268
|
+
for (const [a, v] of Object.entries(planned)) {
|
|
1269
|
+
const k = String(a).toLowerCase();
|
|
1270
|
+
plannedMap.set(k, (plannedMap.get(k) ?? 0n) + BigInt(v));
|
|
1271
|
+
}
|
|
1272
|
+
} catch {
|
|
1273
|
+
return "invalid plannedOutputs (amounts must be base-unit integers)";
|
|
1274
|
+
}
|
|
1275
|
+
for (const k of /* @__PURE__ */ new Set([...provenMap.keys(), ...plannedMap.keys()])) {
|
|
1276
|
+
if ((provenMap.get(k) ?? 0n) !== (plannedMap.get(k) ?? 0n))
|
|
1277
|
+
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)`;
|
|
1278
|
+
}
|
|
1279
|
+
return void 0;
|
|
1254
1280
|
}
|
|
1255
1281
|
async function provenAuthor(client, provenTx) {
|
|
1256
1282
|
const in0 = provenTx.inputs?.[0];
|
|
@@ -1307,17 +1333,35 @@ async function preverifyOffer(opts) {
|
|
|
1307
1333
|
return { ok: false, trust: "unverified", reason: "the offer record doesn't match the on-chain (merkle-proven) commitment" };
|
|
1308
1334
|
const seller = await provenAuthor(opts.client, provenTx);
|
|
1309
1335
|
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" };
|
|
1336
|
+
const offerRec = rec;
|
|
1310
1337
|
const w = rec.want ?? {};
|
|
1311
1338
|
const payto = w.payto && ADDR.test(String(w.payto).toLowerCase()) ? String(w.payto).toLowerCase() : seller;
|
|
1312
|
-
const terms =
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1339
|
+
const terms = (0, import_cairnx_core2.provenOfferTerms)(offerRec, blockHeight);
|
|
1340
|
+
let outputPlan;
|
|
1341
|
+
let sumsMismatch;
|
|
1342
|
+
if (opts.pay !== void 0 && opts.pay !== null && !(0, import_cairnx_core2.isTokenWant)(offerRec.want) && offerRec.min === void 0) {
|
|
1343
|
+
const provenState = {
|
|
1344
|
+
id,
|
|
1345
|
+
seller,
|
|
1346
|
+
give: offerRec.give,
|
|
1347
|
+
want: offerRec.want,
|
|
1348
|
+
taker: offerRec.taker,
|
|
1349
|
+
bid: offerRec.bid,
|
|
1350
|
+
status: "open",
|
|
1351
|
+
expiresEpoch: 0,
|
|
1352
|
+
height: blockHeight,
|
|
1353
|
+
feeBps: terms.feeBps
|
|
1354
|
+
};
|
|
1355
|
+
const plan = (0, import_cairnx_core2.fillOutputPlan)(provenState, opts.pay);
|
|
1356
|
+
if (plan.kind === "csd-outputs") {
|
|
1357
|
+
outputPlan = plan.outputs;
|
|
1358
|
+
if (opts.plannedOutputs !== void 0 && opts.plannedOutputs !== null)
|
|
1359
|
+
sumsMismatch = bindPlannedSums(opts.plannedOutputs, plan.outputs);
|
|
1360
|
+
} else if (plan.kind === "undeliverable") {
|
|
1361
|
+
sumsMismatch = `this pay would not deliver against the proven offer (${plan.reason}) - refusing (the CSD would be lost)`;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
const base = { payto, seller, terms, blockHeight, ...outputPlan ? { outputPlan } : {} };
|
|
1321
1365
|
if (opts.servedOffer !== void 0 && opts.servedOffer !== null) {
|
|
1322
1366
|
const so = opts.servedOffer;
|
|
1323
1367
|
const servedPayto = so?.want?.payto !== void 0 && so?.want?.payto !== null ? String(so.want.payto).toLowerCase() : "";
|
|
@@ -1325,9 +1369,10 @@ async function preverifyOffer(opts) {
|
|
|
1325
1369
|
return { ok: false, trust: "verified", reason: "the served payment recipient doesn't match the offer's on-chain author/record", ...base };
|
|
1326
1370
|
if (so?.seller !== void 0 && so?.seller !== null && String(so.seller).toLowerCase() !== seller)
|
|
1327
1371
|
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 };
|
|
1328
|
-
if (bindOfferTerms(opts.servedOffer, terms))
|
|
1329
|
-
return { ok: false, trust: "verified", reason: "the served
|
|
1372
|
+
if ((0, import_cairnx_core2.bindOfferTerms)(opts.servedOffer, terms, { give: true, wantType: true }))
|
|
1373
|
+
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 };
|
|
1330
1374
|
}
|
|
1375
|
+
if (sumsMismatch) return { ok: false, trust: "verified", reason: sumsMismatch, ...base };
|
|
1331
1376
|
return { ok: true, trust: "verified", ...base };
|
|
1332
1377
|
}
|
|
1333
1378
|
|
|
@@ -1423,6 +1468,10 @@ var CairnController = class {
|
|
|
1423
1468
|
// src/index.ts
|
|
1424
1469
|
var DEFAULT_SPV_CHECKPOINT = { height: 38142, hash: "0x00000000000140f023cc0ee1457a40833f2fcb4de44291b9d373e50f17b97232" };
|
|
1425
1470
|
var DEFAULT_CAIRN = "https://cairn-substrate.com";
|
|
1471
|
+
var SPV_HTTP_TIMEOUT_MS = 15e3;
|
|
1472
|
+
var SPV_HTTP_MAX_RETRIES = 4;
|
|
1473
|
+
var spvSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1474
|
+
var spvRetryDelayMs = (attempt, retryAfterSecs) => Math.min(4e3, Math.max(700 * (attempt + 1), retryAfterSecs * 1e3));
|
|
1426
1475
|
var Cairn = class {
|
|
1427
1476
|
config;
|
|
1428
1477
|
chain;
|
|
@@ -1438,6 +1487,8 @@ var Cairn = class {
|
|
|
1438
1487
|
_spvLight = null;
|
|
1439
1488
|
_spvSeeded = false;
|
|
1440
1489
|
_spvCp;
|
|
1490
|
+
// W12: the cairn base the SPV header-batch provider fetches /api/headers from (same base the rest of the SDK uses).
|
|
1491
|
+
_cairnBase;
|
|
1441
1492
|
constructor(config = {}) {
|
|
1442
1493
|
this.config = config;
|
|
1443
1494
|
const fetch = config.fetch;
|
|
@@ -1446,6 +1497,7 @@ var Cairn = class {
|
|
|
1446
1497
|
const rpc = config.baseUrls?.rpc ?? `${cairn}/api/rpc`;
|
|
1447
1498
|
const indexer = (config.baseUrls?.indexer ?? `${cairn}/explorer/api`).replace(/\/+$/, "");
|
|
1448
1499
|
this.cairnHttp = new Http({ baseUrl: cairn, fetch, timeoutMs });
|
|
1500
|
+
this._cairnBase = cairn;
|
|
1449
1501
|
const indexerHttp = new Http({ baseUrl: indexer, fetch, timeoutMs });
|
|
1450
1502
|
const swarmHttp = config.baseUrls?.swarm ? new Http({ baseUrl: config.baseUrls.swarm.replace(/\/+$/, ""), fetch, timeoutMs }) : void 0;
|
|
1451
1503
|
this._spvCp = config.spvCheckpoint ?? DEFAULT_SPV_CHECKPOINT;
|
|
@@ -1482,13 +1534,69 @@ var Cairn = class {
|
|
|
1482
1534
|
/** The PoW-verifying SPV light client, seeded ONCE from the pinned checkpoint (shared by the M3 header-merkle
|
|
1483
1535
|
* bind and the F13 offer pre-verify). Reused across calls; the first call near tip syncs the post-checkpoint span. */
|
|
1484
1536
|
async seededSpvLight() {
|
|
1485
|
-
if (!this._spvLight) this._spvLight = new import_csd_light.LightClient({
|
|
1537
|
+
if (!this._spvLight) this._spvLight = new import_csd_light.LightClient({
|
|
1538
|
+
client: this.chain.client,
|
|
1539
|
+
checkpoints: { [this._spvCp.height]: this._spvCp.hash },
|
|
1540
|
+
headersBatchProvider: (from, count) => this.fetchHeadersBatch(from, count)
|
|
1541
|
+
});
|
|
1486
1542
|
if (!this._spvSeeded) {
|
|
1487
1543
|
await this._spvLight.syncFromCheckpoint(this._spvCp.height, this._spvCp.hash);
|
|
1544
|
+
try {
|
|
1545
|
+
const tipHeight = Number((await this.chain.tip()).height);
|
|
1546
|
+
const have = this._spvLight.baseHeight + this._spvLight.chain.length - 1;
|
|
1547
|
+
if (Number.isFinite(tipHeight) && tipHeight > have) await this._spvLight.sync(tipHeight);
|
|
1548
|
+
} catch {
|
|
1549
|
+
}
|
|
1488
1550
|
this._spvSeeded = true;
|
|
1489
1551
|
}
|
|
1490
1552
|
return this._spvLight;
|
|
1491
1553
|
}
|
|
1554
|
+
/**
|
|
1555
|
+
* W12 batch header transport for the checkpoint..tip forward sync, backed by the cairn server's
|
|
1556
|
+
* `/api/headers/:from/:count`. Carries ZERO trust: the LightClient re-verifies every header's PoW /
|
|
1557
|
+
* prev-link / LWMA, so a forged or stale row fails closed and retrying transport proves nothing new.
|
|
1558
|
+
* Retry budget: a 429 (per-IP header budget) or 502/503 (a height past the lagging indexer, or the
|
|
1559
|
+
* server's 10s deadline) is a transient data-availability hiccup; back off (honouring Retry-After) a
|
|
1560
|
+
* bounded number of times, then THROW so `sync()` fails closed. A non-dense range is rejected.
|
|
1561
|
+
*
|
|
1562
|
+
* B7d / F6 DEPENDENCY: `/api/headers` serves no Access-Control-Allow-Origin today (`corsPublic` is
|
|
1563
|
+
* mounted on `/explorer/api`, `/trade/api` and `/api/rpc`, not here). A SAME-ORIGIN dApp (hosted on the
|
|
1564
|
+
* cairn base) reaches it now; a CROSS-ORIGIN third-party dApp cannot until B7d adds the CORS mount and
|
|
1565
|
+
* restarts the cairn service. This code is correct as written; it becomes reachable cross-origin only
|
|
1566
|
+
* once B7d ships. This is a recorded dependency, not a blocker for authoring/testing.
|
|
1567
|
+
*/
|
|
1568
|
+
async fetchHeadersBatch(from, count) {
|
|
1569
|
+
const g = this.config.fetch ?? globalThis.fetch;
|
|
1570
|
+
if (!g) throw new Error("No fetch available for SPV header sync - pass `fetch` in the SDK options.");
|
|
1571
|
+
const doFetch = this.config.fetch ? this.config.fetch : g.bind(globalThis);
|
|
1572
|
+
const url = `${this._cairnBase}/api/headers/${from}/${count}`;
|
|
1573
|
+
for (let attempt = 0; ; attempt++) {
|
|
1574
|
+
let res;
|
|
1575
|
+
const ctrl = new AbortController();
|
|
1576
|
+
const timer = setTimeout(() => ctrl.abort(), SPV_HTTP_TIMEOUT_MS);
|
|
1577
|
+
try {
|
|
1578
|
+
res = await doFetch(url, { signal: ctrl.signal });
|
|
1579
|
+
} catch (e) {
|
|
1580
|
+
if (attempt >= SPV_HTTP_MAX_RETRIES) throw e;
|
|
1581
|
+
await spvSleep(spvRetryDelayMs(attempt, 0));
|
|
1582
|
+
continue;
|
|
1583
|
+
} finally {
|
|
1584
|
+
clearTimeout(timer);
|
|
1585
|
+
}
|
|
1586
|
+
if (res.ok) {
|
|
1587
|
+
const rows = (await res.json())?.headers;
|
|
1588
|
+
if (!Array.isArray(rows) || rows.length !== count) throw new Error(`/api/headers: non-dense range (${from}/${count})`);
|
|
1589
|
+
return rows.map((r) => ({ header: r.header, hash: r.hash }));
|
|
1590
|
+
}
|
|
1591
|
+
if ((res.status === 429 || res.status === 502 || res.status === 503) && attempt < SPV_HTTP_MAX_RETRIES) {
|
|
1592
|
+
res.body?.cancel?.().catch(() => {
|
|
1593
|
+
});
|
|
1594
|
+
await spvSleep(spvRetryDelayMs(attempt, Number(res.headers.get("retry-after")) || 0));
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
throw new Error(`/api/headers ${res.status}`);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1492
1600
|
/**
|
|
1493
1601
|
* F13 — corroborate an offer ON-CHAIN before building a `fillOffer` payment's `outputs`. Merkle-proves the
|
|
1494
1602
|
* offer into the PoW-verified header chain (from the pinned SPV checkpoint), binds the record to its on-chain
|
|
@@ -1552,9 +1660,14 @@ var Cairn = class {
|
|
|
1552
1660
|
discoverProviders,
|
|
1553
1661
|
errorCode,
|
|
1554
1662
|
feeBpsAt,
|
|
1663
|
+
fillEndorsement,
|
|
1664
|
+
fillIsSafe,
|
|
1665
|
+
fillOutputPlan,
|
|
1555
1666
|
getWallet,
|
|
1556
1667
|
isInstalled,
|
|
1557
1668
|
mapProviderError,
|
|
1558
1669
|
mapSubmitResultError,
|
|
1559
|
-
preverifyOffer
|
|
1670
|
+
preverifyOffer,
|
|
1671
|
+
previewFill,
|
|
1672
|
+
requiredFillOutputs
|
|
1560
1673
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -10,6 +10,8 @@ export { ContentClientOptions, PreparedContent } from './content.cjs';
|
|
|
10
10
|
import { RegistryClient } from './registry.cjs';
|
|
11
11
|
import { WalletConnection, DetectOptions } from './connect.cjs';
|
|
12
12
|
export { AttestParams, CairnProvider, ConnectResult, FillParams, ProposeParams, ProviderDetail, ProviderInfo, SealClaimParams, SendParams, SignInResult, SiwcParams, SiwcResult, TxResult, WalletCapabilities, WalletPermission, connect, detectProvider, discoverProviders, getWallet, isInstalled } from './connect.cjs';
|
|
13
|
+
import { ProvenOfferTerms } from '@inversealtruism/cairnx-core';
|
|
14
|
+
export { FillEndorsement, FillOutputPlan, FillPreview, FillSafety, ProvenOfferTerms, bindOfferTerms, feeBpsAt, fillEndorsement, fillIsSafe, fillOutputPlan, previewFill, requiredFillOutputs } from '@inversealtruism/cairnx-core';
|
|
13
15
|
import { InclusionResult } from '@inversealtruism/csd-light';
|
|
14
16
|
export { CairnController, CairnControllerOptions, CairnState } from './controller.cjs';
|
|
15
17
|
import '@inversealtruism/csd-client';
|
|
@@ -104,15 +106,6 @@ declare class NamesClient {
|
|
|
104
106
|
state(): Promise<unknown>;
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
/** The fee/rebate-relevant fields of an offer, derived from the MERKLE-PROVEN offer (never a served object). */
|
|
108
|
-
interface ProvenOfferTerms {
|
|
109
|
-
height: number;
|
|
110
|
-
feeBps: number;
|
|
111
|
-
value?: string;
|
|
112
|
-
taker?: string;
|
|
113
|
-
bid?: string;
|
|
114
|
-
min?: string;
|
|
115
|
-
}
|
|
116
109
|
/** The trust-labeled verdict. `ok:false` + `transient:true` = a retryable chain-catching-up soft-fail (NOT a
|
|
117
110
|
* proven mismatch). `payto`/`seller`/`terms` are populated whenever they could be merkle-derived. */
|
|
118
111
|
interface OfferFillCheck {
|
|
@@ -126,12 +119,17 @@ interface OfferFillCheck {
|
|
|
126
119
|
seller?: string;
|
|
127
120
|
terms?: ProvenOfferTerms;
|
|
128
121
|
blockHeight?: number;
|
|
122
|
+
/** B7b (REBIND W3/M1): the PROVEN CSD outputs to build for `pay` (a WHOLE fill of a non-partial offer),
|
|
123
|
+
* sized from the merkle-proven offer by the single-sourced `fillOutputPlan`. Present only when `pay` was
|
|
124
|
+
* passed and the offer could be sized. Build EXACTLY these; never the resolver-served outputs a lying
|
|
125
|
+
* resolver could redirect. A partial (min-bearing) offer is left unsized (its running paid/delivered is
|
|
126
|
+
* not provable at this corroboration layer; the wallet's fill-SPV is the payment-grade sizer). */
|
|
127
|
+
outputPlan?: {
|
|
128
|
+
to: string;
|
|
129
|
+
value: bigint;
|
|
130
|
+
}[];
|
|
129
131
|
}
|
|
130
|
-
|
|
131
|
-
declare const feeBpsAt: (height: number) => number;
|
|
132
|
-
/** true iff any fee/rebate/partial-sizing field of the SERVED offer diverges from the merkle-proven terms `t`
|
|
133
|
-
* (fail-closed: any divergence => the caller refuses). Byte-identical to cairnx-core `bindOfferTerms`. */
|
|
134
|
-
declare function bindOfferTerms(servedOffer: unknown, t: ProvenOfferTerms): boolean;
|
|
132
|
+
|
|
135
133
|
/**
|
|
136
134
|
* Pre-verify an offer on-chain before building a `fillOffer` payment. `light` is a PoW-verifying light client
|
|
137
135
|
* (its `verifyTxInclusion` surfaces the merkle-proven tx + committed appPayloadHash); `client` reads source txs
|
|
@@ -147,11 +145,20 @@ declare function preverifyOffer(opts: {
|
|
|
147
145
|
};
|
|
148
146
|
offerId: string;
|
|
149
147
|
servedOffer?: unknown;
|
|
148
|
+
/** B7b (REBIND W3/M1): the CSD base units you intend to pay. When given, the result carries `outputPlan`
|
|
149
|
+
* (the PROVEN CSD outputs to build for a WHOLE fill of a non-partial offer, sized by the single-sourced
|
|
150
|
+
* `fillOutputPlan`) and, if you also pass `plannedOutputs`, binds them EXACTLY to that proven plan.
|
|
151
|
+
* A partial (min-bearing) offer is left unsized here; the wallet's fill-SPV is its payment-grade sizer. */
|
|
152
|
+
pay?: bigint | string | number;
|
|
153
|
+
/** B7b: your planned per-address CSD sums (the outputs you WILL build), bound both ways to `outputPlan`.
|
|
154
|
+
* Requires `pay`. A missing/low leg is a doomed underpay; an extra/high leg is a W3 overpay or an N26
|
|
155
|
+
* smuggled output - both refused. */
|
|
156
|
+
plannedOutputs?: Record<string, bigint | string | number>;
|
|
150
157
|
}): Promise<OfferFillCheck>;
|
|
151
158
|
|
|
152
159
|
/** Stable machine-readable error codes (branch on these, not the message text). */
|
|
153
160
|
type CairnErrorCode = "NOT_INSTALLED" | "USER_REJECTED" | "WALLET_LOCKED" | "UNSUPPORTED_METHOD" | "HTTP_ERROR" | "CONTENT_VERIFICATION" | "ACCOUNT_CHANGED" | "FIRST_PARTY_ONLY" | "RATE_LIMITED" | "APPROVAL_CLOSED" | "FORBIDDEN" | "INTERNAL" | "GHOST_INPUTS_SKIPPED" | "VERIFY_UNAVAILABLE" | "VERIFY_TAMPER" | "INSUFFICIENT" | "TOO_MANY_INPUTS" | "FEE_TOO_LOW" | "FEE_CAP" | "BAD_FEE" | "ZERO_ADDR_REFUSED" | "BAD_OUTPUTS" | "NO_OUTPUTS" | "BAD_REQUEST" | "OFFER_UNKNOWN" | "FILL_UNSAFE" | "FILL_WRONG_TARGET" | "SOURCE_DIVERGENCE" | "SUBMIT_REJECTED" | "SUBMIT_MAYBE_INFLIGHT" | "SUBMIT_DUPLICATE" | "NOTHING_TO_CONSOLIDATE" | "UNKNOWN";
|
|
154
|
-
declare const SDK_VERSION = "0.
|
|
161
|
+
declare const SDK_VERSION = "0.4.0";
|
|
155
162
|
interface CairnErrorOptions {
|
|
156
163
|
code?: CairnErrorCode;
|
|
157
164
|
docsPath?: string;
|
|
@@ -171,7 +178,7 @@ declare class CairnError extends Error {
|
|
|
171
178
|
/** Relative docs path (under the SDK docs base), if any. */
|
|
172
179
|
readonly docsPath?: string;
|
|
173
180
|
/** The cairn-sdk version that produced this error. */
|
|
174
|
-
readonly version = "0.
|
|
181
|
+
readonly version = "0.4.0";
|
|
175
182
|
/**
|
|
176
183
|
* Whether a dApp may safely AUTO-retry this failure. `false` for fund-safety refusals and user-intent
|
|
177
184
|
* declines (the default); `true` only for provably transient infra faults where nothing was signed.
|
|
@@ -312,6 +319,7 @@ declare class Cairn {
|
|
|
312
319
|
private _spvLight;
|
|
313
320
|
private _spvSeeded;
|
|
314
321
|
private readonly _spvCp;
|
|
322
|
+
private readonly _cairnBase;
|
|
315
323
|
constructor(config?: CairnConfig);
|
|
316
324
|
/**
|
|
317
325
|
* PoW-VERIFIED header merkle at a height (audit M3 — the no-PoW half). Lazily seeds a light
|
|
@@ -325,6 +333,21 @@ declare class Cairn {
|
|
|
325
333
|
/** The PoW-verifying SPV light client, seeded ONCE from the pinned checkpoint (shared by the M3 header-merkle
|
|
326
334
|
* bind and the F13 offer pre-verify). Reused across calls; the first call near tip syncs the post-checkpoint span. */
|
|
327
335
|
private seededSpvLight;
|
|
336
|
+
/**
|
|
337
|
+
* W12 batch header transport for the checkpoint..tip forward sync, backed by the cairn server's
|
|
338
|
+
* `/api/headers/:from/:count`. Carries ZERO trust: the LightClient re-verifies every header's PoW /
|
|
339
|
+
* prev-link / LWMA, so a forged or stale row fails closed and retrying transport proves nothing new.
|
|
340
|
+
* Retry budget: a 429 (per-IP header budget) or 502/503 (a height past the lagging indexer, or the
|
|
341
|
+
* server's 10s deadline) is a transient data-availability hiccup; back off (honouring Retry-After) a
|
|
342
|
+
* bounded number of times, then THROW so `sync()` fails closed. A non-dense range is rejected.
|
|
343
|
+
*
|
|
344
|
+
* B7d / F6 DEPENDENCY: `/api/headers` serves no Access-Control-Allow-Origin today (`corsPublic` is
|
|
345
|
+
* mounted on `/explorer/api`, `/trade/api` and `/api/rpc`, not here). A SAME-ORIGIN dApp (hosted on the
|
|
346
|
+
* cairn base) reaches it now; a CROSS-ORIGIN third-party dApp cannot until B7d adds the CORS mount and
|
|
347
|
+
* restarts the cairn service. This code is correct as written; it becomes reachable cross-origin only
|
|
348
|
+
* once B7d ships. This is a recorded dependency, not a blocker for authoring/testing.
|
|
349
|
+
*/
|
|
350
|
+
private fetchHeadersBatch;
|
|
328
351
|
/**
|
|
329
352
|
* F13 — corroborate an offer ON-CHAIN before building a `fillOffer` payment's `outputs`. Merkle-proves the
|
|
330
353
|
* offer into the PoW-verified header chain (from the pinned SPV checkpoint), binds the record to its on-chain
|
|
@@ -349,4 +372,4 @@ declare class Cairn {
|
|
|
349
372
|
get connected(): boolean;
|
|
350
373
|
}
|
|
351
374
|
|
|
352
|
-
export { BoardClient, Cairn, type CairnBaseUrls, type CairnConfig, CairnError, type CairnErrorCode, type CairnErrorOptions, Chain, ContentClient, ContentVerificationError, DEFAULT_SPV_CHECKPOINT, DetectOptions, FetchLike, Http, HttpError, IndexerClient, type NameDetail, type NameResolution, NamesClient, NotInstalledError, type OfferFillCheck, type PrimaryName,
|
|
375
|
+
export { BoardClient, Cairn, type CairnBaseUrls, type CairnConfig, CairnError, type CairnErrorCode, type CairnErrorOptions, Chain, ContentClient, ContentVerificationError, DEFAULT_SPV_CHECKPOINT, DetectOptions, FetchLike, Http, HttpError, IndexerClient, type NameDetail, type NameResolution, NamesClient, NotInstalledError, type OfferFillCheck, type PrimaryName, RegistryClient, SDK_VERSION, SubmitInFlightError, type TokenInfo, UnsupportedMethodError, UserRejectedError, WalletConnection, WalletLockedError, errorCode, mapProviderError, mapSubmitResultError, preverifyOffer };
|