ethagent 3.2.0 → 3.3.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 (61) hide show
  1. package/README.md +15 -16
  2. package/package.json +1 -1
  3. package/src/identity/continuity/history.ts +8 -8
  4. package/src/identity/continuity/publicSkills.ts +2 -79
  5. package/src/identity/continuity/skills/publicSkillsSync.ts +8 -7
  6. package/src/identity/continuity/snapshots.ts +3 -8
  7. package/src/identity/continuity/storage/defaults.ts +3 -3
  8. package/src/identity/continuity/storage/paths.ts +1 -1
  9. package/src/identity/continuity/storage/scaffold.ts +37 -25
  10. package/src/identity/continuity/storage/status.ts +11 -11
  11. package/src/identity/continuity/storage/types.ts +4 -4
  12. package/src/identity/continuity/storage.ts +4 -4
  13. package/src/identity/ens/agentRecords.ts +61 -45
  14. package/src/identity/ens/ensAutomation/read.ts +7 -10
  15. package/src/identity/ens/ensAutomation/setup.ts +10 -16
  16. package/src/identity/ens/ensAutomation/types.ts +0 -1
  17. package/src/identity/ens/ensAutomation.ts +1 -0
  18. package/src/identity/ens/ensLookup/records.ts +1 -1
  19. package/src/identity/ens/ensLookup.ts +1 -1
  20. package/src/identity/ens/erc7930.ts +48 -0
  21. package/src/identity/hub/OperationalRoutes.tsx +4 -2
  22. package/src/identity/hub/Routes.tsx +1 -1
  23. package/src/identity/hub/continuity/effects.ts +17 -39
  24. package/src/identity/hub/continuity/skills/NewSkillVisibilityScreen.tsx +2 -2
  25. package/src/identity/hub/continuity/skills/SkillActionsScreen.tsx +2 -2
  26. package/src/identity/hub/continuity/state.ts +1 -1
  27. package/src/identity/hub/continuity/vault.ts +16 -50
  28. package/src/identity/hub/create/effects.ts +12 -16
  29. package/src/identity/hub/ens/EnsEditAdvancedScreens.tsx +0 -76
  30. package/src/identity/hub/ens/EnsEditFlow.tsx +28 -8
  31. package/src/identity/hub/ens/EnsEditMaintenanceScreens.tsx +3 -7
  32. package/src/identity/hub/ens/EnsEditReviewScreens.tsx +14 -18
  33. package/src/identity/hub/ens/EnsEditShared.tsx +4 -6
  34. package/src/identity/hub/ens/EnsEditSimpleScreens.tsx +2 -2
  35. package/src/identity/hub/ens/EnsFlow.tsx +0 -3
  36. package/src/identity/hub/ens/editCopy.ts +7 -15
  37. package/src/identity/hub/ens/transactions.ts +67 -18
  38. package/src/identity/hub/ens/types.ts +0 -2
  39. package/src/identity/hub/profile/EditProfileFlow.tsx +0 -3
  40. package/src/identity/hub/profile/effects.ts +15 -30
  41. package/src/identity/hub/profile/identity.ts +2 -4
  42. package/src/identity/hub/profile/operatorSave.ts +10 -30
  43. package/src/identity/hub/restore/RestoreFlow.tsx +9 -9
  44. package/src/identity/hub/restore/apply.ts +7 -8
  45. package/src/identity/hub/restore/helpers.ts +3 -3
  46. package/src/identity/hub/restore/recovery.ts +9 -10
  47. package/src/identity/hub/restore/resolve.ts +11 -9
  48. package/src/identity/hub/shared/components/MenuScreen.tsx +3 -3
  49. package/src/identity/hub/shared/components/UnlinkedIdentityScreen.tsx +2 -2
  50. package/src/identity/hub/shared/effects/sync.ts +1 -1
  51. package/src/identity/hub/transfer/TokenTransferScreens.tsx +1 -1
  52. package/src/identity/hub/transfer/effects.ts +10 -31
  53. package/src/identity/hub/useIdentityHubContinuity.ts +12 -12
  54. package/src/identity/hub/useIdentityHubController.ts +12 -3
  55. package/src/identity/registry/erc8004/metadata.ts +10 -27
  56. package/src/identity/registry/erc8004/types.ts +0 -1
  57. package/src/storage/config.ts +1 -2
  58. package/src/storage/identity.ts +3 -3
  59. package/src/storage/rewind.ts +1 -1
  60. package/src/tools/privateContinuityEditTool.ts +4 -4
  61. package/src/utils/withRetry.ts +2 -2
@@ -4,7 +4,6 @@ import {
4
4
  prepareSyncedIdentityMarkdownScaffold,
5
5
  prepareSyncedSkillsTree,
6
6
  readContinuityFiles,
7
- readPublicSkillsFile,
8
7
  writeIdentityMarkdownScaffold,
9
8
  type IdentityMarkdownScaffold,
10
9
  } from '../../continuity/storage.js'
