@arkade-os/swap 0.0.10 → 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-5NPYNQ5V.js → chunk-RIC5ZTZK.js} +68 -28
- package/dist/index.cjs +196 -43
- package/dist/index.d.cts +61 -15
- package/dist/index.d.ts +61 -15
- package/dist/index.js +129 -16
- 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-C8FXlmHY.d.cts → repository-B02vsgcV.d.cts} +42 -5
- package/dist/{repository-Cw2EvvZG.d.ts → repository-CfE18Fif.d.ts} +42 -5
- package/dist/{rfq-DglvHMNC.d.ts → rfq-DkckzRKK.d.cts} +44 -16
- package/dist/{rfq-DglvHMNC.d.cts → rfq-DkckzRKK.d.ts} +44 -16
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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-
|
|
1
|
+
import { ProvisionedKey, ProvisionedClaimSecret, asset, RelativeTimelock, 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-B02vsgcV.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-B02vsgcV.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-DkckzRKK.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-DkckzRKK.cjs';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Which wallet key signs this leg. Stored at `profile.signer`.
|
|
@@ -182,7 +182,18 @@ declare function onchainSendProfile(result: {
|
|
|
182
182
|
minConfirmations: number;
|
|
183
183
|
}): Omit<OnchainSendProfile, "signer" | "hashlock">;
|
|
184
184
|
|
|
185
|
-
/**
|
|
185
|
+
/**
|
|
186
|
+
* The contracts, one per WANT side — pure data, shared verbatim with any other
|
|
187
|
+
* implementation.
|
|
188
|
+
*
|
|
189
|
+
* **These are the base: an offer carrying an exit delay compiles to a third
|
|
190
|
+
* closure that is not in either file.** The tree is not fixed-shape — the
|
|
191
|
+
* protocol defines the exit as `iff ExitDelay`, and solverd appends it the same
|
|
192
|
+
* way (`pkg/swap/contract/offer.go`, `VtxoScript`) — so it cannot be a function
|
|
193
|
+
* in a static artifact without splitting these into one file per exit variant.
|
|
194
|
+
* {@link withExitClosure} owns that step, and the golden in `offer.test.ts`
|
|
195
|
+
* pins the artifact it produces so the whole contract is still readable as data.
|
|
196
|
+
*/
|
|
186
197
|
declare const swapPrograms: Record<"wantAsset" | "wantBtc", ReturnType<typeof arkade.parseArtifact>>;
|
|
187
198
|
/** A full-fill offer. Exactly one field names an asset: `wantAsset` set = the
|
|
188
199
|
* fill must deliver that asset (the deposit may be BTC or another asset,
|
|
@@ -203,6 +214,14 @@ interface Offer {
|
|
|
203
214
|
makerPublicKey: Uint8Array;
|
|
204
215
|
/** Covenant co-signer (emulator) x-only key (32 bytes). */
|
|
205
216
|
emulatorPubkey: Uint8Array;
|
|
217
|
+
/** Partial-fill numerator. Reserved wire space in V1: carried through the
|
|
218
|
+
* codec so an offer that sets it decodes, never interpreted here. */
|
|
219
|
+
ratioNum?: bigint;
|
|
220
|
+
/** Partial-fill denominator. Set with {@link Offer.ratioNum} or not at all. */
|
|
221
|
+
ratioDen?: bigint;
|
|
222
|
+
/** The maker's unilateral exit path. Present adds a third closure to the
|
|
223
|
+
* taproot tree, so it changes `swapPkScript` — see {@link offerVtxoScript}. */
|
|
224
|
+
exitDelay?: RelativeTimelock;
|
|
206
225
|
}
|
|
207
226
|
/** Compile the offer's contract: program + args -> taproot tree. */
|
|
208
227
|
declare function offerVtxoScript(offer: Omit<Offer, "swapPkScript">, serverPubkey: Uint8Array): InstanceType<typeof arkade.ArkadeProgramScript>;
|
|
@@ -233,6 +252,14 @@ declare function decodeOffer(data: Uint8Array): Offer;
|
|
|
233
252
|
* funding rather than after: nothing is at stake yet, so a failure can throw
|
|
234
253
|
* and be retried, where the same failure after `wallet.send` would leave a
|
|
235
254
|
* funded deposit unwatched with no way to notice.
|
|
255
|
+
*
|
|
256
|
+
* **The maker's unilateral exit closure is built by default**, at the server's
|
|
257
|
+
* own `unilateralExitDelay` — the same thing solverd does. Without it `cancel`
|
|
258
|
+
* is the only way back out, and `cancel` needs the server's signature: a server
|
|
259
|
+
* that will not co-sign leaves the deposit stuck at the swap address until the
|
|
260
|
+
* VTXO expires and the operator sweeps it. An offer has no expiry of its own,
|
|
261
|
+
* so that exposure has no end. `noExit` opts out for a caller who wants the
|
|
262
|
+
* smaller tree and accepts the dependency.
|
|
236
263
|
*/
|
|
237
264
|
declare function createOffer(wallet: IWallet, arkServerUrl: string, params: {
|
|
238
265
|
wantAmount: bigint;
|
|
@@ -241,6 +268,12 @@ declare function createOffer(wallet: IWallet, arkServerUrl: string, params: {
|
|
|
241
268
|
/** Co-signer key override (33-byte compressed hex); see
|
|
242
269
|
* {@link resolveEmulatorPubkey}. */
|
|
243
270
|
emulatorPubkey?: string;
|
|
271
|
+
/** Override the exit closure's delay. Defaults to the server's own
|
|
272
|
+
* `unilateralExitDelay`, which is the delay solverd uses too. */
|
|
273
|
+
exitDelay?: RelativeTimelock;
|
|
274
|
+
/** Publish without the exit closure, leaving `cancel` — which needs the
|
|
275
|
+
* server — as the only way back out. See the note on this function. */
|
|
276
|
+
noExit?: boolean;
|
|
244
277
|
}): Promise<{
|
|
245
278
|
/** The encoded offer, hex. **Persist this** — it is the only input
|
|
246
279
|
* `cancelOffer` needs to rebuild the covenant, and the restore scan reads
|
|
@@ -263,19 +296,23 @@ declare function createOffer(wallet: IWallet, arkServerUrl: string, params: {
|
|
|
263
296
|
/**
|
|
264
297
|
* Cancel an offer: spend the swap VTXO back to the user. Returns the ark txid.
|
|
265
298
|
*
|
|
266
|
-
* This is the refund path — how a user takes back a deposit no
|
|
267
|
-
* **
|
|
268
|
-
* place at the swap address rather than expiring:
|
|
269
|
-
* "expired" state to unwind, at the cost of the refund being something the
|
|
299
|
+
* This is the cooperative refund path — how a user takes back a deposit no
|
|
300
|
+
* solver filled. **No path here is on a deadline**, so an unfilled deposit
|
|
301
|
+
* keeps its place at the swap address rather than expiring: nothing to miss and
|
|
302
|
+
* no "expired" state to unwind, at the cost of the refund being something the
|
|
270
303
|
* user asks for rather than something a clock delivers.
|
|
271
304
|
*
|
|
272
|
-
*
|
|
305
|
+
* The routes out of the covenant are deliberately asymmetric:
|
|
273
306
|
* - `fulfill` is signed by the **server alone**, but the covenant constrains
|
|
274
307
|
* it to pay output 0 to `makerWP` for at least `wantAmount` — a solver
|
|
275
308
|
* cannot take the deposit without delivering.
|
|
276
309
|
* - `cancel` is a **2-of-2 of the user and the server**, so cancelling is
|
|
277
310
|
* cooperative: the server co-signs. No solver signature is involved, so the
|
|
278
311
|
* refund never depends on the counterparty being reachable.
|
|
312
|
+
* - `exit` is the user **alone** after a relative timelock, present unless the
|
|
313
|
+
* offer was created with `noExit`. It is the route that survives a server
|
|
314
|
+
* that will not co-sign this one, and it is reached by unrolling the VTXO
|
|
315
|
+
* onchain rather than through this function.
|
|
279
316
|
*
|
|
280
317
|
* Cancel therefore races a fill rather than pre-empting it. An offer the solver
|
|
281
318
|
* is filling in the same moment may be spent by `fulfill` first, in which case
|
|
@@ -451,10 +488,19 @@ type SpendKind = "cancelled" | "fulfilled" | "indeterminate";
|
|
|
451
488
|
/**
|
|
452
489
|
* Classify a spend by the covenant leaf it took.
|
|
453
490
|
*
|
|
454
|
-
* The
|
|
455
|
-
*
|
|
456
|
-
* each input's `tapLeafScript`, so the spend
|
|
457
|
-
* reads an answer rather than inferring one.
|
|
491
|
+
* The vocabulary is what became of the deposit, not which key moved it:
|
|
492
|
+
* `fulfill` is the solver paying for it, and everything else returns it to the
|
|
493
|
+
* user. A submitted ark tx keeps each input's `tapLeafScript`, so the spend
|
|
494
|
+
* *states* which one it used — this reads an answer rather than inferring one.
|
|
495
|
+
*
|
|
496
|
+
* **`exit` reports `cancelled`, like `cancel` does.** The two differ in who had
|
|
497
|
+
* to agree — `cancel` is cooperative with the signer, `exit` is the maker alone
|
|
498
|
+
* after a delay — but not in outcome: the deposit went back unfilled either
|
|
499
|
+
* way, which is the question this answers. Reporting the exit leaf as
|
|
500
|
+
* `indeterminate` instead would be worse than imprecise: no status is written,
|
|
501
|
+
* so the swap stays `pending`, `restoreAssetSwaps` re-queues it on every scan,
|
|
502
|
+
* and `retireOfferContract` never runs — leaving a dead script in the
|
|
503
|
+
* subscription and the failsafe poll for the life of the wallet.
|
|
458
504
|
*
|
|
459
505
|
* **Hand it the transaction that actually spends the deposit outpoint, which is
|
|
460
506
|
* the checkpoint, not the ark tx.** A spend is two linked transactions: the
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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-
|
|
1
|
+
import { ProvisionedKey, ProvisionedClaimSecret, asset, RelativeTimelock, 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-CfE18Fif.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-CfE18Fif.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-DkckzRKK.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-DkckzRKK.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Which wallet key signs this leg. Stored at `profile.signer`.
|
|
@@ -182,7 +182,18 @@ declare function onchainSendProfile(result: {
|
|
|
182
182
|
minConfirmations: number;
|
|
183
183
|
}): Omit<OnchainSendProfile, "signer" | "hashlock">;
|
|
184
184
|
|
|
185
|
-
/**
|
|
185
|
+
/**
|
|
186
|
+
* The contracts, one per WANT side — pure data, shared verbatim with any other
|
|
187
|
+
* implementation.
|
|
188
|
+
*
|
|
189
|
+
* **These are the base: an offer carrying an exit delay compiles to a third
|
|
190
|
+
* closure that is not in either file.** The tree is not fixed-shape — the
|
|
191
|
+
* protocol defines the exit as `iff ExitDelay`, and solverd appends it the same
|
|
192
|
+
* way (`pkg/swap/contract/offer.go`, `VtxoScript`) — so it cannot be a function
|
|
193
|
+
* in a static artifact without splitting these into one file per exit variant.
|
|
194
|
+
* {@link withExitClosure} owns that step, and the golden in `offer.test.ts`
|
|
195
|
+
* pins the artifact it produces so the whole contract is still readable as data.
|
|
196
|
+
*/
|
|
186
197
|
declare const swapPrograms: Record<"wantAsset" | "wantBtc", ReturnType<typeof arkade.parseArtifact>>;
|
|
187
198
|
/** A full-fill offer. Exactly one field names an asset: `wantAsset` set = the
|
|
188
199
|
* fill must deliver that asset (the deposit may be BTC or another asset,
|
|
@@ -203,6 +214,14 @@ interface Offer {
|
|
|
203
214
|
makerPublicKey: Uint8Array;
|
|
204
215
|
/** Covenant co-signer (emulator) x-only key (32 bytes). */
|
|
205
216
|
emulatorPubkey: Uint8Array;
|
|
217
|
+
/** Partial-fill numerator. Reserved wire space in V1: carried through the
|
|
218
|
+
* codec so an offer that sets it decodes, never interpreted here. */
|
|
219
|
+
ratioNum?: bigint;
|
|
220
|
+
/** Partial-fill denominator. Set with {@link Offer.ratioNum} or not at all. */
|
|
221
|
+
ratioDen?: bigint;
|
|
222
|
+
/** The maker's unilateral exit path. Present adds a third closure to the
|
|
223
|
+
* taproot tree, so it changes `swapPkScript` — see {@link offerVtxoScript}. */
|
|
224
|
+
exitDelay?: RelativeTimelock;
|
|
206
225
|
}
|
|
207
226
|
/** Compile the offer's contract: program + args -> taproot tree. */
|
|
208
227
|
declare function offerVtxoScript(offer: Omit<Offer, "swapPkScript">, serverPubkey: Uint8Array): InstanceType<typeof arkade.ArkadeProgramScript>;
|
|
@@ -233,6 +252,14 @@ declare function decodeOffer(data: Uint8Array): Offer;
|
|
|
233
252
|
* funding rather than after: nothing is at stake yet, so a failure can throw
|
|
234
253
|
* and be retried, where the same failure after `wallet.send` would leave a
|
|
235
254
|
* funded deposit unwatched with no way to notice.
|
|
255
|
+
*
|
|
256
|
+
* **The maker's unilateral exit closure is built by default**, at the server's
|
|
257
|
+
* own `unilateralExitDelay` — the same thing solverd does. Without it `cancel`
|
|
258
|
+
* is the only way back out, and `cancel` needs the server's signature: a server
|
|
259
|
+
* that will not co-sign leaves the deposit stuck at the swap address until the
|
|
260
|
+
* VTXO expires and the operator sweeps it. An offer has no expiry of its own,
|
|
261
|
+
* so that exposure has no end. `noExit` opts out for a caller who wants the
|
|
262
|
+
* smaller tree and accepts the dependency.
|
|
236
263
|
*/
|
|
237
264
|
declare function createOffer(wallet: IWallet, arkServerUrl: string, params: {
|
|
238
265
|
wantAmount: bigint;
|
|
@@ -241,6 +268,12 @@ declare function createOffer(wallet: IWallet, arkServerUrl: string, params: {
|
|
|
241
268
|
/** Co-signer key override (33-byte compressed hex); see
|
|
242
269
|
* {@link resolveEmulatorPubkey}. */
|
|
243
270
|
emulatorPubkey?: string;
|
|
271
|
+
/** Override the exit closure's delay. Defaults to the server's own
|
|
272
|
+
* `unilateralExitDelay`, which is the delay solverd uses too. */
|
|
273
|
+
exitDelay?: RelativeTimelock;
|
|
274
|
+
/** Publish without the exit closure, leaving `cancel` — which needs the
|
|
275
|
+
* server — as the only way back out. See the note on this function. */
|
|
276
|
+
noExit?: boolean;
|
|
244
277
|
}): Promise<{
|
|
245
278
|
/** The encoded offer, hex. **Persist this** — it is the only input
|
|
246
279
|
* `cancelOffer` needs to rebuild the covenant, and the restore scan reads
|
|
@@ -263,19 +296,23 @@ declare function createOffer(wallet: IWallet, arkServerUrl: string, params: {
|
|
|
263
296
|
/**
|
|
264
297
|
* Cancel an offer: spend the swap VTXO back to the user. Returns the ark txid.
|
|
265
298
|
*
|
|
266
|
-
* This is the refund path — how a user takes back a deposit no
|
|
267
|
-
* **
|
|
268
|
-
* place at the swap address rather than expiring:
|
|
269
|
-
* "expired" state to unwind, at the cost of the refund being something the
|
|
299
|
+
* This is the cooperative refund path — how a user takes back a deposit no
|
|
300
|
+
* solver filled. **No path here is on a deadline**, so an unfilled deposit
|
|
301
|
+
* keeps its place at the swap address rather than expiring: nothing to miss and
|
|
302
|
+
* no "expired" state to unwind, at the cost of the refund being something the
|
|
270
303
|
* user asks for rather than something a clock delivers.
|
|
271
304
|
*
|
|
272
|
-
*
|
|
305
|
+
* The routes out of the covenant are deliberately asymmetric:
|
|
273
306
|
* - `fulfill` is signed by the **server alone**, but the covenant constrains
|
|
274
307
|
* it to pay output 0 to `makerWP` for at least `wantAmount` — a solver
|
|
275
308
|
* cannot take the deposit without delivering.
|
|
276
309
|
* - `cancel` is a **2-of-2 of the user and the server**, so cancelling is
|
|
277
310
|
* cooperative: the server co-signs. No solver signature is involved, so the
|
|
278
311
|
* refund never depends on the counterparty being reachable.
|
|
312
|
+
* - `exit` is the user **alone** after a relative timelock, present unless the
|
|
313
|
+
* offer was created with `noExit`. It is the route that survives a server
|
|
314
|
+
* that will not co-sign this one, and it is reached by unrolling the VTXO
|
|
315
|
+
* onchain rather than through this function.
|
|
279
316
|
*
|
|
280
317
|
* Cancel therefore races a fill rather than pre-empting it. An offer the solver
|
|
281
318
|
* is filling in the same moment may be spent by `fulfill` first, in which case
|
|
@@ -451,10 +488,19 @@ type SpendKind = "cancelled" | "fulfilled" | "indeterminate";
|
|
|
451
488
|
/**
|
|
452
489
|
* Classify a spend by the covenant leaf it took.
|
|
453
490
|
*
|
|
454
|
-
* The
|
|
455
|
-
*
|
|
456
|
-
* each input's `tapLeafScript`, so the spend
|
|
457
|
-
* reads an answer rather than inferring one.
|
|
491
|
+
* The vocabulary is what became of the deposit, not which key moved it:
|
|
492
|
+
* `fulfill` is the solver paying for it, and everything else returns it to the
|
|
493
|
+
* user. A submitted ark tx keeps each input's `tapLeafScript`, so the spend
|
|
494
|
+
* *states* which one it used — this reads an answer rather than inferring one.
|
|
495
|
+
*
|
|
496
|
+
* **`exit` reports `cancelled`, like `cancel` does.** The two differ in who had
|
|
497
|
+
* to agree — `cancel` is cooperative with the signer, `exit` is the maker alone
|
|
498
|
+
* after a delay — but not in outcome: the deposit went back unfilled either
|
|
499
|
+
* way, which is the question this answers. Reporting the exit leaf as
|
|
500
|
+
* `indeterminate` instead would be worse than imprecise: no status is written,
|
|
501
|
+
* so the swap stays `pending`, `restoreAssetSwaps` re-queues it on every scan,
|
|
502
|
+
* and `retireOfferContract` never runs — leaving a dead script in the
|
|
503
|
+
* subscription and the failsafe poll for the life of the wallet.
|
|
458
504
|
*
|
|
459
505
|
* **Hand it the transaction that actually spends the deposit outpoint, which is
|
|
460
506
|
* the checkpoint, not the ark tx.** A spend is two linked transactions: the
|
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-RIC5ZTZK.js";
|
|
67
67
|
import {
|
|
68
68
|
InMemoryAssetSwapRepository,
|
|
69
69
|
marketsCacheKey
|
|
@@ -329,12 +329,30 @@ var swapPrograms = {
|
|
|
329
329
|
wantAsset: arkade.parseArtifact(swap_want_asset_program_default),
|
|
330
330
|
wantBtc: arkade.parseArtifact(swap_want_btc_program_default)
|
|
331
331
|
};
|
|
332
|
+
function withExitClosure(program, exit) {
|
|
333
|
+
if (!exit) return program;
|
|
334
|
+
return {
|
|
335
|
+
...program,
|
|
336
|
+
// typed params are authoritative: an undeclared `$exitDelay` fails
|
|
337
|
+
// validateProgram instead of compiling against an unbound value
|
|
338
|
+
params: [...program.params ?? [], { name: "exitDelay", type: "int" }],
|
|
339
|
+
functions: {
|
|
340
|
+
...program.functions,
|
|
341
|
+
exit: {
|
|
342
|
+
tapscript: { signers: ["$user"], csv: { type: exit.type, value: "$exitDelay" } }
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
332
347
|
function swapProgramBinding(offer, serverPubkey) {
|
|
333
348
|
if (offer.makerPkScript.length !== FIELDS.makerPkScript.width) {
|
|
334
349
|
throw new Error("makerPkScript is not a 34-byte taproot scriptPubKey");
|
|
335
350
|
}
|
|
336
351
|
return {
|
|
337
|
-
program:
|
|
352
|
+
program: withExitClosure(
|
|
353
|
+
offer.wantAsset ? swapPrograms.wantAsset : swapPrograms.wantBtc,
|
|
354
|
+
offer.exitDelay
|
|
355
|
+
),
|
|
338
356
|
args: {
|
|
339
357
|
makerWP: offer.makerPkScript.subarray(2),
|
|
340
358
|
wantAmount: offer.wantAmount,
|
|
@@ -344,7 +362,8 @@ function swapProgramBinding(offer, serverPubkey) {
|
|
|
344
362
|
...offer.wantAsset && {
|
|
345
363
|
wantAssetTxid: offer.wantAsset.txid.slice().reverse(),
|
|
346
364
|
wantAssetGroupIndex: offer.wantAsset.groupIndex
|
|
347
|
-
}
|
|
365
|
+
},
|
|
366
|
+
...offer.exitDelay && { exitDelay: offer.exitDelay.value }
|
|
348
367
|
},
|
|
349
368
|
keys: {
|
|
350
369
|
serverKey: serverPubkey,
|
|
@@ -365,9 +384,27 @@ var FIELDS = {
|
|
|
365
384
|
makerPkScript: { tag: 5, width: 34 },
|
|
366
385
|
makerPublicKey: { tag: 7, width: 32 },
|
|
367
386
|
emulatorPubkey: { tag: 8, width: 32 },
|
|
368
|
-
|
|
387
|
+
ratioNum: { tag: 9, width: 8 },
|
|
388
|
+
ratioDen: { tag: 10, width: 8 },
|
|
389
|
+
offerAsset: { tag: 11, width: void 0 },
|
|
390
|
+
exitTimelock: { tag: 12, width: 9 }
|
|
369
391
|
};
|
|
392
|
+
var EXIT_TYPES = ["blocks", "seconds"];
|
|
370
393
|
var NAMES = Object.fromEntries(Object.entries(FIELDS).map(([k, f]) => [f.tag, k]));
|
|
394
|
+
function u64(name, value) {
|
|
395
|
+
if (value < BigInt(0) || value >> BigInt(64) > BigInt(0)) {
|
|
396
|
+
throw new Error(`${name} does not fit the offer wire format (u64)`);
|
|
397
|
+
}
|
|
398
|
+
const out = new Uint8Array(FIELDS.wantAmount.width);
|
|
399
|
+
new DataView(out.buffer).setBigUint64(0, value, false);
|
|
400
|
+
return out;
|
|
401
|
+
}
|
|
402
|
+
var readU64 = (value) => new DataView(value.buffer, value.byteOffset).getBigUint64(0, false);
|
|
403
|
+
var setRatio = (name, value) => {
|
|
404
|
+
if (value === void 0 || value === BigInt(0)) return void 0;
|
|
405
|
+
if (value < BigInt(0)) throw new Error(`${name} does not fit the offer wire format (u64)`);
|
|
406
|
+
return value;
|
|
407
|
+
};
|
|
371
408
|
function tlv(type, value) {
|
|
372
409
|
if (value.length > 65535) throw new Error("TLV value exceeds the u16 length field");
|
|
373
410
|
return concatBytes(Uint8Array.of(type, value.length >> 8 & 255, value.length & 255), value);
|
|
@@ -386,24 +423,45 @@ function encodeOffer(offer) {
|
|
|
386
423
|
throw new Error(`${name} must be ${FIELDS[name].width} bytes`);
|
|
387
424
|
}
|
|
388
425
|
}
|
|
389
|
-
|
|
390
|
-
|
|
426
|
+
const ratioNum = setRatio("ratioNum", offer.ratioNum);
|
|
427
|
+
const ratioDen = setRatio("ratioDen", offer.ratioDen);
|
|
428
|
+
if (ratioNum === void 0 !== (ratioDen === void 0)) {
|
|
429
|
+
throw new Error("offer must carry both ratioNum and ratioDen, or neither");
|
|
391
430
|
}
|
|
392
|
-
const amount = new Uint8Array(FIELDS.wantAmount.width);
|
|
393
|
-
new DataView(amount.buffer).setBigUint64(0, offer.wantAmount, false);
|
|
394
431
|
const recs = [
|
|
395
432
|
tlv(FIELDS.swapPkScript.tag, offer.swapPkScript),
|
|
396
|
-
tlv(FIELDS.wantAmount.tag,
|
|
433
|
+
tlv(FIELDS.wantAmount.tag, u64("wantAmount", offer.wantAmount))
|
|
397
434
|
];
|
|
398
435
|
if (offer.wantAsset) recs.push(tlv(FIELDS.wantAsset.tag, offer.wantAsset.serialize()));
|
|
436
|
+
if (ratioNum !== void 0) recs.push(tlv(FIELDS.ratioNum.tag, u64("ratioNum", ratioNum)));
|
|
437
|
+
if (ratioDen !== void 0) recs.push(tlv(FIELDS.ratioDen.tag, u64("ratioDen", ratioDen)));
|
|
399
438
|
if (offer.offerAsset) recs.push(tlv(FIELDS.offerAsset.tag, offer.offerAsset.serialize()));
|
|
400
439
|
recs.push(
|
|
401
440
|
tlv(FIELDS.makerPkScript.tag, offer.makerPkScript),
|
|
402
441
|
tlv(FIELDS.makerPublicKey.tag, offer.makerPublicKey),
|
|
403
442
|
tlv(FIELDS.emulatorPubkey.tag, offer.emulatorPubkey)
|
|
404
443
|
);
|
|
444
|
+
if (offer.exitDelay) recs.push(tlv(FIELDS.exitTimelock.tag, encodeExitDelay(offer.exitDelay)));
|
|
405
445
|
return concatBytes(...recs);
|
|
406
446
|
}
|
|
447
|
+
function encodeExitDelay(exit) {
|
|
448
|
+
return concatBytes(
|
|
449
|
+
Uint8Array.of(EXIT_TYPES.indexOf(assertExitDelay(exit).type)),
|
|
450
|
+
u64("exitDelay", exit.value)
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
function assertExitDelay(exit) {
|
|
454
|
+
if (EXIT_TYPES.indexOf(exit.type) < 0) {
|
|
455
|
+
throw new Error(`unknown exitDelay locktime type: ${exit.type}`);
|
|
456
|
+
}
|
|
457
|
+
if (exit.value <= BigInt(0)) {
|
|
458
|
+
throw new Error("exitDelay must be a positive relative locktime");
|
|
459
|
+
}
|
|
460
|
+
if (exit.value >> BigInt(32) > BigInt(0)) {
|
|
461
|
+
throw new Error("exitDelay does not fit the locktime field (u32)");
|
|
462
|
+
}
|
|
463
|
+
return exit;
|
|
464
|
+
}
|
|
407
465
|
function decodeOffer(data) {
|
|
408
466
|
const fields = {};
|
|
409
467
|
let off = 0;
|
|
@@ -423,6 +481,12 @@ function decodeOffer(data) {
|
|
|
423
481
|
for (const name of ["wantAsset", "offerAsset"]) {
|
|
424
482
|
if (fields[name]?.length === 0) throw new Error(`missing/invalid ${name}`);
|
|
425
483
|
}
|
|
484
|
+
for (const [name, value] of Object.entries(fields)) {
|
|
485
|
+
const width = FIELDS[name].width;
|
|
486
|
+
if (width !== void 0 && value.length !== width) {
|
|
487
|
+
throw new Error(`missing/invalid ${name}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
426
490
|
const need = (name) => {
|
|
427
491
|
const v = fields[name];
|
|
428
492
|
const len = FIELDS[name].width;
|
|
@@ -434,16 +498,36 @@ function decodeOffer(data) {
|
|
|
434
498
|
if (Boolean(fields.wantAsset) === Boolean(fields.offerAsset)) {
|
|
435
499
|
throw new Error("offer must carry exactly one of wantAsset or offerAsset");
|
|
436
500
|
}
|
|
501
|
+
const readRatio = (name) => {
|
|
502
|
+
const raw = fields[name];
|
|
503
|
+
if (!raw) return void 0;
|
|
504
|
+
const value = readU64(raw);
|
|
505
|
+
if (value === BigInt(0)) throw new Error(`missing/invalid ${name}`);
|
|
506
|
+
return value;
|
|
507
|
+
};
|
|
508
|
+
const ratioNum = readRatio("ratioNum");
|
|
509
|
+
const ratioDen = readRatio("ratioDen");
|
|
510
|
+
if (ratioNum === void 0 !== (ratioDen === void 0)) {
|
|
511
|
+
throw new Error("offer must carry both ratioNum and ratioDen, or neither");
|
|
512
|
+
}
|
|
437
513
|
return {
|
|
438
514
|
swapPkScript: need("swapPkScript"),
|
|
439
|
-
wantAmount:
|
|
515
|
+
wantAmount: readU64(amount),
|
|
440
516
|
...fields.wantAsset && { wantAsset: asset.AssetId.fromBytes(fields.wantAsset) },
|
|
441
517
|
...fields.offerAsset && { offerAsset: asset.AssetId.fromBytes(fields.offerAsset) },
|
|
442
518
|
makerPkScript: need("makerPkScript"),
|
|
443
519
|
makerPublicKey: need("makerPublicKey"),
|
|
444
|
-
emulatorPubkey: need("emulatorPubkey")
|
|
520
|
+
emulatorPubkey: need("emulatorPubkey"),
|
|
521
|
+
...ratioNum !== void 0 && { ratioNum },
|
|
522
|
+
...ratioDen !== void 0 && { ratioDen },
|
|
523
|
+
...fields.exitTimelock && { exitDelay: decodeExitDelay(fields.exitTimelock) }
|
|
445
524
|
};
|
|
446
525
|
}
|
|
526
|
+
function decodeExitDelay(value) {
|
|
527
|
+
const type = EXIT_TYPES[value[0]];
|
|
528
|
+
if (!type) throw new Error(`unknown exitDelay locktime type: 0x${value[0].toString(16)}`);
|
|
529
|
+
return { type, value: readU64(value.subarray(1)) };
|
|
530
|
+
}
|
|
447
531
|
var OFFER_CONTRACT_LABEL = "Arkade swap offer";
|
|
448
532
|
var OFFER_CONTRACT_KIND = "asset-swap-offer";
|
|
449
533
|
async function registerOfferContract(wallet, arkServerUrl, network, binding, serverPubkey, expectedPkScript) {
|
|
@@ -469,6 +553,14 @@ async function registerOfferContract(wallet, arkServerUrl, network, binding, ser
|
|
|
469
553
|
});
|
|
470
554
|
await promoteOfferContract(contractManager, hex2.encode(expectedPkScript));
|
|
471
555
|
}
|
|
556
|
+
function serverExitDelay(delay) {
|
|
557
|
+
if (typeof delay !== "bigint" || delay <= BigInt(0)) {
|
|
558
|
+
throw new Error(
|
|
559
|
+
"the server reports no usable unilateralExitDelay; pass `exitDelay` to set the offer's exit closure explicitly, or `noExit: true` to publish without one"
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
return assertExitDelay({ value: delay, type: delay < BigInt(512) ? "blocks" : "seconds" });
|
|
563
|
+
}
|
|
472
564
|
async function createOffer(wallet, arkServerUrl, params) {
|
|
473
565
|
if (Boolean(params.wantAsset) === Boolean(params.offerAsset)) {
|
|
474
566
|
throw new Error("set exactly one of wantAsset (BTC->asset) or offerAsset (asset->BTC)");
|
|
@@ -489,7 +581,11 @@ async function createOffer(wallet, arkServerUrl, params) {
|
|
|
489
581
|
offerAsset: params.offerAsset,
|
|
490
582
|
makerPkScript: ArkAddress.decode(makerAddress).pkScript,
|
|
491
583
|
makerPublicKey,
|
|
492
|
-
emulatorPubkey: emuKey
|
|
584
|
+
emulatorPubkey: emuKey,
|
|
585
|
+
// checked HERE, before the covenant is derived and registered below:
|
|
586
|
+
// deferring it to `encodeOffer` leaves a registered contract behind for
|
|
587
|
+
// an offer that then fails to encode. @see assertExitDelay
|
|
588
|
+
exitDelay: params.noExit ? void 0 : params.exitDelay ? assertExitDelay(params.exitDelay) : serverExitDelay(info.unilateralExitDelay)
|
|
493
589
|
};
|
|
494
590
|
const script = offerVtxoScript(binding, serverPubKey);
|
|
495
591
|
const offer = { ...binding, swapPkScript: script.pkScript };
|
|
@@ -1178,7 +1274,9 @@ function classifySpend(offer, serverPubkey, spendTx, deposit) {
|
|
|
1178
1274
|
const script = offerVtxoScript(offer, serverPubkey);
|
|
1179
1275
|
if (hex5.encode(script.pkScript) !== hex5.encode(offer.swapPkScript)) return "indeterminate";
|
|
1180
1276
|
leaves = {
|
|
1181
|
-
|
|
1277
|
+
// both routes that hand the deposit back; `exit` is absent on an
|
|
1278
|
+
// offer that carries no exit closure, and drops out here
|
|
1279
|
+
returned: ["cancel", "exit"].map((name) => script.functionByName(name)?.leafScript).filter((leaf) => leaf !== void 0),
|
|
1182
1280
|
fulfill: script.functionByName("fulfill")?.leafScript
|
|
1183
1281
|
};
|
|
1184
1282
|
} catch {
|
|
@@ -1190,7 +1288,7 @@ function classifySpend(offer, serverPubkey, spendTx, deposit) {
|
|
|
1190
1288
|
if (hex5.encode(input.txid) !== deposit.txid) continue;
|
|
1191
1289
|
for (const leaf of input.tapLeafScript ?? []) {
|
|
1192
1290
|
const spent = hex5.encode(scriptFromTapLeafScript(leaf));
|
|
1193
|
-
if (leaves.
|
|
1291
|
+
if (leaves.returned.some((back) => spent === hex5.encode(back))) return "cancelled";
|
|
1194
1292
|
if (leaves.fulfill && spent === hex5.encode(leaves.fulfill)) return "fulfilled";
|
|
1195
1293
|
}
|
|
1196
1294
|
}
|
|
@@ -1548,10 +1646,10 @@ async function pushRefundWithoutReceiver(ark, input) {
|
|
|
1548
1646
|
input.script.options.refundLocktime
|
|
1549
1647
|
);
|
|
1550
1648
|
}
|
|
1551
|
-
const refundPkScript = input.refundPkScript ?? input.script.options.
|
|
1649
|
+
const refundPkScript = input.refundPkScript ?? input.script.options.nonInteractiveParameters?.senderPkScript;
|
|
1552
1650
|
if (!refundPkScript) {
|
|
1553
1651
|
throw new Error(
|
|
1554
|
-
"no refund destination: the contract carries no
|
|
1652
|
+
"no refund destination: the contract carries no emulator covenant suite, so pass refundPkScript explicitly"
|
|
1555
1653
|
);
|
|
1556
1654
|
}
|
|
1557
1655
|
const info = await ark.getInfo();
|
|
@@ -1603,6 +1701,21 @@ async function refundIfUnresolved(transport, ark, indexer, input) {
|
|
|
1603
1701
|
const status = await transport.status(input.rfqId);
|
|
1604
1702
|
if (status && isResolved(status.state)) return { outcome: "resolved", status };
|
|
1605
1703
|
if (now() >= input.refundLocktime) {
|
|
1704
|
+
let fate = { fate: "unknown" };
|
|
1705
|
+
try {
|
|
1706
|
+
fate = await readLockupFate(indexer, {
|
|
1707
|
+
swapPkScript: input.script.pkScript,
|
|
1708
|
+
paymentHash: input.paymentHash
|
|
1709
|
+
});
|
|
1710
|
+
} catch {
|
|
1711
|
+
}
|
|
1712
|
+
if (fate.fate === "exited") {
|
|
1713
|
+
return {
|
|
1714
|
+
outcome: "exited",
|
|
1715
|
+
outpoints: fate.outpoints.map((o) => `${o.txid}:${o.vout}`),
|
|
1716
|
+
status
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1606
1719
|
const vtxos = await findLockupVtxos(indexer, input.script.pkScript);
|
|
1607
1720
|
if (vtxos.length === 0) return { outcome: "nothing_to_refund", status };
|
|
1608
1721
|
try {
|
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-B02vsgcV.cjs';
|
|
3
3
|
import '@arkade-os/solver-discovery';
|
|
4
4
|
import '@arkade-os/sdk';
|
|
5
|
-
import '../../rfq-
|
|
5
|
+
import '../../rfq-DkckzRKK.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-CfE18Fif.js';
|
|
3
3
|
import '@arkade-os/solver-discovery';
|
|
4
4
|
import '@arkade-os/sdk';
|
|
5
|
-
import '../../rfq-
|
|
5
|
+
import '../../rfq-DkckzRKK.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-B02vsgcV.cjs';
|
|
3
3
|
import '@arkade-os/solver-discovery';
|
|
4
4
|
import '@arkade-os/sdk';
|
|
5
|
-
import '../../rfq-
|
|
5
|
+
import '../../rfq-DkckzRKK.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-CfE18Fif.js';
|
|
3
3
|
import '@arkade-os/solver-discovery';
|
|
4
4
|
import '@arkade-os/sdk';
|
|
5
|
-
import '../../rfq-
|
|
5
|
+
import '../../rfq-DkckzRKK.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* SQLite backend over the SDK's `SQLExecutor`, so any driver plugs in
|