Skip to content

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

Description

@pasevin

Summary

Form preview and exported apps currently render blockchain-address fields via TransactionFormDynamicFormField → bare AddressField, which shows the simple ENS feedback UX (inline “Resolved to 0x…” announcer + optional cross-network disclaimer).

Elsewhere in the product we already ship a rich ENS preview (avatar + reverse-ENS card) using Pattern A:

  • AddressFieldWithResolvedPreview (@openzeppelin/ui-components)
  • ResolvedAddressFieldPreviewWithNameResolution (@openzeppelin/ui-renderer)
  • parent useWatch for previewAddress

Examples: Address Book Add Alias dialog, builder EOA config / hardcoded address surfaces (BlockchainAddressFieldWithRichPreview).

Goal: let form builders choose the ENS address preview style for function input fields in preview + exported apps, without forking TransactionForm per app.

Mode UX Default for
rich (new default for new forms) Pattern A preview card below field; suppresses forward success announcer New Contract UIs
simple (current behavior) Inline “Resolved to 0x…” text announcer Existing saved/exported forms (backward compat)

Problem

fieldRegistry in @openzeppelin/ui-renderer maps blockchain-addressAddressField only. Builder-specific wrappers (BlockchainAddressFieldWithRichPreview) are not used by:

  • FormPreview.tsx (TransactionForm)
  • Exported GeneratedForm (form-component.template.tsxTransactionForm)

So preview/export diverge from the richer UX users see in Address Book.


Design principles (constitution-aligned)

Per UI Builder Constitution — Principle I: chain-agnostic core, adapter-led architecture.

✅ DO: capability-led detection

Gate features on runtime capabilities, not ecosystem strings:

// Forward name resolution available?
Boolean(runtime?.nameResolution?.resolveName)

// Reverse name resolution available? (rich preview card)
Boolean(runtime?.nameResolution?.resolveAddress)

This matches existing SF-3 wiring (useRuntimeNameResolveractiveRuntime?.nameResolution) and NameResolutionCapability structural feature detection in @openzeppelin/ui-types.

When a chain has no name-resolution service, the adapter omits nameResolution from EcosystemRuntime — UI degrades gracefully (hex-only address input, empty resolver → UNSUPPORTED_NETWORK for names).

❌ DON'T: ecosystem conditionals

Do not use networkConfig.ecosystem === 'evm' (or similar) to show/hide the toggle or pick preview mode. That duplicates adapter knowledge and violates Principle I.


Proposed architecture

Builder UI toggle (General Settings)
    ↓
BuilderFormConfig.ensAddressPreview: 'rich' | 'simple'
    ↓
FormSchemaFactory.builderConfigToRenderSchema()
    ↓
RenderFormSchema.ensAddressPreview  (embedded in export @@FORM_SCHEMA_JSON@@)
    ↓
TransactionForm reads schema
    ↓
DynamicFormField (blockchain-address)
    ├─ 'rich'  → AddressFieldWithResolvedPreview + ResolvedAddressFieldPreviewWithNameResolution + useWatch
    └─ 'simple' → AddressField (current)

Single implementation path in @openzeppelin/ui-renderer so builder preview, export, and any direct TransactionForm consumer stay aligned.


Scope

In scope

  • Form-level setting persisted in BuilderFormConfig / RenderFormSchema
  • Builder UI control (General Settings or dedicated “ENS display” section)
  • TransactionForm + DynamicFormField rich/simple rendering for blockchain-address
  • Nested address fields (object / array / map) inherit form-level mode
  • Export codegen carries setting in serialized form schema (no template fork)
  • Backward compatibility for existing Contract UI records / exports without the field
  • Capability-led UI visibility (show toggle only when forward name resolution is available)

Out of scope (this issue)

  • Per-field override (form-level only for v1)
  • Changing Address Book Add Alias (already rich + network-scoped internally in ui-renderer 3.4+)
  • EOA execution config / contract-definition builder surfaces (already use BlockchainAddressFieldWithRichPreview; optional follow-up: dedupe via shared renderer component)
  • New name-resolution protocols beyond what adapters expose today

Implementation plan

Phase 1 — @openzeppelin/ui-types + @openzeppelin/ui-renderer (upstream)

