ethagent 1.1.2 → 2.0.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 (268) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +124 -32
  3. package/package.json +8 -3
  4. package/src/app/FirstRun.tsx +190 -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 +35 -15
  11. package/src/chat/ContextLimitView.tsx +4 -4
  12. package/src/chat/ContinuityEditReviewView.tsx +10 -22
  13. package/src/chat/CopyPicker.tsx +0 -1
  14. package/src/chat/MessageList.tsx +62 -45
  15. package/src/chat/PermissionPrompt.tsx +13 -9
  16. package/src/chat/PlanApprovalView.tsx +3 -3
  17. package/src/chat/ResumeView.tsx +1 -4
  18. package/src/chat/RewindView.tsx +2 -2
  19. package/src/chat/chatInputState.ts +1 -1
  20. package/src/chat/chatScreenUtils.ts +22 -11
  21. package/src/chat/chatSessionState.ts +2 -2
  22. package/src/chat/chatTurnOrchestrator.ts +16 -81
  23. package/src/chat/commands.ts +1 -1
  24. package/src/chat/textCursor.ts +1 -1
  25. package/src/chat/transcriptViewport.ts +2 -7
  26. package/src/cli/ResetConfirmView.tsx +1 -1
  27. package/src/cli/main.tsx +9 -3
  28. package/src/cli/preview.tsx +0 -5
  29. package/src/cli/updateNotice.ts +4 -2
  30. package/src/identity/continuity/editor.ts +7 -107
  31. package/src/identity/continuity/envelope.ts +1048 -40
  32. package/src/identity/continuity/history.ts +4 -4
  33. package/src/identity/continuity/localBackup.ts +249 -0
  34. package/src/identity/continuity/privateEdit/apply.ts +170 -0
  35. package/src/identity/continuity/privateEdit/diff.ts +82 -0
  36. package/src/identity/continuity/privateEdit/files.ts +23 -0
  37. package/src/identity/continuity/privateEdit/types.ts +28 -0
  38. package/src/identity/continuity/privateEdit.ts +10 -298
  39. package/src/identity/continuity/publicSkills.ts +8 -9
  40. package/src/identity/continuity/snapshots.ts +17 -6
  41. package/src/identity/continuity/storage/defaults.ts +111 -0
  42. package/src/identity/continuity/storage/files.ts +72 -0
  43. package/src/identity/continuity/storage/markdown.ts +81 -0
  44. package/src/identity/continuity/storage/paths.ts +24 -0
  45. package/src/identity/continuity/storage/scaffold.ts +124 -0
  46. package/src/identity/continuity/storage/status.ts +86 -0
  47. package/src/identity/continuity/storage/types.ts +27 -0
  48. package/src/identity/continuity/storage.ts +32 -507
  49. package/src/identity/continuity/zipWriter.ts +95 -0
  50. package/src/identity/crypto/backupEnvelope.ts +14 -247
  51. package/src/identity/crypto/eth.ts +7 -7
  52. package/src/identity/ens/agentRecords.ts +96 -0
  53. package/src/identity/ens/ensAutomation/contracts.ts +38 -0
  54. package/src/identity/ens/ensAutomation/delete.ts +80 -0
  55. package/src/identity/ens/ensAutomation/names.ts +14 -0
  56. package/src/identity/ens/ensAutomation/operators.ts +29 -0
  57. package/src/identity/ens/ensAutomation/read.ts +114 -0
  58. package/src/identity/ens/ensAutomation/root.ts +63 -0
  59. package/src/identity/ens/ensAutomation/setup.ts +284 -0
  60. package/src/identity/ens/ensAutomation/transactions.ts +107 -0
  61. package/src/identity/ens/ensAutomation/types.ts +126 -0
  62. package/src/identity/ens/ensAutomation.ts +29 -0
  63. package/src/identity/ens/ensLookup/client.ts +43 -0
  64. package/src/identity/ens/ensLookup/constants.ts +26 -0
  65. package/src/identity/ens/ensLookup/discovery.ts +70 -0
  66. package/src/identity/ens/ensLookup/names.ts +34 -0
  67. package/src/identity/ens/ensLookup/records.ts +45 -0
  68. package/src/identity/ens/ensLookup/resolve.ts +75 -0
  69. package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
  70. package/src/identity/ens/ensLookup/types.ts +38 -0
  71. package/src/identity/ens/ensLookup/validation.ts +72 -0
  72. package/src/identity/ens/ensLookup.ts +19 -0
  73. package/src/identity/ens/ensRegistration.ts +199 -0
  74. package/src/identity/ens/resolverDelegation.ts +48 -0
  75. package/src/identity/hub/IdentityHub.tsx +13 -817
  76. package/src/identity/hub/OperationalRoutes.tsx +370 -0
  77. package/src/identity/hub/Routes.tsx +361 -0
  78. package/src/identity/hub/advancedEnsValidation.ts +45 -0
  79. package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
  80. package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
  81. package/src/identity/hub/components/FlowTimeline.tsx +27 -0
  82. package/src/identity/hub/components/IdentitySummary.tsx +190 -0
  83. package/src/identity/hub/components/MenuScreen.tsx +237 -0
  84. package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
  85. package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
  86. package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
  87. package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
  88. package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
  89. package/src/identity/hub/effects/create.ts +310 -0
  90. package/src/identity/hub/effects/ens/flows.ts +218 -0
  91. package/src/identity/hub/effects/ens/index.ts +11 -0
  92. package/src/identity/hub/effects/ens/transactions.ts +239 -0
  93. package/src/identity/hub/effects/index.ts +74 -0
  94. package/src/identity/hub/effects/profile/profileState.ts +173 -0
  95. package/src/identity/hub/effects/publicProfile/index.ts +5 -0
  96. package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
  97. package/src/identity/hub/effects/rebackup/index.ts +7 -0
  98. package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
  99. package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
  100. package/src/identity/hub/effects/receipts.ts +46 -0
  101. package/src/identity/hub/effects/restore/apply.ts +112 -0
  102. package/src/identity/hub/effects/restore/auth.ts +159 -0
  103. package/src/identity/hub/effects/restore/discover.ts +86 -0
  104. package/src/identity/hub/effects/restore/envelopes.ts +21 -0
  105. package/src/identity/hub/effects/restore/fetch.ts +25 -0
  106. package/src/identity/hub/effects/restore/index.ts +22 -0
  107. package/src/identity/hub/effects/restore/recovery.ts +135 -0
  108. package/src/identity/hub/effects/restore/resolve.ts +102 -0
  109. package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
  110. package/src/identity/hub/effects/restore/shared.ts +91 -0
  111. package/src/identity/hub/effects/restoreAdmin.ts +93 -0
  112. package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
  113. package/src/identity/hub/effects/shared/snapshot.ts +336 -0
  114. package/src/identity/hub/effects/shared/sync.ts +190 -0
  115. package/src/identity/hub/effects/token-transfer/index.ts +6 -0
  116. package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
  117. package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
  118. package/src/identity/hub/effects/types.ts +53 -0
  119. package/src/identity/hub/effects/vault/preflight.ts +50 -0
  120. package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
  121. package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
  122. package/src/identity/hub/{screens → flows/continuity}/RecoveryConfirmScreen.tsx +28 -19
  123. package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
  124. package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
  125. package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
  126. package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
  127. package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
  128. package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
  129. package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
  130. package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
  131. package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
  132. package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
  133. package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
  134. package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
  135. package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
  136. package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
  137. package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
  138. package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
  139. package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
  140. package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
  141. package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
  142. package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
  143. package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
  144. package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
  145. package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
  146. package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
  147. package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +23 -44
  148. package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
  149. package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
  150. package/src/identity/hub/identityHubReducer.ts +164 -99
  151. package/src/identity/hub/model/continuity.ts +94 -0
  152. package/src/identity/hub/model/copy.ts +35 -0
  153. package/src/identity/hub/model/custody.ts +54 -0
  154. package/src/identity/hub/model/ens.ts +49 -0
  155. package/src/identity/hub/model/errors.ts +140 -0
  156. package/src/identity/hub/model/format.ts +15 -0
  157. package/src/identity/hub/model/identity.ts +94 -0
  158. package/src/identity/hub/model/network.ts +32 -0
  159. package/src/identity/hub/model/transfer.ts +57 -0
  160. package/src/identity/hub/operatorWallets.ts +131 -0
  161. package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
  162. package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
  163. package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
  164. package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
  165. package/src/identity/hub/reconciliation/index.ts +21 -0
  166. package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
  167. package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
  168. package/src/identity/hub/txGuard.ts +51 -0
  169. package/src/identity/hub/types.ts +17 -0
  170. package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
  171. package/src/identity/hub/useIdentityHubController.ts +396 -0
  172. package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
  173. package/src/identity/hub/utils.ts +79 -0
  174. package/src/identity/identityCompat.ts +34 -0
  175. package/src/identity/profile/agentIcon.ts +61 -0
  176. package/src/identity/profile/imagePicker.ts +12 -12
  177. package/src/identity/registry/erc8004/abi.ts +14 -0
  178. package/src/identity/registry/erc8004/chains.ts +150 -0
  179. package/src/identity/registry/erc8004/client.ts +11 -0
  180. package/src/identity/registry/erc8004/discovery.ts +511 -0
  181. package/src/identity/registry/erc8004/metadata.ts +335 -0
  182. package/src/identity/registry/erc8004/ownership.ts +121 -0
  183. package/src/identity/registry/erc8004/preflight.ts +123 -0
  184. package/src/identity/registry/erc8004/transactions.ts +77 -0
  185. package/src/identity/registry/erc8004/types.ts +88 -0
  186. package/src/identity/registry/erc8004/uri.ts +59 -0
  187. package/src/identity/registry/erc8004/utils.ts +58 -0
  188. package/src/identity/registry/erc8004.ts +53 -1106
  189. package/src/identity/registry/fieldParsers.ts +28 -0
  190. package/src/identity/registry/operatorVault/bytecode.ts +98 -0
  191. package/src/identity/registry/operatorVault/constants.ts +38 -0
  192. package/src/identity/registry/operatorVault/read.ts +246 -0
  193. package/src/identity/registry/operatorVault/transactions.ts +81 -0
  194. package/src/identity/registry/operatorVault.ts +44 -0
  195. package/src/identity/storage/ipfs.ts +26 -24
  196. package/src/identity/wallet/browserWallet/gas.ts +41 -0
  197. package/src/identity/wallet/browserWallet/html.ts +106 -0
  198. package/src/identity/wallet/browserWallet/http.ts +28 -0
  199. package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
  200. package/src/identity/wallet/browserWallet/requests.ts +191 -0
  201. package/src/identity/wallet/browserWallet/session.ts +325 -0
  202. package/src/identity/wallet/browserWallet/types.ts +192 -0
  203. package/src/identity/wallet/browserWallet/validation.ts +74 -0
  204. package/src/identity/wallet/browserWallet.ts +30 -393
  205. package/src/identity/wallet/page/constants.ts +5 -0
  206. package/src/identity/wallet/page/controller.ts +251 -0
  207. package/src/identity/wallet/page/copy.ts +340 -0
  208. package/src/identity/wallet/page/grainient.ts +278 -0
  209. package/src/identity/wallet/page/html.ts +28 -0
  210. package/src/identity/wallet/page/markup.ts +50 -0
  211. package/src/identity/wallet/page/state.ts +9 -0
  212. package/src/identity/wallet/page/styles/base.ts +259 -0
  213. package/src/identity/wallet/page/styles/components.ts +262 -0
  214. package/src/identity/wallet/page/styles/index.ts +5 -0
  215. package/src/identity/wallet/page/styles/responsive.ts +247 -0
  216. package/src/identity/wallet/page/types.ts +47 -0
  217. package/src/identity/wallet/page/view.ts +535 -0
  218. package/src/identity/wallet/page/walletProvider.ts +70 -0
  219. package/src/identity/wallet/page.tsx +38 -0
  220. package/src/identity/wallet/walletPurposeCompat.ts +27 -0
  221. package/src/mcp/manager.ts +0 -1
  222. package/src/models/ModelPicker.tsx +36 -30
  223. package/src/models/catalog.ts +5 -2
  224. package/src/models/huggingface.ts +9 -9
  225. package/src/models/llamacpp.ts +13 -13
  226. package/src/models/modelDisplay.ts +75 -0
  227. package/src/models/modelPickerOptions.ts +16 -3
  228. package/src/models/modelRecommendation.ts +0 -1
  229. package/src/providers/errors.ts +16 -0
  230. package/src/providers/gemini.ts +252 -39
  231. package/src/providers/registry.ts +2 -2
  232. package/src/providers/retry.ts +1 -1
  233. package/src/runtime/sessionMode.ts +1 -1
  234. package/src/runtime/systemPrompt.ts +2 -0
  235. package/src/runtime/toolExecution.ts +18 -22
  236. package/src/runtime/toolIntent.ts +0 -20
  237. package/src/runtime/turn.ts +0 -92
  238. package/src/storage/atomicWrite.ts +4 -1
  239. package/src/storage/config.ts +181 -5
  240. package/src/storage/identity.ts +9 -3
  241. package/src/storage/secrets.ts +2 -2
  242. package/src/tools/bashSafety.ts +8 -0
  243. package/src/tools/changeDirectoryTool.ts +1 -1
  244. package/src/tools/deleteFileTool.ts +4 -4
  245. package/src/tools/editTool.ts +4 -4
  246. package/src/tools/editUtils.ts +5 -5
  247. package/src/tools/privateContinuityEditTool.ts +4 -5
  248. package/src/tools/privateContinuityReadTool.ts +1 -2
  249. package/src/tools/registry.ts +30 -0
  250. package/src/tools/writeFileTool.ts +5 -5
  251. package/src/ui/BrandSplash.tsx +20 -85
  252. package/src/ui/ProgressBar.tsx +3 -5
  253. package/src/ui/Select.tsx +20 -8
  254. package/src/ui/Spinner.tsx +38 -3
  255. package/src/ui/Surface.tsx +2 -2
  256. package/src/ui/TextInput.tsx +63 -20
  257. package/src/ui/theme.ts +7 -34
  258. package/src/utils/openExternal.ts +21 -0
  259. package/src/utils/withRetry.ts +47 -3
  260. package/src/identity/hub/identityHubEffects.ts +0 -937
  261. package/src/identity/hub/identityHubModel.ts +0 -371
  262. package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -156
  263. package/src/identity/hub/screens/EditProfileFlow.tsx +0 -146
  264. package/src/identity/hub/screens/IdentitySummary.tsx +0 -106
  265. package/src/identity/hub/screens/MenuScreen.tsx +0 -117
  266. package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
  267. package/src/identity/wallet/wallet-page/wallet.html +0 -1202
  268. /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
