@arkade-os/swap 0.0.9 → 0.0.11
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 +17 -0
- package/dist/{chunk-2FEMUOIH.js → chunk-RIC5ZTZK.js} +71 -33
- package/dist/index.cjs +235 -52
- package/dist/index.d.cts +61 -15
- package/dist/index.d.ts +61 -15
- package/dist/index.js +166 -20
- package/dist/nostr.d.cts +1 -1
- package/dist/nostr.d.ts +1 -1
- package/dist/nostr.js +1 -1
- package/dist/repositories/realm/index.d.cts +2 -2
- package/dist/repositories/realm/index.d.ts +2 -2
- package/dist/repositories/sqlite/index.d.cts +2 -2
- package/dist/repositories/sqlite/index.d.ts +2 -2
- package/dist/{repository-DEHLtD9l.d.cts → repository-B02vsgcV.d.cts} +106 -12
- package/dist/{repository-DIAr5XYk.d.ts → repository-CfE18Fif.d.ts} +106 -12
- package/dist/{rfq-hbzhTWHT.d.ts → rfq-DkckzRKK.d.cts} +54 -24
- package/dist/{rfq-hbzhTWHT.d.cts → rfq-DkckzRKK.d.ts} +54 -24
- package/package.json +4 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DiscoveredMarket } from '@arkade-os/solver-discovery';
|
|
2
2
|
import { IWallet, ProvisionedKey, ProvisionedClaimSecret, RestArkProvider, RestIndexerProvider, VHTLC, Identity, IContractManager } from '@arkade-os/sdk';
|
|
3
|
-
import { x as RfqStatus, y as RfqTransport, a as OnchainHtlc, e as ChainUtxo, C as ChainSource, t as OnchainHtlcPhase } from './rfq-
|
|
3
|
+
import { x as RfqStatus, y as RfqTransport, a as OnchainHtlc, e as ChainUtxo, C as ChainSource, t as OnchainHtlcPhase } from './rfq-DkckzRKK.cjs';
|
|
4
4
|
|
|
5
5
|
type AssetSwapStatus = "pending" | "cancelling" | "fulfilled" | "cancelled" | "recoverable" | "awaiting_fill" | "claimable" | "claimed" | "refunded_l1";
|
|
6
6
|
/** The sentinel asset id for BTC itself, as opposed to a 68-hex asset id.
|
|
@@ -227,12 +227,15 @@ interface LockupVtxo {
|
|
|
227
227
|
* It is still the trader's money and it is still visible, which is why
|
|
228
228
|
* {@link findLockupVtxos} returns it. What it is not is refundable by
|
|
229
229
|
* {@link pushRefundWithoutReceiver}: that builds an offchain Ark
|
|
230
|
-
* transaction, and the SDK's own predicates
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
230
|
+
* transaction, and the SDK's own predicates put the two states on opposite
|
|
231
|
+
* sides — `canSpendOffchain` is false whenever `canRecoverOnchain` is true
|
|
232
|
+
* (`wallet/vtxo.ts`), and the latter is documented as "must be recovered
|
|
233
|
+
* into a fresh batch rather than spent offchain". Holding the trader's
|
|
234
|
+
* `sender` key does not change that; a sweep removes the leaf from the live
|
|
235
|
+
* tree, not the signature from the trader.
|
|
236
|
+
*
|
|
237
|
+
* Opposite, but not exhaustive: an unrolled output satisfies neither, and
|
|
238
|
+
* {@link findLockupVtxos} drops those before they reach this type at all.
|
|
236
239
|
*
|
|
237
240
|
* `packages/boltz-swap` splits on exactly this fact rather than working
|
|
238
241
|
* around it: `settleRefundWithoutReceiver` sends a live VTXO through an
|
|
@@ -321,6 +324,20 @@ declare class LockupNeedsRecoveryError extends Error {
|
|
|
321
324
|
* That function refuses the recoverable ones by name rather than submitting a
|
|
322
325
|
* spend the server must reject.
|
|
323
326
|
*
|
|
327
|
+
* **Unrolled outputs are the one exception, and are dropped.** A unilaterally
|
|
328
|
+
* exited output lives onchain behind its CSV; no offchain spend of any leaf can
|
|
329
|
+
* reach it, and `LockupVtxo` carries no field to say so, so a caller could not
|
|
330
|
+
* tell it apart from a live one. Whether arkd returns such an output under
|
|
331
|
+
* `spendableOnly` is not determinable from here, so the exclusion is made
|
|
332
|
+
* defensively rather than assumed. It costs the two waiting callers nothing
|
|
333
|
+
* they wanted: `awaitLockupFunding` keeps waiting for a claimable lockup
|
|
334
|
+
* instead of publishing `P` into a spend that cannot land, and
|
|
335
|
+
* `refundIfUnresolved` reports rather than grinding a doomed push to its
|
|
336
|
+
* deadline. Both that function and `RfqSwapManager` name the exit through
|
|
337
|
+
* {@link readLockupFate}, which queries unfiltered and reports it as `exited`;
|
|
338
|
+
* this drop is the second line, and what still refuses the push on a pass where
|
|
339
|
+
* the fate read learned nothing.
|
|
340
|
+
*
|
|
324
341
|
* This read — not the RFQ's reported state — is the authority on whether
|
|
325
342
|
* there is anything left at the lockup.
|
|
326
343
|
*
|
|
@@ -374,6 +391,29 @@ type LockupFate =
|
|
|
374
391
|
fate: "returned";
|
|
375
392
|
spends: readonly LockupSpend[];
|
|
376
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* At least one output was unilaterally exited: it sits onchain under the
|
|
396
|
+
* VHTLC script, where no offchain claim or refund can reach it.
|
|
397
|
+
*
|
|
398
|
+
* Not terminal, and not a loss. The money is still under the same script
|
|
399
|
+
* with the same leaves, so `completeUnroll` plus an onchain spend can still
|
|
400
|
+
* end the swap either way — which is why this outranks `open`: an output
|
|
401
|
+
* that is "still unspent" but unreachable is not a swap that is merely
|
|
402
|
+
* running.
|
|
403
|
+
*
|
|
404
|
+
* It outranks a verdict too, on a lockup where a sibling output was claimed
|
|
405
|
+
* or returned. That is the rule `open` already sets, not a new one: the
|
|
406
|
+
* unspent test short-circuits before any witness is read, so a partially
|
|
407
|
+
* resolved lockup has never reported `claimed`/`returned`. What changes is
|
|
408
|
+
* only that such a lockup now says why it is unresolved.
|
|
409
|
+
*/
|
|
410
|
+
| {
|
|
411
|
+
fate: "exited";
|
|
412
|
+
outpoints: readonly {
|
|
413
|
+
txid: string;
|
|
414
|
+
vout: number;
|
|
415
|
+
}[];
|
|
416
|
+
}
|
|
377
417
|
/** Nothing was learned: no outputs visible, an output spent by nothing the
|
|
378
418
|
* indexer names, a spend it could not produce, or a blob that would not
|
|
379
419
|
* decode. Never an answer. */
|
|
@@ -409,6 +449,11 @@ type LockupFate =
|
|
|
409
449
|
* response to `unknown` is the same as to `open`: keep watching, and let the
|
|
410
450
|
* refund timelock — which no outage can move — be what ends the wait.
|
|
411
451
|
*
|
|
452
|
+
* **An exit is read before anything else, and over the whole set.** It is the
|
|
453
|
+
* one fact that makes an unspent output unreachable, so it outranks `open`; and
|
|
454
|
+
* it is scanned across every output rather than in outpoint order, so which
|
|
455
|
+
* output happens to come first cannot change the answer.
|
|
456
|
+
*
|
|
412
457
|
* Ask-the-indexer, don't-trust-local-state: read fresh on every poll, never
|
|
413
458
|
* cached, the same posture {@link findLockupVtxos} already establishes.
|
|
414
459
|
*/
|
|
@@ -447,9 +492,9 @@ declare function readLockupFate(indexer: LockupSpendIndexer, input: {
|
|
|
447
492
|
* {@link refundIfUnresolved}, which retries.
|
|
448
493
|
*
|
|
449
494
|
* **Swept outputs are refused, not attempted.** This is an OFFCHAIN spend, and
|
|
450
|
-
* a swept output is no longer a live leaf: `canSpendOffchain`
|
|
451
|
-
* `canRecoverOnchain`
|
|
452
|
-
*
|
|
495
|
+
* a swept output is no longer a live leaf: `canSpendOffchain` is false wherever
|
|
496
|
+
* `canRecoverOnchain` is true, so a recoverable input cannot be spent this way
|
|
497
|
+
* whatever key signs it (see
|
|
453
498
|
* {@link LockupVtxo.recoverable}). Because every input lands in ONE aggregate
|
|
454
499
|
* transaction, a single swept output would take the live ones down with it —
|
|
455
500
|
* so the whole push is refused with {@link LockupNeedsRecoveryError} naming the
|
|
@@ -512,6 +557,21 @@ type RefundOutcome =
|
|
|
512
557
|
outpoints: string[];
|
|
513
558
|
vtxos: LockupVtxo[];
|
|
514
559
|
status: RfqStatus | null;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* The lockup was unilaterally exited: its outputs sit onchain under the VHTLC
|
|
563
|
+
* script, where no offchain refund can reach them. Returned rather than
|
|
564
|
+
* retried: no amount of waiting changes where the money lives. Complete the
|
|
565
|
+
* unroll and spend the outputs onchain — then there is nothing left to refund.
|
|
566
|
+
*
|
|
567
|
+
* Distinct from {@link RefundOutcome} `needs_recovery` on purpose: that
|
|
568
|
+
* variant's remedy is recovery into a fresh batch, which is a spend no batch
|
|
569
|
+
* can make for an output that is already onchain.
|
|
570
|
+
*/
|
|
571
|
+
| {
|
|
572
|
+
outcome: "exited";
|
|
573
|
+
outpoints: string[];
|
|
574
|
+
status: RfqStatus | null;
|
|
515
575
|
};
|
|
516
576
|
/**
|
|
517
577
|
* Ask first, then fall back: watch the swap for the solver to resolve it, and
|
|
@@ -540,16 +600,36 @@ type RefundOutcome =
|
|
|
540
600
|
* is gone the CLTV refund is not "not yet" but "not this way", so it returns
|
|
541
601
|
* `needs_recovery` naming the outpoints rather than retrying until the
|
|
542
602
|
* deadline. Recover them and call again.
|
|
603
|
+
* - **An exited lockup ends it the same way, and is checked first.** Each pass
|
|
604
|
+
* past the deadline asks {@link readLockupFate} before reading what is
|
|
605
|
+
* refundable, so an output that has been unilaterally exited returns `exited`
|
|
606
|
+
* instead of feeding a push that cannot land. It costs one extra `getVtxos`
|
|
607
|
+
* per such pass (three where there were two), plus a `getVirtualTxs` on a
|
|
608
|
+
* fully-spent lockup; only the pass that returns `exited` saves the other two.
|
|
609
|
+
* Paid to prevent a push that would otherwise be retried to the deadline and
|
|
610
|
+
* then rethrown. A failing fate read is swallowed, not raised: it is a
|
|
611
|
+
* shortcut, and losing it must not end a wait the ordinary path could answer.
|
|
612
|
+
*
|
|
613
|
+
* A lockup funded in two sends of which only one exited reports `exited` for
|
|
614
|
+
* the whole thing and leaves the live half unrefunded. That is deliberate:
|
|
615
|
+
* `RfqSwapManager` reports the same lockup `exited` on the same any-output
|
|
616
|
+
* rule, and the two must not disagree.
|
|
543
617
|
*
|
|
544
618
|
* Safe to call late, and safe to call again: a caller recovering from a crash
|
|
545
619
|
* well past the deadline skips straight to the push, and a lockup that is
|
|
546
620
|
* already empty comes back as `nothing_to_refund` instead of an error.
|
|
547
621
|
*/
|
|
548
|
-
declare function refundIfUnresolved(transport: RfqTransport, ark: RefundArkProvider, indexer:
|
|
622
|
+
declare function refundIfUnresolved(transport: RfqTransport, ark: RefundArkProvider, indexer: LockupSpendIndexer, input: {
|
|
549
623
|
rfqId: string;
|
|
550
624
|
script: InstanceType<typeof VHTLC.ScriptV2>;
|
|
551
625
|
/** @see pushRefundWithoutReceiver */
|
|
552
626
|
sender: Identity;
|
|
627
|
+
/**
|
|
628
|
+
* `sha256(P)`, hex — the quote's `payment_hash`, as {@link readLockupFate}
|
|
629
|
+
* takes it. Not derivable from `script`, whose `preimageHash` is a
|
|
630
|
+
* `hash160` of the same secret.
|
|
631
|
+
*/
|
|
632
|
+
paymentHash: string;
|
|
553
633
|
/** `refund_locktime` from the quote, unix seconds. */
|
|
554
634
|
refundLocktime: number;
|
|
555
635
|
/** Defaults to the contract's own committed refund destination. */
|
|
@@ -1162,7 +1242,9 @@ interface RfqSwapManagerDeps {
|
|
|
1162
1242
|
* `settled`; a lockup fully spent by anything else ends it `refunded`.
|
|
1163
1243
|
* Anything the indexer could not answer is `unknown`, which is NOT an
|
|
1164
1244
|
* answer: the pass carries on to the steps below, whose deadlines an indexer
|
|
1165
|
-
* outage has no bearing on.
|
|
1245
|
+
* outage has no bearing on. `exited` — an output unilaterally taken onchain
|
|
1246
|
+
* — ends neither the swap nor the pass: it blocks the Arkade half below,
|
|
1247
|
+
* with the L1 half left running.
|
|
1166
1248
|
* 2. **Drive the trader's claim.** On an onchain send that is the L1 fill — see
|
|
1167
1249
|
* {@link nextOnchainAction}. On a receive it is the lockup itself, and it
|
|
1168
1250
|
* ends the pass: that leg has no step 3.
|
|
@@ -1493,6 +1575,18 @@ declare class RfqSwapManager {
|
|
|
1493
1575
|
private arm;
|
|
1494
1576
|
private pollSwap;
|
|
1495
1577
|
private runPass;
|
|
1578
|
+
/**
|
|
1579
|
+
* The lockup was unilaterally exited: its outputs sit onchain under the
|
|
1580
|
+
* VHTLC script, where no offchain claim or refund can reach them.
|
|
1581
|
+
*
|
|
1582
|
+
* `needs_counterparty` rather than a terminal state, because the money still
|
|
1583
|
+
* needs action and the swap can still end either way — an onchain claim can
|
|
1584
|
+
* reveal the preimage, an onchain refund can return it — and that state is
|
|
1585
|
+
* documented as re-checked every pass. It must be set from HERE and not from
|
|
1586
|
+
* inside `driveArkadeRefund`, whose two `unblock` calls would lift it again
|
|
1587
|
+
* on the very next pass.
|
|
1588
|
+
*/
|
|
1589
|
+
private blockExitedLockup;
|
|
1496
1590
|
/**
|
|
1497
1591
|
* The receive leg's whole state machine: claim the solver-funded lockup
|
|
1498
1592
|
* while the window is open, and recognise the shapes in which it can be
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DiscoveredMarket } from '@arkade-os/solver-discovery';
|
|
2
2
|
import { IWallet, ProvisionedKey, ProvisionedClaimSecret, RestArkProvider, RestIndexerProvider, VHTLC, Identity, IContractManager } from '@arkade-os/sdk';
|
|
3
|
-
import { x as RfqStatus, y as RfqTransport, a as OnchainHtlc, e as ChainUtxo, C as ChainSource, t as OnchainHtlcPhase } from './rfq-
|
|
3
|
+
import { x as RfqStatus, y as RfqTransport, a as OnchainHtlc, e as ChainUtxo, C as ChainSource, t as OnchainHtlcPhase } from './rfq-DkckzRKK.js';
|
|
4
4
|
|
|
5
5
|
type AssetSwapStatus = "pending" | "cancelling" | "fulfilled" | "cancelled" | "recoverable" | "awaiting_fill" | "claimable" | "claimed" | "refunded_l1";
|
|
6
6
|
/** The sentinel asset id for BTC itself, as opposed to a 68-hex asset id.
|
|
@@ -227,12 +227,15 @@ interface LockupVtxo {
|
|
|
227
227
|
* It is still the trader's money and it is still visible, which is why
|
|
228
228
|
* {@link findLockupVtxos} returns it. What it is not is refundable by
|
|
229
229
|
* {@link pushRefundWithoutReceiver}: that builds an offchain Ark
|
|
230
|
-
* transaction, and the SDK's own predicates
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
230
|
+
* transaction, and the SDK's own predicates put the two states on opposite
|
|
231
|
+
* sides — `canSpendOffchain` is false whenever `canRecoverOnchain` is true
|
|
232
|
+
* (`wallet/vtxo.ts`), and the latter is documented as "must be recovered
|
|
233
|
+
* into a fresh batch rather than spent offchain". Holding the trader's
|
|
234
|
+
* `sender` key does not change that; a sweep removes the leaf from the live
|
|
235
|
+
* tree, not the signature from the trader.
|
|
236
|
+
*
|
|
237
|
+
* Opposite, but not exhaustive: an unrolled output satisfies neither, and
|
|
238
|
+
* {@link findLockupVtxos} drops those before they reach this type at all.
|
|
236
239
|
*
|
|
237
240
|
* `packages/boltz-swap` splits on exactly this fact rather than working
|
|
238
241
|
* around it: `settleRefundWithoutReceiver` sends a live VTXO through an
|
|
@@ -321,6 +324,20 @@ declare class LockupNeedsRecoveryError extends Error {
|
|
|
321
324
|
* That function refuses the recoverable ones by name rather than submitting a
|
|
322
325
|
* spend the server must reject.
|
|
323
326
|
*
|
|
327
|
+
* **Unrolled outputs are the one exception, and are dropped.** A unilaterally
|
|
328
|
+
* exited output lives onchain behind its CSV; no offchain spend of any leaf can
|
|
329
|
+
* reach it, and `LockupVtxo` carries no field to say so, so a caller could not
|
|
330
|
+
* tell it apart from a live one. Whether arkd returns such an output under
|
|
331
|
+
* `spendableOnly` is not determinable from here, so the exclusion is made
|
|
332
|
+
* defensively rather than assumed. It costs the two waiting callers nothing
|
|
333
|
+
* they wanted: `awaitLockupFunding` keeps waiting for a claimable lockup
|
|
334
|
+
* instead of publishing `P` into a spend that cannot land, and
|
|
335
|
+
* `refundIfUnresolved` reports rather than grinding a doomed push to its
|
|
336
|
+
* deadline. Both that function and `RfqSwapManager` name the exit through
|
|
337
|
+
* {@link readLockupFate}, which queries unfiltered and reports it as `exited`;
|
|
338
|
+
* this drop is the second line, and what still refuses the push on a pass where
|
|
339
|
+
* the fate read learned nothing.
|
|
340
|
+
*
|
|
324
341
|
* This read — not the RFQ's reported state — is the authority on whether
|
|
325
342
|
* there is anything left at the lockup.
|
|
326
343
|
*
|
|
@@ -374,6 +391,29 @@ type LockupFate =
|
|
|
374
391
|
fate: "returned";
|
|
375
392
|
spends: readonly LockupSpend[];
|
|
376
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* At least one output was unilaterally exited: it sits onchain under the
|
|
396
|
+
* VHTLC script, where no offchain claim or refund can reach it.
|
|
397
|
+
*
|
|
398
|
+
* Not terminal, and not a loss. The money is still under the same script
|
|
399
|
+
* with the same leaves, so `completeUnroll` plus an onchain spend can still
|
|
400
|
+
* end the swap either way — which is why this outranks `open`: an output
|
|
401
|
+
* that is "still unspent" but unreachable is not a swap that is merely
|
|
402
|
+
* running.
|
|
403
|
+
*
|
|
404
|
+
* It outranks a verdict too, on a lockup where a sibling output was claimed
|
|
405
|
+
* or returned. That is the rule `open` already sets, not a new one: the
|
|
406
|
+
* unspent test short-circuits before any witness is read, so a partially
|
|
407
|
+
* resolved lockup has never reported `claimed`/`returned`. What changes is
|
|
408
|
+
* only that such a lockup now says why it is unresolved.
|
|
409
|
+
*/
|
|
410
|
+
| {
|
|
411
|
+
fate: "exited";
|
|
412
|
+
outpoints: readonly {
|
|
413
|
+
txid: string;
|
|
414
|
+
vout: number;
|
|
415
|
+
}[];
|
|
416
|
+
}
|
|
377
417
|
/** Nothing was learned: no outputs visible, an output spent by nothing the
|
|
378
418
|
* indexer names, a spend it could not produce, or a blob that would not
|
|
379
419
|
* decode. Never an answer. */
|
|
@@ -409,6 +449,11 @@ type LockupFate =
|
|
|
409
449
|
* response to `unknown` is the same as to `open`: keep watching, and let the
|
|
410
450
|
* refund timelock — which no outage can move — be what ends the wait.
|
|
411
451
|
*
|
|
452
|
+
* **An exit is read before anything else, and over the whole set.** It is the
|
|
453
|
+
* one fact that makes an unspent output unreachable, so it outranks `open`; and
|
|
454
|
+
* it is scanned across every output rather than in outpoint order, so which
|
|
455
|
+
* output happens to come first cannot change the answer.
|
|
456
|
+
*
|
|
412
457
|
* Ask-the-indexer, don't-trust-local-state: read fresh on every poll, never
|
|
413
458
|
* cached, the same posture {@link findLockupVtxos} already establishes.
|
|
414
459
|
*/
|
|
@@ -447,9 +492,9 @@ declare function readLockupFate(indexer: LockupSpendIndexer, input: {
|
|
|
447
492
|
* {@link refundIfUnresolved}, which retries.
|
|
448
493
|
*
|
|
449
494
|
* **Swept outputs are refused, not attempted.** This is an OFFCHAIN spend, and
|
|
450
|
-
* a swept output is no longer a live leaf: `canSpendOffchain`
|
|
451
|
-
* `canRecoverOnchain`
|
|
452
|
-
*
|
|
495
|
+
* a swept output is no longer a live leaf: `canSpendOffchain` is false wherever
|
|
496
|
+
* `canRecoverOnchain` is true, so a recoverable input cannot be spent this way
|
|
497
|
+
* whatever key signs it (see
|
|
453
498
|
* {@link LockupVtxo.recoverable}). Because every input lands in ONE aggregate
|
|
454
499
|
* transaction, a single swept output would take the live ones down with it —
|
|
455
500
|
* so the whole push is refused with {@link LockupNeedsRecoveryError} naming the
|
|
@@ -512,6 +557,21 @@ type RefundOutcome =
|
|
|
512
557
|
outpoints: string[];
|
|
513
558
|
vtxos: LockupVtxo[];
|
|
514
559
|
status: RfqStatus | null;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* The lockup was unilaterally exited: its outputs sit onchain under the VHTLC
|
|
563
|
+
* script, where no offchain refund can reach them. Returned rather than
|
|
564
|
+
* retried: no amount of waiting changes where the money lives. Complete the
|
|
565
|
+
* unroll and spend the outputs onchain — then there is nothing left to refund.
|
|
566
|
+
*
|
|
567
|
+
* Distinct from {@link RefundOutcome} `needs_recovery` on purpose: that
|
|
568
|
+
* variant's remedy is recovery into a fresh batch, which is a spend no batch
|
|
569
|
+
* can make for an output that is already onchain.
|
|
570
|
+
*/
|
|
571
|
+
| {
|
|
572
|
+
outcome: "exited";
|
|
573
|
+
outpoints: string[];
|
|
574
|
+
status: RfqStatus | null;
|
|
515
575
|
};
|
|
516
576
|
/**
|
|
517
577
|
* Ask first, then fall back: watch the swap for the solver to resolve it, and
|
|
@@ -540,16 +600,36 @@ type RefundOutcome =
|
|
|
540
600
|
* is gone the CLTV refund is not "not yet" but "not this way", so it returns
|
|
541
601
|
* `needs_recovery` naming the outpoints rather than retrying until the
|
|
542
602
|
* deadline. Recover them and call again.
|
|
603
|
+
* - **An exited lockup ends it the same way, and is checked first.** Each pass
|
|
604
|
+
* past the deadline asks {@link readLockupFate} before reading what is
|
|
605
|
+
* refundable, so an output that has been unilaterally exited returns `exited`
|
|
606
|
+
* instead of feeding a push that cannot land. It costs one extra `getVtxos`
|
|
607
|
+
* per such pass (three where there were two), plus a `getVirtualTxs` on a
|
|
608
|
+
* fully-spent lockup; only the pass that returns `exited` saves the other two.
|
|
609
|
+
* Paid to prevent a push that would otherwise be retried to the deadline and
|
|
610
|
+
* then rethrown. A failing fate read is swallowed, not raised: it is a
|
|
611
|
+
* shortcut, and losing it must not end a wait the ordinary path could answer.
|
|
612
|
+
*
|
|
613
|
+
* A lockup funded in two sends of which only one exited reports `exited` for
|
|
614
|
+
* the whole thing and leaves the live half unrefunded. That is deliberate:
|
|
615
|
+
* `RfqSwapManager` reports the same lockup `exited` on the same any-output
|
|
616
|
+
* rule, and the two must not disagree.
|
|
543
617
|
*
|
|
544
618
|
* Safe to call late, and safe to call again: a caller recovering from a crash
|
|
545
619
|
* well past the deadline skips straight to the push, and a lockup that is
|
|
546
620
|
* already empty comes back as `nothing_to_refund` instead of an error.
|
|
547
621
|
*/
|
|
548
|
-
declare function refundIfUnresolved(transport: RfqTransport, ark: RefundArkProvider, indexer:
|
|
622
|
+
declare function refundIfUnresolved(transport: RfqTransport, ark: RefundArkProvider, indexer: LockupSpendIndexer, input: {
|
|
549
623
|
rfqId: string;
|
|
550
624
|
script: InstanceType<typeof VHTLC.ScriptV2>;
|
|
551
625
|
/** @see pushRefundWithoutReceiver */
|
|
552
626
|
sender: Identity;
|
|
627
|
+
/**
|
|
628
|
+
* `sha256(P)`, hex — the quote's `payment_hash`, as {@link readLockupFate}
|
|
629
|
+
* takes it. Not derivable from `script`, whose `preimageHash` is a
|
|
630
|
+
* `hash160` of the same secret.
|
|
631
|
+
*/
|
|
632
|
+
paymentHash: string;
|
|
553
633
|
/** `refund_locktime` from the quote, unix seconds. */
|
|
554
634
|
refundLocktime: number;
|
|
555
635
|
/** Defaults to the contract's own committed refund destination. */
|
|
@@ -1162,7 +1242,9 @@ interface RfqSwapManagerDeps {
|
|
|
1162
1242
|
* `settled`; a lockup fully spent by anything else ends it `refunded`.
|
|
1163
1243
|
* Anything the indexer could not answer is `unknown`, which is NOT an
|
|
1164
1244
|
* answer: the pass carries on to the steps below, whose deadlines an indexer
|
|
1165
|
-
* outage has no bearing on.
|
|
1245
|
+
* outage has no bearing on. `exited` — an output unilaterally taken onchain
|
|
1246
|
+
* — ends neither the swap nor the pass: it blocks the Arkade half below,
|
|
1247
|
+
* with the L1 half left running.
|
|
1166
1248
|
* 2. **Drive the trader's claim.** On an onchain send that is the L1 fill — see
|
|
1167
1249
|
* {@link nextOnchainAction}. On a receive it is the lockup itself, and it
|
|
1168
1250
|
* ends the pass: that leg has no step 3.
|
|
@@ -1493,6 +1575,18 @@ declare class RfqSwapManager {
|
|
|
1493
1575
|
private arm;
|
|
1494
1576
|
private pollSwap;
|
|
1495
1577
|
private runPass;
|
|
1578
|
+
/**
|
|
1579
|
+
* The lockup was unilaterally exited: its outputs sit onchain under the
|
|
1580
|
+
* VHTLC script, where no offchain claim or refund can reach them.
|
|
1581
|
+
*
|
|
1582
|
+
* `needs_counterparty` rather than a terminal state, because the money still
|
|
1583
|
+
* needs action and the swap can still end either way — an onchain claim can
|
|
1584
|
+
* reveal the preimage, an onchain refund can return it — and that state is
|
|
1585
|
+
* documented as re-checked every pass. It must be set from HERE and not from
|
|
1586
|
+
* inside `driveArkadeRefund`, whose two `unblock` calls would lift it again
|
|
1587
|
+
* on the very next pass.
|
|
1588
|
+
*/
|
|
1589
|
+
private blockExitedLockup;
|
|
1496
1590
|
/**
|
|
1497
1591
|
* The receive leg's whole state machine: claim the solver-funded lockup
|
|
1498
1592
|
* while the window is open, and recognise the shapes in which it can be
|
|
@@ -255,11 +255,15 @@ declare class SwapRefusal extends Error {
|
|
|
255
255
|
readonly rfqId: string | undefined;
|
|
256
256
|
constructor(reason: string, rfqId?: string);
|
|
257
257
|
}
|
|
258
|
-
/**
|
|
258
|
+
/**
|
|
259
|
+
* The solver's address does not match the local derivation. NEVER fund past
|
|
260
|
+
* this. `derived` is every candidate address tried — more than one when the
|
|
261
|
+
* derivation itself is ambiguous, see {@link verifyLockupAddress}.
|
|
262
|
+
*/
|
|
259
263
|
declare class AddressMismatch extends Error {
|
|
260
|
-
readonly derived: string;
|
|
264
|
+
readonly derived: string | string[];
|
|
261
265
|
readonly quoted: string | undefined;
|
|
262
|
-
constructor(derived: string, quoted?: string);
|
|
266
|
+
constructor(derived: string | string[], quoted?: string);
|
|
263
267
|
}
|
|
264
268
|
/** A fresh client-chosen negotiation id: 32 random bytes, lowercase hex. */
|
|
265
269
|
declare const newRfqId: () => string;
|
|
@@ -323,9 +327,24 @@ declare const arkadeSwapRequest: (input: {
|
|
|
323
327
|
* 90 because the refund CLTV matures against median-time-past (BIP-113),
|
|
324
328
|
* which lags wall clock by ~1h — a smaller wall-clock margin is no margin. */
|
|
325
329
|
declare const MIN_HEADROOM_SECONDS: number;
|
|
326
|
-
/**
|
|
327
|
-
*
|
|
328
|
-
|
|
330
|
+
/**
|
|
331
|
+
* Compare-only check of the solver's address against YOUR OWN derivation(s)
|
|
332
|
+
* — never extends trust, only narrows it.
|
|
333
|
+
*
|
|
334
|
+
* `derivedAddress` may be a single address or an array of candidates. Pass an
|
|
335
|
+
* array when your own derivation is ambiguous — as it is for the covenant
|
|
336
|
+
* lockups while solvers roll out the timelocked non-interactive refund leaf:
|
|
337
|
+
* nothing on the wire says whether a given quote's covenant carries it (the
|
|
338
|
+
* shape is fixed by the solver's own build, not negotiated per quote), so the
|
|
339
|
+
* only safe move is to derive BOTH shapes and accept whichever one the quote's
|
|
340
|
+
* own `lockup_address` matches. This loses no security: every candidate shape
|
|
341
|
+
* pins the refund to the trader's own refund destination, so a solver gains
|
|
342
|
+
* nothing by choosing which one to quote.
|
|
343
|
+
*
|
|
344
|
+
* Throws {@link AddressMismatch} only when NONE of the candidates match.
|
|
345
|
+
* Returns the address that matched, so calls chain exactly as before.
|
|
346
|
+
*/
|
|
347
|
+
declare const verifyLockupAddress: (quote: RfqQuote, derivedAddress: string | string[]) => string;
|
|
329
348
|
/** The user's gates, checked immediately before funding — never at quote
|
|
330
349
|
* time. Throws with a stable `reason` property. `invoiceExpiresAt` applies to
|
|
331
350
|
* BOLT11 profiles only; `onchain` adds the L1-HTLC gates (§ guardrails of the
|
|
@@ -413,15 +432,17 @@ declare const unilateralRefundWithoutReceiverDelay: (claimDelay: number) => numb
|
|
|
413
432
|
* hex); the script's HASH160 commitment is derived from it here, which is why
|
|
414
433
|
* the trader never needs to see `P`.
|
|
415
434
|
*
|
|
416
|
-
* Every quote gets the full
|
|
435
|
+
* Every quote gets the full emulator-covenant suite on top of VHTLC's own six
|
|
417
436
|
* (`claim`/`refund`/`refundWithoutReceiver`/`unilateralClaim`/
|
|
418
|
-
* `unilateralRefund`/`unilateralRefundWithoutReceiver`)
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
* own `
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
437
|
+
* `unilateralRefund`/`unilateralRefundWithoutReceiver`): `nonInteractiveClaim`
|
|
438
|
+
* (server + emulator, pays the solver's own `receiverPkScript`, no solver
|
|
439
|
+
* signature needed), `nonInteractiveRefund` (server + solver + emulator, pays
|
|
440
|
+
* the trader's own `refundPkScript`, no timelock and no trader signature
|
|
441
|
+
* needed — see {@link VHTLC.Options.nonInteractiveParameters}'s doc comment for why
|
|
442
|
+
* that matters), and its timelocked twin `nonInteractiveRefundWithoutReceiver`
|
|
443
|
+
* (server + emulator alone, after `refundLocktime` — the only refund tier
|
|
444
|
+
* needing no participant at all). Nine leaves in all, unless `legacy` says
|
|
445
|
+
* otherwise.
|
|
425
446
|
*/
|
|
426
447
|
declare function lightningSendVtxoScript(params: {
|
|
427
448
|
/** Binding field #1: the solver's x-only key, from the quote. */
|
|
@@ -439,7 +460,7 @@ declare function lightningSendVtxoScript(params: {
|
|
|
439
460
|
/** Emulator x-only key (32 bytes). */
|
|
440
461
|
emulatorPubkey: Uint8Array;
|
|
441
462
|
/** Where a refund must pay: the trader's P2TR pkScript (34 bytes). Also
|
|
442
|
-
*
|
|
463
|
+
* the refund covenants' destination. */
|
|
443
464
|
refundPkScript: Uint8Array;
|
|
444
465
|
/** The trader's own key — VHTLC's `sender` role. Required on every
|
|
445
466
|
* interactive refund-side leaf; the trader generates and persists it
|
|
@@ -450,6 +471,11 @@ declare function lightningSendVtxoScript(params: {
|
|
|
450
471
|
* covenant key can be derived; the trader does not otherwise use or trust
|
|
451
472
|
* this value. P2TR pkScript, 34 bytes. */
|
|
452
473
|
receiverPkScript: Uint8Array;
|
|
474
|
+
/** LEGACY REBUILD ONLY — see {@link VHTLC.Options.nonInteractiveParameters}'s
|
|
475
|
+
* `legacy` field. Set only to re-derive a lockup funded before the
|
|
476
|
+
* timelocked refund leaf shipped; {@link matchQuotedLockup} passes it when
|
|
477
|
+
* the quote's own address says the solver quoted that shape. */
|
|
478
|
+
legacy?: "preTimelockedRefund";
|
|
453
479
|
}): InstanceType<typeof VHTLC.ScriptV2>;
|
|
454
480
|
/** Every input {@link lightningSendVtxoScript} builds from. Derived from the
|
|
455
481
|
* builder rather than restated, so the two cannot drift. */
|
|
@@ -487,12 +513,11 @@ interface InvoiceFacts {
|
|
|
487
513
|
* throws while nothing is funded. `RfqSwapManager` re-registers as a backstop
|
|
488
514
|
* for older records; a repeat write is a no-op.
|
|
489
515
|
*
|
|
490
|
-
* The `sender` key
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
* recovers the funds even without it — but it needs the SOLVER's active
|
|
516
|
+
* The `sender` key is the wallet's identity key, reused by {@link
|
|
517
|
+
* provisionRefundKey} — returned as `senderPubkey` plus `secrets`. `secrets`
|
|
518
|
+
* holds only a public descriptor; the signer re-derives from the wallet, so
|
|
519
|
+
* nothing secret is at rest. Persist `secrets` with the record anyway: it is
|
|
520
|
+
* how the refund signer is found again. `nonInteractiveRefund` recovers the funds even without it — but it needs the SOLVER's active
|
|
496
521
|
* cooperation, not just infrastructure uptime.
|
|
497
522
|
*/
|
|
498
523
|
declare function requestLightningSend(wallet: IWallet, arkServerUrl: string, transport: RfqTransport, params: {
|
|
@@ -515,7 +540,8 @@ declare function requestLightningSend(wallet: IWallet, arkServerUrl: string, tra
|
|
|
515
540
|
* `lockup` (with `address`): without it the manager can only poll, and
|
|
516
541
|
* cannot retire the row this call just wrote. */
|
|
517
542
|
script: InstanceType<typeof VHTLC.ScriptV2>;
|
|
518
|
-
/** Where a failed swap refunds.
|
|
543
|
+
/** Where a failed swap refunds — the same address `secrets.pkScript` was
|
|
544
|
+
* decoded from, so the quote and the covenant always name one script. */
|
|
519
545
|
refundAddress: string;
|
|
520
546
|
/** The VHTLC `sender` x-only key, bound into the covenant. Public. */
|
|
521
547
|
senderPubkey: Uint8Array;
|
|
@@ -528,7 +554,9 @@ declare function requestLightningSend(wallet: IWallet, arkServerUrl: string, tra
|
|
|
528
554
|
* Returned so a consumer can persist the swap without re-deriving any of
|
|
529
555
|
* it. Half of these are not on the quote: `serverPubkey` and `claimDelay`
|
|
530
556
|
* come from this wallet's own `getInfo()`, `emulatorPubkey` from a
|
|
531
|
-
* per-network pin, `refundPkScript` from
|
|
557
|
+
* per-network pin, `refundPkScript` from `secrets` — decoded from the
|
|
558
|
+
* refund address at provisioning time, the same address this call returns
|
|
559
|
+
* as `refundAddress`.
|
|
532
560
|
*
|
|
533
561
|
* All public. Persisting them is optional: this call also registers the
|
|
534
562
|
* lockup as a contract, and that row is where `rebuildRfqSwap` takes its
|
|
@@ -784,7 +812,7 @@ declare const assertReceivable: (input: {
|
|
|
784
812
|
/** Absolute sats ceiling on `from_amount`. */
|
|
785
813
|
maxPayAmount?: number;
|
|
786
814
|
}) => void;
|
|
787
|
-
/** Compile the RECEIVE-direction VHTLC: the same
|
|
815
|
+
/** Compile the RECEIVE-direction VHTLC: the same suite-carrying tree as {@link
|
|
788
816
|
* lightningSendVtxoScript} with the roles inverted — the trader is the
|
|
789
817
|
* `receiver` (it generated `P` and claims the lockup with it), the solver is
|
|
790
818
|
* the `sender` (it funds the lockup and holds the refund recourse). One
|
|
@@ -815,6 +843,8 @@ declare function receiveVtxoScript(params: {
|
|
|
815
843
|
/** The trader's own Arkade payout pkScript (decoded from its payout
|
|
816
844
|
* address) — `nonInteractiveClaim`'s pinned destination. */
|
|
817
845
|
payoutPkScript: Uint8Array;
|
|
846
|
+
/** LEGACY REBUILD ONLY — see {@link lightningSendVtxoScript}'s `legacy`. */
|
|
847
|
+
legacy?: "preTimelockedRefund";
|
|
818
848
|
}): InstanceType<typeof VHTLC.ScriptV2>;
|
|
819
849
|
/** Every input {@link receiveVtxoScript} builds from; see
|
|
820
850
|
* {@link LightningSendTreeParams}. */
|