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,68 @@
1
+ import type { AgentReconciliation } from '../reconciliation/index.js'
2
+ import type { IdentityPerspective } from '../model/custody.js'
3
+
4
+ type MenuFlags = {
5
+ prepareTransferDisabled: boolean
6
+ prepareTransferReason?: string
7
+ custodyModeDisabled: boolean
8
+ custodyModeReason?: string
9
+ ensNameDisabled: boolean
10
+ ensNameReason?: string
11
+ saveSnapshotDisabled: boolean
12
+ refetchLatestDisabled: boolean
13
+ tokenValuesUnlinkedNote?: string
14
+
15
+ custodyAsterisk: boolean
16
+ custodyHint?: string
17
+ saveSnapshotAsterisk: boolean
18
+ saveSnapshotHint?: string
19
+ }
20
+
21
+ export function menuFlagsFromReconciliation(r: AgentReconciliation, perspective: IdentityPerspective = 'unknown'): MenuFlags {
22
+ const unlinked = r.token === 'unlinked'
23
+ const inVault = r.custody === 'advanced' || r.custody === 'mid-flow-uri-pending'
24
+ const isOperator = perspective === 'operator'
25
+
26
+ let prepareTransferReason: string | undefined
27
+ if (isOperator) {
28
+ prepareTransferReason = 'Operators cannot transfer the token.'
29
+ } else if (!unlinked && (r.custody === 'advanced' || r.custody === 'mid-flow-uri-pending')) {
30
+ prepareTransferReason = 'Token is in the vault. Withdraw it first in Custody Mode.'
31
+ }
32
+
33
+ const custodyAsterisk = r.custody === 'mid-flow-uri-pending' || r.ensRecords === 'drift' || r.vault === 'missing'
34
+ let custodyHint: string | undefined
35
+ if (isOperator) {
36
+ custodyHint = undefined
37
+ } else if (r.custody === 'mid-flow-uri-pending') {
38
+ custodyHint = 'Advanced setup pending. Open to finish.'
39
+ } else if (r.vault === 'missing') {
40
+ custodyHint = 'Vault contract not found. Open to redeploy.'
41
+ } else if (r.ensRecords === 'drift') {
42
+ custodyHint = 'ENS records out of sync. Open to fix.'
43
+ }
44
+
45
+ const custodyModeReason = isOperator
46
+ ? 'Operators cannot change custody, withdraw the token, or manage operators.'
47
+ : undefined
48
+ const ensNameReason = isOperator
49
+ ? 'Operators cannot change the ENS subdomain or its records.'
50
+ : undefined
51
+
52
+ return {
53
+ prepareTransferDisabled: unlinked || inVault || isOperator,
54
+ ...(prepareTransferReason ? { prepareTransferReason } : {}),
55
+ custodyModeDisabled: unlinked || isOperator,
56
+ ...(custodyModeReason ? { custodyModeReason } : {}),
57
+ ensNameDisabled: unlinked || isOperator,
58
+ ...(ensNameReason ? { ensNameReason } : {}),
59
+ saveSnapshotDisabled: unlinked,
60
+ refetchLatestDisabled: unlinked,
61
+ ...(unlinked ? { tokenValuesUnlinkedNote: 'Token no longer linked to this wallet, values retained for reference' } : {}),
62
+
63
+ custodyAsterisk: custodyAsterisk && !isOperator,
64
+ ...(custodyHint ? { custodyHint } : {}),
65
+ saveSnapshotAsterisk: r.agentUri === 'local-newer',
66
+ ...(r.agentUri === 'local-newer' ? { saveSnapshotHint: 'Local state newer than chain. Save to publish the latest agentURI.' } : {}),
67
+ }
68
+ }
@@ -0,0 +1,310 @@
1
+ import type { Address, Hex } from 'viem'
2
+ import type { EthagentConfig, EthagentIdentity, SelectableNetwork } from '../../../storage/config.js'
3
+ import { saveConfig } from '../../../storage/config.js'
4
+ import {
5
+ createContinuitySnapshotChallenge,
6
+ createContinuitySnapshotEnvelope,
7
+ serializeContinuitySnapshotEnvelope,
8
+ } from '../../continuity/envelope.js'
9
+ import {
10
+ continuityAgentSnapshot,
11
+ defaultContinuityFiles,
12
+ writeIdentityMarkdownScaffold,
13
+ } from '../../continuity/storage.js'
14
+ import {
15
+ createAgentCard,
16
+ defaultPublicSkillsProfile,
17
+ renderPublicSkillsJson,
18
+ serializeAgentCard,
19
+ } from '../../continuity/publicSkills.js'
20
+ import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
21
+ import { addToIpfs, DEFAULT_IPFS_API_URL, isPinataUploadUrl } from '../../storage/ipfs.js'
22
+ import {
23
+ chainIdForNetwork,
24
+ createErc8004PublicClient,
25
+ encodeRegisterAgent,
26
+ erc8004ConfigForSupportedChain,
27
+ normalizeErc8004RegistryConfig,
28
+ preflightRegisterAgent,
29
+ registeredAgentFromReceipt,
30
+ withEthagentPointers,
31
+ type Erc8004RegistryConfig,
32
+ } from '../../registry/erc8004.js'
33
+ import { registryConfigFromConfig, type RegistryResolution } from '../../registry/registryConfig.js'
34
+ import { resolveValidatedPinataJwt, savePinataJwt } from '../../storage/pinataJwt.js'
35
+ import { setOwnerAddressField } from '../../identityCompat.js'
36
+ import {
37
+ requestBrowserWalletSignatureAndTransaction,
38
+ } from '../../wallet/browserWallet.js'
39
+ import { initialAgentState, PREFLIGHT_AGENT_URI } from '../model/identity.js'
40
+ import type { Step } from '../identityHubReducer.js'
41
+ import type { EffectCallbacks } from './types.js'
42
+ import { awaitConfirmedReceipt } from './receipts.js'
43
+ import { assertVerifiedPin } from './shared/profilePrep.js'
44
+
45
+ type BackupMetadata = NonNullable<EthagentIdentity['backup']>
46
+ type PublicSkillsMetadata = NonNullable<EthagentIdentity['publicSkills']>
47
+
48
+ type CreatePreparedTransaction = {
49
+ ownerAddress: Address
50
+ agentUri: string
51
+ metadataCid: string
52
+ backup: BackupMetadata
53
+ publicSkills: PublicSkillsMetadata
54
+ state: Record<string, unknown>
55
+ continuityFiles: ReturnType<typeof defaultContinuityFiles>
56
+ publicSkillsJson: string
57
+ }
58
+
59
+ export async function runCreatePreflight(
60
+ step: Extract<Step, { kind: 'create-preflight' }>,
61
+ config: EthagentConfig | undefined,
62
+ callbacks: EffectCallbacks,
63
+ ): Promise<void> {
64
+ const resolution = step.network
65
+ ? registryResolutionForNetwork(step.network)
66
+ : registryConfigFromConfig(config)
67
+ if (!resolution.config) {
68
+ callbacks.onStep({ kind: 'create-registry', name: step.name, description: step.description, resolution, custodyMode: step.custodyMode })
69
+ return
70
+ }
71
+ const apiUrl = DEFAULT_IPFS_API_URL
72
+ let jwt: string | undefined
73
+ try {
74
+ jwt = isPinataUploadUrl(apiUrl) ? await resolveValidatedPinataJwt() : undefined
75
+ } catch (err: unknown) {
76
+ callbacks.onStep({
77
+ kind: 'create-storage',
78
+ name: step.name,
79
+ description: step.description,
80
+ registry: resolution.config,
81
+ custodyMode: step.custodyMode,
82
+ error: (err as Error).message,
83
+ })
84
+ return
85
+ }
86
+ if (isPinataUploadUrl(apiUrl) && !jwt) {
87
+ callbacks.onStep({ kind: 'create-storage', name: step.name, description: step.description, registry: resolution.config, custodyMode: step.custodyMode })
88
+ return
89
+ }
90
+ callbacks.onStep({ kind: 'create-signing', name: step.name, description: step.description, registry: resolution.config, custodyMode: step.custodyMode, pinataJwt: jwt })
91
+ }
92
+
93
+ function registryResolutionForNetwork(network: SelectableNetwork): RegistryResolution {
94
+ const chainId = chainIdForNetwork(network)
95
+ try {
96
+ const registry = erc8004ConfigForSupportedChain(chainId)
97
+ return {
98
+ config: registry,
99
+ network,
100
+ chainId,
101
+ needsRegistryAddress: false,
102
+ defaultRpcUrl: registry.rpcUrl,
103
+ }
104
+ } catch {
105
+ return {
106
+ config: null,
107
+ network,
108
+ chainId,
109
+ needsRegistryAddress: true,
110
+ defaultRpcUrl: '',
111
+ }
112
+ }
113
+ }
114
+
115
+ export async function runCreateSigning(
116
+ step: Extract<Step, { kind: 'create-signing' }>,
117
+ callbacks: EffectCallbacks,
118
+ ): Promise<void> {
119
+ const result = await requestBrowserWalletSignatureAndTransaction<CreatePreparedTransaction>({
120
+ chainId: step.registry.chainId,
121
+ messageForAccount: account => createContinuitySnapshotChallenge(account),
122
+ onReady: callbacks.onWalletReady,
123
+ purpose: 'create-agent',
124
+ prepareTransaction: async wallet => {
125
+ await preflightRegisterAgent({
126
+ ...step.registry,
127
+ ownerAddress: wallet.account,
128
+ agentURI: PREFLIGHT_AGENT_URI,
129
+ })
130
+ const state = initialAgentState(step.name, step.description, wallet.account)
131
+ state.custodyMode = step.custodyMode
132
+ if (step.custodyMode === 'advanced') {
133
+ setOwnerAddressField(state, wallet.account)
134
+ }
135
+ const draftIdentity = identityDraftForBackup({
136
+ ownerAddress: wallet.account,
137
+ registry: step.registry,
138
+ state,
139
+ })
140
+ const continuityFiles = defaultContinuityFiles(draftIdentity)
141
+ const publicProfile = defaultPublicSkillsProfile(draftIdentity)
142
+ const publicSkillsJson = renderPublicSkillsJson(publicProfile)
143
+ const publicSkillsPin = await addToIpfs(DEFAULT_IPFS_API_URL, publicSkillsJson, fetch, { pinataJwt: step.pinataJwt })
144
+ assertVerifiedPin(publicSkillsPin)
145
+ const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeAgentCard(createAgentCard(publicProfile)), fetch, { pinataJwt: step.pinataJwt })
146
+ assertVerifiedPin(agentCardPin)
147
+ const envelope = createContinuitySnapshotEnvelope({
148
+ ownerAddress: wallet.account,
149
+ walletSignature: wallet.signature,
150
+ payload: {
151
+ agent: continuityAgentSnapshot(draftIdentity),
152
+ files: continuityFiles,
153
+ transcript: [],
154
+ state,
155
+ },
156
+ })
157
+ const statePin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeContinuitySnapshotEnvelope(envelope), fetch, { pinataJwt: step.pinataJwt })
158
+ assertVerifiedPin(statePin)
159
+ const cid = statePin.cid
160
+ const backup: BackupMetadata = {
161
+ cid,
162
+ createdAt: envelope.createdAt,
163
+ envelopeVersion: envelope.envelopeVersion,
164
+ ipfsApiUrl: DEFAULT_IPFS_API_URL,
165
+ status: 'pinned',
166
+ ownerAddress: wallet.account,
167
+ chainId: step.registry.chainId,
168
+ rpcUrl: step.registry.rpcUrl,
169
+ identityRegistryAddress: step.registry.identityRegistryAddress,
170
+ }
171
+ const publicSkills: PublicSkillsMetadata = {
172
+ cid: publicSkillsPin.cid,
173
+ agentCardCid: agentCardPin.cid,
174
+ updatedAt: envelope.createdAt,
175
+ status: 'pinned',
176
+ }
177
+ const registration = withEthagentPointers({
178
+ type: 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1',
179
+ name: step.name,
180
+ ...(step.description ? { description: step.description } : {}),
181
+ ...(typeof state.imageUrl === 'string' ? { image: state.imageUrl } : {}),
182
+ }, {
183
+ backup: { cid, envelopeVersion: envelope.envelopeVersion, createdAt: envelope.createdAt },
184
+ publicDiscovery: { skillsCid: publicSkills.cid, agentCardCid: publicSkills.agentCardCid, updatedAt: publicSkills.updatedAt },
185
+ registration: { chainId: step.registry.chainId, identityRegistryAddress: step.registry.identityRegistryAddress },
186
+ ownerAddress: wallet.account,
187
+ })
188
+ const metadataPin = await addToIpfs(DEFAULT_IPFS_API_URL, JSON.stringify(registration, null, 2), fetch, { pinataJwt: step.pinataJwt })
189
+ assertVerifiedPin(metadataPin)
190
+ const metadataCid = metadataPin.cid
191
+ const agentUri = `ipfs://${metadataCid}`
192
+ return {
193
+ to: step.registry.identityRegistryAddress,
194
+ data: encodeRegisterAgent({ agentURI: agentUri }),
195
+ prepared: {
196
+ ownerAddress: wallet.account,
197
+ agentUri,
198
+ metadataCid,
199
+ backup: { ...backup, metadataCid, agentUri },
200
+ publicSkills,
201
+ state,
202
+ continuityFiles,
203
+ publicSkillsJson,
204
+ },
205
+ }
206
+ },
207
+ })
208
+ const client = createErc8004PublicClient(step.registry)
209
+ const receipt = await awaitConfirmedReceipt(client, result.txHash, 'Agent registration', { kind: 'register', chainId: step.registry.chainId })
210
+ const registered = registeredAgentFromReceipt({
211
+ logs: receipt.logs.map(log => ({ address: log.address, topics: [...log.topics] as Hex[], data: log.data })),
212
+ identityRegistryAddress: step.registry.identityRegistryAddress,
213
+ ownerAddress: result.prepared.ownerAddress,
214
+ fallbackAgentURI: result.prepared.agentUri,
215
+ })
216
+ const backup: BackupMetadata = {
217
+ ...result.prepared.backup,
218
+ agentId: registered.agentId.toString(),
219
+ agentUri: registered.agentURI,
220
+ txHash: result.txHash,
221
+ }
222
+ const nextIdentity: EthagentIdentity = {
223
+ source: 'erc8004',
224
+ address: result.prepared.ownerAddress,
225
+ ownerAddress: result.prepared.ownerAddress,
226
+ connectedWallet: result.prepared.ownerAddress,
227
+ createdAt: result.prepared.backup.createdAt,
228
+ chainId: step.registry.chainId,
229
+ rpcUrl: step.registry.rpcUrl,
230
+ identityRegistryAddress: step.registry.identityRegistryAddress,
231
+ agentId: registered.agentId.toString(),
232
+ agentUri: registered.agentURI,
233
+ metadataCid: result.prepared.metadataCid,
234
+ state: result.prepared.state,
235
+ backup,
236
+ publicSkills: result.prepared.publicSkills,
237
+ }
238
+ await writeIdentityMarkdownScaffold(nextIdentity, {
239
+ ...defaultContinuityFiles(nextIdentity),
240
+ 'skills.json': result.prepared.publicSkillsJson,
241
+ })
242
+ await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'initial published snapshot' }).catch(() => null)
243
+ await callbacks.onIdentityComplete(nextIdentity, `ERC-8004 agent registered · #${registered.agentId.toString()}`, 'create')
244
+ }
245
+
246
+ export async function runRegistrySubmit(
247
+ value: string,
248
+ step: Extract<Step, { kind: 'create-registry' }>,
249
+ config: EthagentConfig | undefined,
250
+ onConfigChange: ((config: EthagentConfig) => void) | undefined,
251
+ callbacks: EffectCallbacks,
252
+ ): Promise<void> {
253
+ const registry = normalizeErc8004RegistryConfig({
254
+ chainId: step.resolution.chainId,
255
+ rpcUrl: step.resolution.defaultRpcUrl,
256
+ identityRegistryAddress: value.trim(),
257
+ })
258
+ if (config && onConfigChange) {
259
+ const next: EthagentConfig = {
260
+ ...config,
261
+ erc8004: {
262
+ chainId: registry.chainId,
263
+ rpcUrl: registry.rpcUrl,
264
+ identityRegistryAddress: registry.identityRegistryAddress,
265
+ },
266
+ }
267
+ await saveConfig(next)
268
+ onConfigChange(next)
269
+ }
270
+ const apiUrl = DEFAULT_IPFS_API_URL
271
+ let jwt: string | undefined
272
+ try {
273
+ jwt = isPinataUploadUrl(apiUrl) ? await resolveValidatedPinataJwt() : undefined
274
+ } catch (err: unknown) {
275
+ callbacks.onStep({ kind: 'create-storage', name: step.name, description: step.description, registry, custodyMode: step.custodyMode, error: (err as Error).message })
276
+ return
277
+ }
278
+ if (isPinataUploadUrl(apiUrl) && !jwt) {
279
+ callbacks.onStep({ kind: 'create-storage', name: step.name, description: step.description, registry, custodyMode: step.custodyMode })
280
+ return
281
+ }
282
+ callbacks.onStep({ kind: 'create-signing', name: step.name, description: step.description, registry, custodyMode: step.custodyMode, pinataJwt: jwt })
283
+ }
284
+
285
+ export async function runStorageSubmit(
286
+ input: string,
287
+ step: Extract<Step, { kind: 'create-storage' }>,
288
+ callbacks: EffectCallbacks,
289
+ ): Promise<void> {
290
+ const { jwt: pinataJwt } = await savePinataJwt(input)
291
+ callbacks.onStep({ kind: 'create-signing', name: step.name, description: step.description, registry: step.registry, custodyMode: step.custodyMode, pinataJwt })
292
+ }
293
+
294
+ function identityDraftForBackup(args: {
295
+ ownerAddress: Address
296
+ registry: Erc8004RegistryConfig
297
+ state: Record<string, unknown>
298
+ }): EthagentIdentity {
299
+ return {
300
+ source: 'erc8004',
301
+ address: args.ownerAddress,
302
+ ownerAddress: args.ownerAddress,
303
+ createdAt: typeof args.state.createdAt === 'string' ? args.state.createdAt : new Date().toISOString(),
304
+ chainId: args.registry.chainId,
305
+ rpcUrl: args.registry.rpcUrl,
306
+ identityRegistryAddress: args.registry.identityRegistryAddress,
307
+ agentUri: PREFLIGHT_AGENT_URI,
308
+ state: args.state,
309
+ }
310
+ }
@@ -0,0 +1,218 @@
1
+ import type { Address, PublicClient } from 'viem'
2
+ import type { EthagentIdentity } from '../../../../storage/config.js'
3
+ import type { AgentEnsRecordState, AgentEnsRecords } from '../../../ens/agentRecords.js'
4
+ import type { Erc8004RegistryConfig } from '../../../registry/erc8004.js'
5
+ import {
6
+ openBrowserWalletSession,
7
+ type WalletPurpose,
8
+ } from '../../../wallet/browserWallet.js'
9
+ import type { ProfileUpdates, Step } from '../../identityHubReducer.js'
10
+ import type { EffectCallbacks, EnsClearProgress, EnsLinkProgress, EnsUpdateProgress } from '../types.js'
11
+ import {
12
+ runEnsSetupRecordsTransaction,
13
+ runEnsSetupRegistryTransaction,
14
+ runUpdateEnsRecords,
15
+ } from './transactions.js'
16
+ import { runRebackupSigningInSession } from '../rebackup/runRebackup.js'
17
+
18
+ type EnsRecordsFlowArgs = {
19
+ identity: EthagentIdentity
20
+ registry: Erc8004RegistryConfig
21
+ fullName: string
22
+ ownerAddress: Address
23
+ records: AgentEnsRecords
24
+ currentRecords?: AgentEnsRecordState
25
+ pinataJwt?: string
26
+ publicClient?: PublicClient
27
+ }
28
+
29
+ async function runEnsRecordsThenSnapshot(args: {
30
+ flowId: 'ens-clear' | 'ens-update' | 'ens-link'
31
+ flow: EnsRecordsFlowArgs
32
+ clearRecords: boolean
33
+ recordsPurpose: WalletPurpose
34
+ callbacks: EffectCallbacks
35
+ reportProgress: (phase: 'records-tx' | 'records-confirming' | 'snapshot-sign' | 'snapshot-tx' | 'snapshot-confirming') => void
36
+ preRecordsSteps?: number
37
+ totalSteps: number
38
+ }): Promise<void> {
39
+ const { flow } = args
40
+ const session = await openBrowserWalletSession({ onReady: args.callbacks.onWalletReady })
41
+ try {
42
+ args.reportProgress('records-tx')
43
+ const recordsStepIndex = (args.preRecordsSteps ?? 0) + 1
44
+ await runUpdateEnsRecords({
45
+ fullName: flow.fullName,
46
+ ownerAddress: flow.ownerAddress,
47
+ records: flow.records,
48
+ ...(flow.currentRecords ? { currentRecords: flow.currentRecords } : {}),
49
+ callbacks: args.callbacks,
50
+ purpose: args.recordsPurpose,
51
+ clearRecords: args.clearRecords,
52
+ ...(flow.publicClient ? { publicClient: flow.publicClient } : {}),
53
+ tokenChainId: flow.registry.chainId,
54
+ session,
55
+ flowId: args.flowId,
56
+ flowStep: recordsStepIndex,
57
+ })
58
+ args.reportProgress('records-confirming')
59
+ args.reportProgress('snapshot-sign')
60
+ const snapshotStepIndex = recordsStepIndex + 1
61
+ const nextEnsName = args.clearRecords ? '' : flow.fullName
62
+ const profileUpdates: ProfileUpdates = { ensName: nextEnsName }
63
+ const rebackupStep: Extract<Step, { kind: 'rebackup-signing' }> = {
64
+ kind: 'rebackup-signing',
65
+ identity: flow.identity,
66
+ registry: flow.registry,
67
+ profileUpdates,
68
+ ...(flow.pinataJwt ? { pinataJwt: flow.pinataJwt } : {}),
69
+ }
70
+ args.reportProgress('snapshot-tx')
71
+ await runRebackupSigningInSession(rebackupStep, args.callbacks, session, {
72
+ flowId: args.flowId,
73
+ flowStep: snapshotStepIndex,
74
+ })
75
+ args.reportProgress('snapshot-confirming')
76
+ } finally {
77
+ await session.close().catch(() => null)
78
+ args.callbacks.onWalletReady(null)
79
+ }
80
+ }
81
+
82
+ export async function runEnsUnlinkFlow(
83
+ step: Extract<Step, { kind: 'ens-clear-flow-running' }>,
84
+ callbacks: EffectCallbacks,
85
+ ): Promise<void> {
86
+ await runEnsRecordsThenSnapshot({
87
+ flowId: 'ens-clear',
88
+ clearRecords: true,
89
+ recordsPurpose: 'clear-ens-records',
90
+ flow: {
91
+ identity: step.identity,
92
+ registry: step.registry,
93
+ fullName: step.fullName,
94
+ ownerAddress: step.ownerAddress,
95
+ records: step.records,
96
+ ...(step.currentRecords ? { currentRecords: step.currentRecords } : {}),
97
+ ...(step.pinataJwt ? { pinataJwt: step.pinataJwt } : {}),
98
+ },
99
+ callbacks,
100
+ reportProgress: phase => callbacks.onEnsClearProgress?.({ phase, label: ensClearLabelFor(phase) }),
101
+ totalSteps: 2,
102
+ })
103
+ callbacks.onEnsClearProgress?.(null)
104
+ }
105
+
106
+ export async function runEnsUpdateFlow(
107
+ step: Extract<Step, { kind: 'ens-update-flow-running' }>,
108
+ callbacks: EffectCallbacks,
109
+ ): Promise<void> {
110
+ await runEnsRecordsThenSnapshot({
111
+ flowId: 'ens-update',
112
+ clearRecords: false,
113
+ recordsPurpose: 'update-ens-records',
114
+ flow: {
115
+ identity: step.identity,
116
+ registry: step.registry,
117
+ fullName: step.fullName,
118
+ ownerAddress: step.ownerAddress,
119
+ records: step.records,
120
+ ...(step.currentRecords ? { currentRecords: step.currentRecords } : {}),
121
+ ...(step.pinataJwt ? { pinataJwt: step.pinataJwt } : {}),
122
+ },
123
+ callbacks,
124
+ reportProgress: phase => callbacks.onEnsUpdateProgress?.({ phase, label: ensUpdateLabelFor(phase) }),
125
+ totalSteps: 2,
126
+ })
127
+ callbacks.onEnsUpdateProgress?.(null)
128
+ }
129
+
130
+ function ensClearLabelFor(phase: EnsClearProgress['phase']): string {
131
+ switch (phase) {
132
+ case 'records-tx': return 'Clearing ENS records on Ethereum Mainnet...'
133
+ case 'records-confirming': return 'Waiting for ENS clear confirmation...'
134
+ case 'snapshot-sign': return 'Sign to save the cleared snapshot...'
135
+ case 'snapshot-tx': return 'Publishing cleared snapshot to the agent token URI...'
136
+ case 'snapshot-confirming': return 'Waiting for snapshot publish confirmation...'
137
+ }
138
+ }
139
+
140
+ function ensUpdateLabelFor(phase: EnsUpdateProgress['phase']): string {
141
+ switch (phase) {
142
+ case 'records-tx': return 'Updating ENS records on Ethereum Mainnet...'
143
+ case 'records-confirming': return 'Waiting for ENS records confirmation...'
144
+ case 'snapshot-sign': return 'Sign to save the updated snapshot...'
145
+ case 'snapshot-tx': return 'Publishing updated snapshot to the agent token URI...'
146
+ case 'snapshot-confirming': return 'Waiting for snapshot publish confirmation...'
147
+ }
148
+ }
149
+
150
+ function ensLinkLabelFor(phase: EnsLinkProgress['phase']): string {
151
+ switch (phase) {
152
+ case 'registry-tx': return 'Creating ENS subdomain on Ethereum Mainnet...'
153
+ case 'registry-confirming': return 'Waiting for subdomain creation confirmation...'
154
+ case 'records-tx': return 'Setting ENS records on Ethereum Mainnet...'
155
+ case 'records-confirming': return 'Waiting for ENS records confirmation...'
156
+ case 'snapshot-sign': return 'Sign to save the linked snapshot...'
157
+ case 'snapshot-tx': return 'Publishing linked snapshot to the agent token URI...'
158
+ case 'snapshot-confirming': return 'Waiting for snapshot publish confirmation...'
159
+ }
160
+ }
161
+
162
+ export async function runEnsLinkFlow(
163
+ step: Extract<Step, { kind: 'ens-link-flow-running' }>,
164
+ callbacks: EffectCallbacks,
165
+ ): Promise<void> {
166
+ const session = await openBrowserWalletSession({ onReady: callbacks.onWalletReady })
167
+ const report = (phase: EnsLinkProgress['phase']): void => {
168
+ callbacks.onEnsLinkProgress?.({ phase, label: ensLinkLabelFor(phase) })
169
+ }
170
+ try {
171
+ let stepIndex = 0
172
+ const willRunRegistry = step.setup.registryAction !== 'none'
173
+ if (willRunRegistry) {
174
+ stepIndex += 1
175
+ report('registry-tx')
176
+ await runEnsSetupRegistryTransaction({
177
+ setup: step.setup,
178
+ callbacks,
179
+ tokenChainId: step.registry.chainId,
180
+ session,
181
+ flowId: 'ens-link',
182
+ flowStep: stepIndex,
183
+ })
184
+ report('registry-confirming')
185
+ }
186
+ stepIndex += 1
187
+ report('records-tx')
188
+ await runEnsSetupRecordsTransaction({
189
+ setup: step.setup,
190
+ callbacks,
191
+ tokenChainId: step.registry.chainId,
192
+ session,
193
+ flowId: 'ens-link',
194
+ flowStep: stepIndex,
195
+ })
196
+ report('records-confirming')
197
+ stepIndex += 1
198
+ report('snapshot-sign')
199
+ const profileUpdates: ProfileUpdates = { ensName: step.setup.fullName }
200
+ const rebackupStep: Extract<Step, { kind: 'rebackup-signing' }> = {
201
+ kind: 'rebackup-signing',
202
+ identity: step.identity,
203
+ registry: step.registry,
204
+ profileUpdates,
205
+ ...(step.pinataJwt ? { pinataJwt: step.pinataJwt } : {}),
206
+ }
207
+ report('snapshot-tx')
208
+ await runRebackupSigningInSession(rebackupStep, callbacks, session, {
209
+ flowId: 'ens-link',
210
+ flowStep: stepIndex,
211
+ })
212
+ report('snapshot-confirming')
213
+ } finally {
214
+ await session.close().catch(() => null)
215
+ callbacks.onWalletReady(null)
216
+ callbacks.onEnsLinkProgress?.(null)
217
+ }
218
+ }
@@ -0,0 +1,11 @@
1
+ export {
2
+ runEnsLinkFlow,
3
+ runEnsUnlinkFlow,
4
+ runEnsUpdateFlow,
5
+ } from './flows.js'
6
+ export {
7
+ ensRecordWritesForUpdate,
8
+ runEnsSetupRecordsTransaction,
9
+ runEnsSetupRegistryTransaction,
10
+ runUpdateEnsRecords,
11
+ } from './transactions.js'