@arkade-os/swap 0.0.3 → 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 +88 -47
- package/dist/{chunk-HLMMA3LJ.js → chunk-DLM6BVTB.js} +54 -243
- package/dist/index.cjs +127 -278
- package/dist/index.d.cts +201 -9
- package/dist/index.d.ts +201 -9
- package/dist/index.js +87 -65
- 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-CCAFoFtR.d.cts → rfq-DjZlesr4.d.cts} +15 -323
- package/dist/{rfq-CCAFoFtR.d.ts → rfq-DjZlesr4.d.ts} +15 -323
- 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,59 @@ 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 | random, stored on the record | the preimage only |
|
|
443
|
+
|
|
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.
|
|
429
450
|
|
|
430
451
|
```ts
|
|
431
452
|
const swap = await requestOnchainSend(/* … */);
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
+
);
|
|
441
467
|
}
|
|
442
468
|
|
|
443
469
|
// 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
|
-
//
|
|
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.
|
|
447
474
|
const sender = await senderIdentityForSwapRecord(wallet, record);
|
|
448
475
|
```
|
|
449
476
|
|
|
@@ -454,17 +481,15 @@ terminal: the lockup stays funded and watched, a solver claim still ends the swa
|
|
|
454
481
|
`pending`. The manager reports the same state when nothing is wired to act (`enableAutoActions:
|
|
455
482
|
false`, or no callbacks) and the window has passed.
|
|
456
483
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
`rfqSecretsToRecord` stores them under `AssetSwap.fallbackSecrets` as a complete versioned
|
|
460
|
-
record. The discriminant is a type-level fact, so a consumer written against the derivable arm
|
|
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.
|
|
484
|
+
A caller-supplied preimage keeps `signingDescriptor` for the sender key and stores only
|
|
485
|
+
`preimageHex` as secret material.
|
|
463
486
|
|
|
464
|
-
|
|
465
|
-
a descriptor derive the _identical_ preimage, so one solver learning its own
|
|
466
|
-
other swap's.
|
|
467
|
-
|
|
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.
|
|
468
493
|
|
|
469
494
|
The derivation is `sha256(signSchnorrDeterministic(sha256("Arkade-RFQ-Preimage-v1" ‖ xonly(32) ‖
|
|
470
495
|
u32le(0))))`, mirroring NArk's Boltz scheme (`SwapsManagementService.cs:128-160`) with an
|
|
@@ -480,24 +505,40 @@ index from the wallet's receive stream, and a swap index never becomes a funded
|
|
|
480
505
|
so it looks _unused_ to a seed-only `restore()` gap scan. Many consecutive swap allocations between
|
|
481
506
|
two funded receive indices can therefore exceed the scan's `gapLimit` (default 20) and stop it
|
|
482
507
|
before later-funded addresses are found. Keep the swap repository in backups (restore then adopts
|
|
483
|
-
each record's descriptor via `
|
|
508
|
+
each record's descriptor via `adoptContractDescriptor`), or raise `gapLimit` on seed-only restores
|
|
484
509
|
after heavy swap use.
|
|
485
510
|
|
|
486
511
|
## Breaking changes on this branch (pre-release migration notes)
|
|
487
512
|
|
|
488
513
|
The package is pre-release; these notes replace a changelog for consumers tracking the branch.
|
|
489
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.
|
|
490
535
|
- **`requestLightningSend` / `requestOnchainSend` return `secrets`, not top-level raw key material.**
|
|
491
536
|
`senderPrivateKey` is gone from both return types; caller-owned onchain preimages live inside
|
|
492
537
|
`secrets` and must be persisted with the record. `pushRefundWithoutReceiver` /
|
|
493
538
|
`refundIfUnresolved` take `sender: Identity` instead of `senderPrivateKey: Uint8Array` — build
|
|
494
539
|
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.
|
|
540
|
+
sign reporting `RefundNotLocallyPossibleError` rather than a `TypeError` at the push site.
|
|
541
|
+
`AssetSwap` gains `signingDescriptor?` and `preimageHex?`.
|
|
501
542
|
- **Every derived address changed, in both corridors.** The lightning-send lockup moved from the
|
|
502
543
|
3-leaf program-artifact VHTLC to the 8-leaf `VHTLC.ScriptV2` (non-interactive claim and refund
|
|
503
544
|
leaves), and the L1 HTLC's claim leaf gained a `SIZE 32 EQUALVERIFY` preimage-length guard. Both
|
|
@@ -516,11 +557,11 @@ scanned? })` — the server key is required because a spend is classified by reb
|
|
|
516
557
|
indistinguishable from a fill. Leaves have no such failure mode.
|
|
517
558
|
- **A spend that cannot be classified is no longer restored as `fulfilled`.** It leaves the funding
|
|
518
559
|
txid unanswered so a later scan decides it. Records are never written on a guess.
|
|
519
|
-
- **`AssetSwap` gained
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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.
|
|
524
565
|
- **A write that gates something irreversible throws; one that follows it does not.**
|
|
525
566
|
`addAssetSwap` and `updateAssetSwap` throw on a failed read or write — nothing irreversible may
|
|
526
567
|
happen until the record is durable, which is why `cancelOffer` writes its `cancelling` marker
|