@arkade-os/swap 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-2FEMUOIH.js → chunk-5NPYNQ5V.js} +3 -5
- package/dist/index.cjs +39 -9
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +38 -5
- 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-DIAr5XYk.d.ts → repository-C8FXlmHY.d.cts} +68 -11
- package/dist/{repository-DEHLtD9l.d.cts → repository-Cw2EvvZG.d.ts} +68 -11
- package/dist/{rfq-hbzhTWHT.d.ts → rfq-DglvHMNC.d.cts} +10 -8
- package/dist/{rfq-hbzhTWHT.d.cts → rfq-DglvHMNC.d.ts} +10 -8
- package/package.json +4 -4
|
@@ -639,10 +639,8 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
|
|
|
639
639
|
const rfqId = params.rfqId ?? newRfqId();
|
|
640
640
|
const secrets = await provisionRefundKey(wallet);
|
|
641
641
|
const senderPubkey = secrets.pubkey;
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
wallet.getAddress()
|
|
645
|
-
]);
|
|
642
|
+
const refundAddress = secrets.address;
|
|
643
|
+
const info = await new RestArkProvider(arkServerUrl).getInfo();
|
|
646
644
|
const quote = await transport.requestQuote(
|
|
647
645
|
lightningSendRequest({ rfqId, invoice: params.invoice.raw, refundAddress, senderPubkey })
|
|
648
646
|
);
|
|
@@ -677,7 +675,7 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
|
|
|
677
675
|
),
|
|
678
676
|
senderPubkey,
|
|
679
677
|
receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
|
|
680
|
-
refundPkScript:
|
|
678
|
+
refundPkScript: secrets.pkScript
|
|
681
679
|
};
|
|
682
680
|
const script = lightningSendVtxoScript(treeParams);
|
|
683
681
|
const address = script.address(network.hrp, serverPubkey).encode();
|
package/dist/index.cjs
CHANGED
|
@@ -609,8 +609,8 @@ async function cancelOffer(wallet, arkServerUrl, offerHex, opts) {
|
|
|
609
609
|
// script and the payout script comes from wallet.getAddress(), so the
|
|
610
610
|
// client's network (which only shapes address derivation) is unused here
|
|
611
611
|
});
|
|
612
|
-
const
|
|
613
|
-
const { program, args, keys } = swapProgramBinding(offer,
|
|
612
|
+
const operatorPubkey = swapAddress ? import_sdk2.ArkAddress.decode(swapAddress).serverPubKey : client.serverKey;
|
|
613
|
+
const { program, args, keys } = swapProgramBinding(offer, operatorPubkey);
|
|
614
614
|
const rebuilt = new import_sdk2.arkade.ArkadeProgramScript(program, args, keys);
|
|
615
615
|
if (import_base2.hex.encode(rebuilt.pkScript) !== import_base2.hex.encode(offer.swapPkScript)) {
|
|
616
616
|
throw new Error(
|
|
@@ -2135,10 +2135,8 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
|
|
|
2135
2135
|
const rfqId = params.rfqId ?? newRfqId();
|
|
2136
2136
|
const secrets = await (0, import_sdk10.provisionRefundKey)(wallet);
|
|
2137
2137
|
const senderPubkey = secrets.pubkey;
|
|
2138
|
-
const
|
|
2139
|
-
|
|
2140
|
-
wallet.getAddress()
|
|
2141
|
-
]);
|
|
2138
|
+
const refundAddress = secrets.address;
|
|
2139
|
+
const info = await new import_sdk9.RestArkProvider(arkServerUrl).getInfo();
|
|
2142
2140
|
const quote = await transport.requestQuote(
|
|
2143
2141
|
lightningSendRequest({ rfqId, invoice: params.invoice.raw, refundAddress, senderPubkey })
|
|
2144
2142
|
);
|
|
@@ -2173,7 +2171,7 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
|
|
|
2173
2171
|
),
|
|
2174
2172
|
senderPubkey,
|
|
2175
2173
|
receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
|
|
2176
|
-
refundPkScript:
|
|
2174
|
+
refundPkScript: secrets.pkScript
|
|
2177
2175
|
};
|
|
2178
2176
|
const script = lightningSendVtxoScript(treeParams);
|
|
2179
2177
|
const address = script.address(network.hrp, serverPubkey).encode();
|
|
@@ -2747,6 +2745,7 @@ async function findLockupVtxos(indexer, swapPkScript) {
|
|
|
2747
2745
|
[recoverable.vtxos ?? [], true]
|
|
2748
2746
|
]) {
|
|
2749
2747
|
for (const vtxo of vtxos) {
|
|
2748
|
+
if (vtxo.isUnrolled) continue;
|
|
2750
2749
|
const key = `${vtxo.txid}:${vtxo.vout}`;
|
|
2751
2750
|
if (seen.has(key)) continue;
|
|
2752
2751
|
seen.add(key);
|
|
@@ -2769,10 +2768,17 @@ async function readLockupFate(indexer, input) {
|
|
|
2769
2768
|
const { vtxos } = await indexer.getVtxos({ scripts: [import_base11.hex.encode(input.swapPkScript)] });
|
|
2770
2769
|
const all = vtxos ?? [];
|
|
2771
2770
|
if (all.length === 0) return { fate: "unknown" };
|
|
2771
|
+
const exited = all.filter((vtxo) => vtxo.isUnrolled && !(0, import_sdk11.hasTerminalSpend)(vtxo));
|
|
2772
|
+
if (exited.length > 0) {
|
|
2773
|
+
return {
|
|
2774
|
+
fate: "exited",
|
|
2775
|
+
outpoints: exited.map((vtxo) => ({ txid: vtxo.txid, vout: vtxo.vout }))
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2772
2778
|
const spentBy = /* @__PURE__ */ new Map();
|
|
2773
2779
|
let everySpendNamed = true;
|
|
2774
2780
|
for (const vtxo of all) {
|
|
2775
|
-
if (!
|
|
2781
|
+
if (!(0, import_sdk11.hasTerminalSpend)(vtxo)) return { fate: "open" };
|
|
2776
2782
|
if (vtxo.spentBy)
|
|
2777
2783
|
spentBy.set(vtxo.spentBy, {
|
|
2778
2784
|
checkpointTxid: vtxo.spentBy,
|
|
@@ -3660,12 +3666,36 @@ var RfqSwapManager = class {
|
|
|
3660
3666
|
this.setState(swap, fate.fate === "claimed" ? "settled" : "refunded");
|
|
3661
3667
|
return;
|
|
3662
3668
|
}
|
|
3663
|
-
if (swap.kind === "lightning_receive")
|
|
3669
|
+
if (swap.kind === "lightning_receive") {
|
|
3670
|
+
return fate.fate === "exited" ? this.blockExitedLockup(swap, fate) : this.driveReceiveClaim(swap);
|
|
3671
|
+
}
|
|
3664
3672
|
if (swap.kind === "onchain_send" && swap.state !== "claimed") {
|
|
3665
3673
|
if (await this.driveOnchain(swap) === "handled") return;
|
|
3666
3674
|
}
|
|
3675
|
+
if (fate.fate === "exited") {
|
|
3676
|
+
const claiming = swap.state === "claimable" || swap.state === "claimed";
|
|
3677
|
+
if (this.config.now() < swap.refundLocktime && claiming) return;
|
|
3678
|
+
return this.blockExitedLockup(swap, fate);
|
|
3679
|
+
}
|
|
3667
3680
|
await this.driveArkadeRefund(swap);
|
|
3668
3681
|
}
|
|
3682
|
+
/**
|
|
3683
|
+
* The lockup was unilaterally exited: its outputs sit onchain under the
|
|
3684
|
+
* VHTLC script, where no offchain claim or refund can reach them.
|
|
3685
|
+
*
|
|
3686
|
+
* `needs_counterparty` rather than a terminal state, because the money still
|
|
3687
|
+
* needs action and the swap can still end either way — an onchain claim can
|
|
3688
|
+
* reveal the preimage, an onchain refund can return it — and that state is
|
|
3689
|
+
* documented as re-checked every pass. It must be set from HERE and not from
|
|
3690
|
+
* inside `driveArkadeRefund`, whose two `unblock` calls would lift it again
|
|
3691
|
+
* on the very next pass.
|
|
3692
|
+
*/
|
|
3693
|
+
blockExitedLockup(swap, fate) {
|
|
3694
|
+
this.block(
|
|
3695
|
+
swap,
|
|
3696
|
+
`the lockup was unilaterally exited (${fate.outpoints.length} output(s) onchain), so no offchain spend can move it \u2014 complete the unroll and spend it onchain`
|
|
3697
|
+
);
|
|
3698
|
+
}
|
|
3669
3699
|
/**
|
|
3670
3700
|
* The receive leg's whole state machine: claim the solver-funded lockup
|
|
3671
3701
|
* while the window is open, and recognise the shapes in which it can be
|
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ProvisionedKey, ProvisionedClaimSecret, asset, IWallet, arkade, RestIndexerProvider, Transaction, IContractManager, VHTLC, Identity, ActivityResolver } from '@arkade-os/sdk';
|
|
2
|
-
import { S as SwapSecretsProjection, R as RfqSwapRecord, A as AssetSwapRepository, a as AssetSwap, M as MarketsCacheEntry, b as RefundArkProvider, c as RefundIndexer, L as LockupVtxo, d as RfqSwap, e as ArkadeRefundResult, f as LockupSpendIndexer, g as RfqSwapState } from './repository-
|
|
3
|
-
export { h as AssetSwapStatus, i as AvailableRfqSwapManagerCallbacks, B as BTC_ASSET_ID, I as InMemoryAssetSwapRepository, j as LightningReceiveSwap, k as LightningSendSwap, l as LockupFate, m as LockupNeedsRecoveryError, n as LockupParams, o as LockupSpend, O as OnchainSendAction, p as OnchainSendSwap, P as PersistableRfqSwap, q as PreimageBlockedReason, r as PreimageNotRecoverableError, s as REFUND_MTP_LAG_SECONDS, t as RFQ_RESOLVED_STATES, u as RFQ_SWAP_RETENTION_SECONDS, v as RFQ_SWAP_TERMINAL_STATES, w as RefundOutcome, x as RfqRestoreFailure, y as RfqRestoreOptions, z as RfqRestoreResult, C as RfqSwapActionName, D as RfqSwapLockup, E as RfqSwapManager, F as RfqSwapManagerCallbacks, G as RfqSwapManagerConfig, H as RfqSwapManagerDeps, J as RfqSwapManagerEvents, K as RfqSwapOrigin, N as RfqSwapOriginRequired, Q as RfqSwapOutcome, T as RfqSwapRecordStore, U as SwapContractRegistry, V as addAssetSwap, W as awaitRfqResolution, X as createRfqSwapRecord, Y as findLockupVtxos, Z as getAssetSwaps, _ as getAssetSwapsOrThrow, $ as isRfqSwapTerminal, a0 as isRfqTerminal, a1 as nextOnchainAction, a2 as preimageForSwapRecord, a3 as pushRefundWithoutReceiver, a4 as readLockupFate, a5 as rebuildRfqSwap, a6 as refundIfUnresolved, a7 as rfqSwapOriginOf, a8 as shouldRetainRfqSwap, a9 as swapSecretsToRecord, aa as updateAssetSwap, ab as updateAssetSwapBestEffort, ac as updateRfqSwapRecord } from './repository-
|
|
2
|
+
import { S as SwapSecretsProjection, R as RfqSwapRecord, A as AssetSwapRepository, a as AssetSwap, M as MarketsCacheEntry, b as RefundArkProvider, c as RefundIndexer, L as LockupVtxo, d as RfqSwap, e as ArkadeRefundResult, f as LockupSpendIndexer, g as RfqSwapState } from './repository-C8FXlmHY.cjs';
|
|
3
|
+
export { h as AssetSwapStatus, i as AvailableRfqSwapManagerCallbacks, B as BTC_ASSET_ID, I as InMemoryAssetSwapRepository, j as LightningReceiveSwap, k as LightningSendSwap, l as LockupFate, m as LockupNeedsRecoveryError, n as LockupParams, o as LockupSpend, O as OnchainSendAction, p as OnchainSendSwap, P as PersistableRfqSwap, q as PreimageBlockedReason, r as PreimageNotRecoverableError, s as REFUND_MTP_LAG_SECONDS, t as RFQ_RESOLVED_STATES, u as RFQ_SWAP_RETENTION_SECONDS, v as RFQ_SWAP_TERMINAL_STATES, w as RefundOutcome, x as RfqRestoreFailure, y as RfqRestoreOptions, z as RfqRestoreResult, C as RfqSwapActionName, D as RfqSwapLockup, E as RfqSwapManager, F as RfqSwapManagerCallbacks, G as RfqSwapManagerConfig, H as RfqSwapManagerDeps, J as RfqSwapManagerEvents, K as RfqSwapOrigin, N as RfqSwapOriginRequired, Q as RfqSwapOutcome, T as RfqSwapRecordStore, U as SwapContractRegistry, V as addAssetSwap, W as awaitRfqResolution, X as createRfqSwapRecord, Y as findLockupVtxos, Z as getAssetSwaps, _ as getAssetSwapsOrThrow, $ as isRfqSwapTerminal, a0 as isRfqTerminal, a1 as nextOnchainAction, a2 as preimageForSwapRecord, a3 as pushRefundWithoutReceiver, a4 as readLockupFate, a5 as rebuildRfqSwap, a6 as refundIfUnresolved, a7 as rfqSwapOriginOf, a8 as shouldRetainRfqSwap, a9 as swapSecretsToRecord, aa as updateAssetSwap, ab as updateAssetSwapBestEffort, ac as updateRfqSwapRecord } from './repository-C8FXlmHY.cjs';
|
|
4
4
|
import { Network, LocalCardInput, DiscoveredMarket, Side, OfferPlan } from '@arkade-os/solver-discovery';
|
|
5
|
-
import { O as OnchainNetwork, a as OnchainHtlc, b as OnchainHtlcParams } from './rfq-
|
|
6
|
-
export { A as ARKADE_ASSET, c as ARKADE_BTC, d as AddressMismatch, C as ChainSource, e as ChainUtxo, H as HtlcUtxo, I as InvoiceFacts, L as LIGHTNING_BTC, f as LIGHTNING_RECEIVE_PAIR, g as LIGHTNING_SEND_PAIR, h as LOCKTIME_THRESHOLD, i as LightningReceiveTreeParams, j as LightningSendTreeParams, M as MAX_MIN_CONFIRMATIONS, k as MIN_CLAIM_WINDOW_SECONDS, l as MIN_HEADROOM_SECONDS, m as ONCHAIN_BTC, n as ONCHAIN_CLAIM_MARGIN_SECONDS, o as ONCHAIN_DUST_SATS, p as ONCHAIN_ORDER_MARGIN_SECONDS, q as ONCHAIN_RECEIVE_PAIR, r as ONCHAIN_SECONDS_PER_BLOCK, s as ONCHAIN_SEND_PAIR, t as OnchainHtlcPhase, R as RFQ_TERMINAL_STATES, u as RelaySocket, v as RfqQuote, w as RfqRefusalReason, x as RfqStatus, y as RfqTransport, S as SOLO_REFUND_HEADROOM_SECONDS, z as SwapRefusal, B as arkadeAssetLeg, D as arkadeSwapRequest, E as assertFundable, F as assertReceivable, G as awaitOnchainFill, J as buildHtlcClaim, K as buildHtlcRefund, N as claimOnchainFill, P as classifyOnchainHtlc, Q as deriveLightningReceive, T as deriveOnchainReceive, U as deriveOnchainSend, V as extractPreimage, W as httpTransport, X as lightningReceiveRequest, Y as lightningSendRequest, Z as lightningSendVtxoScript, _ as newPreimage, $ as newRfqId, a0 as offerTermsFromQuote, a1 as onchainHtlcScript, a2 as onchainReceiveRequest, a3 as onchainSendRequest, a4 as paymentHashOf, a5 as receiveVtxoScript, a6 as relayTransport, a7 as requestLightningReceive, a8 as requestLightningSend, a9 as requestOnchainReceive, aa as requestOnchainSend, ab as rfqPair, ac as unilateralClaimDelay, ad as unilateralRefundDelay, ae as unilateralRefundWithoutReceiverDelay, af as verifyLockupAddress, ag as verifyReceiveInvoice } from './rfq-
|
|
5
|
+
import { O as OnchainNetwork, a as OnchainHtlc, b as OnchainHtlcParams } from './rfq-DglvHMNC.cjs';
|
|
6
|
+
export { A as ARKADE_ASSET, c as ARKADE_BTC, d as AddressMismatch, C as ChainSource, e as ChainUtxo, H as HtlcUtxo, I as InvoiceFacts, L as LIGHTNING_BTC, f as LIGHTNING_RECEIVE_PAIR, g as LIGHTNING_SEND_PAIR, h as LOCKTIME_THRESHOLD, i as LightningReceiveTreeParams, j as LightningSendTreeParams, M as MAX_MIN_CONFIRMATIONS, k as MIN_CLAIM_WINDOW_SECONDS, l as MIN_HEADROOM_SECONDS, m as ONCHAIN_BTC, n as ONCHAIN_CLAIM_MARGIN_SECONDS, o as ONCHAIN_DUST_SATS, p as ONCHAIN_ORDER_MARGIN_SECONDS, q as ONCHAIN_RECEIVE_PAIR, r as ONCHAIN_SECONDS_PER_BLOCK, s as ONCHAIN_SEND_PAIR, t as OnchainHtlcPhase, R as RFQ_TERMINAL_STATES, u as RelaySocket, v as RfqQuote, w as RfqRefusalReason, x as RfqStatus, y as RfqTransport, S as SOLO_REFUND_HEADROOM_SECONDS, z as SwapRefusal, B as arkadeAssetLeg, D as arkadeSwapRequest, E as assertFundable, F as assertReceivable, G as awaitOnchainFill, J as buildHtlcClaim, K as buildHtlcRefund, N as claimOnchainFill, P as classifyOnchainHtlc, Q as deriveLightningReceive, T as deriveOnchainReceive, U as deriveOnchainSend, V as extractPreimage, W as httpTransport, X as lightningReceiveRequest, Y as lightningSendRequest, Z as lightningSendVtxoScript, _ as newPreimage, $ as newRfqId, a0 as offerTermsFromQuote, a1 as onchainHtlcScript, a2 as onchainReceiveRequest, a3 as onchainSendRequest, a4 as paymentHashOf, a5 as receiveVtxoScript, a6 as relayTransport, a7 as requestLightningReceive, a8 as requestLightningSend, a9 as requestOnchainReceive, aa as requestOnchainSend, ab as rfqPair, ac as unilateralClaimDelay, ad as unilateralRefundDelay, ae as unilateralRefundWithoutReceiverDelay, af as verifyLockupAddress, ag as verifyReceiveInvoice } from './rfq-DglvHMNC.cjs';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Which wallet key signs this leg. Stored at `profile.signer`.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ProvisionedKey, ProvisionedClaimSecret, asset, IWallet, arkade, RestIndexerProvider, Transaction, IContractManager, VHTLC, Identity, ActivityResolver } from '@arkade-os/sdk';
|
|
2
|
-
import { S as SwapSecretsProjection, R as RfqSwapRecord, A as AssetSwapRepository, a as AssetSwap, M as MarketsCacheEntry, b as RefundArkProvider, c as RefundIndexer, L as LockupVtxo, d as RfqSwap, e as ArkadeRefundResult, f as LockupSpendIndexer, g as RfqSwapState } from './repository-
|
|
3
|
-
export { h as AssetSwapStatus, i as AvailableRfqSwapManagerCallbacks, B as BTC_ASSET_ID, I as InMemoryAssetSwapRepository, j as LightningReceiveSwap, k as LightningSendSwap, l as LockupFate, m as LockupNeedsRecoveryError, n as LockupParams, o as LockupSpend, O as OnchainSendAction, p as OnchainSendSwap, P as PersistableRfqSwap, q as PreimageBlockedReason, r as PreimageNotRecoverableError, s as REFUND_MTP_LAG_SECONDS, t as RFQ_RESOLVED_STATES, u as RFQ_SWAP_RETENTION_SECONDS, v as RFQ_SWAP_TERMINAL_STATES, w as RefundOutcome, x as RfqRestoreFailure, y as RfqRestoreOptions, z as RfqRestoreResult, C as RfqSwapActionName, D as RfqSwapLockup, E as RfqSwapManager, F as RfqSwapManagerCallbacks, G as RfqSwapManagerConfig, H as RfqSwapManagerDeps, J as RfqSwapManagerEvents, K as RfqSwapOrigin, N as RfqSwapOriginRequired, Q as RfqSwapOutcome, T as RfqSwapRecordStore, U as SwapContractRegistry, V as addAssetSwap, W as awaitRfqResolution, X as createRfqSwapRecord, Y as findLockupVtxos, Z as getAssetSwaps, _ as getAssetSwapsOrThrow, $ as isRfqSwapTerminal, a0 as isRfqTerminal, a1 as nextOnchainAction, a2 as preimageForSwapRecord, a3 as pushRefundWithoutReceiver, a4 as readLockupFate, a5 as rebuildRfqSwap, a6 as refundIfUnresolved, a7 as rfqSwapOriginOf, a8 as shouldRetainRfqSwap, a9 as swapSecretsToRecord, aa as updateAssetSwap, ab as updateAssetSwapBestEffort, ac as updateRfqSwapRecord } from './repository-
|
|
2
|
+
import { S as SwapSecretsProjection, R as RfqSwapRecord, A as AssetSwapRepository, a as AssetSwap, M as MarketsCacheEntry, b as RefundArkProvider, c as RefundIndexer, L as LockupVtxo, d as RfqSwap, e as ArkadeRefundResult, f as LockupSpendIndexer, g as RfqSwapState } from './repository-Cw2EvvZG.js';
|
|
3
|
+
export { h as AssetSwapStatus, i as AvailableRfqSwapManagerCallbacks, B as BTC_ASSET_ID, I as InMemoryAssetSwapRepository, j as LightningReceiveSwap, k as LightningSendSwap, l as LockupFate, m as LockupNeedsRecoveryError, n as LockupParams, o as LockupSpend, O as OnchainSendAction, p as OnchainSendSwap, P as PersistableRfqSwap, q as PreimageBlockedReason, r as PreimageNotRecoverableError, s as REFUND_MTP_LAG_SECONDS, t as RFQ_RESOLVED_STATES, u as RFQ_SWAP_RETENTION_SECONDS, v as RFQ_SWAP_TERMINAL_STATES, w as RefundOutcome, x as RfqRestoreFailure, y as RfqRestoreOptions, z as RfqRestoreResult, C as RfqSwapActionName, D as RfqSwapLockup, E as RfqSwapManager, F as RfqSwapManagerCallbacks, G as RfqSwapManagerConfig, H as RfqSwapManagerDeps, J as RfqSwapManagerEvents, K as RfqSwapOrigin, N as RfqSwapOriginRequired, Q as RfqSwapOutcome, T as RfqSwapRecordStore, U as SwapContractRegistry, V as addAssetSwap, W as awaitRfqResolution, X as createRfqSwapRecord, Y as findLockupVtxos, Z as getAssetSwaps, _ as getAssetSwapsOrThrow, $ as isRfqSwapTerminal, a0 as isRfqTerminal, a1 as nextOnchainAction, a2 as preimageForSwapRecord, a3 as pushRefundWithoutReceiver, a4 as readLockupFate, a5 as rebuildRfqSwap, a6 as refundIfUnresolved, a7 as rfqSwapOriginOf, a8 as shouldRetainRfqSwap, a9 as swapSecretsToRecord, aa as updateAssetSwap, ab as updateAssetSwapBestEffort, ac as updateRfqSwapRecord } from './repository-Cw2EvvZG.js';
|
|
4
4
|
import { Network, LocalCardInput, DiscoveredMarket, Side, OfferPlan } from '@arkade-os/solver-discovery';
|
|
5
|
-
import { O as OnchainNetwork, a as OnchainHtlc, b as OnchainHtlcParams } from './rfq-
|
|
6
|
-
export { A as ARKADE_ASSET, c as ARKADE_BTC, d as AddressMismatch, C as ChainSource, e as ChainUtxo, H as HtlcUtxo, I as InvoiceFacts, L as LIGHTNING_BTC, f as LIGHTNING_RECEIVE_PAIR, g as LIGHTNING_SEND_PAIR, h as LOCKTIME_THRESHOLD, i as LightningReceiveTreeParams, j as LightningSendTreeParams, M as MAX_MIN_CONFIRMATIONS, k as MIN_CLAIM_WINDOW_SECONDS, l as MIN_HEADROOM_SECONDS, m as ONCHAIN_BTC, n as ONCHAIN_CLAIM_MARGIN_SECONDS, o as ONCHAIN_DUST_SATS, p as ONCHAIN_ORDER_MARGIN_SECONDS, q as ONCHAIN_RECEIVE_PAIR, r as ONCHAIN_SECONDS_PER_BLOCK, s as ONCHAIN_SEND_PAIR, t as OnchainHtlcPhase, R as RFQ_TERMINAL_STATES, u as RelaySocket, v as RfqQuote, w as RfqRefusalReason, x as RfqStatus, y as RfqTransport, S as SOLO_REFUND_HEADROOM_SECONDS, z as SwapRefusal, B as arkadeAssetLeg, D as arkadeSwapRequest, E as assertFundable, F as assertReceivable, G as awaitOnchainFill, J as buildHtlcClaim, K as buildHtlcRefund, N as claimOnchainFill, P as classifyOnchainHtlc, Q as deriveLightningReceive, T as deriveOnchainReceive, U as deriveOnchainSend, V as extractPreimage, W as httpTransport, X as lightningReceiveRequest, Y as lightningSendRequest, Z as lightningSendVtxoScript, _ as newPreimage, $ as newRfqId, a0 as offerTermsFromQuote, a1 as onchainHtlcScript, a2 as onchainReceiveRequest, a3 as onchainSendRequest, a4 as paymentHashOf, a5 as receiveVtxoScript, a6 as relayTransport, a7 as requestLightningReceive, a8 as requestLightningSend, a9 as requestOnchainReceive, aa as requestOnchainSend, ab as rfqPair, ac as unilateralClaimDelay, ad as unilateralRefundDelay, ae as unilateralRefundWithoutReceiverDelay, af as verifyLockupAddress, ag as verifyReceiveInvoice } from './rfq-
|
|
5
|
+
import { O as OnchainNetwork, a as OnchainHtlc, b as OnchainHtlcParams } from './rfq-DglvHMNC.js';
|
|
6
|
+
export { A as ARKADE_ASSET, c as ARKADE_BTC, d as AddressMismatch, C as ChainSource, e as ChainUtxo, H as HtlcUtxo, I as InvoiceFacts, L as LIGHTNING_BTC, f as LIGHTNING_RECEIVE_PAIR, g as LIGHTNING_SEND_PAIR, h as LOCKTIME_THRESHOLD, i as LightningReceiveTreeParams, j as LightningSendTreeParams, M as MAX_MIN_CONFIRMATIONS, k as MIN_CLAIM_WINDOW_SECONDS, l as MIN_HEADROOM_SECONDS, m as ONCHAIN_BTC, n as ONCHAIN_CLAIM_MARGIN_SECONDS, o as ONCHAIN_DUST_SATS, p as ONCHAIN_ORDER_MARGIN_SECONDS, q as ONCHAIN_RECEIVE_PAIR, r as ONCHAIN_SECONDS_PER_BLOCK, s as ONCHAIN_SEND_PAIR, t as OnchainHtlcPhase, R as RFQ_TERMINAL_STATES, u as RelaySocket, v as RfqQuote, w as RfqRefusalReason, x as RfqStatus, y as RfqTransport, S as SOLO_REFUND_HEADROOM_SECONDS, z as SwapRefusal, B as arkadeAssetLeg, D as arkadeSwapRequest, E as assertFundable, F as assertReceivable, G as awaitOnchainFill, J as buildHtlcClaim, K as buildHtlcRefund, N as claimOnchainFill, P as classifyOnchainHtlc, Q as deriveLightningReceive, T as deriveOnchainReceive, U as deriveOnchainSend, V as extractPreimage, W as httpTransport, X as lightningReceiveRequest, Y as lightningSendRequest, Z as lightningSendVtxoScript, _ as newPreimage, $ as newRfqId, a0 as offerTermsFromQuote, a1 as onchainHtlcScript, a2 as onchainReceiveRequest, a3 as onchainSendRequest, a4 as paymentHashOf, a5 as receiveVtxoScript, a6 as relayTransport, a7 as requestLightningReceive, a8 as requestLightningSend, a9 as requestOnchainReceive, aa as requestOnchainSend, ab as rfqPair, ac as unilateralClaimDelay, ad as unilateralRefundDelay, ae as unilateralRefundWithoutReceiverDelay, af as verifyLockupAddress, ag as verifyReceiveInvoice } from './rfq-DglvHMNC.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Which wallet key signs this leg. Stored at `profile.signer`.
|
package/dist/index.js
CHANGED
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
unilateralRefundWithoutReceiverDelay,
|
|
64
64
|
verifyLockupAddress,
|
|
65
65
|
verifyReceiveInvoice
|
|
66
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-5NPYNQ5V.js";
|
|
67
67
|
import {
|
|
68
68
|
InMemoryAssetSwapRepository,
|
|
69
69
|
marketsCacheKey
|
|
@@ -527,8 +527,8 @@ async function cancelOffer(wallet, arkServerUrl, offerHex, opts) {
|
|
|
527
527
|
// script and the payout script comes from wallet.getAddress(), so the
|
|
528
528
|
// client's network (which only shapes address derivation) is unused here
|
|
529
529
|
});
|
|
530
|
-
const
|
|
531
|
-
const { program, args, keys } = swapProgramBinding(offer,
|
|
530
|
+
const operatorPubkey = swapAddress ? ArkAddress.decode(swapAddress).serverPubKey : client.serverKey;
|
|
531
|
+
const { program, args, keys } = swapProgramBinding(offer, operatorPubkey);
|
|
532
532
|
const rebuilt = new arkade.ArkadeProgramScript(program, args, keys);
|
|
533
533
|
if (hex2.encode(rebuilt.pkScript) !== hex2.encode(offer.swapPkScript)) {
|
|
534
534
|
throw new Error(
|
|
@@ -1412,6 +1412,7 @@ import {
|
|
|
1412
1412
|
assertSubmittedArkTxid,
|
|
1413
1413
|
buildOffchainTx,
|
|
1414
1414
|
getArkPsbtFields,
|
|
1415
|
+
hasTerminalSpend,
|
|
1415
1416
|
matchServerCheckpoints
|
|
1416
1417
|
} from "@arkade-os/sdk";
|
|
1417
1418
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -1472,6 +1473,7 @@ async function findLockupVtxos(indexer, swapPkScript) {
|
|
|
1472
1473
|
[recoverable.vtxos ?? [], true]
|
|
1473
1474
|
]) {
|
|
1474
1475
|
for (const vtxo of vtxos) {
|
|
1476
|
+
if (vtxo.isUnrolled) continue;
|
|
1475
1477
|
const key = `${vtxo.txid}:${vtxo.vout}`;
|
|
1476
1478
|
if (seen.has(key)) continue;
|
|
1477
1479
|
seen.add(key);
|
|
@@ -1494,10 +1496,17 @@ async function readLockupFate(indexer, input) {
|
|
|
1494
1496
|
const { vtxos } = await indexer.getVtxos({ scripts: [hex7.encode(input.swapPkScript)] });
|
|
1495
1497
|
const all = vtxos ?? [];
|
|
1496
1498
|
if (all.length === 0) return { fate: "unknown" };
|
|
1499
|
+
const exited = all.filter((vtxo) => vtxo.isUnrolled && !hasTerminalSpend(vtxo));
|
|
1500
|
+
if (exited.length > 0) {
|
|
1501
|
+
return {
|
|
1502
|
+
fate: "exited",
|
|
1503
|
+
outpoints: exited.map((vtxo) => ({ txid: vtxo.txid, vout: vtxo.vout }))
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1497
1506
|
const spentBy = /* @__PURE__ */ new Map();
|
|
1498
1507
|
let everySpendNamed = true;
|
|
1499
1508
|
for (const vtxo of all) {
|
|
1500
|
-
if (!vtxo
|
|
1509
|
+
if (!hasTerminalSpend(vtxo)) return { fate: "open" };
|
|
1501
1510
|
if (vtxo.spentBy)
|
|
1502
1511
|
spentBy.set(vtxo.spentBy, {
|
|
1503
1512
|
checkpointTxid: vtxo.spentBy,
|
|
@@ -2389,12 +2398,36 @@ var RfqSwapManager = class {
|
|
|
2389
2398
|
this.setState(swap, fate.fate === "claimed" ? "settled" : "refunded");
|
|
2390
2399
|
return;
|
|
2391
2400
|
}
|
|
2392
|
-
if (swap.kind === "lightning_receive")
|
|
2401
|
+
if (swap.kind === "lightning_receive") {
|
|
2402
|
+
return fate.fate === "exited" ? this.blockExitedLockup(swap, fate) : this.driveReceiveClaim(swap);
|
|
2403
|
+
}
|
|
2393
2404
|
if (swap.kind === "onchain_send" && swap.state !== "claimed") {
|
|
2394
2405
|
if (await this.driveOnchain(swap) === "handled") return;
|
|
2395
2406
|
}
|
|
2407
|
+
if (fate.fate === "exited") {
|
|
2408
|
+
const claiming = swap.state === "claimable" || swap.state === "claimed";
|
|
2409
|
+
if (this.config.now() < swap.refundLocktime && claiming) return;
|
|
2410
|
+
return this.blockExitedLockup(swap, fate);
|
|
2411
|
+
}
|
|
2396
2412
|
await this.driveArkadeRefund(swap);
|
|
2397
2413
|
}
|
|
2414
|
+
/**
|
|
2415
|
+
* The lockup was unilaterally exited: its outputs sit onchain under the
|
|
2416
|
+
* VHTLC script, where no offchain claim or refund can reach them.
|
|
2417
|
+
*
|
|
2418
|
+
* `needs_counterparty` rather than a terminal state, because the money still
|
|
2419
|
+
* needs action and the swap can still end either way — an onchain claim can
|
|
2420
|
+
* reveal the preimage, an onchain refund can return it — and that state is
|
|
2421
|
+
* documented as re-checked every pass. It must be set from HERE and not from
|
|
2422
|
+
* inside `driveArkadeRefund`, whose two `unblock` calls would lift it again
|
|
2423
|
+
* on the very next pass.
|
|
2424
|
+
*/
|
|
2425
|
+
blockExitedLockup(swap, fate) {
|
|
2426
|
+
this.block(
|
|
2427
|
+
swap,
|
|
2428
|
+
`the lockup was unilaterally exited (${fate.outpoints.length} output(s) onchain), so no offchain spend can move it \u2014 complete the unroll and spend it onchain`
|
|
2429
|
+
);
|
|
2430
|
+
}
|
|
2398
2431
|
/**
|
|
2399
2432
|
* The receive leg's whole state machine: claim the solver-funded lockup
|
|
2400
2433
|
* while the window is open, and recognise the shapes in which it can be
|
package/dist/nostr.d.cts
CHANGED
package/dist/nostr.d.ts
CHANGED
package/dist/nostr.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { RealmLike } from '@arkade-os/sdk/repositories/realm';
|
|
2
|
-
import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-
|
|
2
|
+
import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-C8FXlmHY.cjs';
|
|
3
3
|
import '@arkade-os/solver-discovery';
|
|
4
4
|
import '@arkade-os/sdk';
|
|
5
|
-
import '../../rfq-
|
|
5
|
+
import '../../rfq-DglvHMNC.cjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Realm backend for React Native.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { RealmLike } from '@arkade-os/sdk/repositories/realm';
|
|
2
|
-
import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-
|
|
2
|
+
import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-Cw2EvvZG.js';
|
|
3
3
|
import '@arkade-os/solver-discovery';
|
|
4
4
|
import '@arkade-os/sdk';
|
|
5
|
-
import '../../rfq-
|
|
5
|
+
import '../../rfq-DglvHMNC.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Realm backend for React Native.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { SQLExecutor } from '@arkade-os/sdk/repositories/sqlite';
|
|
2
|
-
import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-
|
|
2
|
+
import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-C8FXlmHY.cjs';
|
|
3
3
|
import '@arkade-os/solver-discovery';
|
|
4
4
|
import '@arkade-os/sdk';
|
|
5
|
-
import '../../rfq-
|
|
5
|
+
import '../../rfq-DglvHMNC.cjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* SQLite backend over the SDK's `SQLExecutor`, so any driver plugs in
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { SQLExecutor } from '@arkade-os/sdk/repositories/sqlite';
|
|
2
|
-
import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-
|
|
2
|
+
import { A as AssetSwapRepository, a as AssetSwap, R as RfqSwapRecord, M as MarketsCacheEntry } from '../../repository-Cw2EvvZG.js';
|
|
3
3
|
import '@arkade-os/solver-discovery';
|
|
4
4
|
import '@arkade-os/sdk';
|
|
5
|
-
import '../../rfq-
|
|
5
|
+
import '../../rfq-DglvHMNC.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* SQLite backend over the SDK's `SQLExecutor`, so any driver plugs in
|
|
@@ -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-DglvHMNC.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,18 @@ 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 `nothing_to_refund` instead of grinding a doomed
|
|
336
|
+
* push to its deadline. The manager reads the exit through
|
|
337
|
+
* {@link readLockupFate}, which queries unfiltered and reports it as `exited`.
|
|
338
|
+
*
|
|
324
339
|
* This read — not the RFQ's reported state — is the authority on whether
|
|
325
340
|
* there is anything left at the lockup.
|
|
326
341
|
*
|
|
@@ -374,6 +389,29 @@ type LockupFate =
|
|
|
374
389
|
fate: "returned";
|
|
375
390
|
spends: readonly LockupSpend[];
|
|
376
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* At least one output was unilaterally exited: it sits onchain under the
|
|
394
|
+
* VHTLC script, where no offchain claim or refund can reach it.
|
|
395
|
+
*
|
|
396
|
+
* Not terminal, and not a loss. The money is still under the same script
|
|
397
|
+
* with the same leaves, so `completeUnroll` plus an onchain spend can still
|
|
398
|
+
* end the swap either way — which is why this outranks `open`: an output
|
|
399
|
+
* that is "still unspent" but unreachable is not a swap that is merely
|
|
400
|
+
* running.
|
|
401
|
+
*
|
|
402
|
+
* It outranks a verdict too, on a lockup where a sibling output was claimed
|
|
403
|
+
* or returned. That is the rule `open` already sets, not a new one: the
|
|
404
|
+
* unspent test short-circuits before any witness is read, so a partially
|
|
405
|
+
* resolved lockup has never reported `claimed`/`returned`. What changes is
|
|
406
|
+
* only that such a lockup now says why it is unresolved.
|
|
407
|
+
*/
|
|
408
|
+
| {
|
|
409
|
+
fate: "exited";
|
|
410
|
+
outpoints: readonly {
|
|
411
|
+
txid: string;
|
|
412
|
+
vout: number;
|
|
413
|
+
}[];
|
|
414
|
+
}
|
|
377
415
|
/** Nothing was learned: no outputs visible, an output spent by nothing the
|
|
378
416
|
* indexer names, a spend it could not produce, or a blob that would not
|
|
379
417
|
* decode. Never an answer. */
|
|
@@ -409,6 +447,11 @@ type LockupFate =
|
|
|
409
447
|
* response to `unknown` is the same as to `open`: keep watching, and let the
|
|
410
448
|
* refund timelock — which no outage can move — be what ends the wait.
|
|
411
449
|
*
|
|
450
|
+
* **An exit is read before anything else, and over the whole set.** It is the
|
|
451
|
+
* one fact that makes an unspent output unreachable, so it outranks `open`; and
|
|
452
|
+
* it is scanned across every output rather than in outpoint order, so which
|
|
453
|
+
* output happens to come first cannot change the answer.
|
|
454
|
+
*
|
|
412
455
|
* Ask-the-indexer, don't-trust-local-state: read fresh on every poll, never
|
|
413
456
|
* cached, the same posture {@link findLockupVtxos} already establishes.
|
|
414
457
|
*/
|
|
@@ -447,9 +490,9 @@ declare function readLockupFate(indexer: LockupSpendIndexer, input: {
|
|
|
447
490
|
* {@link refundIfUnresolved}, which retries.
|
|
448
491
|
*
|
|
449
492
|
* **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
|
-
*
|
|
493
|
+
* a swept output is no longer a live leaf: `canSpendOffchain` is false wherever
|
|
494
|
+
* `canRecoverOnchain` is true, so a recoverable input cannot be spent this way
|
|
495
|
+
* whatever key signs it (see
|
|
453
496
|
* {@link LockupVtxo.recoverable}). Because every input lands in ONE aggregate
|
|
454
497
|
* transaction, a single swept output would take the live ones down with it —
|
|
455
498
|
* so the whole push is refused with {@link LockupNeedsRecoveryError} naming the
|
|
@@ -1162,7 +1205,9 @@ interface RfqSwapManagerDeps {
|
|
|
1162
1205
|
* `settled`; a lockup fully spent by anything else ends it `refunded`.
|
|
1163
1206
|
* Anything the indexer could not answer is `unknown`, which is NOT an
|
|
1164
1207
|
* answer: the pass carries on to the steps below, whose deadlines an indexer
|
|
1165
|
-
* outage has no bearing on.
|
|
1208
|
+
* outage has no bearing on. `exited` — an output unilaterally taken onchain
|
|
1209
|
+
* — ends neither the swap nor the pass: it blocks the Arkade half below,
|
|
1210
|
+
* with the L1 half left running.
|
|
1166
1211
|
* 2. **Drive the trader's claim.** On an onchain send that is the L1 fill — see
|
|
1167
1212
|
* {@link nextOnchainAction}. On a receive it is the lockup itself, and it
|
|
1168
1213
|
* ends the pass: that leg has no step 3.
|
|
@@ -1493,6 +1538,18 @@ declare class RfqSwapManager {
|
|
|
1493
1538
|
private arm;
|
|
1494
1539
|
private pollSwap;
|
|
1495
1540
|
private runPass;
|
|
1541
|
+
/**
|
|
1542
|
+
* The lockup was unilaterally exited: its outputs sit onchain under the
|
|
1543
|
+
* VHTLC script, where no offchain claim or refund can reach them.
|
|
1544
|
+
*
|
|
1545
|
+
* `needs_counterparty` rather than a terminal state, because the money still
|
|
1546
|
+
* needs action and the swap can still end either way — an onchain claim can
|
|
1547
|
+
* reveal the preimage, an onchain refund can return it — and that state is
|
|
1548
|
+
* documented as re-checked every pass. It must be set from HERE and not from
|
|
1549
|
+
* inside `driveArkadeRefund`, whose two `unblock` calls would lift it again
|
|
1550
|
+
* on the very next pass.
|
|
1551
|
+
*/
|
|
1552
|
+
private blockExitedLockup;
|
|
1496
1553
|
/**
|
|
1497
1554
|
* The receive leg's whole state machine: claim the solver-funded lockup
|
|
1498
1555
|
* 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-DglvHMNC.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,18 @@ 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 `nothing_to_refund` instead of grinding a doomed
|
|
336
|
+
* push to its deadline. The manager reads the exit through
|
|
337
|
+
* {@link readLockupFate}, which queries unfiltered and reports it as `exited`.
|
|
338
|
+
*
|
|
324
339
|
* This read — not the RFQ's reported state — is the authority on whether
|
|
325
340
|
* there is anything left at the lockup.
|
|
326
341
|
*
|
|
@@ -374,6 +389,29 @@ type LockupFate =
|
|
|
374
389
|
fate: "returned";
|
|
375
390
|
spends: readonly LockupSpend[];
|
|
376
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* At least one output was unilaterally exited: it sits onchain under the
|
|
394
|
+
* VHTLC script, where no offchain claim or refund can reach it.
|
|
395
|
+
*
|
|
396
|
+
* Not terminal, and not a loss. The money is still under the same script
|
|
397
|
+
* with the same leaves, so `completeUnroll` plus an onchain spend can still
|
|
398
|
+
* end the swap either way — which is why this outranks `open`: an output
|
|
399
|
+
* that is "still unspent" but unreachable is not a swap that is merely
|
|
400
|
+
* running.
|
|
401
|
+
*
|
|
402
|
+
* It outranks a verdict too, on a lockup where a sibling output was claimed
|
|
403
|
+
* or returned. That is the rule `open` already sets, not a new one: the
|
|
404
|
+
* unspent test short-circuits before any witness is read, so a partially
|
|
405
|
+
* resolved lockup has never reported `claimed`/`returned`. What changes is
|
|
406
|
+
* only that such a lockup now says why it is unresolved.
|
|
407
|
+
*/
|
|
408
|
+
| {
|
|
409
|
+
fate: "exited";
|
|
410
|
+
outpoints: readonly {
|
|
411
|
+
txid: string;
|
|
412
|
+
vout: number;
|
|
413
|
+
}[];
|
|
414
|
+
}
|
|
377
415
|
/** Nothing was learned: no outputs visible, an output spent by nothing the
|
|
378
416
|
* indexer names, a spend it could not produce, or a blob that would not
|
|
379
417
|
* decode. Never an answer. */
|
|
@@ -409,6 +447,11 @@ type LockupFate =
|
|
|
409
447
|
* response to `unknown` is the same as to `open`: keep watching, and let the
|
|
410
448
|
* refund timelock — which no outage can move — be what ends the wait.
|
|
411
449
|
*
|
|
450
|
+
* **An exit is read before anything else, and over the whole set.** It is the
|
|
451
|
+
* one fact that makes an unspent output unreachable, so it outranks `open`; and
|
|
452
|
+
* it is scanned across every output rather than in outpoint order, so which
|
|
453
|
+
* output happens to come first cannot change the answer.
|
|
454
|
+
*
|
|
412
455
|
* Ask-the-indexer, don't-trust-local-state: read fresh on every poll, never
|
|
413
456
|
* cached, the same posture {@link findLockupVtxos} already establishes.
|
|
414
457
|
*/
|
|
@@ -447,9 +490,9 @@ declare function readLockupFate(indexer: LockupSpendIndexer, input: {
|
|
|
447
490
|
* {@link refundIfUnresolved}, which retries.
|
|
448
491
|
*
|
|
449
492
|
* **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
|
-
*
|
|
493
|
+
* a swept output is no longer a live leaf: `canSpendOffchain` is false wherever
|
|
494
|
+
* `canRecoverOnchain` is true, so a recoverable input cannot be spent this way
|
|
495
|
+
* whatever key signs it (see
|
|
453
496
|
* {@link LockupVtxo.recoverable}). Because every input lands in ONE aggregate
|
|
454
497
|
* transaction, a single swept output would take the live ones down with it —
|
|
455
498
|
* so the whole push is refused with {@link LockupNeedsRecoveryError} naming the
|
|
@@ -1162,7 +1205,9 @@ interface RfqSwapManagerDeps {
|
|
|
1162
1205
|
* `settled`; a lockup fully spent by anything else ends it `refunded`.
|
|
1163
1206
|
* Anything the indexer could not answer is `unknown`, which is NOT an
|
|
1164
1207
|
* answer: the pass carries on to the steps below, whose deadlines an indexer
|
|
1165
|
-
* outage has no bearing on.
|
|
1208
|
+
* outage has no bearing on. `exited` — an output unilaterally taken onchain
|
|
1209
|
+
* — ends neither the swap nor the pass: it blocks the Arkade half below,
|
|
1210
|
+
* with the L1 half left running.
|
|
1166
1211
|
* 2. **Drive the trader's claim.** On an onchain send that is the L1 fill — see
|
|
1167
1212
|
* {@link nextOnchainAction}. On a receive it is the lockup itself, and it
|
|
1168
1213
|
* ends the pass: that leg has no step 3.
|
|
@@ -1493,6 +1538,18 @@ declare class RfqSwapManager {
|
|
|
1493
1538
|
private arm;
|
|
1494
1539
|
private pollSwap;
|
|
1495
1540
|
private runPass;
|
|
1541
|
+
/**
|
|
1542
|
+
* The lockup was unilaterally exited: its outputs sit onchain under the
|
|
1543
|
+
* VHTLC script, where no offchain claim or refund can reach them.
|
|
1544
|
+
*
|
|
1545
|
+
* `needs_counterparty` rather than a terminal state, because the money still
|
|
1546
|
+
* needs action and the swap can still end either way — an onchain claim can
|
|
1547
|
+
* reveal the preimage, an onchain refund can return it — and that state is
|
|
1548
|
+
* documented as re-checked every pass. It must be set from HERE and not from
|
|
1549
|
+
* inside `driveArkadeRefund`, whose two `unblock` calls would lift it again
|
|
1550
|
+
* on the very next pass.
|
|
1551
|
+
*/
|
|
1552
|
+
private blockExitedLockup;
|
|
1496
1553
|
/**
|
|
1497
1554
|
* The receive leg's whole state machine: claim the solver-funded lockup
|
|
1498
1555
|
* while the window is open, and recognise the shapes in which it can be
|
|
@@ -487,12 +487,11 @@ interface InvoiceFacts {
|
|
|
487
487
|
* throws while nothing is funded. `RfqSwapManager` re-registers as a backstop
|
|
488
488
|
* for older records; a repeat write is a no-op.
|
|
489
489
|
*
|
|
490
|
-
* The `sender` key
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
* recovers the funds even without it — but it needs the SOLVER's active
|
|
490
|
+
* The `sender` key is the wallet's identity key, reused by {@link
|
|
491
|
+
* provisionRefundKey} — returned as `senderPubkey` plus `secrets`. `secrets`
|
|
492
|
+
* holds only a public descriptor; the signer re-derives from the wallet, so
|
|
493
|
+
* nothing secret is at rest. Persist `secrets` with the record anyway: it is
|
|
494
|
+
* how the refund signer is found again. `nonInteractiveRefund` recovers the funds even without it — but it needs the SOLVER's active
|
|
496
495
|
* cooperation, not just infrastructure uptime.
|
|
497
496
|
*/
|
|
498
497
|
declare function requestLightningSend(wallet: IWallet, arkServerUrl: string, transport: RfqTransport, params: {
|
|
@@ -515,7 +514,8 @@ declare function requestLightningSend(wallet: IWallet, arkServerUrl: string, tra
|
|
|
515
514
|
* `lockup` (with `address`): without it the manager can only poll, and
|
|
516
515
|
* cannot retire the row this call just wrote. */
|
|
517
516
|
script: InstanceType<typeof VHTLC.ScriptV2>;
|
|
518
|
-
/** Where a failed swap refunds.
|
|
517
|
+
/** Where a failed swap refunds — the same address `secrets.pkScript` was
|
|
518
|
+
* decoded from, so the quote and the covenant always name one script. */
|
|
519
519
|
refundAddress: string;
|
|
520
520
|
/** The VHTLC `sender` x-only key, bound into the covenant. Public. */
|
|
521
521
|
senderPubkey: Uint8Array;
|
|
@@ -528,7 +528,9 @@ declare function requestLightningSend(wallet: IWallet, arkServerUrl: string, tra
|
|
|
528
528
|
* Returned so a consumer can persist the swap without re-deriving any of
|
|
529
529
|
* it. Half of these are not on the quote: `serverPubkey` and `claimDelay`
|
|
530
530
|
* come from this wallet's own `getInfo()`, `emulatorPubkey` from a
|
|
531
|
-
* per-network pin, `refundPkScript` from
|
|
531
|
+
* per-network pin, `refundPkScript` from `secrets` — decoded from the
|
|
532
|
+
* refund address at provisioning time, the same address this call returns
|
|
533
|
+
* as `refundAddress`.
|
|
532
534
|
*
|
|
533
535
|
* All public. Persisting them is optional: this call also registers the
|
|
534
536
|
* lockup as a contract, and that row is where `rebuildRfqSwap` takes its
|
|
@@ -487,12 +487,11 @@ interface InvoiceFacts {
|
|
|
487
487
|
* throws while nothing is funded. `RfqSwapManager` re-registers as a backstop
|
|
488
488
|
* for older records; a repeat write is a no-op.
|
|
489
489
|
*
|
|
490
|
-
* The `sender` key
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
* recovers the funds even without it — but it needs the SOLVER's active
|
|
490
|
+
* The `sender` key is the wallet's identity key, reused by {@link
|
|
491
|
+
* provisionRefundKey} — returned as `senderPubkey` plus `secrets`. `secrets`
|
|
492
|
+
* holds only a public descriptor; the signer re-derives from the wallet, so
|
|
493
|
+
* nothing secret is at rest. Persist `secrets` with the record anyway: it is
|
|
494
|
+
* how the refund signer is found again. `nonInteractiveRefund` recovers the funds even without it — but it needs the SOLVER's active
|
|
496
495
|
* cooperation, not just infrastructure uptime.
|
|
497
496
|
*/
|
|
498
497
|
declare function requestLightningSend(wallet: IWallet, arkServerUrl: string, transport: RfqTransport, params: {
|
|
@@ -515,7 +514,8 @@ declare function requestLightningSend(wallet: IWallet, arkServerUrl: string, tra
|
|
|
515
514
|
* `lockup` (with `address`): without it the manager can only poll, and
|
|
516
515
|
* cannot retire the row this call just wrote. */
|
|
517
516
|
script: InstanceType<typeof VHTLC.ScriptV2>;
|
|
518
|
-
/** Where a failed swap refunds.
|
|
517
|
+
/** Where a failed swap refunds — the same address `secrets.pkScript` was
|
|
518
|
+
* decoded from, so the quote and the covenant always name one script. */
|
|
519
519
|
refundAddress: string;
|
|
520
520
|
/** The VHTLC `sender` x-only key, bound into the covenant. Public. */
|
|
521
521
|
senderPubkey: Uint8Array;
|
|
@@ -528,7 +528,9 @@ declare function requestLightningSend(wallet: IWallet, arkServerUrl: string, tra
|
|
|
528
528
|
* Returned so a consumer can persist the swap without re-deriving any of
|
|
529
529
|
* it. Half of these are not on the quote: `serverPubkey` and `claimDelay`
|
|
530
530
|
* come from this wallet's own `getInfo()`, `emulatorPubkey` from a
|
|
531
|
-
* per-network pin, `refundPkScript` from
|
|
531
|
+
* per-network pin, `refundPkScript` from `secrets` — decoded from the
|
|
532
|
+
* refund address at provisioning time, the same address this call returns
|
|
533
|
+
* as `refundAddress`.
|
|
532
534
|
*
|
|
533
535
|
* All public. Persisting them is optional: this call also registers the
|
|
534
536
|
* lockup as a contract, and that row is where `rebuildRfqSwap` takes its
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arkade-os/swap",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Client-side Arkade Intents asset swaps: discover markets, quote, create/track/cancel offers, restore from chain.",
|
|
6
6
|
"repository": {
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@noble/hashes": "2.0.1",
|
|
71
71
|
"@scure/base": "2.0.0",
|
|
72
72
|
"@scure/btc-signer": "2.0.1",
|
|
73
|
-
"@arkade-os/sdk": "0.4.
|
|
73
|
+
"@arkade-os/sdk": "0.4.67"
|
|
74
74
|
},
|
|
75
75
|
"peerDependencies": {
|
|
76
76
|
"nostr-tools": "^2.12.0"
|
|
@@ -91,8 +91,8 @@
|
|
|
91
91
|
"scripts": {
|
|
92
92
|
"build": "tsup src/index.ts src/nostr.ts src/repositories/sqlite/index.ts src/repositories/realm/index.ts --format esm,cjs --dts --clean",
|
|
93
93
|
"typecheck": "tsc --noEmit",
|
|
94
|
-
"format": "
|
|
95
|
-
"lint": "
|
|
94
|
+
"format": "biome format --write src test",
|
|
95
|
+
"lint": "biome format src test",
|
|
96
96
|
"test": "vitest run --exclude test/e2e",
|
|
97
97
|
"test:unit": "vitest run --exclude test/e2e",
|
|
98
98
|
"test:integration": "vitest run test/e2e/** --exclude test/e2e/rfq*.test.ts",
|