Tracking: open a companion issue/PR in openzeppelin-ui (blocks builder integration).

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

    export type EnsAddressPreviewMode = 'rich' | 'simple';
    
    // On CommonFormProperties or RenderFormSchema:
    ensAddressPreview?: EnsAddressPreviewMode;
  2. Renderer field component — e.g. BlockchainAddressDynamicField:

    • useWatchpreviewAddress
    • rich: Pattern A (AddressFieldWithResolvedPreview + ResolvedAddressFieldPreviewWithNameResolution, networkId from adapter.networkConfig.id)
    • simple: bare AddressField
    • Thread mode via context from TransactionForm through recursive DynamicFormField calls
  3. Defaults in renderer:

    • ensAddressPreview omitted'simple' (preserves existing exported apps)
    • explicit 'rich' / 'simple' honored
  4. Degradation (no throws):

    • No nameResolution capability → both modes behave as plain address field (existing SF-3 empty-resolver behavior)
    • rich without resolveAddress → forward resolution still works; preview card may show address-only
  5. Tests: DynamicFormField / TransactionForm for both modes; nested fields; capability-absent runtime

Dependency floors (when shipping): align with network-scoped ENS stack already adopted in builder PR #401 (ui-renderer ^3.4.0, ui-components ^3.8.0, ui-react ^3.3.0).


Phase 2 — ui-builder (this repo)

  1. Extend BuilderFormConfig with ensAddressPreview?: EnsAddressPreviewMode (default rich for newly created forms).
  2. FormSchemaFactory.builderConfigToRenderSchema already spreads builder config → schema; verify field is included in export JSON.
  3. Builder UI — radio in General Settings:
    • Rich preview — “Show avatar and reverse-ENS card below address fields”
    • Simple — “Show inline ‘Resolved to 0x…’ text”
    • Visible only when Boolean(runtime?.nameResolution?.resolveName) (capability-led, not ecosystem)
  4. Migration: existing ContractUIRecord.formConfig without ensAddressPreview → render as 'simple' (renderer default); do not auto-migrate DB on read.
  5. Tests: EnsExportPins, export snapshot tests, form schema factory unit tests.
  6. Optional cleanup: reimplement BlockchainAddressFieldWithRichPreview as thin wrapper around upstream BlockchainAddressDynamicField to avoid duplicate Pattern A wiring.

Related work already merged/in flight: PR #401 (ENS featureset, rich preview on builder-only surfaces, export ENS wiring).


Builder UI mock (copy)

ENS address preview

Choose how address fields display ENS resolution feedback.

  • Rich preview (recommended) — avatar + name card below the field
  • Simple — inline “Resolved to 0x…” text

Only shown when the active network runtime exposes forward name resolution (nameResolution.resolveName).


Acceptance criteria

  • New form defaults to rich preview in builder; toggling updates live Form Preview immediately
  • Exported app ZIP contains ensAddressPreview in embedded form schema JSON
  • Exported app rich mode matches builder preview (same TransactionForm path)
  • Existing saved Contract UIs without the field keep simple announcer (no visual regression)
  • Stellar / other chains without nameResolution capability: toggle hidden; address fields work as today
  • No ecosystem === 'evm' (or similar) conditionals in builder or renderer for this feature
  • ENS forward + reverse + mainnet L1 fallback (003 opt-in) still work in both modes where capability exists
  • Unit/integration tests green in ui-renderer and ui-builder

References

Canonical Pattern A (openzeppelin-ui):

  • examples/basic-react-app/src/components/AddressFieldDemo.tsx
  • examples/basic-react-app/src/components/ENSResolutionDemo.tsx
  • packages/renderer/src/components/AddressBookWidget/AddAliasDialog.tsx
  • packages/renderer/src/components/ResolvedAddressFieldPreviewWithNameResolution.tsx
  • packages/react/src/hooks/nameResolution/useRuntimeNameResolver.ts

ui-builder today:

  • apps/builder/src/components/UIBuilder/StepFormCustomization/FormPreview.tsxTransactionForm
  • apps/builder/src/components/fields/BlockchainAddressFieldWithRichPreview.tsx
  • apps/builder/src/core/factories/FormSchemaFactory.ts
  • apps/builder/src/export/codeTemplates/form-component.template.tsx

Types:

  • NameResolutionCapability@openzeppelin/ui-types (resolveName?, resolveAddress?, structural feature detection)

Suggested labels / milestone


Open questions

  1. Should rich require both resolveName and resolveAddress, or is forward-only + address card acceptable when reverse is missing?
  2. Per-field override in a later version — worth reserving schema shape (field.metadata.ensAddressPreview) or keep form-level only?
  3. Should builder EOA / contract-definition surfaces respect the same form-level setting, or stay always-rich?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions