Skip to content

multi: add labels for static addresses - #1178

Open
GustavoStingelin wants to merge 45 commits into
lightninglabs:multi-address-corefrom
GustavoStingelin:feat/static-address-label
Open

GustavoStingelin wants to merge 45 commits into
lightninglabs:multi-address-corefrom
GustavoStingelin:feat/static-address-label

Conversation

@GustavoStingelin

@GustavoStingelin GustavoStingelin commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

Adds local labels for static addresses. Stacked on #1139 (multi-address-core).

  • loop static new --label labels a new receive address, and loop static updatelabel changes or clears a label.
  • Labels are stored in LoopDB. They are never sent to the server and are not part of the address script.
  • loop static listunspent shows each UTXO's address_label. Deposits in the deposit, withdrawal and loop-in listings show address_label and funding_tx_label, the lnd label of the funding transaction.
  • Change addresses created by withdrawals and loop-ins inherit the labels of the deposits they spend. Distinct labels are joined with , up to the 500-character limit.
  • Also fixes the shared --label help text: adds a missing space, and gives listswaps its own help, since there the flag is a filter.

ListStaticAddresses, for finding labeled addresses that haven't been funded yet, will come in a follow-up PR.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

Comment thread staticaddr/address/manager.go Outdated
@GustavoStingelin
GustavoStingelin force-pushed the feat/static-address-label branch from ae23a9d to 92270e0 Compare July 17, 2026 21:32
@starius

starius commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

I propose to include the current state of #1139 into this PR, so own label commits go after @hieblmi's commits. Add a note to the PR description not to review the multi-address commits.

I attempted to do a rebase using AI, here is my attempt: https://github.com/starius/loop/commits/staticaddr-combined/ It is not functional.

Some semantic rebase is needed. For example:

  • PR 1178 adds 000022_static_address_label, while PR 1139 already uses migrations 22–24 for multi-address persistence.
  • PR 1178 assigns protobuf field 2 in NewStaticAddressRequest to label; PR 1139 assigns field 2 to send_coins.
  • PR 1178 assigns protobuf field 3 in NewStaticAddressResponse to label; PR 1139 assigns field 3 to send_coins_response.

Found some issues in that multi-address + labels branch that I got:

  • the multi-address manager returns an internal *Parameters pointer after releasing its lock, while label update mutates that same object. go test -race reports a real concurrent read/write race. repro
  • after multi-address is applied, static new returns a derived receive address, but static summary still reads the legacy/root seed. The two commands no longer describe the same address. repro
  • every generated change address is stored with an empty label, regardless of the labels on the consumed deposits. I think labels should be inherited by change - that is a common practice among wallets
  • testing gap: the SQL tests read the label back through the same open database/store instance. They do not close and reopen the database or rebuild the manager cache.
  • CLI: the shared --label help text says "label for this swap" and lacks a space, while static deposit --label labels the lnd funding transaction and static new --label labels the address.
  • ListUnspentDeposits includes the label, but historical deposit/list responses carry the address without the corresponding label - needs unification

Some of them will be resolved by a proper rebase, but some seem genuine.

@GustavoStingelin

Copy link
Copy Markdown
Contributor Author

Okay, if we agree to rebase this on top of #1139, I’d prefer to wait until @hieblmi has finished it and its CI is green. This should avoid premature conflicts and duplicated work from having to address #1139’s current race failures in the combined branch.

@GustavoStingelin

Copy link
Copy Markdown
Contributor Author

just rebased from master

@GustavoStingelin
GustavoStingelin force-pushed the feat/static-address-label branch 2 times, most recently from 3172db4 to af8f2ae Compare September 24, 2026 19:17
@GustavoStingelin
GustavoStingelin changed the base branch from master to multi-address-core September 24, 2026 19:17
@GustavoStingelin
GustavoStingelin force-pushed the feat/static-address-label branch from af8f2ae to 084565d Compare September 24, 2026 19:47
@GustavoStingelin

Copy link
Copy Markdown
Contributor Author

@hieblmi rebased this on multi-address-core (thanks!). The rebase raised two design questions I'd like to settle before we go further:

  1. How should address labels relate to funding transaction labels?
  2. Should we add a ListStaticAddresses RPC?

