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
Adds new network-backed reverse-resolution logic and wires it into send and transaction UI/metadata, which could impact address handling and performance if lookups fail or are slow. Changes are scoped to name display/metadata but touch core send entry points.

Overview
Adds reverse address-to-name resolution across the app. A new nameServices dispatcher performs cached reverse lookups for ENS (Ethereum mainnet), Unstoppable Domains (EVM chains when API key is set), and ZNS (Zcash), with inflight de-duping and guarded null-caching.

Send flow now captures a unified resolvedName (from forward entry or reverse lookup) in AddressTile2/SendScene2, displays an optional inline service badge, and persists the resolved name into transaction metadata/payee naming.

Transaction history UI replaces the Zcash-only useZnsName hook with a generic useReverseName, showing resolved recipient names (and an inline ENS logo where available) in TransactionListRow and TransactionDetailsScene. Logout now clears the new reverse-lookup cache, and snapshots are updated due to SendScene layout/styling refactors.

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

j0ntz added 5 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.
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).
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 1 potential issue.

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 a1911f1. Configure here.

this.setState({ validLabel: result.name })
})
.catch((_err: unknown) => undefined)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale reverse lookup not cancelled on domain input

Medium Severity

The reverseLookupSeq counter is only incremented inside tryReverseLookup (called for non-domain inputs). When the user switches from a raw address to a domain name (e.g. alice.eth), onChangeTextDelayed takes the checkIfDomain branch and never bumps the counter. A slow in-flight reverse lookup from the previous raw-address input still passes the seq === this.reverseLookupSeq guard. Since validLabel was cleared to undefined at the top of onChangeTextDelayed, the stale callback can briefly set validLabel to the wrong resolved name before the forward resolution overwrites it.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a1911f1. 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