Skip to content

Reverse-lookup names (ENS / UD / ZNS) across send flow and tx history#6008

Open
j0ntz wants to merge 5 commits intodevelopfrom
jon/zns-reverse-lookup
Open

Reverse-lookup names (ENS / UD / ZNS) across send flow and tx history#6008
j0ntz wants to merge 5 commits intodevelopfrom
jon/zns-reverse-lookup

Conversation

@j0ntz
Copy link
Copy Markdown
Contributor

@j0ntz j0ntz commented May 1, 2026

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Requirements

If you have made any visual changes to the GUI. Make sure you have:

  • Tested on iOS device
  • Tested on Android device
  • Tested on small-screen device (iPod Touch)
  • Tested on large-screen device (tablet)

Description

Asana task

Asana task

Reverse-resolve recipient addresses to ENS / Unstoppable Domains / ZNS names across the send flow, address modal, and transaction history. Previously only forward resolution worked (typing alice.eth resolved to 0x…); now pasting 0x… surfaces alice.eth and that name persists into transaction metadata.

What's in the branch (oldest → newest)

  1. Fix lint warnings before reverse-lookup featurelint-warnings.sh prep on the touched files: convert AddressModal and SendComponent to React.FC<Props>, and replace two styled() wrappers in SendScene2 with useTheme() + cacheStyles() equivalents. Pure refactor; behavior preserved (snapshot diff is incidental DOM-prop leakage that the new code correctly drops).

  2. Add reverse-lookup dispatcher for ENS, UD, and ZNS — new src/util/nameServices.ts with a reverseLookupName(pluginId, address) function. Per-chain dispatch:

    • ZNS — Zcash only.
    • ENS — Ethereum L1 mainnet only (ENSIP-3 reverse via ethers v5 lookupAddress; ENSIP-19 multichain reverse needs ethers v6, out of scope).
    • Unstoppable Domains — any EVM chain (caip2.namespace === 'eip155'), only when UNSTOPPABLE_DOMAINS_API_KEY is set. UD's reverse only resolves EVM addresses per their docs.

    Cache uses guard-on-success semantics: positive results cache eagerly, but transient errors don't get cached as null (avoids poisoning addresses across the process lifetime). Inflight dedup prevents duplicate network hits when the same address renders in many list rows on first paint.

  3. Use reverse-lookup dispatcher in transaction history — adds useReverseName (multi-service replacement for the old useZnsName) and a small NameServicePrefix visual element (1rem inline logo). Swaps both TransactionListRow and TransactionDetailsScene over.

    • TransactionListRow: text-only render (the row is dense; an inline logo would crowd it).
    • TransactionDetailsScene: prefix the resolved name with a 1rem-tall logo, but only when the displayed name actually came from a reverse lookup. User-set contact names and the default "To"/"From" label render plain. Missing logo assets (UD, ZNS today) fall back to no-prefix without reserving space.
  4. Reverse-lookup names in send flow — wires the dispatcher into AddressTile2's entry path. After parseUri succeeds, attempts a reverse lookup if no forward-typed name was captured. The new recipientNameService prop drives the inline NameServicePrefix badge above the address. FIO and Zano handles continue to render plain. SendScene2 carries resolvedName through spendTarget.otherParams and the post-broadcast payeeName so the resolved name persists into transaction metadata (the chain-specific Zcash branch is generalized to handle any service).

  5. Live reverse-lookup feedback in AddressModal — when the user pastes an address into the address modal, attempt a reverse lookup gated on coreWallet.parseUri succeeding (so we only hit the network when the input is a valid address for the chain — no per-keystroke lookups). Surfaces the resolved name in the input's existing green validLabel slot, mirroring the forward-resolution UX. A monotonically increasing sequence counter scopes each lookup so a slow late-arriving result can't clobber the label after the user has moved on.

Visual design notes

  • Logo prefix is only in AddressTile2 and TransactionDetailsScene. Transaction list rows stay text-only (per discussion).
  • Adding the prefix never mutates the existing icon area (contact thumbnail / chain icon / arrow overlay are untouched). The badge sits inline before the name text.
  • Only ENS has a bundled logo today (src/assets/images/ens_logo.png). UD and ZNS render text-only until assets are added — the component degrades gracefully (no placeholder, no reserved space).

Caveats / known limitations

  • ENS multichain: reverse resolution is L1 Ethereum only. ENSIP-19 (multichain reverse) needs ethers v6.
  • UD reverse: requires UNSTOPPABLE_DOMAINS_API_KEY; only resolves EVM addresses per UD's documented Resolution.reverse scope.
  • ZNS: Orchard-only Zcash addresses (per the ZcashNames SDK indexer constraints).

The dispatcher is structured so adding a new service or expanding chain support is a one-spot change in getReverseLookupServices.


Note

Medium Risk
Touches send-recipient handling and transaction display/metadata, adding new async network lookups and caching that could affect UI correctness or performance if misbehaving. Core funds flow remains unchanged, but name resolution failures/latency need verification across chains and environments (UD API key).

Overview
Adds multi-service reverse name resolution (ENS, Unstoppable Domains, ZNS) so pasted recipient addresses can display a human-readable name and persist that name through the send flow.

Introduces util/nameServices + useReverseName (with caching/inflight dedupe) and replaces the Zcash-only useZnsName usages in transaction list/details; details view can now show an inline service badge via new NameServicePrefix.

Updates AddressTile2 and AddressModal to attempt reverse lookup after successful address parsing (with stale-result guards), and refactors SendScene2 layout/styling while switching spend-target metadata from znsName to a generic resolvedName (name + service).

Reviewed by Cursor Bugbot for commit 1c1a20a. Bugbot is set up for automated code reviews on this repo. Configure here.

j0ntz added 4 commits May 1, 2026 15:50
Convert AddressModal and SendComponent to React.FC<Props>, and replace
the two `styled()` wrappers in SendScene2 with regular React Native
components driven by `useTheme()` + `cacheStyles()`. No behavior change.

The slider-view snapshot updates reflect two equivalent shifts: the old
`styled(View)` HOC was incidentally forwarding `hasNotifications` and
`insetBottom` as DOM props (which the underlying View ignored), and the
inlined style is now an array `[base, { bottom }]` rather than a merged
object. Both render to the same pixels.

