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
@@ -3,6 +3,12 @@ import type { AgentEnsRecords, AgentRecordDiff } from '../../ens/agentRecords.js
3
3
  import type { EnsRegistryAction, EnsSetupBlockedPlan } from '../../ens/ensAutomation.js'
4
4
  import type { CustodyMode } from '../custody/state.js'
5
5
 
6
+ export function abbreviateHexBlobs(input: string): string {
7
+ return input.replace(/0x([0-9a-fA-F]{20,})/g, (_match, hex) => {
8
+ return `0x${hex.slice(0, 8)}...${hex.slice(-8)}`
9
+ })
10
+ }
11
+
6
12
  export type EnsLinkOptions = {
7
13
  mode: 'simple' | 'advanced'
8
14
  ownerAddress?: Address
@@ -18,7 +24,7 @@ export function recordsHaveCurrentValues(recordsDiff: AgentRecordDiff[]): boolea
18
24
  }
19
25
 
20
26
  export function emptyAgentEnsRecords(): AgentEnsRecords {
21
- return { token: '' }
27
+ return {}
22
28
  }
23
29
 
24
30
  export function unlinkEnsLinkOptions(savedCustodyMode: CustodyMode | undefined, savedOwnerAddress: string): EnsLinkOptions {
@@ -46,18 +52,6 @@ export function modeSwitchHeading(
46
52
  return 'Automation'
47
53
  }
48
54
 
49
- export function setupSwitchNotice(
50
- currentEnsName: string,
51
- currentMode: CustodyMode | undefined,
52
- nextEnsName: string,
53
- nextMode: 'simple' | 'advanced',
54
- ): string | null {
55
- if (!currentEnsName && !currentMode) return null
56
- const currentTopology = currentMode === 'advanced' ? 'advanced' : currentMode === 'simple' ? 'simple' : undefined
57
- if (currentEnsName === nextEnsName && currentTopology === nextMode) return null
58
- return 'This replaces the saved ENS setup directly. Reset is only for clearing the current link.'
59
- }
60
-
61
55
  export function advancedSubdomainStatusText(action: EnsRegistryAction): string {
62
56
  switch (action) {
63
57
  case 'create-subdomain':
@@ -84,8 +78,6 @@ export function manualReasonTitle(reason: EnsSetupBlockedPlan['reason']): string
84
78
  case 'wrapped-parent':
85
79
  case 'subdomain-wrapped':
86
80
  return 'ENS NameWrapper ownership could not be verified'
87
- case 'token-record-collision':
88
- return 'Subdomain already points to another token'
89
81
  case 'token-owner-mismatch':
90
82
  return 'Owner wallet does not own this ERC-8004 token'
91
83
  case 'token-owner-lookup-failed':
@@ -5,10 +5,11 @@ import {
5
5
  createErc8004PublicClient,
6
6
  supportedErc8004ChainForId,
7
7
  } from '../../registry/erc8004.js'
8
- import { encodeSetEthagentTextRecords } from '../../ens/ensLookup.js'
9
- import { encodeEnsRecordsTransaction, encodeEnsRegistryTransaction, type EnsSetupPlan } from '../../ens/ensAutomation.js'
10
- import type { AgentEnsRecordState, AgentEnsRecords } from '../../ens/agentRecords.js'
11
- import { changedRecords } from '../../ens/agentRecords.js'
8
+ import { encodeSetEnsip25TextRecord, readEthagentTextRecords } from '../../ens/ensLookup.js'
9
+ import { encodeEnsRecordsTransaction, encodeEnsRegistryTransaction, readAddressRecord, type EnsSetupPlan } from '../../ens/ensAutomation.js'
10
+ import type { AgentEnsRecordState, AgentEnsRecords, AgentRecordDiff } from '../../ens/agentRecords.js'
11
+ import { changedRecords, clearedRecords, diffRecords } from '../../ens/agentRecords.js'
12
+ import { namehash, getAddress } from 'viem'
12
13
  import { sendBrowserWalletTransaction, type BrowserWalletSession, type WalletPurpose } from '../../wallet/browserWallet.js'
13
14
  import type { EffectCallbacks } from '../shared/effects/types.js'
14
15
  function chainLabel(chainId: number): string {
@@ -32,17 +33,29 @@ export async function runUpdateEnsRecords(args: {
32
33
  session?: BrowserWalletSession
33
34
  flowId?: string
34
35
  flowStep?: number
35
- }): Promise<{ txHash: string }> {
36
+ }): Promise<{ txHash: string } | { skipped: true }> {
37
+ const publicClient = args.publicClient ?? createMainnetEnsPublicClient()
38
+ const queryKeys = Array.from(new Set([
39
+ ...Object.keys(args.records ?? {}),
40
+ ...Object.keys(args.currentRecords ?? {}),
41
+ ]))
42
+ let freshCurrent: AgentEnsRecordState = args.currentRecords ?? {}
43
+ if (queryKeys.length > 0) {
44
+ try {
45
+ freshCurrent = await readEthagentTextRecords(args.fullName, queryKeys, { publicClient })
46
+ } catch {
47
+ freshCurrent = args.currentRecords ?? {}
48
+ }
49
+ }
36
50
  const next = ensRecordWritesForUpdate({
37
51
  records: args.records,
38
- currentRecords: args.currentRecords,
52
+ currentRecords: freshCurrent,
39
53
  clearRecords: args.clearRecords,
40
54
  })
41
55
  if (Object.keys(next).length === 0) {
42
- throw new Error('No ENS records to update')
56
+ return { skipped: true }
43
57
  }
44
- const publicClient = args.publicClient ?? createMainnetEnsPublicClient()
45
- const encoded = await encodeSetEthagentTextRecords(args.fullName, next, { publicClient })
58
+ const encoded = await encodeSetEnsip25TextRecord(args.fullName, next, { publicClient })
46
59
  await preflightEnsRecordTransaction({
47
60
  fullName: args.fullName,
48
61
  account: args.ownerAddress,
@@ -83,10 +96,11 @@ export function ensRecordWritesForUpdate(args: {
83
96
  currentRecords?: AgentEnsRecordState
84
97
  clearRecords?: boolean
85
98
  }): Record<string, string> {
99
+ const current = args.currentRecords ?? {}
86
100
  if (args.clearRecords) {
87
- return changedRecords(args.currentRecords ?? {}, { token: '' })
101
+ return changedRecords(current, clearedRecords(current))
88
102
  }
89
- return changedRecords(args.currentRecords ?? { token: '' }, args.records)
103
+ return changedRecords(current, args.records)
90
104
  }
91
105
 
92
106
  export async function runEnsSetupRegistryTransaction(args: {
@@ -148,13 +162,14 @@ export async function runEnsSetupRecordsTransaction(args: {
148
162
  flowId?: string
149
163
  flowStep?: number
150
164
  }): Promise<{ txHash: string } | null> {
151
- const encoded = encodeEnsRecordsTransaction(args.setup)
152
- if (!encoded) return null
153
165
  const publicClient = args.publicClient ?? createMainnetEnsPublicClient()
154
- const purpose: WalletPurpose = args.setup.mode === 'simple' ? 'set-simple-ens-records' : 'set-agent-ens-records'
166
+ const freshSetup = await refreshEnsSetupAgainstChain(args.setup, publicClient)
167
+ const encoded = encodeEnsRecordsTransaction(freshSetup)
168
+ if (!encoded) return null
169
+ const purpose: WalletPurpose = freshSetup.mode === 'simple' ? 'set-simple-ens-records' : 'set-agent-ens-records'
155
170
  await preflightEnsRecordTransaction({
156
- fullName: args.setup.fullName,
157
- account: args.setup.ownerAddress,
171
+ fullName: freshSetup.fullName,
172
+ account: freshSetup.ownerAddress,
158
173
  to: encoded.to,
159
174
  data: encoded.data,
160
175
  publicClient,
@@ -164,7 +179,7 @@ export async function runEnsSetupRecordsTransaction(args: {
164
179
  if (args.session) {
165
180
  const result = await args.session.sendTransaction({
166
181
  chainId: 1,
167
- expectedAccount: args.setup.ownerAddress,
182
+ expectedAccount: freshSetup.ownerAddress,
168
183
  to: encoded.to,
169
184
  data: encoded.data,
170
185
  purpose,
@@ -176,7 +191,7 @@ export async function runEnsSetupRecordsTransaction(args: {
176
191
  }
177
192
  const result = await sendBrowserWalletTransaction({
178
193
  chainId: 1,
179
- expectedAccount: args.setup.ownerAddress,
194
+ expectedAccount: freshSetup.ownerAddress,
180
195
  to: encoded.to,
181
196
  data: encoded.data,
182
197
  purpose,
@@ -187,6 +202,40 @@ export async function runEnsSetupRecordsTransaction(args: {
187
202
  return { txHash: result.txHash }
188
203
  }
189
204
 
205
+ async function refreshEnsSetupAgainstChain(setup: EnsSetupPlan, publicClient: PublicClient): Promise<EnsSetupPlan> {
206
+ if (setup.registryAction !== 'none' && !setup.recordDiffs.length && !setup.addressRecord.changed) {
207
+ return setup
208
+ }
209
+ const node = namehash(setup.fullName)
210
+ const keys = Array.from(new Set(setup.recordDiffs.map(diff => diff.key)))
211
+ let freshCurrent: AgentEnsRecordState = setup.currentRecords
212
+ if (keys.length > 0) {
213
+ try {
214
+ freshCurrent = await readEthagentTextRecords(setup.fullName, keys, { publicClient })
215
+ } catch {
216
+ freshCurrent = setup.currentRecords
217
+ }
218
+ }
219
+ let freshAddress = setup.addressRecord.current
220
+ try {
221
+ freshAddress = await readAddressRecord(publicClient, setup.resolverAddress, node)
222
+ } catch {
223
+ freshAddress = setup.addressRecord.current
224
+ }
225
+ const addressChanged = !freshAddress || getAddress(freshAddress).toLowerCase() !== getAddress(setup.addressRecord.next).toLowerCase()
226
+ const recordDiffs: AgentRecordDiff[] = diffRecords(freshCurrent, setup.nextRecords)
227
+ return {
228
+ ...setup,
229
+ currentRecords: freshCurrent,
230
+ recordDiffs,
231
+ addressRecord: {
232
+ current: freshAddress,
233
+ next: setup.addressRecord.next,
234
+ changed: addressChanged,
235
+ },
236
+ }
237
+ }
238
+
190
239
  export function createMainnetEnsPublicClient(): PublicClient {
191
240
  return createErc8004PublicClient({
192
241
  chainId: 1,
@@ -31,7 +31,6 @@ export type SimpleEnsPhase =
31
31
  | { kind: 'review'; fullName: string; validation: EnsValidation; recordsDiff: AgentRecordDiff[]; currentRecords: AgentEnsRecordState; nextRecords: AgentEnsRecords; mode: 'simple' | 'advanced'; ownerAddress?: Address; operatorWallet?: Address }
32
32
 
33
33
  export type AdvancedEnsPhase =
34
- | { kind: 'advanced-transfer-check' }
35
34
  | { kind: 'advanced-root-check'; rootName: string }
36
35
  | { kind: 'advanced-subdomain'; rootName: string; label?: string; error?: string }
37
36
  | { kind: 'advanced-subdomain-check'; rootName: string; label: string }
@@ -71,7 +70,6 @@ export type EnsEditProps = {
71
70
  onEnsRecordsUpdate: (fullName: string, records: AgentEnsRecords, options: EnsLinkOptions, clearRecords?: boolean, currentRecords?: AgentEnsRecordState) => void
72
71
  onEnsSetup: (setup: EnsSetupPlan) => void
73
72
  onManageOperatorWalletAccess: () => void
74
- onWithdrawToken: () => void
75
73
  initialView?: 'advanced'
76
74
  onBack: () => void
77
75
  }
@@ -26,7 +26,6 @@ type EditProfileFlowProps = {
26
26
  onEnsRecordsUpdate: (fullName: string, records: AgentEnsRecords, options: EnsLinkOptions, clearRecords?: boolean, currentRecords?: AgentEnsRecordState) => void
27
27
  onEnsSetup: (setup: EnsSetupPlan) => void
28
28
  onManageOperatorWalletAccess: () => void
29
- onWithdrawToken: () => void
30
29
  onBack: () => void
31
30
  onMenu: () => void
32
31
  }
@@ -49,7 +48,6 @@ export const EditProfileFlow: React.FC<EditProfileFlowProps> = ({
49
48
  onEnsRecordsUpdate,
50
49
  onEnsSetup,
51
50
  onManageOperatorWalletAccess,
52
- onWithdrawToken,
53
51
  onBack,
54
52
  onMenu,
55
53
  }) => {
@@ -101,7 +99,6 @@ export const EditProfileFlow: React.FC<EditProfileFlowProps> = ({
101
99
  onEnsRecordsUpdate={onEnsRecordsUpdate}
102
100
  onEnsSetup={onEnsSetup}
103
101
  onManageOperatorWalletAccess={onManageOperatorWalletAccess}
104
- onWithdrawToken={onWithdrawToken}
105
102
  initialView={step.initialView}
106
103
  onBack={onBack}
107
104
  />
@@ -6,15 +6,9 @@ import {
6
6
  type WalletChallengePurpose,
7
7
  } from '../../continuity/envelope.js'
8
8
  import {
9
- prepareSyncedPublicSkillsJson,
10
- writePublicSkillsFile,
9
+ writeAgentCardFile,
11
10
  } from '../../continuity/storage.js'
12
- import {
13
- createAgentCard,
14
- defaultPublicSkillsProfile,
15
- serializeAgentCard,
16
- } from '../../continuity/publicSkills.js'
17
- import { syncPublicSkillsManifest } from '../../continuity/skills/publicSkillsSync.js'
11
+ import { syncAgentCardManifest } from '../../continuity/skills/publicSkillsSync.js'
18
12
  import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
19
13
  import { addToIpfs, DEFAULT_IPFS_API_URL, isPinataUploadUrl } from '../../storage/ipfs.js'
20
14
  import {
@@ -63,15 +57,15 @@ import {
63
57
  prepareOperatorProfileArtifacts,
64
58
  } from './operatorSave.js'
65
59
 
66
- type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
60
+ type AgentCardMetadata = NonNullable<EthagentIdentity['agentCard']>
67
61
 
68
62
  type PublicProfilePreparedTransaction = {
69
63
  ownerAddress: Address
70
64
  agentUri: string
71
65
  metadataCid: string
72
- publicSkills: PublicSkillsMetadata
66
+ agentCard: AgentCardMetadata
73
67
  identity: EthagentIdentity
74
- publicSkillsJson: string
68
+ agentCardJson: string
75
69
  }
76
70
 
77
71
  export async function runPublicProfilePreflight(
@@ -151,20 +145,12 @@ async function runPublicProfileSigningInner(
151
145
  includeLastBackedUpAt: false,
152
146
  })
153
147
  const nextIdentityForFiles: EthagentIdentity = { ...step.identity, state }
154
- const publicSkillsJson = await syncPublicSkillsManifest(nextIdentityForFiles)
155
- const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
156
- assertVerifiedPin(publicSkillsPin)
157
- const agentCardPin = await addToIpfs(
158
- DEFAULT_IPFS_API_URL,
159
- serializeAgentCard(createAgentCard(defaultPublicSkillsProfile(nextIdentityForFiles))),
160
- fetch,
161
- { pinataJwt: step.pinataJwt },
162
- )
148
+ const agentCardJson = await syncAgentCardManifest(nextIdentityForFiles)
149
+ const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, agentCardJson, fetch, { pinataJwt: step.pinataJwt })
163
150
  assertVerifiedPin(agentCardPin)
164
151
  const updatedAt = new Date().toISOString()
165
- const publicSkills: PublicSkillsMetadata = {
166
- cid: publicSkillsPin.cid,
167
- agentCardCid: agentCardPin.cid,
152
+ const agentCard: AgentCardMetadata = {
153
+ cid: agentCardPin.cid,
168
154
  updatedAt,
169
155
  status: 'pinned',
170
156
  }
@@ -181,8 +167,7 @@ async function runPublicProfileSigningInner(
181
167
  }, {
182
168
  backup: existingBackup,
183
169
  publicDiscovery: {
184
- skillsCid: publicSkills.cid,
185
- agentCardCid: publicSkills.agentCardCid,
170
+ agentCardCid: agentCard.cid,
186
171
  updatedAt,
187
172
  },
188
173
  registration: {
@@ -212,9 +197,9 @@ async function runPublicProfileSigningInner(
212
197
  ownerAddress: snapshotOwner,
213
198
  agentUri,
214
199
  metadataCid,
215
- publicSkills,
200
+ agentCard,
216
201
  identity: { ...step.identity, state },
217
- publicSkillsJson,
202
+ agentCardJson,
218
203
  },
219
204
  }
220
205
  },
@@ -231,9 +216,9 @@ async function runPublicProfileSigningInner(
231
216
  identityRegistryAddress: step.registry.identityRegistryAddress,
232
217
  agentUri: result.prepared.agentUri,
233
218
  metadataCid: result.prepared.metadataCid,
234
- publicSkills: result.prepared.publicSkills,
219
+ agentCard: result.prepared.agentCard,
235
220
  }
236
- await writePublicSkillsFile(nextIdentity, result.prepared.publicSkillsJson)
221
+ await writeAgentCardFile(nextIdentity, result.prepared.agentCardJson)
237
222
  await markCurrentContinuityFilesPublished(nextIdentity)
238
223
  const resolverSyncWarning = await syncVaultOperatorsAfterOwnerSave({
239
224
  beforeIdentity: step.identity,
@@ -345,7 +330,7 @@ async function runOperatorWalletVaultPublicProfileSave(args: {
345
330
  ? { ...prepared.nextIdentity.backup, txHash: vaultTx.txHash }
346
331
  : prepared.nextIdentity.backup,
347
332
  }
348
- await writePublicSkillsFile(nextIdentity, prepared.publicSkillsJson)
333
+ await writeAgentCardFile(nextIdentity, prepared.agentCardJson)
349
334
  await markCurrentContinuityFilesPublished(nextIdentity).catch(() => null)
350
335
  await recordPublishedContinuitySnapshot({
351
336
  identity: nextIdentity,
@@ -74,16 +74,14 @@ export function identitySummaryRows(
74
74
  const tokenValue = identity?.agentId ? `#${identity.agentId}` : 'not created'
75
75
  const chain = chainSummaryRow(config, identity)
76
76
  const stateValue = backup?.cid ? shortCid(backup.cid) : 'not saved yet'
77
- const skillsValue = identity?.publicSkills?.cid ? shortCid(identity.publicSkills.cid) : 'not saved'
78
- const cardValue = identity?.publicSkills?.agentCardCid ? shortCid(identity.publicSkills.agentCardCid) : 'not saved'
77
+ const cardValue = identity?.agentCard?.cid ? shortCid(identity.agentCard.cid) : 'not saved'
79
78
  const iconValue = typeof identity?.state?.imageUrl === 'string' && identity.state.imageUrl.trim() ? 'attached' : 'not attached'
80
79
  return [
81
80
  { label: 'owner wallet', value: ownerValue, tone: identity ? 'ok' : 'dim' },
82
81
  { label: 'token', value: tokenValue, tone: identity?.agentId ? 'ok' : 'dim' },
83
82
  { label: 'network', value: chain.value, tone: chain.tone },
84
83
  { label: 'state', value: stateValue, tone: backup ? 'ok' : 'dim' },
85
- { label: 'skills', value: skillsValue, tone: identity?.publicSkills?.cid ? 'ok' : 'dim' },
86
- { label: 'card', value: cardValue, tone: identity?.publicSkills?.agentCardCid ? 'ok' : 'dim' },
84
+ { label: 'card', value: cardValue, tone: identity?.agentCard?.cid ? 'ok' : 'dim' },
87
85
  { label: 'icon', value: iconValue, tone: iconValue === 'attached' ? 'ok' : 'dim' },
88
86
  ]
89
87
  }
@@ -10,14 +10,7 @@ import {
10
10
  readContinuityFiles,
11
11
  } from '../../continuity/storage.js'
12
12
  import {
13
- appendPublicSkillEntries,
14
- createAgentCard,
15
- defaultPublicSkillsProfile,
16
- serializeAgentCard,
17
- } from '../../continuity/publicSkills.js'
18
- import {
19
- derivePublicSkillEntries,
20
- syncPublicSkillsManifest,
13
+ syncAgentCardManifest,
21
14
  } from '../../continuity/skills/publicSkillsSync.js'
22
15
  import { addToIpfs, DEFAULT_IPFS_API_URL } from '../../storage/ipfs.js'
23
16
  import {
@@ -40,12 +33,12 @@ import {
40
33
  } from '../continuity/snapshot.js'
41
34
 
42
35
  type BackupMetadata = NonNullable<EthagentIdentity['backup']>
43
- type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
36
+ type AgentCardMetadata = NonNullable<EthagentIdentity['agentCard']>
44
37
  type WalletAccessContext = NonNullable<ReturnType<typeof walletRestoreAccessContext>>
45
38
 
46
39
  export type OperatorProfileArtifacts = {
47
40
  nextIdentity: EthagentIdentity
48
- publicSkillsJson: string
41
+ agentCardJson: string
49
42
  agentUri: string
50
43
  metadataCid: string
51
44
  }
@@ -75,20 +68,8 @@ export async function prepareOperatorProfileArtifacts(args: {
75
68
  })
76
69
  const nextIdentityForFiles: EthagentIdentity = { ...step.identity, state }
77
70
 
78
- const publicSkillsJson = await syncPublicSkillsManifest(nextIdentityForFiles)
79
- const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
80
- assertVerifiedPin(publicSkillsPin)
81
- const publicSkillEntries = await derivePublicSkillEntries(nextIdentityForFiles)
82
- const augmentedPublicProfile = appendPublicSkillEntries(
83
- defaultPublicSkillsProfile(nextIdentityForFiles),
84
- publicSkillEntries,
85
- )
86
- const agentCardPin = await addToIpfs(
87
- DEFAULT_IPFS_API_URL,
88
- serializeAgentCard(createAgentCard(augmentedPublicProfile)),
89
- fetch,
90
- { pinataJwt: step.pinataJwt },
91
- )
71
+ const agentCardJson = await syncAgentCardManifest(nextIdentityForFiles)
72
+ const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, agentCardJson, fetch, { pinataJwt: step.pinataJwt })
92
73
  assertVerifiedPin(agentCardPin)
93
74
 
94
75
  const continuityFiles = await readContinuityFiles(nextIdentityForFiles)
@@ -108,9 +89,8 @@ export async function prepareOperatorProfileArtifacts(args: {
108
89
  const statePin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeContinuitySnapshotEnvelope(envelope), fetch, { pinataJwt: step.pinataJwt })
109
90
  assertVerifiedPin(statePin)
110
91
 
111
- const publicSkills: PublicSkillsMetadata = {
112
- cid: publicSkillsPin.cid,
113
- agentCardCid: agentCardPin.cid,
92
+ const agentCard: AgentCardMetadata = {
93
+ cid: agentCardPin.cid,
114
94
  updatedAt: envelope.createdAt,
115
95
  status: 'pinned',
116
96
  }
@@ -134,7 +114,7 @@ export async function prepareOperatorProfileArtifacts(args: {
134
114
  ...(uploadedImageUri ? { image: uploadedImageUri } : {}),
135
115
  }, {
136
116
  backup: { cid: statePin.cid, envelopeVersion: envelope.envelopeVersion, createdAt: envelope.createdAt },
137
- publicDiscovery: { skillsCid: publicSkills.cid, agentCardCid: publicSkills.agentCardCid, updatedAt: publicSkills.updatedAt },
117
+ publicDiscovery: { agentCardCid: agentCard.cid, updatedAt: agentCard.updatedAt },
138
118
  registration: { chainId: step.registry.chainId, identityRegistryAddress: step.registry.identityRegistryAddress, agentId: step.identity.agentId },
139
119
  ensName: nextEnsName,
140
120
  operators: operatorsPointerFromState(state, nextEnsName),
@@ -149,14 +129,14 @@ export async function prepareOperatorProfileArtifacts(args: {
149
129
  ...step.identity,
150
130
  state,
151
131
  backup: { ...backup, metadataCid, agentUri },
152
- publicSkills,
132
+ agentCard,
153
133
  agentUri,
154
134
  metadataCid,
155
135
  }
156
136
 
157
137
  return {
158
138
  nextIdentity,
159
- publicSkillsJson,
139
+ agentCardJson,
160
140
  agentUri,
161
141
  metadataCid,
162
142
  }
@@ -101,7 +101,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
101
101
  if (step.kind === 'restore-recovery-input') {
102
102
  return (
103
103
  <Surface
104
- title={isSwitch ? 'Load Agent' : 'Restore Agent'}
104
+ title={isSwitch ? 'Switch Agent' : 'Restore Agent'}
105
105
  subtitle="The connected wallet doesn't directly own an agent token on this network."
106
106
  footer={footerHint('enter select · esc back')}
107
107
  >
@@ -126,7 +126,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
126
126
  if (step.busy) {
127
127
  return (
128
128
  <Surface
129
- title={isSwitch ? 'Load Agent' : 'Restore Agent'}
129
+ title={isSwitch ? 'Switch Agent' : 'Restore Agent'}
130
130
  subtitle="Looking up the agent onchain."
131
131
  footer={footerHint('esc cancels')}
132
132
  >
@@ -138,7 +138,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
138
138
  }
139
139
  return (
140
140
  <Surface
141
- title={isSwitch ? 'Load Agent' : 'Restore Agent'}
141
+ title={isSwitch ? 'Switch Agent' : 'Restore Agent'}
142
142
  subtitle="Enter the agent's ENS name to decrypt with an authorized operator wallet."
143
143
  footer={footerHint('enter continue · esc back')}
144
144
  >
@@ -162,7 +162,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
162
162
  if (step.busy) {
163
163
  return (
164
164
  <Surface
165
- title={isSwitch ? 'Load Agent' : 'Restore Agent'}
165
+ title={isSwitch ? 'Switch Agent' : 'Restore Agent'}
166
166
  subtitle="Looking up the agent onchain."
167
167
  footer={footerHint('esc cancels')}
168
168
  >
@@ -174,7 +174,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
174
174
  }
175
175
  return (
176
176
  <Surface
177
- title={isSwitch ? 'Load Agent' : 'Restore Agent'}
177
+ title={isSwitch ? 'Switch Agent' : 'Restore Agent'}
178
178
  subtitle={`Enter the ERC-8004 token ID on ${networkLabelForRegistry(step.registry)}.`}
179
179
  footer={footerHint('enter continue · esc back')}
180
180
  >
@@ -223,7 +223,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
223
223
  if (step.kind === 'restore-select-token') {
224
224
  return (
225
225
  <Surface
226
- title={isSwitch ? 'Load an Agent' : 'Choose Your Agent'}
226
+ title={isSwitch ? 'Switch Agent' : 'Choose Your Agent'}
227
227
  subtitle={step.ownerHandle}
228
228
  footer={footerHint('enter select · esc back')}
229
229
  >
@@ -260,7 +260,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
260
260
  if (step.kind === 'restore-fetching') {
261
261
  return (
262
262
  <BusyScreen
263
- title={isSwitch ? 'Loading Agent' : 'Restoring Your Agent'}
263
+ title={isSwitch ? 'Switching Agent' : 'Restoring Your Agent'}
264
264
  subtitle="IPFS"
265
265
  label="opening encrypted state from IPFS..."
266
266
  onCancel={onBack}
@@ -348,7 +348,7 @@ function restoreAuthorizationView(
348
348
  title: 'Operator Wallet Required',
349
349
  subtitle: `Sign with the operator wallet ${shortAddress(requester)} to decrypt this snapshot.`,
350
350
  label: 'waiting for operator wallet signature...',
351
- progressTitle: isSwitch ? 'Loading Agent' : 'Restoring Your Agent',
351
+ progressTitle: isSwitch ? 'Switching Agent' : 'Restoring Your Agent',
352
352
  }
353
353
  }
354
354
 
@@ -356,6 +356,6 @@ function restoreAuthorizationView(
356
356
  title: 'Owner Wallet Required',
357
357
  subtitle: `This encrypted snapshot requires the owner wallet ${shortAddress(owner)}.`,
358
358
  label: 'waiting for owner wallet signature...',
359
- progressTitle: isSwitch ? 'Loading Agent' : 'Restoring Your Agent',
359
+ progressTitle: isSwitch ? 'Switching Agent' : 'Restoring Your Agent',
360
360
  }
361
361
  }
@@ -10,7 +10,7 @@ import {
10
10
  restoreSkillsTree,
11
11
  writeContinuityFiles,
12
12
  } from '../../continuity/storage.js'
13
- import { syncPublicSkillsManifest } from '../../continuity/skills/publicSkillsSync.js'
13
+ import { syncAgentCardManifest } from '../../continuity/skills/publicSkillsSync.js'
14
14
  import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
15
15
  import { requestBrowserWalletSignature } from '../../wallet/browserWallet.js'
16
16
  import { setVaultAddressField } from '../../identityCompat.js'
@@ -18,7 +18,7 @@ import type { Step } from '../identityHubReducer.js'
18
18
  import type { EffectCallbacks } from '../shared/effects/types.js'
19
19
  import { isContinuitySnapshotEnvelope } from './envelopes.js'
20
20
  import { restoreSignatureRequestForStep } from './auth.js'
21
- import { type BackupMetadata, operatorStateFromCandidate, restorePublishedPublicSkills } from './helpers.js'
21
+ import { type BackupMetadata, operatorStateFromCandidate, restorePublishedAgentCard } from './helpers.js'
22
22
 
23
23
  export async function runRestoreAuthorize(
24
24
  step: Extract<Step, { kind: 'restore-authorizing' }>,
@@ -99,10 +99,9 @@ export async function runRestoreAuthorize(
99
99
  metadataCid: step.candidate.metadataCid,
100
100
  state: restoredState,
101
101
  backup,
102
- ...(step.candidate.publicDiscovery ? {
103
- publicSkills: {
104
- ...(step.candidate.publicDiscovery.skillsCid ? { cid: step.candidate.publicDiscovery.skillsCid } : {}),
105
- ...(step.candidate.publicDiscovery.agentCardCid ? { agentCardCid: step.candidate.publicDiscovery.agentCardCid } : {}),
102
+ ...(step.candidate.publicDiscovery?.agentCardCid ? {
103
+ agentCard: {
104
+ cid: step.candidate.publicDiscovery.agentCardCid,
106
105
  ...(step.candidate.publicDiscovery.updatedAt ? { updatedAt: step.candidate.publicDiscovery.updatedAt } : {}),
107
106
  status: 'pinned',
108
107
  },
@@ -115,9 +114,9 @@ export async function runRestoreAuthorize(
115
114
  await restoreSkillsTree(nextIdentity, continuitySkills)
116
115
  }
117
116
  callbacks.onRestoreProgress?.({ phase: 'finishing', label: 'finalizing restored identity...' })
118
- await restorePublishedPublicSkills(nextIdentity, step.apiUrl, step.candidate.publicDiscovery?.skillsCid)
117
+ await restorePublishedAgentCard(nextIdentity, step.apiUrl, step.candidate.publicDiscovery?.agentCardCid)
119
118
  await ensureIdentityMarkdownScaffold(nextIdentity)
120
- await syncPublicSkillsManifest(nextIdentity).catch(() => null)
119
+ await syncAgentCardManifest(nextIdentity).catch(() => null)
121
120
  await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'restored from agent backup' }).catch(() => null)
122
121
  await callbacks.onIdentityComplete(nextIdentity, `ERC-8004 agent restored · #${step.candidate.agentId.toString()}`, 'restore')
123
122
  }
@@ -6,7 +6,7 @@ import {
6
6
  } from '../../continuity/envelope.js'
7
7
  import { catFromIpfs } from '../../storage/ipfs.js'
8
8
  import type { Erc8004AgentCandidate } from '../../registry/erc8004.js'
9
- import { writePublicSkillsFile } from '../../continuity/storage.js'
9
+ import { writeAgentCardFile } from '../../continuity/storage.js'
10
10
  import { normalizeApprovedOperatorWallets } from '../shared/operatorWallets.js'
11
11
 
12
12
  export type BackupMetadata = NonNullable<EthagentIdentity['backup']>
@@ -75,7 +75,7 @@ export function operatorStateFromCandidate(candidate: Erc8004AgentCandidate): Re
75
75
  }
76
76
  }
77
77
 
78
- export async function restorePublishedPublicSkills(
78
+ export async function restorePublishedAgentCard(
79
79
  identity: EthagentIdentity,
80
80
  apiUrl: string,
81
81
  cid: string | undefined,
@@ -83,7 +83,7 @@ export async function restorePublishedPublicSkills(
83
83
  if (!cid) return false
84
84
  try {
85
85
  const raw = await catFromIpfs(apiUrl, cid)
86
- await writePublicSkillsFile(identity, new TextDecoder().decode(raw))
86
+ await writeAgentCardFile(identity, new TextDecoder().decode(raw))
87
87
  return true
88
88
  } catch {
89
89
  return false
@@ -12,7 +12,7 @@ import {
12
12
  restoreSkillsTree,
13
13
  writeContinuityFiles,
14
14
  } from '../../continuity/storage.js'
15
- import { syncPublicSkillsManifest } from '../../continuity/skills/publicSkillsSync.js'
15
+ import { syncAgentCardManifest } from '../../continuity/skills/publicSkillsSync.js'
16
16
  import { recordPublishedContinuitySnapshot, updatePublishedContinuitySnapshotContentHashes } from '../../continuity/snapshots.js'
17
17
  import { catFromIpfs, DEFAULT_IPFS_API_URL } from '../../storage/ipfs.js'
18
18
  import {
@@ -24,7 +24,7 @@ import { setVaultAddressField } from '../../identityCompat.js'
24
24
  import type { EffectCallbacks } from '../shared/effects/types.js'
25
25
  import { isContinuitySnapshotEnvelope, parseRestorableEnvelope } from './envelopes.js'
26
26
  import { restoreMessageForWallet } from './auth.js'
27
- import { type BackupMetadata, operatorStateFromCandidate, restorePublishedPublicSkills } from './helpers.js'
27
+ import { type BackupMetadata, operatorStateFromCandidate, restorePublishedAgentCard } from './helpers.js'
28
28
 
29
29
  export async function runRecoveryRefetch(
30
30
  identity: EthagentIdentity,
@@ -46,7 +46,7 @@ export async function runRecoveryRefetch(
46
46
  const raw = await catFromIpfs(apiUrl, candidate.backup.cid)
47
47
  const envelope = parseRestorableEnvelope(raw)
48
48
  if (!isContinuitySnapshotEnvelope(envelope)) {
49
- throw new Error('This snapshot is in an unsupported envelope format and cannot be refetched here; use Load Agent')
49
+ throw new Error('This snapshot is in an unsupported envelope format and cannot be refetched here; use Switch Agent')
50
50
  }
51
51
  const eligibleAddresses: Address[] = [ownerAddress]
52
52
  if (isWalletContinuitySnapshotEnvelope(envelope)) {
@@ -119,10 +119,9 @@ export async function runRecoveryRefetch(
119
119
  metadataCid: candidate.metadataCid,
120
120
  state: refreshedState,
121
121
  backup: refreshedBackup,
122
- ...(candidate.publicDiscovery ? {
123
- publicSkills: {
124
- ...(candidate.publicDiscovery.skillsCid ? { cid: candidate.publicDiscovery.skillsCid } : {}),
125
- ...(candidate.publicDiscovery.agentCardCid ? { agentCardCid: candidate.publicDiscovery.agentCardCid } : {}),
122
+ ...(candidate.publicDiscovery?.agentCardCid ? {
123
+ agentCard: {
124
+ cid: candidate.publicDiscovery.agentCardCid,
126
125
  ...(candidate.publicDiscovery.updatedAt ? { updatedAt: candidate.publicDiscovery.updatedAt } : {}),
127
126
  status: 'pinned',
128
127
  },
@@ -133,11 +132,11 @@ export async function runRecoveryRefetch(
133
132
  await restoreSkillsTree(nextIdentity, payload.skills)
134
133
  }
135
134
  callbacks.onRestoreProgress?.({ phase: 'finishing', label: 'finalizing refreshed identity...' })
136
- const publicSkillsRestored = await restorePublishedPublicSkills(nextIdentity, apiUrl, candidate.publicDiscovery?.skillsCid)
135
+ const agentCardRestored = await restorePublishedAgentCard(nextIdentity, apiUrl, candidate.publicDiscovery?.agentCardCid)
137
136
  await ensureIdentityMarkdownScaffold(nextIdentity)
138
- await syncPublicSkillsManifest(nextIdentity).catch(() => null)
137
+ await syncAgentCardManifest(nextIdentity).catch(() => null)
139
138
  await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'Refetched Latest Snapshot From Onchain' }).catch(() => null)
140
- if (publicSkillsRestored) {
139
+ if (agentCardRestored) {
141
140
  const contentHashes = await localContinuitySnapshotContentHashes(nextIdentity)
142
141
  await updatePublishedContinuitySnapshotContentHashes(nextIdentity, candidate.backup.cid, contentHashes).catch(() => null)
143
142
  }