ethagent 1.1.2 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +124 -32
  3. package/package.json +8 -3
  4. package/src/app/FirstRun.tsx +190 -146
  5. package/src/app/FirstRunTimeline.tsx +47 -0
  6. package/src/app/input/AppInputProvider.tsx +1 -1
  7. package/src/app/keybindings/KeybindingProvider.tsx +1 -1
  8. package/src/chat/ChatBottomPane.tsx +0 -1
  9. package/src/chat/ChatInput.tsx +6 -6
  10. package/src/chat/ChatScreen.tsx +35 -15
  11. package/src/chat/ContextLimitView.tsx +4 -4
  12. package/src/chat/ContinuityEditReviewView.tsx +10 -22
  13. package/src/chat/CopyPicker.tsx +0 -1
  14. package/src/chat/MessageList.tsx +62 -45
  15. package/src/chat/PermissionPrompt.tsx +13 -9
  16. package/src/chat/PlanApprovalView.tsx +3 -3
  17. package/src/chat/ResumeView.tsx +1 -4
  18. package/src/chat/RewindView.tsx +2 -2
  19. package/src/chat/chatInputState.ts +1 -1
  20. package/src/chat/chatScreenUtils.ts +22 -11
  21. package/src/chat/chatSessionState.ts +2 -2
  22. package/src/chat/chatTurnOrchestrator.ts +16 -81
  23. package/src/chat/commands.ts +1 -1
  24. package/src/chat/textCursor.ts +1 -1
  25. package/src/chat/transcriptViewport.ts +2 -7
  26. package/src/cli/ResetConfirmView.tsx +1 -1
  27. package/src/cli/main.tsx +9 -3
  28. package/src/cli/preview.tsx +0 -5
  29. package/src/cli/updateNotice.ts +4 -2
  30. package/src/identity/continuity/editor.ts +7 -107
  31. package/src/identity/continuity/envelope.ts +1048 -40
  32. package/src/identity/continuity/history.ts +4 -4
  33. package/src/identity/continuity/localBackup.ts +249 -0
  34. package/src/identity/continuity/privateEdit/apply.ts +170 -0
  35. package/src/identity/continuity/privateEdit/diff.ts +82 -0
  36. package/src/identity/continuity/privateEdit/files.ts +23 -0
  37. package/src/identity/continuity/privateEdit/types.ts +28 -0
  38. package/src/identity/continuity/privateEdit.ts +10 -298
  39. package/src/identity/continuity/publicSkills.ts +8 -9
  40. package/src/identity/continuity/snapshots.ts +17 -6
  41. package/src/identity/continuity/storage/defaults.ts +111 -0
  42. package/src/identity/continuity/storage/files.ts +72 -0
  43. package/src/identity/continuity/storage/markdown.ts +81 -0
  44. package/src/identity/continuity/storage/paths.ts +24 -0
  45. package/src/identity/continuity/storage/scaffold.ts +124 -0
  46. package/src/identity/continuity/storage/status.ts +86 -0
  47. package/src/identity/continuity/storage/types.ts +27 -0
  48. package/src/identity/continuity/storage.ts +32 -507
  49. package/src/identity/continuity/zipWriter.ts +95 -0
  50. package/src/identity/crypto/backupEnvelope.ts +14 -247
  51. package/src/identity/crypto/eth.ts +7 -7
  52. package/src/identity/ens/agentRecords.ts +96 -0
  53. package/src/identity/ens/ensAutomation/contracts.ts +38 -0
  54. package/src/identity/ens/ensAutomation/delete.ts +80 -0
  55. package/src/identity/ens/ensAutomation/names.ts +14 -0
  56. package/src/identity/ens/ensAutomation/operators.ts +29 -0
  57. package/src/identity/ens/ensAutomation/read.ts +114 -0
  58. package/src/identity/ens/ensAutomation/root.ts +63 -0
  59. package/src/identity/ens/ensAutomation/setup.ts +284 -0
  60. package/src/identity/ens/ensAutomation/transactions.ts +107 -0
  61. package/src/identity/ens/ensAutomation/types.ts +126 -0
  62. package/src/identity/ens/ensAutomation.ts +29 -0
  63. package/src/identity/ens/ensLookup/client.ts +43 -0
  64. package/src/identity/ens/ensLookup/constants.ts +26 -0
  65. package/src/identity/ens/ensLookup/discovery.ts +70 -0
  66. package/src/identity/ens/ensLookup/names.ts +34 -0
  67. package/src/identity/ens/ensLookup/records.ts +45 -0
  68. package/src/identity/ens/ensLookup/resolve.ts +75 -0
  69. package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
  70. package/src/identity/ens/ensLookup/types.ts +38 -0
  71. package/src/identity/ens/ensLookup/validation.ts +72 -0
  72. package/src/identity/ens/ensLookup.ts +19 -0
  73. package/src/identity/ens/ensRegistration.ts +199 -0
  74. package/src/identity/ens/resolverDelegation.ts +48 -0
  75. package/src/identity/hub/IdentityHub.tsx +13 -817
  76. package/src/identity/hub/OperationalRoutes.tsx +370 -0
  77. package/src/identity/hub/Routes.tsx +361 -0
  78. package/src/identity/hub/advancedEnsValidation.ts +45 -0
  79. package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
  80. package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
  81. package/src/identity/hub/components/FlowTimeline.tsx +27 -0
  82. package/src/identity/hub/components/IdentitySummary.tsx +190 -0
  83. package/src/identity/hub/components/MenuScreen.tsx +237 -0
  84. package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
  85. package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
  86. package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
  87. package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
  88. package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
  89. package/src/identity/hub/effects/create.ts +310 -0
  90. package/src/identity/hub/effects/ens/flows.ts +218 -0
  91. package/src/identity/hub/effects/ens/index.ts +11 -0
  92. package/src/identity/hub/effects/ens/transactions.ts +239 -0
  93. package/src/identity/hub/effects/index.ts +74 -0
  94. package/src/identity/hub/effects/profile/profileState.ts +173 -0
  95. package/src/identity/hub/effects/publicProfile/index.ts +5 -0
  96. package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
  97. package/src/identity/hub/effects/rebackup/index.ts +7 -0
  98. package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
  99. package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
  100. package/src/identity/hub/effects/receipts.ts +46 -0
  101. package/src/identity/hub/effects/restore/apply.ts +112 -0
  102. package/src/identity/hub/effects/restore/auth.ts +159 -0
  103. package/src/identity/hub/effects/restore/discover.ts +86 -0
  104. package/src/identity/hub/effects/restore/envelopes.ts +21 -0
  105. package/src/identity/hub/effects/restore/fetch.ts +25 -0
  106. package/src/identity/hub/effects/restore/index.ts +22 -0
  107. package/src/identity/hub/effects/restore/recovery.ts +135 -0
  108. package/src/identity/hub/effects/restore/resolve.ts +102 -0
  109. package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
  110. package/src/identity/hub/effects/restore/shared.ts +91 -0
  111. package/src/identity/hub/effects/restoreAdmin.ts +93 -0
  112. package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
  113. package/src/identity/hub/effects/shared/snapshot.ts +336 -0
  114. package/src/identity/hub/effects/shared/sync.ts +190 -0
  115. package/src/identity/hub/effects/token-transfer/index.ts +6 -0
  116. package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
  117. package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
  118. package/src/identity/hub/effects/types.ts +53 -0
  119. package/src/identity/hub/effects/vault/preflight.ts +50 -0
  120. package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
  121. package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
  122. package/src/identity/hub/{screens → flows/continuity}/RecoveryConfirmScreen.tsx +28 -19
  123. package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
  124. package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
  125. package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
  126. package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
  127. package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
  128. package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
  129. package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
  130. package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
  131. package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
  132. package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
  133. package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
  134. package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
  135. package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
  136. package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
  137. package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
  138. package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
  139. package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
  140. package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
  141. package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
  142. package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
  143. package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
  144. package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
  145. package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
  146. package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
  147. package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +23 -44
  148. package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
  149. package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
  150. package/src/identity/hub/identityHubReducer.ts +164 -99
  151. package/src/identity/hub/model/continuity.ts +94 -0
  152. package/src/identity/hub/model/copy.ts +35 -0
  153. package/src/identity/hub/model/custody.ts +54 -0
  154. package/src/identity/hub/model/ens.ts +49 -0
  155. package/src/identity/hub/model/errors.ts +140 -0
  156. package/src/identity/hub/model/format.ts +15 -0
  157. package/src/identity/hub/model/identity.ts +94 -0
  158. package/src/identity/hub/model/network.ts +32 -0
  159. package/src/identity/hub/model/transfer.ts +57 -0
  160. package/src/identity/hub/operatorWallets.ts +131 -0
  161. package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
  162. package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
  163. package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
  164. package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
  165. package/src/identity/hub/reconciliation/index.ts +21 -0
  166. package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
  167. package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
  168. package/src/identity/hub/txGuard.ts +51 -0
  169. package/src/identity/hub/types.ts +17 -0
  170. package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
  171. package/src/identity/hub/useIdentityHubController.ts +396 -0
  172. package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
  173. package/src/identity/hub/utils.ts +79 -0
  174. package/src/identity/identityCompat.ts +34 -0
  175. package/src/identity/profile/agentIcon.ts +61 -0
  176. package/src/identity/profile/imagePicker.ts +12 -12
  177. package/src/identity/registry/erc8004/abi.ts +14 -0
  178. package/src/identity/registry/erc8004/chains.ts +150 -0
  179. package/src/identity/registry/erc8004/client.ts +11 -0
  180. package/src/identity/registry/erc8004/discovery.ts +511 -0
  181. package/src/identity/registry/erc8004/metadata.ts +335 -0
  182. package/src/identity/registry/erc8004/ownership.ts +121 -0
  183. package/src/identity/registry/erc8004/preflight.ts +123 -0
  184. package/src/identity/registry/erc8004/transactions.ts +77 -0
  185. package/src/identity/registry/erc8004/types.ts +88 -0
  186. package/src/identity/registry/erc8004/uri.ts +59 -0
  187. package/src/identity/registry/erc8004/utils.ts +58 -0
  188. package/src/identity/registry/erc8004.ts +53 -1106
  189. package/src/identity/registry/fieldParsers.ts +28 -0
  190. package/src/identity/registry/operatorVault/bytecode.ts +98 -0
  191. package/src/identity/registry/operatorVault/constants.ts +38 -0
  192. package/src/identity/registry/operatorVault/read.ts +246 -0
  193. package/src/identity/registry/operatorVault/transactions.ts +81 -0
  194. package/src/identity/registry/operatorVault.ts +44 -0
  195. package/src/identity/storage/ipfs.ts +26 -24
  196. package/src/identity/wallet/browserWallet/gas.ts +41 -0
  197. package/src/identity/wallet/browserWallet/html.ts +106 -0
  198. package/src/identity/wallet/browserWallet/http.ts +28 -0
  199. package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
  200. package/src/identity/wallet/browserWallet/requests.ts +191 -0
  201. package/src/identity/wallet/browserWallet/session.ts +325 -0
  202. package/src/identity/wallet/browserWallet/types.ts +192 -0
  203. package/src/identity/wallet/browserWallet/validation.ts +74 -0
  204. package/src/identity/wallet/browserWallet.ts +30 -393
  205. package/src/identity/wallet/page/constants.ts +5 -0
  206. package/src/identity/wallet/page/controller.ts +251 -0
  207. package/src/identity/wallet/page/copy.ts +340 -0
  208. package/src/identity/wallet/page/grainient.ts +278 -0
  209. package/src/identity/wallet/page/html.ts +28 -0
  210. package/src/identity/wallet/page/markup.ts +50 -0
  211. package/src/identity/wallet/page/state.ts +9 -0
  212. package/src/identity/wallet/page/styles/base.ts +259 -0
  213. package/src/identity/wallet/page/styles/components.ts +262 -0
  214. package/src/identity/wallet/page/styles/index.ts +5 -0
  215. package/src/identity/wallet/page/styles/responsive.ts +247 -0
  216. package/src/identity/wallet/page/types.ts +47 -0
  217. package/src/identity/wallet/page/view.ts +535 -0
  218. package/src/identity/wallet/page/walletProvider.ts +70 -0
  219. package/src/identity/wallet/page.tsx +38 -0
  220. package/src/identity/wallet/walletPurposeCompat.ts +27 -0
  221. package/src/mcp/manager.ts +0 -1
  222. package/src/models/ModelPicker.tsx +36 -30
  223. package/src/models/catalog.ts +5 -2
  224. package/src/models/huggingface.ts +9 -9
  225. package/src/models/llamacpp.ts +13 -13
  226. package/src/models/modelDisplay.ts +75 -0
  227. package/src/models/modelPickerOptions.ts +16 -3
  228. package/src/models/modelRecommendation.ts +0 -1
  229. package/src/providers/errors.ts +16 -0
  230. package/src/providers/gemini.ts +252 -39
  231. package/src/providers/registry.ts +2 -2
  232. package/src/providers/retry.ts +1 -1
  233. package/src/runtime/sessionMode.ts +1 -1
  234. package/src/runtime/systemPrompt.ts +2 -0
  235. package/src/runtime/toolExecution.ts +18 -22
  236. package/src/runtime/toolIntent.ts +0 -20
  237. package/src/runtime/turn.ts +0 -92
  238. package/src/storage/atomicWrite.ts +4 -1
  239. package/src/storage/config.ts +181 -5
  240. package/src/storage/identity.ts +9 -3
  241. package/src/storage/secrets.ts +2 -2
  242. package/src/tools/bashSafety.ts +8 -0
  243. package/src/tools/changeDirectoryTool.ts +1 -1
  244. package/src/tools/deleteFileTool.ts +4 -4
  245. package/src/tools/editTool.ts +4 -4
  246. package/src/tools/editUtils.ts +5 -5
  247. package/src/tools/privateContinuityEditTool.ts +4 -5
  248. package/src/tools/privateContinuityReadTool.ts +1 -2
  249. package/src/tools/registry.ts +30 -0
  250. package/src/tools/writeFileTool.ts +5 -5
  251. package/src/ui/BrandSplash.tsx +20 -85
  252. package/src/ui/ProgressBar.tsx +3 -5
  253. package/src/ui/Select.tsx +20 -8
  254. package/src/ui/Spinner.tsx +38 -3
  255. package/src/ui/Surface.tsx +2 -2
  256. package/src/ui/TextInput.tsx +63 -20
  257. package/src/ui/theme.ts +7 -34
  258. package/src/utils/openExternal.ts +21 -0
  259. package/src/utils/withRetry.ts +47 -3
  260. package/src/identity/hub/identityHubEffects.ts +0 -937
  261. package/src/identity/hub/identityHubModel.ts +0 -371
  262. package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -156
  263. package/src/identity/hub/screens/EditProfileFlow.tsx +0 -146
  264. package/src/identity/hub/screens/IdentitySummary.tsx +0 -106
  265. package/src/identity/hub/screens/MenuScreen.tsx +0 -117
  266. package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
  267. package/src/identity/wallet/wallet-page/wallet.html +0 -1202
  268. /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
@@ -0,0 +1,162 @@
1
+ import React from 'react'
2
+ import { Box, Text } from 'ink'
3
+ import type { Address } from 'viem'
4
+ import { Surface } from '../../../../ui/Surface.js'
5
+ import { TextInput } from '../../../../ui/TextInput.js'
6
+ import { theme } from '../../../../ui/theme.js'
7
+ import {
8
+ formatRecordValue,
9
+ type AgentEnsRecordState,
10
+ } from '../../../ens/agentRecords.js'
11
+ import {
12
+ isEthDomain,
13
+ sanitizeSubdomainPrefix,
14
+ } from '../../../ens/ensLookup.js'
15
+ import {
16
+ displayCustodyMode,
17
+ readIdentityStateString,
18
+ type CustodyMode,
19
+ } from '../../model/custody.js'
20
+ import { ensValidationReasonText } from '../../model/ens.js'
21
+ import { shortAddress } from '../../model/format.js'
22
+ import { readValidationFromState } from './ensEditCopy.js'
23
+ import type { EnsEditProps } from './ensEditTypes.js'
24
+
25
+ export const footerHint = (hint: string) => <Text color={theme.dim}>{hint}</Text>
26
+
27
+ export const renderRecordValue = (field: keyof AgentEnsRecordState, value: string) =>
28
+ value
29
+ ? <Text color={theme.accentPeriwinkle}>{formatRecordValue(field, value)}</Text>
30
+ : <Text color={theme.dim}>Unset</Text>
31
+
32
+ export function rootErrorMessage(
33
+ reason: 'invalid-root' | 'missing-token-id' | 'root-not-owned' | 'wrapped-parent' | 'root-owner-mismatch' | 'token-owner-mismatch' | 'token-owner-lookup-failed' | 'lookup-failed',
34
+ detail: string,
35
+ rootName: string,
36
+ ): string {
37
+ switch (reason) {
38
+ case 'invalid-root':
39
+ return 'Enter the parent .eth name, e.g. name.eth'
40
+ case 'missing-token-id':
41
+ return 'This identity is missing an ERC-8004 token id'
42
+ case 'root-not-owned':
43
+ return `${rootName} does not have an ENS manager on Ethereum mainnet. Switch wallets or pick a different parent name.`
44
+ case 'wrapped-parent':
45
+ return `${rootName} ENS NameWrapper ownership could not be verified: ${detail}`
46
+ case 'root-owner-mismatch':
47
+ return `Connected wallet does not manage ${rootName}. ${detail}`
48
+ case 'token-owner-mismatch':
49
+ return `Connected wallet manages ${rootName} but does not own this ERC-8004 token. ${detail}. Move the token to this wallet, then submit again.`
50
+ case 'token-owner-lookup-failed':
51
+ return `Could not verify ERC-8004 token owner: ${detail}`
52
+ case 'lookup-failed':
53
+ return `ENS lookup failed: ${detail}`
54
+ }
55
+ }
56
+
57
+ export const EnsSetupRow: React.FC<{ label: string; value: string; muted?: boolean }> = ({ label, value, muted }) => (
58
+ <Box flexDirection="row">
59
+ <Box width={16}>
60
+ <Text color={theme.dim}>{label}</Text>
61
+ </Box>
62
+ <Box flexShrink={1}>
63
+ <Text color={muted ? theme.dim : theme.text}>{value}</Text>
64
+ </Box>
65
+ </Box>
66
+ )
67
+
68
+ export const EnsStatusBanner: React.FC<{ identity: EnsEditProps['identity']; noRootEnsName?: boolean }> = ({ identity, noRootEnsName }) => {
69
+ const ensName = readIdentityStateString(identity.state, 'ensName')
70
+ if (!ensName) {
71
+ const status = noRootEnsName
72
+ ? 'Not Linked. This wallet does not own a root .eth ENS name.'
73
+ : 'Not Linked. Choose a setup below.'
74
+ return <Text color={theme.dim}>Status: <Text color={theme.dim}>{status}</Text></Text>
75
+ }
76
+ const validation = readValidationFromState(identity.state)
77
+ if (validation?.ok) {
78
+ return <Text color={theme.dim}>Status: <Text color={theme.accentPeriwinkle}>linked, {ensName}</Text></Text>
79
+ }
80
+ return (
81
+ <Text color={theme.dim}>
82
+ Status: <Text color={theme.accentError}>issue, {ensName}</Text>
83
+ <Text color={theme.dim}> ({ensValidationReasonText(validation?.reason)})</Text>
84
+ </Text>
85
+ )
86
+ }
87
+
88
+ type AssignEnsCurrentSetupProps = {
89
+ currentEnsName: string
90
+ currentMode: CustodyMode | undefined
91
+ ownerAddress: string
92
+ operatorAddress: string
93
+ tokenNetworkLabel: string
94
+ }
95
+
96
+ export const AssignEnsCurrentSetup: React.FC<AssignEnsCurrentSetupProps> = ({
97
+ currentEnsName,
98
+ currentMode,
99
+ ownerAddress,
100
+ operatorAddress,
101
+ tokenNetworkLabel,
102
+ }) => {
103
+ const modeLabel = displayCustodyMode(currentMode)
104
+ return (
105
+ <Box marginTop={1} flexDirection="column">
106
+ <Text color={theme.dim}>Current Setup</Text>
107
+ <EnsSetupRow label="ENS name" value={currentEnsName || 'None'} muted={!currentEnsName} />
108
+ <EnsSetupRow label="Custody" value={modeLabel} />
109
+ <EnsSetupRow label="ENS network" value="Ethereum Mainnet" />
110
+ <EnsSetupRow label="Token network" value={tokenNetworkLabel} />
111
+ {currentMode === 'advanced'
112
+ ? (
113
+ <>
114
+ {ownerAddress ? <EnsSetupRow label="Owner wallet" value={shortAddress(ownerAddress)} /> : null}
115
+ {operatorAddress ? <EnsSetupRow label="Operator wallet" value={shortAddress(operatorAddress)} /> : null}
116
+ </>
117
+ )
118
+ : null}
119
+ </Box>
120
+ )
121
+ }
122
+
123
+ type SubdomainEntryProps = {
124
+ parent: string
125
+ ownerAddress: Address
126
+ suggestion: string
127
+ error?: string
128
+ onConfirm: (fullName: string) => void
129
+ onBack: () => void
130
+ }
131
+
132
+ export const SubdomainEntry: React.FC<SubdomainEntryProps> = ({ parent, ownerAddress, suggestion, error, onConfirm, onBack }) => (
133
+ <Surface
134
+ title={`Subdomain of ${parent}`}
135
+ footer={footerHint('enter continues · esc back')}
136
+ >
137
+ <Text color={theme.dim}>Type the subdomain name that goes before .{parent}.</Text>
138
+ <Text color={theme.dim}>Use lowercase letters, digits, and hyphens.</Text>
139
+ {error ? <Text color={theme.accentError}>{error}</Text> : null}
140
+ <TextInput
141
+ key={`edit-ens-subdomain-${parent}`}
142
+ initialValue={suggestion}
143
+ placeholder="subdomain name"
144
+ validate={value => {
145
+ const v = sanitizeSubdomainPrefix(value)
146
+ if (!v) return 'Subdomain name cannot be empty'
147
+ if (value.includes('.')) return 'Enter only the subdomain name, not the full ENS name'
148
+ if (!isEthDomain(`${v}.${parent}`)) return 'Invalid characters in subdomain name'
149
+ return null
150
+ }}
151
+ onSubmit={value => {
152
+ const prefix = sanitizeSubdomainPrefix(value)
153
+ if (!prefix) return
154
+ onConfirm(`${prefix}.${parent}`)
155
+ }}
156
+ onCancel={onBack}
157
+ />
158
+ <Box marginTop={1}>
159
+ <Text color={theme.dim}>Wallet: {shortAddress(ownerAddress)}</Text>
160
+ </Box>
161
+ </Surface>
162
+ )
@@ -0,0 +1,518 @@
1
+ import React from 'react'
2
+ import { Box, Text } from 'ink'
3
+ import { formatEther, type Address } from 'viem'
4
+ import { Surface } from '../../../../ui/Surface.js'
5
+ import { Select, type SelectOption } from '../../../../ui/Select.js'
6
+ import { TextInput } from '../../../../ui/TextInput.js'
7
+ import { Spinner } from '../../../../ui/Spinner.js'
8
+ import { theme } from '../../../../ui/theme.js'
9
+ import {
10
+ createMainnetClient,
11
+ isEthDomain,
12
+ normalizeEthDomain,
13
+ } from '../../../ens/ensLookup.js'
14
+ import {
15
+ generateRegistrationSecret,
16
+ ONE_YEAR_SECONDS,
17
+ readNameAvailable,
18
+ readRentPrice,
19
+ validateRegistrableName,
20
+ } from '../../../ens/ensRegistration.js'
21
+ import { isRootEthName } from '../../../ens/ensAutomation.js'
22
+ import type { Erc8004RegistryConfig } from '../../../registry/erc8004.js'
23
+ import type { BrowserWalletReady } from '../../../wallet/browserWallet.js'
24
+ import {
25
+ readIdentityStateString,
26
+ type CustodyMode,
27
+ } from '../../model/custody.js'
28
+ import { shortAddress } from '../../model/format.js'
29
+ import {
30
+ recordsDiffHasChanges,
31
+ type EnsLinkOptions,
32
+ } from './ensEditCopy.js'
33
+ import {
34
+ EnsSetupRow,
35
+ EnsStatusBanner,
36
+ footerHint,
37
+ SubdomainEntry,
38
+ } from './EnsEditShared.js'
39
+ import {
40
+ EnsSetupBlockedScreen,
41
+ EnsSetupReviewScreen,
42
+ ReviewScreen,
43
+ SimpleEnsIssueScreen,
44
+ } from './EnsEditReviewScreens.js'
45
+ import {
46
+ EscCancel,
47
+ RegisterRootCommitRunner,
48
+ RegisterRootTxRunner,
49
+ RegisterRootWaitScreen,
50
+ } from './EnsEditRunners.js'
51
+ import type {
52
+ DiscoveryState,
53
+ EnsEditProps,
54
+ EnsPhase,
55
+ } from './ensEditTypes.js'
56
+
57
+ type SimpleScreenProps = {
58
+ phase: EnsPhase
59
+ discovery: DiscoveryState
60
+ ownerAddress: Address
61
+ discoveryStartedAt: number
62
+ validationError: string | null
63
+ currentEnsName: string
64
+ savedCustodyMode: CustodyMode | undefined
65
+ registryNetworkLabel: string
66
+ registry: Erc8004RegistryConfig
67
+ agentNameSuggestion: string
68
+ operatorWalletSession: BrowserWalletReady | null
69
+ setOperatorWalletSession: (session: BrowserWalletReady | null) => void
70
+ setPhase: (phase: EnsPhase) => void
71
+ cancelDiscoveryToModeSelect: () => void
72
+ runDiscovery: () => void
73
+ runValidation: (fullName: string, mode: 'simple' | 'advanced', phaseOwnerAddress?: Address, operatorWallet?: Address) => Promise<void>
74
+ backToSimpleSubdomain: (fullName: string) => void
75
+ runSimpleCreatePreflight: (fullName: string) => void
76
+ onEnsSetup: EnsEditProps['onEnsSetup']
77
+ onEnsLink: EnsEditProps['onEnsLink']
78
+ onEnsRecordsUpdate: EnsEditProps['onEnsRecordsUpdate']
79
+ identity: EnsEditProps['identity']
80
+ }
81
+
82
+ export function renderSimpleEnsPhase({
83
+ phase,
84
+ discovery,
85
+ ownerAddress,
86
+ discoveryStartedAt,
87
+ validationError,
88
+ currentEnsName,
89
+ savedCustodyMode,
90
+ registryNetworkLabel,
91
+ registry,
92
+ agentNameSuggestion,
93
+ operatorWalletSession,
94
+ setOperatorWalletSession,
95
+ setPhase,
96
+ cancelDiscoveryToModeSelect,
97
+ runDiscovery,
98
+ runValidation,
99
+ backToSimpleSubdomain,
100
+ runSimpleCreatePreflight,
101
+ onEnsSetup,
102
+ onEnsLink,
103
+ onEnsRecordsUpdate,
104
+ identity,
105
+ }: SimpleScreenProps): React.ReactNode | null {
106
+ const statusBanner = (
107
+ <EnsStatusBanner identity={identity} />
108
+ )
109
+
110
+ if (phase.kind === 'discovering' || discovery.status === 'loading') {
111
+ return (
112
+ <Surface
113
+ title="Assign ENS Name"
114
+ subtitle="Reading your primary ENS name from Ethereum mainnet."
115
+ footer={footerHint('esc cancels')}
116
+ >
117
+ <Box marginTop={1}>
118
+ <Spinner
119
+ label={`Looking up root ENS names for ${shortAddress(ownerAddress)}...`}
120
+ startedAt={discoveryStartedAt}
121
+ />
122
+ </Box>
123
+ <EscCancel onCancel={cancelDiscoveryToModeSelect} />
124
+ </Surface>
125
+ )
126
+ }
127
+
128
+ if (phase.kind === 'pick-parent') {
129
+ type DomainAction = `pick:${string}` | 'register-root' | 'manual' | 'retry' | 'back'
130
+ const ownedNames = discovery.status === 'ok' || discovery.status === 'error' ? discovery.names : []
131
+ const errorMessage = discovery.status === 'error' ? 'Root ENS Lookup Failed' : null
132
+ const warningMessage = discovery.status === 'ok' ? discovery.warning : null
133
+
134
+ const noOwnedNames = discovery.status === 'ok' && ownedNames.length === 0
135
+
136
+ const options: Array<SelectOption<DomainAction>> = [
137
+ ...(ownedNames.length > 0
138
+ ? [
139
+ { value: 'pick:section' as DomainAction, role: 'section' as const, label: 'Your ENS Names' },
140
+ ...ownedNames.map(name => ({
141
+ value: `pick:${name}` as DomainAction,
142
+ label: name,
143
+ hint: `Next, choose the subdomain under ${name}`,
144
+ })),
145
+ ]
146
+ : []),
147
+ { value: 'register-root' as DomainAction, role: 'section' as const, label: 'Get a New One' },
148
+ { value: 'register-root' as DomainAction, label: 'Register New ENS Name', hint: 'Register an ENS name through ENS to use as a root' },
149
+ ...(noOwnedNames
150
+ ? [{ value: 'retry' as DomainAction, label: 'Scan Again', hint: 'Retry the search after registering' }]
151
+ : []),
152
+ ...(discovery.status === 'error'
153
+ ? [
154
+ { value: 'retry' as DomainAction, label: errorMessage ? 'Try Again' : 'Retry Lookup', hint: 'Retry root ENS name search' },
155
+ { value: 'manual' as DomainAction, label: 'Enter ENS Name Manually', hint: 'Lookup failed; type a root .eth name you own' },
156
+ ]
157
+ : []),
158
+ { value: 'back', role: 'section', label: 'Navigation' },
159
+ { value: 'back', label: 'Back', hint: 'Return to setup type', role: 'utility' },
160
+ ]
161
+
162
+ return (
163
+ <Surface
164
+ title="Assign ENS Name"
165
+ subtitle="Choose a root .eth name, then create a dedicated agent subdomain under it."
166
+ footer={footerHint('enter select · esc back')}
167
+ >
168
+ <EnsStatusBanner identity={identity} noRootEnsName={noOwnedNames} />
169
+ {validationError ? <Text color={theme.accentError}>{validationError}</Text> : null}
170
+ {errorMessage ? <Text color={theme.accentError}>{errorMessage}: {discovery.status === 'error' ? discovery.message : ''}</Text> : null}
171
+ {warningMessage ? <Text color={theme.accentPeriwinkle}>{warningMessage}</Text> : null}
172
+ <Box marginTop={1}>
173
+ <Select<DomainAction>
174
+ options={options}
175
+ hintLayout="inline"
176
+ onSubmit={choice => {
177
+ if (choice === 'back') return setPhase({ kind: 'mode-select' })
178
+ if (choice === 'manual') { setPhase({ kind: 'manual-parent' }); return }
179
+ if (choice === 'register-root') { setPhase({ kind: 'register-root-input' }); return }
180
+ if (choice === 'retry') {
181
+ runDiscovery()
182
+ return
183
+ }
184
+ if (choice.startsWith('pick:')) {
185
+ const name = choice.slice('pick:'.length)
186
+ if (name) setPhase({ kind: 'pick-subdomain', parent: name })
187
+ }
188
+ }}
189
+ onCancel={() => setPhase({ kind: 'mode-select' })}
190
+ />
191
+ </Box>
192
+ </Surface>
193
+ )
194
+ }
195
+
196
+ if (phase.kind === 'register-root-input') {
197
+ return (
198
+ <Surface
199
+ title="Register an ENS Name"
200
+ subtitle="Pick a name to register on Ethereum mainnet for 1 year. The connected wallet pays and becomes the owner."
201
+ footer={footerHint('enter continues · esc back')}
202
+ >
203
+ {phase.error ? <Text color={theme.accentError}>{phase.error}</Text> : null}
204
+ <Box marginTop={1}>
205
+ <TextInput
206
+ placeholder="myname"
207
+ validate={value => {
208
+ const result = validateRegistrableName(value)
209
+ return result.ok ? null : result.detail
210
+ }}
211
+ onSubmit={value => {
212
+ const result = validateRegistrableName(value)
213
+ if (!result.ok) {
214
+ setPhase({ kind: 'register-root-input', error: result.detail })
215
+ return
216
+ }
217
+ const secret = generateRegistrationSecret()
218
+ setPhase({ kind: 'register-root-pricing', label: result.label, secret, busy: true })
219
+ const client = createMainnetClient()
220
+ Promise.all([
221
+ readNameAvailable(client, result.label),
222
+ readRentPrice(client, result.label, BigInt(ONE_YEAR_SECONDS)),
223
+ ])
224
+ .then(([available, price]) => {
225
+ if (!available) {
226
+ setPhase({ kind: 'register-root-input', error: `${result.label}.eth is already registered. Pick a different name.` })
227
+ return
228
+ }
229
+ setPhase({ kind: 'register-root-pricing', label: result.label, secret, busy: false, price })
230
+ })
231
+ .catch((err: unknown) => {
232
+ setPhase({ kind: 'register-root-input', error: err instanceof Error ? err.message : String(err) })
233
+ })
234
+ }}
235
+ onCancel={() => setPhase({ kind: 'pick-parent' })}
236
+ />
237
+ </Box>
238
+ </Surface>
239
+ )
240
+ }
241
+
242
+ if (phase.kind === 'register-root-pricing') {
243
+ const fullName = `${phase.label}.eth`
244
+ if (phase.busy || !phase.price) {
245
+ return (
246
+ <Surface
247
+ title="Register an ENS Name"
248
+ subtitle={`Checking availability and price for ${fullName}...`}
249
+ footer={footerHint('esc back')}
250
+ >
251
+ <Box marginTop={1}><Text color={theme.textSubtle}>Reading from Ethereum mainnet...</Text></Box>
252
+ </Surface>
253
+ )
254
+ }
255
+ const totalEth = formatEther(phase.price.total)
256
+ const baseEth = formatEther(phase.price.base)
257
+ const premiumEth = formatEther(phase.price.premium)
258
+ return (
259
+ <Surface
260
+ title="Register an ENS Name"
261
+ subtitle={`${fullName} is available. Review the cost and continue with two transactions.`}
262
+ footer={footerHint('enter select · esc back')}
263
+ >
264
+ <Box flexDirection="column">
265
+ <EnsSetupRow label="Name" value={fullName} />
266
+ <EnsSetupRow label="Owner" value={shortAddress(ownerAddress)} />
267
+ <EnsSetupRow label="Duration" value="1 year" />
268
+ <EnsSetupRow label="Rent" value={`${baseEth} ETH`} />
269
+ {phase.price.premium > 0n ? <EnsSetupRow label="Premium" value={`${premiumEth} ETH`} /> : null}
270
+ <EnsSetupRow label="Total" value={`${totalEth} ETH`} />
271
+ <Box marginTop={1}><Text color={theme.dim}>Two transactions: a commit, a 60-second wait, then the registration. Keep this tab open through both.</Text></Box>
272
+ <Box marginTop={1}><Text color={theme.dim}>Manage renewals on https://app.ens.domains/ before the year is up.</Text></Box>
273
+ </Box>
274
+ <Box marginTop={1}>
275
+ <Select<'commit' | 'pick-different' | 'back'>
276
+ options={[
277
+ { value: 'commit', role: 'section', label: 'Continue' },
278
+ { value: 'commit', label: 'Continue to Commit', hint: 'Sign the first transaction' },
279
+ { value: 'pick-different', label: 'Pick a Different Name', hint: 'Return to name input' },
280
+ { value: 'back', role: 'section', label: 'Navigation' },
281
+ { value: 'back', label: 'Back', hint: 'Return to root selection', role: 'utility' },
282
+ ]}
283
+ hintLayout="inline"
284
+ onSubmit={choice => {
285
+ if (choice === 'commit') {
286
+ setPhase({ kind: 'register-root-commit-tx', label: phase.label, secret: phase.secret, price: phase.price! })
287
+ return
288
+ }
289
+ if (choice === 'pick-different') return setPhase({ kind: 'register-root-input' })
290
+ setPhase({ kind: 'pick-parent' })
291
+ }}
292
+ onCancel={() => setPhase({ kind: 'pick-parent' })}
293
+ />
294
+ </Box>
295
+ </Surface>
296
+ )
297
+ }
298
+
299
+ if (phase.kind === 'register-root-commit-tx') {
300
+ return (
301
+ <RegisterRootCommitRunner
302
+ phase={phase}
303
+ ownerAddress={ownerAddress}
304
+ walletSession={operatorWalletSession}
305
+ onWalletReady={setOperatorWalletSession}
306
+ onCommitted={() => setPhase({ kind: 'register-root-wait', label: phase.label, secret: phase.secret, price: phase.price, commitMinedAt: Date.now() })}
307
+ onError={msg => setPhase({ kind: 'register-root-input', error: msg })}
308
+ />
309
+ )
310
+ }
311
+
312
+ if (phase.kind === 'register-root-wait') {
313
+ return (
314
+ <RegisterRootWaitScreen
315
+ phase={phase}
316
+ onReady={() => setPhase({ kind: 'register-root-tx', label: phase.label, secret: phase.secret, price: phase.price })}
317
+ onCancel={() => setPhase({ kind: 'register-root-input', error: 'Commit cancelled. Names must be re-committed; secrets do not carry over.' })}
318
+ />
319
+ )
320
+ }
321
+
322
+ if (phase.kind === 'register-root-tx') {
323
+ return (
324
+ <RegisterRootTxRunner
325
+ phase={phase}
326
+ ownerAddress={ownerAddress}
327
+ walletSession={operatorWalletSession}
328
+ onWalletReady={setOperatorWalletSession}
329
+ onRegistered={() => setPhase({ kind: 'register-root-done', fullName: `${phase.label}.eth` })}
330
+ onError={msg => setPhase({ kind: 'register-root-input', error: msg })}
331
+ />
332
+ )
333
+ }
334
+
335
+ if (phase.kind === 'register-root-done') {
336
+ return (
337
+ <Surface
338
+ title="ENS Name Registered"
339
+ subtitle={`${phase.fullName} is now owned by ${shortAddress(ownerAddress)}.`}
340
+ footer={footerHint('enter select · esc back')}
341
+ >
342
+ <Box flexDirection="column">
343
+ <Text color={theme.text}>Manage renewals and other settings on https://app.ens.domains/ before the year is up.</Text>
344
+ <Text color={theme.dim}>The name will appear in the picker on the next scan.</Text>
345
+ </Box>
346
+ <Box marginTop={1}>
347
+ <Select<'scan' | 'back'>
348
+ options={[
349
+ { value: 'scan', role: 'section', label: 'Next' },
350
+ { value: 'scan', label: 'Scan Again', hint: 'Re-run root ENS name discovery' },
351
+ { value: 'back', role: 'section', label: 'Navigation' },
352
+ { value: 'back', label: 'Back to ENS', hint: 'Return to ENS menu', role: 'utility' },
353
+ ]}
354
+ hintLayout="inline"
355
+ onSubmit={choice => {
356
+ if (choice === 'scan') {
357
+ runDiscovery()
358
+ return
359
+ }
360
+ setPhase({ kind: 'mode-select' })
361
+ }}
362
+ onCancel={() => setPhase({ kind: 'mode-select' })}
363
+ />
364
+ </Box>
365
+ </Surface>
366
+ )
367
+ }
368
+
369
+ if (phase.kind === 'manual-parent') {
370
+ return (
371
+ <Surface
372
+ title="Your Root .eth Name"
373
+ subtitle="Type the root .eth name you own. Your agent becomes a subdomain of it."
374
+ footer={footerHint('enter continues · esc back')}
375
+ >
376
+ {statusBanner}
377
+ <Box marginTop={1}>
378
+ <Text color={theme.dim}>Only root .eth names on Ethereum mainnet are supported.</Text>
379
+ </Box>
380
+ <TextInput
381
+ key="edit-ens-parent-manual"
382
+ placeholder="e.g. name.eth"
383
+ validate={value => {
384
+ const v = normalizeEthDomain(value)
385
+ if (!v) return 'Enter a .eth name'
386
+ if (!isEthDomain(v)) return 'Must be a valid .eth name'
387
+ if (!isRootEthName(v)) return 'Enter a root .eth name, e.g. name.eth'
388
+ return null
389
+ }}
390
+ onSubmit={value => setPhase({ kind: 'pick-subdomain', parent: normalizeEthDomain(value) })}
391
+ onCancel={() => setPhase({ kind: 'pick-parent' })}
392
+ />
393
+ </Surface>
394
+ )
395
+ }
396
+
397
+ if (phase.kind === 'pick-subdomain') {
398
+ return (
399
+ <SubdomainEntry
400
+ parent={phase.parent}
401
+ ownerAddress={ownerAddress}
402
+ suggestion={phase.label || agentNameSuggestion}
403
+ error={phase.error}
404
+ onConfirm={fullName => { void runValidation(fullName, 'simple') }}
405
+ onBack={() => setPhase({ kind: 'pick-parent' })}
406
+ />
407
+ )
408
+ }
409
+
410
+ if (phase.kind === 'validating') {
411
+ return (
412
+ <Surface
413
+ title="Check ENS Name"
414
+ subtitle={phase.mode === 'simple' ? undefined : `Verifying ${phase.fullName}`}
415
+ footer={footerHint('esc cancels')}
416
+ >
417
+ <Box marginTop={1}>
418
+ <Spinner label="Looking up resolver and address record..." />
419
+ </Box>
420
+ <EscCancel onCancel={() => backToSimpleSubdomain(phase.fullName)} />
421
+ </Surface>
422
+ )
423
+ }
424
+
425
+ if (phase.kind === 'simple-name-missing') {
426
+ return (
427
+ <SimpleEnsIssueScreen
428
+ fullName={phase.fullName}
429
+ validation={phase.validation}
430
+ onCreate={() => runSimpleCreatePreflight(phase.fullName)}
431
+ onCheckAgain={() => { void runValidation(phase.fullName, 'simple') }}
432
+ onChange={() => backToSimpleSubdomain(phase.fullName)}
433
+ onBack={() => backToSimpleSubdomain(phase.fullName)}
434
+ />
435
+ )
436
+ }
437
+
438
+ if (phase.kind === 'simple-create-preflight') {
439
+ return (
440
+ <Surface
441
+ title="Prepare ENS Name"
442
+ footer={footerHint('esc back')}
443
+ >
444
+ <Box flexDirection="column">
445
+ <Text color={theme.dim}>Name: <Text color={theme.text}>{phase.fullName}</Text></Text>
446
+ <Text color={theme.dim}>Connected wallet will create the subdomain and set the required records if checks pass.</Text>
447
+ </Box>
448
+ <Box marginTop={1}>
449
+ <Spinner label="checking parent ownership and record changes..." />
450
+ </Box>
451
+ <EscCancel onCancel={() => backToSimpleSubdomain(phase.fullName)} />
452
+ </Surface>
453
+ )
454
+ }
455
+
456
+ if (phase.kind === 'simple-create-review') {
457
+ return (
458
+ <EnsSetupReviewScreen
459
+ setup={phase.setup}
460
+ currentEnsName={currentEnsName}
461
+ currentMode={savedCustodyMode}
462
+ registry={registry}
463
+ onBegin={() => {
464
+ if (phase.setup.txCount > 0) {
465
+ onEnsSetup(phase.setup)
466
+ return
467
+ }
468
+ onEnsLink(phase.setup.fullName, { mode: 'simple' })
469
+ }}
470
+ onBack={() => backToSimpleSubdomain(phase.setup.fullName)}
471
+ />
472
+ )
473
+ }
474
+
475
+ if (phase.kind === 'simple-create-blocked') {
476
+ return (
477
+ <EnsSetupBlockedScreen
478
+ fallback={phase.fallback}
479
+ onCheckAgain={() => runSimpleCreatePreflight(phase.fallback.fullName)}
480
+ onBack={() => backToSimpleSubdomain(phase.fallback.fullName)}
481
+ />
482
+ )
483
+ }
484
+
485
+ if (phase.kind === 'review') {
486
+ const linkOptions: EnsLinkOptions = phase.mode === 'advanced' && phase.ownerAddress && phase.operatorWallet
487
+ ? { mode: 'advanced', ownerAddress: phase.ownerAddress, operatorWallet: phase.operatorWallet }
488
+ : { mode: 'simple' }
489
+ return (
490
+ <ReviewScreen
491
+ fullName={phase.fullName}
492
+ ownerAddress={ownerAddress}
493
+ validation={phase.validation}
494
+ recordsDiff={phase.recordsDiff}
495
+ nextRecords={phase.nextRecords}
496
+ currentEnsName={currentEnsName}
497
+ currentMode={savedCustodyMode}
498
+ registryNetworkLabel={registryNetworkLabel}
499
+ mode={phase.mode}
500
+ onContinue={() => {
501
+ if (recordsDiffHasChanges(phase.recordsDiff)) {
502
+ onEnsRecordsUpdate(phase.fullName, phase.nextRecords, linkOptions, false, phase.currentRecords)
503
+ return
504
+ }
505
+ onEnsLink(phase.fullName, linkOptions)
506
+ }}
507
+ onCheckAgain={() => { void runValidation(phase.fullName, phase.mode, phase.ownerAddress, phase.operatorWallet) }}
508
+ onChange={() => setPhase(phase.mode === 'advanced' ? { kind: 'advanced-transfer-check' } : { kind: 'pick-parent' })}
509
+ onCreate={phase.mode === 'simple' && !phase.validation.ok && phase.validation.reason === 'no-owner'
510
+ ? () => runSimpleCreatePreflight(phase.fullName)
511
+ : undefined}
512
+ onBack={() => backToSimpleSubdomain(phase.fullName)}
513
+ />
514
+ )
515
+ }
516
+
517
+ return null
518
+ }