@inversealtruism/cairn-sdk 0.2.3 → 0.3.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/board.cjs +8 -1
- package/dist/board.js +2 -2
- package/dist/{chunk-72YSLTLO.js → chunk-AGU6K5EZ.js} +1 -1
- package/dist/{chunk-TKZXUBDX.js → chunk-BN3PFJDY.js} +23 -9
- package/dist/{chunk-SVW7FUBJ.js → chunk-CZWDA3A5.js} +1 -1
- package/dist/{chunk-M6OSWJVC.js → chunk-FMXNJQWL.js} +50 -1
- package/dist/{chunk-AAOETAUE.js → chunk-JKC5K253.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 +212 -34
- package/dist/index.d.cts +108 -5
- package/dist/index.d.ts +108 -5
- package/dist/index.js +133 -18
- package/dist/react.cjs +68 -8
- package/dist/react.js +3 -3
- package/package.json +5 -5
package/dist/index.d.cts
CHANGED
|
@@ -10,12 +10,13 @@ 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';
|
|
15
|
+
export { bindOfferTerms, feeBpsAt } from '@inversealtruism/cairnx-core';
|
|
14
16
|
import '@inversealtruism/csd-client';
|
|
15
17
|
import '@inversealtruism/csd-tx';
|
|
16
18
|
import '@inversealtruism/csd-codec';
|
|
17
19
|
import '@inversealtruism/csd-crypto';
|
|
18
|
-
import '@inversealtruism/csd-light';
|
|
19
20
|
import '@inversealtruism/csd-registry';
|
|
20
21
|
|
|
21
22
|
/** `/cairnx/resolve/:name` — the display-path name→address resolution. */
|
|
@@ -104,13 +105,60 @@ declare class NamesClient {
|
|
|
104
105
|
state(): Promise<unknown>;
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
/** The fee/rebate-relevant fields of an offer, derived from the MERKLE-PROVEN offer (never a served object). */
|
|
109
|
+
interface ProvenOfferTerms {
|
|
110
|
+
height: number;
|
|
111
|
+
feeBps: number;
|
|
112
|
+
value?: string;
|
|
113
|
+
taker?: string;
|
|
114
|
+
bid?: string;
|
|
115
|
+
min?: string;
|
|
116
|
+
}
|
|
117
|
+
/** The trust-labeled verdict. `ok:false` + `transient:true` = a retryable chain-catching-up soft-fail (NOT a
|
|
118
|
+
* proven mismatch). `payto`/`seller`/`terms` are populated whenever they could be merkle-derived. */
|
|
119
|
+
interface OfferFillCheck {
|
|
120
|
+
ok: boolean;
|
|
121
|
+
/** "verified" = merkle-proven inclusion + on-chain author; "proof-consistent" = a proof that folds but the
|
|
122
|
+
* header could not be PoW-verified (indexer-trusted); "unverified" = could not prove. */
|
|
123
|
+
trust: "verified" | "proof-consistent" | "unverified";
|
|
124
|
+
reason?: string;
|
|
125
|
+
transient?: boolean;
|
|
126
|
+
payto?: string;
|
|
127
|
+
seller?: string;
|
|
128
|
+
terms?: ProvenOfferTerms;
|
|
129
|
+
blockHeight?: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Pre-verify an offer on-chain before building a `fillOffer` payment. `light` is a PoW-verifying light client
|
|
134
|
+
* (its `verifyTxInclusion` surfaces the merkle-proven tx + committed appPayloadHash); `client` reads source txs
|
|
135
|
+
* for the prevout-owner author bind. Pass `servedOffer` (the resolver's offer object) to also bind its
|
|
136
|
+
* payto/seller/terms to the proven values. See the module header for the trust posture.
|
|
137
|
+
*/
|
|
138
|
+
declare function preverifyOffer(opts: {
|
|
139
|
+
light: {
|
|
140
|
+
verifyTxInclusion(txidHex: string): Promise<InclusionResult>;
|
|
141
|
+
};
|
|
142
|
+
client: {
|
|
143
|
+
tx(id: string): Promise<unknown>;
|
|
144
|
+
};
|
|
145
|
+
offerId: string;
|
|
146
|
+
servedOffer?: unknown;
|
|
147
|
+
}): Promise<OfferFillCheck>;
|
|
148
|
+
|
|
107
149
|
/** 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.2
|
|
150
|
+
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";
|
|
151
|
+
declare const SDK_VERSION = "0.3.2";
|
|
110
152
|
interface CairnErrorOptions {
|
|
111
153
|
code?: CairnErrorCode;
|
|
112
154
|
docsPath?: string;
|
|
113
155
|
cause?: unknown;
|
|
156
|
+
/**
|
|
157
|
+
* Whether a dApp may safely AUTO-retry this failure without fresh user intent. Transient infra faults
|
|
158
|
+
* (e.g. a node that was briefly unreachable, nothing signed) are `true`; fund-safety refusals are `false`.
|
|
159
|
+
* Defaults to `false` (fail-closed: never invite a retry unless the error is provably transient).
|
|
160
|
+
*/
|
|
161
|
+
retryable?: boolean;
|
|
114
162
|
}
|
|
115
163
|
declare class CairnError extends Error {
|
|
116
164
|
/** Stable machine code for programmatic branching. */
|
|
@@ -120,7 +168,13 @@ declare class CairnError extends Error {
|
|
|
120
168
|
/** Relative docs path (under the SDK docs base), if any. */
|
|
121
169
|
readonly docsPath?: string;
|
|
122
170
|
/** The cairn-sdk version that produced this error. */
|
|
123
|
-
readonly version = "0.2
|
|
171
|
+
readonly version = "0.3.2";
|
|
172
|
+
/**
|
|
173
|
+
* Whether a dApp may safely AUTO-retry this failure. `false` for fund-safety refusals and user-intent
|
|
174
|
+
* declines (the default); `true` only for provably transient infra faults where nothing was signed.
|
|
175
|
+
* A `SubmitInFlightError` is `false` too: reconcile its `txid`, never blind-retry.
|
|
176
|
+
*/
|
|
177
|
+
readonly retryable: boolean;
|
|
124
178
|
constructor(shortMessage: string, opts?: CairnErrorOptions);
|
|
125
179
|
/**
|
|
126
180
|
* Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
|
|
@@ -156,7 +210,40 @@ declare class ContentVerificationError extends CairnError {
|
|
|
156
210
|
expected: string;
|
|
157
211
|
constructor(expected: string);
|
|
158
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* The wallet SUBMITTED but the outcome is AMBIGUOUS: the tx MAY already be in the mempool. Thrown by the
|
|
215
|
+
* write wrappers for the nested `SUBMIT_MAYBE_INFLIGHT` (request threw / timed out / 5xx gateway / unreadable
|
|
216
|
+
* body) and `SUBMIT_DUPLICATE` (node answered "already present or mempool conflict") codes. It CARRIES the
|
|
217
|
+
* locally-computed consensus `txid` so a dApp can RECONCILE (check the explorer / balance) instead of blindly
|
|
218
|
+
* re-broadcasting, which would re-invite the double-broadcast this contract exists to prevent (F14). It is NOT
|
|
219
|
+
* retryable (`retryable === false`); `maybeSent === true` distinguishes it from a definitely-not-sent terminal
|
|
220
|
+
* refusal. See WALLET-ERROR-CODES.md in the cairn-wallet repo.
|
|
221
|
+
*/
|
|
222
|
+
declare class SubmitInFlightError extends CairnError {
|
|
223
|
+
/** The locally-computed consensus txid to reconcile against the chain (may be undefined if the wallet omitted it). */
|
|
224
|
+
readonly txid?: string;
|
|
225
|
+
/** Always true: the tx MAY be in the mempool. Reconcile via `txid`; never blind-retry. */
|
|
226
|
+
readonly maybeSent = true;
|
|
227
|
+
constructor(message: string, code: "SUBMIT_MAYBE_INFLIGHT" | "SUBMIT_DUPLICATE", txid?: string);
|
|
228
|
+
}
|
|
159
229
|
declare function mapProviderError(error: string, code?: string): CairnError;
|
|
230
|
+
/**
|
|
231
|
+
* F14: map a NESTED SubmitResult refusal (`{ ok:false, code, error, txid }`, ridden inside an outer-ok reply)
|
|
232
|
+
* onto a typed error the write wrappers THROW. Without this a fund-safety refusal would RESOLVE with a phantom
|
|
233
|
+
* `txid:undefined`, so a dApp doing `const { txid } = await fillOffer(...); markPaid(txid)` marks a REFUSED
|
|
234
|
+
* fill as paid. Three buckets:
|
|
235
|
+
* - AMBIGUOUS-INFLIGHT (`SUBMIT_MAYBE_INFLIGHT` / `SUBMIT_DUPLICATE`): a `SubmitInFlightError` that CARRIES
|
|
236
|
+
* the locally-computed `txid` to reconcile (never blind-retry).
|
|
237
|
+
* - TRANSIENT (`VERIFY_UNAVAILABLE` / `OFFER_UNKNOWN`): `retryable === true` (nothing signed; retry shortly).
|
|
238
|
+
* - DEFINITIVE fund-safety refusal (everything else, incl. an unknown / absent code): `retryable === false`.
|
|
239
|
+
* Fail-closed: an explicit `ok:false` with no recognized code still throws a non-retryable refusal.
|
|
240
|
+
*/
|
|
241
|
+
declare function mapSubmitResultError(result: {
|
|
242
|
+
ok?: boolean;
|
|
243
|
+
code?: string;
|
|
244
|
+
error?: string;
|
|
245
|
+
txid?: string;
|
|
246
|
+
}): CairnError;
|
|
160
247
|
/** Convenience: the stable code of any error (or "UNKNOWN" for a non-CairnError). */
|
|
161
248
|
declare function errorCode(err: unknown): CairnErrorCode;
|
|
162
249
|
|
|
@@ -232,6 +319,22 @@ declare class Cairn {
|
|
|
232
319
|
* header chain is cached across calls; the first call near tip syncs the post-checkpoint span.
|
|
233
320
|
*/
|
|
234
321
|
private verifiedHeaderMerkleAt;
|
|
322
|
+
/** The PoW-verifying SPV light client, seeded ONCE from the pinned checkpoint (shared by the M3 header-merkle
|
|
323
|
+
* bind and the F13 offer pre-verify). Reused across calls; the first call near tip syncs the post-checkpoint span. */
|
|
324
|
+
private seededSpvLight;
|
|
325
|
+
/**
|
|
326
|
+
* F13 — corroborate an offer ON-CHAIN before building a `fillOffer` payment's `outputs`. Merkle-proves the
|
|
327
|
+
* offer into the PoW-verified header chain (from the pinned SPV checkpoint), binds the record to its on-chain
|
|
328
|
+
* commitment, derives the payment recipient + seller from the offer's on-chain AUTHOR (the funding input's
|
|
329
|
+
* prevout owner, txid-committed, NOT the malleable scriptSig), and, if you pass the resolver-SERVED `offer`
|
|
330
|
+
* object, binds its payto/seller + fee/rebate/partial terms to the proven ones. Returns a TRUST-LABELED result
|
|
331
|
+
* (fail-closed on a positive mismatch, fail-soft `transient` on a lagging / below-checkpoint chain view).
|
|
332
|
+
*
|
|
333
|
+
* This is best-effort corroboration, NOT the payment-grade boundary: the Cairn Wallet's OWN on-device
|
|
334
|
+
* fill-SPV (0.2.60+, fails-closed before signing) is that. A dApp settling real value should still clear this
|
|
335
|
+
* before building `outputs`, so it never hands the wallet a payment a lying resolver would redirect.
|
|
336
|
+
*/
|
|
337
|
+
verifyOfferForFill(offerId: string, servedOffer?: unknown): Promise<OfferFillCheck>;
|
|
235
338
|
/**
|
|
236
339
|
* Detect + connect the Cairn Wallet (browser only). Wires the wallet into
|
|
237
340
|
* `cairn.board` for signed writes. Resolves to the connected address.
|
|
@@ -243,4 +346,4 @@ declare class Cairn {
|
|
|
243
346
|
get connected(): boolean;
|
|
244
347
|
}
|
|
245
348
|
|
|
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 };
|
|
349
|
+
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, errorCode, mapProviderError, mapSubmitResultError, preverifyOffer };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,12 +10,13 @@ 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';
|
|
15
|
+
export { bindOfferTerms, feeBpsAt } from '@inversealtruism/cairnx-core';
|
|
14
16
|
import '@inversealtruism/csd-client';
|
|
15
17
|
import '@inversealtruism/csd-tx';
|
|
16
18
|
import '@inversealtruism/csd-codec';
|
|
17
19
|
import '@inversealtruism/csd-crypto';
|
|
18
|
-
import '@inversealtruism/csd-light';
|
|
19
20
|
import '@inversealtruism/csd-registry';
|
|
20
21
|
|
|
21
22
|
/** `/cairnx/resolve/:name` — the display-path name→address resolution. */
|
|
@@ -104,13 +105,60 @@ declare class NamesClient {
|
|
|
104
105
|
state(): Promise<unknown>;
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
/** The fee/rebate-relevant fields of an offer, derived from the MERKLE-PROVEN offer (never a served object). */
|
|
109
|
+
interface ProvenOfferTerms {
|
|
110
|
+
height: number;
|
|
111
|
+
feeBps: number;
|
|
112
|
+
value?: string;
|
|
113
|
+
taker?: string;
|
|
114
|
+
bid?: string;
|
|
115
|
+
min?: string;
|
|
116
|
+
}
|
|
117
|
+
/** The trust-labeled verdict. `ok:false` + `transient:true` = a retryable chain-catching-up soft-fail (NOT a
|
|
118
|
+
* proven mismatch). `payto`/`seller`/`terms` are populated whenever they could be merkle-derived. */
|
|
119
|
+
interface OfferFillCheck {
|
|
120
|
+
ok: boolean;
|
|
121
|
+
/** "verified" = merkle-proven inclusion + on-chain author; "proof-consistent" = a proof that folds but the
|
|
122
|
+
* header could not be PoW-verified (indexer-trusted); "unverified" = could not prove. */
|
|
123
|
+
trust: "verified" | "proof-consistent" | "unverified";
|
|
124
|
+
reason?: string;
|
|
125
|
+
transient?: boolean;
|
|
126
|
+
payto?: string;
|
|
127
|
+
seller?: string;
|
|
128
|
+
terms?: ProvenOfferTerms;
|
|
129
|
+
blockHeight?: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Pre-verify an offer on-chain before building a `fillOffer` payment. `light` is a PoW-verifying light client
|
|
134
|
+
* (its `verifyTxInclusion` surfaces the merkle-proven tx + committed appPayloadHash); `client` reads source txs
|
|
135
|
+
* for the prevout-owner author bind. Pass `servedOffer` (the resolver's offer object) to also bind its
|
|
136
|
+
* payto/seller/terms to the proven values. See the module header for the trust posture.
|
|
137
|
+
*/
|
|
138
|
+
declare function preverifyOffer(opts: {
|
|
139
|
+
light: {
|
|
140
|
+
verifyTxInclusion(txidHex: string): Promise<InclusionResult>;
|
|
141
|
+
};
|
|
142
|
+
client: {
|
|
143
|
+
tx(id: string): Promise<unknown>;
|
|
144
|
+
};
|
|
145
|
+
offerId: string;
|
|
146
|
+
servedOffer?: unknown;
|
|
147
|
+
}): Promise<OfferFillCheck>;
|
|
148
|
+
|
|
107
149
|
/** 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.2
|
|
150
|
+
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";
|
|
151
|
+
declare const SDK_VERSION = "0.3.2";
|
|
110
152
|
interface CairnErrorOptions {
|
|
111
153
|
code?: CairnErrorCode;
|
|
112
154
|
docsPath?: string;
|
|
113
155
|
cause?: unknown;
|
|
156
|
+
/**
|
|
157
|
+
* Whether a dApp may safely AUTO-retry this failure without fresh user intent. Transient infra faults
|
|
158
|
+
* (e.g. a node that was briefly unreachable, nothing signed) are `true`; fund-safety refusals are `false`.
|
|
159
|
+
* Defaults to `false` (fail-closed: never invite a retry unless the error is provably transient).
|
|
160
|
+
*/
|
|
161
|
+
retryable?: boolean;
|
|
114
162
|
}
|
|
115
163
|
declare class CairnError extends Error {
|
|
116
164
|
/** Stable machine code for programmatic branching. */
|
|
@@ -120,7 +168,13 @@ declare class CairnError extends Error {
|
|
|
120
168
|
/** Relative docs path (under the SDK docs base), if any. */
|
|
121
169
|
readonly docsPath?: string;
|
|
122
170
|
/** The cairn-sdk version that produced this error. */
|
|
123
|
-
readonly version = "0.2
|
|
171
|
+
readonly version = "0.3.2";
|
|
172
|
+
/**
|
|
173
|
+
* Whether a dApp may safely AUTO-retry this failure. `false` for fund-safety refusals and user-intent
|
|
174
|
+
* declines (the default); `true` only for provably transient infra faults where nothing was signed.
|
|
175
|
+
* A `SubmitInFlightError` is `false` too: reconcile its `txid`, never blind-retry.
|
|
176
|
+
*/
|
|
177
|
+
readonly retryable: boolean;
|
|
124
178
|
constructor(shortMessage: string, opts?: CairnErrorOptions);
|
|
125
179
|
/**
|
|
126
180
|
* Walk the `cause` chain. With a predicate, returns the FIRST matching error (or null). Without one,
|
|
@@ -156,7 +210,40 @@ declare class ContentVerificationError extends CairnError {
|
|
|
156
210
|
expected: string;
|
|
157
211
|
constructor(expected: string);
|
|
158
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* The wallet SUBMITTED but the outcome is AMBIGUOUS: the tx MAY already be in the mempool. Thrown by the
|
|
215
|
+
* write wrappers for the nested `SUBMIT_MAYBE_INFLIGHT` (request threw / timed out / 5xx gateway / unreadable
|
|
216
|
+
* body) and `SUBMIT_DUPLICATE` (node answered "already present or mempool conflict") codes. It CARRIES the
|
|
217
|
+
* locally-computed consensus `txid` so a dApp can RECONCILE (check the explorer / balance) instead of blindly
|
|
218
|
+
* re-broadcasting, which would re-invite the double-broadcast this contract exists to prevent (F14). It is NOT
|
|
219
|
+
* retryable (`retryable === false`); `maybeSent === true` distinguishes it from a definitely-not-sent terminal
|
|
220
|
+
* refusal. See WALLET-ERROR-CODES.md in the cairn-wallet repo.
|
|
221
|
+
*/
|
|
222
|
+
declare class SubmitInFlightError extends CairnError {
|
|
223
|
+
/** The locally-computed consensus txid to reconcile against the chain (may be undefined if the wallet omitted it). */
|
|
224
|
+
readonly txid?: string;
|
|
225
|
+
/** Always true: the tx MAY be in the mempool. Reconcile via `txid`; never blind-retry. */
|
|
226
|
+
readonly maybeSent = true;
|
|
227
|
+
constructor(message: string, code: "SUBMIT_MAYBE_INFLIGHT" | "SUBMIT_DUPLICATE", txid?: string);
|
|
228
|
+
}
|
|
159
229
|
declare function mapProviderError(error: string, code?: string): CairnError;
|
|
230
|
+
/**
|
|
231
|
+
* F14: map a NESTED SubmitResult refusal (`{ ok:false, code, error, txid }`, ridden inside an outer-ok reply)
|
|
232
|
+
* onto a typed error the write wrappers THROW. Without this a fund-safety refusal would RESOLVE with a phantom
|
|
233
|
+
* `txid:undefined`, so a dApp doing `const { txid } = await fillOffer(...); markPaid(txid)` marks a REFUSED
|
|
234
|
+
* fill as paid. Three buckets:
|
|
235
|
+
* - AMBIGUOUS-INFLIGHT (`SUBMIT_MAYBE_INFLIGHT` / `SUBMIT_DUPLICATE`): a `SubmitInFlightError` that CARRIES
|
|
236
|
+
* the locally-computed `txid` to reconcile (never blind-retry).
|
|
237
|
+
* - TRANSIENT (`VERIFY_UNAVAILABLE` / `OFFER_UNKNOWN`): `retryable === true` (nothing signed; retry shortly).
|
|
238
|
+
* - DEFINITIVE fund-safety refusal (everything else, incl. an unknown / absent code): `retryable === false`.
|
|
239
|
+
* Fail-closed: an explicit `ok:false` with no recognized code still throws a non-retryable refusal.
|
|
240
|
+
*/
|
|
241
|
+
declare function mapSubmitResultError(result: {
|
|
242
|
+
ok?: boolean;
|
|
243
|
+
code?: string;
|
|
244
|
+
error?: string;
|
|
245
|
+
txid?: string;
|
|
246
|
+
}): CairnError;
|
|
160
247
|
/** Convenience: the stable code of any error (or "UNKNOWN" for a non-CairnError). */
|
|
161
248
|
declare function errorCode(err: unknown): CairnErrorCode;
|
|
162
249
|
|
|
@@ -232,6 +319,22 @@ declare class Cairn {
|
|
|
232
319
|
* header chain is cached across calls; the first call near tip syncs the post-checkpoint span.
|
|
233
320
|
*/
|
|
234
321
|
private verifiedHeaderMerkleAt;
|
|
322
|
+
/** The PoW-verifying SPV light client, seeded ONCE from the pinned checkpoint (shared by the M3 header-merkle
|
|
323
|
+
* bind and the F13 offer pre-verify). Reused across calls; the first call near tip syncs the post-checkpoint span. */
|
|
324
|
+
private seededSpvLight;
|
|
325
|
+
/**
|
|
326
|
+
* F13 — corroborate an offer ON-CHAIN before building a `fillOffer` payment's `outputs`. Merkle-proves the
|
|
327
|
+
* offer into the PoW-verified header chain (from the pinned SPV checkpoint), binds the record to its on-chain
|
|
328
|
+
* commitment, derives the payment recipient + seller from the offer's on-chain AUTHOR (the funding input's
|
|
329
|
+
* prevout owner, txid-committed, NOT the malleable scriptSig), and, if you pass the resolver-SERVED `offer`
|
|
330
|
+
* object, binds its payto/seller + fee/rebate/partial terms to the proven ones. Returns a TRUST-LABELED result
|
|
331
|
+
* (fail-closed on a positive mismatch, fail-soft `transient` on a lagging / below-checkpoint chain view).
|
|
332
|
+
*
|
|
333
|
+
* This is best-effort corroboration, NOT the payment-grade boundary: the Cairn Wallet's OWN on-device
|
|
334
|
+
* fill-SPV (0.2.60+, fails-closed before signing) is that. A dApp settling real value should still clear this
|
|
335
|
+
* before building `outputs`, so it never hands the wallet a payment a lying resolver would redirect.
|
|
336
|
+
*/
|
|
337
|
+
verifyOfferForFill(offerId: string, servedOffer?: unknown): Promise<OfferFillCheck>;
|
|
235
338
|
/**
|
|
236
339
|
* Detect + connect the Cairn Wallet (browser only). Wires the wallet into
|
|
237
340
|
* `cairn.board` for signed writes. Resolves to the connected address.
|
|
@@ -243,4 +346,4 @@ declare class Cairn {
|
|
|
243
346
|
get connected(): boolean;
|
|
244
347
|
}
|
|
245
348
|
|
|
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 };
|
|
349
|
+
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, errorCode, 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-CZWDA3A5.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-JKC5K253.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-AGU6K5EZ.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-BN3PFJDY.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-FMXNJQWL.js";
|
|
40
42
|
|
|
41
43
|
// src/http.ts
|
|
42
44
|
var DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
|
|
@@ -143,8 +145,9 @@ var Http = class {
|
|
|
143
145
|
try {
|
|
144
146
|
const res = await this.raw("GET", path, { query });
|
|
145
147
|
if (!res.ok) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
+
if (!(res.status >= 500 && attempt < this.retries)) throw new HttpError(res.status, url, await safeText(res));
|
|
149
|
+
res.body?.cancel().catch(() => {
|
|
150
|
+
});
|
|
148
151
|
} else {
|
|
149
152
|
return this.parseJson(res, new TextDecoder().decode(await readCapped(res, this.maxBytes)), url);
|
|
150
153
|
}
|
|
@@ -189,7 +192,7 @@ var NamesClient = class {
|
|
|
189
192
|
async resolve(name) {
|
|
190
193
|
if (!nameOk(name)) return null;
|
|
191
194
|
try {
|
|
192
|
-
return await this.http.getJson(`/trade/api/cairnx/resolve/${name}`);
|
|
195
|
+
return await this.http.getJson(`/trade/api/cairnx/resolve/${encodeURIComponent(name)}`);
|
|
193
196
|
} catch (e) {
|
|
194
197
|
if (e?.status === 404) return null;
|
|
195
198
|
throw e;
|
|
@@ -199,7 +202,7 @@ var NamesClient = class {
|
|
|
199
202
|
async name(name) {
|
|
200
203
|
if (!nameOk(name)) return null;
|
|
201
204
|
try {
|
|
202
|
-
return await this.http.getJson(`/trade/api/cairnx/name/${name}`);
|
|
205
|
+
return await this.http.getJson(`/trade/api/cairnx/name/${encodeURIComponent(name)}`);
|
|
203
206
|
} catch (e) {
|
|
204
207
|
if (e?.status === 404) return null;
|
|
205
208
|
throw e;
|
|
@@ -209,7 +212,7 @@ var NamesClient = class {
|
|
|
209
212
|
async nameHistory(name) {
|
|
210
213
|
if (!nameOk(name)) return null;
|
|
211
214
|
try {
|
|
212
|
-
return await this.http.getJson(`/trade/api/cairnx/name-history/${name}`);
|
|
215
|
+
return await this.http.getJson(`/trade/api/cairnx/name-history/${encodeURIComponent(name)}`);
|
|
213
216
|
} catch (e) {
|
|
214
217
|
if (e?.status === 404) return null;
|
|
215
218
|
throw e;
|
|
@@ -267,6 +270,92 @@ var NamesClient = class {
|
|
|
267
270
|
}
|
|
268
271
|
};
|
|
269
272
|
|
|
273
|
+
// src/fillverify.ts
|
|
274
|
+
import { rpcTxToTx } from "@inversealtruism/csd-client";
|
|
275
|
+
import { txid, payloadHash } from "@inversealtruism/csd-codec";
|
|
276
|
+
import { parseRecord, feeBpsAt, bindOfferTerms } from "@inversealtruism/cairnx-core";
|
|
277
|
+
var ADDR = /^0x[0-9a-f]{40}$/;
|
|
278
|
+
var HASH = /^0x[0-9a-f]{64}$/;
|
|
279
|
+
var COINBASE = "0x" + "00".repeat(32);
|
|
280
|
+
async function provenAuthor(client, provenTx) {
|
|
281
|
+
const in0 = provenTx.inputs?.[0];
|
|
282
|
+
if (!in0 || String(in0.prevTxid).toLowerCase() === COINBASE && Number(in0.vout) === 4294967295) return null;
|
|
283
|
+
let info;
|
|
284
|
+
try {
|
|
285
|
+
info = await client.tx(String(in0.prevTxid));
|
|
286
|
+
} catch {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
const body = info?.tx ?? info;
|
|
290
|
+
if (!body || !Array.isArray(body.outputs) || !Array.isArray(body.inputs)) return null;
|
|
291
|
+
let src;
|
|
292
|
+
try {
|
|
293
|
+
src = rpcTxToTx(body);
|
|
294
|
+
} catch {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
if (String(txid(src)).toLowerCase() !== String(in0.prevTxid).toLowerCase()) return null;
|
|
298
|
+
const out = src.outputs?.[Number(in0.vout)];
|
|
299
|
+
const spk = out?.scriptPubkey ? String(out.scriptPubkey).toLowerCase() : null;
|
|
300
|
+
return spk && ADDR.test(spk) ? spk : null;
|
|
301
|
+
}
|
|
302
|
+
async function preverifyOffer(opts) {
|
|
303
|
+
const id = String(opts.offerId).toLowerCase();
|
|
304
|
+
if (!HASH.test(id)) return { ok: false, trust: "unverified", reason: "invalid offer id" };
|
|
305
|
+
let incl;
|
|
306
|
+
try {
|
|
307
|
+
incl = await opts.light.verifyTxInclusion(id);
|
|
308
|
+
} catch (e) {
|
|
309
|
+
return { ok: false, trust: "unverified", transient: true, reason: `couldn't reach the chain to verify the offer (${e?.message ?? e})` };
|
|
310
|
+
}
|
|
311
|
+
if (!incl.included || incl.trustLevel !== "verified-inclusion" || !incl.tx || !incl.appPayloadHash)
|
|
312
|
+
return { ok: false, trust: "unverified", transient: true, blockHeight: incl.blockHeight, reason: incl.reason ?? `offer not merkle-proven on-chain (${incl.trustLevel})` };
|
|
313
|
+
const blockHeight = Number(incl.blockHeight);
|
|
314
|
+
let provenTx, provenId;
|
|
315
|
+
try {
|
|
316
|
+
provenTx = rpcTxToTx(incl.tx);
|
|
317
|
+
provenId = txid(provenTx);
|
|
318
|
+
} catch {
|
|
319
|
+
return { ok: false, trust: "unverified", reason: "the on-chain offer tx couldn't be re-derived" };
|
|
320
|
+
}
|
|
321
|
+
if (String(provenId).toLowerCase() !== id) return { ok: false, trust: "unverified", reason: "the merkle-proven tx doesn't match the offer id" };
|
|
322
|
+
const app = incl.tx.app;
|
|
323
|
+
if (!app || app.type !== "Propose") return { ok: false, trust: "unverified", reason: "the offer tx is not a Propose" };
|
|
324
|
+
let rec;
|
|
325
|
+
try {
|
|
326
|
+
rec = parseRecord(app.uri, incl.appPayloadHash);
|
|
327
|
+
} catch {
|
|
328
|
+
rec = null;
|
|
329
|
+
}
|
|
330
|
+
if (!rec || rec.t !== "offer") return { ok: false, trust: "unverified", reason: "the offer record doesn't bind to its on-chain commitment" };
|
|
331
|
+
if (String(payloadHash(rec)).toLowerCase() !== String(incl.appPayloadHash).toLowerCase())
|
|
332
|
+
return { ok: false, trust: "unverified", reason: "the offer record doesn't match the on-chain (merkle-proven) commitment" };
|
|
333
|
+
const seller = await provenAuthor(opts.client, provenTx);
|
|
334
|
+
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" };
|
|
335
|
+
const w = rec.want ?? {};
|
|
336
|
+
const payto = w.payto && ADDR.test(String(w.payto).toLowerCase()) ? String(w.payto).toLowerCase() : seller;
|
|
337
|
+
const terms = {
|
|
338
|
+
height: blockHeight,
|
|
339
|
+
feeBps: feeBpsAt(blockHeight),
|
|
340
|
+
value: w.value !== void 0 ? String(w.value) : void 0,
|
|
341
|
+
taker: rec.taker !== void 0 ? String(rec.taker).toLowerCase() : void 0,
|
|
342
|
+
bid: rec.bid !== void 0 ? String(rec.bid).toLowerCase() : void 0,
|
|
343
|
+
min: rec.min !== void 0 ? String(rec.min) : void 0
|
|
344
|
+
};
|
|
345
|
+
const base = { payto, seller, terms, blockHeight };
|
|
346
|
+
if (opts.servedOffer !== void 0 && opts.servedOffer !== null) {
|
|
347
|
+
const so = opts.servedOffer;
|
|
348
|
+
const servedPayto = so?.want?.payto !== void 0 && so?.want?.payto !== null ? String(so.want.payto).toLowerCase() : "";
|
|
349
|
+
if (servedPayto && servedPayto !== payto)
|
|
350
|
+
return { ok: false, trust: "verified", reason: "the served payment recipient doesn't match the offer's on-chain author/record", ...base };
|
|
351
|
+
if (so?.seller !== void 0 && so?.seller !== null && String(so.seller).toLowerCase() !== seller)
|
|
352
|
+
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 };
|
|
353
|
+
if (bindOfferTerms(opts.servedOffer, terms))
|
|
354
|
+
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 };
|
|
355
|
+
}
|
|
356
|
+
return { ok: true, trust: "verified", ...base };
|
|
357
|
+
}
|
|
358
|
+
|
|
270
359
|
// src/index.ts
|
|
271
360
|
var DEFAULT_SPV_CHECKPOINT = { height: 38142, hash: "0x00000000000140f023cc0ee1457a40833f2fcb4de44291b9d373e50f17b97232" };
|
|
272
361
|
var DEFAULT_CAIRN = "https://cairn-substrate.com";
|
|
@@ -319,18 +408,39 @@ var Cairn = class {
|
|
|
319
408
|
async verifiedHeaderMerkleAt(height) {
|
|
320
409
|
if (!Number.isInteger(height) || height < this._spvCp.height)
|
|
321
410
|
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
|
-
}
|
|
411
|
+
const lc = await this.seededSpvLight();
|
|
328
412
|
const haveTo = lc.baseHeight + lc.chain.length - 1;
|
|
329
413
|
if (haveTo < height) await lc.sync(height);
|
|
330
414
|
const vh = lc.chain[height - lc.baseHeight];
|
|
331
415
|
if (!vh) throw new Error(`light client has no verified header at ${height}`);
|
|
332
416
|
return String(vh.header.merkle);
|
|
333
417
|
}
|
|
418
|
+
/** The PoW-verifying SPV light client, seeded ONCE from the pinned checkpoint (shared by the M3 header-merkle
|
|
419
|
+
* bind and the F13 offer pre-verify). Reused across calls; the first call near tip syncs the post-checkpoint span. */
|
|
420
|
+
async seededSpvLight() {
|
|
421
|
+
if (!this._spvLight) this._spvLight = new LightClient({ client: this.chain.client, checkpoints: { [this._spvCp.height]: this._spvCp.hash } });
|
|
422
|
+
if (!this._spvSeeded) {
|
|
423
|
+
await this._spvLight.syncFromCheckpoint(this._spvCp.height, this._spvCp.hash);
|
|
424
|
+
this._spvSeeded = true;
|
|
425
|
+
}
|
|
426
|
+
return this._spvLight;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* F13 — corroborate an offer ON-CHAIN before building a `fillOffer` payment's `outputs`. Merkle-proves the
|
|
430
|
+
* offer into the PoW-verified header chain (from the pinned SPV checkpoint), binds the record to its on-chain
|
|
431
|
+
* commitment, derives the payment recipient + seller from the offer's on-chain AUTHOR (the funding input's
|
|
432
|
+
* prevout owner, txid-committed, NOT the malleable scriptSig), and, if you pass the resolver-SERVED `offer`
|
|
433
|
+
* object, binds its payto/seller + fee/rebate/partial terms to the proven ones. Returns a TRUST-LABELED result
|
|
434
|
+
* (fail-closed on a positive mismatch, fail-soft `transient` on a lagging / below-checkpoint chain view).
|
|
435
|
+
*
|
|
436
|
+
* This is best-effort corroboration, NOT the payment-grade boundary: the Cairn Wallet's OWN on-device
|
|
437
|
+
* fill-SPV (0.2.60+, fails-closed before signing) is that. A dApp settling real value should still clear this
|
|
438
|
+
* before building `outputs`, so it never hands the wallet a payment a lying resolver would redirect.
|
|
439
|
+
*/
|
|
440
|
+
async verifyOfferForFill(offerId, servedOffer) {
|
|
441
|
+
const light = await this.seededSpvLight();
|
|
442
|
+
return preverifyOffer({ light, client: this.chain.client, offerId, servedOffer });
|
|
443
|
+
}
|
|
334
444
|
/**
|
|
335
445
|
* Detect + connect the Cairn Wallet (browser only). Wires the wallet into
|
|
336
446
|
* `cairn.board` for signed writes. Resolves to the connected address.
|
|
@@ -366,15 +476,20 @@ export {
|
|
|
366
476
|
NotInstalledError,
|
|
367
477
|
RegistryClient,
|
|
368
478
|
SDK_VERSION,
|
|
479
|
+
SubmitInFlightError,
|
|
369
480
|
UnsupportedMethodError,
|
|
370
481
|
UserRejectedError,
|
|
371
482
|
WalletConnection,
|
|
372
483
|
WalletLockedError,
|
|
484
|
+
bindOfferTerms,
|
|
373
485
|
connect,
|
|
374
486
|
detectProvider,
|
|
375
487
|
discoverProviders,
|
|
376
488
|
errorCode,
|
|
489
|
+
feeBpsAt,
|
|
377
490
|
getWallet,
|
|
378
491
|
isInstalled,
|
|
379
|
-
mapProviderError
|
|
492
|
+
mapProviderError,
|
|
493
|
+
mapSubmitResultError,
|
|
494
|
+
preverifyOffer
|
|
380
495
|
};
|