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
4 times, most recently
from
October 27, 2026 15:12
47f09e2 to
f828adc
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.
hieblmi
force-pushed
the
hai/asset-reservations-stepwise
branch
3 times, most recently
from
November 3, 2026 16:04
b20b34a to
b8fcda0
Compare
Build the swapkit adapter against the released pre-btcd-v2 modules. Pin tapd's bounded legacy TLV decoder at the v0.8.8 release commit. Map contracts and asset descriptors into SDK plans and reuse the authenticated wallet connection.
Taproot Assets v0.8.8 fixes an asset-invoice settlement regression. Check the running backend before new on-chain operations. Verify proof history, active and passive holdings, Bitcoin anchors and fee prevouts. Keep offline transaction and witness construction available.
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.
Bind native compact paths to approved signed parents. After parents confirm, replace proof metadata with authenticated histories without altering the transaction or final witnesses.
Reconcile fallback, preimage and refund spends against agreed terms. Recover and export exact proofs, including after ambiguous publication or a restart. Verify against the transaction that actually mined.
Define the reservation purchase flow, its costs, and its acceptance criteria before building asset Loop Out on top. The plan uses held prepays, durable payment and funding records, and recovery through named FSM actions. Lay out the implementation as small commits and keep the previous prototype's databases outside this experiment.
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.
Define a reservation's quoted asset amount, fee, confirmation depth, and usable 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.
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.
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.
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.
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.
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.
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.
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.
Restore unfinished reservations and start one FSM worker for each. Route approval and wake 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. 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.
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.
Carry the native receiving RFQs for the prepay and full-amount probe so the client can validate exact asset amounts, invoice terms, and route hints. Report probe receipt and invoice cancellation separately. Put probe-only cancellation in a dedicated cancellation request, keeping it separate from reservation identity. Regenerate the protobuf bindings.
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. Validate each live HTLC's identity and asset records, then require their total to equal the agreed amount. Return every part's expiry for settlement checks. Keep receipt verification tied to the original quote so an expired quote does not invalidate an earlier settlement.
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.
Connect the purchase manager to loopd's database, configured tapd, server connection, and authenticated LND services. Start recovery during daemon initialization and wait for saved purchases before serving calls. 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.
Allow the proof RPC to receive the existing 16 MiB proof limit plus response metadata. Share the raw proof bound across verification adapters and cover large and oversized responses through real gRPC calls.
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.
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.
End quote acquisition after one minute across restarts, and stop invalid quotes before any payment. Preserve the existing OutOfRange outcome for funding refusals separately from unavailable or invalid quotes. Display the exact BTC prepay and implied prepay/probe prices before approval. Reject RFQ rates more than five percent apart without treating server-supplied rates as an independent market price.
Use tap-sdk v0.1.1 with Taproot Assets v0.8.8. Bind purchases to the shared CSV contract and fixed asset descriptor. Require an isolated asset commitment and bind the verified proof to the quoted issuance, amount, script key and funding outpoint. Keep node failures retryable. Expose contract signing before wallet fee input finalization so the server can recover a reservation with the same SDK signing policy.
hieblmi
force-pushed
the
hai/asset-reservations-stepwise
branch
2 times, most recently
from
November 4, 2026 13:49
f1241fd to
96aaced
Compare
Keep the original exact-ID protocol. For a group request bind the funding and Lightning issuances in one immutable quote. Verify both genesis proofs before probing and verify the actual funding output before marking the reservation ready.
hieblmi
force-pushed
the
hai/asset-reservations-stepwise
branch
from
November 4, 2026 15:00
3afb1bf to
f8c13c5
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
0ad07d71cand 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
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.