@arkade-os/swap 0.0.3 → 0.0.5
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 +189 -54
- package/dist/{chunk-HLMMA3LJ.js → chunk-Q4FAYBXS.js} +60 -247
- package/dist/index.cjs +212 -301
- package/dist/index.d.cts +283 -10
- package/dist/index.d.ts +283 -10
- package/dist/index.js +150 -69
- package/dist/nostr.cjs +21 -14
- package/dist/nostr.d.cts +14 -3
- package/dist/nostr.d.ts +14 -3
- package/dist/nostr.js +10 -2
- package/dist/{rfq-CCAFoFtR.d.cts → rfq-BH2yvo3O.d.cts} +50 -335
- package/dist/{rfq-CCAFoFtR.d.ts → rfq-BH2yvo3O.d.ts} +50 -335
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -303,8 +303,7 @@ import {
|
|
|
303
303
|
awaitOnchainFill,
|
|
304
304
|
claimOnchainFill,
|
|
305
305
|
addAssetSwap,
|
|
306
|
-
|
|
307
|
-
rfqSecretsToRecord,
|
|
306
|
+
swapSecretsToRecord,
|
|
308
307
|
} from "@arkade-os/swap";
|
|
309
308
|
|
|
310
309
|
const swap = await requestOnchainSend(wallet, arkServerUrl, httpTransport(solverUrl), {
|
|
@@ -323,7 +322,7 @@ await addAssetSwap(repository, {
|
|
|
323
322
|
swapPkScript: hex.encode(swap.swapPkScript),
|
|
324
323
|
htlcPkScriptHex: hex.encode(swap.htlc.pkScript),
|
|
325
324
|
htlcLocktime: swap.htlc.refundLocktime,
|
|
326
|
-
...
|
|
325
|
+
...swapSecretsToRecord(swap.secrets),
|
|
327
326
|
});
|
|
328
327
|
await wallet.send({ address: swap.address, amount: swap.fundAmount });
|
|
329
328
|
|
|
@@ -333,7 +332,7 @@ const utxo = await awaitOnchainFill(chain, swap.htlc, minConfirmations);
|
|
|
333
332
|
await claimOnchainFill(chain, {
|
|
334
333
|
htlc: swap.htlc,
|
|
335
334
|
utxo,
|
|
336
|
-
preimage:
|
|
335
|
+
preimage: swap.secrets.preimage,
|
|
337
336
|
payoutPkScript,
|
|
338
337
|
feeRateSatVb,
|
|
339
338
|
sign,
|
|
@@ -355,11 +354,9 @@ Crash recovery is record-driven, not chain-driven: `classifyOnchainHtlc` re-deri
|
|
|
355
354
|
state (unfunded / awaiting confirmations / claimable / refundable / claimed-with-P / swept) from
|
|
356
355
|
`ChainSource` plus the stored outpoint — without the stored record a spent HTLC is
|
|
357
356
|
indistinguishable from an unfunded one, which is why persisting before funding is mandatory. The
|
|
358
|
-
`AssetSwap` record carries the onchain fields (`paymentHash`, `signingDescriptor`,
|
|
359
|
-
|
|
360
|
-
`
|
|
361
|
-
`fallbackSecrets` is versioned and discriminated: `{ version: 1, type: "stored",
|
|
362
|
-
senderPrivateKeyHex, preimageHex? }`.
|
|
357
|
+
`AssetSwap` record carries the onchain fields (`paymentHash`, `signingDescriptor`, `preimageHex`
|
|
358
|
+
for a P that cannot be re-derived, `htlcPkScriptHex`, `htlcLocktime`, `l1Txid`) and the statuses
|
|
359
|
+
`awaiting_fill / claimable / claimed / refunded_l1`.
|
|
363
360
|
|
|
364
361
|
**On-board corridors are covered.** `requestLightningReceive` (`lightning:BTC -> arkade:BTC`) and
|
|
365
362
|
`requestOnchainReceive` (`onchain:BTC -> arkade:BTC`) mirror the send-side flows: quote → derive
|
|
@@ -421,29 +418,67 @@ Lightning HTLC lapses, and **the payer is refunded** — the trader loses the in
|
|
|
421
418
|
funds it was holding. Which is why staying online to claim is an obligation and not a preference:
|
|
422
419
|
covclaimd cannot claim this covenant today, so the claim packet's offline path does not yet run.
|
|
423
420
|
|
|
424
|
-
##
|
|
421
|
+
## Swap secrets come from the wallet, not from this package
|
|
425
422
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
423
|
+
This package holds no key logic at all. It names the leg it is building and the SDK answers:
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
// a leg we fund — all it needs is the key that refunds it
|
|
427
|
+
const { pubkey: refundPubkey, descriptor: refundDescriptor } = await provisionRefundKey(wallet);
|
|
428
|
+
// a leg we claim — the key that receives it, and the P that unlocks it
|
|
429
|
+
const { pubkey, descriptor, preimage, paymentHash, mustPersistPreimage } =
|
|
430
|
+
await provisionClaimSecret(wallet);
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Where the key comes from is the wallet's decision, invisible here: an HD wallet allocates a fresh
|
|
434
|
+
descriptor per swap, a static wallet answers with its one `tr(pubkey)`. The record keeps the
|
|
435
|
+
descriptor, which is public, and `contractSigner(wallet, descriptor)` recovers the signer.
|
|
436
|
+
|
|
437
|
+
What each swap stores, and what is recoverable:
|
|
438
|
+
|
|
439
|
+
| Wallet answers with | Spending key | Preimage (when the leg needs one) | Secret at rest |
|
|
440
|
+
| ------------------------- | --------------------- | ------------------------------------- | ----------------- |
|
|
441
|
+
| fresh HD descriptor | re-derives from seed | derives deterministically | none |
|
|
442
|
+
| static `tr(pubkey)` | the wallet's identity | derives from a public per-swap salt | none |
|
|
443
|
+
| a signer that cannot sign | | | |
|
|
444
|
+
| deterministically | the wallet's identity | random, stored on the record | the preimage only |
|
|
445
|
+
|
|
446
|
+
The preimage split follows the **descriptor's shape**, not the wallet's type. An HD child
|
|
447
|
+
descriptor is unique to its swap, so `sha256(sign_det(...))` over the key alone is safe. A static
|
|
448
|
+
descriptor is the same key for every swap, so that derivation would repeat across swaps — one
|
|
449
|
+
solver learning its own preimage would learn every other swap's — and the uniqueness has to come
|
|
450
|
+
from the message instead: the SDK mints 32 random bytes per swap, signs a **salted** message, and
|
|
451
|
+
stores the salt in the clear.
|
|
452
|
+
|
|
453
|
+
**The salt is not a secret.** Knowing it yields nothing without the seed, which is the whole
|
|
454
|
+
difference from the preimage it replaces: the record goes from carrying a per-swap _secret_ to a
|
|
455
|
+
per-swap _public_ value, exactly what `signingDescriptor` already is. Recoverability is unchanged
|
|
456
|
+
in shape — keep the record and the swap recovers from the seed.
|
|
457
|
+
|
|
458
|
+
Only a signer that cannot sign deterministically at all — an external or extension signer — still
|
|
459
|
+
gets a random stored preimage. `mustPersistPreimage` says which you got, and it is the only thing
|
|
460
|
+
to branch on. A stored preimage remains the one secret at rest in the design, and it is never a
|
|
461
|
+
private key.
|
|
429
462
|
|
|
430
463
|
```ts
|
|
431
464
|
const swap = await requestOnchainSend(/* … */);
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
465
|
+
// `swapSecretsToRecord` stores the public descriptor always, then whichever of
|
|
466
|
+
// `preimageSaltHex` (derivable) or `preimageHex` (not) the wallet produced.
|
|
467
|
+
await saveSwap({ ...record, ...swapSecretsToRecord(swap.secrets) });
|
|
468
|
+
|
|
469
|
+
// Later, from the seed plus the record's public fields. Only ask for a
|
|
470
|
+
// preimage the corridor gave us one for: a lightning send's P belongs to the
|
|
471
|
+
// payee, so this throws on those records rather than inventing something the
|
|
472
|
+
// chain will never match. `LIGHTNING_SEND_PAIR` is exported from this package.
|
|
473
|
+
if (record.pair !== LIGHTNING_SEND_PAIR) {
|
|
474
|
+
const preimage = await preimageForSwapRecord(wallet, record);
|
|
441
475
|
}
|
|
442
476
|
|
|
443
477
|
// For a refund, take the composition instead of the guard: it turns all three
|
|
444
|
-
// ways a wallet can fail to produce the sender key —
|
|
445
|
-
//
|
|
446
|
-
//
|
|
478
|
+
// ways a wallet can fail to produce the sender key — the record names no
|
|
479
|
+
// descriptor, the descriptor is another seed's, the wallet holds the key but
|
|
480
|
+
// cannot sign — into one typed `RefundNotLocallyPossibleError` carrying which,
|
|
481
|
+
// and lets a signer outage stay retryable. Wire `refundArkade` to this.
|
|
447
482
|
const sender = await senderIdentityForSwapRecord(wallet, record);
|
|
448
483
|
```
|
|
449
484
|
|
|
@@ -454,23 +489,45 @@ terminal: the lockup stays funded and watched, a solver claim still ends the swa
|
|
|
454
489
|
`pending`. The manager reports the same state when nothing is wired to act (`enableAutoActions:
|
|
455
490
|
false`, or no callbacks) and the window has passed.
|
|
456
491
|
|
|
457
|
-
`
|
|
458
|
-
|
|
459
|
-
`
|
|
460
|
-
|
|
461
|
-
alone will not compile against the fallback. A caller-supplied preimage on an HD wallet keeps
|
|
462
|
-
`signingDescriptor` for the sender key and stores only `preimageHex` as secret material.
|
|
492
|
+
`preimageForSwapRecord` is the read path to wire, not a hand-rolled `contractPreimage` call: it
|
|
493
|
+
knows which of the record's fields are derivation inputs, and it verifies the result against
|
|
494
|
+
`paymentHash`. A caller that forgets to pass the salt gets a _wrong_ preimage from a wallet that can
|
|
495
|
+
derive, not an error — and that surfaces as an opaque script failure at claim time.
|
|
463
496
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
497
|
+
Every refusal is a `PreimageNotRecoverableError` carrying a `reason`: `no-secrets` (the record
|
|
498
|
+
predates the descriptor), `malformed-record`, `not-derivable` (nothing to derive from, or a key this
|
|
499
|
+
wallet does not hold), or `hash-mismatch` (derived, but wrong — a tampered salt or the wrong seed).
|
|
500
|
+
Branch on `reason`, never on message text. It is deliberately **not**
|
|
501
|
+
`RefundNotLocallyPossibleError`: that one means no local refund is possible and `RfqSwapManager`
|
|
502
|
+
reports `needs_counterparty` for it, which is a different verdict from a claim-path read failing.
|
|
468
503
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
preimage
|
|
504
|
+
A caller-supplied preimage keeps `signingDescriptor` for the sender key and stores only
|
|
505
|
+
`preimageHex` as secret material.
|
|
506
|
+
|
|
507
|
+
On an HD wallet each swap **allocates** its own descriptor rather than peeking at the current one:
|
|
508
|
+
two swaps sharing a descriptor derive the _identical_ preimage, so one solver learning its own
|
|
509
|
+
preimage would learn the other swap's. (Static wallets share their one descriptor by design — the
|
|
510
|
+
per-swap salt is what separates their preimages instead.) On restore, `adoptContractDescriptor`
|
|
511
|
+
(from `@arkade-os/sdk`) moves the wallet's watermark past a restored record's index so it cannot be
|
|
512
|
+
handed out twice; a static descriptor names no index and adopts as a no-op.
|
|
513
|
+
|
|
514
|
+
Two derivations, picked by the descriptor's shape:
|
|
515
|
+
|
|
516
|
+
```
|
|
517
|
+
HD child sha256(sign_det(sha256("Arkade-RFQ-Preimage-v1" ‖ xonly(32) ‖ u32le(0))))
|
|
518
|
+
static/salted sha256(sign_det(sha256("Arkade-Contract-Preimage-Salted-v1" ‖ xonly(32) ‖ salt(32))))
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
The first mirrors NArk's Boltz scheme (`SwapsManagementService.cs:128-160`) with an RFQ-scoped tag.
|
|
522
|
+
NArk has no RFQ corridor yet, so this tag defines the scheme rather than matching one; it is
|
|
523
|
+
deliberately distinct from the Boltz tag so one wallet key cannot derive the same preimage for both
|
|
524
|
+
corridors.
|
|
525
|
+
|
|
526
|
+
The salted tag is corridor-generic where the first is not, and that asymmetry is deliberate: the v1
|
|
527
|
+
tags must be per-corridor because v1 pins its message index, leaving the tag as the only separation
|
|
528
|
+
between two corridors reaching the same key. The salted form mints a fresh salt per swap, so no two
|
|
529
|
+
swaps share a message within a corridor or across two — the salt carries the separation, and the tag
|
|
530
|
+
names the layer rather than the corridor.
|
|
474
531
|
|
|
475
532
|
**Not covered:** seed-only discovery after the swap repository is wiped. An unspent L1 HTLC reveals
|
|
476
533
|
too little public quote data to rediscover, so the record remains required.
|
|
@@ -480,24 +537,102 @@ index from the wallet's receive stream, and a swap index never becomes a funded
|
|
|
480
537
|
so it looks _unused_ to a seed-only `restore()` gap scan. Many consecutive swap allocations between
|
|
481
538
|
two funded receive indices can therefore exceed the scan's `gapLimit` (default 20) and stop it
|
|
482
539
|
before later-funded addresses are found. Keep the swap repository in backups (restore then adopts
|
|
483
|
-
each record's descriptor via `
|
|
540
|
+
each record's descriptor via `adoptContractDescriptor`), or raise `gapLimit` on seed-only restores
|
|
484
541
|
after heavy swap use.
|
|
485
542
|
|
|
486
|
-
##
|
|
543
|
+
## Upgrading from 0.0.3
|
|
544
|
+
|
|
545
|
+
0.0.1–0.0.3 are published. Under npm's 0.0.x rules `^0.0.3` resolves to exactly 0.0.3, so nothing
|
|
546
|
+
auto-upgrades into the changes below — but a consumer that does upgrade meets them all in one jump,
|
|
547
|
+
so they are written as one migration rather than per-release fragments.
|
|
487
548
|
|
|
488
|
-
|
|
549
|
+
**Key provisioning moved into the SDK.** `packages/swap/src/secrets.ts` is gone. `deriveSwapSecrets`,
|
|
550
|
+
`randomSwapSecrets`, `preimageForRfqSecrets`, `senderIdentityForRfqSecrets`, `rfqSecretsToRecord`,
|
|
551
|
+
`rfqSecretsOfRecord`, `isPerSwapDescriptor`, `RFQ_PREIMAGE_TAG` and `SwapSecrets` no longer exist.
|
|
552
|
+
Import `provisionRefundKey`, `provisionClaimSecret`, `contractSigner`, `contractPreimage`,
|
|
553
|
+
`isPerArtifactDescriptor` and `ARKADE_SWAP_PREIMAGE_TAG` from `@arkade-os/sdk` instead;
|
|
554
|
+
`swapSecretsToRecord` and `senderIdentityForSwapRecord` stay in this package. No consumer branches
|
|
555
|
+
on wallet type any more, and no swap record can carry a private key.
|
|
556
|
+
|
|
557
|
+
**`contractPreimage` takes an options object.** `contractPreimage(wallet, descriptor, stored?)`
|
|
558
|
+
became `contractPreimage(wallet, descriptor, { stored?, salt? })`. Prefer `preimageForSwapRecord`,
|
|
559
|
+
which reads both fields off the record and verifies against `paymentHash`.
|
|
560
|
+
|
|
561
|
+
**Static wallets derive their preimage instead of storing it.** New records from such wallets carry
|
|
562
|
+
`preimageSaltHex` and no `preimageHex`; `mustPersistPreimage` is now `false` for them, so the
|
|
563
|
+
"persist the preimage" warning stops firing. Nothing at rest is secret unless the signer cannot sign
|
|
564
|
+
deterministically at all.
|
|
565
|
+
|
|
566
|
+
**`AssetSwap` gains `preimageSaltHex?`, and `AssetSwapRepository.version` is `2`.** External
|
|
567
|
+
repository implementations must recompile — deliberately, because a field-mapped backend that drops
|
|
568
|
+
`preimageSaltHex` leaves the swap unclaimable exactly as one dropping `preimageHex` does. Records
|
|
569
|
+
written by 0.0.1–0.0.3 need no rewrite and no migration: the field is optional, older rows resolve
|
|
570
|
+
through their stored `preimageHex` or their HD descriptor, and `DB_VERSION` is unchanged.
|
|
571
|
+
|
|
572
|
+
## Breaking changes on this branch (pre-release migration notes)
|
|
489
573
|
|
|
574
|
+
Notes from before 0.0.1, kept for consumers who tracked the branch.
|
|
575
|
+
|
|
576
|
+
- **Every derived address changed again, in both corridors — the unilateral ladder was re-spaced.**
|
|
577
|
+
`unilateralRefundDelay` now sits **level with** `claimDelay` instead of one 512s step above it,
|
|
578
|
+
and `unilateralRefundWithoutReceiverDelay` sits `SOLO_REFUND_HEADROOM_SECONDS` (4096s, newly
|
|
579
|
+
exported) above it instead of two steps. The old ladder spaced all three leaves one step apart as
|
|
580
|
+
though they were interchangeable rungs; they are not. Only `unilateralRefundWithoutReceiver` is a
|
|
581
|
+
solo path for the funder, so it is the only one whose timing can steal, and one 512s tick was
|
|
582
|
+
never enough for a claimant to complete a unilateral exit in. The two-signature refund needs no
|
|
583
|
+
separation at all, since neither party can spend that leaf alone. This tracks the reference
|
|
584
|
+
solver's [lightning-swap-service#81](https://github.com/arkade-os/lightning-swap-service/pull/81);
|
|
585
|
+
the two derivations must produce **the same three delay values** for the same operator, which is
|
|
586
|
+
what keeps the derived addresses identical. **Deployment must be coordinated** on the same terms
|
|
587
|
+
as the entry below: for a quote not yet funded, a mismatch refuses it at `verifyLockupAddress`
|
|
588
|
+
rather than losing funds.
|
|
589
|
+
|
|
590
|
+
**An in-flight lockup funded before the upgrade needs care, and the entry below understates
|
|
591
|
+
this.** The delays are not quote fields and are not persisted on the swap record
|
|
592
|
+
(`AssetSwap` keeps `swapPkScript`, not `claimDelay`), and `RfqSwap`'s own doc tells callers to
|
|
593
|
+
*rebuild* the script on restart from the quote's binding fields — which re-derives the delays
|
|
594
|
+
under whatever ladder is compiled in. So a trader who funded on `0.0.4`, upgraded, and restarted
|
|
595
|
+
rebuilds a **new** address, and `refundIfUnresolved` finds no VTXOs there and returns
|
|
596
|
+
`nothing_to_refund` — a terminal-sounding answer for money still locked at the old script, with
|
|
597
|
+
`refundLocktime` still ticking. Until the delays are persisted and rebuilt from the stored value,
|
|
598
|
+
drain in-flight lockups before upgrading, or rebuild the old script from the pre-upgrade delays
|
|
599
|
+
by hand. This is a pre-existing gap that any address-moving change hits, not one this change
|
|
600
|
+
introduces.
|
|
601
|
+
|
|
602
|
+
`unilateralClaimDelay`'s BIP68 ceiling tightened to reserve the full headroom rather than two
|
|
603
|
+
steps. Note this guard alone is **not** mirrored in the reference solver, which still rejects
|
|
604
|
+
only above `0xffff * 512`: for an operator `unilateralExitDelay` in `(33549824, 33553920]`
|
|
605
|
+
seconds the trader throws here while the solver quotes and then fails deeper in its own script
|
|
606
|
+
build. Both refuse, at different seams with different messages, so it is a diagnosability wart
|
|
607
|
+
rather than a fund risk — and the window is unreachable in practice (~388 days).
|
|
608
|
+
|
|
609
|
+
- **`secrets.ts` is gone; key provisioning moved into `@arkade-os/sdk`.** This package no longer
|
|
610
|
+
derives, mints, or names keys. It asks the SDK for what the leg needs — `provisionRefundKey(wallet)`
|
|
611
|
+
for a leg it funds, `provisionClaimSecret(wallet, { preimage? })` for one it claims — and
|
|
612
|
+
recovers with `contractSigner(wallet, descriptor)` / `contractPreimage(wallet, descriptor,
|
|
613
|
+
stored?)`. The returned `ProvisionedKey` / `ProvisionedClaimSecret` replace `SwapSecrets`, and
|
|
614
|
+
`descriptor` replaces `signingDescriptor` on them. Removed from this package with no
|
|
615
|
+
replacement here: `deriveSwapSecrets`, `randomSwapSecrets`, `senderPubkeyForRfqSecrets`,
|
|
616
|
+
`preimageForRfqSecrets`, `senderIdentityForRfqSecrets`, `isPerSwapDescriptor`, `derivePreimage`,
|
|
617
|
+
`buildPreimageMessage`, `RFQ_PREIMAGE_TAG`, `isDeterministicSigner`, `adoptSwapDescriptor` (now
|
|
618
|
+
`adoptContractDescriptor` in the SDK), `SwapSecrets` / `DerivedSwapSecrets` /
|
|
619
|
+
`StoredSwapSecrets`, and `rfqSecretsToRecord` / `rfqSecretsOfRecord` — persist a provisioned
|
|
620
|
+
secret with **`swapSecretsToRecord`** from `store` instead, and read P back with
|
|
621
|
+
`contractPreimage`. `RefundNotLocallyPossibleError` and `senderIdentityForSwapRecord` stay here
|
|
622
|
+
(now in `refundBlocked.ts`): they are swap lifecycle, not key provisioning.
|
|
623
|
+
- **No swap record can carry a private key.** `AssetSwap.fallbackSecrets` and the
|
|
624
|
+
`AssetSwapFallbackSecrets` types are deleted rather than kept readable, and `preimageHex` — set
|
|
625
|
+
only when the wallet reports `mustPersistPreimage` — is the record's one secret field. A record
|
|
626
|
+
written by 0.0.1–0.0.3 carries no `signingDescriptor`, so `senderIdentityForSwapRecord` refuses
|
|
627
|
+
it with `no-secrets` rather than silently mis-signing; those versions shipped before any
|
|
628
|
+
consumer, which is the window for doing this without a secret migration.
|
|
490
629
|
- **`requestLightningSend` / `requestOnchainSend` return `secrets`, not top-level raw key material.**
|
|
491
630
|
`senderPrivateKey` is gone from both return types; caller-owned onchain preimages live inside
|
|
492
631
|
`secrets` and must be persisted with the record. `pushRefundWithoutReceiver` /
|
|
493
632
|
`refundIfUnresolved` take `sender: Identity` instead of `senderPrivateKey: Uint8Array` — build
|
|
494
633
|
it from the record with `senderIdentityForSwapRecord`, which is what keeps a wallet that cannot
|
|
495
|
-
sign reporting `RefundNotLocallyPossibleError` rather than a `TypeError` at the push site
|
|
496
|
-
`
|
|
497
|
-
gains `signingDescriptor?`,
|
|
498
|
-
`preimageHex?`, and complete stored-arm `fallbackSecrets?`. Landed while the package is
|
|
499
|
-
unpublished and consumer-free, which is the whole window for doing it: after a consumer ships,
|
|
500
|
-
the same change becomes a secret migration across every deployed wallet.
|
|
634
|
+
sign reporting `RefundNotLocallyPossibleError` rather than a `TypeError` at the push site.
|
|
635
|
+
`AssetSwap` gains `signingDescriptor?` and `preimageHex?`.
|
|
501
636
|
- **Every derived address changed, in both corridors.** The lightning-send lockup moved from the
|
|
502
637
|
3-leaf program-artifact VHTLC to the 8-leaf `VHTLC.ScriptV2` (non-interactive claim and refund
|
|
503
638
|
leaves), and the L1 HTLC's claim leaf gained a `SIZE 32 EQUALVERIFY` preimage-length guard. Both
|
|
@@ -516,11 +651,11 @@ scanned? })` — the server key is required because a spend is classified by reb
|
|
|
516
651
|
indistinguishable from a fill. Leaves have no such failure mode.
|
|
517
652
|
- **A spend that cannot be classified is no longer restored as `fulfilled`.** It leaves the funding
|
|
518
653
|
txid unanswered so a later scan decides it. Records are never written on a guess.
|
|
519
|
-
- **`AssetSwap` gained
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
654
|
+
- **`AssetSwap` gained `signingDescriptor?`**, and `preimageHex` now means "P that cannot be
|
|
655
|
+
re-derived" — caller-supplied, or minted for a static descriptor. The repository version stays
|
|
656
|
+
`1` — the package is unreleased, so there is no stored record to migrate — but a field-mapped
|
|
657
|
+
backend must persist the record whole: silently dropping `preimageHex` leaves a static swap
|
|
658
|
+
permanently unclaimable.
|
|
524
659
|
- **A write that gates something irreversible throws; one that follows it does not.**
|
|
525
660
|
`addAssetSwap` and `updateAssetSwap` throw on a failed read or write — nothing irreversible may
|
|
526
661
|
happen until the record is durable, which is why `cancelOffer` writes its `cancelling` marker
|