@@ -1,937 +0,0 @@
1
- import fs from 'node:fs/promises'
2
- import os from 'node:os'
3
- import path from 'node:path'
4
- import type { Address, Hex } from 'viem'
5
- import type { EthagentConfig, EthagentIdentity, SelectableNetwork } from '../../storage/config.js'
6
- import { saveConfig } from '../../storage/config.js'
7
- import {
8
- assertAgentStateBackupOwner,
9
- parseAgentStateBackupEnvelope,
10
- restoreAgentStateBackupEnvelope,
11
- } from '../crypto/backupEnvelope.js'
12
- import {
13
- CONTINUITY_SNAPSHOT_ENVELOPE_VERSION,
14
- assertContinuitySnapshotOwner,
15
- createContinuitySnapshotChallenge,
16
- createContinuitySnapshotEnvelope,
17
- parseContinuitySnapshotEnvelope,
18
- restoreContinuitySnapshotEnvelope,
19
- serializeContinuitySnapshotEnvelope,
20
- type ContinuitySnapshotEnvelope,
21
- } from '../continuity/envelope.js'
22
- import {
23
- continuityAgentSnapshot,
24
- continuityVaultStatus,
25
- defaultContinuityFiles,
26
- ensurePublicSkillsFile,
27
- ensureIdentityMarkdownScaffold,
28
- ensureContinuityFiles,
29
- continuitySnapshotContentHashes,
30
- equalContinuitySnapshotHashes,
31
- localContinuitySnapshotContentHashes,
32
- prepareSyncedIdentityMarkdownScaffold,
33
- prepareSyncedPublicSkillsJson,
34
- readContinuityFiles,
35
- readPublicSkillsFile,
36
- writeContinuityFiles,
37
- writeIdentityMarkdownScaffold,
38
- writePublicSkillsFile,
39
- type IdentityMarkdownScaffold,
40
- } from '../continuity/storage.js'
41
- import {
42
- createAgentCard,
43
- defaultPublicSkillsProfile,
44
- renderPublicSkillsJson,
45
- serializeAgentCard,
46
- } from '../continuity/publicSkills.js'
47
- import {
48
- recordPublishedContinuitySnapshot,
49
- updatePublishedContinuitySnapshotContentHashes,
50
- } from '../continuity/snapshots.js'
51
- import { addFileToIpfs, addToIpfs, catFromIpfs, DEFAULT_IPFS_API_URL, isPinataUploadUrl, type IpfsAddResult } from '../storage/ipfs.js'
52
- import {
53
- AgentTokenIdRequiredError,
54
- chainIdForNetwork,
55
- createErc8004PublicClient,
56
- discoverOwnedAgentBackups,
57
- discoverOwnedAgentBackupByTokenId,
58
- encodeRegisterAgent,
59
- encodeSetAgentUri,
60
- erc8004ConfigForSupportedChain,
61
- normalizeErc8004RegistryConfig,
62
- preflightRegisterAgent,
63
- preflightSetAgentUri,
64
- registeredAgentFromReceipt,
65
- withEthagentBackupPointer,
66
- withEthagentPointers,
67
- type Erc8004AgentCandidate,
68
- type Erc8004RegistryConfig,
69
- } from '../registry/erc8004.js'
70
- import { getAddress } from 'viem'
71
- import { registryConfigFromConfig, type RegistryResolution } from '../registry/registryConfig.js'
72
- import { resolveValidatedPinataJwt, savePinataJwt } from '../storage/pinataJwt.js'
73
- import {
74
- requestBrowserWalletAccount,
75
- requestBrowserWalletSignature,
76
- requestBrowserWalletSignatureAndTransaction,
77
- sendBrowserWalletTransaction,
78
- type BrowserWalletReady,
79
- } from '../wallet/browserWallet.js'
80
- import { initialAgentState, PREFLIGHT_AGENT_URI } from './identityHubModel.js'
81
- import type { Step, ProfileUpdates, RestorePurpose } from './identityHubReducer.js'
82
-
83
- type BackupMetadata = NonNullable<EthagentIdentity['backup']>
84
- type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
85
-
86
- type CreatePreparedTransaction = {
87
- ownerAddress: Address
88
- agentUri: string
89
- metadataCid: string
90
- backup: BackupMetadata
91
- publicSkills: PublicSkillsMetadata
92
- state: Record<string, unknown>
93
- continuityFiles: ReturnType<typeof defaultContinuityFiles>
94
- publicSkillsJson: string
95
- }
96
-
97
- type RebackupPreparedTransaction = {
98
- ownerAddress: Address
99
- agentUri: string
100
- metadataCid: string
101
- backup: BackupMetadata
102
- publicSkills: PublicSkillsMetadata
103
- identity: EthagentIdentity
104
- markdownScaffold?: IdentityMarkdownScaffold
105
- }
106
-
107
- type PublicProfilePreparedTransaction = {
108
- ownerAddress: Address
109
- agentUri: string
110
- metadataCid: string
111
- publicSkills: PublicSkillsMetadata
112
- identity: EthagentIdentity
113
- publicSkillsJson: string
114
- }
115
-
116
- export type EffectCallbacks = {
117
- onStep: (step: Step) => void
118
- onWalletReady: (session: BrowserWalletReady | null) => void
119
- onIdentityComplete: (identity: EthagentIdentity, message: string) => Promise<void>
120
- onRestoreProgress?: (progress: RestoreProgress | null) => void
121
- }
122
-
123
- export type RestoreProgress = {
124
- phase: 'decrypting' | 'writing' | 'finishing'
125
- label: string
126
- }
127
-
128
- export async function runCreatePreflight(
129
- step: Extract<Step, { kind: 'create-preflight' }>,
130
- config: EthagentConfig | undefined,
131
- callbacks: EffectCallbacks,
132
- ): Promise<void> {
133
- const resolution = step.network
134
- ? registryResolutionForNetwork(step.network)
135
- : registryConfigFromConfig(config)
136
- if (!resolution.config) {
137
- callbacks.onStep({ kind: 'create-registry', name: step.name, description: step.description, resolution })
138
- return
139
- }
140
- const apiUrl = DEFAULT_IPFS_API_URL
141
- let jwt: string | undefined
142
- try {
143
- jwt = isPinataUploadUrl(apiUrl) ? await resolveValidatedPinataJwt() : undefined
144
- } catch (err: unknown) {
145
- callbacks.onStep({
146
- kind: 'create-storage',
147
- name: step.name,
148
- description: step.description,
149
- registry: resolution.config,
150
- error: (err as Error).message,
151
- })
152
- return
153
- }
154
- if (isPinataUploadUrl(apiUrl) && !jwt) {
155
- callbacks.onStep({ kind: 'create-storage', name: step.name, description: step.description, registry: resolution.config })
156
- return
157
- }
158
- callbacks.onStep({ kind: 'create-signing', name: step.name, description: step.description, registry: resolution.config, pinataJwt: jwt })
159
- }
160
-
161
- function registryResolutionForNetwork(network: SelectableNetwork): RegistryResolution {
162
- const chainId = chainIdForNetwork(network)
163
- try {
164
- const registry = erc8004ConfigForSupportedChain(chainId)
165
- return {
166
- config: registry,
167
- network,
168
- chainId,
169
- needsRegistryAddress: false,
170
- defaultRpcUrl: registry.rpcUrl,
171
- }
172
- } catch {
173
- return {
174
- config: null,
175
- network,
176
- chainId,
177
- needsRegistryAddress: true,
178
- defaultRpcUrl: '',
179
- }
180
- }
181
- }
182
-
183
- export async function runCreateSigning(
184
- step: Extract<Step, { kind: 'create-signing' }>,
185
- callbacks: EffectCallbacks,
186
- ): Promise<void> {
187
- const result = await requestBrowserWalletSignatureAndTransaction<CreatePreparedTransaction>({
188
- chainId: step.registry.chainId,
189
- messageForAccount: account => createContinuitySnapshotChallenge(account),
190
- onReady: callbacks.onWalletReady,
191
- prepareTransaction: async wallet => {
192
- await preflightRegisterAgent({
193
- ...step.registry,
194
- ownerAddress: wallet.account,
195
- agentURI: PREFLIGHT_AGENT_URI,
196
- })
197
- const state = initialAgentState(step.name, step.description, wallet.account)
198
- const draftIdentity = identityDraftForBackup({
199
- ownerAddress: wallet.account,
200
- registry: step.registry,
201
- state,
202
- })
203
- const continuityFiles = defaultContinuityFiles(draftIdentity)
204
- const publicProfile = defaultPublicSkillsProfile(draftIdentity)
205
- const publicSkillsJson = renderPublicSkillsJson(publicProfile)
206
- const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
207
- assertVerifiedPin(publicSkillsPin)
208
- const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeAgentCard(createAgentCard(publicProfile)), fetch, { pinataJwt: step.pinataJwt })
209
- assertVerifiedPin(agentCardPin)
210
- const envelope = createContinuitySnapshotEnvelope({
211
- ownerAddress: wallet.account,
212
- walletSignature: wallet.signature,
213
- payload: {
214
- agent: continuityAgentSnapshot(draftIdentity),
215
- files: continuityFiles,
216
- transcript: [],
217
- state,
218
- },
219
- })
220
- const statePin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeContinuitySnapshotEnvelope(envelope), fetch, { pinataJwt: step.pinataJwt })
221
- assertVerifiedPin(statePin)
222
- const cid = statePin.cid
223
- const backup: BackupMetadata = {
224
- cid,
225
- createdAt: envelope.createdAt,
226
- envelopeVersion: envelope.envelopeVersion,
227
- ipfsApiUrl: DEFAULT_IPFS_API_URL,
228
- status: 'pinned',
229
- ownerAddress: wallet.account,
230
- chainId: step.registry.chainId,
231
- rpcUrl: step.registry.rpcUrl,
232
- identityRegistryAddress: step.registry.identityRegistryAddress,
233
- }
234
- const publicSkills: PublicSkillsMetadata = {
235
- cid: publicSkillsPin.cid,
236
- agentCardCid: agentCardPin.cid,
237
- updatedAt: envelope.createdAt,
238
- status: 'pinned',
239
- }
240
- const registration = withEthagentBackupPointer({
241
- type: 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1',
242
- name: step.name,
243
- ...(step.description ? { description: step.description } : {}),
244
- ...(typeof state.imageUrl === 'string' ? { image: state.imageUrl } : {}),
245
- }, {
246
- cid,
247
- envelopeVersion: envelope.envelopeVersion,
248
- createdAt: envelope.createdAt,
249
- }, {
250
- skillsCid: publicSkills.cid,
251
- agentCardCid: publicSkills.agentCardCid,
252
- updatedAt: publicSkills.updatedAt,
253
- }, {
254
- chainId: step.registry.chainId,
255
- identityRegistryAddress: step.registry.identityRegistryAddress,
256
- })
257
- const metadataPin = await addToIpfs(DEFAULT_IPFS_API_URL, JSON.stringify(registration, null, 2), fetch, { pinataJwt: step.pinataJwt })
258
- assertVerifiedPin(metadataPin)
259
- const metadataCid = metadataPin.cid
260
- const agentUri = `ipfs://${metadataCid}`
261
- return {
262
- to: step.registry.identityRegistryAddress,
263
- data: encodeRegisterAgent({ agentURI: agentUri }),
264
- prepared: {
265
- ownerAddress: wallet.account,
266
- agentUri,
267
- metadataCid,
268
- backup: { ...backup, metadataCid, agentUri },
269
- publicSkills,
270
- state,
271
- continuityFiles,
272
- publicSkillsJson,
273
- },
274
- }
275
- },
276
- })
277
- const client = createErc8004PublicClient(step.registry)
278
- const receipt = await client.waitForTransactionReceipt({ hash: result.txHash })
279
- const registered = registeredAgentFromReceipt({
280
- logs: receipt.logs.map(log => ({ address: log.address, topics: [...log.topics] as Hex[], data: log.data })),
281
- identityRegistryAddress: step.registry.identityRegistryAddress,
282
- ownerAddress: result.prepared.ownerAddress,
283
- })
284
- const backup: BackupMetadata = {
285
- ...result.prepared.backup,
286
- agentId: registered.agentId.toString(),
287
- agentUri: registered.agentURI,
288
- txHash: result.txHash,
289
- }
290
- const nextIdentity: EthagentIdentity = {
291
- source: 'erc8004',
292
- address: result.prepared.ownerAddress,
293
- ownerAddress: result.prepared.ownerAddress,
294
- createdAt: result.prepared.backup.createdAt,
295
- chainId: step.registry.chainId,
296
- rpcUrl: step.registry.rpcUrl,
297
- identityRegistryAddress: step.registry.identityRegistryAddress,
298
- agentId: registered.agentId.toString(),
299
- agentUri: registered.agentURI,
300
- metadataCid: result.prepared.metadataCid,
301
- state: result.prepared.state,
302
- backup,
303
- publicSkills: result.prepared.publicSkills,
304
- }
305
- await writeIdentityMarkdownScaffold(nextIdentity, {
306
- ...defaultContinuityFiles(nextIdentity),
307
- 'skills.json': result.prepared.publicSkillsJson,
308
- })
309
- await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'initial published snapshot' }).catch(() => null)
310
- await callbacks.onIdentityComplete(nextIdentity, `ERC-8004 agent registered · #${registered.agentId.toString()}`)
311
- }
312
-
313
- export async function runRestoreDiscover(
314
- step: Extract<Step, { kind: 'restore-discovering' }>,
315
- _config: EthagentConfig | undefined,
316
- callbacks: EffectCallbacks,
317
- ): Promise<void> {
318
- const candidates = await discoverOwnedAgentBackups({
319
- ...step.registry,
320
- ownerHandle: step.ownerHandle,
321
- ipfsApiUrl: DEFAULT_IPFS_API_URL,
322
- })
323
- callbacks.onStep(restoreTokenSelectionStep({
324
- ownerHandle: step.ownerHandle,
325
- registry: step.registry,
326
- candidates,
327
- purpose: step.purpose,
328
- }))
329
- }
330
-
331
- export async function runRestoreConnectWallet(
332
- step: Extract<Step, { kind: 'restore-wallet' }>,
333
- callbacks: EffectCallbacks,
334
- ): Promise<void> {
335
- const wallet = await requestBrowserWalletAccount({
336
- onReady: callbacks.onWalletReady,
337
- })
338
- callbacks.onStep({ kind: 'restore-network', ownerHandle: wallet.account, purpose: step.purpose })
339
- }
340
-
341
- export function restoreTokenSelectionStep(args: {
342
- ownerHandle: string
343
- registry: Erc8004RegistryConfig
344
- candidates: Erc8004AgentCandidate[]
345
- purpose?: RestorePurpose
346
- }): Extract<Step, { kind: 'restore-select-token' }> {
347
- const restorable = args.candidates.filter(candidate => candidate.backup?.cid)
348
- if (restorable.length === 0) {
349
- throw new Error(args.candidates.length === 0
350
- ? 'no agent identities owned by that wallet on this network'
351
- : 'no owned agent identity has recoverable ethagent state on this network')
352
- }
353
- return {
354
- kind: 'restore-select-token',
355
- ownerHandle: args.ownerHandle,
356
- registry: args.registry,
357
- candidates: restorable,
358
- purpose: args.purpose,
359
- }
360
- }
361
-
362
- export function isAgentTokenIdRequiredError(err: unknown): err is AgentTokenIdRequiredError {
363
- return err instanceof AgentTokenIdRequiredError
364
- }
365
-
366
- export async function runRestoreTokenIdSubmit(
367
- value: string,
368
- step: Extract<Step, { kind: 'restore-token-id' }>,
369
- callbacks: EffectCallbacks,
370
- ): Promise<void> {
371
- const tokenId = parseTokenId(value)
372
- const candidate = await discoverOwnedAgentBackupByTokenId({
373
- ...step.registry,
374
- ownerHandle: step.ownerHandle,
375
- tokenId,
376
- ipfsApiUrl: DEFAULT_IPFS_API_URL,
377
- })
378
- if (!candidate.backup?.cid) {
379
- throw new Error('that agent token does not have recoverable ethagent state')
380
- }
381
- callbacks.onStep({
382
- kind: 'restore-fetching',
383
- cid: candidate.backup.cid,
384
- apiUrl: DEFAULT_IPFS_API_URL,
385
- candidate,
386
- purpose: step.purpose,
387
- })
388
- }
389
-
390
- function parseTokenId(value: string): bigint {
391
- const normalized = value.trim().replace(/^#/, '')
392
- if (!/^\d+$/.test(normalized)) throw new Error('enter a token id')
393
- return BigInt(normalized)
394
- }
395
-
396
- export async function runRestoreFetch(
397
- step: Extract<Step, { kind: 'restore-fetching' }>,
398
- callbacks: EffectCallbacks,
399
- ): Promise<void> {
400
- const raw = await catFromIpfs(step.apiUrl, step.cid)
401
- const envelope = parseRestorableEnvelope(raw)
402
- if (isContinuitySnapshotEnvelope(envelope)) {
403
- assertContinuitySnapshotOwner(envelope, step.candidate.ownerAddress)
404
- } else {
405
- assertAgentStateBackupOwner(envelope, step.candidate.ownerAddress)
406
- }
407
- callbacks.onStep({ kind: 'restore-authorizing', cid: step.cid, apiUrl: step.apiUrl, envelope, candidate: step.candidate, purpose: step.purpose })
408
- }
409
-
410
- export async function runRestoreAuthorize(
411
- step: Extract<Step, { kind: 'restore-authorizing' }>,
412
- callbacks: EffectCallbacks,
413
- ): Promise<void> {
414
- const wallet = await requestBrowserWalletSignature({
415
- chainId: step.candidate.chainId,
416
- expectedAccount: step.candidate.ownerAddress,
417
- message: step.envelope.challenge,
418
- onReady: callbacks.onWalletReady,
419
- })
420
- callbacks.onWalletReady(null)
421
- callbacks.onRestoreProgress?.({ phase: 'decrypting', label: 'signature received · decrypting encrypted snapshot...' })
422
- let restored: ReturnType<typeof restoreAgentStateBackupEnvelope> | ReturnType<typeof restoreContinuitySnapshotEnvelope>
423
- let continuityFiles: ReturnType<typeof restoreContinuitySnapshotEnvelope>['files'] | undefined
424
- if (isContinuitySnapshotEnvelope(step.envelope)) {
425
- const payload = restoreContinuitySnapshotEnvelope({
426
- envelope: step.envelope,
427
- walletSignature: wallet.signature,
428
- })
429
- restored = payload
430
- continuityFiles = payload.files
431
- } else {
432
- restored = restoreAgentStateBackupEnvelope({
433
- envelope: step.envelope,
434
- walletSignature: wallet.signature,
435
- })
436
- }
437
- callbacks.onRestoreProgress?.({ phase: 'writing', label: 'restoring local agent files...' })
438
- const backup: BackupMetadata = {
439
- cid: step.cid,
440
- createdAt: step.envelope.createdAt,
441
- envelopeVersion: step.envelope.envelopeVersion,
442
- ipfsApiUrl: step.apiUrl,
443
- status: 'restored',
444
- ownerAddress: step.candidate.ownerAddress,
445
- chainId: step.candidate.chainId,
446
- rpcUrl: step.candidate.rpcUrl,
447
- identityRegistryAddress: step.candidate.identityRegistryAddress,
448
- agentId: step.candidate.agentId.toString(),
449
- agentUri: step.candidate.agentUri,
450
- metadataCid: step.candidate.metadataCid,
451
- }
452
- const nextIdentity: EthagentIdentity = {
453
- source: 'erc8004',
454
- address: step.candidate.ownerAddress,
455
- ownerAddress: step.candidate.ownerAddress,
456
- createdAt: restored.createdAt,
457
- chainId: step.candidate.chainId,
458
- rpcUrl: step.candidate.rpcUrl,
459
- identityRegistryAddress: step.candidate.identityRegistryAddress,
460
- agentId: step.candidate.agentId.toString(),
461
- agentUri: step.candidate.agentUri,
462
- metadataCid: step.candidate.metadataCid,
463
- state: {
464
- ...restored.state,
465
- ...(step.candidate.name ? { name: step.candidate.name } : {}),
466
- ...(step.candidate.description ? { description: step.candidate.description } : {}),
467
- ...(step.candidate.imageUrl ? { imageUrl: step.candidate.imageUrl } : {}),
468
- },
469
- backup,
470
- ...(step.candidate.publicDiscovery ? {
471
- publicSkills: {
472
- ...(step.candidate.publicDiscovery.skillsCid ? { cid: step.candidate.publicDiscovery.skillsCid } : {}),
473
- ...(step.candidate.publicDiscovery.agentCardCid ? { agentCardCid: step.candidate.publicDiscovery.agentCardCid } : {}),
474
- ...(step.candidate.publicDiscovery.updatedAt ? { updatedAt: step.candidate.publicDiscovery.updatedAt } : {}),
475
- status: 'pinned',
476
- },
477
- } : {}),
478
- }
479
- if (continuityFiles) {
480
- await writeContinuityFiles(nextIdentity, continuityFiles)
481
- }
482
- callbacks.onRestoreProgress?.({ phase: 'finishing', label: 'finalizing restored identity...' })
483
- await restorePublishedPublicSkills(nextIdentity, step.apiUrl, step.candidate.publicDiscovery?.skillsCid)
484
- await ensureIdentityMarkdownScaffold(nextIdentity)
485
- await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'restored from agent backup' }).catch(() => null)
486
- await callbacks.onIdentityComplete(nextIdentity, `ERC-8004 agent restored · #${step.candidate.agentId.toString()}`)
487
- }
488
-
489
- export async function runRegistrySubmit(
490
- value: string,
491
- step: Extract<Step, { kind: 'create-registry' }>,
492
- config: EthagentConfig | undefined,
493
- onConfigChange: ((config: EthagentConfig) => void) | undefined,
494
- callbacks: EffectCallbacks,
495
- ): Promise<void> {
496
- const registry = normalizeErc8004RegistryConfig({
497
- chainId: step.resolution.chainId,
498
- rpcUrl: step.resolution.defaultRpcUrl,
499
- identityRegistryAddress: value.trim(),
500
- })
501
- if (config && onConfigChange) {
502
- const next: EthagentConfig = {
503
- ...config,
504
- erc8004: {
505
- chainId: registry.chainId,
506
- rpcUrl: registry.rpcUrl,
507
- identityRegistryAddress: registry.identityRegistryAddress,
508
- },
509
- }
510
- await saveConfig(next)
511
- onConfigChange(next)
512
- }
513
- const apiUrl = DEFAULT_IPFS_API_URL
514
- let jwt: string | undefined
515
- try {
516
- jwt = isPinataUploadUrl(apiUrl) ? await resolveValidatedPinataJwt() : undefined
517
- } catch (err: unknown) {
518
- callbacks.onStep({ kind: 'create-storage', name: step.name, description: step.description, registry, error: (err as Error).message })
519
- return
520
- }
521
- if (isPinataUploadUrl(apiUrl) && !jwt) {
522
- callbacks.onStep({ kind: 'create-storage', name: step.name, description: step.description, registry })
523
- return
524
- }
525
- callbacks.onStep({ kind: 'create-signing', name: step.name, description: step.description, registry, pinataJwt: jwt })
526
- }
527
-
528
- export async function runRestoreRegistrySubmit(
529
- value: string,
530
- step: Extract<Step, { kind: 'restore-registry' }>,
531
- config: EthagentConfig | undefined,
532
- onConfigChange: ((config: EthagentConfig) => void) | undefined,
533
- callbacks: EffectCallbacks,
534
- ): Promise<void> {
535
- const resolution = registryConfigFromConfig(config)
536
- const registry = normalizeErc8004RegistryConfig({
537
- chainId: resolution.chainId,
538
- rpcUrl: resolution.config?.rpcUrl ?? resolution.defaultRpcUrl,
539
- identityRegistryAddress: value.trim(),
540
- })
541
- if (config && onConfigChange) {
542
- const next: EthagentConfig = {
543
- ...config,
544
- erc8004: {
545
- chainId: registry.chainId,
546
- rpcUrl: registry.rpcUrl,
547
- identityRegistryAddress: registry.identityRegistryAddress,
548
- },
549
- }
550
- await saveConfig(next)
551
- onConfigChange(next)
552
- }
553
- callbacks.onStep({ kind: 'restore-discovering', ownerHandle: step.ownerHandle, registry, purpose: step.purpose })
554
- }
555
-
556
- export async function runStorageSubmit(
557
- input: string,
558
- step: Extract<Step, { kind: 'create-storage' }>,
559
- callbacks: EffectCallbacks,
560
- ): Promise<void> {
561
- const { jwt: pinataJwt } = await savePinataJwt(input)
562
- callbacks.onStep({ kind: 'create-signing', name: step.name, description: step.description, registry: step.registry, pinataJwt })
563
- }
564
-
565
- export async function runRebackupPreflight(
566
- identity: EthagentIdentity,
567
- registry: Erc8004RegistryConfig,
568
- callbacks: EffectCallbacks,
569
- profileUpdates?: ProfileUpdates,
570
- returnTo: Step = { kind: 'menu' },
571
- ): Promise<void> {
572
- const status = await continuityVaultStatus(identity)
573
- if (!status.ready) {
574
- throw new Error('restore local SOUL.md and MEMORY.md working files before saving an encrypted snapshot')
575
- }
576
- const apiUrl = DEFAULT_IPFS_API_URL
577
- let jwt: string | undefined
578
- try {
579
- jwt = isPinataUploadUrl(apiUrl) ? await resolveValidatedPinataJwt() : undefined
580
- } catch (err: unknown) {
581
- callbacks.onStep({ kind: 'rebackup-storage', identity, registry, error: (err as Error).message, profileUpdates, returnTo })
582
- return
583
- }
584
- if (isPinataUploadUrl(apiUrl) && !jwt) {
585
- callbacks.onStep({ kind: 'rebackup-storage', identity, registry, profileUpdates, returnTo })
586
- return
587
- }
588
- callbacks.onStep({ kind: 'rebackup-signing', identity, registry, pinataJwt: jwt, profileUpdates, returnTo })
589
- }
590
-
591
- export async function runRebackupSigning(
592
- step: Extract<Step, { kind: 'rebackup-signing' }>,
593
- callbacks: EffectCallbacks,
594
- ): Promise<void> {
595
- const expectedOwner = step.identity.ownerAddress ?? step.identity.address
596
- const result = await requestBrowserWalletSignatureAndTransaction<RebackupPreparedTransaction>({
597
- chainId: step.registry.chainId,
598
- messageForAccount: account => createContinuitySnapshotChallenge(account),
599
- onReady: callbacks.onWalletReady,
600
- ...(expectedOwner ? { expectedAccount: getAddress(expectedOwner) } : {}),
601
- prepareTransaction: async wallet => {
602
- if (!step.identity.agentId) throw new Error('cannot back up: identity is missing an agent token id')
603
- if (expectedOwner && wallet.account.toLowerCase() !== expectedOwner.toLowerCase()) {
604
- throw new Error(`connect the wallet that owns this agent (${expectedOwner}) and try again`)
605
- }
606
- const baseState = (step.identity.state ?? {}) as Record<string, unknown>
607
- const profile = step.profileUpdates ?? {}
608
- const nextName = typeof profile.name === 'string' && profile.name.trim() ? profile.name.trim() : (typeof baseState.name === 'string' ? baseState.name : undefined)
609
- const nextDescription = profile.description !== undefined ? profile.description.trim() : (typeof baseState.description === 'string' ? baseState.description : '')
610
- const uploadedImageUri = profile.imagePath === 'delete'
611
- ? ''
612
- : profile.imagePath
613
- ? await uploadAgentImage(profile.imagePath, step.pinataJwt)
614
- : typeof baseState.imageUrl === 'string' && baseState.imageUrl.trim()
615
- ? baseState.imageUrl.trim()
616
- : undefined
617
- const state: Record<string, unknown> = {
618
- ...baseState,
619
- ...(nextName !== undefined ? { name: nextName } : {}),
620
- description: nextDescription,
621
- lastBackedUpAt: new Date().toISOString(),
622
- }
623
- if (uploadedImageUri === '') {
624
- delete state.imageUrl
625
- } else if (uploadedImageUri) {
626
- state.imageUrl = uploadedImageUri
627
- }
628
- const nextIdentityForFiles: EthagentIdentity = { ...step.identity, state }
629
- const markdownScaffold = step.profileUpdates
630
- ? await prepareSyncedIdentityMarkdownScaffold(nextIdentityForFiles)
631
- : undefined
632
- const continuityFiles = markdownScaffold
633
- ? { 'SOUL.md': markdownScaffold['SOUL.md'], 'MEMORY.md': markdownScaffold['MEMORY.md'] }
634
- : await readContinuityFiles(nextIdentityForFiles)
635
- const publicSkillsJson = markdownScaffold
636
- ? markdownScaffold['skills.json']
637
- : await readPublicSkillsFile(nextIdentityForFiles)
638
- const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
639
- assertVerifiedPin(publicSkillsPin)
640
- const agentCardPin = await addToIpfs(
641
- DEFAULT_IPFS_API_URL,
642
- serializeAgentCard(createAgentCard(defaultPublicSkillsProfile(nextIdentityForFiles))),
643
- fetch,
644
- { pinataJwt: step.pinataJwt },
645
- )
646
- assertVerifiedPin(agentCardPin)
647
- const envelope = createContinuitySnapshotEnvelope({
648
- ownerAddress: wallet.account,
649
- walletSignature: wallet.signature,
650
- payload: {
651
- agent: continuityAgentSnapshot(nextIdentityForFiles),
652
- files: continuityFiles,
653
- transcript: [],
654
- state,
655
- },
656
- })
657
- const statePin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeContinuitySnapshotEnvelope(envelope), fetch, { pinataJwt: step.pinataJwt })
658
- assertVerifiedPin(statePin)
659
- const cid = statePin.cid
660
- const backup: BackupMetadata = {
661
- cid,
662
- createdAt: envelope.createdAt,
663
- envelopeVersion: envelope.envelopeVersion,
664
- ipfsApiUrl: DEFAULT_IPFS_API_URL,
665
- status: 'pinned',
666
- ownerAddress: wallet.account,
667
- chainId: step.registry.chainId,
668
- rpcUrl: step.registry.rpcUrl,
669
- identityRegistryAddress: step.registry.identityRegistryAddress,
670
- agentId: step.identity.agentId,
671
- }
672
- const publicSkills: PublicSkillsMetadata = {
673
- cid: publicSkillsPin.cid,
674
- agentCardCid: agentCardPin.cid,
675
- updatedAt: envelope.createdAt,
676
- status: 'pinned',
677
- }
678
- const registration = withEthagentBackupPointer({
679
- type: 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1',
680
- name: nextName ?? deriveAgentName(step.identity),
681
- ...(nextDescription ? { description: nextDescription } : {}),
682
- ...(uploadedImageUri ? { image: uploadedImageUri } : {}),
683
- }, {
684
- cid,
685
- envelopeVersion: envelope.envelopeVersion,
686
- createdAt: envelope.createdAt,
687
- }, {
688
- skillsCid: publicSkills.cid,
689
- agentCardCid: publicSkills.agentCardCid,
690
- updatedAt: publicSkills.updatedAt,
691
- }, {
692
- chainId: step.registry.chainId,
693
- identityRegistryAddress: step.registry.identityRegistryAddress,
694
- agentId: step.identity.agentId,
695
- })
696
- const metadataPin = await addToIpfs(DEFAULT_IPFS_API_URL, JSON.stringify(registration, null, 2), fetch, { pinataJwt: step.pinataJwt })
697
- assertVerifiedPin(metadataPin)
698
- const metadataCid = metadataPin.cid
699
- const agentUri = `ipfs://${metadataCid}`
700
- const agentId = BigInt(step.identity.agentId)
701
- await preflightSetAgentUri({
702
- ...step.registry,
703
- account: wallet.account,
704
- agentId,
705
- newUri: agentUri,
706
- })
707
- return {
708
- to: step.registry.identityRegistryAddress,
709
- data: encodeSetAgentUri({ agentId, newUri: agentUri }),
710
- prepared: {
711
- ownerAddress: wallet.account,
712
- agentUri,
713
- metadataCid,
714
- backup: { ...backup, metadataCid, agentUri },
715
- publicSkills,
716
- identity: { ...step.identity, state },
717
- ...(markdownScaffold ? { markdownScaffold } : {}),
718
- },
719
- }
720
- },
721
- })
722
- const client = createErc8004PublicClient(step.registry)
723
- await client.waitForTransactionReceipt({ hash: result.txHash })
724
- const nextIdentity: EthagentIdentity = {
725
- ...result.prepared.identity,
726
- source: 'erc8004',
727
- address: getAddress(result.prepared.ownerAddress),
728
- ownerAddress: getAddress(result.prepared.ownerAddress),
729
- chainId: step.registry.chainId,
730
- rpcUrl: step.registry.rpcUrl,
731
- identityRegistryAddress: step.registry.identityRegistryAddress,
732
- agentUri: result.prepared.agentUri,
733
- metadataCid: result.prepared.metadataCid,
734
- backup: { ...result.prepared.backup, txHash: result.txHash },
735
- publicSkills: result.prepared.publicSkills,
736
- }
737
- if (result.prepared.markdownScaffold) {
738
- await writeIdentityMarkdownScaffold(nextIdentity, result.prepared.markdownScaffold)
739
- }
740
- await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'published encrypted snapshot' }).catch(() => null)
741
- const completionMessage = step.profileUpdates ? 'profile updated and backup saved' : 'agent backup saved'
742
- await callbacks.onIdentityComplete(nextIdentity, completionMessage)
743
- }
744
-
745
- export async function runRebackupStorageSubmit(
746
- input: string,
747
- step: Extract<Step, { kind: 'rebackup-storage' }>,
748
- callbacks: EffectCallbacks,
749
- ): Promise<void> {
750
- const { jwt: pinataJwt } = await savePinataJwt(input)
751
- callbacks.onStep({ kind: 'rebackup-signing', identity: step.identity, registry: step.registry, pinataJwt, profileUpdates: step.profileUpdates, returnTo: step.returnTo })
752
- }
753
-
754
-
755
-
756
-
757
- export async function runRecoveryRefetch(
758
- identity: EthagentIdentity,
759
- registry: Erc8004RegistryConfig,
760
- callbacks: EffectCallbacks,
761
- ): Promise<void> {
762
- if (!identity.agentId) throw new Error('cannot refetch: identity is missing an agent token id')
763
- const ownerAddress = getAddress(identity.ownerAddress ?? identity.address)
764
- const candidate = await discoverOwnedAgentBackupByTokenId({
765
- ...registry,
766
- ownerHandle: ownerAddress,
767
- tokenId: BigInt(identity.agentId),
768
- ipfsApiUrl: identity.backup?.ipfsApiUrl ?? DEFAULT_IPFS_API_URL,
769
- })
770
- if (!candidate.backup?.cid) {
771
- throw new Error('the published agent does not have a recoverable encrypted snapshot')
772
- }
773
- const apiUrl = identity.backup?.ipfsApiUrl ?? DEFAULT_IPFS_API_URL
774
- const raw = await catFromIpfs(apiUrl, candidate.backup.cid)
775
- const envelope = parseRestorableEnvelope(raw)
776
- if (!isContinuitySnapshotEnvelope(envelope)) {
777
- throw new Error('on-chain backup is in a legacy format and cannot be refetched here; use switch agent')
778
- }
779
- assertContinuitySnapshotOwner(envelope, ownerAddress)
780
- const wallet = await requestBrowserWalletSignature({
781
- chainId: candidate.chainId,
782
- expectedAccount: ownerAddress,
783
- message: envelope.challenge,
784
- onReady: callbacks.onWalletReady,
785
- })
786
- callbacks.onWalletReady(null)
787
- callbacks.onRestoreProgress?.({ phase: 'decrypting', label: 'signature received · decrypting on-chain snapshot...' })
788
- const payload = restoreContinuitySnapshotEnvelope({ envelope, walletSignature: wallet.signature })
789
- callbacks.onRestoreProgress?.({ phase: 'writing', label: 'overwriting local SOUL.md, MEMORY.md, skills.json...' })
790
- const refreshedBackup: BackupMetadata = {
791
- cid: candidate.backup.cid,
792
- createdAt: envelope.createdAt,
793
- envelopeVersion: envelope.envelopeVersion,
794
- ipfsApiUrl: apiUrl,
795
- status: 'restored',
796
- ownerAddress,
797
- chainId: candidate.chainId,
798
- rpcUrl: candidate.rpcUrl,
799
- identityRegistryAddress: candidate.identityRegistryAddress,
800
- agentId: candidate.agentId.toString(),
801
- agentUri: candidate.agentUri,
802
- metadataCid: candidate.metadataCid,
803
- }
804
- const nextIdentity: EthagentIdentity = {
805
- ...identity,
806
- source: 'erc8004',
807
- address: ownerAddress,
808
- ownerAddress,
809
- chainId: candidate.chainId,
810
- rpcUrl: candidate.rpcUrl,
811
- identityRegistryAddress: candidate.identityRegistryAddress,
812
- agentId: candidate.agentId.toString(),
813
- agentUri: candidate.agentUri,
814
- metadataCid: candidate.metadataCid,
815
- state: {
816
- ...payload.state,
817
- ...(candidate.name ? { name: candidate.name } : {}),
818
- ...(candidate.description ? { description: candidate.description } : {}),
819
- ...(candidate.imageUrl ? { imageUrl: candidate.imageUrl } : {}),
820
- },
821
- backup: refreshedBackup,
822
- ...(candidate.publicDiscovery ? {
823
- publicSkills: {
824
- ...(candidate.publicDiscovery.skillsCid ? { cid: candidate.publicDiscovery.skillsCid } : {}),
825
- ...(candidate.publicDiscovery.agentCardCid ? { agentCardCid: candidate.publicDiscovery.agentCardCid } : {}),
826
- ...(candidate.publicDiscovery.updatedAt ? { updatedAt: candidate.publicDiscovery.updatedAt } : {}),
827
- status: 'pinned',
828
- },
829
- } : {}),
830
- }
831
- await writeContinuityFiles(nextIdentity, payload.files)
832
- callbacks.onRestoreProgress?.({ phase: 'finishing', label: 'finalizing refreshed identity...' })
833
- await restorePublishedPublicSkills(nextIdentity, apiUrl, candidate.publicDiscovery?.skillsCid)
834
- await ensureIdentityMarkdownScaffold(nextIdentity)
835
- await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'refetched latest snapshot from chain' }).catch(() => null)
836
- await callbacks.onIdentityComplete(nextIdentity, 'latest published snapshot restored from chain')
837
- }
838
-
839
-
840
-
841
-
842
- function deriveAgentName(identity: EthagentIdentity): string {
843
- const state = (identity.state ?? {}) as Record<string, unknown>
844
- const name = typeof state.name === 'string' ? state.name.trim() : ''
845
- if (name) return name
846
- return identity.agentId ? `agent #${identity.agentId}` : 'unnamed agent'
847
- }
848
-
849
- async function uploadAgentImage(imagePath: string, pinataJwt: string | undefined): Promise<string> {
850
- const file = resolveImagePath(imagePath)
851
- const data = await fs.readFile(file)
852
- const contentType = imageContentType(file)
853
- const pin = await addFileToIpfs(DEFAULT_IPFS_API_URL, data, path.basename(file), contentType, fetch, { pinataJwt })
854
- assertVerifiedPin(pin)
855
- return `ipfs://${pin.cid}`
856
- }
857
-
858
- function resolveImagePath(input: string): string {
859
- const trimmed = input.trim()
860
- if (!trimmed) throw new Error('image file path is empty')
861
- if (/^https?:\/\//i.test(trimmed) || /^ipfs:\/\//i.test(trimmed)) {
862
- throw new Error('enter a local image file path; ethagent uploads it to IPFS')
863
- }
864
- if (!/\.(png|jpe?g|gif|webp|svg)$/i.test(trimmed)) {
865
- throw new Error('agent image must be png, jpg, gif, webp, or svg')
866
- }
867
- return path.resolve(trimmed.replace(/^~(?=$|[\\/])/, os.homedir()))
868
- }
869
-
870
- function imageContentType(file: string): string {
871
- const ext = path.extname(file).toLowerCase()
872
- switch (ext) {
873
- case '.png':
874
- return 'image/png'
875
- case '.jpg':
876
- case '.jpeg':
877
- return 'image/jpeg'
878
- case '.gif':
879
- return 'image/gif'
880
- case '.webp':
881
- return 'image/webp'
882
- case '.svg':
883
- return 'image/svg+xml'
884
- default:
885
- throw new Error('agent image must be png, jpg, gif, webp, or svg')
886
- }
887
- }
888
-
889
- function assertVerifiedPin(pin: IpfsAddResult, expectedCid?: string): void {
890
- if (expectedCid && pin.cid !== expectedCid) throw new Error('IPFS pin verification did not match the published CID')
891
- if (!pin.pinVerified) throw new Error(`IPFS pin was not verified for ${pin.cid}`)
892
- }
893
-
894
- function parseRestorableEnvelope(raw: string | Uint8Array): ReturnType<typeof parseAgentStateBackupEnvelope> | ContinuitySnapshotEnvelope {
895
- const text = typeof raw === 'string' ? raw : new TextDecoder().decode(raw)
896
- const parsed = JSON.parse(text) as { envelopeVersion?: unknown }
897
- if (parsed.envelopeVersion === CONTINUITY_SNAPSHOT_ENVELOPE_VERSION) {
898
- return parseContinuitySnapshotEnvelope(text)
899
- }
900
- return parseAgentStateBackupEnvelope(text)
901
- }
902
-
903
- function isContinuitySnapshotEnvelope(envelope: ReturnType<typeof parseRestorableEnvelope>): envelope is ContinuitySnapshotEnvelope {
904
- return envelope.envelopeVersion === CONTINUITY_SNAPSHOT_ENVELOPE_VERSION
905
- }
906
-
907
- function identityDraftForBackup(args: {
908
- ownerAddress: Address
909
- registry: Erc8004RegistryConfig
910
- state: Record<string, unknown>
911
- }): EthagentIdentity {
912
- return {
913
- source: 'erc8004',
914
- address: args.ownerAddress,
915
- ownerAddress: args.ownerAddress,
916
- createdAt: typeof args.state.createdAt === 'string' ? args.state.createdAt : new Date().toISOString(),
917
- chainId: args.registry.chainId,
918
- rpcUrl: args.registry.rpcUrl,
919
- identityRegistryAddress: args.registry.identityRegistryAddress,
920
- agentUri: PREFLIGHT_AGENT_URI,
921
- state: args.state,
922
- }
923
- }
924
-
925
- async function restorePublishedPublicSkills(
926
- identity: EthagentIdentity,
927
- apiUrl: string,
928
- cid: string | undefined,
929
- ): Promise<void> {
930
- if (!cid) return
931
- try {
932
- const raw = await catFromIpfs(apiUrl, cid)
933
- await writePublicSkillsFile(identity, new TextDecoder().decode(raw))
934
- } catch {
935
- // Public skills are recoverable from IPFS later and must not block private restore.
936
- }
937
- }