ethagent 3.2.0 → 3.3.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 (54) 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 -1
  22. package/src/identity/hub/continuity/effects.ts +17 -39
  23. package/src/identity/hub/continuity/skills/NewSkillVisibilityScreen.tsx +2 -2
  24. package/src/identity/hub/continuity/skills/SkillActionsScreen.tsx +2 -2
  25. package/src/identity/hub/continuity/state.ts +1 -1
  26. package/src/identity/hub/continuity/vault.ts +16 -50
  27. package/src/identity/hub/create/effects.ts +12 -16
  28. package/src/identity/hub/ens/EnsEditFlow.tsx +19 -5
  29. package/src/identity/hub/ens/EnsEditReviewScreens.tsx +11 -11
  30. package/src/identity/hub/ens/EnsEditShared.tsx +2 -6
  31. package/src/identity/hub/ens/editCopy.ts +1 -3
  32. package/src/identity/hub/ens/transactions.ts +67 -18
  33. package/src/identity/hub/profile/effects.ts +15 -30
  34. package/src/identity/hub/profile/identity.ts +2 -4
  35. package/src/identity/hub/profile/operatorSave.ts +10 -30
  36. package/src/identity/hub/restore/RestoreFlow.tsx +9 -9
  37. package/src/identity/hub/restore/apply.ts +7 -8
  38. package/src/identity/hub/restore/helpers.ts +3 -3
  39. package/src/identity/hub/restore/recovery.ts +9 -10
  40. package/src/identity/hub/restore/resolve.ts +11 -9
  41. package/src/identity/hub/shared/components/MenuScreen.tsx +3 -3
  42. package/src/identity/hub/shared/components/UnlinkedIdentityScreen.tsx +2 -2
  43. package/src/identity/hub/shared/effects/sync.ts +1 -1
  44. package/src/identity/hub/transfer/TokenTransferScreens.tsx +1 -1
  45. package/src/identity/hub/transfer/effects.ts +10 -31
  46. package/src/identity/hub/useIdentityHubContinuity.ts +12 -12
  47. package/src/identity/hub/useIdentityHubController.ts +12 -3
  48. package/src/identity/registry/erc8004/metadata.ts +10 -27
  49. package/src/identity/registry/erc8004/types.ts +0 -1
  50. package/src/storage/config.ts +1 -2
  51. package/src/storage/identity.ts +3 -3
  52. package/src/storage/rewind.ts +1 -1
  53. package/src/tools/privateContinuityEditTool.ts +4 -4
  54. package/src/utils/withRetry.ts +2 -2
@@ -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')
@@ -2,8 +2,8 @@ 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,
6
5
  buildAgentEnsRecords,
6
+ buildEnsip25Key,
7
7
  diffRecords,
8
8
  recordsFromTextMap,
9
9
  } from '../../ens/agentRecords.js'
@@ -137,8 +137,15 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
137
137
  setPhase({ kind: 'validating', fullName, mode, ownerAddress: phaseOwnerAddress, operatorWallet })
138
138
  try {
139
139
  const validation = await validateAgentEnsLink(fullName, ownerAddress)
140
- const currentText = validation.ok
141
- ? await readEthagentTextRecords(fullName, AGENT_RECORD_READ_KEY_LIST)
140
+ const readKeys = identity.agentId
141
+ ? [buildEnsip25Key({
142
+ chainId: registry.chainId,
143
+ identityRegistryAddress: registry.identityRegistryAddress,
144
+ agentId: identity.agentId,
145
+ })]
146
+ : []
147
+ const currentText = validation.ok && readKeys.length > 0
148
+ ? await readEthagentTextRecords(fullName, readKeys)
142
149
  : {}
143
150
  const current = recordsFromTextMap(currentText)
144
151
  const next = buildAgentEnsRecords({
@@ -252,7 +259,14 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
252
259
  const runUnlinkEnsLoading = React.useCallback((fullName: string): void => {
253
260
  setValidationError(null)
254
261
  setPhase({ kind: 'unlink-loading', fullName })
255
- readEthagentTextRecords(fullName, AGENT_RECORD_READ_KEY_LIST)
262
+ const readKeys = identity.agentId
263
+ ? [buildEnsip25Key({
264
+ chainId: registry.chainId,
265
+ identityRegistryAddress: registry.identityRegistryAddress,
266
+ agentId: identity.agentId,
267
+ })]
268
+ : []
269
+ readEthagentTextRecords(fullName, readKeys)
256
270
  .then(currentText => {
257
271
  const currentRecords = recordsFromTextMap(currentText)
258
272
  setPhase({
@@ -266,7 +280,7 @@ export const EnsEditFlow: React.FC<EnsEditProps> = ({
266
280
  setValidationError(err instanceof Error ? err.message : String(err))
267
281
  setPhase({ kind: 'mode-select' })
268
282
  })
269
- }, [])
283
+ }, [identity.agentId, registry.chainId, registry.identityRegistryAddress])
270
284
 
271
285
  const maintenanceScreen = renderEnsMaintenancePhase({
272
286
  phase,
@@ -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 {
@@ -194,7 +192,9 @@ export const EnsSetupBlockedScreen: React.FC<EnsSetupBlockedScreenProps> = ({
194
192
  {isSimple
195
193
  ? <EnsSetupRow label="Wallet" value={fallback.ownerAddress ? shortAddress(fallback.ownerAddress) : shortAddress(fallback.operatorAddress)} />
196
194
  : (fallback.ownerAddress ? <EnsSetupRow label="Owner wallet" value={shortAddress(fallback.ownerAddress)} /> : null)}
197
- {fallback.nextRecords?.token ? <EnsSetupRow label="Token link" value={fallback.nextRecords.token} /> : null}
195
+ {fallback.nextRecords && Object.keys(fallback.nextRecords).length > 0
196
+ ? <EnsSetupRow label="Attestation" value="ENSIP-25 agent-registration record" />
197
+ : null}
198
198
  <EnsSetupRow label="Address" value={`Set the subdomain address record to the ${isSimple ? 'connected wallet' : 'owner wallet'}.`} />
199
199
  {!isSimple
200
200
  ? (
@@ -271,7 +271,7 @@ export const UnlinkEnsReviewScreen: React.FC<UnlinkEnsReviewScreenProps> = ({
271
271
  {changedDiffs.map(diff => (
272
272
  <Text key={diff.key}>
273
273
  <Text color={theme.dim}>{` ${diff.key} `}</Text>
274
- <Text color={theme.accentPeriwinkle}>{formatRecordValue(diff.field, diff.current)}</Text>
274
+ <Text color={theme.accentPeriwinkle}>{diff.current}</Text>
275
275
  </Text>
276
276
  ))}
277
277
  </Box>
@@ -420,16 +420,16 @@ export const ReviewScreen: React.FC<ReviewScreenProps> = ({
420
420
  : null}
421
421
  {recordsDiff.map(diff => (
422
422
  <Text key={diff.key}>
423
- <Text color={theme.dim}>{`- ${recordLabel(diff.field)}: `}</Text>
423
+ <Text color={theme.dim}>{`- ${diff.key}: `}</Text>
424
424
  {diff.changed
425
425
  ? (
426
426
  <>
427
- {renderRecordValue(diff.field, diff.current)}
427
+ {renderRecordValue(diff.current)}
428
428
  <Text color={theme.dim}>{' → '}</Text>
429
- {renderRecordValue(diff.field, diff.next)}
429
+ {renderRecordValue(diff.next)}
430
430
  </>
431
431
  )
432
- : renderRecordValue(diff.field, diff.next)}
432
+ : renderRecordValue(diff.next)}
433
433
  </Text>
434
434
  ))}
435
435
  {!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,9 @@ 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
+ export const renderRecordValue = (value: string) =>
28
24
  value
29
- ? <Text color={theme.accentPeriwinkle}>{formatRecordValue(field, value)}</Text>
25
+ ? <Text color={theme.accentPeriwinkle}>{value}</Text>
30
26
  : <Text color={theme.dim}>Unset</Text>
31
27
 
32
28
  export function rootErrorMessage(
@@ -18,7 +18,7 @@ export function recordsHaveCurrentValues(recordsDiff: AgentRecordDiff[]): boolea
18
18
  }
19
19
 
20
20
  export function emptyAgentEnsRecords(): AgentEnsRecords {
21
- return { token: '' }
21
+ return {}
22
22
  }
23
23
 
24
24
  export function unlinkEnsLinkOptions(savedCustodyMode: CustodyMode | undefined, savedOwnerAddress: string): EnsLinkOptions {
@@ -84,8 +84,6 @@ export function manualReasonTitle(reason: EnsSetupBlockedPlan['reason']): string
84
84
  case 'wrapped-parent':
85
85
  case 'subdomain-wrapped':
86
86
  return 'ENS NameWrapper ownership could not be verified'
87
- case 'token-record-collision':
88
- return 'Subdomain already points to another token'
89
87
  case 'token-owner-mismatch':
90
88
  return 'Owner wallet does not own this ERC-8004 token'
91
89
  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,
@@ -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
  }