ethagent 1.1.1 → 2.0.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 (271) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +127 -29
  3. package/package.json +16 -9
  4. package/src/app/FirstRun.tsx +192 -146
  5. package/src/app/FirstRunTimeline.tsx +47 -0
  6. package/src/app/input/AppInputProvider.tsx +1 -1
  7. package/src/app/keybindings/KeybindingProvider.tsx +1 -1
  8. package/src/chat/ChatBottomPane.tsx +0 -1
  9. package/src/chat/ChatInput.tsx +6 -6
  10. package/src/chat/ChatScreen.tsx +43 -18
  11. package/src/chat/ContextLimitView.tsx +4 -4
  12. package/src/chat/ContinuityEditReviewView.tsx +11 -17
  13. package/src/chat/ConversationStack.tsx +3 -0
  14. package/src/chat/CopyPicker.tsx +0 -1
  15. package/src/chat/MessageList.tsx +62 -45
  16. package/src/chat/PermissionPrompt.tsx +13 -9
  17. package/src/chat/PlanApprovalView.tsx +3 -3
  18. package/src/chat/ResumeView.tsx +1 -4
  19. package/src/chat/RewindView.tsx +2 -2
  20. package/src/chat/TranscriptView.tsx +6 -0
  21. package/src/chat/chatInputState.ts +1 -1
  22. package/src/chat/chatScreenUtils.ts +22 -11
  23. package/src/chat/chatSessionState.ts +2 -2
  24. package/src/chat/chatTurnOrchestrator.ts +16 -81
  25. package/src/chat/commands.ts +1 -1
  26. package/src/chat/textCursor.ts +1 -1
  27. package/src/chat/transcriptViewport.ts +2 -7
  28. package/src/cli/ResetConfirmView.tsx +1 -1
  29. package/src/cli/main.tsx +9 -3
  30. package/src/cli/preview.tsx +0 -5
  31. package/src/cli/updateNotice.ts +5 -3
  32. package/src/identity/continuity/editor.ts +7 -107
  33. package/src/identity/continuity/envelope.ts +1048 -40
  34. package/src/identity/continuity/history.ts +4 -4
  35. package/src/identity/continuity/localBackup.ts +249 -0
  36. package/src/identity/continuity/privateEdit/apply.ts +170 -0
  37. package/src/identity/continuity/privateEdit/diff.ts +82 -0
  38. package/src/identity/continuity/privateEdit/files.ts +23 -0
  39. package/src/identity/continuity/privateEdit/types.ts +28 -0
  40. package/src/identity/continuity/privateEdit.ts +10 -298
  41. package/src/identity/continuity/publicSkills.ts +8 -9
  42. package/src/identity/continuity/snapshots.ts +17 -6
  43. package/src/identity/continuity/storage/defaults.ts +111 -0
  44. package/src/identity/continuity/storage/files.ts +72 -0
  45. package/src/identity/continuity/storage/markdown.ts +81 -0
  46. package/src/identity/continuity/storage/paths.ts +24 -0
  47. package/src/identity/continuity/storage/scaffold.ts +124 -0
  48. package/src/identity/continuity/storage/status.ts +86 -0
  49. package/src/identity/continuity/storage/types.ts +27 -0
  50. package/src/identity/continuity/storage.ts +32 -507
  51. package/src/identity/continuity/zipWriter.ts +95 -0
  52. package/src/identity/crypto/backupEnvelope.ts +14 -247
  53. package/src/identity/crypto/eth.ts +7 -7
  54. package/src/identity/ens/agentRecords.ts +96 -0
  55. package/src/identity/ens/ensAutomation/contracts.ts +38 -0
  56. package/src/identity/ens/ensAutomation/delete.ts +80 -0
  57. package/src/identity/ens/ensAutomation/names.ts +14 -0
  58. package/src/identity/ens/ensAutomation/operators.ts +29 -0
  59. package/src/identity/ens/ensAutomation/read.ts +114 -0
  60. package/src/identity/ens/ensAutomation/root.ts +63 -0
  61. package/src/identity/ens/ensAutomation/setup.ts +284 -0
  62. package/src/identity/ens/ensAutomation/transactions.ts +107 -0
  63. package/src/identity/ens/ensAutomation/types.ts +126 -0
  64. package/src/identity/ens/ensAutomation.ts +29 -0
  65. package/src/identity/ens/ensLookup/client.ts +43 -0
  66. package/src/identity/ens/ensLookup/constants.ts +26 -0
  67. package/src/identity/ens/ensLookup/discovery.ts +70 -0
  68. package/src/identity/ens/ensLookup/names.ts +34 -0
  69. package/src/identity/ens/ensLookup/records.ts +45 -0
  70. package/src/identity/ens/ensLookup/resolve.ts +75 -0
  71. package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
  72. package/src/identity/ens/ensLookup/types.ts +38 -0
  73. package/src/identity/ens/ensLookup/validation.ts +72 -0
  74. package/src/identity/ens/ensLookup.ts +19 -0
  75. package/src/identity/ens/ensRegistration.ts +199 -0
  76. package/src/identity/ens/resolverDelegation.ts +48 -0
  77. package/src/identity/hub/IdentityHub.tsx +13 -815
  78. package/src/identity/hub/OperationalRoutes.tsx +370 -0
  79. package/src/identity/hub/Routes.tsx +361 -0
  80. package/src/identity/hub/advancedEnsValidation.ts +45 -0
  81. package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
  82. package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
  83. package/src/identity/hub/components/FlowTimeline.tsx +27 -0
  84. package/src/identity/hub/components/IdentitySummary.tsx +190 -0
  85. package/src/identity/hub/components/MenuScreen.tsx +237 -0
  86. package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
  87. package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
  88. package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
  89. package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
  90. package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
  91. package/src/identity/hub/effects/create.ts +310 -0
  92. package/src/identity/hub/effects/ens/flows.ts +218 -0
  93. package/src/identity/hub/effects/ens/index.ts +11 -0
  94. package/src/identity/hub/effects/ens/transactions.ts +239 -0
  95. package/src/identity/hub/effects/index.ts +74 -0
  96. package/src/identity/hub/effects/profile/profileState.ts +173 -0
  97. package/src/identity/hub/effects/publicProfile/index.ts +5 -0
  98. package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
  99. package/src/identity/hub/effects/rebackup/index.ts +7 -0
  100. package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
  101. package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
  102. package/src/identity/hub/effects/receipts.ts +46 -0
  103. package/src/identity/hub/effects/restore/apply.ts +112 -0
  104. package/src/identity/hub/effects/restore/auth.ts +159 -0
  105. package/src/identity/hub/effects/restore/discover.ts +86 -0
  106. package/src/identity/hub/effects/restore/envelopes.ts +21 -0
  107. package/src/identity/hub/effects/restore/fetch.ts +25 -0
  108. package/src/identity/hub/effects/restore/index.ts +22 -0
  109. package/src/identity/hub/effects/restore/recovery.ts +135 -0
  110. package/src/identity/hub/effects/restore/resolve.ts +102 -0
  111. package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
  112. package/src/identity/hub/effects/restore/shared.ts +91 -0
  113. package/src/identity/hub/effects/restoreAdmin.ts +93 -0
  114. package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
  115. package/src/identity/hub/effects/shared/snapshot.ts +336 -0
  116. package/src/identity/hub/effects/shared/sync.ts +190 -0
  117. package/src/identity/hub/effects/token-transfer/index.ts +6 -0
  118. package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
  119. package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
  120. package/src/identity/hub/effects/types.ts +53 -0
  121. package/src/identity/hub/effects/vault/preflight.ts +50 -0
  122. package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
  123. package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
  124. package/src/identity/hub/flows/continuity/RecoveryConfirmScreen.tsx +104 -0
  125. package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
  126. package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
  127. package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
  128. package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
  129. package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
  130. package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
  131. package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
  132. package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
  133. package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
  134. package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
  135. package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
  136. package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
  137. package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
  138. package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
  139. package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
  140. package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
  141. package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
  142. package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
  143. package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
  144. package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
  145. package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
  146. package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
  147. package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
  148. package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
  149. package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +25 -43
  150. package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
  151. package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
  152. package/src/identity/hub/identityHubReducer.ts +166 -101
  153. package/src/identity/hub/model/continuity.ts +94 -0
  154. package/src/identity/hub/model/copy.ts +35 -0
  155. package/src/identity/hub/model/custody.ts +54 -0
  156. package/src/identity/hub/model/ens.ts +49 -0
  157. package/src/identity/hub/model/errors.ts +140 -0
  158. package/src/identity/hub/model/format.ts +15 -0
  159. package/src/identity/hub/model/identity.ts +94 -0
  160. package/src/identity/hub/model/network.ts +32 -0
  161. package/src/identity/hub/model/transfer.ts +57 -0
  162. package/src/identity/hub/operatorWallets.ts +131 -0
  163. package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
  164. package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
  165. package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
  166. package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
  167. package/src/identity/hub/reconciliation/index.ts +21 -0
  168. package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
  169. package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
  170. package/src/identity/hub/txGuard.ts +51 -0
  171. package/src/identity/hub/types.ts +17 -0
  172. package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
  173. package/src/identity/hub/useIdentityHubController.ts +396 -0
  174. package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
  175. package/src/identity/hub/utils.ts +79 -0
  176. package/src/identity/identityCompat.ts +34 -0
  177. package/src/identity/profile/agentIcon.ts +61 -0
  178. package/src/identity/profile/imagePicker.ts +12 -12
  179. package/src/identity/registry/erc8004/abi.ts +14 -0
  180. package/src/identity/registry/erc8004/chains.ts +150 -0
  181. package/src/identity/registry/erc8004/client.ts +11 -0
  182. package/src/identity/registry/erc8004/discovery.ts +511 -0
  183. package/src/identity/registry/erc8004/metadata.ts +335 -0
  184. package/src/identity/registry/erc8004/ownership.ts +121 -0
  185. package/src/identity/registry/erc8004/preflight.ts +123 -0
  186. package/src/identity/registry/erc8004/transactions.ts +77 -0
  187. package/src/identity/registry/erc8004/types.ts +88 -0
  188. package/src/identity/registry/erc8004/uri.ts +59 -0
  189. package/src/identity/registry/erc8004/utils.ts +58 -0
  190. package/src/identity/registry/erc8004.ts +53 -1106
  191. package/src/identity/registry/fieldParsers.ts +28 -0
  192. package/src/identity/registry/operatorVault/bytecode.ts +98 -0
  193. package/src/identity/registry/operatorVault/constants.ts +38 -0
  194. package/src/identity/registry/operatorVault/read.ts +246 -0
  195. package/src/identity/registry/operatorVault/transactions.ts +81 -0
  196. package/src/identity/registry/operatorVault.ts +44 -0
  197. package/src/identity/storage/ipfs.ts +26 -24
  198. package/src/identity/wallet/browserWallet/gas.ts +41 -0
  199. package/src/identity/wallet/browserWallet/html.ts +106 -0
  200. package/src/identity/wallet/browserWallet/http.ts +28 -0
  201. package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
  202. package/src/identity/wallet/browserWallet/requests.ts +191 -0
  203. package/src/identity/wallet/browserWallet/session.ts +325 -0
  204. package/src/identity/wallet/browserWallet/types.ts +192 -0
  205. package/src/identity/wallet/browserWallet/validation.ts +74 -0
  206. package/src/identity/wallet/browserWallet.ts +30 -393
  207. package/src/identity/wallet/page/constants.ts +5 -0
  208. package/src/identity/wallet/page/controller.ts +251 -0
  209. package/src/identity/wallet/page/copy.ts +340 -0
  210. package/src/identity/wallet/page/grainient.ts +278 -0
  211. package/src/identity/wallet/page/html.ts +28 -0
  212. package/src/identity/wallet/page/markup.ts +50 -0
  213. package/src/identity/wallet/page/state.ts +9 -0
  214. package/src/identity/wallet/page/styles/base.ts +259 -0
  215. package/src/identity/wallet/page/styles/components.ts +262 -0
  216. package/src/identity/wallet/page/styles/index.ts +5 -0
  217. package/src/identity/wallet/page/styles/responsive.ts +247 -0
  218. package/src/identity/wallet/page/types.ts +47 -0
  219. package/src/identity/wallet/page/view.ts +535 -0
  220. package/src/identity/wallet/page/walletProvider.ts +70 -0
  221. package/src/identity/wallet/page.tsx +38 -0
  222. package/src/identity/wallet/walletPurposeCompat.ts +27 -0
  223. package/src/mcp/manager.ts +0 -1
  224. package/src/models/ModelPicker.tsx +36 -30
  225. package/src/models/catalog.ts +5 -2
  226. package/src/models/huggingface.ts +9 -9
  227. package/src/models/llamacpp.ts +13 -13
  228. package/src/models/modelDisplay.ts +75 -0
  229. package/src/models/modelPickerOptions.ts +16 -3
  230. package/src/models/modelRecommendation.ts +0 -1
  231. package/src/providers/errors.ts +16 -0
  232. package/src/providers/gemini.ts +252 -39
  233. package/src/providers/registry.ts +2 -2
  234. package/src/providers/retry.ts +1 -1
  235. package/src/runtime/sessionMode.ts +1 -1
  236. package/src/runtime/systemPrompt.ts +2 -0
  237. package/src/runtime/toolExecution.ts +18 -22
  238. package/src/runtime/toolIntent.ts +0 -20
  239. package/src/runtime/turn.ts +0 -92
  240. package/src/storage/atomicWrite.ts +4 -1
  241. package/src/storage/config.ts +181 -5
  242. package/src/storage/identity.ts +9 -3
  243. package/src/storage/secrets.ts +2 -2
  244. package/src/tools/bashSafety.ts +8 -0
  245. package/src/tools/changeDirectoryTool.ts +1 -1
  246. package/src/tools/deleteFileTool.ts +4 -4
  247. package/src/tools/editTool.ts +4 -4
  248. package/src/tools/editUtils.ts +5 -5
  249. package/src/tools/privateContinuityEditTool.ts +4 -5
  250. package/src/tools/privateContinuityReadTool.ts +1 -2
  251. package/src/tools/registry.ts +30 -0
  252. package/src/tools/writeFileTool.ts +5 -5
  253. package/src/ui/BrandSplash.tsx +20 -85
  254. package/src/ui/ProgressBar.tsx +3 -5
  255. package/src/ui/Select.tsx +21 -9
  256. package/src/ui/Spinner.tsx +38 -3
  257. package/src/ui/Surface.tsx +3 -3
  258. package/src/ui/TextInput.tsx +191 -29
  259. package/src/ui/theme.ts +7 -34
  260. package/src/utils/openExternal.ts +21 -0
  261. package/src/utils/withRetry.ts +47 -3
  262. package/src/identity/hub/identityHubEffects.ts +0 -937
  263. package/src/identity/hub/identityHubModel.ts +0 -291
  264. package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -144
  265. package/src/identity/hub/screens/EditProfileFlow.tsx +0 -145
  266. package/src/identity/hub/screens/IdentitySummary.tsx +0 -90
  267. package/src/identity/hub/screens/MenuScreen.tsx +0 -117
  268. package/src/identity/hub/screens/RecoveryConfirmScreen.tsx +0 -87
  269. package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
  270. package/src/identity/wallet/wallet-page/wallet.html +0 -1202
  271. /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
@@ -0,0 +1,451 @@
1
+ import { getAddress, type Address } from 'viem'
2
+ import type { Hex } from 'viem'
3
+ import type { EthagentIdentity } from '../../../../storage/config.js'
4
+ import {
5
+ continuityVaultStatus,
6
+ prepareSyncedIdentityMarkdownScaffold,
7
+ readContinuityFiles,
8
+ readPublicSkillsFile,
9
+ writeIdentityMarkdownScaffold,
10
+ type IdentityMarkdownScaffold,
11
+ } from '../../../continuity/storage.js'
12
+ import {
13
+ createWalletRestoreAccessChallenge,
14
+ serializeContinuitySnapshotEnvelope,
15
+ type WalletChallengePurpose,
16
+ } from '../../../continuity/envelope.js'
17
+ import {
18
+ createAgentCard,
19
+ defaultPublicSkillsProfile,
20
+ serializeAgentCard,
21
+ } from '../../../continuity/publicSkills.js'
22
+ import { recordPublishedContinuitySnapshot } from '../../../continuity/snapshots.js'
23
+ import { addToIpfs, DEFAULT_IPFS_API_URL, isPinataUploadUrl } from '../../../storage/ipfs.js'
24
+ import {
25
+ createErc8004PublicClient,
26
+ encodeSetAgentUri,
27
+ preflightSetAgentUri,
28
+ withEthagentPointers,
29
+ type Erc8004RegistryConfig,
30
+ } from '../../../registry/erc8004.js'
31
+ import { resolveValidatedPinataJwt, savePinataJwt } from '../../../storage/pinataJwt.js'
32
+ import {
33
+ requestBrowserWalletSignatureAndTransaction,
34
+ type BrowserWalletSession,
35
+ type BrowserWalletSignature,
36
+ type WalletPurpose,
37
+ } from '../../../wallet/browserWallet.js'
38
+ import {
39
+ encodeRotateAgentURI,
40
+ isAgentInVault,
41
+ } from '../../../registry/operatorVault.js'
42
+ import type { Step, ProfileUpdates } from '../../identityHubReducer.js'
43
+ import { acquireTxGuard, releaseTxGuard } from '../../txGuard.js'
44
+ import type { EffectCallbacks } from '../types.js'
45
+ import { awaitConfirmedReceipt } from '../receipts.js'
46
+ import {
47
+ assertVerifiedPin,
48
+ deriveAgentName,
49
+ prepareProfileStateForSave,
50
+ readEnsOkFromState,
51
+ } from '../shared/profilePrep.js'
52
+ import {
53
+ assertSnapshotSaveSignerAuthorized,
54
+ createContinuityEnvelopeForSave,
55
+ expectedAccountForSnapshotSave,
56
+ operatorsPointerFromState,
57
+ ownerAddressForSnapshotSave,
58
+ resolveProfileUpdatesEpoch,
59
+ snapshotSaveWalletRole,
60
+ walletRestoreAccessContext,
61
+ } from '../shared/snapshot.js'
62
+ import {
63
+ appendResolverSyncWarning,
64
+ markCurrentContinuityFilesPublished,
65
+ resolverSyncWarningMessage,
66
+ syncResolverApprovalsAfterOwnerSave,
67
+ } from '../shared/sync.js'
68
+ import { runOperatorWalletRebackup } from './operatorVault.js'
69
+
70
+ type BackupMetadata = NonNullable<EthagentIdentity['backup']>
71
+ type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
72
+
73
+ type RebackupPreparedTransaction = {
74
+ ownerAddress: Address
75
+ agentUri: string
76
+ metadataCid: string
77
+ backup: BackupMetadata
78
+ publicSkills: PublicSkillsMetadata
79
+ identity: EthagentIdentity
80
+ markdownScaffold?: IdentityMarkdownScaffold
81
+ }
82
+
83
+ export async function runRebackupPreflight(
84
+ identity: EthagentIdentity,
85
+ registry: Erc8004RegistryConfig,
86
+ callbacks: EffectCallbacks,
87
+ profileUpdates?: ProfileUpdates,
88
+ returnTo: Step = { kind: 'menu' },
89
+ walletPurpose?: WalletPurpose,
90
+ vaultAddress?: `0x${string}`,
91
+ ): Promise<void> {
92
+ const status = await continuityVaultStatus(identity)
93
+ if (!status.ready) {
94
+ throw new Error('Restore local continuity files before saving an encrypted snapshot')
95
+ }
96
+ const apiUrl = DEFAULT_IPFS_API_URL
97
+ let jwt: string | undefined
98
+ try {
99
+ jwt = isPinataUploadUrl(apiUrl) ? await resolveValidatedPinataJwt() : undefined
100
+ } catch (err: unknown) {
101
+ callbacks.onStep({ kind: 'rebackup-storage', identity, registry, error: (err as Error).message, profileUpdates, returnTo, walletPurpose, vaultAddress })
102
+ return
103
+ }
104
+ if (isPinataUploadUrl(apiUrl) && !jwt) {
105
+ callbacks.onStep({ kind: 'rebackup-storage', identity, registry, profileUpdates, returnTo, walletPurpose, vaultAddress })
106
+ return
107
+ }
108
+ callbacks.onStep({ kind: 'rebackup-signing', identity, registry, pinataJwt: jwt, profileUpdates, returnTo, walletPurpose, vaultAddress })
109
+ }
110
+
111
+ async function resolveRebackupVaultRoute(args: {
112
+ registry: Erc8004RegistryConfig
113
+ vaultAddress?: Address
114
+ agentId: bigint
115
+ signerAccount: Address
116
+ }): Promise<{ vaultAddress: Address } | null> {
117
+ if (!args.vaultAddress) return null
118
+ const client = createErc8004PublicClient(args.registry)
119
+ const status = await isAgentInVault({
120
+ client,
121
+ vaultAddress: args.vaultAddress,
122
+ registry: getAddress(args.registry.identityRegistryAddress),
123
+ agentId: args.agentId,
124
+ })
125
+ if (!status.inVault) return null
126
+ const expected = getAddress(args.signerAccount)
127
+ if (!status.ownerAddress || status.ownerAddress.toLowerCase() !== expected.toLowerCase()) {
128
+ throw new Error(
129
+ `Vault-level owner ${status.ownerAddress ?? 'unknown'} does not match signing wallet ${expected}; cannot rotate agentURI through the vault`,
130
+ )
131
+ }
132
+ return { vaultAddress: getAddress(args.vaultAddress) }
133
+ }
134
+
135
+ export async function runRebackupSigning(
136
+ step: Extract<Step, { kind: 'rebackup-signing' }>,
137
+ callbacks: EffectCallbacks,
138
+ ): Promise<void> {
139
+ acquireTxGuard('rebackup')
140
+ try {
141
+ return await runRebackupSigningInner(step, callbacks)
142
+ } finally {
143
+ releaseTxGuard('rebackup')
144
+ }
145
+ }
146
+
147
+ export async function runRebackupSigningInSession(
148
+ step: Extract<Step, { kind: 'rebackup-signing' }>,
149
+ callbacks: EffectCallbacks,
150
+ session: BrowserWalletSession,
151
+ flow?: { flowId?: string; flowStep?: number },
152
+ ): Promise<void> {
153
+ acquireTxGuard('rebackup')
154
+ try {
155
+ return await runRebackupSigningInner(step, callbacks, { session, flow })
156
+ } finally {
157
+ releaseTxGuard('rebackup')
158
+ }
159
+ }
160
+
161
+ async function runRebackupSigningInner(
162
+ step: Extract<Step, { kind: 'rebackup-signing' }>,
163
+ callbacks: EffectCallbacks,
164
+ opts?: { session?: BrowserWalletSession; flow?: { flowId?: string; flowStep?: number } },
165
+ ): Promise<void> {
166
+ const sourceAgentId = step.identity.agentId
167
+ if (!sourceAgentId) throw new Error('Cannot back up: identity is missing an agent token ID')
168
+ const resolvedUpdates = resolveProfileUpdatesEpoch(step.identity, step.profileUpdates)
169
+ step = { ...step, ...(resolvedUpdates !== undefined ? { profileUpdates: resolvedUpdates } : {}) }
170
+ if (snapshotSaveWalletRole(step.identity, step.profileUpdates) === 'operator') {
171
+ await runOperatorWalletRebackup({
172
+ step,
173
+ callbacks,
174
+ walletPurpose: step.walletPurpose ?? rebackupWalletPurpose(step.identity, step.profileUpdates),
175
+ deriveAgentName,
176
+ })
177
+ return
178
+ }
179
+ const snapshotOwner = ownerAddressForSnapshotSave(step.identity, step.profileUpdates)
180
+ const purpose: WalletPurpose = step.walletPurpose
181
+ ?? (step.vaultAddress ? 'rotate-agent-uri-vault-owner' : rebackupWalletPurpose(step.identity, step.profileUpdates))
182
+ const challengePurpose: WalletChallengePurpose = 'restore-owner'
183
+ const walletAccess = walletRestoreAccessContext(step.identity, step.registry, step.profileUpdates, snapshotOwner)
184
+ if (!walletAccess) throw new Error('Cannot back up: missing wallet restore access context')
185
+ const expectedSigner = expectedAccountForSnapshotSave(step.identity, step.profileUpdates, walletAccess)
186
+ const orchestratorFlowId = opts?.flow?.flowId
187
+ const orchestratorFlowStep = opts?.flow?.flowStep
188
+ const effectiveFlowId = orchestratorFlowId
189
+ ?? (step.profileUpdates?.custodyPhase === 'switch-advanced' ? 'advanced-custody' : undefined)
190
+ const requestSpec = {
191
+ chainId: step.registry.chainId,
192
+ messageForAccount: (account: Address) => createWalletRestoreAccessChallenge({
193
+ token: walletAccess.token,
194
+ ownerAddress: snapshotOwner,
195
+ walletAddress: account,
196
+ accessEpoch: walletAccess.accessEpoch,
197
+ ...(challengePurpose ? { purpose: challengePurpose } : {}),
198
+ }),
199
+ purpose,
200
+ ...(effectiveFlowId ? { flowId: effectiveFlowId } : {}),
201
+ ...(typeof orchestratorFlowStep === 'number' ? { flowStep: orchestratorFlowStep } : {}),
202
+ ...(expectedSigner ? { expectedAccount: expectedSigner } : {}),
203
+ prepareTransaction: async (wallet: BrowserWalletSignature) => {
204
+ assertSnapshotSaveSignerAuthorized(step.identity, step.profileUpdates, wallet.account, snapshotOwner, walletAccess)
205
+ const {
206
+ state,
207
+ nextName,
208
+ nextDescription,
209
+ nextEnsName,
210
+ uploadedImageUri,
211
+ } = await prepareProfileStateForSave({
212
+ identity: step.identity,
213
+ registry: step.registry,
214
+ profileUpdates: step.profileUpdates,
215
+ pinataJwt: step.pinataJwt,
216
+ ownerAddress: snapshotOwner,
217
+ walletAccount: getAddress(wallet.account),
218
+ includeLastBackedUpAt: true,
219
+ })
220
+ const nextIdentityForFiles: EthagentIdentity = { ...step.identity, state }
221
+ const markdownScaffold = step.profileUpdates
222
+ ? await prepareSyncedIdentityMarkdownScaffold(nextIdentityForFiles)
223
+ : undefined
224
+ const continuityFiles = markdownScaffold
225
+ ? { 'SOUL.md': markdownScaffold['SOUL.md'], 'MEMORY.md': markdownScaffold['MEMORY.md'] }
226
+ : await readContinuityFiles(nextIdentityForFiles)
227
+ const publicSkillsJson = markdownScaffold
228
+ ? markdownScaffold['skills.json']
229
+ : await readPublicSkillsFile(nextIdentityForFiles)
230
+ const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
231
+ assertVerifiedPin(publicSkillsPin)
232
+ const agentCardPin = await addToIpfs(
233
+ DEFAULT_IPFS_API_URL,
234
+ serializeAgentCard(createAgentCard(defaultPublicSkillsProfile(nextIdentityForFiles))),
235
+ fetch,
236
+ { pinataJwt: step.pinataJwt },
237
+ )
238
+ assertVerifiedPin(agentCardPin)
239
+ const envelope = createContinuityEnvelopeForSave({
240
+ identity: nextIdentityForFiles,
241
+ registry: step.registry,
242
+ ownerAddress: snapshotOwner,
243
+ signerAddress: wallet.account,
244
+ walletSignature: wallet.signature,
245
+ state,
246
+ files: continuityFiles,
247
+ walletAccess,
248
+ ...(challengePurpose ? { challengePurpose } : {}),
249
+ })
250
+ const statePin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeContinuitySnapshotEnvelope(envelope), fetch, { pinataJwt: step.pinataJwt })
251
+ assertVerifiedPin(statePin)
252
+ const cid = statePin.cid
253
+ const backup: BackupMetadata = {
254
+ cid,
255
+ createdAt: envelope.createdAt,
256
+ envelopeVersion: envelope.envelopeVersion,
257
+ ipfsApiUrl: DEFAULT_IPFS_API_URL,
258
+ status: 'pinned',
259
+ ownerAddress: snapshotOwner,
260
+ chainId: step.registry.chainId,
261
+ rpcUrl: step.registry.rpcUrl,
262
+ identityRegistryAddress: step.registry.identityRegistryAddress,
263
+ agentId: sourceAgentId,
264
+ }
265
+ const publicSkills: PublicSkillsMetadata = {
266
+ cid: publicSkillsPin.cid,
267
+ agentCardCid: agentCardPin.cid,
268
+ updatedAt: envelope.createdAt,
269
+ status: 'pinned',
270
+ }
271
+ const registration = withEthagentPointers({
272
+ type: 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1',
273
+ name: nextName ?? deriveAgentName(step.identity),
274
+ ...(nextDescription ? { description: nextDescription } : {}),
275
+ ...(uploadedImageUri ? { image: uploadedImageUri } : {}),
276
+ }, {
277
+ backup: { cid, envelopeVersion: envelope.envelopeVersion, createdAt: envelope.createdAt },
278
+ publicDiscovery: { skillsCid: publicSkills.cid, agentCardCid: publicSkills.agentCardCid, updatedAt: publicSkills.updatedAt },
279
+ registration: { chainId: step.registry.chainId, identityRegistryAddress: step.registry.identityRegistryAddress, agentId: sourceAgentId },
280
+ ensName: nextEnsName,
281
+ operators: operatorsPointerFromState(state, nextEnsName),
282
+ ownerAddress: snapshotOwner,
283
+ })
284
+ const metadataPin = await addToIpfs(DEFAULT_IPFS_API_URL, JSON.stringify(registration, null, 2), fetch, { pinataJwt: step.pinataJwt })
285
+ assertVerifiedPin(metadataPin)
286
+ const metadataCid = metadataPin.cid
287
+ const agentUri = `ipfs://${metadataCid}`
288
+ const agentId = BigInt(sourceAgentId)
289
+ const vaultRoute = await resolveRebackupVaultRoute({
290
+ registry: step.registry,
291
+ vaultAddress: step.vaultAddress,
292
+ agentId,
293
+ signerAccount: wallet.account,
294
+ })
295
+ if (vaultRoute) {
296
+ const vaultCall = encodeRotateAgentURI({
297
+ registry: getAddress(step.registry.identityRegistryAddress),
298
+ agentId,
299
+ newURI: agentUri,
300
+ vaultAddress: vaultRoute.vaultAddress,
301
+ })
302
+ return {
303
+ to: vaultCall.to,
304
+ data: vaultCall.data,
305
+ prepared: {
306
+ ownerAddress: snapshotOwner,
307
+ agentUri,
308
+ metadataCid,
309
+ backup: { ...backup, metadataCid, agentUri },
310
+ publicSkills,
311
+ identity: { ...step.identity, state },
312
+ ...(markdownScaffold ? { markdownScaffold } : {}),
313
+ },
314
+ }
315
+ }
316
+ await preflightSetAgentUri({
317
+ ...step.registry,
318
+ account: wallet.account,
319
+ agentId,
320
+ newUri: agentUri,
321
+ })
322
+ return {
323
+ to: step.registry.identityRegistryAddress,
324
+ data: encodeSetAgentUri({ agentId, newUri: agentUri }),
325
+ prepared: {
326
+ ownerAddress: snapshotOwner,
327
+ agentUri,
328
+ metadataCid,
329
+ backup: { ...backup, metadataCid, agentUri },
330
+ publicSkills,
331
+ identity: { ...step.identity, state },
332
+ ...(markdownScaffold ? { markdownScaffold } : {}),
333
+ },
334
+ }
335
+ },
336
+ }
337
+ const result = opts?.session
338
+ ? await opts.session.requestSignatureAndTransaction<RebackupPreparedTransaction>(requestSpec)
339
+ : await requestBrowserWalletSignatureAndTransaction<RebackupPreparedTransaction>({
340
+ ...requestSpec,
341
+ onReady: callbacks.onWalletReady,
342
+ })
343
+ const client = createErc8004PublicClient(step.registry)
344
+ await awaitConfirmedReceipt(client, result.txHash as Hex, 'Agent URI rotation', { kind: step.vaultAddress ? 'rebackup-uri-vault' : 'rebackup-uri', chainId: step.registry.chainId })
345
+ const nextIdentity: EthagentIdentity = {
346
+ ...result.prepared.identity,
347
+ source: 'erc8004',
348
+ address: getAddress(result.prepared.ownerAddress),
349
+ ownerAddress: getAddress(result.prepared.ownerAddress),
350
+ chainId: step.registry.chainId,
351
+ rpcUrl: step.registry.rpcUrl,
352
+ identityRegistryAddress: step.registry.identityRegistryAddress,
353
+ agentUri: result.prepared.agentUri,
354
+ metadataCid: result.prepared.metadataCid,
355
+ backup: { ...result.prepared.backup, txHash: result.txHash },
356
+ publicSkills: result.prepared.publicSkills,
357
+ }
358
+ if (result.prepared.markdownScaffold) {
359
+ await writeIdentityMarkdownScaffold(nextIdentity, result.prepared.markdownScaffold)
360
+ }
361
+ await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'published encrypted snapshot' }).catch(() => null)
362
+ await markCurrentContinuityFilesPublished(nextIdentity).catch(() => null)
363
+ const resolverSyncWarning = await syncResolverApprovalsAfterOwnerSave({
364
+ beforeIdentity: step.identity,
365
+ afterIdentity: nextIdentity,
366
+ registry: step.registry,
367
+ ...(step.vaultAddress ? { vaultAddress: step.vaultAddress } : {}),
368
+ callbacks,
369
+ }).then(() => null).catch(err => resolverSyncWarningMessage(err))
370
+ const completionMessage = appendResolverSyncWarning(
371
+ rebackupCompletionMessage(
372
+ step.profileUpdates,
373
+ step.identity,
374
+ readEnsOkFromState(nextIdentity.state),
375
+ ),
376
+ resolverSyncWarning,
377
+ )
378
+ await callbacks.onIdentityComplete(nextIdentity, completionMessage, 'update')
379
+ }
380
+
381
+ export async function runRebackupStorageSubmit(
382
+ input: string,
383
+ step: Extract<Step, { kind: 'rebackup-storage' }>,
384
+ callbacks: EffectCallbacks,
385
+ ): Promise<void> {
386
+ const { jwt: pinataJwt } = await savePinataJwt(input)
387
+ callbacks.onStep({ kind: 'rebackup-signing', identity: step.identity, registry: step.registry, pinataJwt, profileUpdates: step.profileUpdates, returnTo: step.returnTo, walletPurpose: step.walletPurpose, vaultAddress: step.vaultAddress })
388
+ }
389
+
390
+ function rebackupWalletPurpose(
391
+ identity: EthagentIdentity,
392
+ profileUpdates: ProfileUpdates | undefined,
393
+ ): WalletPurpose {
394
+ const role = snapshotSaveWalletRole(identity, profileUpdates)
395
+ const snapshotPurpose = role === 'operator'
396
+ ? 'update-snapshot-operator' as const
397
+ : role === 'owner'
398
+ ? 'update-snapshot-owner' as const
399
+ : 'update-snapshot-connected' as const
400
+ const profilePurpose = role === 'operator'
401
+ ? 'update-profile-operator' as const
402
+ : role === 'owner'
403
+ ? 'update-profile-owner' as const
404
+ : 'update-profile-connected' as const
405
+ if (!profileUpdates) return snapshotPurpose
406
+ const baseState = (identity.state ?? {}) as Record<string, unknown>
407
+ const currentEns = typeof baseState.ensName === 'string' ? baseState.ensName.trim() : ''
408
+ const ensTouched = typeof profileUpdates.ensName === 'string'
409
+ const profileFieldsTouched = profileUpdates.name !== undefined
410
+ || profileUpdates.description !== undefined
411
+ || profileUpdates.imagePath !== undefined
412
+ const operatorFieldsTouched = profileUpdates.ownerAddress !== undefined
413
+ || profileUpdates.approvedOperatorWallets !== undefined
414
+ || profileUpdates.activeOperatorAddress !== undefined
415
+ || profileUpdates.restoreAccessEpoch !== undefined
416
+ if (operatorFieldsTouched && !ensTouched && !profileFieldsTouched) return 'update-operators'
417
+ if (ensTouched && !profileFieldsTouched) {
418
+ const next = (profileUpdates.ensName ?? '').trim()
419
+ if (!next && currentEns) return 'clear-ens'
420
+ return 'update-ens'
421
+ }
422
+ if (profileFieldsTouched) return profilePurpose
423
+ return snapshotPurpose
424
+ }
425
+
426
+ export function rebackupCompletionMessage(
427
+ profileUpdates: ProfileUpdates | undefined,
428
+ identity: EthagentIdentity,
429
+ ensOk?: boolean,
430
+ ): string {
431
+ if (!profileUpdates) return 'Backup Saved'
432
+ const baseState = (identity.state ?? {}) as Record<string, unknown>
433
+ const currentEns = typeof baseState.ensName === 'string' ? baseState.ensName.trim() : ''
434
+ const ensTouched = typeof profileUpdates.ensName === 'string'
435
+ const profileFieldsTouched = profileUpdates.name !== undefined
436
+ || profileUpdates.description !== undefined
437
+ || profileUpdates.imagePath !== undefined
438
+ const operatorFieldsTouched = profileUpdates.ownerAddress !== undefined
439
+ || profileUpdates.approvedOperatorWallets !== undefined
440
+ || profileUpdates.activeOperatorAddress !== undefined
441
+ || profileUpdates.restoreAccessEpoch !== undefined
442
+ if (operatorFieldsTouched && !ensTouched && !profileFieldsTouched) return 'Operator Wallets Updated'
443
+ if (ensTouched && !profileFieldsTouched) {
444
+ const next = (profileUpdates.ensName ?? '').trim()
445
+ if (!next && currentEns) return 'ENS Unlinked'
446
+ if (next) return ensOk === false ? 'ENS Issue' : 'ENS Linked'
447
+ return 'ENS Updated'
448
+ }
449
+ if (profileFieldsTouched) return 'Profile Updated'
450
+ return 'Backup Saved'
451
+ }
@@ -0,0 +1,46 @@
1
+ import type { Hex, PublicClient } from 'viem'
2
+ import type { PendingTxKind } from '../../../storage/config.js'
3
+ import { clearPendingTx, recordPendingTx } from '../../../storage/config.js'
4
+
5
+ export async function awaitConfirmedReceipt(
6
+ client: Pick<PublicClient, 'waitForTransactionReceipt'>,
7
+ hash: Hex,
8
+ action: string,
9
+ pending?: { kind: PendingTxKind; chainId: number },
10
+ ): ReturnType<PublicClient['waitForTransactionReceipt']> {
11
+ if (pending) {
12
+ await recordPendingTx({
13
+ hash,
14
+ kind: pending.kind,
15
+ chainId: pending.chainId,
16
+ submittedAt: new Date().toISOString(),
17
+ }).catch(() => null)
18
+ }
19
+ try {
20
+ const receipt = await client.waitForTransactionReceipt({ hash })
21
+ if (receipt.status !== 'success') {
22
+ throw new Error(`${action} reverted onchain (tx ${hash}). Check the transaction on a block explorer for the revert reason.`)
23
+ }
24
+ return receipt
25
+ } finally {
26
+ if (pending) {
27
+ await clearPendingTx().catch(() => null)
28
+ }
29
+ }
30
+ }
31
+
32
+ export async function awaitOptionalReceipt(
33
+ client: Pick<PublicClient, 'waitForTransactionReceipt'>,
34
+ hash: Hex,
35
+ action: string,
36
+ ): Promise<void> {
37
+ let receipt
38
+ try {
39
+ receipt = await client.waitForTransactionReceipt({ hash })
40
+ } catch {
41
+ return
42
+ }
43
+ if (receipt.status !== 'success') {
44
+ throw new Error(`${action} reverted onchain (tx ${hash}).`)
45
+ }
46
+ }
@@ -0,0 +1,112 @@
1
+ import { getAddress } from 'viem'
2
+ import type { EthagentIdentity } from '../../../../storage/config.js'
3
+ import { restoreAgentStateBackupEnvelope } from '../../../crypto/backupEnvelope.js'
4
+ import {
5
+ restoreContinuitySnapshotEnvelope,
6
+ transferSnapshotMetadataFromEnvelope,
7
+ } from '../../../continuity/envelope.js'
8
+ import { ensureIdentityMarkdownScaffold, writeContinuityFiles } from '../../../continuity/storage.js'
9
+ import { recordPublishedContinuitySnapshot } from '../../../continuity/snapshots.js'
10
+ import { requestBrowserWalletSignature } from '../../../wallet/browserWallet.js'
11
+ import { setOperatorVaultAddressField } from '../../../identityCompat.js'
12
+ import type { Step } from '../../identityHubReducer.js'
13
+ import type { EffectCallbacks } from '../types.js'
14
+ import { isContinuitySnapshotEnvelope } from './envelopes.js'
15
+ import { restoreSignatureRequestForStep } from './auth.js'
16
+ import { type BackupMetadata, operatorStateFromCandidate, restorePublishedPublicSkills } from './shared.js'
17
+
18
+ export async function runRestoreAuthorize(
19
+ step: Extract<Step, { kind: 'restore-authorizing' }>,
20
+ callbacks: EffectCallbacks,
21
+ ): Promise<void> {
22
+ const signatureRequest = restoreSignatureRequestForStep(step)
23
+ const wallet = await requestBrowserWalletSignature({
24
+ chainId: step.candidate.chainId,
25
+ expectedAccount: signatureRequest.expectedAccount,
26
+ message: signatureRequest.message,
27
+ purpose: signatureRequest.purpose,
28
+ onReady: callbacks.onWalletReady,
29
+ })
30
+ callbacks.onWalletReady(null)
31
+ callbacks.onRestoreProgress?.({ phase: 'decrypting', label: 'signature received · decrypting encrypted snapshot...' })
32
+ let restored: ReturnType<typeof restoreAgentStateBackupEnvelope> | ReturnType<typeof restoreContinuitySnapshotEnvelope>
33
+ let continuityFiles: ReturnType<typeof restoreContinuitySnapshotEnvelope>['files'] | undefined
34
+ if (isContinuitySnapshotEnvelope(step.envelope)) {
35
+ const payload = restoreContinuitySnapshotEnvelope({
36
+ envelope: step.envelope,
37
+ walletSignature: wallet.signature,
38
+ currentOwnerAddress: wallet.account,
39
+ })
40
+ restored = payload
41
+ continuityFiles = payload.files
42
+ } else {
43
+ restored = restoreAgentStateBackupEnvelope({
44
+ envelope: step.envelope,
45
+ walletSignature: wallet.signature,
46
+ })
47
+ }
48
+ callbacks.onRestoreProgress?.({ phase: 'writing', label: 'restoring local agent files...' })
49
+ const transferSnapshot = isContinuitySnapshotEnvelope(step.envelope)
50
+ ? transferSnapshotMetadataFromEnvelope(step.envelope)
51
+ : null
52
+ const backup: BackupMetadata = {
53
+ cid: step.cid,
54
+ createdAt: step.envelope.createdAt,
55
+ envelopeVersion: step.envelope.envelopeVersion,
56
+ ipfsApiUrl: step.apiUrl,
57
+ status: 'restored',
58
+ ownerAddress: step.candidate.ownerAddress,
59
+ chainId: step.candidate.chainId,
60
+ rpcUrl: step.candidate.rpcUrl,
61
+ identityRegistryAddress: step.candidate.identityRegistryAddress,
62
+ agentId: step.candidate.agentId.toString(),
63
+ agentUri: step.candidate.agentUri,
64
+ metadataCid: step.candidate.metadataCid,
65
+ ...(transferSnapshot ? { transferSnapshot } : {}),
66
+ }
67
+ const restoreRequester = step.requesterAddress && /^0x[a-fA-F0-9]{40}$/.test(step.requesterAddress)
68
+ ? getAddress(step.requesterAddress)
69
+ : step.candidate.ownerAddress
70
+ const tokenOwnerAddress = step.candidate.tokenOwnerAddress ?? step.candidate.ownerAddress
71
+ const restoredState: Record<string, unknown> = {
72
+ ...restored.state,
73
+ ...(step.candidate.name ? { name: step.candidate.name } : {}),
74
+ ...(step.candidate.description ? { description: step.candidate.description } : {}),
75
+ ...(step.candidate.imageUrl ? { imageUrl: step.candidate.imageUrl } : {}),
76
+ ...operatorStateFromCandidate(step.candidate),
77
+ }
78
+ if (tokenOwnerAddress.toLowerCase() !== step.candidate.ownerAddress.toLowerCase()) {
79
+ setOperatorVaultAddressField(restoredState, getAddress(tokenOwnerAddress))
80
+ }
81
+ const nextIdentity: EthagentIdentity = {
82
+ source: 'erc8004',
83
+ address: tokenOwnerAddress,
84
+ ownerAddress: step.candidate.ownerAddress,
85
+ connectedWallet: restoreRequester,
86
+ createdAt: restored.createdAt,
87
+ chainId: step.candidate.chainId,
88
+ rpcUrl: step.candidate.rpcUrl,
89
+ identityRegistryAddress: step.candidate.identityRegistryAddress,
90
+ agentId: step.candidate.agentId.toString(),
91
+ agentUri: step.candidate.agentUri,
92
+ metadataCid: step.candidate.metadataCid,
93
+ state: restoredState,
94
+ backup,
95
+ ...(step.candidate.publicDiscovery ? {
96
+ publicSkills: {
97
+ ...(step.candidate.publicDiscovery.skillsCid ? { cid: step.candidate.publicDiscovery.skillsCid } : {}),
98
+ ...(step.candidate.publicDiscovery.agentCardCid ? { agentCardCid: step.candidate.publicDiscovery.agentCardCid } : {}),
99
+ ...(step.candidate.publicDiscovery.updatedAt ? { updatedAt: step.candidate.publicDiscovery.updatedAt } : {}),
100
+ status: 'pinned',
101
+ },
102
+ } : {}),
103
+ }
104
+ if (continuityFiles) {
105
+ await writeContinuityFiles(nextIdentity, continuityFiles)
106
+ }
107
+ callbacks.onRestoreProgress?.({ phase: 'finishing', label: 'finalizing restored identity...' })
108
+ await restorePublishedPublicSkills(nextIdentity, step.apiUrl, step.candidate.publicDiscovery?.skillsCid)
109
+ await ensureIdentityMarkdownScaffold(nextIdentity)
110
+ await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'restored from agent backup' }).catch(() => null)
111
+ await callbacks.onIdentityComplete(nextIdentity, `ERC-8004 agent restored · #${step.candidate.agentId.toString()}`, 'restore')
112
+ }