ethagent 2.1.1 → 2.2.0

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 (60) hide show
  1. package/package.json +1 -1
  2. package/src/auth/openaiOAuth/credentials.ts +47 -0
  3. package/src/auth/openaiOAuth/crypto.ts +23 -0
  4. package/src/auth/openaiOAuth/index.ts +238 -0
  5. package/src/auth/openaiOAuth/landingPage.ts +125 -0
  6. package/src/auth/openaiOAuth/listener.ts +151 -0
  7. package/src/auth/openaiOAuth/refresh.ts +70 -0
  8. package/src/auth/openaiOAuth/shared.ts +115 -0
  9. package/src/chat/chatSessionState.ts +2 -1
  10. package/src/chat/commands.ts +2 -1
  11. package/src/identity/ens/agentRecords.ts +5 -19
  12. package/src/identity/ens/ensAutomation/setup.ts +0 -1
  13. package/src/identity/ens/ensAutomation/types.ts +0 -1
  14. package/src/identity/hub/OperationalRoutes.tsx +2 -11
  15. package/src/identity/hub/components/IdentitySummary.tsx +8 -3
  16. package/src/identity/hub/components/MenuScreen.tsx +1 -2
  17. package/src/identity/hub/components/menuFlagsFromReconciliation.ts +1 -3
  18. package/src/identity/hub/effects/ens/transactions.ts +15 -15
  19. package/src/identity/hub/effects/index.ts +0 -1
  20. package/src/identity/hub/effects/profile/profileState.ts +12 -4
  21. package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +37 -159
  22. package/src/identity/hub/effects/rebackup/runRebackup.ts +2 -2
  23. package/src/identity/hub/effects/restoreAdmin.ts +2 -61
  24. package/src/identity/hub/effects/shared/sync.ts +3 -44
  25. package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +1 -39
  26. package/src/identity/hub/flows/custody/custodyFlowActions.ts +5 -3
  27. package/src/identity/hub/flows/custody/custodyFlowTypes.ts +1 -1
  28. package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +80 -175
  29. package/src/identity/hub/flows/ens/EnsEditFlow.tsx +20 -75
  30. package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +16 -56
  31. package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +0 -18
  32. package/src/identity/hub/flows/ens/EnsEditRunners.tsx +0 -136
  33. package/src/identity/hub/flows/ens/EnsEditShared.tsx +5 -4
  34. package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +56 -205
  35. package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +7 -0
  36. package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +0 -31
  37. package/src/identity/hub/flows/ens/ensEditCopy.ts +1 -1
  38. package/src/identity/hub/flows/ens/ensEditTypes.ts +6 -20
  39. package/src/identity/hub/flows/profile/EditProfileFlow.tsx +7 -0
  40. package/src/identity/hub/flows/restore/RestoreFlow.tsx +5 -5
  41. package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +0 -1
  42. package/src/identity/hub/reconciliation/agentReconciliation/run.ts +1 -34
  43. package/src/identity/hub/reconciliation/agentReconciliation/types.ts +0 -4
  44. package/src/identity/hub/reconciliation/index.ts +0 -7
  45. package/src/identity/hub/reconciliation/walletSetup.ts +1 -194
  46. package/src/identity/wallet/browserWallet/types.ts +0 -5
  47. package/src/identity/wallet/page/copy.ts +1 -31
  48. package/src/identity/wallet/walletPurposeCompat.ts +0 -2
  49. package/src/models/ModelPicker.tsx +246 -8
  50. package/src/models/catalog.ts +28 -1
  51. package/src/models/modelPickerOptions.ts +15 -1
  52. package/src/providers/openai-responses-format.ts +156 -0
  53. package/src/providers/openai-responses.ts +276 -0
  54. package/src/providers/registry.ts +85 -8
  55. package/src/runtime/systemPrompt.ts +1 -1
  56. package/src/runtime/turn.ts +0 -1
  57. package/src/storage/secrets.ts +4 -1
  58. package/src/tools/privateContinuityEditTool.ts +6 -0
  59. package/src/utils/openExternal.ts +20 -10
  60. package/src/identity/ens/ensRegistration.ts +0 -199
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import { Box, Text } from 'ink'
3
- import { getAddress, type Address } from 'viem'
3
+ import { type Address } from 'viem'
4
4
  import { Surface } from '../../../../ui/Surface.js'
5
5
  import { Select } from '../../../../ui/Select.js'
6
6
  import { TextInput } from '../../../../ui/TextInput.js'
@@ -12,17 +12,14 @@ import {
12
12
  } from '../../../ens/ensLookup.js'
13
13
  import { isRootEthName } from '../../../ens/ensAutomation.js'
14
14
  import type { Erc8004RegistryConfig } from '../../../registry/erc8004.js'
15
- import type { BrowserWalletReady } from '../../../wallet/browserWallet.js'
16
15
  import {
17
16
  type CustodyMode,
18
17
  } from '../../model/custody.js'
19
18
  import { shortAddress } from '../../model/format.js'
20
- import { WalletApprovalScreen } from '../../components/WalletApprovalScreen.js'
21
- import { advancedSubdomainStatusText } from './ensEditCopy.js'
22
19
  import {
23
- EnsSetupRow,
24
20
  footerHint,
25
21
  } from './EnsEditShared.js'
22
+ import { IdentitySummary } from '../../components/IdentitySummary.js'
26
23
  import {
27
24
  EnsSetupBlockedScreen,
28
25
  EnsSetupReviewScreen,
@@ -32,109 +29,112 @@ import type {
32
29
  EnsEditProps,
33
30
  EnsPhase,
34
31
  } from './ensEditTypes.js'
32
+ import type { AgentReconciliation } from '../../reconciliation/index.js'
35
33
 
36
34
  type AdvancedScreenProps = {
37
35
  phase: EnsPhase
36
+ identity: EnsEditProps['identity']
38
37
  ownerAddress: Address
39
38
  agentId: EnsEditProps['identity']['agentId']
40
- savedOwnerAddress: string
41
- savedOperator: string
42
- savedRootName: string
39
+ reconciliation: AgentReconciliation
43
40
  savedSubdomainLabel: string
44
41
  agentNameSuggestion: string
45
42
  currentEnsName: string
46
43
  savedCustodyMode: CustodyMode | undefined
47
44
  registry: Erc8004RegistryConfig
48
- operatorWalletSession: BrowserWalletReady | null
49
45
  setPhase: (phase: EnsPhase) => void
50
- connectOperatorWallet: (rootName: string, label: string) => void
51
- runAdvancedRootCheck: (rootName: string) => void
46
+ runDiscovery: (mode?: 'simple' | 'advanced') => void
52
47
  runAdvancedSubdomainCheck: (rootName: string, label: string) => void
53
- runAdvancedPreflight: (rootName: string, label: string, operatorWallet: Address) => void
54
48
  onEnsSetup: EnsEditProps['onEnsSetup']
55
49
  onEnsLink: EnsEditProps['onEnsLink']
50
+ onWithdrawToken: () => void
56
51
  }
57
52
 
58
53
  export function renderAdvancedEnsPhase({
59
54
  phase,
55
+ identity,
60
56
  ownerAddress,
61
57
  agentId,
62
- savedOwnerAddress,
63
- savedOperator,
64
- savedRootName,
58
+ reconciliation,
65
59
  savedSubdomainLabel,
66
60
  agentNameSuggestion,
67
61
  currentEnsName,
68
62
  savedCustodyMode,
69
63
  registry,
70
- operatorWalletSession,
71
64
  setPhase,
72
- connectOperatorWallet,
73
- runAdvancedRootCheck,
65
+ runDiscovery,
74
66
  runAdvancedSubdomainCheck,
75
- runAdvancedPreflight,
76
67
  onEnsSetup,
77
68
  onEnsLink,
69
+ onWithdrawToken,
78
70
  }: AdvancedScreenProps): React.ReactNode | null {
79
71
  if (phase.kind === 'advanced-transfer-check') {
80
- type TransferCheckAction = 'skip' | 'back'
72
+ type TransferCheckAction = 'continue' | 'withdraw' | 'back'
73
+ const custody = reconciliation.custody
74
+ const tokenInVault = custody === 'advanced' || custody === 'mid-flow-uri-pending'
75
+ const tokenInOwnerWallet = custody === 'simple' || custody === 'withdrawn'
76
+ const probePending = custody === 'unknown' && reconciliation.rpc !== 'failing'
77
+ const probeFailed = reconciliation.rpc === 'failing'
78
+
79
+ const options: Array<{ value: TransferCheckAction; role?: 'section' | 'utility'; label: string; hint?: string }> = []
80
+ options.push({ value: 'continue', role: 'section', label: 'Setup' })
81
+ if (tokenInOwnerWallet) {
82
+ options.push({
83
+ value: 'continue',
84
+ label: 'Continue ENS Setup',
85
+ hint: 'Owner wallet holds this token onchain.',
86
+ })
87
+ } else if (tokenInVault) {
88
+ options.push({
89
+ value: 'withdraw',
90
+ label: 'Withdraw Token',
91
+ hint: 'Pull token out to sign ENS records. Redeposit to the Vault any time after.',
92
+ })
93
+ } else if (probePending) {
94
+ options.push({
95
+ value: 'continue',
96
+ label: 'Checking onchain state…',
97
+ hint: 'Try again in a moment.',
98
+ })
99
+ } else if (probeFailed) {
100
+ options.push({
101
+ value: 'continue',
102
+ label: 'Onchain check unavailable',
103
+ hint: 'RPC unreachable. Resolve connectivity, then retry.',
104
+ })
105
+ } else {
106
+ options.push({
107
+ value: 'continue',
108
+ label: 'Token Owner Unknown',
109
+ hint: 'Try again in a moment.',
110
+ })
111
+ }
112
+ options.push({ value: 'back', role: 'section', label: 'Navigation' })
113
+ options.push({ value: 'back', label: 'Back', hint: 'Return to setup type', role: 'utility' })
114
+
81
115
  return (
82
116
  <Surface
83
117
  title="Token Custody Check"
84
- subtitle="ENS setup continues only after the owner wallet holds this token."
118
+ subtitle="ENS setup continues only after the owner wallet holds this token onchain."
85
119
  footer={footerHint('enter select · esc back')}
86
120
  >
87
- <Box flexDirection="column">
88
- <Text color={theme.dim}>Current token owner: <Text color={theme.text}>{shortAddress(ownerAddress)}</Text></Text>
89
- <Box marginTop={1} flexDirection="column">
90
- <EnsSetupRow label="Owner wallet" value={`Holds ERC-8004 token #${agentId ?? 'unknown'} and signs ENS records.`} />
91
- <EnsSetupRow label="Operator wallet" value="Restores snapshots; never controls the token." />
92
- <EnsSetupRow label="Token moves" value="If the token is in the Vault, withdraw it first from Custody Mode." />
93
- </Box>
94
- </Box>
95
121
  <Box marginTop={1}>
96
122
  <Select<TransferCheckAction>
97
- options={[
98
- { value: 'skip', role: 'section', label: 'Setup' },
99
- { value: 'skip', label: 'Continue ENS Setup', hint: 'The connected wallet is already the owner wallet' },
100
- { value: 'back', role: 'section', label: 'Navigation' },
101
- { value: 'back', label: 'Back', hint: 'Return to setup type', role: 'utility' },
102
- ]}
123
+ options={options}
103
124
  hintLayout="inline"
104
125
  onSubmit={choice => {
105
- if (choice === 'skip') return setPhase({ kind: 'advanced-root', rootName: savedRootName })
106
- return setPhase({ kind: 'mode-select' })
107
- }}
108
- onCancel={() => setPhase({ kind: 'mode-select' })}
109
- />
110
- </Box>
111
- </Surface>
112
- )
113
- }
114
-
115
- if (phase.kind === 'advanced-root') {
116
- return (
117
- <Surface
118
- title="Root ENS"
119
- footer={footerHint('enter next · esc back')}
120
- >
121
- <Box flexDirection="column">
122
- <Text color={theme.dim}>Enter the parent .eth name. The owner wallet must manage it and own this ERC-8004 token.</Text>
123
- {savedOwnerAddress ? <Text color={theme.dim}>Saved owner wallet: <Text color={theme.text}>{shortAddress(savedOwnerAddress)}</Text></Text> : null}
124
- {phase.error ? <Text color={theme.accentError}>{phase.error}</Text> : null}
125
- </Box>
126
- <Box marginTop={1}>
127
- <TextInput
128
- key="advanced-root"
129
- initialValue={phase.rootName || savedRootName}
130
- placeholder="name.eth"
131
- validate={value => {
132
- const root = normalizeEthDomain(value)
133
- if (!root) return 'Enter a parent .eth name'
134
- if (!isRootEthName(root)) return 'Enter the parent .eth name, e.g. name.eth'
135
- return null
126
+ if (choice === 'withdraw') {
127
+ onWithdrawToken()
128
+ return
129
+ }
130
+ if (choice === 'continue' && tokenInOwnerWallet) {
131
+ runDiscovery('advanced')
132
+ return
133
+ }
134
+ if (choice === 'back') {
135
+ return setPhase({ kind: 'mode-select' })
136
+ }
136
137
  }}
137
- onSubmit={value => runAdvancedRootCheck(normalizeEthDomain(value))}
138
138
  onCancel={() => setPhase({ kind: 'mode-select' })}
139
139
  />
140
140
  </Box>
@@ -161,18 +161,19 @@ export function renderAdvancedEnsPhase({
161
161
  return (
162
162
  <Surface
163
163
  title="Agent Subdomain"
164
+ subtitle={`Pick a subdomain label under ${rootName}.`}
164
165
  footer={footerHint('enter next · esc back')}
165
166
  >
166
- <Box flexDirection="column">
167
- <Text color={theme.dim}>Create one subdomain for this agent only. Root .eth names stay parent names.</Text>
168
- <Text color={theme.dim}>Parent: <Text color={theme.text}>{rootName}</Text></Text>
169
- {phase.error ? <Text color={theme.accentError}>{phase.error}</Text> : null}
170
- </Box>
167
+ {phase.error ? (
168
+ <Box flexDirection="column">
169
+ <Text color={theme.accentError}>{phase.error}</Text>
170
+ </Box>
171
+ ) : null}
171
172
  <Box marginTop={1}>
172
173
  <TextInput
173
174
  key={`advanced-subdomain-${rootName}`}
174
- initialValue={phase.label || savedSubdomainLabel || agentNameSuggestion}
175
- placeholder="agent-name"
175
+ initialValue={phase.label || savedSubdomainLabel || ''}
176
+ placeholder={agentNameSuggestion || 'agent-name'}
176
177
  validate={value => {
177
178
  const trimmed = value.trim()
178
179
  const label = sanitizeSubdomainPrefix(trimmed)
@@ -182,7 +183,7 @@ export function renderAdvancedEnsPhase({
182
183
  return null
183
184
  }}
184
185
  onSubmit={value => runAdvancedSubdomainCheck(rootName, sanitizeSubdomainPrefix(value))}
185
- onCancel={() => setPhase({ kind: 'advanced-root', rootName })}
186
+ onCancel={() => setPhase({ kind: 'pick-parent', mode: 'advanced' })}
186
187
  />
187
188
  </Box>
188
189
  </Surface>
@@ -193,12 +194,9 @@ export function renderAdvancedEnsPhase({
193
194
  return (
194
195
  <Surface
195
196
  title="Check Agent Subdomain"
197
+ subtitle={`Verifying ${phase.label}.${phase.rootName} on Ethereum Mainnet.`}
196
198
  footer={footerHint('esc back')}
197
199
  >
198
- <Box flexDirection="column">
199
- <Text color={theme.dim}>Agent ENS: <Text color={theme.text}>{phase.label}.{phase.rootName}</Text></Text>
200
- <Text color={theme.dim}>Checking whether the subdomain is ready or needs the owner wallet to create it.</Text>
201
- </Box>
202
200
  <Box marginTop={1}>
203
201
  <Spinner label="checking agent subdomain..." />
204
202
  </Box>
@@ -207,98 +205,6 @@ export function renderAdvancedEnsPhase({
207
205
  )
208
206
  }
209
207
 
210
- if (phase.kind === 'advanced-operator-wallet') {
211
- const { rootName, label } = phase
212
- return (
213
- <Surface
214
- title="Operator Wallet"
215
- footer={footerHint('enter select · esc back')}
216
- >
217
- <Box flexDirection="column">
218
- <Text color={theme.dim}>Agent ENS: <Text color={theme.text}>{label}.{rootName}</Text></Text>
219
- {phase.registryAction ? <Text color={theme.dim}>{advancedSubdomainStatusText(phase.registryAction)}</Text> : null}
220
- <Text color={theme.dim}>Choose the operator wallet for snapshot restore access and onchain ERC-8004 URI rotation via the Vault.</Text>
221
- <Text color={theme.dim}>The operator wallet has no authority over this ENS subdomain or any token transfer; the owner wallet is the sole signer for both.</Text>
222
- <Text color={theme.dim}>We only read the operator's address here so it can be added to the snapshot envelope and vault operator list later.</Text>
223
- {savedOperator ? <Text color={theme.dim}>Saved operator wallet: <Text color={theme.text}>{shortAddress(savedOperator)}</Text></Text> : null}
224
- {phase.error ? <Text color={theme.accentError}>{phase.error}</Text> : null}
225
- </Box>
226
- <Box marginTop={1}>
227
- <Select<'connect' | 'enter' | 'back'>
228
- options={[
229
- { value: 'connect', role: 'section', label: 'Operator Wallet' },
230
- { value: 'connect', label: 'Connect Wallet', hint: 'Connect the wallet that will be the operator' },
231
- { value: 'enter', label: 'Enter Wallet Address', hint: 'Paste the operator wallet address' },
232
- { value: 'back', role: 'section', label: 'Navigation' },
233
- { value: 'back', label: 'Back', hint: 'Return to subdomain', role: 'utility' },
234
- ]}
235
- hintLayout="inline"
236
- onSubmit={choice => {
237
- if (choice === 'connect') return connectOperatorWallet(rootName, label)
238
- if (choice === 'enter') return setPhase({ kind: 'advanced-operator-wallet-manual', rootName, label })
239
- return setPhase({ kind: 'advanced-subdomain', rootName, label })
240
- }}
241
- onCancel={() => setPhase({ kind: 'advanced-subdomain', rootName, label })}
242
- />
243
- </Box>
244
- </Surface>
245
- )
246
- }
247
-
248
- if (phase.kind === 'advanced-operator-wallet-manual') {
249
- const { rootName, label } = phase
250
- return (
251
- <Surface
252
- title="Operator Wallet"
253
- footer={footerHint('enter next · esc back')}
254
- >
255
- <Box flexDirection="column">
256
- <Text color={theme.dim}>The operator wallet is saved in ERC-8004 metadata for lookup and restore access.</Text>
257
- <Text color={theme.dim}>It gets no token approval or transfer right.</Text>
258
- <Text color={theme.dim}>Owner wallet signs the ENS and ERC-8004 transactions after this address is checked.</Text>
259
- <Text color={theme.dim}>Any future token move still starts with Prepare Token Transfer.</Text>
260
- {phase.error ? <Text color={theme.accentError}>{phase.error}</Text> : null}
261
- </Box>
262
- <Box marginTop={1}>
263
- <TextInput
264
- key="advanced-operator-wallet-manual"
265
- initialValue={savedOperator}
266
- placeholder="0x..."
267
- validate={value => /^0x[0-9a-fA-F]{40}$/.test(value.trim()) ? null : 'enter a valid 0x address'}
268
- onSubmit={value => runAdvancedPreflight(rootName, label, getAddress(value.trim()))}
269
- onCancel={() => setPhase({ kind: 'advanced-operator-wallet', rootName, label })}
270
- />
271
- </Box>
272
- </Surface>
273
- )
274
- }
275
-
276
- if (phase.kind === 'advanced-operator-wallet-connecting') {
277
- return (
278
- <WalletApprovalScreen
279
- title="Connect Wallet"
280
- subtitle="Connect the operator wallet only to read its address for ERC-8004 metadata. It does not sign or submit a transaction."
281
- walletSession={operatorWalletSession}
282
- label="waiting for wallet connection..."
283
- onCancel={() => setPhase({ kind: 'advanced-operator-wallet', rootName: phase.rootName, label: phase.label })}
284
- />
285
- )
286
- }
287
-
288
- if (phase.kind === 'advanced-preflight') {
289
- return (
290
- <Surface
291
- title="Check ENS Setup"
292
- footer={footerHint('esc back')}
293
- >
294
- <Box marginTop={1}>
295
- <Spinner label="checking ens setup..." />
296
- </Box>
297
- <EscCancel onCancel={() => setPhase({ kind: 'advanced-operator-wallet', rootName: phase.rootName, label: phase.label })} />
298
- </Surface>
299
- )
300
- }
301
-
302
208
  if (phase.kind === 'advanced-review') {
303
209
  return (
304
210
  <EnsSetupReviewScreen
@@ -314,10 +220,9 @@ export function renderAdvancedEnsPhase({
314
220
  onEnsLink(phase.setup.fullName, {
315
221
  mode: 'advanced',
316
222
  ownerAddress: phase.setup.ownerAddress,
317
- operatorWallet: phase.setup.operatorAddress,
318
223
  })
319
224
  }}
320
- onBack={() => setPhase({ kind: 'advanced-operator-wallet', rootName: phase.setup.rootName, label: phase.setup.label })}
225
+ onBack={() => setPhase({ kind: 'advanced-subdomain', rootName: phase.setup.rootName, label: phase.setup.label })}
321
226
  />
322
227
  )
323
228
  }
@@ -326,8 +231,8 @@ export function renderAdvancedEnsPhase({
326
231
  return (
327
232
  <EnsSetupBlockedScreen
328
233
  fallback={phase.fallback}
329
- onCheckAgain={() => runAdvancedPreflight(phase.fallback.rootName, phase.fallback.label, phase.fallback.operatorAddress)}
330
- onBack={() => setPhase({ kind: 'advanced-operator-wallet', rootName: phase.fallback.rootName, label: phase.fallback.label })}
234
+ onCheckAgain={() => runAdvancedSubdomainCheck(phase.fallback.rootName, phase.fallback.label)}
235
+ onBack={() => setPhase({ kind: 'advanced-subdomain', rootName: phase.fallback.rootName, label: phase.fallback.label })}
331
236
  />
332
237
  )
333
238
  }
@@ -1,7 +1,6 @@
1
1
  import React from 'react'
2
2
  import { getAddress, type Address } from 'viem'
3
3
  import type { BrowserWalletReady } from '../../../wallet/browserWallet.js'
4
- import { requestBrowserWalletAccount } from '../../../wallet/browserWallet.js'
5
4
  import {
6
5
  AGENT_RECORD_READ_KEY_LIST,
7
6
  buildAgentEnsRecords,
@@ -45,11 +44,13 @@ export type { EnsLinkOptions }
45
44
  export const EnsEditFlow: React.FC<EnsEditProps> = ({
46
45
  identity,
47
46
  registry,
47
+ reconciliation,
48
48
  onEnsLink,
49
49
  onEnsUnlink,
50
50
  onEnsRecordsUpdate,
51
51
  onEnsSetup,
52
52
  onManageOperatorWalletAccess,
53
+ onWithdrawToken,
53
54
  initialView,
54
55
  onBack,
55
56
  }) => {
@@ -59,7 +60,6 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
59
60
  const savedRootName = currentEnsParts?.parent ?? ''
60
61
  const savedSubdomainLabel = currentEnsParts?.label ?? ''
61
62
  const agentNameSuggestion = sanitizeSubdomainPrefix(readIdentityStateString(identity.state, 'name'))
62
- const agentCardCid = identity.publicSkills?.agentCardCid
63
63
  const savedCustodyMode = readCustodyMode(identity.state)
64
64
  const savedOwnerAddress = readIdentityStateString(identity.state, 'ownerAddress')
65
65
  const savedOperator = readIdentityStateString(identity.state, 'activeOperatorAddress')
@@ -76,13 +76,13 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
76
76
  const [operatorWalletSession, setOperatorWalletSession] = React.useState<BrowserWalletReady | null>(null)
77
77
  const discoveryControllerRef = React.useRef<AbortController | null>(null)
78
78
 
79
- const runDiscovery = React.useCallback(() => {
79
+ const runDiscovery = React.useCallback((targetMode: 'simple' | 'advanced' = 'simple') => {
80
80
  discoveryControllerRef.current?.abort()
81
81
  const controller = new AbortController()
82
82
  discoveryControllerRef.current = controller
83
83
  setDiscovery({ status: 'loading' })
84
84
  setDiscoveryStartedAt(Date.now())
85
- setPhase({ kind: 'discovering' })
85
+ setPhase({ kind: 'discovering', mode: targetMode })
86
86
  discoverOwnedEnsNameDetails(ownerAddress, {
87
87
  signal: controller.signal,
88
88
  budgetMs: 30_000,
@@ -93,7 +93,7 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
93
93
  if (discoveryControllerRef.current === controller) discoveryControllerRef.current = null
94
94
  if (result.status === 'error') {
95
95
  setDiscovery({ status: 'error', message: discoveryErrorMessage(result.errors), names: [] })
96
- setPhase({ kind: 'pick-parent' })
96
+ setPhase({ kind: 'pick-parent', mode: targetMode })
97
97
  return
98
98
  }
99
99
  setDiscovery({
@@ -101,13 +101,13 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
101
101
  names: result.names,
102
102
  ...(result.status === 'partial' ? { warning: 'Some ENS lookup sources failed; showing root names found so far.' } : {}),
103
103
  })
104
- setPhase({ kind: 'pick-parent' })
104
+ setPhase({ kind: 'pick-parent', mode: targetMode })
105
105
  })
106
106
  .catch((err: unknown) => {
107
107
  if (controller.signal.aborted) return
108
108
  if (discoveryControllerRef.current === controller) discoveryControllerRef.current = null
109
109
  setDiscovery({ status: 'error', message: err instanceof Error ? err.message : String(err), names: [] })
110
- setPhase({ kind: 'pick-parent' })
110
+ setPhase({ kind: 'pick-parent', mode: targetMode })
111
111
  })
112
112
  }, [ownerAddress])
113
113
 
@@ -145,7 +145,6 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
145
145
  chainId: registry.chainId,
146
146
  identityRegistryAddress: registry.identityRegistryAddress,
147
147
  agentId: identity.agentId,
148
- agentCardCid,
149
148
  })
150
149
  const recordsDiff = diffRecords(current, next)
151
150
  if (mode === 'simple' && !validation.ok && validation.reason === 'no-owner') {
@@ -157,36 +156,7 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
157
156
  setValidationError(err instanceof Error ? err.message : String(err))
158
157
  setPhase({ kind: 'pick-parent' })
159
158
  }
160
- }, [ownerAddress, registry, identity.agentId, agentCardCid])
161
-
162
- const runAdvancedPreflight = React.useCallback((
163
- rootName: string,
164
- label: string,
165
- operatorWallet: Address,
166
- ): void => {
167
- setPhase({ kind: 'advanced-preflight', rootName, label, operatorWallet })
168
- preflightEnsSetup({
169
- rootName,
170
- label,
171
- operatorAddress: operatorWallet,
172
- registry,
173
- agentId: identity.agentId,
174
- agentCardCid,
175
- }).then(result => {
176
- if (result.ok) {
177
- setPhase({ kind: 'advanced-review', setup: result.setup })
178
- return
179
- }
180
- setPhase({ kind: 'advanced-manual', fallback: result.fallback })
181
- }).catch((err: unknown) => {
182
- setPhase({
183
- kind: 'advanced-operator-wallet',
184
- rootName,
185
- label,
186
- error: err instanceof Error ? err.message : String(err),
187
- })
188
- })
189
- }, [agentCardCid, identity.agentId, registry])
159
+ }, [ownerAddress, registry, identity.agentId])
190
160
 
191
161
  const runAdvancedRootCheck = React.useCallback((rootName: string): void => {
192
162
  setPhase({ kind: 'advanced-root-check', rootName })
@@ -197,12 +167,12 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
197
167
  agentId: identity.agentId,
198
168
  }).then(result => {
199
169
  if (result.ok) {
200
- setPhase({ kind: 'advanced-subdomain', rootName, label: savedSubdomainLabel || agentNameSuggestion })
170
+ setPhase({ kind: 'advanced-subdomain', rootName, label: savedSubdomainLabel })
201
171
  return
202
172
  }
203
- setPhase({ kind: 'advanced-root', rootName, error: rootErrorMessage(result.reason, result.detail, rootName) })
173
+ setPhase({ kind: 'pick-parent', mode: 'advanced', error: rootErrorMessage(result.reason, result.detail, rootName) })
204
174
  }).catch((err: unknown) => {
205
- setPhase({ kind: 'advanced-root', rootName, error: err instanceof Error ? err.message : String(err) })
175
+ setPhase({ kind: 'pick-parent', mode: 'advanced', error: err instanceof Error ? err.message : String(err) })
206
176
  })
207
177
  }, [agentNameSuggestion, identity.agentId, ownerAddress, registry, savedSubdomainLabel])
208
178
 
@@ -215,15 +185,9 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
215
185
  allowSameOwnerOperator: true,
216
186
  registry,
217
187
  agentId: identity.agentId,
218
- agentCardCid,
219
188
  }).then(result => {
220
189
  if (result.ok) {
221
- setPhase({
222
- kind: 'advanced-operator-wallet',
223
- rootName: result.setup.rootName,
224
- label: result.setup.label,
225
- registryAction: result.setup.registryAction,
226
- })
190
+ setPhase({ kind: 'advanced-review', setup: result.setup })
227
191
  return
228
192
  }
229
193
  setPhase({ kind: 'advanced-manual', fallback: result.fallback })
@@ -235,7 +199,7 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
235
199
  error: err instanceof Error ? err.message : String(err),
236
200
  })
237
201
  })
238
- }, [agentCardCid, identity.agentId, ownerAddress, registry])
202
+ }, [identity.agentId, ownerAddress, registry])
239
203
 
240
204
  const runSimpleCreatePreflight = React.useCallback((fullName: string): void => {
241
205
  const parts = splitSubdomainName(fullName)
@@ -253,7 +217,6 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
253
217
  allowSameOwnerOperator: true,
254
218
  registry,
255
219
  agentId: identity.agentId,
256
- agentCardCid,
257
220
  }).then(result => {
258
221
  if (result.ok) {
259
222
  setPhase({ kind: 'simple-create-review', setup: result.setup })
@@ -268,23 +231,7 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
268
231
  error: err instanceof Error ? err.message : String(err),
269
232
  })
270
233
  })
