@arkade-os/swap 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +562 -0
- package/dist/chunk-C5P7R7JT.js +1363 -0
- package/dist/index.cjs +3568 -0
- package/dist/index.d.cts +1747 -0
- package/dist/index.d.ts +1747 -0
- package/dist/index.js +2242 -0
- package/dist/nostr.cjs +223 -0
- package/dist/nostr.d.cts +99 -0
- package/dist/nostr.d.ts +99 -0
- package/dist/nostr.js +134 -0
- package/dist/rfq-CRgIOQ_y.d.cts +1219 -0
- package/dist/rfq-CRgIOQ_y.d.ts +1219 -0
- package/package.json +81 -0
|
@@ -0,0 +1,1219 @@
|
|
|
1
|
+
import { ReadonlyIdentity, IWallet, Identity, asset, VHTLC } from '@arkade-os/sdk';
|
|
2
|
+
import { DiscoveredMarket } from '@arkade-os/solver-discovery';
|
|
3
|
+
|
|
4
|
+
type AssetSwapStatus = "pending" | "cancelling" | "fulfilled" | "cancelled" | "recoverable" | "awaiting_fill" | "claimable" | "claimed" | "refunded_l1";
|
|
5
|
+
/** The sentinel asset id for BTC itself, as opposed to a 68-hex asset id.
|
|
6
|
+
* Lives here with the {@link AssetSwap} fields it describes so the market and
|
|
7
|
+
* restore layers share one spelling instead of re-typing the literal. */
|
|
8
|
+
declare const BTC_ASSET_ID = "btc";
|
|
9
|
+
interface AssetSwapFallbackSecretsV1 {
|
|
10
|
+
version: 1;
|
|
11
|
+
type: "stored";
|
|
12
|
+
senderPrivateKeyHex: string;
|
|
13
|
+
/** Onchain-send only. A lightning send's preimage belongs to the payee. */
|
|
14
|
+
preimageHex?: string;
|
|
15
|
+
}
|
|
16
|
+
type AssetSwapFallbackSecrets = AssetSwapFallbackSecretsV1;
|
|
17
|
+
interface AssetSwap {
|
|
18
|
+
/** Funding txid — the swap's identity. */
|
|
19
|
+
id: string;
|
|
20
|
+
/** 'btc' or a 68-hex asset id. */
|
|
21
|
+
fromAsset: string;
|
|
22
|
+
toAsset: string;
|
|
23
|
+
/** Atomic amounts as strings (bigint is not JSON-safe). */
|
|
24
|
+
fromAmount: string;
|
|
25
|
+
/** The covenant wantAmount — a floor, the fill pays >= this. */
|
|
26
|
+
toAmount: string;
|
|
27
|
+
swapAddress: string;
|
|
28
|
+
/** Hex pkScript of the swap contract — the indexer monitoring key. */
|
|
29
|
+
swapPkScript: string;
|
|
30
|
+
/** TLV offer — needed to rebuild the contract for cancel. */
|
|
31
|
+
offerHex: string;
|
|
32
|
+
fundingTxid: string;
|
|
33
|
+
spentTxid?: string;
|
|
34
|
+
status: AssetSwapStatus;
|
|
35
|
+
createdAt: number;
|
|
36
|
+
completedAt?: number;
|
|
37
|
+
/** RFQ pair string, e.g. `arkade:BTC->onchain:BTC`. */
|
|
38
|
+
pair?: string;
|
|
39
|
+
/** `sha256(P)`, hex. Public, and how a restore confirms a candidate
|
|
40
|
+
* derivation is the right one. */
|
|
41
|
+
paymentHash?: string;
|
|
42
|
+
/**
|
|
43
|
+
* The HD descriptor this swap's secrets derive from. Public — it is what
|
|
44
|
+
* lets the record carry no secrets at all. Present iff the swap was
|
|
45
|
+
* created on a wallet that can allocate.
|
|
46
|
+
*/
|
|
47
|
+
signingDescriptor?: string;
|
|
48
|
+
/** P, hex, when the user supplied a preimage that is not seed-derived. */
|
|
49
|
+
preimageHex?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Complete stored-arm secrets for wallets that cannot derive. Versioned
|
|
52
|
+
* and discriminated so restore can rebuild both the sender identity and,
|
|
53
|
+
* for onchain sends, P.
|
|
54
|
+
*/
|
|
55
|
+
fallbackSecrets?: AssetSwapFallbackSecrets;
|
|
56
|
+
/** The L1 HTLC's pkScript, hex — the chain-watch key. */
|
|
57
|
+
htlcPkScriptHex?: string;
|
|
58
|
+
htlcLocktime?: number;
|
|
59
|
+
/** The L1 funding txid, once observed. */
|
|
60
|
+
l1Txid?: string;
|
|
61
|
+
}
|
|
62
|
+
/** All swaps, newest-first. Insertion order is not chronological — the restore
|
|
63
|
+
* scan rebuilds records in tx-scan order — so sort at read to keep
|
|
64
|
+
* newest-first canonical for every consumer. */
|
|
65
|
+
declare const getAssetSwapsOrThrow: (repository: AssetSwapRepository) => Promise<AssetSwap[]>;
|
|
66
|
+
/** The consumer read: a broken backend reads as no swaps rather than crashing
|
|
67
|
+
* a history view. Mutations must use {@link getAssetSwapsOrThrow} instead —
|
|
68
|
+
* swallowing the read there would let "the backend is gone" masquerade as "no
|
|
69
|
+
* such swap" and skip the write silently. */
|
|
70
|
+
declare const getAssetSwaps: (repository: AssetSwapRepository) => Promise<AssetSwap[]>;
|
|
71
|
+
/** Add a swap; no-op if the id is already stored. Returns the updated list.
|
|
72
|
+
* THROWS on a failed write — nothing irreversible may happen until this record
|
|
73
|
+
* is durable, so the caller must not fund on a failure. */
|
|
74
|
+
declare const addAssetSwap: (repository: AssetSwapRepository, swap: AssetSwap) => Promise<AssetSwap[]>;
|
|
75
|
+
/** Merge changes into a swap by id. Returns the updated list.
|
|
76
|
+
* THROWS on a failed read or write, like {@link addAssetSwap} — use this for a
|
|
77
|
+
* write that gates something irreversible. Transitions written *after* the
|
|
78
|
+
* irreversible act belong on {@link updateAssetSwapBestEffort}. */
|
|
79
|
+
declare const updateAssetSwap: (repository: AssetSwapRepository, id: string, changes: Partial<Omit<AssetSwap, "id">>) => Promise<AssetSwap[]>;
|
|
80
|
+
/**
|
|
81
|
+
* {@link updateAssetSwap} for transitions that follow an irreversible action (a
|
|
82
|
+
* broadcast claim, a spent lockup): failing the caller there would report as
|
|
83
|
+
* failed a swap whose funds already moved, and a stale status is recoverable —
|
|
84
|
+
* crash recovery re-derives the true state from the chain
|
|
85
|
+
* (`classifyOnchainHtlc`).
|
|
86
|
+
*
|
|
87
|
+
* `persisted` is the part that must not be hidden: a caller that notifies on a
|
|
88
|
+
* change, or treats one as terminal, has to know the store did not agree.
|
|
89
|
+
*/
|
|
90
|
+
declare const updateAssetSwapBestEffort: (repository: AssetSwapRepository, id: string, changes: Partial<Omit<AssetSwap, "id">>) => Promise<{
|
|
91
|
+
swaps: AssetSwap[];
|
|
92
|
+
persisted: boolean;
|
|
93
|
+
}>;
|
|
94
|
+
|
|
95
|
+
/** A registry discovery result held for reuse. Refetchable — unlike a swap
|
|
96
|
+
* record, losing it costs one network round trip — but it must survive a cold
|
|
97
|
+
* boot: serving it stale is what keeps quoting alive while a registry is down. */
|
|
98
|
+
interface MarketsCacheEntry {
|
|
99
|
+
markets: DiscoveredMarket[];
|
|
100
|
+
fetchedAt: number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Everything the package persists, following the monorepo repository
|
|
104
|
+
* convention (versioned interface, AsyncDisposable, one backend per
|
|
105
|
+
* platform — see the Boltz plugin's SwapRepository). Consumers construct
|
|
106
|
+
* exactly one of these; there is no second storage seam.
|
|
107
|
+
*
|
|
108
|
+
* Durable records (swaps) and rebuildable state (the restore scan's txid
|
|
109
|
+
* cursor, the markets cache) live side by side because they share a
|
|
110
|
+
* lifetime: all three belong to one wallet on one device, and a consumer
|
|
111
|
+
* that wipes one wants all three gone.
|
|
112
|
+
*
|
|
113
|
+
* ponytail: no query filters — every consumer reads all swaps and filters
|
|
114
|
+
* in memory; mirror the Boltz plugin's GetSwapsFilter when a consumer needs
|
|
115
|
+
* subset queries.
|
|
116
|
+
*/
|
|
117
|
+
interface AssetSwapRepository extends AsyncDisposable {
|
|
118
|
+
readonly version: 1;
|
|
119
|
+
/** Insert or replace a swap by id. Store the record whole: `fallbackSecrets`
|
|
120
|
+
* is secret-bearing, and a field-mapped backend that drops it loses the
|
|
121
|
+
* stored arm's claim and refund keys. */
|
|
122
|
+
saveSwap(swap: AssetSwap): Promise<void>;
|
|
123
|
+
/** All stored swaps, in no particular order — `getAssetSwaps` is the
|
|
124
|
+
* canonical newest-first read. */
|
|
125
|
+
getAllSwaps(): Promise<AssetSwap[]>;
|
|
126
|
+
/** Sent txids already checked for offer packets (see restore.ts). */
|
|
127
|
+
getScannedTxids(): Promise<Set<string>>;
|
|
128
|
+
markTxidsScanned(txids: Iterable<string>): Promise<void>;
|
|
129
|
+
/** Cached registry markets, or undefined on a miss. */
|
|
130
|
+
getCachedMarkets(network: string, registry: string): Promise<MarketsCacheEntry | undefined>;
|
|
131
|
+
saveCachedMarkets(network: string, registry: string, entry: MarketsCacheEntry): Promise<void>;
|
|
132
|
+
clear(): Promise<void>;
|
|
133
|
+
}
|
|
134
|
+
declare class InMemoryAssetSwapRepository implements AssetSwapRepository {
|
|
135
|
+
readonly version: 1;
|
|
136
|
+
private readonly swaps;
|
|
137
|
+
private readonly scanned;
|
|
138
|
+
private readonly markets;
|
|
139
|
+
saveSwap(swap: AssetSwap): Promise<void>;
|
|
140
|
+
getAllSwaps(): Promise<AssetSwap[]>;
|
|
141
|
+
getScannedTxids(): Promise<Set<string>>;
|
|
142
|
+
markTxidsScanned(txids: Iterable<string>): Promise<void>;
|
|
143
|
+
getCachedMarkets(network: string, registry: string): Promise<MarketsCacheEntry | undefined>;
|
|
144
|
+
saveCachedMarkets(network: string, registry: string, entry: MarketsCacheEntry): Promise<void>;
|
|
145
|
+
clear(): Promise<void>;
|
|
146
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** L1 confirmation-depth and reorg margin between dependent timelocks. */
|
|
150
|
+
declare const ONCHAIN_ORDER_MARGIN_SECONDS: number;
|
|
151
|
+
/** Don't broadcast a claim with less than this before the refund leaf opens:
|
|
152
|
+
* MTP lag plus confirmation time. Past this point the safe move is to let the
|
|
153
|
+
* swap die and take the covenant refund — claiming into the counterparty's
|
|
154
|
+
* live refund window risks losing the race AND publishing P. */
|
|
155
|
+
declare const ONCHAIN_CLAIM_MARGIN_SECONDS: number;
|
|
156
|
+
/** Bounds on the confirmation depth a quote may demand. */
|
|
157
|
+
declare const MAX_MIN_CONFIRMATIONS = 6;
|
|
158
|
+
/** Conservative block interval for converting depths into wall-clock time. */
|
|
159
|
+
declare const ONCHAIN_SECONDS_PER_BLOCK = 600;
|
|
160
|
+
/**
|
|
161
|
+
* Outputs below this are unspendable in practice; builders refuse them.
|
|
162
|
+
*
|
|
163
|
+
* 330, not 546: Bitcoin Core's dust threshold is a function of the OUTPUT
|
|
164
|
+
* type, and 546 is the P2PKH figure. Both payout scripts on this corridor are
|
|
165
|
+
* taproot — the claim pays the user's Arkade-side L1 address and the refund
|
|
166
|
+
* pays the trader's — for which the threshold is 330 (the same number
|
|
167
|
+
* `FALLBACK_WALLET_DUST_AMOUNT` already uses in the core SDK). Holding the
|
|
168
|
+
* P2PKH number here rejects payouts between 330 and 546 that the network
|
|
169
|
+
* would relay perfectly well, which on a refund path means refusing to return
|
|
170
|
+
* funds that could have been returned.
|
|
171
|
+
*
|
|
172
|
+
* Should a caller ever pass a legacy `payoutPkScript`, this floor is too low
|
|
173
|
+
* for that output and the spend would be non-standard; the threshold would
|
|
174
|
+
* then have to be derived from the script rather than fixed.
|
|
175
|
+
*
|
|
176
|
+
* `BigInt(330)` rather than a `330n` literal, matching the rest of the
|
|
177
|
+
* package: a bigint literal needs an ES2020 target, and this source is read
|
|
178
|
+
* directly by consumers that target lower — forcing every one of them to
|
|
179
|
+
* raise their own target for a single constant. The compiled output is
|
|
180
|
+
* identical.
|
|
181
|
+
*/
|
|
182
|
+
declare const ONCHAIN_DUST_SATS: bigint;
|
|
183
|
+
/** 32 random bytes. The user generates P for BOTH onchain directions. */
|
|
184
|
+
declare const newPreimage: () => Uint8Array;
|
|
185
|
+
/** `sha256(P)`, hex — the wire `payment_hash`, same convention as BOLT11. */
|
|
186
|
+
declare const paymentHashOf: (preimage: Uint8Array) => string;
|
|
187
|
+
type OnchainNetwork = "bitcoin" | "testnet" | "regtest";
|
|
188
|
+
interface OnchainHtlcParams {
|
|
189
|
+
/** `sha256(P)`, hex; the HASH160 commitment is derived internally. */
|
|
190
|
+
paymentHash: string;
|
|
191
|
+
/** x-only key that claims with the preimage. */
|
|
192
|
+
claimKey: Uint8Array;
|
|
193
|
+
/** x-only key that refunds after the locktime. */
|
|
194
|
+
refundKey: Uint8Array;
|
|
195
|
+
/** Absolute unix seconds (consensus matures it against median-time-past). */
|
|
196
|
+
refundLocktime: number;
|
|
197
|
+
}
|
|
198
|
+
interface OnchainHtlc {
|
|
199
|
+
address: string;
|
|
200
|
+
/** `0x5120…` — the P2TR output script. */
|
|
201
|
+
pkScript: Uint8Array;
|
|
202
|
+
leaves: {
|
|
203
|
+
claim: Uint8Array;
|
|
204
|
+
refund: Uint8Array;
|
|
205
|
+
};
|
|
206
|
+
/** Serialized control blocks per leaf, ready for a script-path witness. */
|
|
207
|
+
controlBlocks: {
|
|
208
|
+
claim: Uint8Array;
|
|
209
|
+
refund: Uint8Array;
|
|
210
|
+
};
|
|
211
|
+
paymentHash: string;
|
|
212
|
+
refundLocktime: number;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Derive the two-leaf taproot HTLC. Internal key is the BIP-341 NUMS point, so
|
|
216
|
+
* there is no key-path spend, ever:
|
|
217
|
+
*
|
|
218
|
+
* claim: `OP_SIZE 32 OP_EQUALVERIFY OP_HASH160 <h160> OP_EQUALVERIFY <claimKey> OP_CHECKSIG`
|
|
219
|
+
* refund: `<locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP <refundKey> OP_CHECKSIG`
|
|
220
|
+
*
|
|
221
|
+
* The claim leaf's `OP_SIZE 32 OP_EQUALVERIFY` prefix pins the witness
|
|
222
|
+
* preimage to exactly 32 bytes before it's hashed — the same shape real HTLC
|
|
223
|
+
* scripts (e.g. BOLT3's) carry, and this contract's preimage is always
|
|
224
|
+
* exactly 32 bytes by construction.
|
|
225
|
+
*
|
|
226
|
+
* Pure derivation — pinned byte-for-byte by the golden test; any drift here
|
|
227
|
+
* changes addresses on BOTH sides of a swap.
|
|
228
|
+
*/
|
|
229
|
+
declare function onchainHtlcScript(params: OnchainHtlcParams, network: OnchainNetwork): OnchainHtlc;
|
|
230
|
+
interface HtlcUtxo {
|
|
231
|
+
txid: string;
|
|
232
|
+
vout: number;
|
|
233
|
+
amount: bigint;
|
|
234
|
+
}
|
|
235
|
+
interface SpendResult {
|
|
236
|
+
txHex: string;
|
|
237
|
+
txid: string;
|
|
238
|
+
/** `utxo.amount − fee` — what actually lands at the payout script. */
|
|
239
|
+
payoutAmount: bigint;
|
|
240
|
+
}
|
|
241
|
+
/** Script-path spend of the claim leaf; the witness reveals P — that is how
|
|
242
|
+
* the counterparty learns it, so never build this unless the claim will win
|
|
243
|
+
* (see {@link claimOnchainFill}). `sign` is BIP340 over the claim key. */
|
|
244
|
+
declare const buildHtlcClaim: (input: {
|
|
245
|
+
htlc: OnchainHtlc;
|
|
246
|
+
utxo: HtlcUtxo;
|
|
247
|
+
preimage: Uint8Array;
|
|
248
|
+
payoutPkScript: Uint8Array;
|
|
249
|
+
feeRateSatVb: number;
|
|
250
|
+
sign: (sighash: Uint8Array) => Promise<Uint8Array>;
|
|
251
|
+
}) => Promise<SpendResult>;
|
|
252
|
+
/** Script-path spend of the refund leaf; consensus-valid only once nLockTime
|
|
253
|
+
* has matured against median-time-past — gate on {@link ChainSource.getMtp},
|
|
254
|
+
* not wall clock. `sign` is BIP340 over the refund key. */
|
|
255
|
+
declare const buildHtlcRefund: (input: {
|
|
256
|
+
htlc: OnchainHtlc;
|
|
257
|
+
utxo: HtlcUtxo;
|
|
258
|
+
payoutPkScript: Uint8Array;
|
|
259
|
+
feeRateSatVb: number;
|
|
260
|
+
sign: (sighash: Uint8Array) => Promise<Uint8Array>;
|
|
261
|
+
}) => Promise<SpendResult>;
|
|
262
|
+
interface ChainUtxo extends HtlcUtxo {
|
|
263
|
+
confirmations: number;
|
|
264
|
+
}
|
|
265
|
+
/** The package's whole view of Bitcoin L1. An esplora-backed implementation
|
|
266
|
+
* belongs to the caller (a reference one lives in the test suite); the package
|
|
267
|
+
* itself stays backend-free. */
|
|
268
|
+
interface ChainSource {
|
|
269
|
+
/** Confirmed+mempool outputs paying a script; used to detect the fill. */
|
|
270
|
+
getScriptUtxos(pkScript: Uint8Array): Promise<ChainUtxo[]>;
|
|
271
|
+
/** The spend of an outpoint, if any — where P is extracted from. */
|
|
272
|
+
getSpendingTx(txid: string, vout: number): Promise<{
|
|
273
|
+
txHex: string;
|
|
274
|
+
} | null>;
|
|
275
|
+
broadcast(txHex: string): Promise<string>;
|
|
276
|
+
/** Current median-time-past, unix seconds — gates refund broadcasting. */
|
|
277
|
+
getMtp(): Promise<number>;
|
|
278
|
+
}
|
|
279
|
+
/** Read P out of a claim spend's witness: the 32-byte item whose sha256 is the
|
|
280
|
+
* payment hash. Null when the tx reveals no matching preimage (e.g. a refund
|
|
281
|
+
* spend, or an unrelated tx). */
|
|
282
|
+
declare function extractPreimage(txHex: string, paymentHash: string): Uint8Array | null;
|
|
283
|
+
/** Poll {@link ChainSource} until the HTLC is funded to the required depth.
|
|
284
|
+
* Picks the largest qualifying output when several exist. Throws (reason
|
|
285
|
+
* `fill_timeout`) once `deadline` (unix seconds) passes without one. */
|
|
286
|
+
declare function awaitOnchainFill(chain: ChainSource, htlc: OnchainHtlc, minConfirmations: number, options?: {
|
|
287
|
+
pollMs?: number;
|
|
288
|
+
deadline?: number;
|
|
289
|
+
}): Promise<ChainUtxo>;
|
|
290
|
+
/**
|
|
291
|
+
* Claim the fill: build the claim spend and broadcast it. Broadcasting
|
|
292
|
+
* publishes P (mempool) — by design, it is how the solver gets paid — so this
|
|
293
|
+
* refuses (reason `claim_window_closed`) when less than
|
|
294
|
+
* {@link ONCHAIN_CLAIM_MARGIN_SECONDS} remains before the refund leaf opens:
|
|
295
|
+
* past that point, let the swap die and take the covenant refund instead of
|
|
296
|
+
* racing the counterparty's refund with P exposed.
|
|
297
|
+
*/
|
|
298
|
+
declare function claimOnchainFill(chain: ChainSource, input: {
|
|
299
|
+
htlc: OnchainHtlc;
|
|
300
|
+
utxo: HtlcUtxo;
|
|
301
|
+
preimage: Uint8Array;
|
|
302
|
+
payoutPkScript: Uint8Array;
|
|
303
|
+
feeRateSatVb: number;
|
|
304
|
+
sign: (sighash: Uint8Array) => Promise<Uint8Array>;
|
|
305
|
+
/** Injected for tests; defaults to wall clock. */
|
|
306
|
+
now?: number;
|
|
307
|
+
}): Promise<{
|
|
308
|
+
txid: string;
|
|
309
|
+
payoutAmount: bigint;
|
|
310
|
+
}>;
|
|
311
|
+
/** Where an onchain HTLC stands, for crash recovery (see the store docs:
|
|
312
|
+
* persisting the record BEFORE funding is what makes this classification —
|
|
313
|
+
* and the claim — possible after a restart). */
|
|
314
|
+
type OnchainHtlcPhase = {
|
|
315
|
+
phase: "unfunded";
|
|
316
|
+
} | {
|
|
317
|
+
phase: "awaiting_confirmations";
|
|
318
|
+
utxo: ChainUtxo;
|
|
319
|
+
} | {
|
|
320
|
+
phase: "claimable";
|
|
321
|
+
utxo: ChainUtxo;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* The refund leaf has matured, which means the CLAIM WINDOW IS CLOSED —
|
|
325
|
+
* `claimOnchainFill` throws `claim_window_closed` from here, by design.
|
|
326
|
+
* A recovery caller reading this phase must not try to claim: the correct
|
|
327
|
+
* action is to let the counterparty's L1 refund settle and take the
|
|
328
|
+
* Arkade-side covenant refund. Reaching this phase on a swap you expected
|
|
329
|
+
* to claim means the claim was missed, not that it is still available.
|
|
330
|
+
*/
|
|
331
|
+
| {
|
|
332
|
+
phase: "refundable";
|
|
333
|
+
utxo: ChainUtxo;
|
|
334
|
+
} | {
|
|
335
|
+
phase: "claimed";
|
|
336
|
+
txid: string;
|
|
337
|
+
preimage: Uint8Array;
|
|
338
|
+
} | {
|
|
339
|
+
phase: "swept";
|
|
340
|
+
txid: string;
|
|
341
|
+
};
|
|
342
|
+
/**
|
|
343
|
+
* Classify an HTLC from chain state alone. `funding` (the known outpoint from
|
|
344
|
+
* the stored record) is what distinguishes "never funded" from "funded and
|
|
345
|
+
* already spent": without it a spent HTLC looks unfunded.
|
|
346
|
+
*
|
|
347
|
+
* `claimed` carries the preimage read from the spend's witness — the receipt;
|
|
348
|
+
* `swept` is a spend that reveals no preimage (the counterparty's refund).
|
|
349
|
+
*/
|
|
350
|
+
declare function classifyOnchainHtlc(chain: ChainSource, input: {
|
|
351
|
+
htlc: OnchainHtlc;
|
|
352
|
+
minConfirmations: number;
|
|
353
|
+
funding?: {
|
|
354
|
+
txid: string;
|
|
355
|
+
vout: number;
|
|
356
|
+
};
|
|
357
|
+
}): Promise<OnchainHtlcPhase>;
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Domain separator for the preimage derivation.
|
|
361
|
+
*
|
|
362
|
+
* NArk scopes its tag by protocol+provider (`Arkade-Boltz-Preimage-v1`,
|
|
363
|
+
* `SwapsManagementService.cs:128`) so any Arkade SDK reproduces the same
|
|
364
|
+
* preimage. NArk has no RFQ corridor yet, so this tag defines the scheme
|
|
365
|
+
* rather than mirroring one; it is deliberately distinct from the Boltz tag,
|
|
366
|
+
* or the same wallet key would derive one preimage for both corridors.
|
|
367
|
+
*/
|
|
368
|
+
declare const RFQ_PREIMAGE_TAG = "Arkade-RFQ-Preimage-v1";
|
|
369
|
+
/**
|
|
370
|
+
* `TAG ‖ xonly(32) ‖ u32le(index)` — the message that gets BIP-340 signed.
|
|
371
|
+
*
|
|
372
|
+
* Anchored on the canonical x-only key rather than the descriptor string:
|
|
373
|
+
* restore reconstructs a bare descriptor that serialises differently from the
|
|
374
|
+
* signing descriptor used at create time, and only the key agrees across both.
|
|
375
|
+
*/
|
|
376
|
+
declare function buildPreimageMessage(xonly: Uint8Array, index: number): Uint8Array;
|
|
377
|
+
/** No secrets at rest: everything re-derives from the seed plus this. */
|
|
378
|
+
interface DerivedSwapSecrets {
|
|
379
|
+
derivable: true;
|
|
380
|
+
/** Public. Persist it on the swap record; it is what restore keys off. */
|
|
381
|
+
signingDescriptor: string;
|
|
382
|
+
/** Onchain-send only, when the caller supplied P instead of deriving it. */
|
|
383
|
+
preimage?: Uint8Array;
|
|
384
|
+
}
|
|
385
|
+
/** The wallet could not allocate. These are real secrets — persist them. */
|
|
386
|
+
interface StoredSwapSecrets {
|
|
387
|
+
derivable: false;
|
|
388
|
+
senderPrivateKey: Uint8Array;
|
|
389
|
+
/** Onchain-send only. A lightning send's preimage belongs to the payee. */
|
|
390
|
+
preimage?: Uint8Array;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Which arm a swap got. The discriminant makes the persistence obligation a
|
|
394
|
+
* type-level fact: a consumer written against {@link DerivedSwapSecrets} alone
|
|
395
|
+
* fails to compile when handed the stored arm.
|
|
396
|
+
*/
|
|
397
|
+
type SwapSecrets = DerivedSwapSecrets | StoredSwapSecrets;
|
|
398
|
+
/**
|
|
399
|
+
* Allocate a descriptor for one swap, or `undefined` when the wallet cannot.
|
|
400
|
+
*
|
|
401
|
+
* Allocates — never peeks. `getCurrentSigningDescriptor` returns the same
|
|
402
|
+
* descriptor until the wallet rotates, and two swaps sharing a descriptor
|
|
403
|
+
* derive the *identical* preimage, so one solver learning its own preimage
|
|
404
|
+
* would learn the other swap's.
|
|
405
|
+
*
|
|
406
|
+
* Cost of allocating: the index is consumed even when the quote is later
|
|
407
|
+
* refused, and a swap index never turns into a funded receive contract, so a
|
|
408
|
+
* long run of swaps widens the "unused" gap a seed-only `restore()` scan sees
|
|
409
|
+
* (see the README's gap-limit note). Restores that keep the swap repository
|
|
410
|
+
* are unaffected — `adoptSwapDescriptor` re-claims each record's index.
|
|
411
|
+
*/
|
|
412
|
+
declare function deriveSwapSecrets(wallet: IWallet): Promise<DerivedSwapSecrets | undefined>;
|
|
413
|
+
/**
|
|
414
|
+
* The fallback arm. Separate from {@link deriveSwapSecrets} so nothing can
|
|
415
|
+
* fabricate a preimage while probing for a derived one.
|
|
416
|
+
*/
|
|
417
|
+
declare function randomSwapSecrets(opts?: {
|
|
418
|
+
preimage?: boolean | Uint8Array;
|
|
419
|
+
}): StoredSwapSecrets;
|
|
420
|
+
/**
|
|
421
|
+
* Serialize or restore the secrets arm a persisted record describes. Normal
|
|
422
|
+
* HD swaps store only `signingDescriptor`; caller-supplied preimages add
|
|
423
|
+
* `preimageHex`; fallback swaps use `fallbackSecrets` so both P and the
|
|
424
|
+
* sender identity survive a restart.
|
|
425
|
+
*/
|
|
426
|
+
declare function rfqSecretsToRecord(secrets: SwapSecrets): {
|
|
427
|
+
signingDescriptor?: string;
|
|
428
|
+
preimageHex?: string;
|
|
429
|
+
fallbackSecrets?: AssetSwapFallbackSecrets;
|
|
430
|
+
};
|
|
431
|
+
declare function rfqSecretsOfRecord(record: {
|
|
432
|
+
signingDescriptor?: string;
|
|
433
|
+
preimageHex?: string;
|
|
434
|
+
fallbackSecrets?: AssetSwapFallbackSecrets;
|
|
435
|
+
}): SwapSecrets | undefined;
|
|
436
|
+
/**
|
|
437
|
+
* Claim a restored swap's index so a later allocation cannot reissue it —
|
|
438
|
+
* which would derive that swap's preimage a second time, for a different swap.
|
|
439
|
+
* Monotonic; a no-op on a wallet that cannot allocate.
|
|
440
|
+
*/
|
|
441
|
+
declare function adoptSwapDescriptor(wallet: IWallet, signingDescriptor: string): Promise<void>;
|
|
442
|
+
/** Why a wallet cannot produce a swap's sender key. Different instructions to
|
|
443
|
+
* a user: restore the other wallet, or accept that this record never carried
|
|
444
|
+
* the secrets at all. */
|
|
445
|
+
type RefundBlockedReason =
|
|
446
|
+
/** The record names no arm: neither `signingDescriptor` nor `fallbackSecrets`. */
|
|
447
|
+
"no-secrets"
|
|
448
|
+
/** It names an arm this version cannot read. */
|
|
449
|
+
| "unreadable-secrets"
|
|
450
|
+
/** The descriptor belongs to another seed, or this wallet is static. */
|
|
451
|
+
| "foreign-descriptor";
|
|
452
|
+
/**
|
|
453
|
+
* The wallet cannot produce this swap's sender key, so no local refund is
|
|
454
|
+
* possible: not a failure to retry, a capability this wallet does not have.
|
|
455
|
+
*
|
|
456
|
+
* Thrown where the cause is discovered rather than translated at the edge, so
|
|
457
|
+
* a `refundArkade` wired through {@link senderIdentityForSwapRecord} reports it
|
|
458
|
+
* to `RfqSwapManager` unwrapped — which is what stops the manager grinding
|
|
459
|
+
* against a push that can never work for the whole refund window.
|
|
460
|
+
*/
|
|
461
|
+
declare class RefundNotLocallyPossibleError extends Error {
|
|
462
|
+
readonly reason: RefundBlockedReason;
|
|
463
|
+
readonly name = "RefundNotLocallyPossibleError";
|
|
464
|
+
constructor(reason: RefundBlockedReason, message: string, options?: {
|
|
465
|
+
cause?: unknown;
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* The VHTLC `sender` identity — the signer for every interactive refund.
|
|
470
|
+
*
|
|
471
|
+
* The capability probe is not the check that matters: `signerForDescriptor`
|
|
472
|
+
* falls back to the plain wallet identity for a descriptor it cannot derive —
|
|
473
|
+
* a different seed, a static wallet — and that identity signs happily with the
|
|
474
|
+
* wrong key, which surfaces only as a solver rejection or a dead claim script.
|
|
475
|
+
* So the check is on what comes back: only a descriptor-bound signer carries
|
|
476
|
+
* `signSchnorrDeterministic`.
|
|
477
|
+
*/
|
|
478
|
+
declare function senderIdentityForRfqSecrets(wallet: IWallet, secrets: SwapSecrets): Promise<Identity>;
|
|
479
|
+
/**
|
|
480
|
+
* The VHTLC `sender` identity for a stored swap record, or a typed refusal.
|
|
481
|
+
*
|
|
482
|
+
* The record→identity composition {@link rfqSecretsOfRecord} deliberately does
|
|
483
|
+
* not do: it stays total so history iteration can call it, so *this* is where
|
|
484
|
+
* "no secrets on the record" becomes a refusal rather than an `undefined` the
|
|
485
|
+
* caller has to remember to check.
|
|
486
|
+
*
|
|
487
|
+
* **Wire `refundArkade` here, not to {@link senderIdentityForRfqSecrets}.**
|
|
488
|
+
* Only two of the three causes are throws; a caller one level down would skip
|
|
489
|
+
* the third silently and turn it into a `TypeError` at the push site, which
|
|
490
|
+
* `RfqSwapManager` then treats as retryable and grinds against for the whole
|
|
491
|
+
* refund window.
|
|
492
|
+
*
|
|
493
|
+
* Takes the record shape structurally, matching {@link rfqSecretsOfRecord}, so
|
|
494
|
+
* either record type can be passed.
|
|
495
|
+
*/
|
|
496
|
+
declare function senderIdentityForSwapRecord(wallet: IWallet, record: {
|
|
497
|
+
signingDescriptor?: string;
|
|
498
|
+
preimageHex?: string;
|
|
499
|
+
fallbackSecrets?: AssetSwapFallbackSecrets;
|
|
500
|
+
}): Promise<Identity>;
|
|
501
|
+
/** The `sender` x-only pubkey, the only half the request flow needs. */
|
|
502
|
+
declare function senderPubkeyForRfqSecrets(wallet: IWallet, secrets: SwapSecrets): Promise<Uint8Array>;
|
|
503
|
+
/**
|
|
504
|
+
* An identity that signs with `aux_rand = 0`, which is what makes the
|
|
505
|
+
* derivation reproducible. `DescriptorIdentity` satisfies it and throws rather
|
|
506
|
+
* than degrading to a random-aux signer.
|
|
507
|
+
*/
|
|
508
|
+
interface DeterministicSigner extends ReadonlyIdentity {
|
|
509
|
+
signSchnorrDeterministic(messageHash: Uint8Array): Promise<Uint8Array>;
|
|
510
|
+
}
|
|
511
|
+
declare function isDeterministicSigner(value: unknown): value is DeterministicSigner;
|
|
512
|
+
/**
|
|
513
|
+
* `sha256(sign(sha256(msg)))`, with the signing key and the message key being
|
|
514
|
+
* the same identity — passing a key in separately is how this silently derives
|
|
515
|
+
* an unrecoverable preimage.
|
|
516
|
+
*/
|
|
517
|
+
declare function derivePreimage(signer: DeterministicSigner): Promise<Uint8Array>;
|
|
518
|
+
/** The onchain-send preimage, re-derived or read back off the stored arm. */
|
|
519
|
+
declare function preimageForRfqSecrets(wallet: IWallet, secrets: SwapSecrets): Promise<Uint8Array>;
|
|
520
|
+
|
|
521
|
+
/** Legs are `<corridor>:<asset>`; a pair is directional, `from->to`. Arkade
|
|
522
|
+
* asset legs stay coarse (`arkade:ASSET`) — the exact asset ids ride the
|
|
523
|
+
* request profile, mirroring how the offer TLV identifies assets. */
|
|
524
|
+
declare const ARKADE_BTC = "arkade:BTC";
|
|
525
|
+
declare const ARKADE_ASSET = "arkade:ASSET";
|
|
526
|
+
declare const LIGHTNING_BTC = "lightning:BTC";
|
|
527
|
+
declare const ONCHAIN_BTC = "onchain:BTC";
|
|
528
|
+
declare const rfqPair: (from: string, to: string) => string;
|
|
529
|
+
/** The implemented pair: pay a BOLT11 invoice out of an Arkade balance. */
|
|
530
|
+
declare const LIGHTNING_SEND_PAIR: string;
|
|
531
|
+
/** On-board via Lightning: pay the solver's hold invoice, land on Arkade. */
|
|
532
|
+
declare const LIGHTNING_RECEIVE_PAIR: string;
|
|
533
|
+
/** Off-board: Arkade sats out to a Bitcoin-L1 HTLC. */
|
|
534
|
+
declare const ONCHAIN_SEND_PAIR: string;
|
|
535
|
+
/** On-board: a Bitcoin-L1 HTLC in, Arkade sats out. */
|
|
536
|
+
declare const ONCHAIN_RECEIVE_PAIR: string;
|
|
537
|
+
/** The closed refusal set. Treat any unknown reason as a generic decline. */
|
|
538
|
+
type RfqRefusalReason = "unsupported_pair" | "unsupported_payload" | "amount_out_of_range" | "exposure_cap" | "invoice_expired" | "quote_conflict" | "pricing_unavailable";
|
|
539
|
+
/** Lifecycle vocabulary; states after which nothing more will happen. */
|
|
540
|
+
declare const RFQ_TERMINAL_STATES: readonly ["settled", "refused", "expired", "refunded", "stuck"];
|
|
541
|
+
/** A refusal from the solver, carrying its closed-set reason. */
|
|
542
|
+
declare class SwapRefusal extends Error {
|
|
543
|
+
readonly reason: string;
|
|
544
|
+
readonly rfqId: string | undefined;
|
|
545
|
+
constructor(reason: string, rfqId?: string);
|
|
546
|
+
}
|
|
547
|
+
/** The solver's address does not match the local derivation. NEVER fund past this. */
|
|
548
|
+
declare class AddressMismatch extends Error {
|
|
549
|
+
readonly derived: string;
|
|
550
|
+
readonly quoted: string | undefined;
|
|
551
|
+
constructor(derived: string, quoted?: string);
|
|
552
|
+
}
|
|
553
|
+
/** A fresh client-chosen negotiation id: 32 random bytes, lowercase hex. */
|
|
554
|
+
declare const newRfqId: () => string;
|
|
555
|
+
interface RfqQuote {
|
|
556
|
+
v: 1;
|
|
557
|
+
type: "rfq_quote";
|
|
558
|
+
rfq_id: string;
|
|
559
|
+
pair: string;
|
|
560
|
+
from_amount: number;
|
|
561
|
+
to_amount: number;
|
|
562
|
+
solver_pubkey: string;
|
|
563
|
+
valid_until: number;
|
|
564
|
+
/** HTLC-class quotes only; absent for arkade↔arkade. */
|
|
565
|
+
refund_locktime?: number;
|
|
566
|
+
profile: {
|
|
567
|
+
[key: string]: unknown;
|
|
568
|
+
payment_hash?: string;
|
|
569
|
+
lockup_address?: string;
|
|
570
|
+
};
|
|
571
|
+
[key: string]: unknown;
|
|
572
|
+
}
|
|
573
|
+
interface RfqStatus {
|
|
574
|
+
v: 1;
|
|
575
|
+
type: "rfq_status";
|
|
576
|
+
rfq_id: string;
|
|
577
|
+
state: string;
|
|
578
|
+
updated_at: number;
|
|
579
|
+
profile: Record<string, unknown>;
|
|
580
|
+
[key: string]: unknown;
|
|
581
|
+
}
|
|
582
|
+
/** The rfq_request for the lightning send profile. A BOLT11 profile is always
|
|
583
|
+
* exact-out: the invoice fixes the amount, so none is restated here.
|
|
584
|
+
* `senderPubkey` is the trader's own key for the VHTLC's sender-side leaves
|
|
585
|
+
* (see {@link lightningSendVtxoScript}) — required, never sent anywhere else,
|
|
586
|
+
* never trusted by the solver as anything but a pubkey to bind into the
|
|
587
|
+
* script. On the wire it's `client_refund_pubkey` (docs/rfq-protocol.md —
|
|
588
|
+
* the solver's schema is `.strict()`, so both the wrong name AND the missing
|
|
589
|
+
* required field would refuse every request). */
|
|
590
|
+
declare const lightningSendRequest: (input: {
|
|
591
|
+
rfqId: string;
|
|
592
|
+
invoice: string;
|
|
593
|
+
refundAddress: string;
|
|
594
|
+
senderPubkey: Uint8Array;
|
|
595
|
+
}) => Record<string, unknown>;
|
|
596
|
+
/** The rfq_request for an arkade↔arkade swap. Exactly one side may name an
|
|
597
|
+
* asset id per direction (BTC has none); the pair string stays coarse and the
|
|
598
|
+
* ids ride the profile, like the offer TLV. Forward-looking: the wire shape is
|
|
599
|
+
* specified, the reference solver does not serve it yet. */
|
|
600
|
+
declare const arkadeSwapRequest: (input: {
|
|
601
|
+
rfqId: string;
|
|
602
|
+
/** Asset the trader deposits; omit when depositing BTC. */
|
|
603
|
+
offerAsset?: asset.AssetId;
|
|
604
|
+
/** Asset the trader wants; omit when wanting BTC. */
|
|
605
|
+
wantAsset?: asset.AssetId;
|
|
606
|
+
amountSide: "from" | "to";
|
|
607
|
+
/** Integer base units of the side named by `amountSide`. */
|
|
608
|
+
amount: number;
|
|
609
|
+
}) => Record<string, unknown>;
|
|
610
|
+
/** Funding gate: refuse unless ≥90 min remain before the refund path opens.
|
|
611
|
+
* 90 because the refund CLTV matures against median-time-past (BIP-113),
|
|
612
|
+
* which lags wall clock by ~1h — a smaller wall-clock margin is no margin. */
|
|
613
|
+
declare const MIN_HEADROOM_SECONDS: number;
|
|
614
|
+
/** Compare-only check of the solver's address against YOUR derivation.
|
|
615
|
+
* Throws {@link AddressMismatch}; returns the address so calls chain. */
|
|
616
|
+
declare const verifyLockupAddress: (quote: RfqQuote, derivedAddress: string) => string;
|
|
617
|
+
/** The user's gates, checked immediately before funding — never at quote
|
|
618
|
+
* time. Throws with a stable `reason` property. `invoiceExpiresAt` applies to
|
|
619
|
+
* BOLT11 profiles only; `onchain` adds the L1-HTLC gates (§ guardrails of the
|
|
620
|
+
* onchain spec) and is required for the onchain pairs.
|
|
621
|
+
*
|
|
622
|
+
* The lightning-receive leg does NOT use this: see {@link assertReceivable}.
|
|
623
|
+
* `refund_locktime` is the SOLVER's on both receive corridors, so
|
|
624
|
+
* `MIN_HEADROOM_SECONDS` gates the wrong side on either — but only the
|
|
625
|
+
* lightning leg has a second clock that can actually run out (the hold
|
|
626
|
+
* invoice's), which is what the split buys. The onchain-receive leg stays here
|
|
627
|
+
* until its own deadline gets the same treatment; the headroom check is merely
|
|
628
|
+
* over-strict there, never unsafe. */
|
|
629
|
+
declare const assertFundable: (input: {
|
|
630
|
+
quote: RfqQuote;
|
|
631
|
+
invoiceExpiresAt?: number;
|
|
632
|
+
now: number;
|
|
633
|
+
onchain?: {
|
|
634
|
+
htlcLocktime: number;
|
|
635
|
+
minConfirmations: number;
|
|
636
|
+
/** "send" = arkade->onchain (the L1 timelock-order gate applies). */
|
|
637
|
+
direction: "send" | "receive";
|
|
638
|
+
};
|
|
639
|
+
}) => void;
|
|
640
|
+
interface RfqTransport {
|
|
641
|
+
requestQuote(payload: Record<string, unknown>): Promise<RfqQuote>;
|
|
642
|
+
status(rfqId: string): Promise<RfqStatus | null>;
|
|
643
|
+
close(): Promise<void>;
|
|
644
|
+
}
|
|
645
|
+
/** HTTP: POST /v1/swap for quotes, GET /v1/rfq/<rfq_id> for status.
|
|
646
|
+
* `fetchImpl` is injectable for tests and non-global-fetch runtimes. */
|
|
647
|
+
declare const httpTransport: (baseUrl: string, options?: {
|
|
648
|
+
fetchImpl?: typeof fetch;
|
|
649
|
+
}) => RfqTransport;
|
|
650
|
+
/** Minimal WebSocket surface the relay transport needs — satisfied by the DOM
|
|
651
|
+
* WebSocket and by `ws` alike, so neither becomes a dependency. */
|
|
652
|
+
interface RelaySocket {
|
|
653
|
+
send(data: string): void;
|
|
654
|
+
close(): void;
|
|
655
|
+
addEventListener(type: "open" | "message" | "error", listener: (event: any) => void): void;
|
|
656
|
+
}
|
|
657
|
+
/** Relay: both parties outbound, addressed by x-only pubkey, speaking the dev
|
|
658
|
+
* broker framing. Nostr (directed kind + NIP-44) replaces only this function.
|
|
659
|
+
* One socket; replies correlated by rfq_id. */
|
|
660
|
+
declare const relayTransport: (relayUrl: string, options: {
|
|
661
|
+
solverPubkey: string;
|
|
662
|
+
clientPubkey: string;
|
|
663
|
+
WebSocketCtor?: new (url: string) => RelaySocket;
|
|
664
|
+
timeoutMs?: number;
|
|
665
|
+
}) => RfqTransport;
|
|
666
|
+
/** The solver's unilateral-claim delay, derived from the Ark server's reported
|
|
667
|
+
* exit delay exactly as the reference solver derives it — both sides read the
|
|
668
|
+
* SAME server, so the derivation (not a quote field) is what keeps the two
|
|
669
|
+
* scripts identical. */
|
|
670
|
+
declare const unilateralClaimDelay: (serverExitDelaySeconds: number) => number;
|
|
671
|
+
/** VHTLC's `unilateralRefund` tier: sender + solver, no server, one 512s step
|
|
672
|
+
* past `claimDelay` — the middle rung between the fully-collaborative paths
|
|
673
|
+
* and the sender's last-resort `unilateralRefundWithoutReceiver`. Same
|
|
674
|
+
* already-rounded `claimDelay` input as {@link unilateralClaimDelay}
|
|
675
|
+
* produces — one rounding, shared across all three tiers. */
|
|
676
|
+
declare const unilateralRefundDelay: (claimDelay: number) => number;
|
|
677
|
+
/** VHTLC's `unilateralRefundWithoutReceiver` tier: sender alone, needs
|
|
678
|
+
* nobody — two 512s steps past `claimDelay`, past {@link
|
|
679
|
+
* unilateralRefundDelay}. */
|
|
680
|
+
declare const unilateralRefundWithoutReceiverDelay: (claimDelay: number) => number;
|
|
681
|
+
/** Compile the lightning-send VHTLC from the quote's binding fields plus the
|
|
682
|
+
* trader's own data. `paymentHash` is the BOLT11 payment hash (`sha256(P)`,
|
|
683
|
+
* hex); the script's HASH160 commitment is derived from it here, which is why
|
|
684
|
+
* the trader never needs to see `P`.
|
|
685
|
+
*
|
|
686
|
+
* Every quote gets the full eight-leaf contract: VHTLC's own six
|
|
687
|
+
* (`claim`/`refund`/`refundWithoutReceiver`/`unilateralClaim`/
|
|
688
|
+
* `unilateralRefund`/`unilateralRefundWithoutReceiver`), plus two more the
|
|
689
|
+
* emulator co-signs under a covenant pinning the payout to a pre-committed
|
|
690
|
+
* destination — `nonInteractiveClaim` (server + emulator, pays the solver's
|
|
691
|
+
* own `receiverPkScript`, no solver signature needed) and
|
|
692
|
+
* `nonInteractiveRefund` (server + solver + emulator, pays the trader's own
|
|
693
|
+
* `refundPkScript`, no timelock and no trader signature needed — see {@link
|
|
694
|
+
* VHTLC.Options.nonInteractiveRefund}'s doc comment for why that matters).
|
|
695
|
+
*/
|
|
696
|
+
declare function lightningSendVtxoScript(params: {
|
|
697
|
+
/** Binding field #1: the solver's x-only key, from the quote. */
|
|
698
|
+
solverPubkey: Uint8Array;
|
|
699
|
+
/** Binding field #2: when the trader's refund path opens, from the quote. */
|
|
700
|
+
refundLocktime: number;
|
|
701
|
+
/** The Ark server's x-only key — the trader's OWN connection. */
|
|
702
|
+
serverPubkey: Uint8Array;
|
|
703
|
+
/** BOLT11 payment hash, hex — from the trader's OWN invoice decode. */
|
|
704
|
+
paymentHash: string;
|
|
705
|
+
/** From {@link unilateralClaimDelay} over the trader's OWN server info.
|
|
706
|
+
* {@link unilateralRefundDelay} and {@link unilateralRefundWithoutReceiverDelay}
|
|
707
|
+
* derive from this same value — one rounding, shared across all three tiers. */
|
|
708
|
+
claimDelay: number;
|
|
709
|
+
/** Emulator x-only key — the SOLVER's deployment, not the trader's own.
|
|
710
|
+
* Not fetched here or anywhere in this package; see {@link
|
|
711
|
+
* requestLightningSend}'s `emulatorPubkey` parameter for where it comes
|
|
712
|
+
* from and why. */
|
|
713
|
+
emulatorPubkey: Uint8Array;
|
|
714
|
+
/** Where a refund must pay: the trader's P2TR pkScript (34 bytes). Also
|
|
715
|
+
* `nonInteractiveRefund`'s covenant destination. */
|
|
716
|
+
refundPkScript: Uint8Array;
|
|
717
|
+
/** The trader's own key — VHTLC's `sender` role. Required on every
|
|
718
|
+
* interactive refund-side leaf; the trader generates and persists it
|
|
719
|
+
* (see {@link requestLightningSend}'s own obligations). */
|
|
720
|
+
senderPubkey: Uint8Array;
|
|
721
|
+
/** The solver's own claim destination, from the quote
|
|
722
|
+
* (`profile.receiver_pk_script`) — needed only so `nonInteractiveClaim`'s
|
|
723
|
+
* covenant key can be derived; the trader does not otherwise use or trust
|
|
724
|
+
* this value. P2TR pkScript, 34 bytes. */
|
|
725
|
+
receiverPkScript: Uint8Array;
|
|
726
|
+
}): InstanceType<typeof VHTLC.ScriptV2>;
|
|
727
|
+
/** The BOLT11 facts the trader read from its OWN decode — this module takes
|
|
728
|
+
* the facts, not the decoder, so any wallet's existing decoder serves. */
|
|
729
|
+
interface InvoiceFacts {
|
|
730
|
+
/** The raw BOLT11 — what travels in the request profile. */
|
|
731
|
+
raw: string;
|
|
732
|
+
/** `sha256(P)`, LOWERCASE hex (64 chars) — {@link verifyReceiveInvoice}
|
|
733
|
+
* compares it byte-for-byte against `paymentHashOf`, which emits lowercase. */
|
|
734
|
+
paymentHash: string;
|
|
735
|
+
amountSats: number;
|
|
736
|
+
/** Absolute expiry, unix seconds. */
|
|
737
|
+
expiresAt: number;
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* The lightning-send user flow, mirroring `createOffer`'s shape: quote →
|
|
741
|
+
* derive locally → verify → gate. Pure of funding on purpose — it returns the
|
|
742
|
+
* address and amount, and the caller funds with its own wallet
|
|
743
|
+
* (`wallet.send({ address, amount })`) before `quote.valid_until`, after
|
|
744
|
+
* which the user may go OFFLINE: filling is non-interactive. Success reveals
|
|
745
|
+
* the preimage in the solver's claim witness (also served via status as
|
|
746
|
+
* `settled`); failure refunds to `refundAddress`.
|
|
747
|
+
*
|
|
748
|
+
* Throws {@link SwapRefusal} (closed reason), {@link AddressMismatch} (never
|
|
749
|
+
* fund), a gate error with a stable `reason`, or
|
|
750
|
+
* {@link LockupRegistrationFailed} — the last one alone means the quote is
|
|
751
|
+
* still good and the same call can be retried once local storage is.
|
|
752
|
+
*
|
|
753
|
+
* Broadcasts nothing, but does write locally: the lockup is registered with the
|
|
754
|
+
* wallet's contract manager before the address is returned, exactly as
|
|
755
|
+
* `createOffer` registers its covenant — so the lockup is watched from the
|
|
756
|
+
* moment it lands and out of generic coin selection, and a persistence failure
|
|
757
|
+
* throws while nothing is funded. `RfqSwapManager` re-registers as a backstop
|
|
758
|
+
* for older records; a repeat write is a no-op.
|
|
759
|
+
*
|
|
760
|
+
* Allocates a fresh `sender` key per call and returns it as `senderPubkey`
|
|
761
|
+
* plus `secrets`. On an HD wallet `secrets` holds only a public descriptor and
|
|
762
|
+
* nothing needs protecting; otherwise it holds the raw key and the caller MUST
|
|
763
|
+
* persist it, or every interactive refund path is gone. `nonInteractiveRefund`
|
|
764
|
+
* still recovers the funds without it — but it needs the SOLVER's active
|
|
765
|
+
* cooperation, not just infrastructure uptime, so losing the key with an
|
|
766
|
+
* unwilling solver is a total loss.
|
|
767
|
+
*/
|
|
768
|
+
declare function requestLightningSend(wallet: IWallet, arkServerUrl: string,
|
|
769
|
+
/** Covenant co-signer (emulator) x-only key — the SOLVER's deployment,
|
|
770
|
+
* not the trader's. This library does NOT fetch or verify it: clients
|
|
771
|
+
* have no network path to the emulator, only the solver and covclaimd
|
|
772
|
+
* do. The caller must obtain this out-of-band, before calling this
|
|
773
|
+
* function, from the solver's signed registry/corridor card (its
|
|
774
|
+
* `emulator_pubkey`, added in arkade-os/solver-registry#18) or an
|
|
775
|
+
* equivalent source it
|
|
776
|
+
* independently trusts, and is responsible for having checked it against
|
|
777
|
+
* that trusted value itself — the same never-trust-only-compare rule as
|
|
778
|
+
* {@link verifyLockupAddress}, just applied by the caller instead of
|
|
779
|
+
* here, because there is nothing in this module to derive it against. */
|
|
780
|
+
emulatorPubkey: Uint8Array, transport: RfqTransport, params: {
|
|
781
|
+
invoice: InvoiceFacts;
|
|
782
|
+
rfqId?: string;
|
|
783
|
+
}): Promise<{
|
|
784
|
+
rfqId: string;
|
|
785
|
+
quote: RfqQuote;
|
|
786
|
+
/** The trader's OWN derivation — the only address to fund. */
|
|
787
|
+
address: string;
|
|
788
|
+
/** What the lockup must carry: the quote's `from_amount` (the invoice
|
|
789
|
+
* amount plus the corridor's fee), in sats. */
|
|
790
|
+
fundAmount: number;
|
|
791
|
+
/** The covenant's scriptPubKey, for watching the lockup and its spend. */
|
|
792
|
+
swapPkScript: Uint8Array;
|
|
793
|
+
/** The covenant itself. Hand it to `RfqSwapManager` as the record's
|
|
794
|
+
* `lockup` (with `address`): without it the manager can only poll, and
|
|
795
|
+
* cannot retire the row this call just wrote. */
|
|
796
|
+
script: InstanceType<typeof VHTLC.ScriptV2>;
|
|
797
|
+
/** Where a failed swap refunds. */
|
|
798
|
+
refundAddress: string;
|
|
799
|
+
/** The VHTLC `sender` x-only key, bound into the covenant. Public. */
|
|
800
|
+
senderPubkey: Uint8Array;
|
|
801
|
+
/** How the `sender` key is recovered later. Persist it with the record —
|
|
802
|
+
* on the derivable arm it holds nothing secret. */
|
|
803
|
+
secrets: SwapSecrets;
|
|
804
|
+
}>;
|
|
805
|
+
/**
|
|
806
|
+
* Map an arkade↔arkade quote onto `createOffer` terms. The trader takes the
|
|
807
|
+
* quote by creating and funding the offer covenant before `valid_until` —
|
|
808
|
+
* the non-interactive fill: the covenant only releases the deposit to a
|
|
809
|
+
* transaction that delivers the quoted want-amount to the trader, so the
|
|
810
|
+
* solver fills or nothing moves. There is no rfq_fill message and no refund
|
|
811
|
+
* timelock; an unfilled offer is cancelled cooperatively (`cancelOffer`).
|
|
812
|
+
*/
|
|
813
|
+
declare const offerTermsFromQuote: (quote: RfqQuote, assets: {
|
|
814
|
+
wantAsset?: asset.AssetId;
|
|
815
|
+
offerAsset?: asset.AssetId;
|
|
816
|
+
}) => {
|
|
817
|
+
wantAmount: bigint;
|
|
818
|
+
wantAsset?: asset.AssetId;
|
|
819
|
+
offerAsset?: asset.AssetId;
|
|
820
|
+
};
|
|
821
|
+
/** The rfq_request for `arkade:BTC->onchain:BTC`. Exact-out means "this much
|
|
822
|
+
* lands in the L1 HTLC". `senderPubkey` is the user's own key for the
|
|
823
|
+
* VHTLC's sender-side leaves — same role as in {@link lightningSendRequest}.
|
|
824
|
+
* On the wire it's `client_refund_pubkey`, same as there. */
|
|
825
|
+
declare const onchainSendRequest: (input: {
|
|
826
|
+
rfqId: string;
|
|
827
|
+
/** `sha256(P)`, hex — user-chosen; see {@link paymentHashOf}. */
|
|
828
|
+
paymentHash: string;
|
|
829
|
+
/** User's x-only L1 key for the HTLC's claim leaf. */
|
|
830
|
+
payoutPubkey: Uint8Array;
|
|
831
|
+
/** User's arkade address — where the covenant refund must pay. */
|
|
832
|
+
refundAddress: string;
|
|
833
|
+
senderPubkey: Uint8Array;
|
|
834
|
+
amount: number;
|
|
835
|
+
amountSide: "from" | "to";
|
|
836
|
+
}) => Record<string, unknown>;
|
|
837
|
+
/** The rfq_request for `lightning:BTC->arkade:BTC`: pay the solver's hold
|
|
838
|
+
* invoice, land on Arkade. The trader generates `P`, keeps it, and sends only
|
|
839
|
+
* `H` plus `P` sealed to covclaimd — the solver never sees `P` until it
|
|
840
|
+
* appears in a claim witness. `payoutPubkey` is the trader's own x-only
|
|
841
|
+
* Arkade key — the covenant's `receiver` role on this leg, so the trader can
|
|
842
|
+
* claim the lockup itself without covclaimd. */
|
|
843
|
+
declare const lightningReceiveRequest: (input: {
|
|
844
|
+
rfqId: string;
|
|
845
|
+
/** `H = sha256(P)`, hex — trader-chosen; see {@link paymentHashOf}. */
|
|
846
|
+
paymentHash: string;
|
|
847
|
+
/** Trader's arkade address — where the swapped sats land. */
|
|
848
|
+
payoutAddress: string;
|
|
849
|
+
/** Trader's x-only arkade key — the covenant's `receiver` role. */
|
|
850
|
+
payoutPubkey: Uint8Array;
|
|
851
|
+
/** `P` sealed to covclaimd, base64 — `sealClaimPacket(...).ciphertext`. */
|
|
852
|
+
claimPacket: string;
|
|
853
|
+
amount: number;
|
|
854
|
+
amountSide: "from" | "to";
|
|
855
|
+
}) => Record<string, unknown>;
|
|
856
|
+
/** The rfq_request for `onchain:BTC->arkade:BTC`. The user funds the L1 HTLC
|
|
857
|
+
* (holding its refund role) and receives Arkade; P travels sealed to
|
|
858
|
+
* covclaimd (see `sealClaimPacket`) so the user can go offline after
|
|
859
|
+
* funding. `payoutPubkey` is the trader's own x-only Arkade key — the
|
|
860
|
+
* covenant's `receiver` role, same as the Lightning receive leg's. */
|
|
861
|
+
declare const onchainReceiveRequest: (input: {
|
|
862
|
+
rfqId: string;
|
|
863
|
+
paymentHash: string;
|
|
864
|
+
/** Trader's arkade address — where the swapped sats land. */
|
|
865
|
+
payoutAddress: string;
|
|
866
|
+
/** Trader's x-only arkade key — the covenant's `receiver` role. */
|
|
867
|
+
payoutPubkey: Uint8Array;
|
|
868
|
+
/** Trader's x-only L1 key for the HTLC's refund leaf. */
|
|
869
|
+
refundPubkey: Uint8Array;
|
|
870
|
+
/** `P` sealed to covclaimd, base64 — `sealClaimPacket(...).ciphertext`. */
|
|
871
|
+
claimPacket: string;
|
|
872
|
+
amount: number;
|
|
873
|
+
amountSide: "from" | "to";
|
|
874
|
+
}) => Record<string, unknown>;
|
|
875
|
+
/**
|
|
876
|
+
* The pure core of {@link requestOnchainSend}: derive BOTH contracts locally
|
|
877
|
+
* from the quote's binding fields plus the user's own data, and refuse on any
|
|
878
|
+
* mismatch. Binding: `solver_pubkey`, `refund_locktime`, `htlc_pubkey`,
|
|
879
|
+
* `htlc_locktime`, `min_confirmations`; `lockup_address` and `htlc_address`
|
|
880
|
+
* are compare-only.
|
|
881
|
+
*/
|
|
882
|
+
declare function deriveOnchainSend(input: {
|
|
883
|
+
quote: RfqQuote;
|
|
884
|
+
paymentHash: string;
|
|
885
|
+
payoutPubkey: Uint8Array;
|
|
886
|
+
serverPubkey: Uint8Array;
|
|
887
|
+
emulatorPubkey: Uint8Array;
|
|
888
|
+
claimDelay: number;
|
|
889
|
+
hrp: string;
|
|
890
|
+
l1Network: OnchainNetwork;
|
|
891
|
+
refundAddress: string;
|
|
892
|
+
/** The user's own key for the VHTLC's sender-side leaves — same role as
|
|
893
|
+
* in {@link requestLightningSend}. */
|
|
894
|
+
senderPubkey: Uint8Array;
|
|
895
|
+
}): {
|
|
896
|
+
address: string;
|
|
897
|
+
swapPkScript: Uint8Array;
|
|
898
|
+
/** The lockup covenant itself — what the contract row is registered from,
|
|
899
|
+
* so the row can never key on a script other than the derived one. */
|
|
900
|
+
script: InstanceType<typeof VHTLC.ScriptV2>;
|
|
901
|
+
htlc: OnchainHtlc;
|
|
902
|
+
refundLocktime: number;
|
|
903
|
+
htlcLocktime: number;
|
|
904
|
+
minConfirmations: number;
|
|
905
|
+
};
|
|
906
|
+
/**
|
|
907
|
+
* The `arkade:BTC->onchain:BTC` user flow, mirroring `requestLightningSend`:
|
|
908
|
+
* quote → derive BOTH contracts locally → verify → gate. Pure of funding —
|
|
909
|
+
* the caller funds `address` with its own wallet before `quote.valid_until`.
|
|
910
|
+
*
|
|
911
|
+
* Registers the arkade lockup before returning the address, on the same terms
|
|
912
|
+
* as {@link requestLightningSend} — including {@link LockupRegistrationFailed},
|
|
913
|
+
* the one throw here that does not mean "walk away from this quote". The L1
|
|
914
|
+
* HTLC is not a contract row: it lives on bitcoin, not on Ark, and the wallet's
|
|
915
|
+
* contract manager knows nothing of it.
|
|
916
|
+
*
|
|
917
|
+
* Two obligations, both LOUD:
|
|
918
|
+
* - **Persist `secrets` (with the record) BEFORE funding.** On an HD wallet it
|
|
919
|
+
* is a public descriptor and both the preimage and the `sender` key
|
|
920
|
+
* re-derive from the seed; otherwise it carries the raw secrets, and losing
|
|
921
|
+
* them forfeits the L1 claim and every interactive refund path — leaving
|
|
922
|
+
* recovery dependent on the solver via `nonInteractiveRefund`.
|
|
923
|
+
* - **Stay claim-capable.** Unlike lightning-send the user cannot go fully
|
|
924
|
+
* offline: it must claim the L1 HTLC (`awaitOnchainFill` →
|
|
925
|
+
* `claimOnchainFill`) before `htlc.refundLocktime`. Missing that window
|
|
926
|
+
* forfeits the fill and falls back to the Arkade covenant refund.
|
|
927
|
+
*/
|
|
928
|
+
declare function requestOnchainSend(wallet: IWallet, arkServerUrl: string,
|
|
929
|
+
/** Covenant co-signer (emulator) x-only key — same parameter, same
|
|
930
|
+
* caller obligation, as {@link requestLightningSend}'s. */
|
|
931
|
+
emulatorPubkey: Uint8Array, transport: RfqTransport, params: {
|
|
932
|
+
amount: number;
|
|
933
|
+
amountSide: "from" | "to";
|
|
934
|
+
/** User's x-only L1 key that will claim the HTLC. */
|
|
935
|
+
payoutPubkey: Uint8Array;
|
|
936
|
+
/** Optional caller-owned P. Persist it with the returned secrets before funding. */
|
|
937
|
+
preimage?: Uint8Array;
|
|
938
|
+
rfqId?: string;
|
|
939
|
+
}): Promise<{
|
|
940
|
+
rfqId: string;
|
|
941
|
+
quote: RfqQuote;
|
|
942
|
+
/** The user's OWN arkade lockup derivation — the only address to fund. */
|
|
943
|
+
address: string;
|
|
944
|
+
fundAmount: number;
|
|
945
|
+
swapPkScript: Uint8Array;
|
|
946
|
+
/** The arkade covenant itself — the record's `lockup` for
|
|
947
|
+
* `RfqSwapManager`, same role as {@link requestLightningSend}'s. */
|
|
948
|
+
script: InstanceType<typeof VHTLC.ScriptV2>;
|
|
949
|
+
refundAddress: string;
|
|
950
|
+
/** The EXPECTED L1 fill, derived locally — watch and claim against this. */
|
|
951
|
+
htlc: OnchainHtlc;
|
|
952
|
+
/** The VHTLC `sender` x-only key, bound into the covenant. Public. */
|
|
953
|
+
senderPubkey: Uint8Array;
|
|
954
|
+
/** How the preimage and the `sender` key are recovered later. Persist it
|
|
955
|
+
* with the record BEFORE funding. */
|
|
956
|
+
secrets: SwapSecrets;
|
|
957
|
+
}>;
|
|
958
|
+
/** Default floor for the window between the last moment the hold invoice can
|
|
959
|
+
* be paid and the solver's refund leaf opening. */
|
|
960
|
+
declare const MIN_CLAIM_WINDOW_SECONDS: number;
|
|
961
|
+
/**
|
|
962
|
+
* Bind the SOLVER's hold invoice to the quote and to the trader's own `H`.
|
|
963
|
+
*
|
|
964
|
+
* This is the only field the trader hands to a third party, and the only
|
|
965
|
+
* attack on this corridor with no on-chain trace: an invoice on some other
|
|
966
|
+
* payment hash is paid to the solver in full and no lockup on `H` is ever
|
|
967
|
+
* funded. NEVER publish an invoice that has not passed this.
|
|
968
|
+
*
|
|
969
|
+
* The decoder is injected — `@arkade-os/swap` takes no BOLT11 dependency — but
|
|
970
|
+
* unlike {@link requestLightningSend}, which takes the caller's facts about
|
|
971
|
+
* the caller's OWN invoice, the comparison lives here: a caller-supplied
|
|
972
|
+
* summary of an adversary's invoice checks nothing.
|
|
973
|
+
*
|
|
974
|
+
* There is no check for "is this actually a hold invoice": on the wire it is
|
|
975
|
+
* indistinguishable from an ordinary one.
|
|
976
|
+
*
|
|
977
|
+
* Reasons: `invoice_undecodable` | `invoice_hash_mismatch` |
|
|
978
|
+
* `invoice_amount_mismatch` | `quote_malformed`.
|
|
979
|
+
*/
|
|
980
|
+
declare const verifyReceiveInvoice: (input: {
|
|
981
|
+
invoice: string;
|
|
982
|
+
decode: (bolt11: string) => InvoiceFacts;
|
|
983
|
+
/** `sha256(P)`, hex — the trader's OWN. */
|
|
984
|
+
paymentHash: string;
|
|
985
|
+
quote: RfqQuote;
|
|
986
|
+
}) => {
|
|
987
|
+
payDeadline: number;
|
|
988
|
+
};
|
|
989
|
+
/**
|
|
990
|
+
* The receive leg's gate, checked before the invoice is published. Separate
|
|
991
|
+
* from {@link assertFundable} because the semantics invert: `refund_locktime`
|
|
992
|
+
* belongs to the SOLVER here, so BIP-113's median-time-past lag extends the
|
|
993
|
+
* trader's claim window instead of shrinking it. What can actually run out is
|
|
994
|
+
* the hold invoice's own window — minutes, not the quote's hour — which is why
|
|
995
|
+
* the claim window is measured from `payDeadline`, the last moment a payer can
|
|
996
|
+
* arm the swap, and not from `now`.
|
|
997
|
+
*
|
|
998
|
+
* `maxPayAmount` is an opt-in absolute ceiling on what the payer is asked for:
|
|
999
|
+
* `assertQuotedAmount` pins the side the request named, so with
|
|
1000
|
+
* `amountSide: "to"` the price is the free variable. Optional because a bad
|
|
1001
|
+
* price is visible to the caller before anything is published — unlike an
|
|
1002
|
+
* opaque invoice or an underfunded lockup.
|
|
1003
|
+
*
|
|
1004
|
+
* Reasons: `quote_expired` | `missing_refund_locktime` | `claim_window_too_short` |
|
|
1005
|
+
* `price_too_high` | `quote_malformed` | `invalid_gate_input`.
|
|
1006
|
+
*/
|
|
1007
|
+
declare const assertReceivable: (input: {
|
|
1008
|
+
quote: RfqQuote;
|
|
1009
|
+
/** From {@link verifyReceiveInvoice}: `min(invoice expiry, valid_until)`. */
|
|
1010
|
+
payDeadline: number;
|
|
1011
|
+
now: number;
|
|
1012
|
+
minClaimWindowSeconds?: number;
|
|
1013
|
+
/** Absolute sats ceiling on `from_amount`. */
|
|
1014
|
+
maxPayAmount?: number;
|
|
1015
|
+
}) => void;
|
|
1016
|
+
/** Compile the RECEIVE-direction VHTLC: the same eight-leaf tree as {@link
|
|
1017
|
+
* lightningSendVtxoScript} with the roles inverted — the trader is the
|
|
1018
|
+
* `receiver` (it generated `P` and claims the lockup with it), the solver is
|
|
1019
|
+
* the `sender` (it funds the lockup and holds the refund recourse). One
|
|
1020
|
+
* function shared by both receive corridors, mirroring the send legs' sharing
|
|
1021
|
+
* of `lightningSendVtxoScript`. */
|
|
1022
|
+
declare function receiveVtxoScript(params: {
|
|
1023
|
+
/** Binding field #1: the solver's x-only key, from the quote — VHTLC's
|
|
1024
|
+
* `sender` role on the receive corridors. */
|
|
1025
|
+
solverPubkey: Uint8Array;
|
|
1026
|
+
/** Binding field #2: the SOLVER's own refund deadline on these legs, from
|
|
1027
|
+
* the quote — after it the solver may reclaim an unclaimed lockup. */
|
|
1028
|
+
refundLocktime: number;
|
|
1029
|
+
/** The Ark server's x-only key — the trader's OWN connection. */
|
|
1030
|
+
serverPubkey: Uint8Array;
|
|
1031
|
+
/** `sha256(P)`, hex — the trader's OWN preimage hash. */
|
|
1032
|
+
paymentHash: string;
|
|
1033
|
+
/** From {@link unilateralClaimDelay} over the trader's OWN server info. */
|
|
1034
|
+
claimDelay: number;
|
|
1035
|
+
/** Emulator x-only key — see {@link requestLightningSend}'s parameter. */
|
|
1036
|
+
emulatorPubkey: Uint8Array;
|
|
1037
|
+
/** The solver's covenant refund destination, from the quote
|
|
1038
|
+
* (`profile.solver_refund_pk_script`) — the one tree parameter nothing
|
|
1039
|
+
* else on the wire determines. */
|
|
1040
|
+
solverRefundPkScript: Uint8Array;
|
|
1041
|
+
/** The trader's own x-only Arkade key — VHTLC's `receiver` role on these
|
|
1042
|
+
* legs, so the trader can claim without covclaimd. */
|
|
1043
|
+
payoutPubkey: Uint8Array;
|
|
1044
|
+
/** The trader's own Arkade payout pkScript (decoded from its payout
|
|
1045
|
+
* address) — `nonInteractiveClaim`'s pinned destination. */
|
|
1046
|
+
payoutPkScript: Uint8Array;
|
|
1047
|
+
}): InstanceType<typeof VHTLC.ScriptV2>;
|
|
1048
|
+
/**
|
|
1049
|
+
* The pure core of {@link requestLightningReceive}: derive the solver-funded
|
|
1050
|
+
* covenant locally from the quote's binding fields plus the trader's own data
|
|
1051
|
+
* and refuse on any address mismatch. The trader funds nothing on Arkade on
|
|
1052
|
+
* this leg — verification is still what makes paying the hold invoice safe:
|
|
1053
|
+
* the lockup the solver will fund must be the tree whose claim paths pay the
|
|
1054
|
+
* trader.
|
|
1055
|
+
*/
|
|
1056
|
+
declare function deriveLightningReceive(input: {
|
|
1057
|
+
quote: RfqQuote;
|
|
1058
|
+
paymentHash: string;
|
|
1059
|
+
payoutPubkey: Uint8Array;
|
|
1060
|
+
payoutAddress: string;
|
|
1061
|
+
serverPubkey: Uint8Array;
|
|
1062
|
+
emulatorPubkey: Uint8Array;
|
|
1063
|
+
claimDelay: number;
|
|
1064
|
+
hrp: string;
|
|
1065
|
+
}): {
|
|
1066
|
+
address: string;
|
|
1067
|
+
swapPkScript: Uint8Array;
|
|
1068
|
+
script: InstanceType<typeof VHTLC.ScriptV2>;
|
|
1069
|
+
/** The solver's hold invoice on `H` — what the trader pays to arm the swap. */
|
|
1070
|
+
invoice: string;
|
|
1071
|
+
refundLocktime: number;
|
|
1072
|
+
};
|
|
1073
|
+
/**
|
|
1074
|
+
* The `lightning:BTC->arkade:BTC` user flow: quote → derive the covenant
|
|
1075
|
+
* locally → verify → gate. Returns the solver's hold invoice to PAY — the
|
|
1076
|
+
* payment itself is the trader's own Lightning wallet's job, exactly as
|
|
1077
|
+
* funding is the caller's job on the send corridors. Once the paid HTLC is
|
|
1078
|
+
* held, the solver funds the lockup; the trader claims it with `P` (its own,
|
|
1079
|
+
* generated here) — itself via the collaborative claim leaf
|
|
1080
|
+
* ({@link claimReceiveLockup} in `claim.ts`), or via covclaimd if it is
|
|
1081
|
+
* offline.
|
|
1082
|
+
*
|
|
1083
|
+
* Three obligations, all of them before the invoice is handed to a payer:
|
|
1084
|
+
*
|
|
1085
|
+
* 1. Persist `secrets` and `expectedAmount`. The preimage and the payout key
|
|
1086
|
+
* re-derive from `secrets` (or, on a non-HD wallet, are carried by it), and
|
|
1087
|
+
* without `expectedAmount` the claim has nothing to compare the funded
|
|
1088
|
+
* value against.
|
|
1089
|
+
* 2. Stay online. covclaimd cannot claim this covenant today, so the offline
|
|
1090
|
+
* path the claim packet exists for does not run yet: an unclaimed lockup is
|
|
1091
|
+
* reclaimed by the solver at `refund_locktime` and the payer refunded.
|
|
1092
|
+
* 3. On {@link LockupRegistrationFailed}, call this function again once the
|
|
1093
|
+
* store is working. The failed attempt is inert — it returned no invoice,
|
|
1094
|
+
* so nothing can be paid into the lockup nobody is watching — and the new
|
|
1095
|
+
* call derives its own preimage and `rfq_id`. Re-registering `error.script`
|
|
1096
|
+
* does NOT resume it: the invoice and `secrets` were never handed back.
|
|
1097
|
+
*
|
|
1098
|
+
* The invoice is the solver's, so it is verified here against the trader's own
|
|
1099
|
+
* `H` and the quote ({@link verifyReceiveInvoice}) before it is returned —
|
|
1100
|
+
* nothing publishable comes back from a failed check, registration included.
|
|
1101
|
+
* Pay before `invoiceExpiresAt`: the hold-invoice window is minutes, not the
|
|
1102
|
+
* quote's `valid_until`.
|
|
1103
|
+
*/
|
|
1104
|
+
declare function requestLightningReceive(wallet: IWallet, arkServerUrl: string,
|
|
1105
|
+
/** Covenant co-signer (emulator) x-only key — same parameter, same
|
|
1106
|
+
* caller obligation, as {@link requestLightningSend}'s. */
|
|
1107
|
+
emulatorPubkey: Uint8Array, transport: RfqTransport, params: {
|
|
1108
|
+
amount: number;
|
|
1109
|
+
amountSide: "from" | "to";
|
|
1110
|
+
/** covclaimd's 33-byte compressed pubkey (from its own info endpoint)
|
|
1111
|
+
* — the claim packet seals to it and only it can ever read `P` early. */
|
|
1112
|
+
covclaimdPubkey: Uint8Array;
|
|
1113
|
+
/** The caller's own BOLT11 decoder, applied to the SOLVER's invoice.
|
|
1114
|
+
* Required: an optional verifier is one integrators skip, and this is
|
|
1115
|
+
* the check whose absence loses the whole payment. */
|
|
1116
|
+
decodeInvoice: (bolt11: string) => InvoiceFacts;
|
|
1117
|
+
/** Opt-in ceiling, in sats, on what the payer will be asked for. */
|
|
1118
|
+
maxPayAmount?: number;
|
|
1119
|
+
rfqId?: string;
|
|
1120
|
+
}): Promise<{
|
|
1121
|
+
rfqId: string;
|
|
1122
|
+
quote: RfqQuote;
|
|
1123
|
+
/** The solver's hold invoice — what the trader pays, for `payAmount`.
|
|
1124
|
+
* Verified against this swap's `H` and the quote's `from_amount`. */
|
|
1125
|
+
invoice: string;
|
|
1126
|
+
/** What the trader pays: the quote's `from_amount`. */
|
|
1127
|
+
payAmount: number;
|
|
1128
|
+
/** What the solver's lockup must carry: the quote's `to_amount`. Persist
|
|
1129
|
+
* it with the record — `pushClaim` refuses to publish `P` for less, and
|
|
1130
|
+
* captured at claim time instead it would be whatever the solver funded. */
|
|
1131
|
+
expectedAmount: number;
|
|
1132
|
+
/** Last moment the invoice can be paid, unix seconds: `min(invoice
|
|
1133
|
+
* expiry, valid_until)`. Absolute on purpose — a countdown returned from
|
|
1134
|
+
* here is stale before the caller reads it; derive one at display time. */
|
|
1135
|
+
invoiceExpiresAt: number;
|
|
1136
|
+
/** The trader's OWN derivation of the lockup the solver must fund. */
|
|
1137
|
+
address: string;
|
|
1138
|
+
swapPkScript: Uint8Array;
|
|
1139
|
+
script: InstanceType<typeof VHTLC.ScriptV2>;
|
|
1140
|
+
payoutAddress: string;
|
|
1141
|
+
/** The trader's covenant `receiver` key, bound into the tree. Public. */
|
|
1142
|
+
payoutPubkey: Uint8Array;
|
|
1143
|
+
/** How the preimage and the payout key are recovered later. Persist it
|
|
1144
|
+
* with the record BEFORE paying the invoice. */
|
|
1145
|
+
secrets: SwapSecrets;
|
|
1146
|
+
}>;
|
|
1147
|
+
/**
|
|
1148
|
+
* The pure core of {@link requestOnchainReceive}: derive BOTH contracts
|
|
1149
|
+
* locally — the solver-funded Arkade covenant and the L1 HTLC the trader
|
|
1150
|
+
* funds — and refuse on any mismatch. Binding: `solver_pubkey`,
|
|
1151
|
+
* `refund_locktime`, `claim_pubkey`, `htlc_locktime`, `min_confirmations`;
|
|
1152
|
+
* `lockup_address` and `htlc_address` are compare-only.
|
|
1153
|
+
*/
|
|
1154
|
+
declare function deriveOnchainReceive(input: {
|
|
1155
|
+
quote: RfqQuote;
|
|
1156
|
+
paymentHash: string;
|
|
1157
|
+
payoutPubkey: Uint8Array;
|
|
1158
|
+
payoutAddress: string;
|
|
1159
|
+
/** The trader's own x-only L1 key — the HTLC's refund role. */
|
|
1160
|
+
refundPubkey: Uint8Array;
|
|
1161
|
+
serverPubkey: Uint8Array;
|
|
1162
|
+
emulatorPubkey: Uint8Array;
|
|
1163
|
+
claimDelay: number;
|
|
1164
|
+
hrp: string;
|
|
1165
|
+
l1Network: OnchainNetwork;
|
|
1166
|
+
}): {
|
|
1167
|
+
address: string;
|
|
1168
|
+
swapPkScript: Uint8Array;
|
|
1169
|
+
script: InstanceType<typeof VHTLC.ScriptV2>;
|
|
1170
|
+
/** The L1 HTLC the trader funds, derived locally — fund only this. */
|
|
1171
|
+
htlc: OnchainHtlc;
|
|
1172
|
+
refundLocktime: number;
|
|
1173
|
+
htlcLocktime: number;
|
|
1174
|
+
minConfirmations: number;
|
|
1175
|
+
};
|
|
1176
|
+
/**
|
|
1177
|
+
* The `onchain:BTC->arkade:BTC` user flow: quote → derive BOTH contracts
|
|
1178
|
+
* locally → verify → gate. Returns the L1 HTLC to fund (`htlc.address`, for
|
|
1179
|
+
* `fundAmount`) — the funding transaction itself is the trader's own L1
|
|
1180
|
+
* wallet's job, exactly as on the send corridors. After `min_confirmations`
|
|
1181
|
+
* the solver funds the Arkade lockup; the trader claims it with `P` (its
|
|
1182
|
+
* own), itself or via covclaimd.
|
|
1183
|
+
*
|
|
1184
|
+
* Persist `secrets` BEFORE funding, and note the direction's own deadline:
|
|
1185
|
+
* if the swap never settles, the L1 HTLC's refund leaf (the trader's
|
|
1186
|
+
* `refundPubkey`) opens at `htlc.refundLocktime` — `buildHtlcRefund` takes it
|
|
1187
|
+
* back from there.
|
|
1188
|
+
*/
|
|
1189
|
+
declare function requestOnchainReceive(wallet: IWallet, arkServerUrl: string,
|
|
1190
|
+
/** Covenant co-signer (emulator) x-only key — same parameter, same
|
|
1191
|
+
* caller obligation, as {@link requestLightningSend}'s. */
|
|
1192
|
+
emulatorPubkey: Uint8Array, transport: RfqTransport, params: {
|
|
1193
|
+
amount: number;
|
|
1194
|
+
amountSide: "from" | "to";
|
|
1195
|
+
/** Trader's x-only L1 key for the HTLC's refund leaf. */
|
|
1196
|
+
refundPubkey: Uint8Array;
|
|
1197
|
+
/** covclaimd's 33-byte compressed pubkey — see {@link requestLightningReceive}. */
|
|
1198
|
+
covclaimdPubkey: Uint8Array;
|
|
1199
|
+
rfqId?: string;
|
|
1200
|
+
}): Promise<{
|
|
1201
|
+
rfqId: string;
|
|
1202
|
+
quote: RfqQuote;
|
|
1203
|
+
/** The trader's OWN derivation of the lockup the solver must fund. */
|
|
1204
|
+
address: string;
|
|
1205
|
+
/** What the trader's L1 funding must carry: the quote's `from_amount`. */
|
|
1206
|
+
fundAmount: number;
|
|
1207
|
+
/** What the solver's lockup must carry: the quote's `to_amount`. Persist
|
|
1208
|
+
* it with the record — see {@link requestLightningReceive}. */
|
|
1209
|
+
expectedAmount: number;
|
|
1210
|
+
swapPkScript: Uint8Array;
|
|
1211
|
+
script: InstanceType<typeof VHTLC.ScriptV2>;
|
|
1212
|
+
/** The EXPECTED L1 contract, derived locally — fund only this address. */
|
|
1213
|
+
htlc: OnchainHtlc;
|
|
1214
|
+
payoutAddress: string;
|
|
1215
|
+
payoutPubkey: Uint8Array;
|
|
1216
|
+
secrets: SwapSecrets;
|
|
1217
|
+
}>;
|
|
1218
|
+
|
|
1219
|
+
export { buildPreimageMessage as $, type AssetSwapRepository as A, BTC_ASSET_ID as B, type ChainSource as C, type DerivedSwapSecrets as D, RFQ_TERMINAL_STATES as E, type RefundBlockedReason as F, RefundNotLocallyPossibleError as G, type HtlcUtxo as H, InMemoryAssetSwapRepository as I, type RelaySocket as J, type RfqQuote as K, LIGHTNING_BTC as L, type MarketsCacheEntry as M, type RfqRefusalReason as N, type OnchainHtlc as O, SwapRefusal as P, type SwapSecrets as Q, type RfqStatus as R, type StoredSwapSecrets as S, addAssetSwap as T, adoptSwapDescriptor as U, arkadeSwapRequest as V, assertFundable as W, assertReceivable as X, awaitOnchainFill as Y, buildHtlcClaim as Z, buildHtlcRefund as _, type AssetSwap as a, claimOnchainFill as a0, classifyOnchainHtlc as a1, deriveLightningReceive as a2, deriveOnchainReceive as a3, deriveOnchainSend as a4, derivePreimage as a5, deriveSwapSecrets as a6, extractPreimage as a7, getAssetSwaps as a8, getAssetSwapsOrThrow as a9, unilateralClaimDelay as aA, unilateralRefundDelay as aB, unilateralRefundWithoutReceiverDelay as aC, updateAssetSwap as aD, updateAssetSwapBestEffort as aE, verifyLockupAddress as aF, verifyReceiveInvoice as aG, httpTransport as aa, isDeterministicSigner as ab, lightningReceiveRequest as ac, lightningSendRequest as ad, lightningSendVtxoScript as ae, newPreimage as af, newRfqId as ag, offerTermsFromQuote as ah, onchainHtlcScript as ai, onchainReceiveRequest as aj, onchainSendRequest as ak, paymentHashOf as al, preimageForRfqSecrets as am, randomSwapSecrets as an, receiveVtxoScript as ao, relayTransport as ap, requestLightningReceive as aq, requestLightningSend as ar, requestOnchainReceive as as, requestOnchainSend as at, rfqPair as au, rfqSecretsOfRecord as av, rfqSecretsToRecord as aw, senderIdentityForRfqSecrets as ax, senderIdentityForSwapRecord as ay, senderPubkeyForRfqSecrets as az, type RfqTransport as b, type ChainUtxo as c, type OnchainHtlcPhase as d, ARKADE_ASSET as e, ARKADE_BTC as f, AddressMismatch as g, type AssetSwapFallbackSecrets as h, type AssetSwapStatus as i, type DeterministicSigner as j, type InvoiceFacts as k, LIGHTNING_RECEIVE_PAIR as l, LIGHTNING_SEND_PAIR as m, MAX_MIN_CONFIRMATIONS as n, MIN_CLAIM_WINDOW_SECONDS as o, MIN_HEADROOM_SECONDS as p, ONCHAIN_BTC as q, ONCHAIN_CLAIM_MARGIN_SECONDS as r, ONCHAIN_DUST_SATS as s, ONCHAIN_ORDER_MARGIN_SECONDS as t, ONCHAIN_RECEIVE_PAIR as u, ONCHAIN_SECONDS_PER_BLOCK as v, ONCHAIN_SEND_PAIR as w, type OnchainHtlcParams as x, type OnchainNetwork as y, RFQ_PREIMAGE_TAG as z };
|