@inversealtruism/cairn-sdk 0.2.2 → 0.3.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 +8 -1
- package/dist/board.js +2 -2
- package/dist/{chunk-3EXZQPPT.js → chunk-4RDXEW35.js} +1 -1
- package/dist/{chunk-ZJURNM5P.js → chunk-E2Z3XSCW.js} +50 -1
- package/dist/{chunk-HI5STMH3.js → chunk-NLCF5T2F.js} +1 -1
- package/dist/{chunk-QRVMYGSL.js → chunk-QV2GIXO7.js} +23 -9
- package/dist/{chunk-LCE4IWMW.js → chunk-UMXLWUEF.js} +1 -1
- package/dist/connect.cjs +68 -8
- package/dist/connect.d.cts +17 -2
- package/dist/connect.d.ts +17 -2
- package/dist/connect.js +2 -2
- package/dist/content.cjs +8 -1
- package/dist/content.js +2 -2
- package/dist/controller.cjs +68 -8
- package/dist/controller.js +3 -3
- package/dist/index.cjs +220 -29
- package/dist/index.d.cts +111 -5
- package/dist/index.d.ts +111 -5
- package/dist/index.js +141 -13
- package/dist/react.cjs +68 -8
- package/dist/react.js +3 -3
- package/package.json +25 -18
package/dist/index.d.cts
CHANGED
|
@@ -10,12 +10,12 @@ 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 { InclusionResult } from '@inversealtruism/csd-light';
|
|
13
14
|
export { CairnController, CairnControllerOptions, CairnState } from './controller.cjs';
|
|
14
15
|
import '@inversealtruism/csd-client';
|
|
15
16
|
import '@inversealtruism/csd-tx';
|
|
16
17
|
import '@inversealtruism/csd-codec';
|
|
17
18
|
import '@inversealtruism/csd-crypto';
|
|
18
|
-
import '@inversealtruism/csd-light';
|
|
19
19
|
import '@inversealtruism/csd-registry';
|
|
20
20
|
|
|
21
21
|
/** `/cairnx/resolve/:name` — the display-path name→address resolution. */
|
|
@@ -104,13 +104,64 @@ declare class NamesClient {
|
|
|
104
104
|
state(): Promise<unknown>;
|
|
105
105
|
}
|
|
106
106
|
|
|
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
|
+
/** The trust-labeled verdict. `ok:false` + `transient:true` = a retryable chain-catching-up soft-fail (NOT a
|
|
117
|
+
* proven mismatch). `payto`/`seller`/`terms` are populated whenever they could be merkle-derived. */
|
|
118
|
+
interface OfferFillCheck {
|
|
119
|
+
ok: boolean;
|
|
120
|
+
/** "verified" = merkle-proven inclusion + on-chain author; "proof-consistent" = a proof that folds but the
|
|
121
|
+
* header could not be PoW-verified (indexer-trusted); "unverified" = could not prove. */
|
|
122
|
+
trust: "verified" | "proof-consistent" | "unverified";
|
|
123
|
+
reason?: string;
|
|
124
|
+
transient?: boolean;
|
|
125
|
+
payto?: string;
|
|
126
|
+
seller?: string;
|
|
127
|
+
terms?: ProvenOfferTerms;
|
|
128
|
+
blockHeight?: number;
|
|
129
|
+
}
|
|
130
|
+
/** The treasury fee rate stamped on an offer at its creation height (resolve.ts: v11 ? (v16 ? 150 : 100) : 0). */
|
|
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;
|
|
135
|
+
/**
|
|
136
|
+
* Pre-verify an offer on-chain before building a `fillOffer` payment. `light` is a PoW-verifying light client
|
|
137
|
+
* (its `verifyTxInclusion` surfaces the merkle-proven tx + committed appPayloadHash); `client` reads source txs
|
|
138
|
+
* for the prevout-owner author bind. Pass `servedOffer` (the resolver's offer object) to also bind its
|
|
139
|
+
* payto/seller/terms to the proven values. See the module header for the trust posture.
|
|
140
|
+
*/
|
|
141
|
+
declare function preverifyOffer(opts: {
|
|
142
|
+
light: {
|
|
143
|
+
verifyTxInclusion(txidHex: string): Promise<InclusionResult>;
|
|
144
|
+
};
|
|
145
|
+
client: {
|
|
146
|
+
tx(id: string): Promise<unknown>;
|
|
147
|
+
};
|
|
148
|
+
offerId: string;
|
|
149
|
+
servedOffer?: unknown;
|
|
150
|
+
}): Promise<OfferFillCheck>;
|
|
151
|
+
|
|
107
152
|
/** Stable machine-readable error codes (branch on these, not the message text). */
|
|
108
|
-
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" | "UNKNOWN";
|
|
109
|
-
declare const SDK_VERSION = "0.
|
|
153
|
+
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.3.0";
|
|
110
155
|
interface CairnErrorOptions {
|
|
111
156
|
code?: CairnErrorCode;
|
|
112
157
|
docsPath?: string;
|
|
113
158
|
cause?: unknown;
|
|
159
|
+
/**
|
|
160
|
+
* Whether a dApp may safely AUTO-retry this failure without fresh user intent. Transient infra faults
|
|
161
|
+
* (e.g. a node that was briefly unreachable, nothing signed) are `true`; fund-safety refusals are `false`.
|
|
162
|
+
* Defaults to `false` (fail-closed: never invite a retry unless the error is provably transient).
|
|
163
|
+
*/
|
|
164
|
+
retryable?: boolean;
|
|
114
165
|
}
|
|
115
166
|
declare class CairnError extends Error {
|
|
116
167
|
/** Stable machine code for programmatic branching. */
|
|
@@ -120,7 +171,13 @@ declare class CairnError extends Error {
|
|
|
120
171
|
/** Relative docs path (under the SDK docs base), if any. */
|
|
121
172
|
readonly docsPath?: string;
|
|
122
173
|
/** The cairn-sdk version that produced this error. */
|
|
123
|
-
readonly version = "0.
|
|
174
|
+
readonly version = "0.3.0";
|
|
175
|
+
/**
|
|
176
|
+
* Whether a dApp may safely AUTO-retry this failure. `false` for fund-safety refusals and user-intent
|
|
177
|
+
* declines (the default); `true` only for provably transient infra faults where nothing was signed.
|
|
178
|
+
* A `SubmitInFlightError` is `false` too: reconcile its `txid`, never blind-retry.
|
|
179
|
+
*/
|
|
180
|
+
readonly retryable: boolean;
|
|
124
181
|
constructor(shortMessage: string, opts?: CairnErrorOptions);
|
|
125
182
|
/**
|
|
126
183
|
* Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
|
|
@@ -156,7 +213,40 @@ declare class ContentVerificationError extends CairnError {
|
|
|
156
213
|
expected: string;
|
|
157
214
|
constructor(expected: string);
|
|
158
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* The wallet SUBMITTED but the outcome is AMBIGUOUS: the tx MAY already be in the mempool. Thrown by the
|
|
218
|
+
* write wrappers for the nested `SUBMIT_MAYBE_INFLIGHT` (request threw / timed out / 5xx gateway / unreadable
|
|
219
|
+
* body) and `SUBMIT_DUPLICATE` (node answered "already present or mempool conflict") codes. It CARRIES the
|
|
220
|
+
* locally-computed consensus `txid` so a dApp can RECONCILE (check the explorer / balance) instead of blindly
|
|
221
|
+
* re-broadcasting, which would re-invite the double-broadcast this contract exists to prevent (F14). It is NOT
|
|
222
|
+
* retryable (`retryable === false`); `maybeSent === true` distinguishes it from a definitely-not-sent terminal
|
|
223
|
+
* refusal. See WALLET-ERROR-CODES.md in the cairn-wallet repo.
|
|
224
|
+
*/
|
|
225
|
+
declare class SubmitInFlightError extends CairnError {
|
|
226
|
+
/** The locally-computed consensus txid to reconcile against the chain (may be undefined if the wallet omitted it). */
|
|
227
|
+
readonly txid?: string;
|
|
228
|
+
/** Always true: the tx MAY be in the mempool. Reconcile via `txid`; never blind-retry. */
|
|
229
|
+
readonly maybeSent = true;
|
|
230
|
+
constructor(message: string, code: "SUBMIT_MAYBE_INFLIGHT" | "SUBMIT_DUPLICATE", txid?: string);
|
|
231
|
+
}
|
|
159
232
|
declare function mapProviderError(error: string, code?: string): CairnError;
|
|
233
|
+
/**
|
|
234
|
+
* F14: map a NESTED SubmitResult refusal (`{ ok:false, code, error, txid }`, ridden inside an outer-ok reply)
|
|
235
|
+
* onto a typed error the write wrappers THROW. Without this a fund-safety refusal would RESOLVE with a phantom
|
|
236
|
+
* `txid:undefined`, so a dApp doing `const { txid } = await fillOffer(...); markPaid(txid)` marks a REFUSED
|
|
237
|
+
* fill as paid. Three buckets:
|
|
238
|
+
* - AMBIGUOUS-INFLIGHT (`SUBMIT_MAYBE_INFLIGHT` / `SUBMIT_DUPLICATE`): a `SubmitInFlightError` that CARRIES
|
|
239
|
+
* the locally-computed `txid` to reconcile (never blind-retry).
|
|
240
|
+
* - TRANSIENT (`VERIFY_UNAVAILABLE` / `OFFER_UNKNOWN`): `retryable === true` (nothing signed; retry shortly).
|
|
241
|
+
* - DEFINITIVE fund-safety refusal (everything else, incl. an unknown / absent code): `retryable === false`.
|
|
242
|
+
* Fail-closed: an explicit `ok:false` with no recognized code still throws a non-retryable refusal.
|
|
243
|
+
*/
|
|
244
|
+
declare function mapSubmitResultError(result: {
|
|
245
|
+
ok?: boolean;
|
|
246
|
+
code?: string;
|
|
247
|
+
error?: string;
|
|
248
|
+
txid?: string;
|
|
249
|
+
}): CairnError;
|
|
160
250
|
/** Convenience: the stable code of any error (or "UNKNOWN" for a non-CairnError). */
|
|
161
251
|
declare function errorCode(err: unknown): CairnErrorCode;
|
|
162
252
|
|
|
@@ -232,6 +322,22 @@ declare class Cairn {
|
|
|
232
322
|
* header chain is cached across calls; the first call near tip syncs the post-checkpoint span.
|
|
233
323
|
*/
|
|
234
324
|
private verifiedHeaderMerkleAt;
|
|
325
|
+
/** The PoW-verifying SPV light client, seeded ONCE from the pinned checkpoint (shared by the M3 header-merkle
|
|
326
|
+
* bind and the F13 offer pre-verify). Reused across calls; the first call near tip syncs the post-checkpoint span. */
|
|
327
|
+
private seededSpvLight;
|
|
328
|
+
/**
|
|
329
|
+
* F13 — corroborate an offer ON-CHAIN before building a `fillOffer` payment's `outputs`. Merkle-proves the
|
|
330
|
+
* offer into the PoW-verified header chain (from the pinned SPV checkpoint), binds the record to its on-chain
|
|
331
|
+
* commitment, derives the payment recipient + seller from the offer's on-chain AUTHOR (the funding input's
|
|
332
|
+
* prevout owner, txid-committed, NOT the malleable scriptSig), and, if you pass the resolver-SERVED `offer`
|
|
333
|
+
* object, binds its payto/seller + fee/rebate/partial terms to the proven ones. Returns a TRUST-LABELED result
|
|
334
|
+
* (fail-closed on a positive mismatch, fail-soft `transient` on a lagging / below-checkpoint chain view).
|
|
335
|
+
*
|
|
336
|
+
* This is best-effort corroboration, NOT the payment-grade boundary: the Cairn Wallet's OWN on-device
|
|
337
|
+
* fill-SPV (0.2.60+, fails-closed before signing) is that. A dApp settling real value should still clear this
|
|
338
|
+
* before building `outputs`, so it never hands the wallet a payment a lying resolver would redirect.
|
|
339
|
+
*/
|
|
340
|
+
verifyOfferForFill(offerId: string, servedOffer?: unknown): Promise<OfferFillCheck>;
|
|
235
341
|
/**
|
|
236
342
|
* Detect + connect the Cairn Wallet (browser only). Wires the wallet into
|
|
237
343
|
* `cairn.board` for signed writes. Resolves to the connected address.
|
|
@@ -243,4 +349,4 @@ declare class Cairn {
|
|
|
243
349
|
get connected(): boolean;
|
|
244
350
|
}
|
|
245
351
|
|
|
246
|
-
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 PrimaryName, RegistryClient, SDK_VERSION, type TokenInfo, UnsupportedMethodError, UserRejectedError, WalletConnection, WalletLockedError, errorCode, mapProviderError };
|
|
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, type ProvenOfferTerms, RegistryClient, SDK_VERSION, SubmitInFlightError, type TokenInfo, UnsupportedMethodError, UserRejectedError, WalletConnection, WalletLockedError, bindOfferTerms, errorCode, feeBpsAt, mapProviderError, mapSubmitResultError, preverifyOffer };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,12 +10,12 @@ export { ContentClientOptions, PreparedContent } from './content.js';
|
|
|
10
10
|
import { RegistryClient } from './registry.js';
|
|
11
11
|
import { WalletConnection, DetectOptions } from './connect.js';
|
|
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.js';
|
|
13
|
+
import { InclusionResult } from '@inversealtruism/csd-light';
|
|
13
14
|
export { CairnController, CairnControllerOptions, CairnState } from './controller.js';
|
|
14
15
|
import '@inversealtruism/csd-client';
|
|
15
16
|
import '@inversealtruism/csd-tx';
|
|
16
17
|
import '@inversealtruism/csd-codec';
|
|
17
18
|
import '@inversealtruism/csd-crypto';
|
|
18
|
-
import '@inversealtruism/csd-light';
|
|
19
19
|
import '@inversealtruism/csd-registry';
|
|
20
20
|
|
|
21
21
|
/** `/cairnx/resolve/:name` — the display-path name→address resolution. */
|
|
@@ -104,13 +104,64 @@ declare class NamesClient {
|
|
|
104
104
|
state(): Promise<unknown>;
|
|
105
105
|
}
|
|
106
106
|
|
|
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
|
+
/** The trust-labeled verdict. `ok:false` + `transient:true` = a retryable chain-catching-up soft-fail (NOT a
|
|
117
|
+
* proven mismatch). `payto`/`seller`/`terms` are populated whenever they could be merkle-derived. */
|
|
118
|
+
interface OfferFillCheck {
|
|
119
|
+
ok: boolean;
|
|
120
|
+
/** "verified" = merkle-proven inclusion + on-chain author; "proof-consistent" = a proof that folds but the
|
|
121
|
+
* header could not be PoW-verified (indexer-trusted); "unverified" = could not prove. */
|
|
122
|
+
trust: "verified" | "proof-consistent" | "unverified";
|
|
123
|
+
reason?: string;
|
|
124
|
+
transient?: boolean;
|
|
125
|
+
payto?: string;
|
|
126
|
+
seller?: string;
|
|
127
|
+
terms?: ProvenOfferTerms;
|
|
128
|
+
blockHeight?: number;
|
|
129
|
+
}
|
|
130
|
+
/** The treasury fee rate stamped on an offer at its creation height (resolve.ts: v11 ? (v16 ? 150 : 100) : 0). */
|
|
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;
|
|
135
|
+
/**
|
|
136
|
+
* Pre-verify an offer on-chain before building a `fillOffer` payment. `light` is a PoW-verifying light client
|
|
137
|
+
* (its `verifyTxInclusion` surfaces the merkle-proven tx + committed appPayloadHash); `client` reads source txs
|
|
138
|
+
* for the prevout-owner author bind. Pass `servedOffer` (the resolver's offer object) to also bind its
|
|
139
|
+
* payto/seller/terms to the proven values. See the module header for the trust posture.
|
|
140
|
+
*/
|
|
141
|
+
declare function preverifyOffer(opts: {
|
|
142
|
+
light: {
|
|
143
|
+
verifyTxInclusion(txidHex: string): Promise<InclusionResult>;
|
|
144
|
+
};
|
|
145
|
+
client: {
|
|
146
|
+
tx(id: string): Promise<unknown>;
|
|
147
|
+
};
|
|
148
|
+
offerId: string;
|
|
149
|
+
servedOffer?: unknown;
|
|
150
|
+
}): Promise<OfferFillCheck>;
|
|
151
|
+
|
|
107
152
|
/** Stable machine-readable error codes (branch on these, not the message text). */
|
|
108
|
-
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" | "UNKNOWN";
|
|
109
|
-
declare const SDK_VERSION = "0.
|
|
153
|
+
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.3.0";
|
|
110
155
|
interface CairnErrorOptions {
|
|
111
156
|
code?: CairnErrorCode;
|
|
112
157
|
docsPath?: string;
|
|
113
158
|
cause?: unknown;
|
|
159
|
+
/**
|
|
160
|
+
* Whether a dApp may safely AUTO-retry this failure without fresh user intent. Transient infra faults
|
|
161
|
+
* (e.g. a node that was briefly unreachable, nothing signed) are `true`; fund-safety refusals are `false`.
|
|
162
|
+
* Defaults to `false` (fail-closed: never invite a retry unless the error is provably transient).
|
|
163
|
+
*/
|
|
164
|
+
retryable?: boolean;
|
|
114
165
|
}
|
|
115
166
|
declare class CairnError extends Error {
|
|
116
167
|
/** Stable machine code for programmatic branching. */
|
|
@@ -120,7 +171,13 @@ declare class CairnError extends Error {
|
|
|
120
171
|
/** Relative docs path (under the SDK docs base), if any. */
|
|
121
172
|
readonly docsPath?: string;
|
|
122
173
|
/** The cairn-sdk version that produced this error. */
|
|
123
|
-
readonly version = "0.
|
|
174
|
+
readonly version = "0.3.0";
|
|
175
|
+
/**
|
|
176
|
+
* Whether a dApp may safely AUTO-retry this failure. `false` for fund-safety refusals and user-intent
|
|
177
|
+
* declines (the default); `true` only for provably transient infra faults where nothing was signed.
|
|
178
|
+
* A `SubmitInFlightError` is `false` too: reconcile its `txid`, never blind-retry.
|
|
179
|
+
*/
|
|
180
|
+
readonly retryable: boolean;
|
|
124
181
|
constructor(shortMessage: string, opts?: CairnErrorOptions);
|
|
125
182
|
/**
|
|
126
183
|
* Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
|
|
@@ -156,7 +213,40 @@ declare class ContentVerificationError extends CairnError {
|
|
|
156
213
|
expected: string;
|
|
157
214
|
constructor(expected: string);
|
|
158
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* The wallet SUBMITTED but the outcome is AMBIGUOUS: the tx MAY already be in the mempool. Thrown by the
|
|
218
|
+
* write wrappers for the nested `SUBMIT_MAYBE_INFLIGHT` (request threw / timed out / 5xx gateway / unreadable
|
|
219
|
+
* body) and `SUBMIT_DUPLICATE` (node answered "already present or mempool conflict") codes. It CARRIES the
|
|
220
|
+
* locally-computed consensus `txid` so a dApp can RECONCILE (check the explorer / balance) instead of blindly
|
|
221
|
+
* re-broadcasting, which would re-invite the double-broadcast this contract exists to prevent (F14). It is NOT
|
|
222
|
+
* retryable (`retryable === false`); `maybeSent === true` distinguishes it from a definitely-not-sent terminal
|
|
223
|
+
* refusal. See WALLET-ERROR-CODES.md in the cairn-wallet repo.
|
|
224
|
+
*/
|
|
225
|
+
declare class SubmitInFlightError extends CairnError {
|
|
226
|
+
/** The locally-computed consensus txid to reconcile against the chain (may be undefined if the wallet omitted it). */
|
|
227
|
+
readonly txid?: string;
|
|
228
|
+
/** Always true: the tx MAY be in the mempool. Reconcile via `txid`; never blind-retry. */
|
|
229
|
+
readonly maybeSent = true;
|
|
230
|
+
constructor(message: string, code: "SUBMIT_MAYBE_INFLIGHT" | "SUBMIT_DUPLICATE", txid?: string);
|
|
231
|
+
}
|
|
159
232
|
declare function mapProviderError(error: string, code?: string): CairnError;
|
|
233
|
+
/**
|
|
234
|
+
* F14: map a NESTED SubmitResult refusal (`{ ok:false, code, error, txid }`, ridden inside an outer-ok reply)
|
|
235
|
+
* onto a typed error the write wrappers THROW. Without this a fund-safety refusal would RESOLVE with a phantom
|
|
236
|
+
* `txid:undefined`, so a dApp doing `const { txid } = await fillOffer(...); markPaid(txid)` marks a REFUSED
|
|
237
|
+
* fill as paid. Three buckets:
|
|
238
|
+
* - AMBIGUOUS-INFLIGHT (`SUBMIT_MAYBE_INFLIGHT` / `SUBMIT_DUPLICATE`): a `SubmitInFlightError` that CARRIES
|
|
239
|
+
* the locally-computed `txid` to reconcile (never blind-retry).
|
|
240
|
+
* - TRANSIENT (`VERIFY_UNAVAILABLE` / `OFFER_UNKNOWN`): `retryable === true` (nothing signed; retry shortly).
|
|
241
|
+
* - DEFINITIVE fund-safety refusal (everything else, incl. an unknown / absent code): `retryable === false`.
|
|
242
|
+
* Fail-closed: an explicit `ok:false` with no recognized code still throws a non-retryable refusal.
|
|
243
|
+
*/
|
|
244
|
+
declare function mapSubmitResultError(result: {
|
|
245
|
+
ok?: boolean;
|
|
246
|
+
code?: string;
|
|
247
|
+
error?: string;
|
|
248
|
+
txid?: string;
|
|
249
|
+
}): CairnError;
|
|
160
250
|
/** Convenience: the stable code of any error (or "UNKNOWN" for a non-CairnError). */
|
|
161
251
|
declare function errorCode(err: unknown): CairnErrorCode;
|
|
162
252
|
|
|
@@ -232,6 +322,22 @@ declare class Cairn {
|
|
|
232
322
|
* header chain is cached across calls; the first call near tip syncs the post-checkpoint span.
|
|
233
323
|
*/
|
|
234
324
|
private verifiedHeaderMerkleAt;
|
|
325
|
+
/** The PoW-verifying SPV light client, seeded ONCE from the pinned checkpoint (shared by the M3 header-merkle
|
|
326
|
+
* bind and the F13 offer pre-verify). Reused across calls; the first call near tip syncs the post-checkpoint span. */
|
|
327
|
+
private seededSpvLight;
|
|
328
|
+
/**
|
|
329
|
+
* F13 — corroborate an offer ON-CHAIN before building a `fillOffer` payment's `outputs`. Merkle-proves the
|
|
330
|
+
* offer into the PoW-verified header chain (from the pinned SPV checkpoint), binds the record to its on-chain
|
|
331
|
+
* commitment, derives the payment recipient + seller from the offer's on-chain AUTHOR (the funding input's
|
|
332
|
+
* prevout owner, txid-committed, NOT the malleable scriptSig), and, if you pass the resolver-SERVED `offer`
|
|
333
|
+
* object, binds its payto/seller + fee/rebate/partial terms to the proven ones. Returns a TRUST-LABELED result
|
|
334
|
+
* (fail-closed on a positive mismatch, fail-soft `transient` on a lagging / below-checkpoint chain view).
|
|
335
|
+
*
|
|
336
|
+
* This is best-effort corroboration, NOT the payment-grade boundary: the Cairn Wallet's OWN on-device
|
|
337
|
+
* fill-SPV (0.2.60+, fails-closed before signing) is that. A dApp settling real value should still clear this
|
|
338
|
+
* before building `outputs`, so it never hands the wallet a payment a lying resolver would redirect.
|
|
339
|
+
*/
|
|
340
|
+
verifyOfferForFill(offerId: string, servedOffer?: unknown): Promise<OfferFillCheck>;
|
|
235
341
|
/**
|
|
236
342
|
* Detect + connect the Cairn Wallet (browser only). Wires the wallet into
|
|
237
343
|
* `cairn.board` for signed writes. Resolves to the connected address.
|
|
@@ -243,4 +349,4 @@ declare class Cairn {
|
|
|
243
349
|
get connected(): boolean;
|
|
244
350
|
}
|
|
245
351
|
|
|
246
|
-
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 PrimaryName, RegistryClient, SDK_VERSION, type TokenInfo, UnsupportedMethodError, UserRejectedError, WalletConnection, WalletLockedError, errorCode, mapProviderError };
|
|
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, type ProvenOfferTerms, RegistryClient, SDK_VERSION, SubmitInFlightError, type TokenInfo, UnsupportedMethodError, UserRejectedError, WalletConnection, WalletLockedError, bindOfferTerms, errorCode, feeBpsAt, mapProviderError, mapSubmitResultError, preverifyOffer };
|
package/dist/index.js
CHANGED
|
@@ -3,20 +3,20 @@ import {
|
|
|
3
3
|
} from "./chunk-BGJZF7S2.js";
|
|
4
4
|
import {
|
|
5
5
|
BoardClient
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-NLCF5T2F.js";
|
|
7
7
|
import {
|
|
8
8
|
Chain,
|
|
9
9
|
LightClient
|
|
10
10
|
} from "./chunk-C452UODV.js";
|
|
11
11
|
import {
|
|
12
12
|
ContentClient
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-4RDXEW35.js";
|
|
14
14
|
import {
|
|
15
15
|
IndexerClient
|
|
16
16
|
} from "./chunk-J5J4JEJ6.js";
|
|
17
17
|
import {
|
|
18
18
|
CairnController
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-UMXLWUEF.js";
|
|
20
20
|
import {
|
|
21
21
|
WalletConnection,
|
|
22
22
|
connect,
|
|
@@ -24,19 +24,21 @@ import {
|
|
|
24
24
|
discoverProviders,
|
|
25
25
|
getWallet,
|
|
26
26
|
isInstalled
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-QV2GIXO7.js";
|
|
28
28
|
import {
|
|
29
29
|
CairnError,
|
|
30
30
|
ContentVerificationError,
|
|
31
31
|
HttpError,
|
|
32
32
|
NotInstalledError,
|
|
33
33
|
SDK_VERSION,
|
|
34
|
+
SubmitInFlightError,
|
|
34
35
|
UnsupportedMethodError,
|
|
35
36
|
UserRejectedError,
|
|
36
37
|
WalletLockedError,
|
|
37
38
|
errorCode,
|
|
38
|
-
mapProviderError
|
|
39
|
-
|
|
39
|
+
mapProviderError,
|
|
40
|
+
mapSubmitResultError
|
|
41
|
+
} from "./chunk-E2Z3XSCW.js";
|
|
40
42
|
|
|
41
43
|
// src/http.ts
|
|
42
44
|
var DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
|
|
@@ -267,6 +269,106 @@ var NamesClient = class {
|
|
|
267
269
|
}
|
|
268
270
|
};
|
|
269
271
|
|
|
272
|
+
// src/fillverify.ts
|
|
273
|
+
import { rpcTxToTx } from "@inversealtruism/csd-client";
|
|
274
|
+
import { txid, payloadHash } from "@inversealtruism/csd-codec";
|
|
275
|
+
import { parseRecord, V11_HEIGHT, V16_HEIGHT, FEE_BPS, FEE_BPS_V16 } from "@inversealtruism/cairnx-core";
|
|
276
|
+
var ADDR = /^0x[0-9a-f]{40}$/;
|
|
277
|
+
var HASH = /^0x[0-9a-f]{64}$/;
|
|
278
|
+
var COINBASE = "0x" + "00".repeat(32);
|
|
279
|
+
var feeBpsAt = (height) => height >= V11_HEIGHT ? height >= V16_HEIGHT ? FEE_BPS_V16 : FEE_BPS : 0;
|
|
280
|
+
function bindOfferTerms(servedOffer, t) {
|
|
281
|
+
const o = servedOffer;
|
|
282
|
+
const s = (v) => v === void 0 || v === null ? "" : String(v).toLowerCase();
|
|
283
|
+
if (Number(o?.height) !== t.height) return true;
|
|
284
|
+
if (Number(o?.feeBps) !== t.feeBps) return true;
|
|
285
|
+
if (t.value !== void 0 && String(o?.want?.value) !== t.value) return true;
|
|
286
|
+
if (s(o?.taker) !== s(t.taker)) return true;
|
|
287
|
+
if (s(o?.bid) !== s(t.bid)) return true;
|
|
288
|
+
const om = o?.min;
|
|
289
|
+
if ((om !== void 0 && om !== null) !== (t.min !== void 0)) return true;
|
|
290
|
+
if (t.min !== void 0 && String(om) !== t.min) return true;
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
async function provenAuthor(client, provenTx) {
|
|
294
|
+
const in0 = provenTx.inputs?.[0];
|
|
295
|
+
if (!in0 || String(in0.prevTxid).toLowerCase() === COINBASE && Number(in0.vout) === 4294967295) return null;
|
|
296
|
+
let info;
|
|
297
|
+
try {
|
|
298
|
+
info = await client.tx(String(in0.prevTxid));
|
|
299
|
+
} catch {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
const body = info?.tx ?? info;
|
|
303
|
+
if (!body || !Array.isArray(body.outputs) || !Array.isArray(body.inputs)) return null;
|
|
304
|
+
let src;
|
|
305
|
+
try {
|
|
306
|
+
src = rpcTxToTx(body);
|
|
307
|
+
} catch {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
if (String(txid(src)).toLowerCase() !== String(in0.prevTxid).toLowerCase()) return null;
|
|
311
|
+
const out = src.outputs?.[Number(in0.vout)];
|
|
312
|
+
const spk = out?.scriptPubkey ? String(out.scriptPubkey).toLowerCase() : null;
|
|
313
|
+
return spk && ADDR.test(spk) ? spk : null;
|
|
314
|
+
}
|
|
315
|
+
async function preverifyOffer(opts) {
|
|
316
|
+
const id = String(opts.offerId).toLowerCase();
|
|
317
|
+
if (!HASH.test(id)) return { ok: false, trust: "unverified", reason: "invalid offer id" };
|
|
318
|
+
let incl;
|
|
319
|
+
try {
|
|
320
|
+
incl = await opts.light.verifyTxInclusion(id);
|
|
321
|
+
} catch (e) {
|
|
322
|
+
return { ok: false, trust: "unverified", transient: true, reason: `couldn't reach the chain to verify the offer (${e?.message ?? e})` };
|
|
323
|
+
}
|
|
324
|
+
if (!incl.included || incl.trustLevel !== "verified-inclusion" || !incl.tx || !incl.appPayloadHash)
|
|
325
|
+
return { ok: false, trust: "unverified", transient: true, blockHeight: incl.blockHeight, reason: incl.reason ?? `offer not merkle-proven on-chain (${incl.trustLevel})` };
|
|
326
|
+
const blockHeight = Number(incl.blockHeight);
|
|
327
|
+
let provenTx, provenId;
|
|
328
|
+
try {
|
|
329
|
+
provenTx = rpcTxToTx(incl.tx);
|
|
330
|
+
provenId = txid(provenTx);
|
|
331
|
+
} catch {
|
|
332
|
+
return { ok: false, trust: "unverified", reason: "the on-chain offer tx couldn't be re-derived" };
|
|
333
|
+
}
|
|
334
|
+
if (String(provenId).toLowerCase() !== id) return { ok: false, trust: "unverified", reason: "the merkle-proven tx doesn't match the offer id" };
|
|
335
|
+
const app = incl.tx.app;
|
|
336
|
+
if (!app || app.type !== "Propose") return { ok: false, trust: "unverified", reason: "the offer tx is not a Propose" };
|
|
337
|
+
let rec;
|
|
338
|
+
try {
|
|
339
|
+
rec = parseRecord(app.uri, incl.appPayloadHash);
|
|
340
|
+
} catch {
|
|
341
|
+
rec = null;
|
|
342
|
+
}
|
|
343
|
+
if (!rec || rec.t !== "offer") return { ok: false, trust: "unverified", reason: "the offer record doesn't bind to its on-chain commitment" };
|
|
344
|
+
if (String(payloadHash(rec)).toLowerCase() !== String(incl.appPayloadHash).toLowerCase())
|
|
345
|
+
return { ok: false, trust: "unverified", reason: "the offer record doesn't match the on-chain (merkle-proven) commitment" };
|
|
346
|
+
const seller = await provenAuthor(opts.client, provenTx);
|
|
347
|
+
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" };
|
|
348
|
+
const w = rec.want ?? {};
|
|
349
|
+
const payto = w.payto && ADDR.test(String(w.payto).toLowerCase()) ? String(w.payto).toLowerCase() : seller;
|
|
350
|
+
const terms = {
|
|
351
|
+
height: blockHeight,
|
|
352
|
+
feeBps: feeBpsAt(blockHeight),
|
|
353
|
+
value: w.value !== void 0 ? String(w.value) : void 0,
|
|
354
|
+
taker: rec.taker !== void 0 ? String(rec.taker).toLowerCase() : void 0,
|
|
355
|
+
bid: rec.bid !== void 0 ? String(rec.bid).toLowerCase() : void 0,
|
|
356
|
+
min: rec.min !== void 0 ? String(rec.min) : void 0
|
|
357
|
+
};
|
|
358
|
+
const base = { payto, seller, terms, blockHeight };
|
|
359
|
+
if (opts.servedOffer !== void 0 && opts.servedOffer !== null) {
|
|
360
|
+
const so = opts.servedOffer;
|
|
361
|
+
const servedPayto = so?.want?.payto !== void 0 && so?.want?.payto !== null ? String(so.want.payto).toLowerCase() : "";
|
|
362
|
+
if (servedPayto && servedPayto !== payto)
|
|
363
|
+
return { ok: false, trust: "verified", reason: "the served payment recipient doesn't match the offer's on-chain author/record", ...base };
|
|
364
|
+
if (so?.seller !== void 0 && so?.seller !== null && String(so.seller).toLowerCase() !== seller)
|
|
365
|
+
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 };
|
|
366
|
+
if (bindOfferTerms(opts.servedOffer, terms))
|
|
367
|
+
return { ok: false, trust: "verified", reason: "the served fee/rebate/partial terms don't match the offer's on-chain record (a lying resolver could mis-size the fill, causing a rejected fill after your payment)", ...base };
|
|
368
|
+
}
|
|
369
|
+
return { ok: true, trust: "verified", ...base };
|
|
370
|
+
}
|
|
371
|
+
|
|
270
372
|
// src/index.ts
|
|
271
373
|
var DEFAULT_SPV_CHECKPOINT = { height: 38142, hash: "0x00000000000140f023cc0ee1457a40833f2fcb4de44291b9d373e50f17b97232" };
|
|
272
374
|
var DEFAULT_CAIRN = "https://cairn-substrate.com";
|
|
@@ -319,18 +421,39 @@ var Cairn = class {
|
|
|
319
421
|
async verifiedHeaderMerkleAt(height) {
|
|
320
422
|
if (!Number.isInteger(height) || height < this._spvCp.height)
|
|
321
423
|
throw new Error(`height ${height} is below the SPV checkpoint ${this._spvCp.height} (cannot PoW-verify backward)`);
|
|
322
|
-
|
|
323
|
-
const lc = this._spvLight;
|
|
324
|
-
if (!this._spvSeeded) {
|
|
325
|
-
await lc.syncFromCheckpoint(this._spvCp.height, this._spvCp.hash);
|
|
326
|
-
this._spvSeeded = true;
|
|
327
|
-
}
|
|
424
|
+
const lc = await this.seededSpvLight();
|
|
328
425
|
const haveTo = lc.baseHeight + lc.chain.length - 1;
|
|
329
426
|
if (haveTo < height) await lc.sync(height);
|
|
330
427
|
const vh = lc.chain[height - lc.baseHeight];
|
|
331
428
|
if (!vh) throw new Error(`light client has no verified header at ${height}`);
|
|
332
429
|
return String(vh.header.merkle);
|
|
333
430
|
}
|
|
431
|
+
/** The PoW-verifying SPV light client, seeded ONCE from the pinned checkpoint (shared by the M3 header-merkle
|
|
432
|
+
* bind and the F13 offer pre-verify). Reused across calls; the first call near tip syncs the post-checkpoint span. */
|
|
433
|
+
async seededSpvLight() {
|
|
434
|
+
if (!this._spvLight) this._spvLight = new LightClient({ client: this.chain.client, checkpoints: { [this._spvCp.height]: this._spvCp.hash } });
|
|
435
|
+
if (!this._spvSeeded) {
|
|
436
|
+
await this._spvLight.syncFromCheckpoint(this._spvCp.height, this._spvCp.hash);
|
|
437
|
+
this._spvSeeded = true;
|
|
438
|
+
}
|
|
439
|
+
return this._spvLight;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* F13 — corroborate an offer ON-CHAIN before building a `fillOffer` payment's `outputs`. Merkle-proves the
|
|
443
|
+
* offer into the PoW-verified header chain (from the pinned SPV checkpoint), binds the record to its on-chain
|
|
444
|
+
* commitment, derives the payment recipient + seller from the offer's on-chain AUTHOR (the funding input's
|
|
445
|
+
* prevout owner, txid-committed, NOT the malleable scriptSig), and, if you pass the resolver-SERVED `offer`
|
|
446
|
+
* object, binds its payto/seller + fee/rebate/partial terms to the proven ones. Returns a TRUST-LABELED result
|
|
447
|
+
* (fail-closed on a positive mismatch, fail-soft `transient` on a lagging / below-checkpoint chain view).
|
|
448
|
+
*
|
|
449
|
+
* This is best-effort corroboration, NOT the payment-grade boundary: the Cairn Wallet's OWN on-device
|
|
450
|
+
* fill-SPV (0.2.60+, fails-closed before signing) is that. A dApp settling real value should still clear this
|
|
451
|
+
* before building `outputs`, so it never hands the wallet a payment a lying resolver would redirect.
|
|
452
|
+
*/
|
|
453
|
+
async verifyOfferForFill(offerId, servedOffer) {
|
|
454
|
+
const light = await this.seededSpvLight();
|
|
455
|
+
return preverifyOffer({ light, client: this.chain.client, offerId, servedOffer });
|
|
456
|
+
}
|
|
334
457
|
/**
|
|
335
458
|
* Detect + connect the Cairn Wallet (browser only). Wires the wallet into
|
|
336
459
|
* `cairn.board` for signed writes. Resolves to the connected address.
|
|
@@ -366,15 +489,20 @@ export {
|
|
|
366
489
|
NotInstalledError,
|
|
367
490
|
RegistryClient,
|
|
368
491
|
SDK_VERSION,
|
|
492
|
+
SubmitInFlightError,
|
|
369
493
|
UnsupportedMethodError,
|
|
370
494
|
UserRejectedError,
|
|
371
495
|
WalletConnection,
|
|
372
496
|
WalletLockedError,
|
|
497
|
+
bindOfferTerms,
|
|
373
498
|
connect,
|
|
374
499
|
detectProvider,
|
|
375
500
|
discoverProviders,
|
|
376
501
|
errorCode,
|
|
502
|
+
feeBpsAt,
|
|
377
503
|
getWallet,
|
|
378
504
|
isInstalled,
|
|
379
|
-
mapProviderError
|
|
505
|
+
mapProviderError,
|
|
506
|
+
mapSubmitResultError,
|
|
507
|
+
preverifyOffer
|
|
380
508
|
};
|