Repository navigation
Conversation
hieblmi
force-pushed
the
hai/asset-reservations-stepwise
branch
3 times, most recently
from
September 23, 2026 12:44
1681b08 to
7ed9ffd
Compare
hieblmi
force-pushed
the
hai/asset-reservations-stepwise
branch
5 times, most recently
from
September 24, 2026 10:56
55ea4d1 to
4a906f8
Compare
hieblmi
force-pushed
the
hai/asset-reservations-stepwise
branch
6 times, most recently
from
November 3, 2026 13:11
e17bf9b to
b20b34a
Compare
hieblmi
force-pushed
the
hai/asset-reservations-stepwise
branch
4 times, most recently
from
November 4, 2026 15:00
3afb1bf to
f8c13c5
Compare
hieblmi
force-pushed
the
hai/asset-reservations-stepwise
branch
4 times, most recently
from
November 11, 2026 10:32
0621981 to
0830380
Compare
Route existing HTLC, static address and reservation scripts through the same leaf serializers. Pin the quote templates so moving the serialization cannot change signed numbers or zero-hash fee estimates.
Define the key roles, cooperative and unilateral paths, witnesses and weight estimates for both directions. Exercise the legacy vectors and execute the spend conditions against the Bitcoin script engine.
Build the adapter against released pre-btcd-v2 modules and retain the bounded legacy TLV decoder. Map contracts into SDK plans and reuse the authenticated wallet connection. Define the shared runtime version floor and operation-scoped checks. Warn about older backends during daemon initialization while leaving ordinary payments and offline witness construction available.
Authenticate proof history, asset commitments, group issuances and Bitcoin inputs before granting on-chain authority. Apply the shared tapd floor at proof boundaries while keeping offline construction available. Distinguish invalid funding candidates from exact outputs waiting on confirmations or a chain backend. Preserve retryable results when no candidate is ready.
Prepare confirmed inputs for native Build and check its allocations, Bitcoin template, change policy and fee before granting authority. Verify wallet-funded fees again after native Commit.
Check signing policy and the saved package before using LND, and accept peer witnesses only for the approved transaction. Preserve durable recovery while ephemeral signing sessions remain caller-owned. Separate contract-input signing from finalization so wallet-funded callers can sign fee inputs before sealing the final anchor transaction.
Bind native compact paths to approved signed parents. After parents confirm, replace proof metadata with authenticated histories without altering the transaction or final witnesses.
Recover contract spends through native SDK packages and exact output proofs. Preserve the original contract descriptors, asset quantities and confirmation evidence across claims and refunds. Make publication retries consult exact archived proofs and the tapd transfer log before logging again. Report pending confirmations without creating duplicate transfer records or repeating their anchor fees.
Bind a recovery fallback package to the authenticated deposit before it is co-signed. The MuSig2 sighash covers the committed PSBT's own previous output, so a peer that changed only the signed input value or script obtained a valid signature that spent nothing real. Compare that prevout with the deposit output, reject a committed asset that carries its own lock time, and require normal broadcast. Verify a caller-provided asset witness commits to its input script key before declaring it to tapd, so a peer cannot bind a wallet script key to an unrelated internal key. Recognize a BIP-341 annex when matching the agreed leaf, reject an annex spend from reconstruction rather than mis-slicing its witness, report a reorg observed during outcome verification as retryable, and guard the observed-spend discovery helpers against a chain that returns a block without the watched spend.
Define quote, status, list, proof, and unpaid-cancellation calls for asset reservations. A client-generated ID identifies the same purchase across retries, including before its funding outpoint exists. Specify authenticated ownership, immutable quote terms, payment details, and public status fields. Generate the protobuf bindings and document the purchase API. � Conflicts: � docs/asset-reservations.md Use a dedicated cancellation request to distinguish probe-only release from cancellation of an unpaid purchase, keeping options separate from reservation identity. Carry receiving RFQs for the prepay and full-amount hold probe so the client can validate exact asset amounts, invoice terms, and route hints. Report probe receipt and invoice cancellation separately. Include the generated protobuf bindings. Define exact-issuance and group selectors from the start. Group quotes bind the on-chain and Lightning issuances and carry their membership evidence for verification before payment.
Define a reservation's quoted asset amount, fee, confirmation depth, and CSV lifetime. Validate positive amounts, storage bounds, overflow, and block-based CSV limits. Check that the quoted lifetime values fit together without imposing a fixed fee or minimum lifetime policy. Test structural boundaries; approval and persistence follow in later commits. Describe the selected funding issuance independently of the original group selector.
Store a reservation's agreed terms separately from its ordered state history. Constrain identifiers, amounts, fees, and lifetime values, and make repeated inserts preserve the existing purchase. Allow a purchase row to exist before the server supplies its quote, with the fee and lifetime unset. Require those terms to form a complete set once supplied. Add schema tests for rollback, duplicate IDs, quoted fees, and invalid records. The shared database test helper selects SQLite or PostgreSQL through the existing build tag. Store the original optional group selector alongside the reservation identity in the initial unreleased schema.
Store the quote, main probe, SkipProbe preference, prepay routing cap, payment identity and result, outpoint, credit, and proof. Read the agreed amounts from the saved quote. Enforce unique funding outpoints and test the transition from an unquoted row. Persist the sole hold probe request, paying node, deadline, and terminal result alongside the reservation purchase. Recovery resumes that payment without replacing the quote or registering another probe. Persist whether a live probe route supplied the fee estimate, so recovery distinguishes missing route history from a zero-fee route.
Add a typed SQL store that saves a purchase and its first state in one transaction. An identical create request returns the saved progress; a reused ID with different terms or keys fails. Updates preserve the agreed terms. Use the SQLite and PostgreSQL database abstraction and test concurrent creation, failed writes, invalid records, and reopening a SQLite database. Persist and compare the original exact-issuance or group selector.
Calculate the timeout and execution cutoff from the first funding confirmation so extra confirmations and restarts cannot extend a reservation's life. Test deadline calculations and signed-height boundaries. Keep lifetime arithmetic separate from proof, confirmation, and spend checks.
Define separate Buy and Approve calls so callers can inspect the quote and main probe before authorizing payment. Buy can request skipped probing; Approve separately permits buying without a successful probe. Approving the exact quote hash accepts its asset fee and BTC prepay amount. The caller supplies the prepay routing cap and SkipProbe choice, which are saved with the transition that authorizes payment. Add get and list calls with purchase-ID and outpoint selectors. Report the probe outcome and estimated routing fees, assign swap read and execute permissions, and generate the bindings. Cancellation stays internal; unapproved quotes expire without a public cancellation RPC. Allow list requests to select one client state or active purchases. The filters are mutually exclusive; omitting both includes all states. Expose probe fee availability separately from probe success. Recovery can verify delivery even if LND has already pruned the route history. Expose mutually exclusive exact-issuance and group selection in the local purchase API.
Retain the immutable quote, main probe, fee estimates, skip preference, prepay routing cap, payment identity, and delivery facts. Save progress and state history in one transaction, and prevent changes to payment choices once the purchase leaves approval. Check the quote hash, routing cap, and probe choice on approval. The hash binds consent to both quoted amounts. Read those amounts from the quote and payment choices from the reservation during recovery. Require a successful main probe unless the caller permits buying without one. Estimate prepay routing fees from the main fee and BTC amount ratio, rounding up and ignoring fixed hop fees. Test persistence, conflicting updates, quote consent, routing caps, outpoints, and fee arithmetic. Bind the probe hash to the client-generated reservation ID. Preserve the probe request, node, deadline, and terminal result across writes and restarts; the purchase keeps one quote and one probe. Save live route fee availability with purchase progress, including an observed zero fee. Missing attempt history does not imply a zero fee. Filter startup recovery in SQL to exclude canceled and expired purchases. Read matching reservations and their latest state and timestamp in one statement, avoiding full history reads and mixed PostgreSQL snapshots. Support either one named state or active purchases in manager list calls. Test filtering, single-statement reads, and recovery past completed records. Preserve the original group request independently of the quoted funding issuance. Reject changed quote or payment issuances and exclude failed quote acquisition from startup recovery.
hieblmi
force-pushed
the
hai/asset-reservations-stepwise
branch
from
November 12, 2026 16:21
0830380 to
6851be6
Compare
Drive quote requests, one full-amount hold probe, approval, prepay payment, and reservation verification through a persistent state machine. Save the probe request before dispatch. Require server receipt and cancellation plus a resolved local payment before approval. A failed probe cancels the unpaid purchase. Resolve any in-flight HTLC before final cancellation; a fresh attempt needs a new purchase ID. Recovery resumes saved work and recognizes completed probes even after their deadline, while still enforcing quote expiry. Save approval and routing limits before sending the prepay, and track the original payment after uncertain results. Settlement always resumes delivery. Verify the proof and funding before Ready, retain the original CSV clock, and stop node calls after a failed database write. Persist fees from live payment updates. Recognize probe success from the payment-level incorrect-details reason and server receipt and cancellation, without requiring terminal HTLC attempt records. A missed live route leaves the estimate unavailable but does not fail delivery. Bound quote acquisition to one minute across restarts and stop invalid quotes before payment. Verify group membership before probing or paying.
Restore unfinished reservations and start one FSM worker for each. Route approval requests through that worker so its actions run in sequence. Periodic OnRecover events resume purchases and monitor ready reservations until expiry. Before starting a new worker, save the reservation ID, client key, requested asset and amount, and SkipProbe choice. Repeated creation requests return the saved reservation; conflicting assets or amounts are rejected. Apply MaxActiveReservations only to new purchases, so lowering the limit cannot prevent recovery. Keep one pending command per worker and return ErrReservationBusy when its queue is full. Bound worker attempts with CallTimeout, return saved snapshots to callers, and cancel and join workers on shutdown. Creation and its snapshot read share one timeout and stop when either the caller or the manager cancels. Test concurrent duplicate requests, recovery above MaxActiveReservations, shutdown during quote retrieval, key derivation, and snapshot reads, automatic expiry of unapproved quotes, and SkipProbe across restarts. Carry the original exact-issuance or group selector through admission and idempotent creation.
Bind the deposit proof to its amount, scripts, outpoint, and local co-signing key before calling tapd. Record verified issuance metadata for assets new to the tapd v0.8.8 verifier, then verify the full history. This does not import wallet assets. Check the proof's block against LND and observe the exact confirmed output. Use LND confirmation, spend, and block notifications to watch deposits and restore watches after restart. As with Instant Out, Ready means no spend is known; it does not wait for a synchronous unspent-output check. Test proof binding, issuance insertion before history verification, rejected proofs, repeated verification, and watch recovery. Use the shared swapkit CSV contract and isolated asset binding from the start. Enforce tapd v0.8.8, bind verified summaries to the exact proof, and authenticate group issuance evidence without importing wallet assets. Allow proof RPC responses up to the 16 MiB raw-proof limit plus metadata. Exercise maximum-sized and oversized responses through real gRPC calls.
Calculate a BTC invoice amount that converts to exactly the requested asset units, and reject rates or RFQ capacities that cannot carry it. Bind the signed invoice to the asset, payee, payment secret, receiving edge, and expiry. Keep incoming HTLC receipt validation with the server's probe and prepay consumers. This client package owns shared quote and invoice checks.
Validate both receiving RFQs and invoices. Prepare one full-amount hold probe for the FSM to save, then dispatch it with SendPaymentV2. Keep the SendPaymentV2 stream open to capture in-flight routes before LND prunes failed attempts. Resume observation with TrackPaymentV2 on the saved node, passing live updates to the FSM for immediate persistence. Bound streams by the saved deadline and recheck it before dispatch. Enforce approved prepay fees, payment lifetime, route CLTV limits, and enough BTC in one channel after its reserve, even when probing is skipped. Send one part to preserve exact asset conversion. Resolve uncertain payments by tracking their saved identity and validate the settled amount and routing fee. Check exact payment issuance and validate each invoice against its own receiving RFQ. The client approves the separately quoted payment prices.
Connect the purchase manager to loopd's database, configured tapd, server connection, and authenticated LND services. Start recovery during daemon initialization. Reservation calls wait for saved purchases to load; recovery does not block the daemon's other services. Use the proof adapter to record issuance metadata before verifying reservation proofs through tapd. Implement quote approval, outpoint selection, and paginated status. Pass the skip preference on quote and approval requests to the manager. Enable purchases when experimental features and tapd are active. Test separate approval, explicit consent to skip probing, and disabled- service responses. Pass the list state or active-only filter to the manager before pagination. Reject conflicting filters and unknown state names, and preserve stable ID pagination for filtered results. Expose fee availability only for successful probes with a saved live route observation. Parse exact-issuance and group selectors at the local RPC boundary.
Add buy, list, and get under loop asset reservation. Probing and cancellation stay internal; unapproved purchases expire automatically. Print the purchase ID before dispatch so a lost reply can be resumed. Accept canonical outpoints once funding is known. Display the full-amount probe result, quoted asset service fee, and routing cap before approval. Estimate prepay routing fees from the main probe and keep the conventional routing limit unless overridden. Failed probes cancel the unpaid purchase; another attempt requires a fresh purchase ID. The --skip_probe flag allows a new purchase without a successful probe. It preserves quote approval and payment limits; --yes only skips the prompt. After approval, wait for verified delivery while the daemon continues recovery. Support mutually exclusive --state and --active_only list filters. Preserve filters across pages; active-only excludes Canceled and Expired while retaining Ready and NeedAdminAttention. Show unavailable fee estimates separately from observed zero fees. A successful probe remains eligible for approval without a captured route. Accept an asset ID or group key and display the exact BTC prepay and implied prepay and probe prices before approval.
Document one full-amount hold probe per quote, verified asset receipt, cancellation, and recovery of the original payment. Failed probes cancel the unpaid purchase; retrying requires a fresh reservation ID. Distinguish the early prototype runs from the Taproot Assets v0.8.8 reservation itests. Describe the BTC-only payer, restart recovery and the 144-block CSV sweep. Swap execution is a separate operation. Document live stream fee capture and success with pruned HTLC attempts. Recovery without a route reports unavailable estimates; retaining failed payment attempts in LND is not required.
hieblmi
force-pushed
the
hai/asset-reservations-stepwise
branch
from
November 13, 2026 13:15
ac432c5 to
08be4be
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
This PR lets a client buy and manage an asset reservation before starting a
swap. A reservation sets aside a fixed asset amount in an on-chain output
for that client, for a limited time. Spending uses a shared client/server key;
a timeout path allows unused funds to be reclaimed.
A buyer selects either an exact asset ID or a compressed, tweaked group key. An exact request requires that issuance both on chain and on the receiving channel. A group request allows different fungible issuances for the two legs. The quote identifies both; loopd verifies the anchor and member issuance proofs before it probes or pays. The buyer approves those exact terms, and delivery must prove the quoted funding issuance.
The client pays the quoted service fee upfront to obtain the reservation.
The asset principal is paid for later, when the client executes the swap.
Under this contract, the prepayment covers the later swap's service fee and
is credited once. Routing and miner fees are separate, and the prepayment is
not refunded if the reservation goes unused.
The purchase flow validates and saves a quote, sends one full-amount asset
hold probe through
SendPaymentV2, asks for approval, pays the BTC prepayinvoice, and verifies delivery before marking the reservation
Ready.The server validates the accepted asset HTLC and saves its receipt before
canceling the probe invoice. Success requires that receipt, server cancellation,
and a failed local payment with
FAILURE_REASON_INCORRECT_PAYMENT_DETAILS.Success does not depend on LND retaining failed HTLC attempts.
Each purchase has one quote and one probe payment. Failed probes cancel the
unpaid purchase; another attempt requires a fresh reservation ID, optionally
with
--skip_probe. Recovery resumes the saved payment or finishes cancellation.The probe checks current liquidity without reserving it for the later swap.
The client captures route fees from live
SendPaymentV2updates and resumesobservation through
TrackPaymentV2. The prepay estimate scales the observedroute fee by the invoice amounts. If recovery misses the route before LND
prunes it, the probe can still succeed with unavailable fee estimates;
probe_fee_knowndistinguishes missing data from an observed zero fee.Purchase flow
flowchart TD Q["RequestQuote: request and validate a quote<br/>Save the purchase ID, keys, and agreed terms"] P["ProbeRoutes: send the full-amount hold probe<br/>Verify receipt and cancellation"] A["AwaitApproval: show the quote and probe result"] PAY["PayPrepay: save payment intent<br/>Pay and track the BTC prepay invoice"] WAIT["WaitForDelivery: obtain the funding outpoint and asset proof"] VERIFY["VerifyReservation: check the asset, amount, keys, proof,<br/>confirmations, and remaining lifetime"] READY["Ready: verified reservation available for a later swap"] CANCEL["CancelPrepay: resolve any payment before cancellation"] CANCELED["Canceled"] Q --> P Q -->|Explicit --skip_probe| A P -->|Verified receipt and resolved payment| A P -->|Probe failed| CANCEL A -->|Approve exact quote and routing cap| PAY A -->|Cancel or quote expires| CANCEL PAY -->|Payment settled| WAIT WAIT --> VERIFY VERIFY -->|Checks pass| READY CANCEL -->|Unpaid cancellation confirmed| CANCELED CANCEL -->|Payment settled instead| WAITNormal approval requires a successful main-payment probe.
--skip_probeexplicitly permits purchase without one; quote validation and payment limits
still apply. Declining the prompt leaves the purchase saved and unpaid.
The daemon saves progress throughout the purchase and resumes unfinished work
after a restart. It records the exact payment before sending and resolves an
uncertain result by tracking that payment. Delivery is verified locally, and
ready reservations remain monitored for spending and expiry. Recovery retains
the original lifetime rather than starting a new clock.
Using the reservation in a swap
A later asset Loop Out will select the funded reservation by its
txid:vout,check that enough time remains, and obtain a fresh execution quote. The client
will pay the main Lightning invoice to receive the reserved assets on-chain
through the swap protocol. The saved prepay credit avoids charging the service
fee twice; the BTC-to-asset conversion price can change before execution and
requires fresh approval.
This PR implements reservation purchase through
Ready. Executing the laterasset swap is follow-up work. It includes persistent terms and state history,
payment recovery, proof and confirmation checks, authenticated RPCs, and
loop asset reservation buy,list, andget. Purchasesrequire
--experimentaland a configured tapd.Extending this to BTC reservations
The same quote, approval, prepay, recovery, and readiness flow can support BTC
reservations. A follow-up would add BTC-denominated terms and a Bitcoin funding
verifier in place of asset proofs and conversion checks, then connect ready
reservations to Instant Out. BTC reservation purchases are not enabled here.
Earlier work and dependency
This replaces the reservation-buying approach in
#883 and
#1195.
The branch stacks on Loop #1225 at
0d603f32653band uses tap-sdk v0.1.1 with Taproot Assets v0.8.8 and the TLV decoder from that tag. Purchases retain their negotiated, server-funded CSV contract. The shared deposit constructor supplies the anchor and refund leaf, and the kit ties the agreed terms to an isolated asset commitment. New on-chain reservations require a running tapd v0.8.8 or later. The server integration is Nautilus #1730.The paying node can retry a transient identity lookup before probe dispatch until the original quote expires. If local verification invalidates a delivered proof after a reorg, the client retains the delivery record, leaves Ready and withdraws proof verification and execution deadlines.
Validation
Rebased on Loop master
f3fa5eadb, retaining the swapkit stack. On89c1de44e0c8, the asset/reservation, database, loopd and CLI tests passed, along with lint, commit-message and generated documentation checks. CI for this rebase is still running.Before this minimum-version update, the full Loop unit suite, affected race suites, lint and module tidy checks passed. The real-node purchase case exercised quote, probe, held prepay, funding, client/server restart, bound proof acceptance, cancellation and the CSV refund.
At the new tapd v0.8.8 minimum, the policy, swapkit, reservation and loopd unit tests pass. The boundary tests cover both the released daemon and rejected older nodes. The v0.8.8 release fixes a regression that could prevent asset invoices from settling. Taproot Assets v0.8.8 release notes
Pull Request Checklist
docs/release-notes/release-notes-next.md.