@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/dist/index.d.cts CHANGED
@@ -11,8 +11,8 @@ import { WalletConnection, DetectOptions } from './connect.cjs';
11
11
  export { AttestParams, CairnProvider, ConnectResult, FillParams, ProposeParams, ProviderDetail, ProviderInfo, SealClaimParams, SendParams, SignInResult, SiwcParams, SiwcResult, TxResult, WalletCapabilities, WalletPermission, connect, detectProvider, discoverProviders, getWallet, isInstalled } from './connect.cjs';
12
12
  import { InclusionResult } from '@inversealtruism/csd-light';
13
13
  export { CairnController, CairnControllerOptions, CairnState } from './controller.cjs';
14
+ import { NodeTxJson } from '@inversealtruism/csd-tx';
14
15
  import '@inversealtruism/csd-client';
15
- import '@inversealtruism/csd-tx';
16
16
  import '@inversealtruism/csd-codec';
17
17
  import '@inversealtruism/csd-crypto';
18
18
 
@@ -138,6 +138,9 @@ declare class NamesClient {
138
138
  /** The trust-labeled verdict. `ok:false` + `transient:true` = a retryable chain-catching-up soft-fail (NOT a
139
139
  * proven mismatch). `payto`/`seller`/`terms` are populated whenever they could be merkle-derived. */
140
140
  interface OfferFillCheck {
141
+ /** `true`: the offer record is proven and no check refused. Without `me` and `tip` it does not say the offer
142
+ * is fillable by you now (it may be bound to another taker, held by someone else's live claim or
143
+ * reservation, or too close to its expiry), and with `sized: false` it does not size your fill. */
141
144
  ok: boolean;
142
145
  /** "verified" = merkle-proven inclusion + on-chain author; "proof-consistent" = a proof that folds but the
143
146
  * header could not be PoW-verified (indexer-trusted); "unverified" = could not prove. */
@@ -149,17 +152,37 @@ interface OfferFillCheck {
149
152
  /** Minted by cairnx-core's `provenOfferTerms`, so it can be passed back to `bindOfferTerms` without the opt-out. */
150
153
  terms?: MintedProvenOfferTerms;
151
154
  blockHeight?: number;
155
+ /** The proven Propose's `expires_epoch`. */
156
+ expiresEpoch?: number;
157
+ /** The first block height at which CairnX treats the offer as expired (cairnx-core `offerExpiryHeightOf` over the
158
+ * proven expiry and anchor). A fill must be mined below it; with `me` and `tip` a fill with less than
159
+ * cairnx-core's `FILL_TIP_MARGIN` blocks of cushion is refused, without them compare it with your tip. */
160
+ expiresAt?: number;
152
161
  /** B7b (REBIND W3/M1): the PROVEN CSD outputs to build for `pay` (a WHOLE fill of a non-partial offer),
153
162
  * sized from the merkle-proven offer by the single-sourced `fillOutputPlan`. Present only when `pay` was
154
- * passed, the served offer carried a `status` and the offer could be sized. Build EXACTLY these; never the
155
- * resolver-served outputs a lying resolver could redirect. A partial (min-bearing) offer is left unsized
156
- * (its running paid/delivered is not provable at this corroboration layer; the wallet's fill-SPV is the
157
- * payment-grade sizer). Liveness is never proven here: the plan is sized only for the status the resolver
158
- * served, so an offer with no served status gets no plan. */
163
+ * passed, the served offer carried a `status`, the offer could be sized, and neither the served status, the
164
+ * endorsement nor the expiry cushion refuses the fill (other refusals keep the proven plan; branch on `ok`).
165
+ * Build EXACTLY these; never the resolver-served outputs a lying resolver could redirect. A partial
166
+ * (min-bearing) offer is left unsized (its running paid/delivered is not provable at this corroboration layer;
167
+ * the wallet's fill-SPV is the payment-grade sizer). Liveness is never proven here: the plan is sized only
168
+ * for the status the resolver served, so an offer with no served status gets no plan. */
159
169
  outputPlan?: {
160
170
  to: string;
161
171
  value: bigint;
162
172
  }[];
173
+ /** Present when `pay` was passed: `true` when `outputPlan` holds the proven outputs for this pay, `false` when
174
+ * the fill could not be sized from proven data (`planReason` says why). `ok: true` with `sized: false` is a
175
+ * verdict on the offer record, not on this fill. */
176
+ sized?: boolean;
177
+ /** Present when `me` and `tip` were passed: cairnx-core's `fillEndorsement` over the served state after the
178
+ * binds. "refused" answers `ok: false`; "not-endorsable" (a token-priced want) is not a refusal. */
179
+ endorsement?: {
180
+ verdict: "endorsed" | "refused" | "not-endorsable";
181
+ reason: string;
182
+ };
183
+ /** Present with `endorsement`: the proposal id the fill's attest must target at `tip` (cairnx-core
184
+ * `fillTargetId`: the offer id, or the fclaim txid while a live reservation routes the offer). */
185
+ fillTarget?: string;
163
186
  /** Present when `plannedOutputs` was passed: `true` when they were bound to `outputPlan` (a mismatch
164
187
  * refuses with `ok: false`), `false` when there was no proven plan to bind them to (`planReason` says why).
165
188
  * `ok: true` with `planBound: false` means your planned outputs were NOT checked. */
@@ -174,7 +197,7 @@ interface OfferFillCheck {
174
197
  * for the prevout-owner author bind. Pass `servedOffer` (the resolver's offer object) to also bind its
175
198
  * payto/seller/terms to the proven values. The FULL object is required: a served offer missing want.payto
176
199
  * or seller is refused (P75-5). A token-priced offer's served want.ticker/want.amount are bound verbatim to
177
- * the record. See the module header for the trust posture.
200
+ * the record. A served status other than open refuses. See the module header for the trust posture.
178
201
  */
179
202
  declare function preverifyOffer(opts: {
180
203
  light: {
@@ -188,17 +211,27 @@ declare function preverifyOffer(opts: {
188
211
  /** B7b (REBIND W3/M1): the CSD base units you intend to pay. When given, the result carries `outputPlan`
189
212
  * (the PROVEN CSD outputs to build for a WHOLE fill of a non-partial offer, sized by the single-sourced
190
213
  * `fillOutputPlan`) and, if you also pass `plannedOutputs`, binds them EXACTLY to that proven plan.
191
- * A partial (min-bearing) offer is left unsized here; the wallet's fill-SPV is its payment-grade sizer. */
214
+ * A partial (min-bearing) offer is left unsized here (`sized: false`); with the served offer, a pay its
215
+ * served progress says delivers nothing (below the minimum) is refused. The wallet's fill-SPV sizes it. */
192
216
  pay?: bigint | string | number;
193
217
  /** B7b: your planned per-address CSD sums (the outputs you WILL build), bound both ways to `outputPlan`.
194
218
  * Requires `pay`. A missing/low leg is a doomed underpay; an extra/high leg is a W3 overpay or an N26
195
- * smuggled output - both refused. */
219
+ * smuggled output - both refused. On a token-priced offer any positive CSD leg is refused. */
196
220
  plannedOutputs?: Record<string, bigint | string | number>;
221
+ /** Your address, the one that signs the fill. With `tip` (and `pay`), the result carries cairnx-core's
222
+ * `fillEndorsement` over the served state: a taker bound to another address, an open CSD offer you hold no
223
+ * live claim or reservation on, or an attest target the offer's routing rejects is refused, and so is a fill
224
+ * that cairnx-core's `offerFillTooLate` says is too close to the offer's expiry. */
225
+ me?: string;
226
+ /** The chain tip height you sign at (for example `(await cairn.chain.tip()).height`). Goes with `me`. */
227
+ tip?: number;
228
+ /** The proposal id your attest will target: the offer id, or the fclaim txid while your reservation routes it. */
229
+ fillTargetId?: string;
197
230
  }): Promise<OfferFillCheck>;
198
231
 
199
232
  /** Stable machine-readable error codes (branch on these, not the message text). */
200
- type CairnErrorCode = "NOT_INSTALLED" | "USER_REJECTED" | "WALLET_LOCKED" | "UNSUPPORTED_METHOD" | "HTTP_ERROR" | "CONTENT_VERIFICATION" | "CONTENT_NOT_JSON" | "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" | "OFFER_SOURCE_CONFLICT" | "FILL_LEGACY_SUNSET" | "VERIFY_HORIZON" | "VERIFY_UNREPRESENTABLE" | "WALLET_UNAVAILABLE" | "REVIEW_REQUIRED" | "UNKNOWN";
201
- declare const SDK_VERSION = "0.8.0";
233
+ type CairnErrorCode = "NOT_INSTALLED" | "USER_REJECTED" | "WALLET_LOCKED" | "UNSUPPORTED_METHOD" | "HTTP_ERROR" | "CONTENT_VERIFICATION" | "CONTENT_NOT_JSON" | "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" | "OFFER_SOURCE_CONFLICT" | "FILL_LEGACY_SUNSET" | "VERIFY_HORIZON" | "VERIFY_UNREPRESENTABLE" | "WALLET_UNAVAILABLE" | "REVIEW_REQUIRED" | "SEAL_NOT_MINED" | "RESERVATION_DENIED" | "RPC_BLOCK_MISMATCH" | "WALLET_TIMEOUT" | "UNKNOWN";
234
+ declare const SDK_VERSION = "0.8.2";
202
235
  interface CairnErrorOptions {
203
236
  code?: CairnErrorCode;
204
237
  docsPath?: string;
@@ -218,7 +251,7 @@ declare class CairnError extends Error {
218
251
  /** Relative docs path (under the SDK docs base), if any. */
219
252
  readonly docsPath?: string;
220
253
  /** The cairn-sdk version that produced this error. */
221
- readonly version = "0.8.0";
254
+ readonly version = "0.8.2";
222
255
  /**
223
256
  * Whether a dApp may safely AUTO-retry this failure. `false` for fund-safety refusals and user-intent
224
257
  * declines (the default); `true` only for provably transient infra faults where nothing was signed.
@@ -261,20 +294,28 @@ declare class ContentVerificationError extends CairnError {
261
294
  constructor(expected: string);
262
295
  }
263
296
  /**
264
- * The wallet SUBMITTED but the outcome is AMBIGUOUS: the tx MAY already be in the mempool. Thrown by the
265
- * write wrappers for the nested `SUBMIT_MAYBE_INFLIGHT` (request threw / timed out / 5xx gateway / unreadable
266
- * body) and `SUBMIT_DUPLICATE` (node answered "already present or mempool conflict") codes. It CARRIES the
267
- * locally-computed consensus `txid` so a dApp can RECONCILE (check the explorer / balance) instead of blindly
268
- * re-broadcasting, which would re-invite the double-broadcast this contract exists to prevent (F14). It is NOT
269
- * retryable (`retryable === false`); `maybeSent === true` distinguishes it from a definitely-not-sent terminal
270
- * refusal. See WALLET-ERROR-CODES.md in the cairn-wallet repo.
297
+ * The outcome of a write is AMBIGUOUS: the tx MAY already be in the mempool. Thrown by the write wrappers for
298
+ * the nested `SUBMIT_MAYBE_INFLIGHT` (request threw / timed out / 5xx gateway / unreadable body) and
299
+ * `SUBMIT_DUPLICATE` (node answered "already present or mempool conflict") codes, for `WALLET_UNAVAILABLE` on a
300
+ * write (the page lost the wallet's reply, which may come after the submit), for `WALLET_TIMEOUT` (the call
301
+ * outlasted the connection timeout while the request stays open in the wallet), and by `Chain.send` when the
302
+ * submit request fails after signing without a 4xx answer (a 4xx is `SUBMIT_REJECTED`). It CARRIES the
303
+ * locally-computed consensus `txid` when one is known so a dApp can RECONCILE (check the explorer / balance)
304
+ * instead of blindly re-broadcasting, which would re-invite the double-broadcast this contract exists to prevent
305
+ * (F14). It is NOT retryable (`retryable === false`);
306
+ * `maybeSent === true` distinguishes it from a definitely-not-sent terminal refusal. See WALLET-ERROR-CODES.md
307
+ * in the cairn-wallet repo.
271
308
  */
272
309
  declare class SubmitInFlightError extends CairnError {
273
- /** The locally-computed consensus txid to reconcile against the chain (may be undefined if the wallet omitted it). */
310
+ /** The locally-computed consensus txid to reconcile against the chain (undefined when it is not known). */
274
311
  readonly txid?: string;
275
- /** Always true: the tx MAY be in the mempool. Reconcile via `txid`; never blind-retry. */
312
+ /** The signed transaction, set only when `Chain.send` threw this (a wallet write keeps it in the wallet).
313
+ * `chain.submit(nodeJson)` resends it unchanged: the same coins under the same txid, so it cannot pay twice,
314
+ * where a new send may select other coins. */
315
+ readonly nodeJson?: NodeTxJson;
316
+ /** Always true: the tx MAY be in the mempool. Reconcile via `txid` or the balance; never blind-retry. */
276
317
  readonly maybeSent = true;
277
- constructor(message: string, code: "SUBMIT_MAYBE_INFLIGHT" | "SUBMIT_DUPLICATE", txid?: string);
318
+ constructor(message: string, code: "SUBMIT_MAYBE_INFLIGHT" | "SUBMIT_DUPLICATE" | "WALLET_UNAVAILABLE" | "WALLET_TIMEOUT", txid?: string, cause?: unknown, nodeJson?: NodeTxJson);
278
319
  }
279
320
  declare function mapProviderError(error: string, code?: string): CairnError;
280
321
  /**
@@ -282,9 +323,10 @@ declare function mapProviderError(error: string, code?: string): CairnError;
282
323
  * onto a typed error the write wrappers THROW. Without this a fund-safety refusal would RESOLVE with a phantom
283
324
  * `txid:undefined`, so a dApp doing `const { txid } = await fillOffer(...); markPaid(txid)` marks a REFUSED
284
325
  * fill as paid. Three buckets:
285
- * - AMBIGUOUS-INFLIGHT (`SUBMIT_MAYBE_INFLIGHT` / `SUBMIT_DUPLICATE`): a `SubmitInFlightError` that CARRIES
286
- * the locally-computed `txid` to reconcile (never blind-retry).
287
- * - TRANSIENT (`VERIFY_UNAVAILABLE` / `OFFER_UNKNOWN`): `retryable === true` (nothing signed; retry shortly).
326
+ * - AMBIGUOUS-INFLIGHT (`SUBMIT_MAYBE_INFLIGHT` / `SUBMIT_DUPLICATE` / `WALLET_UNAVAILABLE`): a
327
+ * `SubmitInFlightError` that CARRIES the locally-computed `txid` when given, to reconcile (never blind-retry).
328
+ * - TRANSIENT (`VERIFY_UNAVAILABLE` / `OFFER_UNKNOWN` / `REVIEW_REQUIRED`): `retryable === true` (nothing sent;
329
+ * retry shortly).
288
330
  * - DEFINITIVE fund-safety refusal (everything else, incl. an unknown / absent code): `retryable === false`.
289
331
  * Fail-closed: an explicit `ok:false` with no recognized code still throws a non-retryable refusal.
290
332
  */
@@ -350,6 +392,7 @@ declare class Cairn {
350
392
  private readonly cairnHttp;
351
393
  private _spvLight;
352
394
  private _spvSeedInflight;
395
+ private _spvReseedInflight;
353
396
  private readonly _spvCp;
354
397
  private readonly _cairnBase;
355
398
  constructor(config?: CairnConfig);
@@ -386,11 +429,7 @@ declare class Cairn {
386
429
  * data-availability hiccup; back off (honouring Retry-After) a bounded number of times, then THROW so
387
430
  * `sync()` fails closed. A non-dense range is rejected terminally (never retried).
388
431
  *
389
- * B7d / F6 DEPENDENCY: `/api/headers` serves no Access-Control-Allow-Origin today (`corsPublic` is
390
- * mounted on `/explorer/api`, `/trade/api` and `/api/rpc`, not here). A SAME-ORIGIN dApp (hosted on the
391
- * cairn base) reaches it now; a CROSS-ORIGIN third-party dApp cannot until B7d adds the CORS mount and
392
- * restarts the cairn service. This code is correct as written; it becomes reachable cross-origin only
393
- * once B7d ships. This is a recorded dependency, not a blocker for authoring/testing.
432
+ * Cross-origin reach depends on the server's CORS mount on `/api/headers` (the cairn server mounts one).
394
433
  */
395
434
  private fetchHeadersBatch;
396
435
  /**
@@ -399,7 +438,8 @@ declare class Cairn {
399
438
  * commitment, derives the payment recipient + seller from the offer's on-chain AUTHOR (the funding input's
400
439
  * prevout owner, txid-committed, NOT the malleable scriptSig), and, if you pass the resolver-SERVED `offer`
401
440
  * object, binds its payto/seller + fee/rebate/partial terms to the proven ones. Returns a TRUST-LABELED result
402
- * (fail-closed on a positive mismatch, fail-soft `transient` on a lagging / below-checkpoint chain view).
441
+ * (fail-closed on a positive mismatch; `transient` on a lagging chain view; final, naming the anchor, for an
442
+ * offer mined below the SPV checkpoint).
403
443
  *
404
444
  * This is best-effort corroboration, NOT the payment-grade boundary: the Cairn Wallet's OWN on-device
405
445
  * fill-SPV (0.2.60+, fails-closed before signing) is that. A dApp settling real value should still clear this
@@ -407,11 +447,16 @@ declare class Cairn {
407
447
  *
408
448
  * Pass opts.pay (the CSD base units you intend to pay) to receive the PROVEN outputPlan for a whole fill of a
409
449
  * non-partial offer, and opts.plannedOutputs to bind your planned per-address sums to it before signing; build
410
- * EXACTLY the outputPlan, never resolver-served outputs.
450
+ * EXACTLY the outputPlan, never resolver-served outputs. Pass opts.me (your address) and opts.tip (the chain tip
451
+ * height) with pay to have cairnx-core's fillEndorsement and offerFillTooLate judge the fill for you at that tip; without them `ok`
452
+ * says the offer record is proven, not that you can fill it now.
411
453
  */
412
454
  verifyOfferForFill(offerId: string, servedOffer?: unknown, opts?: {
413
455
  pay?: bigint | string | number;
414
456
  plannedOutputs?: Record<string, bigint | string | number>;
457
+ me?: string;
458
+ tip?: number;
459
+ fillTargetId?: string;
415
460
  }): Promise<OfferFillCheck>;
416
461
  /**
417
462
  * Detect + connect the Cairn Wallet (browser only) and attach it as `cairn.wallet`.
package/dist/index.d.ts CHANGED
@@ -11,8 +11,8 @@ import { WalletConnection, DetectOptions } from './connect.js';
11
11
  export { AttestParams, CairnProvider, ConnectResult, FillParams, ProposeParams, ProviderDetail, ProviderInfo, SealClaimParams, SendParams, SignInResult, SiwcParams, SiwcResult, TxResult, WalletCapabilities, WalletPermission, connect, detectProvider, discoverProviders, getWallet, isInstalled } from './connect.js';
12
12
  import { InclusionResult } from '@inversealtruism/csd-light';
13
13
  export { CairnController, CairnControllerOptions, CairnState } from './controller.js';
14
+ import { NodeTxJson } from '@inversealtruism/csd-tx';
14
15
  import '@inversealtruism/csd-client';
15
- import '@inversealtruism/csd-tx';
16
16
  import '@inversealtruism/csd-codec';
17
17
  import '@inversealtruism/csd-crypto';
18
18
 
@@ -138,6 +138,9 @@ declare class NamesClient {
138
138
  /** The trust-labeled verdict. `ok:false` + `transient:true` = a retryable chain-catching-up soft-fail (NOT a
139
139
  * proven mismatch). `payto`/`seller`/`terms` are populated whenever they could be merkle-derived. */
140
140
  interface OfferFillCheck {
141
+ /** `true`: the offer record is proven and no check refused. Without `me` and `tip` it does not say the offer
142
+ * is fillable by you now (it may be bound to another taker, held by someone else's live claim or
143
+ * reservation, or too close to its expiry), and with `sized: false` it does not size your fill. */
141
144
  ok: boolean;
142
145
  /** "verified" = merkle-proven inclusion + on-chain author; "proof-consistent" = a proof that folds but the
143
146
  * header could not be PoW-verified (indexer-trusted); "unverified" = could not prove. */
@@ -149,17 +152,37 @@ interface OfferFillCheck {
149
152
  /** Minted by cairnx-core's `provenOfferTerms`, so it can be passed back to `bindOfferTerms` without the opt-out. */
150
153
  terms?: MintedProvenOfferTerms;
151
154
  blockHeight?: number;
155
+ /** The proven Propose's `expires_epoch`. */
156
+ expiresEpoch?: number;
157
+ /** The first block height at which CairnX treats the offer as expired (cairnx-core `offerExpiryHeightOf` over the
158
+ * proven expiry and anchor). A fill must be mined below it; with `me` and `tip` a fill with less than
159
+ * cairnx-core's `FILL_TIP_MARGIN` blocks of cushion is refused, without them compare it with your tip. */
160
+ expiresAt?: number;
152
161
  /** B7b (REBIND W3/M1): the PROVEN CSD outputs to build for `pay` (a WHOLE fill of a non-partial offer),
153
162
  * sized from the merkle-proven offer by the single-sourced `fillOutputPlan`. Present only when `pay` was
154
- * passed, the served offer carried a `status` and the offer could be sized. Build EXACTLY these; never the
155
- * resolver-served outputs a lying resolver could redirect. A partial (min-bearing) offer is left unsized
156
- * (its running paid/delivered is not provable at this corroboration layer; the wallet's fill-SPV is the
157
- * payment-grade sizer). Liveness is never proven here: the plan is sized only for the status the resolver
158
- * served, so an offer with no served status gets no plan. */
163
+ * passed, the served offer carried a `status`, the offer could be sized, and neither the served status, the
164
+ * endorsement nor the expiry cushion refuses the fill (other refusals keep the proven plan; branch on `ok`).
165
+ * Build EXACTLY these; never the resolver-served outputs a lying resolver could redirect. A partial
166
+ * (min-bearing) offer is left unsized (its running paid/delivered is not provable at this corroboration layer;
167
+ * the wallet's fill-SPV is the payment-grade sizer). Liveness is never proven here: the plan is sized only
168
+ * for the status the resolver served, so an offer with no served status gets no plan. */
159
169
  outputPlan?: {
160
170
  to: string;
161
171
  value: bigint;
162
172
  }[];
173
+ /** Present when `pay` was passed: `true` when `outputPlan` holds the proven outputs for this pay, `false` when
174
+ * the fill could not be sized from proven data (`planReason` says why). `ok: true` with `sized: false` is a
175
+ * verdict on the offer record, not on this fill. */
176
+ sized?: boolean;
177
+ /** Present when `me` and `tip` were passed: cairnx-core's `fillEndorsement` over the served state after the
178
+ * binds. "refused" answers `ok: false`; "not-endorsable" (a token-priced want) is not a refusal. */
179
+ endorsement?: {
180
+ verdict: "endorsed" | "refused" | "not-endorsable";
181
+ reason: string;
182
+ };
183
+ /** Present with `endorsement`: the proposal id the fill's attest must target at `tip` (cairnx-core
184
+ * `fillTargetId`: the offer id, or the fclaim txid while a live reservation routes the offer). */
185
+ fillTarget?: string;
163
186
  /** Present when `plannedOutputs` was passed: `true` when they were bound to `outputPlan` (a mismatch
164
187
  * refuses with `ok: false`), `false` when there was no proven plan to bind them to (`planReason` says why).
165
188
  * `ok: true` with `planBound: false` means your planned outputs were NOT checked. */
@@ -174,7 +197,7 @@ interface OfferFillCheck {
174
197
  * for the prevout-owner author bind. Pass `servedOffer` (the resolver's offer object) to also bind its
175
198
  * payto/seller/terms to the proven values. The FULL object is required: a served offer missing want.payto
176
199
  * or seller is refused (P75-5). A token-priced offer's served want.ticker/want.amount are bound verbatim to
177
- * the record. See the module header for the trust posture.
200
+ * the record. A served status other than open refuses. See the module header for the trust posture.
178
201
  */
179
202
  declare function preverifyOffer(opts: {
180
203
  light: {
@@ -188,17 +211,27 @@ declare function preverifyOffer(opts: {
188
211
  /** B7b (REBIND W3/M1): the CSD base units you intend to pay. When given, the result carries `outputPlan`
189
212
  * (the PROVEN CSD outputs to build for a WHOLE fill of a non-partial offer, sized by the single-sourced
190
213
  * `fillOutputPlan`) and, if you also pass `plannedOutputs`, binds them EXACTLY to that proven plan.
191
- * A partial (min-bearing) offer is left unsized here; the wallet's fill-SPV is its payment-grade sizer. */
214
+ * A partial (min-bearing) offer is left unsized here (`sized: false`); with the served offer, a pay its
215
+ * served progress says delivers nothing (below the minimum) is refused. The wallet's fill-SPV sizes it. */
192
216
  pay?: bigint | string | number;
193
217
  /** B7b: your planned per-address CSD sums (the outputs you WILL build), bound both ways to `outputPlan`.
194
218
  * Requires `pay`. A missing/low leg is a doomed underpay; an extra/high leg is a W3 overpay or an N26
195
- * smuggled output - both refused. */
219
+ * smuggled output - both refused. On a token-priced offer any positive CSD leg is refused. */
196
220
  plannedOutputs?: Record<string, bigint | string | number>;
221
+ /** Your address, the one that signs the fill. With `tip` (and `pay`), the result carries cairnx-core's
222
+ * `fillEndorsement` over the served state: a taker bound to another address, an open CSD offer you hold no
223
+ * live claim or reservation on, or an attest target the offer's routing rejects is refused, and so is a fill
224
+ * that cairnx-core's `offerFillTooLate` says is too close to the offer's expiry. */
225
+ me?: string;
226
+ /** The chain tip height you sign at (for example `(await cairn.chain.tip()).height`). Goes with `me`. */
227
+ tip?: number;
228
+ /** The proposal id your attest will target: the offer id, or the fclaim txid while your reservation routes it. */
229
+ fillTargetId?: string;
197
230
  }): Promise<OfferFillCheck>;
198
231
 
199
232
  /** Stable machine-readable error codes (branch on these, not the message text). */
200
- type CairnErrorCode = "NOT_INSTALLED" | "USER_REJECTED" | "WALLET_LOCKED" | "UNSUPPORTED_METHOD" | "HTTP_ERROR" | "CONTENT_VERIFICATION" | "CONTENT_NOT_JSON" | "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" | "OFFER_SOURCE_CONFLICT" | "FILL_LEGACY_SUNSET" | "VERIFY_HORIZON" | "VERIFY_UNREPRESENTABLE" | "WALLET_UNAVAILABLE" | "REVIEW_REQUIRED" | "UNKNOWN";
201
- declare const SDK_VERSION = "0.8.0";
233
+ type CairnErrorCode = "NOT_INSTALLED" | "USER_REJECTED" | "WALLET_LOCKED" | "UNSUPPORTED_METHOD" | "HTTP_ERROR" | "CONTENT_VERIFICATION" | "CONTENT_NOT_JSON" | "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" | "OFFER_SOURCE_CONFLICT" | "FILL_LEGACY_SUNSET" | "VERIFY_HORIZON" | "VERIFY_UNREPRESENTABLE" | "WALLET_UNAVAILABLE" | "REVIEW_REQUIRED" | "SEAL_NOT_MINED" | "RESERVATION_DENIED" | "RPC_BLOCK_MISMATCH" | "WALLET_TIMEOUT" | "UNKNOWN";
234
+ declare const SDK_VERSION = "0.8.2";
202
235
  interface CairnErrorOptions {
203
236
  code?: CairnErrorCode;
204
237
  docsPath?: string;
@@ -218,7 +251,7 @@ declare class CairnError extends Error {
218
251
  /** Relative docs path (under the SDK docs base), if any. */
219
252
  readonly docsPath?: string;
220
253
  /** The cairn-sdk version that produced this error. */
221
- readonly version = "0.8.0";
254
+ readonly version = "0.8.2";
222
255
  /**
223
256
  * Whether a dApp may safely AUTO-retry this failure. `false` for fund-safety refusals and user-intent
224
257
  * declines (the default); `true` only for provably transient infra faults where nothing was signed.
@@ -261,20 +294,28 @@ declare class ContentVerificationError extends CairnError {
261
294
  constructor(expected: string);
262
295
  }
263
296
  /**
264
- * The wallet SUBMITTED but the outcome is AMBIGUOUS: the tx MAY already be in the mempool. Thrown by the
265
- * write wrappers for the nested `SUBMIT_MAYBE_INFLIGHT` (request threw / timed out / 5xx gateway / unreadable
266
- * body) and `SUBMIT_DUPLICATE` (node answered "already present or mempool conflict") codes. It CARRIES the
267
- * locally-computed consensus `txid` so a dApp can RECONCILE (check the explorer / balance) instead of blindly
268
- * re-broadcasting, which would re-invite the double-broadcast this contract exists to prevent (F14). It is NOT
269
- * retryable (`retryable === false`); `maybeSent === true` distinguishes it from a definitely-not-sent terminal
270
- * refusal. See WALLET-ERROR-CODES.md in the cairn-wallet repo.
297
+ * The outcome of a write is AMBIGUOUS: the tx MAY already be in the mempool. Thrown by the write wrappers for
298
+ * the nested `SUBMIT_MAYBE_INFLIGHT` (request threw / timed out / 5xx gateway / unreadable body) and
299
+ * `SUBMIT_DUPLICATE` (node answered "already present or mempool conflict") codes, for `WALLET_UNAVAILABLE` on a
300
+ * write (the page lost the wallet's reply, which may come after the submit), for `WALLET_TIMEOUT` (the call
301
+ * outlasted the connection timeout while the request stays open in the wallet), and by `Chain.send` when the
302
+ * submit request fails after signing without a 4xx answer (a 4xx is `SUBMIT_REJECTED`). It CARRIES the
303
+ * locally-computed consensus `txid` when one is known so a dApp can RECONCILE (check the explorer / balance)
304
+ * instead of blindly re-broadcasting, which would re-invite the double-broadcast this contract exists to prevent
305
+ * (F14). It is NOT retryable (`retryable === false`);
306
+ * `maybeSent === true` distinguishes it from a definitely-not-sent terminal refusal. See WALLET-ERROR-CODES.md
307
+ * in the cairn-wallet repo.
271
308
  */
272
309
  declare class SubmitInFlightError extends CairnError {
273
- /** The locally-computed consensus txid to reconcile against the chain (may be undefined if the wallet omitted it). */
310
+ /** The locally-computed consensus txid to reconcile against the chain (undefined when it is not known). */
274
311
  readonly txid?: string;
275
- /** Always true: the tx MAY be in the mempool. Reconcile via `txid`; never blind-retry. */
312
+ /** The signed transaction, set only when `Chain.send` threw this (a wallet write keeps it in the wallet).
313
+ * `chain.submit(nodeJson)` resends it unchanged: the same coins under the same txid, so it cannot pay twice,
314
+ * where a new send may select other coins. */
315
+ readonly nodeJson?: NodeTxJson;
316
+ /** Always true: the tx MAY be in the mempool. Reconcile via `txid` or the balance; never blind-retry. */
276
317
  readonly maybeSent = true;
277
- constructor(message: string, code: "SUBMIT_MAYBE_INFLIGHT" | "SUBMIT_DUPLICATE", txid?: string);
318
+ constructor(message: string, code: "SUBMIT_MAYBE_INFLIGHT" | "SUBMIT_DUPLICATE" | "WALLET_UNAVAILABLE" | "WALLET_TIMEOUT", txid?: string, cause?: unknown, nodeJson?: NodeTxJson);
278
319
  }
279
320
  declare function mapProviderError(error: string, code?: string): CairnError;
280
321
  /**
@@ -282,9 +323,10 @@ declare function mapProviderError(error: string, code?: string): CairnError;
282
323
  * onto a typed error the write wrappers THROW. Without this a fund-safety refusal would RESOLVE with a phantom
283
324
  * `txid:undefined`, so a dApp doing `const { txid } = await fillOffer(...); markPaid(txid)` marks a REFUSED
284
325
  * fill as paid. Three buckets:
285
- * - AMBIGUOUS-INFLIGHT (`SUBMIT_MAYBE_INFLIGHT` / `SUBMIT_DUPLICATE`): a `SubmitInFlightError` that CARRIES
286
- * the locally-computed `txid` to reconcile (never blind-retry).
287
- * - TRANSIENT (`VERIFY_UNAVAILABLE` / `OFFER_UNKNOWN`): `retryable === true` (nothing signed; retry shortly).
326
+ * - AMBIGUOUS-INFLIGHT (`SUBMIT_MAYBE_INFLIGHT` / `SUBMIT_DUPLICATE` / `WALLET_UNAVAILABLE`): a
327
+ * `SubmitInFlightError` that CARRIES the locally-computed `txid` when given, to reconcile (never blind-retry).
328
+ * - TRANSIENT (`VERIFY_UNAVAILABLE` / `OFFER_UNKNOWN` / `REVIEW_REQUIRED`): `retryable === true` (nothing sent;
329
+ * retry shortly).
288
330
  * - DEFINITIVE fund-safety refusal (everything else, incl. an unknown / absent code): `retryable === false`.
289
331
  * Fail-closed: an explicit `ok:false` with no recognized code still throws a non-retryable refusal.
290
332
  */
@@ -350,6 +392,7 @@ declare class Cairn {
350
392
  private readonly cairnHttp;
351
393
  private _spvLight;
352
394
  private _spvSeedInflight;
395
+ private _spvReseedInflight;
353
396
  private readonly _spvCp;
354
397
  private readonly _cairnBase;
355
398
  constructor(config?: CairnConfig);
@@ -386,11 +429,7 @@ declare class Cairn {
386
429
  * data-availability hiccup; back off (honouring Retry-After) a bounded number of times, then THROW so
387
430
  * `sync()` fails closed. A non-dense range is rejected terminally (never retried).
388
431
  *
389
- * B7d / F6 DEPENDENCY: `/api/headers` serves no Access-Control-Allow-Origin today (`corsPublic` is
390
- * mounted on `/explorer/api`, `/trade/api` and `/api/rpc`, not here). A SAME-ORIGIN dApp (hosted on the
391
- * cairn base) reaches it now; a CROSS-ORIGIN third-party dApp cannot until B7d adds the CORS mount and
392
- * restarts the cairn service. This code is correct as written; it becomes reachable cross-origin only
393
- * once B7d ships. This is a recorded dependency, not a blocker for authoring/testing.
432
+ * Cross-origin reach depends on the server's CORS mount on `/api/headers` (the cairn server mounts one).
394
433
  */
395
434
  private fetchHeadersBatch;
396
435
  /**
@@ -399,7 +438,8 @@ declare class Cairn {
399
438
  * commitment, derives the payment recipient + seller from the offer's on-chain AUTHOR (the funding input's
400
439
  * prevout owner, txid-committed, NOT the malleable scriptSig), and, if you pass the resolver-SERVED `offer`
401
440
  * object, binds its payto/seller + fee/rebate/partial terms to the proven ones. Returns a TRUST-LABELED result
402
- * (fail-closed on a positive mismatch, fail-soft `transient` on a lagging / below-checkpoint chain view).
441
+ * (fail-closed on a positive mismatch; `transient` on a lagging chain view; final, naming the anchor, for an
442
+ * offer mined below the SPV checkpoint).
403
443
  *
404
444
  * This is best-effort corroboration, NOT the payment-grade boundary: the Cairn Wallet's OWN on-device
405
445
  * fill-SPV (0.2.60+, fails-closed before signing) is that. A dApp settling real value should still clear this
@@ -407,11 +447,16 @@ declare class Cairn {
407
447
  *
408
448
  * Pass opts.pay (the CSD base units you intend to pay) to receive the PROVEN outputPlan for a whole fill of a
409
449
  * non-partial offer, and opts.plannedOutputs to bind your planned per-address sums to it before signing; build
410
- * EXACTLY the outputPlan, never resolver-served outputs.
450
+ * EXACTLY the outputPlan, never resolver-served outputs. Pass opts.me (your address) and opts.tip (the chain tip
451
+ * height) with pay to have cairnx-core's fillEndorsement and offerFillTooLate judge the fill for you at that tip; without them `ok`
452
+ * says the offer record is proven, not that you can fill it now.
411
453
  */
412
454
  verifyOfferForFill(offerId: string, servedOffer?: unknown, opts?: {
413
455
  pay?: bigint | string | number;
414
456
  plannedOutputs?: Record<string, bigint | string | number>;
457
+ me?: string;
458
+ tip?: number;
459
+ fillTargetId?: string;
415
460
  }): Promise<OfferFillCheck>;
416
461
  /**
417
462
  * Detect + connect the Cairn Wallet (browser only) and attach it as `cairn.wallet`.