@hieblmi

hieblmi commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

Thanks @GustavoStingelin

  1. How should address labels relate to funding transaction labels?

I think it is valuable to the user to show these together with the funding outpoitns. The loop s listdeposits in multi-address shows something like

{
  "filtered_deposits": [
    {
      "id": "ea6abb…53e71",
      "state": "DEPOSITED",
      "outpoint": "188f55…561be:0",
      "value": "2500000",
      "confirmation_height": "131",
      "blocks_until_expiry": "14395",
      "swap_hash": "",
      "static_address": "bcrt1p…addressA"
    },
    {
      "id": "ab1234…789ef",
      "state": "DEPOSITED",
      "outpoint": "992abc…456de:1",
      "value": "1000000",
      "confirmation_height": "135",
      "blocks_until_expiry": "14399",
      "swap_hash": "",
      "static_address": "bcrt1p…addressB"
    }
  ]
}

We can then add the fields per entry:

"static_address": "bcrt1p…addressA",
"address_label": "Treasury",
"funding_tx_label": "September top-up"

What do you think?

Should we add a ListStaticAddresses RPC?

Is the intention to find addresses with labels, especially if they don't show up in listdeposits?
I think we can do that, and have it paged since this can result in a huge list. And filter for labelled addresses for example.

@GustavoStingelin

Copy link
Copy Markdown
Contributor Author

We can then add the fields per entry

Sounds good 👍

Is the intention to find addresses with labels, especially if they don't show up in listdeposits?

Yes. The main case is a user who generates a few receive addresses but hasn't funded them yet. loop static new always derives a fresh address, so those unfunded addresses don't appear in listunspent/listdeposits. If the user didn't save them elsewhere, they're effectively lost and unused addresses keep piling up. A ListStaticAddresses RPC would let users find their addresses again by label, whether or not they've been funded.

To keep this PR focused on labels, I'll add ListStaticAddresses in a follow-up PR.

@hieblmi
hieblmi force-pushed the multi-address-core branch 3 times, most recently from ad14247 to 5b76079 Compare September 28, 2026 14:40
@litbot-9000

Copy link
Copy Markdown
Collaborator

@hieblmi: review reminder

@GustavoStingelin
GustavoStingelin removed the request for review from hieblmi October 2, 2026 15:00
@GustavoStingelin
GustavoStingelin force-pushed the feat/static-address-label branch 5 times, most recently from 03027f4 to d60dc33 Compare October 6, 2026 02:03
@GustavoStingelin

Copy link
Copy Markdown
Contributor Author
  • Deposits in the deposit, withdrawal and loop-in listings now include address_label and funding_tx_label (the lnd label of the funding transaction), as discussed.
  • Renamed Utxo.label to address_label in ListUnspentDeposits, so both listings use the same name.
  • Removed the label from StaticAddressSummaryResponse. It only ever referred to the legacy/root address.
  • Change addresses created by withdrawals and loop-ins now inherit the labels of the deposits they spend. Distinct labels are joined with , in spend order, labels already inherited aren't repeated, and labels that would exceed the 500-character limit are dropped whole.
  • Deposits and loop-in change addresses loaded from the DB now carry their address label.
  • UpdateStaticAddressLabel returns gRPC NotFound for an unknown address.
  • Fixed the shared --label help text (missing space), and gave listswaps --label its own help, since there it's a filter.

Require one legacy address when deposits exist before migration 23
assigns ownership. Use a regular CHECK-constrained guard table on both
backends without an additional PostgreSQL TEMPORARY privilege.

Cover backfills, ambiguous ownership, restricted roles, rollback and
retry. Document recovery only for a verified rolled-back dirty migration
23, retaining the migration framework's existing dirty-version behavior.
Encode each persisted P2TR script using an explicit network in a free
helper. Address rendering does not need the manager or reconstruction of
the signing keys and script tree, and does not imply an ownership check.
Quote selected deposits with one exact active-set lookup, retaining
presence, state, duplicate and expiry checks without rendering history.

Refresh deposits before taking the unspent response snapshot and filter
against active Deposited records. Historical database rows cannot revive
outputs removed during reconciliation or return stale confirmations.
Keep the lowest-ID active address alongside the script index. Publish it
only after successful activation and update it under the index mutex.

Serve public root lookups from the cache with independent parameter,
script and public-key copies. Cover restart recovery, failed imports and
caller mutation so caching preserves readiness and value ownership.
Read wallet UTXOs outside the map lock, then match their scripts
directly against the active address index under a short lock. This
removes the full-index allocation and copy on each poll while
preserving confirmation bounds and allowing address activation
during the wallet RPC.

Test filtering across addresses and verify wallet RPCs do not hold
the map lock.
Load persisted addresses in bounded pages using an ascending ID cursor
during startup and root recovery. Read wallet watches once and publish
the complete runtime index only after every page and import succeeds.
The existing bulk-read API remains available for compatibility.

Test sparse IDs, page boundaries, failed-page recovery and restart
behavior, including the SQL query on SQLite and PostgreSQL.
Replace the issuance mutex with a context-aware gate so queued callers
can cancel without disturbing the current issuer. Keep root creation,
recovery and derived issuance serialized and retryable. A private mutex
now explicitly guards the active script index and root.

Test canceled root and derived waiters, subsequent progress and
concurrent first callers sharing one root.
Remove the blank line before the closing brace flagged by CI. This is
formatting only; test behavior is unchanged.
Compare lnd counters with persisted address and HTLC indices, verify
wallet identity and advance only lagging families before activation.
Preserve equal or ahead counters without creating addresses.

Allow restoration beyond the manager deadline with RPC timeouts,
cancellation and progress logs. Finish listener initialization before
shutdown teardown to avoid leaking REST or racing wait groups. Cover
restoration, interrupted restarts and shutdown order.
A restored wallet may not have derived a static address key. Pubkey
lookup then misses and can fall back to locator (0, 0), producing an
invalid expiry signature.

Pass the deposit locator and omit PubKey so lndclient sends the locator
over the wire. Cover root, receive and change deposits.
Document fresh receive-address derivation, lazy seed initialization,
funding-address lookup hardening, and the swap:execute permission
required by address creation. Regenerate the CLI, gRPC, Swagger, and
man-page documentation and add feature, breaking-change, and recovery
release notes.
Cost polling can retain a fee share before discovery stores another
static deposit from the same transaction. Different scripts caused the
first deposit to receive the entire fee permanently.

Recognize every owned static output through the active address index
before allocating the fee. Preserve value-based shares, remainder
placement, and existing change and unknown-fee behavior.
Recognize legacy root change alongside derived change and preserve
accounting when both outputs pay the root. Reuse the persisted change
address across fee bumps and restarts while retaining old transactions
and creating fresh signing sessions.

Compute withdrawal values once. Cover legacy and derived change, root
destinations, repeated fee bumps, full withdrawals and unrelated
outputs.
@hieblmi
hieblmi force-pushed the multi-address-core branch from a957b74 to 8400ee7 Compare October 22, 2026 16:13
Add the missing space in the shared --label help, and give listswaps its
own --label help since there it filters swaps instead of labeling one.
Add a label column to static_addresses and a query to update it, and
select it wherever deposits and change addresses join their address.
Add a Label field to the static address parameters. It is local
metadata and not part of the script or server protocol.
Read and write address labels in the address store, and load them with
deposits and loop-in change addresses.
Add a label to NewStaticAddress, a new UpdateStaticAddressLabel RPC,
and labels on unspent deposits and deposit listings.
@GustavoStingelin
GustavoStingelin force-pushed the feat/static-address-label branch from d60dc33 to d9ea83d Compare October 22, 2026 19:12
Label new addresses, relabel them without mutating published address
snapshots, and fill in address and funding transaction labels in
deposit responses.
Add --label to static new and a static updatelabel command.
Change addresses take the distinct labels of the addresses they spend,
joined with ", " up to the label limit.
@GustavoStingelin
GustavoStingelin force-pushed the feat/static-address-label branch from d9ea83d to e025292 Compare October 22, 2026 19:37
@litbot-9000

Copy link
Copy Markdown
Collaborator

@starius: review reminder
@hieblmi: review reminder

@hieblmi
hieblmi force-pushed the multi-address-core branch 4 times, most recently from fac574a to 056e0e1 Compare October 28, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants