@arkade-os/swap 0.0.2 → 0.0.4
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 +115 -83
- package/dist/{chunk-C5P7R7JT.js → chunk-DLM6BVTB.js} +80 -252
- package/dist/index.cjs +163 -303
- package/dist/index.d.cts +207 -22
- package/dist/index.d.ts +207 -22
- package/dist/index.js +95 -76
- package/dist/nostr.cjs +10 -14
- package/dist/nostr.d.cts +1 -2
- package/dist/nostr.d.ts +1 -2
- package/dist/nostr.js +2 -2
- package/dist/{rfq-CRgIOQ_y.d.cts → rfq-DjZlesr4.d.cts} +32 -352
- package/dist/{rfq-CRgIOQ_y.d.ts → rfq-DjZlesr4.d.ts} +32 -352
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Arkade Intents names two participants:
|
|
|
16
16
|
contract, and tracks it to a fill or a cancellation.
|
|
17
17
|
- **solver** — supplies inventory and pricing, and fills the funded contract by delivering
|
|
18
18
|
`wantAmount` to the user's script over the covenant's `fulfill` path. Some specifications and
|
|
19
|
-
repositories use
|
|
19
|
+
repositories use _provider_ or _market maker_ as synonyms.
|
|
20
20
|
|
|
21
21
|
**`maker` and `taker` in this package name contract positions, not product roles.** The covenant
|
|
22
22
|
programs bind `makerWP`, and the `Offer` type carries `makerPkScript` and `makerPublicKey`; those
|
|
@@ -25,14 +25,14 @@ identify the side that funds the swap and receives `wantAmount`. Read them as sc
|
|
|
25
25
|
Arkade Intents documentation deliberately avoids maker and taker for the participants themselves.
|
|
26
26
|
A resting maker order is firm once taken, and nothing here is: the user funds first, and if no
|
|
27
27
|
solver fills, the deposit comes back through `cancelOffer` rather than through an executed trade.
|
|
28
|
-
Naming the sides
|
|
28
|
+
Naming the sides _user_ and _solver_ says who does what without borrowing a guarantee the contract
|
|
29
29
|
does not make.
|
|
30
30
|
|
|
31
31
|
## Request for quote
|
|
32
32
|
|
|
33
33
|
Every Arkade Intents route is request-for-quote: the user states an intent, receives the solver's
|
|
34
34
|
terms as a quote, funds the contract it derives from those terms, and a solver fills it. This
|
|
35
|
-
route is no exception — what is specific to it is
|
|
35
|
+
route is no exception — what is specific to it is _where the quote is resolved_. `quoteOffer`
|
|
36
36
|
prices the swap client-side from the market card the solver publishes: its price feed and its fee,
|
|
37
37
|
the same two inputs a relay quote would carry. Same protocol, one fewer network hop, and a quote
|
|
38
38
|
that is ready before the user finishes typing an amount.
|
|
@@ -52,7 +52,7 @@ with then.
|
|
|
52
52
|
|
|
53
53
|
Every swap has the same two beats, on this route and on the cross-ledger corridors:
|
|
54
54
|
|
|
55
|
-
1. **Funding** — the user funds the contract it derived from the quote. Funding
|
|
55
|
+
1. **Funding** — the user funds the contract it derived from the quote. Funding _is_ acceptance;
|
|
56
56
|
there is no accept message to send, here or anywhere in Arkade Intents.
|
|
57
57
|
2. **Fill, or cancel** — a solver fills by delivering the other side, or the user takes the
|
|
58
58
|
deposit back.
|
|
@@ -90,8 +90,8 @@ funds an offer should keep cancelling within reach.
|
|
|
90
90
|
6. **`rfq`** — the user side of quoted swaps: RFQ negotiation over HTTP or a
|
|
91
91
|
relay, then non-interactive filling (see below). All four reference-solver corridors:
|
|
92
92
|
`arkade:BTC -> lightning:BTC` and `arkade:BTC -> onchain:BTC` (send), `lightning:BTC ->
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
arkade:BTC` and `onchain:BTC -> arkade:BTC` (receive), plus `arkade:BTC|asset ->
|
|
94
|
+
arkade:BTC|asset` (quote, then take by funding an offer from layer 1).
|
|
95
95
|
7. **`onchainHtlc`** — the Bitcoin-L1 side of `arkade:BTC <-> onchain:BTC`: a NUMS-keyed taproot
|
|
96
96
|
HTLC as pure local derivation (golden-pinned), claim/refund spend builders with signing as a
|
|
97
97
|
callback, the injected `ChainSource` seam (the package holds no L1 backend and no keys),
|
|
@@ -113,11 +113,11 @@ the rest:
|
|
|
113
113
|
|
|
114
114
|
```ts
|
|
115
115
|
// BTC -> asset
|
|
116
|
-
const o = await createOffer(wallet, ARK,
|
|
116
|
+
const o = await createOffer(wallet, ARK, { wantAmount: 1000n, wantAsset });
|
|
117
117
|
await wallet.send({ address: o.address, amount: 1000, extensions: [o.extension] });
|
|
118
118
|
|
|
119
119
|
// asset -> BTC (the sats are the VTXO carrier for the asset)
|
|
120
|
-
const o = await createOffer(wallet, ARK,
|
|
120
|
+
const o = await createOffer(wallet, ARK, { wantAmount: 1000n, offerAsset });
|
|
121
121
|
await wallet.send({
|
|
122
122
|
address: o.address,
|
|
123
123
|
amount: 500,
|
|
@@ -126,10 +126,11 @@ await wallet.send({
|
|
|
126
126
|
});
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
The covenant co-signer ("emulator") key defaults to the SDK's per-network pin, resolved from the
|
|
130
|
+
network the Ark server reports — never fetched from the emulator itself. Pass
|
|
131
|
+
`params.emulatorPubkey` (33-byte compressed hex, the same contract as `Arkade.connect`'s option)
|
|
132
|
+
to override it for a self-hosted emulator, an unpinned network (signet, testnet), or a key
|
|
133
|
+
rotation the SDK hasn't shipped yet.
|
|
133
134
|
|
|
134
135
|
### What `createOffer` gives you back
|
|
135
136
|
|
|
@@ -232,15 +233,9 @@ message anywhere: **acceptance is funding**.
|
|
|
232
233
|
import { httpTransport, requestLightningSend } from "@arkade-os/swap";
|
|
233
234
|
|
|
234
235
|
// invoice facts from YOUR OWN decoder — the module takes facts, not a decoder
|
|
235
|
-
const swap = await requestLightningSend(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
emulatorPubkey,
|
|
239
|
-
httpTransport(solverUrl),
|
|
240
|
-
{
|
|
241
|
-
invoice: { raw: bolt11, paymentHash, amountSats, expiresAt },
|
|
242
|
-
},
|
|
243
|
-
);
|
|
236
|
+
const swap = await requestLightningSend(wallet, arkServerUrl, httpTransport(solverUrl), {
|
|
237
|
+
invoice: { raw: bolt11, paymentHash, amountSats, expiresAt },
|
|
238
|
+
});
|
|
244
239
|
// quote verified against the LOCAL derivation and gated; now fund and go offline:
|
|
245
240
|
await wallet.send({ address: swap.address, amount: swap.fundAmount });
|
|
246
241
|
```
|
|
@@ -256,10 +251,8 @@ which the manager can only poll and cannot retire the row when the swap ends.
|
|
|
256
251
|
The trust model is the offer side's, applied to quotes: only `solver_pubkey`,
|
|
257
252
|
`refund_locktime`, `valid_until` and the amounts are used from a quote; every other contract
|
|
258
253
|
parameter is the trader's own data, and anything address-shaped from the solver is compare-only
|
|
259
|
-
(`AddressMismatch` means refuse-to-fund).
|
|
260
|
-
|
|
261
|
-
and covclaimd do — so it must arrive already obtained out of band, from the solver's signed
|
|
262
|
-
registry/corridor card, and already checked against whatever value you independently trust.
|
|
254
|
+
(`AddressMismatch` means refuse-to-fund). The emulator key is neither: as above, it is a
|
|
255
|
+
per-network pin inside the SDK, not solver data.
|
|
263
256
|
Refusals carry a closed reason set (`SwapRefusal`); unknown reasons are a generic decline. The
|
|
264
257
|
`swap-lightning-send.program.json` bytes are frozen the same way the offer programs are — a
|
|
265
258
|
golden test pins the compiled leaves and scriptPubKey to the reference solver's exact script.
|
|
@@ -310,17 +303,14 @@ import {
|
|
|
310
303
|
awaitOnchainFill,
|
|
311
304
|
claimOnchainFill,
|
|
312
305
|
addAssetSwap,
|
|
313
|
-
|
|
314
|
-
rfqSecretsToRecord,
|
|
306
|
+
swapSecretsToRecord,
|
|
315
307
|
} from "@arkade-os/swap";
|
|
316
308
|
|
|
317
|
-
const swap = await requestOnchainSend(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
{ amount: 100_000, amountSide: "to", payoutPubkey },
|
|
323
|
-
);
|
|
309
|
+
const swap = await requestOnchainSend(wallet, arkServerUrl, httpTransport(solverUrl), {
|
|
310
|
+
amount: 100_000,
|
|
311
|
+
amountSide: "to",
|
|
312
|
+
payoutPubkey,
|
|
313
|
+
});
|
|
324
314
|
// Persist the record, including secrets, BEFORE funding. This must succeed:
|
|
325
315
|
// if addAssetSwap throws, do not call wallet.send.
|
|
326
316
|
await addAssetSwap(repository, {
|
|
@@ -332,7 +322,7 @@ await addAssetSwap(repository, {
|
|
|
332
322
|
swapPkScript: hex.encode(swap.swapPkScript),
|
|
333
323
|
htlcPkScriptHex: hex.encode(swap.htlc.pkScript),
|
|
334
324
|
htlcLocktime: swap.htlc.refundLocktime,
|
|
335
|
-
...
|
|
325
|
+
...swapSecretsToRecord(swap.secrets),
|
|
336
326
|
});
|
|
337
327
|
await wallet.send({ address: swap.address, amount: swap.fundAmount });
|
|
338
328
|
|
|
@@ -342,7 +332,7 @@ const utxo = await awaitOnchainFill(chain, swap.htlc, minConfirmations);
|
|
|
342
332
|
await claimOnchainFill(chain, {
|
|
343
333
|
htlc: swap.htlc,
|
|
344
334
|
utxo,
|
|
345
|
-
preimage:
|
|
335
|
+
preimage: swap.secrets.preimage,
|
|
346
336
|
payoutPkScript,
|
|
347
337
|
feeRateSatVb,
|
|
348
338
|
sign,
|
|
@@ -364,11 +354,9 @@ Crash recovery is record-driven, not chain-driven: `classifyOnchainHtlc` re-deri
|
|
|
364
354
|
state (unfunded / awaiting confirmations / claimable / refundable / claimed-with-P / swept) from
|
|
365
355
|
`ChainSource` plus the stored outpoint — without the stored record a spent HTLC is
|
|
366
356
|
indistinguishable from an unfunded one, which is why persisting before funding is mandatory. The
|
|
367
|
-
`AssetSwap` record carries the onchain fields (`paymentHash`, `signingDescriptor`,
|
|
368
|
-
|
|
369
|
-
`
|
|
370
|
-
`fallbackSecrets` is versioned and discriminated: `{ version: 1, type: "stored",
|
|
371
|
-
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`.
|
|
372
360
|
|
|
373
361
|
**On-board corridors are covered.** `requestLightningReceive` (`lightning:BTC -> arkade:BTC`) and
|
|
374
362
|
`requestOnchainReceive` (`onchain:BTC -> arkade:BTC`) mirror the send-side flows: quote → derive
|
|
@@ -377,7 +365,7 @@ against the quote's compare-only addresses → gate. `requestLightningReceive` r
|
|
|
377
365
|
hold invoice to pay; `requestOnchainReceive` returns the L1 HTLC to fund — the payment/broadcast
|
|
378
366
|
itself is the trader's own wallet's job, exactly as on the send corridors.
|
|
379
367
|
|
|
380
|
-
The invoice on the lightning-receive leg is the
|
|
368
|
+
The invoice on the lightning-receive leg is the _solver's_, so the SDK owns the comparison rather
|
|
381
369
|
than taking the caller's facts about it: `requestLightningReceive` requires a `decodeInvoice`
|
|
382
370
|
callback (no BOLT11 dependency is added) and `verifyReceiveInvoice` binds the decoded invoice to
|
|
383
371
|
this swap's `H` and to `quote.from_amount` — an invoice on another payment hash is the one attack
|
|
@@ -403,11 +391,11 @@ implementation and marked provisional (`TODO(claim-packet-vectors)`).
|
|
|
403
391
|
|
|
404
392
|
`RfqSwapManager` drives the lightning-receive leg too, as `kind: "lightning_receive"` records
|
|
405
393
|
carrying `expectedAmount` and wired to a `claimLockup` callback (`pushClaim`, with `expectedAmount`
|
|
406
|
-
and `partiallyClaimed` passed through — the manager's value check decides
|
|
394
|
+
and `partiallyClaimed` passed through — the manager's value check decides _when_ to act, the inner
|
|
407
395
|
one decides whether `P` is published). Nothing is asked of the solver: the reference solver's
|
|
408
396
|
`rfq_status_request` consults neither receive store, so a status poll answers `unknown` for every
|
|
409
397
|
one of these swaps and chain observation is the only workable design. States mean what they do on
|
|
410
|
-
the send legs with the roles swapped — `settled` is
|
|
398
|
+
the send legs with the roles swapped — `settled` is _our own_ claim landing, matched by a
|
|
411
399
|
hash-verified preimage spend rather than by the txid we submitted, so a claim that lands without us
|
|
412
400
|
still counts; `claimed` is a local belief and not terminal; and **`refunded` is a loss**, the solver
|
|
413
401
|
having taken back a lockup we failed to claim. A lockup funded below `expectedAmount` is reported
|
|
@@ -430,29 +418,59 @@ Lightning HTLC lapses, and **the payer is refunded** — the trader loses the in
|
|
|
430
418
|
funds it was holding. Which is why staying online to claim is an obligation and not a preference:
|
|
431
419
|
covclaimd cannot claim this covenant today, so the claim packet's offline path does not yet run.
|
|
432
420
|
|
|
433
|
-
##
|
|
421
|
+
## Swap secrets come from the wallet, not from this package
|
|
422
|
+
|
|
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 | random, stored on the record | the preimage only |
|
|
434
443
|
|
|
435
|
-
The
|
|
436
|
-
|
|
437
|
-
|
|
444
|
+
The preimage split follows the **descriptor's shape**, not the wallet's type: an HD child
|
|
445
|
+
descriptor is unique to its swap, so `sha256(sign_det(...))` is safe; a static descriptor is the
|
|
446
|
+
same key for every swap, so a derived preimage would repeat across swaps — one solver learning its
|
|
447
|
+
own preimage would learn every other swap's — and a per-swap random preimage is stored instead.
|
|
448
|
+
`mustPersistPreimage` says which you got. A stored preimage is the one secret at rest in the
|
|
449
|
+
design, and it is never a private key.
|
|
438
450
|
|
|
439
451
|
```ts
|
|
440
452
|
const swap = await requestOnchainSend(/* … */);
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
453
|
+
// `swapSecretsToRecord` stores the public descriptor always, and `preimageHex`
|
|
454
|
+
// only when the wallet said it cannot re-derive P.
|
|
455
|
+
await saveSwap({ ...record, ...swapSecretsToRecord(swap.secrets) });
|
|
456
|
+
|
|
457
|
+
// Later, from the seed plus that descriptor. Only ask for a preimage the
|
|
458
|
+
// corridor gave us one for: a lightning send's P belongs to the payee, so
|
|
459
|
+
// this throws on those records rather than inventing something the chain will
|
|
460
|
+
// never match. `LIGHTNING_SEND_PAIR` is exported from this package.
|
|
461
|
+
if (record.signingDescriptor && record.pair !== LIGHTNING_SEND_PAIR) {
|
|
462
|
+
const preimage = await contractPreimage(
|
|
463
|
+
wallet,
|
|
464
|
+
record.signingDescriptor,
|
|
465
|
+
record.preimageHex ? hex.decode(record.preimageHex) : undefined,
|
|
466
|
+
);
|
|
450
467
|
}
|
|
451
468
|
|
|
452
469
|
// For a refund, take the composition instead of the guard: it turns all three
|
|
453
|
-
// ways a wallet can fail to produce the sender key —
|
|
454
|
-
//
|
|
455
|
-
//
|
|
470
|
+
// ways a wallet can fail to produce the sender key — the record names no
|
|
471
|
+
// descriptor, the descriptor is another seed's, the wallet holds the key but
|
|
472
|
+
// cannot sign — into one typed `RefundNotLocallyPossibleError` carrying which,
|
|
473
|
+
// and lets a signer outage stay retryable. Wire `refundArkade` to this.
|
|
456
474
|
const sender = await senderIdentityForSwapRecord(wallet, record);
|
|
457
475
|
```
|
|
458
476
|
|
|
@@ -463,17 +481,15 @@ terminal: the lockup stays funded and watched, a solver claim still ends the swa
|
|
|
463
481
|
`pending`. The manager reports the same state when nothing is wired to act (`enableAutoActions:
|
|
464
482
|
false`, or no callbacks) and the window has passed.
|
|
465
483
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
`rfqSecretsToRecord` stores them under `AssetSwap.fallbackSecrets` as a complete versioned
|
|
469
|
-
record. The discriminant is a type-level fact, so a consumer written against the derivable arm
|
|
470
|
-
alone will not compile against the fallback. A caller-supplied preimage on an HD wallet keeps
|
|
471
|
-
`signingDescriptor` for the sender key and stores only `preimageHex` as secret material.
|
|
484
|
+
A caller-supplied preimage keeps `signingDescriptor` for the sender key and stores only
|
|
485
|
+
`preimageHex` as secret material.
|
|
472
486
|
|
|
473
|
-
|
|
474
|
-
a descriptor derive the
|
|
475
|
-
other swap's.
|
|
476
|
-
|
|
487
|
+
On an HD wallet each swap **allocates** its own descriptor rather than peeking at the current one:
|
|
488
|
+
two swaps sharing a descriptor derive the _identical_ preimage, so one solver learning its own
|
|
489
|
+
preimage would learn the other swap's. (Static wallets share their one descriptor by design — that
|
|
490
|
+
is why their preimages are stored per swap, never derived.) On restore, `adoptContractDescriptor`
|
|
491
|
+
(from `@arkade-os/sdk`) moves the wallet's watermark past a restored record's index so it cannot be
|
|
492
|
+
handed out twice; a static descriptor names no index and adopts as a no-op.
|
|
477
493
|
|
|
478
494
|
The derivation is `sha256(signSchnorrDeterministic(sha256("Arkade-RFQ-Preimage-v1" ‖ xonly(32) ‖
|
|
479
495
|
u32le(0))))`, mirroring NArk's Boltz scheme (`SwapsManagementService.cs:128-160`) with an
|
|
@@ -486,27 +502,43 @@ too little public quote data to rediscover, so the record remains required.
|
|
|
486
502
|
|
|
487
503
|
**Gap-limit interaction:** every swap request — including one whose quote is refused — consumes one
|
|
488
504
|
index from the wallet's receive stream, and a swap index never becomes a funded receive contract,
|
|
489
|
-
so it looks
|
|
505
|
+
so it looks _unused_ to a seed-only `restore()` gap scan. Many consecutive swap allocations between
|
|
490
506
|
two funded receive indices can therefore exceed the scan's `gapLimit` (default 20) and stop it
|
|
491
507
|
before later-funded addresses are found. Keep the swap repository in backups (restore then adopts
|
|
492
|
-
each record's descriptor via `
|
|
508
|
+
each record's descriptor via `adoptContractDescriptor`), or raise `gapLimit` on seed-only restores
|
|
493
509
|
after heavy swap use.
|
|
494
510
|
|
|
495
511
|
## Breaking changes on this branch (pre-release migration notes)
|
|
496
512
|
|
|
497
513
|
The package is pre-release; these notes replace a changelog for consumers tracking the branch.
|
|
498
514
|
|
|
515
|
+
- **`secrets.ts` is gone; key provisioning moved into `@arkade-os/sdk`.** This package no longer
|
|
516
|
+
derives, mints, or names keys. It asks the SDK for what the leg needs — `provisionRefundKey(wallet)`
|
|
517
|
+
for a leg it funds, `provisionClaimSecret(wallet, { preimage? })` for one it claims — and
|
|
518
|
+
recovers with `contractSigner(wallet, descriptor)` / `contractPreimage(wallet, descriptor,
|
|
519
|
+
stored?)`. The returned `ProvisionedKey` / `ProvisionedClaimSecret` replace `SwapSecrets`, and
|
|
520
|
+
`descriptor` replaces `signingDescriptor` on them. Removed from this package with no
|
|
521
|
+
replacement here: `deriveSwapSecrets`, `randomSwapSecrets`, `senderPubkeyForRfqSecrets`,
|
|
522
|
+
`preimageForRfqSecrets`, `senderIdentityForRfqSecrets`, `isPerSwapDescriptor`, `derivePreimage`,
|
|
523
|
+
`buildPreimageMessage`, `RFQ_PREIMAGE_TAG`, `isDeterministicSigner`, `adoptSwapDescriptor` (now
|
|
524
|
+
`adoptContractDescriptor` in the SDK), `SwapSecrets` / `DerivedSwapSecrets` /
|
|
525
|
+
`StoredSwapSecrets`, and `rfqSecretsToRecord` / `rfqSecretsOfRecord` — persist a provisioned
|
|
526
|
+
secret with **`swapSecretsToRecord`** from `store` instead, and read P back with
|
|
527
|
+
`contractPreimage`. `RefundNotLocallyPossibleError` and `senderIdentityForSwapRecord` stay here
|
|
528
|
+
(now in `refundBlocked.ts`): they are swap lifecycle, not key provisioning.
|
|
529
|
+
- **No swap record can carry a private key.** `AssetSwap.fallbackSecrets` and the
|
|
530
|
+
`AssetSwapFallbackSecrets` types are deleted rather than kept readable, and `preimageHex` — set
|
|
531
|
+
only when the wallet reports `mustPersistPreimage` — is the record's one secret field. A record
|
|
532
|
+
written by 0.0.1–0.0.3 carries no `signingDescriptor`, so `senderIdentityForSwapRecord` refuses
|
|
533
|
+
it with `no-secrets` rather than silently mis-signing; those versions shipped before any
|
|
534
|
+
consumer, which is the window for doing this without a secret migration.
|
|
499
535
|
- **`requestLightningSend` / `requestOnchainSend` return `secrets`, not top-level raw key material.**
|
|
500
536
|
`senderPrivateKey` is gone from both return types; caller-owned onchain preimages live inside
|
|
501
537
|
`secrets` and must be persisted with the record. `pushRefundWithoutReceiver` /
|
|
502
538
|
`refundIfUnresolved` take `sender: Identity` instead of `senderPrivateKey: Uint8Array` — build
|
|
503
539
|
it from the record with `senderIdentityForSwapRecord`, which is what keeps a wallet that cannot
|
|
504
|
-
sign reporting `RefundNotLocallyPossibleError` rather than a `TypeError` at the push site
|
|
505
|
-
`
|
|
506
|
-
gains `signingDescriptor?`,
|
|
507
|
-
`preimageHex?`, and complete stored-arm `fallbackSecrets?`. Landed while the package is
|
|
508
|
-
unpublished and consumer-free, which is the whole window for doing it: after a consumer ships,
|
|
509
|
-
the same change becomes a secret migration across every deployed wallet.
|
|
540
|
+
sign reporting `RefundNotLocallyPossibleError` rather than a `TypeError` at the push site.
|
|
541
|
+
`AssetSwap` gains `signingDescriptor?` and `preimageHex?`.
|
|
510
542
|
- **Every derived address changed, in both corridors.** The lightning-send lockup moved from the
|
|
511
543
|
3-leaf program-artifact VHTLC to the 8-leaf `VHTLC.ScriptV2` (non-interactive claim and refund
|
|
512
544
|
leaves), and the L1 HTLC's claim leaf gained a `SIZE 32 EQUALVERIFY` preimage-length guard. Both
|
|
@@ -525,11 +557,11 @@ scanned? })` — the server key is required because a spend is classified by reb
|
|
|
525
557
|
indistinguishable from a fill. Leaves have no such failure mode.
|
|
526
558
|
- **A spend that cannot be classified is no longer restored as `fulfilled`.** It leaves the funding
|
|
527
559
|
txid unanswered so a later scan decides it. Records are never written on a guess.
|
|
528
|
-
- **`AssetSwap` gained
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
560
|
+
- **`AssetSwap` gained `signingDescriptor?`**, and `preimageHex` now means "P that cannot be
|
|
561
|
+
re-derived" — caller-supplied, or minted for a static descriptor. The repository version stays
|
|
562
|
+
`1` — the package is unreleased, so there is no stored record to migrate — but a field-mapped
|
|
563
|
+
backend must persist the record whole: silently dropping `preimageHex` leaves a static swap
|
|
564
|
+
permanently unclaimable.
|
|
533
565
|
- **A write that gates something irreversible throws; one that follows it does not.**
|
|
534
566
|
`addAssetSwap` and `updateAssetSwap` throw on a failed read or write — nothing irreversible may
|
|
535
567
|
happen until the record is durable, which is why `cancelOffer` writes its `cancelling` marker
|