271
- }, [agentCardCid, identity.agentId, ownerAddress, registry])
272
-
273
- const connectOperatorWallet = React.useCallback((rootName: string, label: string): void => {
274
- setOperatorWalletSession(null)
275
- setPhase({ kind: 'advanced-operator-wallet-connecting', rootName, label })
276
- requestBrowserWalletAccount({
277
- purpose: 'connect-operator-wallet',
278
- onReady: ready => setOperatorWalletSession(ready),
279
- }).then(wallet => {
280
- const operatorWallet = getAddress(wallet.account)
281
- setOperatorWalletSession(null)
282
- runAdvancedPreflight(rootName, label, operatorWallet)
283
- }).catch((err: unknown) => {
284
- setOperatorWalletSession(null)
285
- setPhase({ kind: 'advanced-operator-wallet', rootName, label, error: err instanceof Error ? err.message : String(err) })
286
- })
287
- }, [runAdvancedPreflight])
234
+ }, [identity.agentId, ownerAddress, registry])
288
235
 
289
236
  const runDeleteSubdomainPreflight = React.useCallback((fullName: string): void => {
290
237
  setValidationError(null)
@@ -323,6 +270,7 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
323
270
 
324
271
  const maintenanceScreen = renderEnsMaintenancePhase({
325
272
  phase,
273
+ identity,
326
274
  currentEnsName,
327
275
  currentEnsCanDelete: Boolean(currentEnsParts),
328
276
  savedCustodyMode,
@@ -340,30 +288,26 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
340
288
  onBack,
341
289
  onEnsUnlink,
342
290
  onEnsRecordsUpdate,
343
- onManageOperatorWalletAccess,
344
291
  })
345
292
  if (maintenanceScreen) return maintenanceScreen
346
293
 
347
294
  const advancedScreen = renderAdvancedEnsPhase({
348
295
  phase,
296
+ identity,
349
297
  ownerAddress,
350
298
  agentId: identity.agentId,
351
- savedOwnerAddress,
352
- savedOperator,
353
- savedRootName,
299
+ reconciliation,
354
300
  savedSubdomainLabel,
355
301
  agentNameSuggestion,
356
302
  currentEnsName,
357
303
  savedCustodyMode,
358
304
  registry,
359
- operatorWalletSession,
360
305
  setPhase,
361
- connectOperatorWallet,
362
- runAdvancedRootCheck,
306
+ runDiscovery,
363
307
  runAdvancedSubdomainCheck,
364
- runAdvancedPreflight,
365
308
  onEnsSetup,
366
309
  onEnsLink,
310
+ onWithdrawToken,
367
311
  })
368
312
  if (advancedScreen) return advancedScreen
369
313
 
@@ -384,6 +328,7 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
384
328
  cancelDiscoveryToModeSelect,
385
329
  runDiscovery,
386
330
  runValidation,
331
+ runAdvancedRootCheck,
387
332
  backToSimpleSubdomain,
388
333
  runSimpleCreatePreflight,
389
334
  onEnsSetup,