Summary
Form preview and exported apps currently render blockchain-address fields via TransactionForm → DynamicFormField → 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-address → AddressField only. Builder-specific wrappers (BlockchainAddressFieldWithRichPreview) are not used by:
FormPreview.tsx (TransactionForm)
- Exported
GeneratedForm (form-component.template.tsx → TransactionForm)
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 (useRuntimeNameResolver → activeRuntime?.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
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).
-
Add typed setting (prefer first-class field over metadata):
export type EnsAddressPreviewMode = 'rich' | 'simple';
// On CommonFormProperties or RenderFormSchema:
ensAddressPreview?: EnsAddressPreviewMode;
-
Renderer field component — e.g. BlockchainAddressDynamicField:
useWatch → previewAddress
rich: Pattern A (AddressFieldWithResolvedPreview + ResolvedAddressFieldPreviewWithNameResolution, networkId from adapter.networkConfig.id)
simple: bare AddressField
- Thread mode via context from
TransactionForm through recursive DynamicFormField calls
-
Defaults in renderer:
ensAddressPreview omitted → 'simple' (preserves existing exported apps)
- explicit
'rich' / 'simple' honored
-
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
-
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)
- Extend
BuilderFormConfig with ensAddressPreview?: EnsAddressPreviewMode (default rich for newly created forms).
FormSchemaFactory.builderConfigToRenderSchema already spreads builder config → schema; verify field is included in export JSON.
- 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)
- Migration: existing
ContractUIRecord.formConfig without ensAddressPreview → render as 'simple' (renderer default); do not auto-migrate DB on read.
- Tests:
EnsExportPins, export snapshot tests, form schema factory unit tests.
- 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
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.tsx → TransactionForm
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
- Should rich require both
resolveName and resolveAddress, or is forward-only + address card acceptable when reverse is missing?
- Per-field override in a later version — worth reserving schema shape (
field.metadata.ensAddressPreview) or keep form-level only?
- Should builder EOA / contract-definition surfaces respect the same form-level setting, or stay always-rich?
Summary
Form preview and exported apps currently render
blockchain-addressfields viaTransactionForm→DynamicFormField→ bareAddressField, which shows the simple ENS feedback UX (inline “Resolved to0x…” 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)useWatchforpreviewAddressExamples: 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
TransactionFormper app.rich(new default for new forms)simple(current behavior)0x…” text announcerProblem
fieldRegistryin@openzeppelin/ui-renderermapsblockchain-address→AddressFieldonly. Builder-specific wrappers (BlockchainAddressFieldWithRichPreview) are not used by:FormPreview.tsx(TransactionForm)GeneratedForm(form-component.template.tsx→TransactionForm)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:
This matches existing SF-3 wiring (
useRuntimeNameResolver→activeRuntime?.nameResolution) andNameResolutionCapabilitystructural feature detection in@openzeppelin/ui-types.When a chain has no name-resolution service, the adapter omits
nameResolutionfromEcosystemRuntime— UI degrades gracefully (hex-only address input, empty resolver →UNSUPPORTED_NETWORKfor 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
Single implementation path in
@openzeppelin/ui-rendererso builder preview, export, and any directTransactionFormconsumer stay aligned.Scope
In scope
BuilderFormConfig/RenderFormSchemaTransactionForm+DynamicFormFieldrich/simple rendering forblockchain-addressOut of scope (this issue)
BlockchainAddressFieldWithRichPreview; optional follow-up: dedupe via shared renderer component)Implementation plan
Phase 1 —
@openzeppelin/ui-types+@openzeppelin/ui-renderer(upstream)Tracking: open a companion issue/PR in openzeppelin-ui (blocks builder integration).
Add typed setting (prefer first-class field over
metadata):Renderer field component — e.g.
BlockchainAddressDynamicField:useWatch→previewAddressrich: Pattern A (AddressFieldWithResolvedPreview+ResolvedAddressFieldPreviewWithNameResolution,networkIdfromadapter.networkConfig.id)simple: bareAddressFieldTransactionFormthrough recursiveDynamicFormFieldcallsDefaults in renderer:
ensAddressPreviewomitted →'simple'(preserves existing exported apps)'rich'/'simple'honoredDegradation (no throws):
nameResolutioncapability → both modes behave as plain address field (existing SF-3 empty-resolver behavior)richwithoutresolveAddress→ forward resolution still works; preview card may show address-onlyTests:
DynamicFormField/TransactionFormfor both modes; nested fields; capability-absent runtimeDependency 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)BuilderFormConfigwithensAddressPreview?: EnsAddressPreviewMode(defaultrichfor newly created forms).FormSchemaFactory.builderConfigToRenderSchemaalready spreads builder config → schema; verify field is included in export JSON.Boolean(runtime?.nameResolution?.resolveName)(capability-led, not ecosystem)ContractUIRecord.formConfigwithoutensAddressPreview→ render as'simple'(renderer default); do not auto-migrate DB on read.EnsExportPins, export snapshot tests, form schema factory unit tests.BlockchainAddressFieldWithRichPreviewas thin wrapper around upstreamBlockchainAddressDynamicFieldto 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)
Acceptance criteria
ensAddressPreviewin embedded form schema JSONTransactionFormpath)nameResolutioncapability: toggle hidden; address fields work as todayecosystem === 'evm'(or similar) conditionals in builder or renderer for this featureReferences
Canonical Pattern A (openzeppelin-ui):
examples/basic-react-app/src/components/AddressFieldDemo.tsxexamples/basic-react-app/src/components/ENSResolutionDemo.tsxpackages/renderer/src/components/AddressBookWidget/AddAliasDialog.tsxpackages/renderer/src/components/ResolvedAddressFieldPreviewWithNameResolution.tsxpackages/react/src/hooks/nameResolution/useRuntimeNameResolver.tsui-builder today:
apps/builder/src/components/UIBuilder/StepFormCustomization/FormPreview.tsx→TransactionFormapps/builder/src/components/fields/BlockchainAddressFieldWithRichPreview.tsxapps/builder/src/core/factories/FormSchemaFactory.tsapps/builder/src/export/codeTemplates/form-component.template.tsxTypes:
NameResolutionCapability—@openzeppelin/ui-types(resolveName?,resolveAddress?, structural feature detection)Suggested labels / milestone
enhancement,feature,evm(product area only if desired — not for runtime gating)Open questions
resolveNameandresolveAddress, or is forward-only + address card acceptable when reverse is missing?field.metadata.ensAddressPreview) or keep form-level only?