Touched here in preparation for reverse-lookup edits in subsequent
commits, per the lint-warnings.sh workflow contract for files entering
the working set.
Introduces `src/util/nameServices.ts` with a `reverseLookupName(pluginId,
address)` function that maps each chain to its supported reverse-lookup
services and tries them in order (positive result wins, transient
failures don't poison the cache).

Service eligibility:
- ZNS: zcash only
- ENS: ethereum only (ENSIP-3 L1 mainnet via ethers v5 lookupAddress)
- Unstoppable Domains: any EVM chain when UNSTOPPABLE_DOMAINS_API_KEY
  is set (UD reverse only resolves EVM addresses per their docs)

The cache uses guard-on-success semantics: results are cached when the
dispatch chain completes cleanly, but transient errors leave the entry
empty so the next call retries. Inflight dedup prevents duplicate
network hits when the same address renders in many list rows.

This replaces the ad-hoc cache in `useZnsName` with a service-aware
equivalent that subsequent commits will wire into the send and
transaction-history scenes.
Adds `useReverseName` (a multi-service replacement for `useZnsName`) and
the `NameServicePrefix` visual element, then swaps both
TransactionListRow and TransactionDetailsScene over to the new hook.

UX surfaces:
- TransactionListRow: text-only render. The list is dense enough that an
  inline logo would crowd the row; the resolved name still appears, just
  without the badge.
- TransactionDetailsScene: prefix the resolved name with a 1rem-tall
  logo. The prefix only renders when the displayed name actually came
  from a reverse lookup — user-set contact names and the default
  "To"/"From" label render plain. Missing logo assets (UD, ZNS today)
  fall back to no-prefix without reserving space.

LoginActions swaps `clearZnsLookupCache` for `clearReverseLookupCache`
on the same logout boundary so per-login cache state stays isolated.

The legacy `useZnsName` hook has no remaining consumers and is removed.
The TransactionDetailsScene snapshot picks up the new row-layout `<View>`
wrapper that hosts the optional prefix; the wrapper is benign when no
prefix is present.
Wires the reverse-lookup dispatcher into AddressTile2's address-entry
path and threads the result through SendScene2 so resolved names appear
in the send tile and persist into transaction metadata.

AddressTile2 changes:
- Replace `ChangeAddressResult.znsName` with `resolvedName`, a
  `{ name, service }` pair that captures both forward-typed names
  (alice.eth, alice.zcash, alice.zec) and reverse-resolved names from
  raw addresses.
- After parseUri succeeds and we have a public address, attempt a
  reverse lookup if no forward-typed name was captured. The dispatcher
  caches per (pluginId, address) so repeated entry of the same address
  is a no-op.
- New `recipientNameService` prop drives an inline `NameServicePrefix`
  badge above the address. FIO and Zano handles continue to render
  plain (no badge), preserving the prior look for those flows.

SendScene2 changes:
- Carry `resolvedName` through `spendTarget.otherParams` and the
  `EditableAmountTile` title (still text-only per the design — the tile
  is a confirmation snapshot, not the live entry surface).
- Generalize the post-broadcast `payeeName` derivation so any
  single-resolved-name spendInfo (ENS / UD / ZNS) produces a payeeName,
  replacing the chain-specific Zcash branch.

Outcome: pasting a 0x address to an Ethereum send shows the resolved
ENS / UD name above the hex address as soon as the lookup completes,
and that name persists as `payeeName` in the broadcasted transaction's
metadata so it surfaces in the transaction history.
Comment thread src/components/modals/AddressModal.tsx
When the user pastes or types a string that doesn't match any forward-
domain pattern, attempt a reverse lookup against the wallet's pluginId
and surface the resolved name in the input's `validLabel` slot. The
existing forward-resolution path (which puts the resolved address in
the same slot) is unchanged, so the green helper text is symmetric:
- type alice.eth → see 0x… resolve below the input
- paste 0x…  → see alice.eth resolve below the input

A monotonically increasing sequence counter scopes each lookup to the
input it was issued for, so a slow late-arriving result can't clobber
the label after the user has moved on.

Adds the user-visible CHANGELOG entry covering the full reverse-lookup
feature (this commit plus the prior three on this branch).
@j0ntz j0ntz force-pushed the jon/zns-reverse-lookup branch from a1911f1 to 1c1a20a Compare May 7, 2026 22:54
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1c1a20a. Configure here.

Comment thread src/util/nameServices.ts

if (allServicesSucceeded) {
cache.set(cacheKey(pluginId, address), null)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uncached UD failures cause repeated network lookups

Medium Severity

For Ethereum mainnet (['ens', 'unstoppable']), when ENS returns null (no reverse record) and the UD SDK's reverse() throws a "no record" error (a permanent condition in the UD SDK), allServicesSucceeded becomes false and the null result is never cached. Since most Ethereum addresses lack UD reverse records, every navigation to a transaction list or re-mount triggers fresh ENS + UD network calls for each unique send-recipient address, defeating the cache entirely. The "guard-on-success" semantics correctly handle transient network errors but do not distinguish them from permanent "no record found" errors thrown by the UD SDK.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1c1a20a. Configure here.

parsedUri.publicAddress
)
if (reverse != null) resolvedName = reverse
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverse lookup blocks send flow without loading indicator

Medium Severity

The await reverseLookupName(…) call in changeAddress runs after setLoading(false) has already dismissed the full-screen spinner. For a first-time Ethereum address lookup this fires sequential network calls to ENS and Unstoppable Domains with no visible loading indicator, causing an unresponsive-looking pause of potentially several seconds before onChangeAddress completes.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1c1a20a. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant