backup: add immutable backups for HD-style recovery - #1173
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request implements a foundational backup system for Loop's static-address state, enabling future HD-wallet-style recovery. It includes significant architectural improvements to deposit management, reconciliation, and database schema to support confirmation risk tracking and change address derivation. The changes also extend the CLI and RPC interfaces to provide better control over static address funding and deposit monitoring. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a new backup service for Loop static-address and L402 state, adds a deposit command to the loop CLI, and updates the loopd daemon to handle static address loop-in risk decisions. The reviewer identified potential nil pointer dereference issues in NewStaticAddress and ListUnspentDeposits and provided code suggestions to implement defensive nil checks.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| func (s *swapClientServer) NewStaticAddress(ctx context.Context, | ||
| _ *looprpc.NewStaticAddressRequest) ( | ||
| req *looprpc.NewStaticAddressRequest) ( | ||
| *looprpc.NewStaticAddressResponse, error) { | ||
|
|
||
| sendCoinsReq := req.GetSendCoinsRequest() |
There was a problem hiding this comment.
The staticAddressManager field can be nil in certain configurations or testing environments. To prevent a potential nil pointer dereference panic when calling NewAddress, add a defensive nil check at the beginning of NewStaticAddress.
| func (s *swapClientServer) NewStaticAddress(ctx context.Context, | |
| _ *looprpc.NewStaticAddressRequest) ( | |
| req *looprpc.NewStaticAddressRequest) ( | |
| *looprpc.NewStaticAddressResponse, error) { | |
| sendCoinsReq := req.GetSendCoinsRequest() | |
| func (s *swapClientServer) NewStaticAddress(ctx context.Context, | |
| req *looprpc.NewStaticAddressRequest) ( | |
| *looprpc.NewStaticAddressResponse, error) { | |
| if s.staticAddressManager == nil { | |
| return nil, status.Errorf(codes.FailedPrecondition, "static address manager not configured") | |
| } | |
| sendCoinsReq := req.GetSendCoinsRequest() |
| func (s *swapClientServer) ListUnspentDeposits(ctx context.Context, | ||
| req *looprpc.ListUnspentDepositsRequest) ( | ||
| *looprpc.ListUnspentDepositsResponse, error) { | ||
|
|
There was a problem hiding this comment.
The staticAddressManager field can be nil in certain configurations or testing environments. To prevent a potential nil pointer dereference panic when calling ListUnspentRaw, add a defensive nil check at the beginning of ListUnspentDeposits.
func (s *swapClientServer) ListUnspentDeposits(ctx context.Context,
req *looprpc.ListUnspentDepositsRequest) (
*looprpc.ListUnspentDepositsResponse, error) {
if s.staticAddressManager == nil {
return nil, status.Errorf(codes.FailedPrecondition, "static address manager not configured")
}Reserve separate key families for static receive and change addresses. This keeps derived keys out of the legacy static-address and HTLC key streams.
Associate every deposit with the static address parameters that created it. This lets restored deposits recover the correct script and signing keys instead of assuming the legacy root address.
Create receive and change addresses from locally derived client keys while reusing the server key and expiry from the legacy seed. Import, persist, and activate each script before returning it to callers.
Filter wallet UTXOs against every active static-address script and attach the matching address parameters to new deposits. This makes deposits to derived receive addresses visible to the deposit manager.
Build timeout sweeps from each deposit own script, expiry, and key locator. Derived-address deposits can now use their unilateral recovery path without falling back to the legacy root parameters.
Construct cooperative MuSig2 sessions from the address parameters stored on each deposit. Loop-ins and withdrawals can therefore combine inputs owned by different derived static addresses.
Map every selected outpoint to the client key that derived its static address and include those proofs in loop-in requests. Keep MuSig2 signing indexed by outpoint so request ordering cannot select the wrong key.
Include the derivation key for every withdrawal input in the server request. This lets the server validate and sign withdrawals that combine deposits from multiple derived addresses.
Join each selected deposit with its persisted static-address row during loop-in recovery. Hydrate legacy rows as needed so restored swaps retain the scripts and key locators required for signing.
Create a fresh static change address for fractional loop-ins and persist its key locator with the selected HTLC outpoint. Recovery reconstructs the same change output instead of returning funds to the legacy root address.
Create a fresh static address for partial-withdrawal change. Keep all withdrawal outputs in the PSBT without separate signing metadata, while preserving full-withdrawal behavior.
Let loop static deposit create a fresh receive address and optionally fund it through lnd SendCoins. Validate funding arguments before address creation and expose the nested request through the client RPC. Regenerate the RPC artifacts, CLI documentation, and replay fixtures for the new command behavior.
Include the owning static address in every deposit RPC response and CLI listing. Users can distinguish deposits created by different receive and change addresses without reconstructing scripts externally. Update generated RPC artifacts and command replay fixtures for the new field.
only the last commit is relevant
This PR introduces an encrypted, immutable backup for each paid L402/static-address generation.
The backup preserves the paid L402 token and the stable static-address metadata required to
reconstruct legacy, receive, and change addresses from the corresponding lnd seed.
The backup intentionally does not store mutable address indexes or deposit state. Instead, it
establishes the foundation for a follow-up PR that will implement HD-wallet-style recovery by
deterministically deriving addresses, scanning the chain, and rebuilding Loop’s local state.