@@ -16,14 +15,7 @@ import {
16
15
  type WalletChallengePurpose,
17
16
  } from '../../continuity/envelope.js'
18
17
  import {
19
- appendPublicSkillEntries,
20
- createAgentCard,
21
- defaultPublicSkillsProfile,
22
- serializeAgentCard,
23
- } from '../../continuity/publicSkills.js'
24
- import {
25
- derivePublicSkillEntries,
26
- syncPublicSkillsManifest,
18
+ syncAgentCardManifest,
27
19
  } from '../../continuity/skills/publicSkillsSync.js'
28
20
  import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
29
21
  import { addToIpfs, DEFAULT_IPFS_API_URL } from '../../storage/ipfs.js'
@@ -60,7 +52,7 @@ import {
60
52
  import { markCurrentContinuityFilesPublished } from '../shared/effects/sync.js'
61
53
 
62
54
  type BackupMetadata = NonNullable<EthagentIdentity['backup']>
63
- type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
55
+ type AgentCardMetadata = NonNullable<EthagentIdentity['agentCard']>
64
56
 
65
57
  type VaultPublishPrepared = {
66
58
  nextIdentity: EthagentIdentity
@@ -68,7 +60,7 @@ type VaultPublishPrepared = {
68
60
  completionMessage: string
69
61
  publishedSources: {
70
62
  privateFiles: ContinuityFiles
71
- publicSkills: string
63
+ agentCard: string
72
64
  skills: ContinuitySkillsTree
73
65
  }
74
66
  }
@@ -142,20 +134,8 @@ export async function runOperatorWalletRebackup(args: {
142
134
  const continuityFiles = markdownScaffold
143
135
  ? { 'SOUL.md': markdownScaffold['SOUL.md'], 'MEMORY.md': markdownScaffold['MEMORY.md'] }
144
136
  : await readContinuityFiles(nextIdentityForFiles)
145
- const publicSkillsJson = await syncPublicSkillsManifest(nextIdentityForFiles)
146
- const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
147
- assertVerifiedPin(publicSkillsPin)
148
- const publicSkillEntries = await derivePublicSkillEntries(nextIdentityForFiles)
149
- const augmentedPublicProfile = appendPublicSkillEntries(
150
- defaultPublicSkillsProfile(nextIdentityForFiles),
151
- publicSkillEntries,
152
- )
153
- const agentCardPin = await addToIpfs(
154
- DEFAULT_IPFS_API_URL,
155
- serializeAgentCard(createAgentCard(augmentedPublicProfile)),
156
- fetch,
157
- { pinataJwt: step.pinataJwt },
158
- )
137
+ const agentCardJson = await syncAgentCardManifest(nextIdentityForFiles)
138
+ const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, agentCardJson, fetch, { pinataJwt: step.pinataJwt })
159
139
  assertVerifiedPin(agentCardPin)
160
140
  const skillsTree = await prepareSyncedSkillsTree(nextIdentityForFiles)
161
141
  const envelope = createContinuityEnvelopeForSave({
@@ -185,9 +165,8 @@ export async function runOperatorWalletRebackup(args: {
185
165
  identityRegistryAddress: step.registry.identityRegistryAddress,
186
166
  agentId: sourceAgentId,
187
167
  }
188
- const publicSkills: PublicSkillsMetadata = {
189
- cid: publicSkillsPin.cid,
190
- agentCardCid: agentCardPin.cid,
168
+ const agentCard: AgentCardMetadata = {
169
+ cid: agentCardPin.cid,
191
170
  updatedAt: envelope.createdAt,
192
171
  status: 'pinned',
193
172
  }
@@ -196,7 +175,7 @@ export async function runOperatorWalletRebackup(args: {
196
175
  ...step.identity,
197
176
  state,
198
177
  backup,
199
- publicSkills,
178
+ agentCard,
200
179
  }
201
180
 
202
181
  if (markdownScaffold) {
@@ -205,7 +184,7 @@ export async function runOperatorWalletRebackup(args: {
205
184
  await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'local operator-wallet snapshot' }).catch(() => null)
206
185
  await markCurrentContinuityFilesPublished(nextIdentity, {
207
186
  privateFiles: continuityFiles,
208
- publicSkills: publicSkillsJson,
187
+ agentCard: agentCardJson,
209
188
  skills: skillsTree,
210
189
  }).catch(() => null)
211
190
  const completionMessage = nextEnsName !== undefined && nextEnsName !== ((step.identity.state as Record<string, unknown> | undefined)?.ensName as string | undefined)
@@ -284,20 +263,8 @@ async function runOperatorWalletVaultPublish(args: {
284
263
  const continuityFiles = markdownScaffold
285
264
  ? { 'SOUL.md': markdownScaffold['SOUL.md'], 'MEMORY.md': markdownScaffold['MEMORY.md'] }
286
265
  : await readContinuityFiles(nextIdentityForFiles)
287
- const publicSkillsJson = await syncPublicSkillsManifest(nextIdentityForFiles)
288
- const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
289
- assertVerifiedPin(publicSkillsPin)
290
- const publicSkillEntries = await derivePublicSkillEntries(nextIdentityForFiles)
291
- const augmentedPublicProfile = appendPublicSkillEntries(
292
- defaultPublicSkillsProfile(nextIdentityForFiles),
293
- publicSkillEntries,
294
- )
295
- const agentCardPin = await addToIpfs(
296
- DEFAULT_IPFS_API_URL,
297
- serializeAgentCard(createAgentCard(augmentedPublicProfile)),
298
- fetch,
299
- { pinataJwt: step.pinataJwt },
300
- )
266
+ const agentCardJson = await syncAgentCardManifest(nextIdentityForFiles)
267
+ const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, agentCardJson, fetch, { pinataJwt: step.pinataJwt })
301
268
  assertVerifiedPin(agentCardPin)
302
269
  const skillsTree = await prepareSyncedSkillsTree(nextIdentityForFiles)
303
270
  const envelope = createContinuityEnvelopeForSave({
@@ -315,9 +282,8 @@ async function runOperatorWalletVaultPublish(args: {
315
282
  const statePin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeContinuitySnapshotEnvelope(envelope), fetch, { pinataJwt: step.pinataJwt })
316
283
  assertVerifiedPin(statePin)
317
284
 
318
- const publicSkills: PublicSkillsMetadata = {
319
- cid: publicSkillsPin.cid,
320
- agentCardCid: agentCardPin.cid,
285
+ const agentCard: AgentCardMetadata = {
286
+ cid: agentCardPin.cid,
321
287
  updatedAt: envelope.createdAt,
322
288
  status: 'pinned',
323
289
  }
@@ -329,7 +295,7 @@ async function runOperatorWalletVaultPublish(args: {
329
295
  ...(uploadedImageUri ? { image: uploadedImageUri } : {}),
330
296
  }, {
331
297
  backup: { cid: statePin.cid, envelopeVersion: envelope.envelopeVersion, createdAt: envelope.createdAt },
332
- publicDiscovery: { skillsCid: publicSkills.cid, agentCardCid: publicSkills.agentCardCid, updatedAt: publicSkills.updatedAt },
298
+ publicDiscovery: { agentCardCid: agentCard.cid, updatedAt: agentCard.updatedAt },
333
299
  registration: { chainId: step.registry.chainId, identityRegistryAddress: step.registry.identityRegistryAddress, agentId: sourceAgentId },
334
300
  ensName: nextEnsName,
335
301
  operators: operatorsPointerFromState(state, nextEnsName),
@@ -365,7 +331,7 @@ async function runOperatorWalletVaultPublish(args: {
365
331
  ...step.identity,
366
332
  state,
367
333
  backup,
368
- publicSkills,
334
+ agentCard,
369
335
  agentUri,
370
336
  metadataCid,
371
337
  }
@@ -383,7 +349,7 @@ async function runOperatorWalletVaultPublish(args: {
383
349
  completionMessage,
384
350
  publishedSources: {
385
351
  privateFiles: continuityFiles,
386
- publicSkills: publicSkillsJson,
352
+ agentCard: agentCardJson,
387
353
  skills: skillsTree,
388
354
  },
389
355
  },
@@ -14,7 +14,6 @@ import {
14
14
  import {
15
15
  createAgentCard,
16
16
  defaultPublicSkillsProfile,
17
- renderPublicSkillsJson,
18
17
  serializeAgentCard,
19
18
  } from '../../continuity/publicSkills.js'
20
19
  import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
@@ -43,17 +42,17 @@ import { awaitConfirmedReceipt } from '../shared/effects/receipts.js'
43
42
  import { assertVerifiedPin } from '../shared/effects/profilePrep.js'
44
43
 
45
44
  type BackupMetadata = NonNullable<EthagentIdentity['backup']>
46
- type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
45
+ type AgentCardMetadata = NonNullable<EthagentIdentity['agentCard']>
47
46
 
48
47
  type CreatePreparedTransaction = {
49
48
  ownerAddress: Address
50
49
  agentUri: string
51
50
  metadataCid: string
52
51
  backup: BackupMetadata
53
- publicSkills: PublicSkillsMetadata
52
+ agentCard: AgentCardMetadata
54
53
  state: Record<string, unknown>
55
54
  continuityFiles: ReturnType<typeof defaultContinuityFiles>
56
- publicSkillsJson: string
55
+ agentCardJson: string
57
56
  }
58
57
 
59
58
  export async function runCreatePreflight(
@@ -139,10 +138,8 @@ export async function runCreateSigning(
139
138
  })
140
139
  const continuityFiles = defaultContinuityFiles(draftIdentity)
141
140
  const publicProfile = defaultPublicSkillsProfile(draftIdentity)
142
- const publicSkillsJson = renderPublicSkillsJson(publicProfile)
143
- const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
144
- assertVerifiedPin(publicSkillsPin)
145
- const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeAgentCard(createAgentCard(publicProfile)), fetch, { pinataJwt: step.pinataJwt })
141
+ const agentCardJson = serializeAgentCard(createAgentCard(publicProfile))
142
+ const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, agentCardJson, fetch, { pinataJwt: step.pinataJwt })
146
143
  assertVerifiedPin(agentCardPin)
147
144
  const envelope = createContinuitySnapshotEnvelope({
148
145
  ownerAddress: wallet.account,
@@ -168,9 +165,8 @@ export async function runCreateSigning(
168
165
  rpcUrl: step.registry.rpcUrl,
169
166
  identityRegistryAddress: step.registry.identityRegistryAddress,
170
167
  }
171
- const publicSkills: PublicSkillsMetadata = {
172
- cid: publicSkillsPin.cid,
173
- agentCardCid: agentCardPin.cid,
168
+ const agentCard: AgentCardMetadata = {
169
+ cid: agentCardPin.cid,
174
170
  updatedAt: envelope.createdAt,
175
171
  status: 'pinned',
176
172
  }
@@ -181,7 +177,7 @@ export async function runCreateSigning(
181
177
  ...(typeof state.imageUrl === 'string' ? { image: state.imageUrl } : {}),
182
178
  }, {
183
179
  backup: { cid, envelopeVersion: envelope.envelopeVersion, createdAt: envelope.createdAt },
184
- publicDiscovery: { skillsCid: publicSkills.cid, agentCardCid: publicSkills.agentCardCid, updatedAt: publicSkills.updatedAt },
180
+ publicDiscovery: { agentCardCid: agentCard.cid, updatedAt: agentCard.updatedAt },
185
181
  registration: { chainId: step.registry.chainId, identityRegistryAddress: step.registry.identityRegistryAddress },
186
182
  ownerAddress: wallet.account,
187
183
  })
@@ -197,10 +193,10 @@ export async function runCreateSigning(
197
193
  agentUri,
198
194
  metadataCid,
199
195
  backup: { ...backup, metadataCid, agentUri },
200
- publicSkills,
196
+ agentCard,
201
197
  state,
202
198
  continuityFiles,
203
- publicSkillsJson,
199
+ agentCardJson,
204
200
  },
205
201
  }
206
202
  },
@@ -233,11 +229,11 @@ export async function runCreateSigning(
233
229
  metadataCid: result.prepared.metadataCid,
234
230
  state: result.prepared.state,
235
231
  backup,
236
- publicSkills: result.prepared.publicSkills,
232
+ agentCard: result.prepared.agentCard,
237
233
  }
238
234
  await writeIdentityMarkdownScaffold(nextIdentity, {
239
235
  ...defaultContinuityFiles(nextIdentity),
240
- 'skills.json': result.prepared.publicSkillsJson,
236
+ 'agent-card.json': result.prepared.agentCardJson,
241
237
  })
242
238
  await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'initial published snapshot' }).catch(() => null)
243
239
  await callbacks.onIdentityComplete(nextIdentity, `ERC-8004 agent registered · #${registered.agentId.toString()}`, 'create')
@@ -46,7 +46,6 @@ type AdvancedScreenProps = {
46
46
  runAdvancedSubdomainCheck: (rootName: string, label: string) => void
47
47
  onEnsSetup: EnsEditProps['onEnsSetup']
48
48
  onEnsLink: EnsEditProps['onEnsLink']
49
- onWithdrawToken: () => void
50
49
  }
51
50
 
52
51
  export function renderAdvancedEnsPhase({
@@ -65,82 +64,7 @@ export function renderAdvancedEnsPhase({
65
64
  runAdvancedSubdomainCheck,
66
65
  onEnsSetup,
67
66
  onEnsLink,
68
- onWithdrawToken,
69
67
  }: AdvancedScreenProps): React.ReactNode | null {
70
- if (phase.kind === 'advanced-transfer-check') {
71
- type TransferCheckAction = 'continue' | 'withdraw' | 'back'
72
- const custody = reconciliation.custody
73
- const tokenInVault = custody === 'advanced' || custody === 'mid-flow-uri-pending'
74
- const tokenInOwnerWallet = custody === 'simple' || custody === 'withdrawn'
75
- const probePending = custody === 'unknown' && reconciliation.rpc !== 'failing'
76
- const probeFailed = reconciliation.rpc === 'failing'
77
-
78
- const options: Array<{ value: TransferCheckAction; role?: 'section' | 'utility'; label: string; hint?: string }> = []
79
- options.push({ value: 'continue', role: 'section', label: 'Setup' })
80
- if (tokenInOwnerWallet) {
81
- options.push({
82
- value: 'continue',
83
- label: 'Continue ENS Setup',
84
- hint: 'Owner wallet holds this token onchain.',
85
- })
86
- } else if (tokenInVault) {
87
- options.push({
88
- value: 'withdraw',
89
- label: 'Withdraw Token',
90
- hint: 'Pull token out to sign ENS records. Redeposit to the Vault any time after.',
91
- })
92
- } else if (probePending) {
93
- options.push({
94
- value: 'continue',
95
- label: 'Checking onchain state…',
96
- hint: 'Try again in a moment.',
97
- })
98
- } else if (probeFailed) {
99
- options.push({
100
- value: 'continue',
101
- label: 'Onchain check unavailable',
102
- hint: 'RPC unreachable. Resolve connectivity, then retry.',
103
- })
104
- } else {
105
- options.push({
106
- value: 'continue',
107
- label: 'Token Owner Unknown',
108
- hint: 'Try again in a moment.',
109
- })
110
- }
111
- options.push({ value: 'back', role: 'section', label: 'Navigation' })
112
- options.push({ value: 'back', label: 'Back', hint: 'Return to setup type', role: 'utility' })
113
-
114
- return (
115
- <Surface
116
- title="Token Custody Check"
117
- subtitle="ENS setup continues only after the owner wallet holds this token onchain."
118
- footer={footerHint('enter select · esc back')}
119
- >
120
- <Box marginTop={1}>
121
- <Select<TransferCheckAction>
122
- options={options}
123
- hintLayout="inline"
124
- onSubmit={choice => {
125
- if (choice === 'withdraw') {
126
- onWithdrawToken()
127
- return
128
- }
129
- if (choice === 'continue' && tokenInOwnerWallet) {
130
- runDiscovery('advanced')
131
- return
132
- }
133
- if (choice === 'back') {
134
- return setPhase({ kind: 'mode-select' })
135
- }
136
- }}
137
- onCancel={() => setPhase({ kind: 'mode-select' })}
138
- />
139
- </Box>
140
- </Surface>
141
- )
142
- }
143
-
144
68
  if (phase.kind === 'advanced-root-check') {
145
69
  return (
146
70
  <Surface
@@ -2,8 +2,9 @@ import React from 'react'
2
2
  import { getAddress, type Address } from 'viem'
3
3
  import type { BrowserWalletReady } from '../../wallet/browserWallet.js'
4
4
  import {
5
- AGENT_RECORD_READ_KEY_LIST,
5
+ AGENT_TOKEN_RECORD_KEY,
6
6
  buildAgentEnsRecords,
7
+ buildEnsip25Key,
7
8
  diffRecords,
8
9
  recordsFromTextMap,
9
10
  } from '../../ens/agentRecords.js'
@@ -19,6 +20,7 @@ import {
19
20
  preflightEnsRoot,
20
21
  preflightEnsSetup,
21
22
  } from '../../ens/ensAutomation.js'
23
+ import { SUPPORTED_ERC8004_CHAINS } from '../../registry/erc8004.js'
22
24
  import {
23
25
  readCustodyMode,
24
26
  readIdentityStateString,
@@ -50,7 +52,6 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
50
52
  onEnsRecordsUpdate,
51
53
  onEnsSetup,
52
54
  onManageOperatorWalletAccess,
53
- onWithdrawToken,
54
55
  initialView,
55
56
  onBack,
56
57
  }) => {
@@ -68,7 +69,7 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
68
69
 
69
70
  const [discovery, setDiscovery] = React.useState<DiscoveryState>({ status: 'idle' })
70
71
  const [phase, setPhase] = React.useState<EnsPhase>(() => {
71
- if (initialView === 'advanced' && !hasAdvancedSetup) return { kind: 'advanced-transfer-check' }
72
+ if (initialView === 'advanced' && !hasAdvancedSetup) return { kind: 'pick-parent', mode: 'advanced' }
72
73
  return { kind: 'mode-select' }
73
74
  })
74
75
  const [validationError, setValidationError] = React.useState<string | null>(null)
@@ -137,8 +138,18 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
137
138
  setPhase({ kind: 'validating', fullName, mode, ownerAddress: phaseOwnerAddress, operatorWallet })
138
139
  try {
139
140
  const validation = await validateAgentEnsLink(fullName, ownerAddress)
140
- const currentText = validation.ok
141
- ? await readEthagentTextRecords(fullName, AGENT_RECORD_READ_KEY_LIST)
141
+ const readKeys = identity.agentId
142
+ ? [
143
+ ...SUPPORTED_ERC8004_CHAINS.map(chain => buildEnsip25Key({
144
+ chainId: chain.chainId,
145
+ identityRegistryAddress: registry.identityRegistryAddress,
146
+ agentId: identity.agentId!,
147
+ })),
148
+ AGENT_TOKEN_RECORD_KEY,
149
+ ]
150
+ : []
151
+ const currentText = validation.ok && readKeys.length > 0
152
+ ? await readEthagentTextRecords(fullName, readKeys)
142
153
  : {}
143
154
  const current = recordsFromTextMap(currentText)
144
155
  const next = buildAgentEnsRecords({
@@ -252,7 +263,17 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
252
263
  const runUnlinkEnsLoading = React.useCallback((fullName: string): void => {
253
264
  setValidationError(null)
254
265
  setPhase({ kind: 'unlink-loading', fullName })
255
- readEthagentTextRecords(fullName, AGENT_RECORD_READ_KEY_LIST)
266
+ const readKeys = identity.agentId
267
+ ? [
268
+ ...SUPPORTED_ERC8004_CHAINS.map(chain => buildEnsip25Key({
269
+ chainId: chain.chainId,
270
+ identityRegistryAddress: registry.identityRegistryAddress,
271
+ agentId: identity.agentId!,
272
+ })),
273
+ AGENT_TOKEN_RECORD_KEY,
274
+ ]
275
+ : []
276
+ readEthagentTextRecords(fullName, readKeys)
256
277
  .then(currentText => {
257
278
  const currentRecords = recordsFromTextMap(currentText)
258
279
  setPhase({
@@ -266,7 +287,7 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
266
287
  setValidationError(err instanceof Error ? err.message : String(err))
267
288
  setPhase({ kind: 'mode-select' })
268
289
  })
269
- }, [])
290
+ }, [identity.agentId, registry.chainId, registry.identityRegistryAddress])
270
291
 
271
292
  const maintenanceScreen = renderEnsMaintenancePhase({
272
293
  phase,
@@ -307,7 +328,6 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
307
328
  runAdvancedSubdomainCheck,
308
329
  onEnsSetup,
309
330
  onEnsLink,
310
- onWithdrawToken,
311
331
  })
312
332
  if (advancedScreen) return advancedScreen
313
333
 
@@ -82,11 +82,11 @@ export function renderEnsMaintenancePhase({
82
82
  const linkHint = multiNeedsCustodySetup
83
83
  ? 'Set Advanced custody first via Custody Mode'
84
84
  : isAdvanced
85
- ? 'Walks you through Root, Name, Review, and Apply'
86
- : 'Walks you through Root, Name, Review, and Apply'
85
+ ? 'Root Name Review Apply'
86
+ : 'Root Name Review Apply'
87
87
  const options: Array<{ value: EnsAction; role?: 'section' | 'utility'; label: string; hint?: string; disabled?: boolean }> = []
88
88
  if (currentEnsName) {
89
- options.push({ value: 'unlink', label: 'Unlink Name', hint: 'Removes this name from the token. Set up a different name afterward by linking again.' })
89
+ options.push({ value: 'unlink', label: 'Unlink Name', hint: 'Removes the name from the token. Link a different one anytime.' })
90
90
  } else {
91
91
  options.push({
92
92
  value: 'link',
@@ -116,10 +116,6 @@ export function renderEnsMaintenancePhase({
116
116
  }
117
117
  if (choice === 'link') {
118
118
  if (multiNeedsCustodySetup) return
119
- if (isAdvanced && savedOwnerAddress) {
120
- setPhase({ kind: 'advanced-transfer-check' })
121
- return
122
- }
123
119
  runDiscovery()
124
120
  return
125
121
  }
@@ -4,11 +4,9 @@ import { getAddress, type Address } from 'viem'
4
4
  import { Surface } from '../../../ui/Surface.js'
5
5
  import { Select, type SelectOption } from '../../../ui/Select.js'
6
6
  import { theme } from '../../../ui/theme.js'
7
- import {
8
- formatRecordValue,
9
- recordLabel,
10
- type AgentEnsRecords,
11
- type AgentRecordDiff,
7
+ import type {
8
+ AgentEnsRecords,
9
+ AgentRecordDiff,
12
10
  } from '../../ens/agentRecords.js'
13
11
  import type { EnsValidation } from '../../ens/ensLookup.js'
14
12
  import type {
@@ -23,9 +21,9 @@ import {
23
21
  import { ensValidationReasonText } from './state.js'
24
22
  import { shortAddress } from '../shared/model/format.js'
25
23
  import {
24
+ abbreviateHexBlobs,
26
25
  manualReasonTitle,
27
26
  modeSwitchHeading,
28
- setupSwitchNotice,
29
27
  } from './editCopy.js'
30
28
  import {
31
29
  EnsSetupRow,
@@ -114,7 +112,6 @@ export const EnsSetupReviewScreen: React.FC<EnsSetupReviewScreenProps> = ({
114
112
  type Action = 'begin' | 'back'
115
113
  const isSimple = setup.mode === 'simple'
116
114
  const signerLabel = isSimple ? 'Connected wallet' : 'Owner wallet'
117
- const switchNotice = setupSwitchNotice(currentEnsName, currentMode, setup.fullName, setup.mode)
118
115
  const createLabel = setup.registryAction === 'create-subdomain'
119
116
  ? 'Create Subdomain'
120
117
  : setup.registryAction === 'create-wrapped-subdomain'
@@ -140,7 +137,6 @@ export const EnsSetupReviewScreen: React.FC<EnsSetupReviewScreenProps> = ({
140
137
  ) : null}
141
138
  <Box flexDirection="column">
142
139
  <Text color={theme.dim}>{modeSwitchHeading(currentEnsName, currentMode, setup.fullName, setup.mode)}</Text>
143
- {switchNotice ? <Text color={theme.dim}>{switchNotice}</Text> : null}
144
140
  <EnsSetupRow label="ENS name" value={setup.fullName} />
145
141
  <EnsSetupRow label="Parent root" value={setup.rootName} />
146
142
  <EnsSetupRow label="Subdomain label" value={setup.label} />
@@ -194,7 +190,9 @@ export const EnsSetupBlockedScreen: React.FC<EnsSetupBlockedScreenProps> = ({
194
190
  {isSimple
195
191
  ? <EnsSetupRow label="Wallet" value={fallback.ownerAddress ? shortAddress(fallback.ownerAddress) : shortAddress(fallback.operatorAddress)} />
196
192
  : (fallback.ownerAddress ? <EnsSetupRow label="Owner wallet" value={shortAddress(fallback.ownerAddress)} /> : null)}
197
- {fallback.nextRecords?.token ? <EnsSetupRow label="Token link" value={fallback.nextRecords.token} /> : null}
193
+ {fallback.nextRecords && Object.keys(fallback.nextRecords).length > 0
194
+ ? <EnsSetupRow label="Attestation" value="ENSIP-25 agent-registration record" />
195
+ : null}
198
196
  <EnsSetupRow label="Address" value={`Set the subdomain address record to the ${isSimple ? 'connected wallet' : 'owner wallet'}.`} />
199
197
  {!isSimple
200
198
  ? (
@@ -270,8 +268,8 @@ export const UnlinkEnsReviewScreen: React.FC<UnlinkEnsReviewScreenProps> = ({
270
268
  <Text color={theme.textSubtle}>Will be cleared:</Text>
271
269
  {changedDiffs.map(diff => (
272
270
  <Text key={diff.key}>
273
- <Text color={theme.dim}>{` ${diff.key} `}</Text>
274
- <Text color={theme.accentPeriwinkle}>{formatRecordValue(diff.field, diff.current)}</Text>
271
+ <Text color={theme.dim}>{` ${abbreviateHexBlobs(diff.key)} `}</Text>
272
+ <Text color={theme.accentPeriwinkle}>{abbreviateHexBlobs(diff.current)}</Text>
275
273
  </Text>
276
274
  ))}
277
275
  </Box>
@@ -342,7 +340,6 @@ export const ReviewScreen: React.FC<ReviewScreenProps> = ({
342
340
  const changedDiffs = recordsDiff.filter(d => d.changed)
343
341
  const hasRecordChanges = changedDiffs.length > 0
344
342
  const reviewSubtitle = 'Review Ethereum Mainnet ENS address and text records before saving this ENS link. No token approval is requested.'
345
- const switchNotice = setupSwitchNotice(currentEnsName, currentMode, fullName, mode)
346
343
 
347
344
  if (!validation.ok) {
348
345
  const reason = ensValidationReasonText(validation.reason)
@@ -396,7 +393,7 @@ export const ReviewScreen: React.FC<ReviewScreenProps> = ({
396
393
 
397
394
  const options: Array<SelectOption<ReviewAction>> = [
398
395
  { value: 'continue', role: 'section', label: modeSwitchHeading(currentEnsName, currentMode, fullName, mode) },
399
- { value: 'continue', label: 'Continue Setup', hint: hasRecordChanges ? `Ethereum Mainnet: sign ${changedDiffs.length} ENS record value${changedDiffs.length === 1 ? '' : 's'}; ${registryNetworkLabel}: save token URI` : `ENS records already match; ${registryNetworkLabel}: save token URI` },
396
+ { value: 'continue', label: 'Continue Setup', hint: hasRecordChanges ? `Ethereum Mainnet: sign ${changedDiffs.length} record${changedDiffs.length === 1 ? '' : 's'}; ${registryNetworkLabel}: save token URI` : `Records match; ${registryNetworkLabel}: save token URI` },
400
397
  { value: 'change', label: 'Pick A Different Name', hint: 'Return to the name picker' },
401
398
  { value: 'back', role: 'section', label: 'Navigation' },
402
399
  { value: 'back', label: 'Back', hint: 'Return to Identity Hub', role: 'utility' },
@@ -414,22 +411,21 @@ export const ReviewScreen: React.FC<ReviewScreenProps> = ({
414
411
  <Box marginBottom={1} flexDirection="column">
415
412
  <Text color={theme.dim}>Current: <Text color={currentEnsName ? theme.text : theme.dim}>{currentEnsName || 'None'}</Text></Text>
416
413
  <Text color={theme.dim}>Next: <Text color={theme.text}>{fullName}</Text></Text>
417
- {switchNotice ? <Text color={theme.dim}>{switchNotice}</Text> : null}
418
414
  </Box>
419
415
  )
420
416
  : null}
421
417
  {recordsDiff.map(diff => (
422
418
  <Text key={diff.key}>
423
- <Text color={theme.dim}>{`- ${recordLabel(diff.field)}: `}</Text>
419
+ <Text color={theme.dim}>{`- ${abbreviateHexBlobs(diff.key)}: `}</Text>
424
420
  {diff.changed
425
421
  ? (
426
422
  <>
427
- {renderRecordValue(diff.field, diff.current)}
423
+ {renderRecordValue(diff.current)}
428
424
  <Text color={theme.dim}>{' → '}</Text>
429
- {renderRecordValue(diff.field, diff.next)}
425
+ {renderRecordValue(diff.next)}
430
426
  </>
431
427
  )
432
- : renderRecordValue(diff.field, diff.next)}
428
+ : renderRecordValue(diff.next)}
433
429
  </Text>
434
430
  ))}
435
431
  {!hasRecordChanges
@@ -4,10 +4,6 @@ import type { Address } from 'viem'
4
4
  import { Surface } from '../../../ui/Surface.js'
5
5
  import { TextInput } from '../../../ui/TextInput.js'
6
6
  import { theme } from '../../../ui/theme.js'
7
- import {
8
- formatRecordValue,
9
- type AgentEnsRecordState,
10
- } from '../../ens/agentRecords.js'
11
7
  import {
12
8
  isEthDomain,
13
9
  sanitizeSubdomainPrefix,
@@ -24,9 +20,11 @@ import type { EnsEditProps } from './types.js'
24
20
 
25
21
  export const footerHint = (hint: string) => <Text color={theme.dim}>{hint}</Text>
26
22
 
27
- export const renderRecordValue = (field: keyof AgentEnsRecordState, value: string) =>
23
+ import { abbreviateHexBlobs } from './editCopy.js'
24
+
25
+ export const renderRecordValue = (value: string) =>
28
26
  value
29
- ? <Text color={theme.accentPeriwinkle}>{formatRecordValue(field, value)}</Text>
27
+ ? <Text color={theme.accentPeriwinkle}>{abbreviateHexBlobs(value)}</Text>
30
28
  : <Text color={theme.dim}>Unset</Text>
31
29
 
32
30
  export function rootErrorMessage(
@@ -135,7 +135,7 @@ export function renderSimpleEnsPhase({
135
135
  ]
136
136
  : []),
137
137
  { value: 'open-ens-domains' as DomainAction, role: 'section' as const, label: 'No Parent Name?' },
138
- { value: 'open-ens-domains' as DomainAction, label: 'Register .eth Name', hint: 'Open the ENS app in your browser; come back when this wallet owns one' },
138
+ { value: 'open-ens-domains' as DomainAction, label: 'Register .eth Name', hint: 'Opens ENS app; return once this wallet owns one' },
139
139
  ...(noOwnedNames || discovery.status === 'ok'
140
140
  ? [{ value: 'retry' as DomainAction, label: 'Scan Again', hint: 'Re-run root .eth name discovery for this wallet' }]
141
141
  : []),
@@ -356,7 +356,7 @@ export function renderSimpleEnsPhase({
356
356
  onEnsLink(phase.fullName, linkOptions)
357
357
  }}
358
358
  onCheckAgain={() => { void runValidation(phase.fullName, phase.mode, phase.ownerAddress, phase.operatorWallet) }}
359
- onChange={() => setPhase(phase.mode === 'advanced' ? { kind: 'advanced-transfer-check' } : { kind: 'pick-parent' })}
359
+ onChange={() => setPhase({ kind: 'pick-parent', mode: phase.mode })}
360
360
  onCreate={phase.mode === 'simple' && !phase.validation.ok && phase.validation.reason === 'no-owner'
361
361
  ? () => runSimpleCreatePreflight(phase.fullName)
362
362
  : undefined}
@@ -34,7 +34,6 @@ type EnsFlowProps = {
34
34
  onWalletReady: (session: BrowserWalletReady | null) => void
35
35
  onTriggerRebackup: (backStep: Step, profileUpdates?: ProfileUpdates) => void
36
36
  onTriggerPublicProfileSave: (backStep: Step, profileUpdates: ProfileUpdates) => void
37
- onWithdrawTokenForEns: (step: Step) => void
38
37
  }
39
38
 
40
39
  export function isEnsStep(step: Step): step is IdentityHubEnsStep {
@@ -59,7 +58,6 @@ export const EnsFlow: React.FC<EnsFlowProps> = ({
59
58
  onWalletReady,
60
59
  onTriggerRebackup,
61
60
  onTriggerPublicProfileSave,
62
- onWithdrawTokenForEns,
63
61
  }) => {
64
62
  if (step.kind === 'manage-ens-operators') {
65
63
  return (
@@ -81,7 +79,6 @@ export const EnsFlow: React.FC<EnsFlowProps> = ({
81
79
  <EditProfileFlow
82
80
  step={step}
83
81
  reconciliation={reconciliation}
84
- onWithdrawToken={() => onWithdrawTokenForEns(step)}
85
82
  onNameSubmit={name => {
86
83
  if (step.kind !== 'edit-profile-name') return
87
84
  onSetStep({