import React from 'react' import { Box, Text } from 'ink' import { getAddress, type Address } from 'viem' import { Surface } from '../../../ui/Surface.js' import { Select, type SelectOption } from '../../../ui/Select.js' import { theme } from '../../../ui/theme.js' import type { AgentEnsRecords, AgentRecordDiff, } from '../../ens/agentRecords.js' import type { EnsValidation } from '../../ens/ensLookup.js' import type { EnsSetupBlockedPlan, EnsSetupPlan, } from '../../ens/ensAutomation.js' import { createErc8004PublicClient, type Erc8004RegistryConfig } from '../../registry/erc8004.js' import { displayCustodyMode, type CustodyMode, } from '../custody/state.js' import { ensValidationReasonText } from './state.js' import { shortAddress } from '../shared/model/format.js' import { FieldRow } from '../shared/components/FieldRow.js' import { abbreviateRecordValue, manualReasonTitle, modeSwitchHeading, } from './editCopy.js' import { EnsSetupRow, footerHint, renderRecordValue, } from './EnsEditShared.js' import type { EnsIssueValidation } from './types.js' type SimpleEnsIssueScreenProps = { fullName: string validation: EnsIssueValidation onCreate: () => void onCheckAgain: () => void onChange: () => void onBack: () => void } export const SimpleEnsIssueScreen: React.FC = ({ fullName, validation, onCreate, onCheckAgain, onChange, onBack, }) => { type Action = 'create' | 'check-again' | 'change' | 'back' const reason = ensValidationReasonText(validation.reason) const showDetail = validation.detail && validation.detail !== reason return ( The subdomain is not on Ethereum Mainnet yet. You can create it from here. {fullName}} /> {reason}} /> {showDetail ? {validation.detail} : null} options={[ { value: 'create', label: 'Create This ENS Name' }, { value: 'change', label: 'Pick A Different Name' }, { value: 'back', label: 'Back', role: 'utility' }, ]} hintLayout="inline" onSubmit={choice => { if (choice === 'create') return onCreate() if (choice === 'check-again') return onCheckAgain() if (choice === 'change') return onChange() return onBack() }} onCancel={onBack} /> ) } type EnsSetupReviewScreenProps = { setup: EnsSetupPlan currentEnsName: string currentMode: CustodyMode | undefined registry: Erc8004RegistryConfig onBegin: () => void onBack: () => void } export const EnsSetupReviewScreen: React.FC = ({ setup, currentEnsName, currentMode, registry, onBegin, onBack, }) => { type Action = 'begin' | 'back' const isSimple = setup.mode === 'simple' const createLabel = setup.registryAction === 'create-subdomain' ? 'Create Subdomain' : setup.registryAction === 'create-wrapped-subdomain' ? 'Create Wrapped Subdomain' : setup.registryAction === 'set-resolver' ? 'Set Resolver' : setup.registryAction === 'set-wrapped-resolver' ? 'Set Wrapped Resolver' : 'Subdomain Ready' const reusingExistingSubdomain = setup.registryAction === 'none' return ( {reusingExistingSubdomain ? ( Subdomain detected from a prior attempt, reusing. ) : null} {modeSwitchHeading(currentEnsName, currentMode, setup.fullName, setup.mode)} options={[ { value: 'begin', label: 'Continue Setup' }, { value: 'back', label: 'Back', role: 'utility' }, ]} hintLayout="inline" onSubmit={choice => { if (choice === 'begin') return onBegin() return onBack() }} onCancel={onBack} /> ) } type EnsSetupBlockedScreenProps = { fallback: EnsSetupBlockedPlan onCheckAgain: () => void onBack: () => void } export const EnsSetupBlockedScreen: React.FC = ({ fallback, onCheckAgain, onBack, }) => { type Action = 'check' | 'back' const isSimple = fallback.mode === 'simple' return ( {manualReasonTitle(fallback.reason)} {fallback.detail} {isSimple ? : (fallback.ownerAddress ? : null)} {fallback.nextRecords && Object.keys(fallback.nextRecords).length > 0 ? : null} {!isSimple ? ( <> To proceed: the owner wallet signs ENS records and must hold this token at setup time. Once setup is done you can deposit the token into the Vault while the ENS subdomain stays with the owner wallet. Operator wallets have no authority on this name; they only rotate the onchain ERC-8004 URI via the Vault. ) : null} options={[ { value: 'check', label: 'Check Again' }, { value: 'back', label: 'Back', role: 'utility' }, ]} hintLayout="inline" onSubmit={choice => { if (choice === 'check') return onCheckAgain() return onBack() }} onCancel={onBack} /> ) } type UnlinkEnsReviewScreenProps = { fullName: string currentMode: CustodyMode | undefined registryNetworkLabel: string recordsDiff: AgentRecordDiff[] onUnlink: () => void onBack: () => void } export const UnlinkEnsReviewScreen: React.FC = ({ fullName, currentMode, registryNetworkLabel, recordsDiff, onUnlink, onBack, }) => { type Action = 'unlink' | 'back' const changedDiffs = recordsDiff.filter(diff => diff.current.trim()) const recordsAlreadyEmpty = changedDiffs.length === 0 return ( {recordsAlreadyEmpty ? ( Removes the token URI link only. ) : ( Will be cleared: {changedDiffs.map(diff => ( {` ${abbreviateRecordValue(diff.key)}`} ))} )} options={[ { value: 'unlink', label: 'Unlink ENS' }, { value: 'back', label: 'Back', role: 'utility' }, ]} hintLayout="inline" onSubmit={choice => { if (choice === 'unlink') return onUnlink() return onBack() }} onCancel={onBack} /> ) } type ReviewScreenProps = { fullName: string ownerAddress: Address validation: EnsValidation recordsDiff: AgentRecordDiff[] nextRecords: AgentEnsRecords currentEnsName: string currentMode: CustodyMode | undefined registryNetworkLabel: string mode: 'simple' | 'advanced' onContinue: () => void onCheckAgain: () => void onChange: () => void onCreate?: () => void onBack: () => void } export const ReviewScreen: React.FC = ({ fullName, ownerAddress, validation, recordsDiff, nextRecords, currentEnsName, currentMode, registryNetworkLabel, mode, onContinue, onCheckAgain, onChange, onCreate, onBack, }) => { void ownerAddress void nextRecords type ReviewAction = 'continue' | 'create' | 'check-again' | 'change' | 'back' const changedDiffs = recordsDiff.filter(d => d.changed) const hasRecordChanges = changedDiffs.length > 0 const reviewSubtitle = 'Review ENS records before linking.' if (!validation.ok) { const reason = ensValidationReasonText(validation.reason) const showDetail = validation.detail && validation.detail !== reason return ( {fullName}} /> {reason}} /> {showDetail ? {validation.detail} : null} options={[ ...(onCreate ? [ { value: 'create' as ReviewAction, label: 'Create This ENS Name' }, ] : []), { value: 'check-again', label: 'Check Again' }, { value: 'change', label: 'Pick A Different Name' }, { value: 'back', label: 'Back', role: 'utility' }, ]} hintLayout="inline" onSubmit={choice => { if (choice === 'create' && onCreate) return onCreate() if (choice === 'check-again') return onCheckAgain() if (choice === 'change') return onChange() return onBack() }} onCancel={onBack} /> ) } const options: Array> = [ { value: 'continue', label: 'Continue Setup' }, { value: 'change', label: 'Pick A Different Name' }, { value: 'back', label: 'Back', role: 'utility' }, ] return ( {currentEnsName || currentMode ? ( ) : null} {recordsDiff.map(diff => ( {`- ${abbreviateRecordValue(diff.key)}`} {diff.changed ? : null} ))} {!hasRecordChanges ? All ENS records already match. Link this ENS name to the token URI. : null} options={options} hintLayout="inline" onSubmit={choice => { if (choice === 'continue') return onContinue() if (choice === 'change') return onChange() return onBack() }} onCancel={onBack} /> ) }