Skip to content

feat(builder): configurable ENS address preview mode (rich vs simple) for forms #402

Description

@pasevin

Summary

Form preview and exported apps currently use TransactionFormDynamicFormField → bare AddressField, which shows the simple ENS forward-resolution UX (inline “Resolved to 0x…” text + optional cross-network disclaimer — see screenshot in PR #401 discussion).

Builder-specific surfaces already use Pattern A (rich preview card with avatar via AddressFieldWithResolvedPreview + ResolvedAddressFieldPreviewWithNameResolution + useWatch), e.g.:

  • apps/builder/src/components/fields/BlockchainAddressFieldWithRichPreview.tsx
  • Address book Add Alias (internal to AddressBookWidget / AddAliasDialog in ui-renderer ≥3.4.0)

Gap: end-user transaction forms (preview + export) never take the rich path because packages/renderer/src/components/fieldRegistry.ts maps blockchain-addressAddressField.

We want a form-level builder setting so users can choose:

Mode UX
rich (new default for new forms) Pattern A preview card (name + avatar) below address inputs
simple (current behavior; preserve for existing exports) Inline “Resolved to 0x…” announcer

Motivation

  • Parity between builder preview, exported apps, and rich ENS surfaces (address book, EOA config)
  • User control over visual density / branding
  • Safe rollout: existing saved/exported forms should not change appearance unless opted in

Architecture (constitution-compliant)

Per .specify/memory/constitution.md Principle I (chain-agnostic, adapter-led):

  • Do NOT gate on ecosystem === 'evm' or chain id strings
  • Do use runtime capabilities: runtime?.nameResolution (NameResolutionCapability)
  • Feature detection is structural per ui-types: capability.resolveName, capability.resolveAddress
  • When capability is absent, components degrade gracefully (existing SF-3 behavior via empty NameResolver)

Data flow

Builder UI toggle (General Settings)
    ↓
BuilderFormConfig.ensAddressPreview: 'rich' | 'simple'
    ↓
FormSchemaFactory.builderConfigToRenderSchema()
    ↓
RenderFormSchema.ensAddressPreview  (serialized into export @@FORM_SCHEMA_JSON@@)
    ↓
TransactionForm reads schema → DynamicFormField → blockchain-address field
    ↓
'rich'  → AddressFieldWithResolvedPreview + ResolvedAddressFieldPreviewWithNameResolution
'simple' → AddressField (current)

Capability-led UI gating (builder only)

Show the setting when the active runtime supports forward name resolution:

const canResolveNames = Boolean(runtime?.nameResolution?.resolveName);

Optional: only advertise rich when resolveAddress is also present; otherwise rich degrades to address-only card.

Hide control when !canResolveNames — because the adapter didn’t publish name resolution, not because of ecosystem string checks.


Upstream dependency (openzeppelin-ui)

This feature requires renderer support — builder-only wrappers will not fix exported apps.

Phase 1 — @openzeppelin/ui-types

Add typed setting (prefer first-class field over metadata):

export type EnsAddressPreviewMode = 'rich' | 'simple';

// On CommonFormProperties or RenderFormSchema:
ensAddressPreview?: EnsAddressPreviewMode;

Phase 2 — @openzeppelin/ui-renderer

  • Implement BlockchainAddressDynamicField (or equivalent) with useWatch + mode switch
  • Wire from DynamicFormField when field.type === 'blockchain-address'
  • Thread mode through nested DynamicFormField recursion (object/array/map children)
  • TransactionForm reads schema.ensAddressPreview and provides context
  • Defaults:
    • undefined / omitted → 'simple' (backward compat for existing exports)
    • explicit 'rich' → Pattern A
    • explicit 'simple' → current announcer

Reference implementations:

  • openzeppelin-ui/examples/basic-react-app/src/components/AddressFieldDemo.tsx
  • openzeppelin-ui/packages/renderer/src/components/AddressBookWidget/AddAliasDialog.tsx
  • openzeppelin-ui/packages/renderer/src/components/ResolvedAddressFieldPreviewWithNameResolution.tsx

Phase 3 — ui-builder (this repo)

  • Extend BuilderFormConfig / persist via ContractUIRecord.formConfig
  • UI: radio in General Settings (or ENS subsection) — Rich vs Simple
  • Default new forms to 'rich'; treat missing field as 'simple' for loaded records
  • FormPreview.tsx — no structural change once renderer supports schema field
  • Export — schema JSON already embedded via form-component.template.tsx; update EnsExportPins.verification.test.ts / snapshots
  • Consolidate BlockchainAddressFieldWithRichPreview.tsx to re-use upstream field component once available
  • Bump ui-renderer floor when upstream ships

Out of scope / anti-patterns

  • ❌ Forking TransactionForm only in builder (export diverges)
  • ❌ Patching fieldRegistry only in export templates
  • ❌ Defaulting existing schemas to 'rich' without migration
  • if (ecosystem === 'evm') conditionals anywhere in UI

Acceptance criteria

  • Builder toggle visible only when runtime.nameResolution?.resolveName is present
  • Form preview reflects selected mode for blockchain-address fields
  • Exported app formSchema includes ensAddressPreview and matches preview
  • Existing Contract UI records without the field render as simple (unchanged)
  • New forms default to rich when name resolution is available
  • Non-capable networks: address fields work; rich mode degrades safely
  • ENS 003 mainnet L1 fallback: simple mode keeps announcer + disclaimer; rich mode uses preview card
  • Tests: renderer unit tests (both modes), builder export pin/snapshot tests, manual QA on Sepolia + pasevin.eth

Related work


Suggested sequencing

  1. openzeppelin-ui issue/PR: types + renderer implementation + tests
  2. ui-builder follow-up on PR feat(builder): enable full ENS featureset with mainnet L1 fallback #401 (or stacked PR): UI toggle, schema plumbing, floor bump, snapshots

Labels

enhancement, ens, export, ui-renderer-dependency

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions