import React from 'react' import { Box, Text } from 'ink' import { type Address } from 'viem' import { Surface } from '../../../ui/Surface.js' import { Select, type SelectOption } from '../../../ui/Select.js' import { Spinner } from '../../../ui/Spinner.js' import { theme } from '../../../ui/theme.js' import { isEthDomain, normalizeEthDomain, } from '../../ens/ensLookup.js' import { isRootEthName } from '../../ens/ensAutomation.js' import type { Erc8004RegistryConfig } from '../../registry/erc8004.js' import type { BrowserWalletReady } from '../../wallet/browserWallet.js' import { type CustodyMode, } from '../custody/state.js' import { shortAddress } from '../shared/model/format.js' import { FieldRow } from '../shared/components/FieldRow.js' import { recordsDiffHasChanges, type EnsLinkOptions, } from './editCopy.js' import { openExternalUrl } from '../../../utils/openExternal.js' import { TextInput } from '../../../ui/TextInput.js' import { EnsStatusBanner, footerHint, SubdomainEntry, } from './EnsEditShared.js' import { EnsSetupBlockedScreen, EnsSetupReviewScreen, ReviewScreen, SimpleEnsIssueScreen, } from './EnsEditReviewScreens.js' import { EscCancel } from './EnsEditRunners.js' import type { DiscoveryState, EnsEditProps, EnsPhase, } from './types.js' const ENS_DOMAINS_URL = 'https://app.ens.domains' type SimpleScreenProps = { phase: EnsPhase discovery: DiscoveryState ownerAddress: Address discoveryStartedAt: number validationError: string | null currentEnsName: string savedCustodyMode: CustodyMode | undefined registryNetworkLabel: string registry: Erc8004RegistryConfig agentNameSuggestion: string operatorWalletSession: BrowserWalletReady | null setOperatorWalletSession: (session: BrowserWalletReady | null) => void setPhase: (phase: EnsPhase) => void cancelDiscoveryToModeSelect: () => void runDiscovery: (mode?: 'simple' | 'advanced') => void runValidation: (fullName: string, mode: 'simple' | 'advanced', phaseOwnerAddress?: Address, operatorWallet?: Address) => Promise runAdvancedRootCheck: (rootName: string) => void backToSimpleSubdomain: (fullName: string) => void runSimpleCreatePreflight: (fullName: string) => void onEnsSetup: EnsEditProps['onEnsSetup'] onEnsLink: EnsEditProps['onEnsLink'] onEnsRecordsUpdate: EnsEditProps['onEnsRecordsUpdate'] identity: EnsEditProps['identity'] } export function renderSimpleEnsPhase({ phase, discovery, ownerAddress, discoveryStartedAt, validationError, currentEnsName, savedCustodyMode, registryNetworkLabel, registry, agentNameSuggestion, operatorWalletSession, setOperatorWalletSession, setPhase, cancelDiscoveryToModeSelect, runDiscovery, runValidation, runAdvancedRootCheck, backToSimpleSubdomain, runSimpleCreatePreflight, onEnsSetup, onEnsLink, onEnsRecordsUpdate, identity, }: SimpleScreenProps): React.ReactNode | null { const statusBanner = ( ) if (phase.kind === 'discovering' || discovery.status === 'loading') { return ( ) } if (phase.kind === 'pick-parent') { type DomainAction = `pick:${string}` | 'open-ens-domains' | 'manual' | 'retry' | 'back' const ownedNames = discovery.status === 'ok' || discovery.status === 'error' ? discovery.names : [] const errorMessage = discovery.status === 'error' ? 'Root ENS Lookup Failed' : null const warningMessage = discovery.status === 'ok' ? discovery.warning : null const noOwnedNames = discovery.status === 'ok' && ownedNames.length === 0 const options: Array> = [ ...(ownedNames.length > 0 ? [ { value: 'pick:section' as DomainAction, role: 'section' as const, label: 'Your ENS Names' }, ...ownedNames.map(name => ({ value: `pick:${name}` as DomainAction, label: name, hint: 'Choose a subdomain', })), ] : []), { value: 'open-ens-domains' as DomainAction, label: 'Register .eth Name', hint: 'Opens the ENS app' }, ...(discovery.status === 'error' ? [ { value: 'retry' as DomainAction, label: errorMessage ? 'Try Again' : 'Retry Lookup', hint: 'Retry lookup' }, { value: 'manual' as DomainAction, label: 'Enter ENS Name Manually' }, ] : []), { value: 'back', label: 'Back', role: 'utility' }, ] const advancedMode = phase.mode === 'advanced' return ( {advancedMode ? Owner wallet: {shortAddress(ownerAddress)} : null} {validationError ? {validationError} : null} {phase.error ? {phase.error} : null} {errorMessage ? {errorMessage}: {discovery.status === 'error' ? discovery.message : ''} : null} {warningMessage ? {warningMessage} : null} {noOwnedNames ? ( No parent .eth name on this wallet yet. Register one at app.ens.domains first. ) : null} options={options} hintLayout="inline" onSubmit={choice => { if (choice === 'back') return setPhase({ kind: 'mode-select' }) if (choice === 'manual') { setPhase({ kind: 'manual-parent', ...(advancedMode ? { mode: 'advanced' as const } : {}) }); return } if (choice === 'open-ens-domains') { openExternalUrl(ENS_DOMAINS_URL) return } if (choice === 'retry') { runDiscovery(advancedMode ? 'advanced' : 'simple') return } if (choice.startsWith('pick:')) { const name = choice.slice('pick:'.length) if (!name) return if (advancedMode) { runAdvancedRootCheck(name) return } setPhase({ kind: 'pick-subdomain', parent: name }) } }} onCancel={() => setPhase({ kind: 'mode-select' })} /> ) } if (phase.kind === 'manual-parent') { const advancedMode = phase.mode === 'advanced' return ( {statusBanner} {advancedMode ? Owner wallet: {shortAddress(ownerAddress)} : null} {phase.error ? {phase.error} : null} { const v = normalizeEthDomain(value) if (!v) return 'Enter a .eth name' if (!isEthDomain(v)) return 'Must be a valid .eth name' if (!isRootEthName(v)) return 'Enter a root .eth name, e.g. name.eth' return null }} onSubmit={value => { const root = normalizeEthDomain(value) if (advancedMode) { runAdvancedRootCheck(root) return } setPhase({ kind: 'pick-subdomain', parent: root }) }} onCancel={() => setPhase({ kind: 'pick-parent', ...(advancedMode ? { mode: 'advanced' as const } : {}) })} /> ) } if (phase.kind === 'pick-subdomain') { return ( { void runValidation(fullName, 'simple') }} onBack={() => setPhase({ kind: 'pick-parent' })} /> ) } if (phase.kind === 'validating') { return ( backToSimpleSubdomain(phase.fullName)} /> ) } if (phase.kind === 'simple-name-missing') { return ( runSimpleCreatePreflight(phase.fullName)} onCheckAgain={() => { void runValidation(phase.fullName, 'simple') }} onChange={() => backToSimpleSubdomain(phase.fullName)} onBack={() => backToSimpleSubdomain(phase.fullName)} /> ) } if (phase.kind === 'simple-create-preflight') { return ( backToSimpleSubdomain(phase.fullName)} /> ) } if (phase.kind === 'simple-create-review') { return ( { if (phase.setup.txCount > 0) { onEnsSetup(phase.setup) return } onEnsLink(phase.setup.fullName, { mode: 'simple' }) }} onBack={() => backToSimpleSubdomain(phase.setup.fullName)} /> ) } if (phase.kind === 'simple-create-blocked') { return ( runSimpleCreatePreflight(phase.fallback.fullName)} onBack={() => backToSimpleSubdomain(phase.fallback.fullName)} /> ) } if (phase.kind === 'review') { const linkOptions: EnsLinkOptions = phase.mode === 'advanced' && phase.ownerAddress && phase.operatorWallet ? { mode: 'advanced', ownerAddress: phase.ownerAddress, operatorWallet: phase.operatorWallet } : { mode: 'simple' } return ( { if (recordsDiffHasChanges(phase.recordsDiff)) { onEnsRecordsUpdate(phase.fullName, phase.nextRecords, linkOptions, false, phase.currentRecords) return } onEnsLink(phase.fullName, linkOptions) }} onCheckAgain={() => { void runValidation(phase.fullName, phase.mode, phase.ownerAddress, phase.operatorWallet) }} onChange={() => setPhase({ kind: 'pick-parent', mode: phase.mode })} onCreate={phase.mode === 'simple' && !phase.validation.ok && phase.validation.reason === 'no-owner' ? () => runSimpleCreatePreflight(phase.fullName) : undefined} onBack={() => backToSimpleSubdomain(phase.fullName)} /> ) } return null }