ethagent 1.1.2 → 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 (268) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +126 -30
  3. package/package.json +7 -2
  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
@@ -0,0 +1,396 @@
1
+ import { useEffect, useReducer, useState } from 'react'
2
+ import type { EthagentConfig, EthagentIdentity } from '../../storage/config.js'
3
+ import { defaultModelFor } from '../../storage/config.js'
4
+ import { setTokenIdentity } from '../../storage/identity.js'
5
+ import type { BrowserWalletReady } from '../wallet/browserWallet.js'
6
+ import { registryConfigFromConfig } from '../registry/registryConfig.js'
7
+ import type { Erc8004RegistryConfig } from '../registry/erc8004.js'
8
+ import {
9
+ hasPinataJwt,
10
+ } from '../storage/pinataJwt.js'
11
+ import {
12
+ assertTokenNotInVault,
13
+ TokenInVaultError,
14
+ } from './effects/vault/preflight.js'
15
+ import {
16
+ runPublicProfilePreflight,
17
+ } from './effects/publicProfile/runPublicProfileSave.js'
18
+ import {
19
+ runRebackupPreflight,
20
+ } from './effects/rebackup/runRebackup.js'
21
+ import type {
22
+ EffectCallbacks,
23
+ IdentityCompletionSource,
24
+ RestoreProgress,
25
+ TokenTransferProgress,
26
+ } from './effects/types.js'
27
+ import { resolveOperatorVaultAddress } from './flows/custody/custodyEffects.js'
28
+ import { useCustodyFlow } from './flows/custody/useCustodyFlow.js'
29
+ import { identityHubErrorView } from './model/errors.js'
30
+ import { readCustodyMode } from './model/custody.js'
31
+ import { selectEnsStatus } from './model/ens.js'
32
+ import { identityHubReducer, type ProfileUpdates, type Step } from './identityHubReducer.js'
33
+ import type { IdentityHubProps } from './types.js'
34
+ import {
35
+ capitalizeFeedbackMessage,
36
+ initialStepForAction,
37
+ isWalletCancelled,
38
+ } from './utils.js'
39
+ import {
40
+ preflightTokenOwnership,
41
+ useAgentReconciliation,
42
+ } from './reconciliation/index.js'
43
+ import { useIdentityHubContinuity } from './useIdentityHubContinuity.js'
44
+ import { useIdentityHubSideEffects } from './useIdentityHubSideEffects.js'
45
+
46
+ export function useIdentityHubController({
47
+ mode,
48
+ config,
49
+ initialAction,
50
+ onComplete,
51
+ onConfigChange,
52
+ }: IdentityHubProps) {
53
+ const [firstRunIdentity, setFirstRunIdentity] = useState<EthagentIdentity | null>(null)
54
+ const identity = config?.identity ?? firstRunIdentity ?? undefined
55
+ const { reconciliation, refresh: refreshReconciliation } = useAgentReconciliation(config ?? null)
56
+ const [step, dispatch] = useReducer(identityHubReducer, initialStepForAction(initialAction, config))
57
+ const [walletSession, setWalletSession] = useState<BrowserWalletReady | null>(null)
58
+ const [restoreProgress, setRestoreProgress] = useState<RestoreProgress | null>(null)
59
+ const [tokenTransferProgress, setTokenTransferProgress] = useState<TokenTransferProgress | null>(null)
60
+ const [jwtSaved, setJwtSaved] = useState<boolean>(false)
61
+ const [copyNotice, setCopyNotice] = useState<string | null>(null)
62
+ const canRebackup = Boolean(identity?.agentId && (identity?.identityRegistryAddress || config?.erc8004?.identityRegistryAddress))
63
+
64
+ const setStep = (s: Step) => dispatch({ type: 'setStep', step: s })
65
+ const back = () => dispatch({ type: 'back', from: step })
66
+ const closeHub = () => onComplete(mode === 'first-run' ? { kind: 'skip' } : { kind: 'cancel' })
67
+
68
+ useEffect(() => { setWalletSession(null) }, [step.kind])
69
+ useEffect(() => {
70
+ if (step.kind !== 'restore-authorizing') setRestoreProgress(null)
71
+ }, [step.kind])
72
+ useEffect(() => {
73
+ if (step.kind !== 'token-transfer-signing') setTokenTransferProgress(null)
74
+ }, [step.kind])
75
+
76
+ useEffect(() => {
77
+ let cancelled = false
78
+ hasPinataJwt().then(v => { if (!cancelled) setJwtSaved(v) }).catch(() => {})
79
+ return () => { cancelled = true }
80
+ }, [step.kind])
81
+
82
+ useEffect(() => { setCopyNotice(null) }, [step.kind])
83
+ useEffect(() => {
84
+ if (!copyNotice) return
85
+ const timer = setTimeout(() => setCopyNotice(null), 2500)
86
+ return () => clearTimeout(timer)
87
+ }, [copyNotice])
88
+
89
+ const registryFromIdentity = (nextIdentity: EthagentIdentity): Erc8004RegistryConfig | null => {
90
+ if (!nextIdentity.chainId || !nextIdentity.identityRegistryAddress) return null
91
+ return {
92
+ chainId: nextIdentity.chainId,
93
+ rpcUrl: nextIdentity.rpcUrl ?? '',
94
+ identityRegistryAddress: nextIdentity.identityRegistryAddress as `0x${string}`,
95
+ }
96
+ }
97
+
98
+ const resolveRegistryForIdentity = (target: EthagentIdentity): Erc8004RegistryConfig | null => {
99
+ const resolution = registryConfigFromConfig(config)
100
+ if (target.chainId && target.identityRegistryAddress) {
101
+ return {
102
+ chainId: target.chainId,
103
+ rpcUrl: target.rpcUrl ?? resolution.defaultRpcUrl,
104
+ identityRegistryAddress: target.identityRegistryAddress as `0x${string}`,
105
+ }
106
+ }
107
+ if (resolution.config) return resolution.config
108
+ return null
109
+ }
110
+
111
+ const completeTokenIdentity = async (nextIdentity: EthagentIdentity, message: string, source?: IdentityCompletionSource): Promise<void> => {
112
+ if (mode === 'first-run' || !config) {
113
+ setFirstRunIdentity(nextIdentity)
114
+ const registry = registryFromIdentity(nextIdentity)
115
+ const custodyMode = readCustodyMode(nextIdentity.state)
116
+
117
+ if (source === 'create' && custodyMode === 'advanced' && registry) {
118
+ const seedConfig: EthagentConfig = {
119
+ version: 1,
120
+ provider: 'llamacpp',
121
+ model: defaultModelFor('llamacpp'),
122
+ firstRunAt: new Date().toISOString(),
123
+ identity: { ...nextIdentity, source: 'erc8004' },
124
+ erc8004: {
125
+ chainId: registry.chainId,
126
+ rpcUrl: registry.rpcUrl,
127
+ identityRegistryAddress: registry.identityRegistryAddress,
128
+ },
129
+ }
130
+ const persisted = await setTokenIdentity(seedConfig, nextIdentity)
131
+ onConfigChange?.(persisted)
132
+ const ownerAddress = (nextIdentity.ownerAddress ?? nextIdentity.address) as `0x${string}`
133
+ const profileUpdates: ProfileUpdates = {
134
+ custodyMode: 'advanced',
135
+ ownerAddress: ownerAddress,
136
+ bumpRestoreAccessEpoch: true,
137
+ custodyPhase: 'switch-advanced',
138
+ }
139
+ setStep({
140
+ kind: 'custody-vault-deploy-tx',
141
+ identity: nextIdentity,
142
+ registry,
143
+ profileUpdates,
144
+ returnTo: { kind: 'menu' },
145
+ })
146
+ return
147
+ }
148
+
149
+ const ensStatus = selectEnsStatus(nextIdentity)
150
+ if (registry && ensStatus.kind === 'none') {
151
+ setStep({ kind: 'first-run-ens-prompt', identity: nextIdentity, registry })
152
+ return
153
+ }
154
+ onComplete({ kind: 'token', identity: nextIdentity })
155
+ return
156
+ }
157
+ const nextConfig = await setTokenIdentity(config, nextIdentity)
158
+ const switchPhase = step.kind === 'rebackup-signing' || step.kind === 'rebackup-storage'
159
+ ? step.profileUpdates?.custodyPhase
160
+ : undefined
161
+ if (switchPhase === 'switch-advanced' || switchPhase === 'switch-simple') {
162
+ onConfigChange?.(nextConfig)
163
+ const registry = resolveRegistryForIdentity(nextIdentity)
164
+ if (registry) {
165
+ const vaultAddress = step.kind === 'rebackup-signing' || step.kind === 'rebackup-storage'
166
+ ? step.vaultAddress
167
+ : undefined
168
+ if (switchPhase === 'switch-advanced') {
169
+ setStep({
170
+ kind: 'custody-advanced-done',
171
+ identity: nextIdentity,
172
+ registry,
173
+ ...(vaultAddress ? { vaultAddress } : {}),
174
+ returnTo: { kind: 'menu' },
175
+ })
176
+ } else {
177
+ setStep({
178
+ kind: 'custody-simple-done',
179
+ identity: nextIdentity,
180
+ registry,
181
+ returnTo: { kind: 'menu' },
182
+ })
183
+ }
184
+ return
185
+ }
186
+ }
187
+ onComplete({ kind: 'updated', config: nextConfig, message: capitalizeFeedbackMessage(message) })
188
+ }
189
+
190
+ const callbacks: EffectCallbacks = {
191
+ onStep: setStep,
192
+ onWalletReady: setWalletSession,
193
+ onIdentityComplete: completeTokenIdentity,
194
+ onRestoreProgress: setRestoreProgress,
195
+ onTokenTransferProgress: setTokenTransferProgress,
196
+ }
197
+
198
+ const handleStepError = (err: unknown, backStep: Step, softCancel: Step = backStep): void => {
199
+ if (isWalletCancelled(err)) {
200
+ setStep(softCancel)
201
+ return
202
+ }
203
+ setStep({ kind: 'error', error: identityHubErrorView(err), back: backStep })
204
+ }
205
+
206
+ const guardOwnership = async (
207
+ target: EthagentIdentity,
208
+ registry: Erc8004RegistryConfig,
209
+ requiredRole: 'token-holder' | 'vault-level-owner',
210
+ backStep: Step,
211
+ ): Promise<boolean> => {
212
+ const result = await preflightTokenOwnership({
213
+ identity: target,
214
+ registry,
215
+ operatorVaults: config?.erc8004?.operatorVaults,
216
+ requiredRole,
217
+ })
218
+ if (result.ok) return true
219
+ if (result.reason === 'lookup-failed') {
220
+ return true
221
+ }
222
+ setStep({
223
+ kind: 'identity-unlinked',
224
+ identity: target,
225
+ registry,
226
+ ...(result.onChainOwner ? { onChainOwner: result.onChainOwner } : {}),
227
+ back: backStep,
228
+ })
229
+ return false
230
+ }
231
+
232
+ const triggerRebackup = (
233
+ backStep: Step,
234
+ profileUpdates?: ProfileUpdates,
235
+ options?: { vaultAddress?: `0x${string}`; useVault?: boolean },
236
+ ): void => {
237
+ if (!identity) return
238
+ const registry = resolveRegistryForIdentity(identity)
239
+ if (!registry) {
240
+ handleStepError(new Error('no agent registry configured for this identity'), backStep)
241
+ return
242
+ }
243
+ const vaultAddress = options?.useVault === false
244
+ ? undefined
245
+ : options?.vaultAddress ?? resolveOperatorVaultAddress(identity, config?.erc8004?.operatorVaults)
246
+ ;(async () => {
247
+ const role: 'token-holder' | 'vault-level-owner' = vaultAddress ? 'vault-level-owner' : 'token-holder'
248
+ const allowed = await guardOwnership(identity, registry, role, backStep)
249
+ if (!allowed) return
250
+ runRebackupPreflight(identity, registry, callbacks, profileUpdates, backStep, undefined, vaultAddress)
251
+ .catch((err: unknown) => handleStepError(err, backStep))
252
+ })()
253
+ }
254
+
255
+ const triggerPublicProfileSave = (backStep: Step, profileUpdates: ProfileUpdates): void => {
256
+ if (!identity) return
257
+ const registry = resolveRegistryForIdentity(identity)
258
+ if (!registry) {
259
+ handleStepError(new Error('no agent registry configured for this identity'), backStep)
260
+ return
261
+ }
262
+ ;(async () => {
263
+ const vaultAddress = resolveOperatorVaultAddress(identity, config?.erc8004?.operatorVaults)
264
+ const allowed = await guardOwnership(identity, registry, 'vault-level-owner', backStep)
265
+ if (!allowed) return
266
+ runPublicProfilePreflight(identity, registry, callbacks, profileUpdates, backStep, vaultAddress)
267
+ .catch((err: unknown) => handleStepError(err, backStep))
268
+ })()
269
+ }
270
+
271
+ const custodyFlow = useCustodyFlow({
272
+ config,
273
+ step,
274
+ setStep,
275
+ callbacks,
276
+ walletSession,
277
+ handleStepError,
278
+ guardOwnership,
279
+ triggerRebackup,
280
+ refreshReconciliation,
281
+ ...(onConfigChange ? { onConfigChange } : {}),
282
+ })
283
+
284
+ const continuity = useIdentityHubContinuity({
285
+ identity,
286
+ step,
287
+ setStep,
288
+ handleStepError,
289
+ })
290
+
291
+ useIdentityHubSideEffects({
292
+ step,
293
+ config,
294
+ callbacks,
295
+ setStep,
296
+ handleStepError,
297
+ triggerRebackup,
298
+ setContinuityReady: continuity.setContinuityReady,
299
+ ...(onConfigChange ? { onConfigChange } : {}),
300
+ })
301
+
302
+ const finishFirstRunIdentity = (): void => {
303
+ if (!firstRunIdentity) {
304
+ onComplete({ kind: 'skip' })
305
+ return
306
+ }
307
+ onComplete({ kind: 'token', identity: firstRunIdentity })
308
+ }
309
+
310
+ const openEnsEdit = (backStep: Step): void => {
311
+ if (!identity) return
312
+ if (!identity.agentId) {
313
+ handleStepError(new Error('Create an agent first to link an ENS name.'), backStep)
314
+ return
315
+ }
316
+ const registry = resolveRegistryForIdentity(identity)
317
+ if (!registry) {
318
+ handleStepError(new Error('no agent registry configured for this identity'), backStep)
319
+ return
320
+ }
321
+ setStep({ kind: 'edit-profile-ens', identity, registry, returnTo: backStep })
322
+ }
323
+
324
+ const openPublicProfileEdit = (backStep: Step): void => {
325
+ if (!identity) return
326
+ const registry = resolveRegistryForIdentity(identity)
327
+ if (!registry) {
328
+ handleStepError(new Error('no agent registry configured for this identity'), backStep)
329
+ return
330
+ }
331
+ setStep({ kind: 'edit-profile-name', identity, registry, returnTo: backStep })
332
+ }
333
+
334
+ const openTokenTransferFlow = async (): Promise<void> => {
335
+ if (!identity) return
336
+ if (!identity.agentId) {
337
+ handleStepError(new Error('Create an agent first to prepare a token transfer.'), { kind: 'menu' })
338
+ return
339
+ }
340
+ const registry = resolveRegistryForIdentity(identity)
341
+ if (!registry) {
342
+ handleStepError(new Error('no agent registry configured for this identity'), { kind: 'menu' })
343
+ return
344
+ }
345
+ setStep({ kind: 'busy', label: 'Checking token custody...' })
346
+ try {
347
+ await assertTokenNotInVault({ identity, registry, operatorVaults: config?.erc8004?.operatorVaults })
348
+ } catch (err: unknown) {
349
+ if (err instanceof TokenInVaultError) {
350
+ setStep({ kind: 'custody-model', identity, registry, returnTo: { kind: 'menu' }, notice: err.message })
351
+ return
352
+ }
353
+ handleStepError(err, { kind: 'menu' })
354
+ return
355
+ }
356
+ setStep({ kind: 'token-transfer-target', identity, registry })
357
+ }
358
+
359
+ return {
360
+ mode,
361
+ config,
362
+ onComplete,
363
+ onConfigChange,
364
+ identity,
365
+ reconciliation,
366
+ step,
367
+ walletSession,
368
+ restoreProgress,
369
+ tokenTransferProgress,
370
+ jwtSaved,
371
+ copyNotice,
372
+ canRebackup,
373
+ callbacks,
374
+ custodyFlow,
375
+ continuityReady: continuity.continuityReady,
376
+ workingStatus: continuity.workingStatus,
377
+ setStep,
378
+ back,
379
+ closeHub,
380
+ setWalletSession,
381
+ setJwtSaved,
382
+ setCopyNotice,
383
+ handleStepError,
384
+ resolveRegistryForIdentity,
385
+ triggerRebackup,
386
+ triggerPublicProfileSave,
387
+ finishFirstRunIdentity,
388
+ openEnsEdit,
389
+ openTokenTransferFlow,
390
+ openPublicProfileEdit,
391
+ openContinuityFile: continuity.openContinuityFile,
392
+ exportLocalBackupZip: continuity.exportLocalBackupZip,
393
+ }
394
+ }
395
+
396
+ export type IdentityHubController = ReturnType<typeof useIdentityHubController>
@@ -0,0 +1,309 @@
1
+ import { useEffect } from 'react'
2
+ import type { EthagentConfig } from '../../storage/config.js'
3
+ import { setTokenIdentity } from '../../storage/identity.js'
4
+ import { isRegistrationPreflightError, pinataErrorText } from './model/errors.js'
5
+ import type { ProfileUpdates, Step } from './identityHubReducer.js'
6
+ import {
7
+ runCreatePreflight,
8
+ runCreateSigning,
9
+ } from './effects/create.js'
10
+ import {
11
+ runRebackupSigning,
12
+ } from './effects/rebackup/runRebackup.js'
13
+ import {
14
+ runPublicProfileSigning,
15
+ } from './effects/publicProfile/runPublicProfileSave.js'
16
+ import {
17
+ runTokenTransferSigning,
18
+ } from './effects/token-transfer/runTokenTransfer.js'
19
+ import {
20
+ runEnsSetupRecordsTransaction,
21
+ runEnsSetupRegistryTransaction,
22
+ runUpdateEnsRecords,
23
+ } from './effects/ens/index.js'
24
+ import {
25
+ runRecoveryRefetch,
26
+ } from './effects/restore/index.js'
27
+ import type { EffectCallbacks } from './effects/types.js'
28
+ import { useRestoreFlowEffects } from './flows/restore/useRestoreFlowEffects.js'
29
+ import {
30
+ isStorageError,
31
+ isWalletCancelled,
32
+ waitForMinimumBusyTime,
33
+ } from './utils.js'
34
+
35
+ type TriggerRebackup = (
36
+ backStep: Step,
37
+ profileUpdates?: ProfileUpdates,
38
+ options?: { vaultAddress?: `0x${string}`; useVault?: boolean },
39
+ ) => void
40
+
41
+ type UseIdentityHubSideEffectsArgs = {
42
+ step: Step
43
+ config: EthagentConfig | undefined
44
+ callbacks: EffectCallbacks
45
+ setStep: (step: Step) => void
46
+ handleStepError: (err: unknown, backStep: Step, softCancel?: Step) => void
47
+ triggerRebackup: TriggerRebackup
48
+ setContinuityReady: (ready: boolean) => void
49
+ onConfigChange?: (config: EthagentConfig) => void
50
+ }
51
+
52
+ export function useIdentityHubSideEffects({
53
+ step,
54
+ config,
55
+ callbacks,
56
+ setStep,
57
+ handleStepError,
58
+ triggerRebackup,
59
+ setContinuityReady,
60
+ onConfigChange,
61
+ }: UseIdentityHubSideEffectsArgs): void {
62
+ useEffect(() => {
63
+ if (step.kind !== 'rebackup-start') return
64
+ triggerRebackup(step.back)
65
+ }, [step])
66
+
67
+ useEffect(() => {
68
+ if (step.kind !== 'create-preflight') return
69
+ let cancelled = false
70
+ const startedAt = Date.now()
71
+ runCreatePreflight(step, config, callbacks)
72
+ .catch(async (err: unknown) => {
73
+ await waitForMinimumBusyTime(startedAt)
74
+ if (!cancelled) handleStepError(err, { kind: 'create-network', name: step.name, description: step.description })
75
+ })
76
+ return () => { cancelled = true }
77
+ }, [step])
78
+
79
+ useEffect(() => {
80
+ if (step.kind !== 'create-signing') return
81
+ let cancelled = false
82
+ const backStep: Step = { kind: 'create-network', name: step.name, description: step.description }
83
+ runCreateSigning(step, callbacks)
84
+ .catch((err: unknown) => {
85
+ if (cancelled) return
86
+ if (isRegistrationPreflightError(err)) {
87
+ handleStepError(err, backStep)
88
+ return
89
+ }
90
+ if (isStorageError(err)) {
91
+ setStep({
92
+ kind: 'create-storage',
93
+ name: step.name,
94
+ description: step.description,
95
+ registry: step.registry,
96
+ custodyMode: step.custodyMode,
97
+ error: pinataErrorText(err),
98
+ pinataJwt: step.pinataJwt,
99
+ })
100
+ return
101
+ }
102
+ handleStepError(err, backStep)
103
+ })
104
+ return () => { cancelled = true }
105
+ }, [step])
106
+
107
+ useRestoreFlowEffects({ step, config, callbacks, handleStepError })
108
+
109
+ useEffect(() => {
110
+ if (step.kind !== 'rebackup-signing') return
111
+ let cancelled = false
112
+ runRebackupSigning(step, callbacks)
113
+ .catch((err: unknown) => {
114
+ if (cancelled) return
115
+ if (isStorageError(err)) {
116
+ setStep({
117
+ kind: 'rebackup-storage',
118
+ identity: step.identity,
119
+ registry: step.registry,
120
+ error: pinataErrorText(err),
121
+ pinataJwt: step.pinataJwt,
122
+ profileUpdates: step.profileUpdates,
123
+ returnTo: step.returnTo,
124
+ walletPurpose: step.walletPurpose,
125
+ })
126
+ return
127
+ }
128
+ handleStepError(err, step.returnTo ?? { kind: 'menu' })
129
+ })
130
+ return () => { cancelled = true }
131
+ }, [step])
132
+
133
+ useEffect(() => {
134
+ if (step.kind !== 'public-profile-signing') return
135
+ let cancelled = false
136
+ runPublicProfileSigning(step, callbacks)
137
+ .catch((err: unknown) => {
138
+ if (cancelled) return
139
+ if (isStorageError(err)) {
140
+ setStep({
141
+ kind: 'public-profile-storage',
142
+ identity: step.identity,
143
+ registry: step.registry,
144
+ error: pinataErrorText(err),
145
+ pinataJwt: step.pinataJwt,
146
+ profileUpdates: step.profileUpdates,
147
+ returnTo: step.returnTo,
148
+ ...(step.vaultAddress ? { vaultAddress: step.vaultAddress } : {}),
149
+ })
150
+ return
151
+ }
152
+ handleStepError(err, step.returnTo ?? { kind: 'continuity-public' })
153
+ })
154
+ return () => { cancelled = true }
155
+ }, [step])
156
+
157
+ useEffect(() => {
158
+ if (step.kind !== 'token-transfer-signing') return
159
+ let cancelled = false
160
+ runTokenTransferSigning(step, callbacks)
161
+ .then(async result => {
162
+ if (cancelled) return
163
+ if (config) {
164
+ const nextConfig = await setTokenIdentity(config, result.identity)
165
+ onConfigChange?.(nextConfig)
166
+ }
167
+ setStep({
168
+ kind: 'token-transfer-ready',
169
+ identity: result.identity,
170
+ registry: step.registry,
171
+ targetHandle: step.targetHandle,
172
+ targetAddress: step.targetAddress,
173
+ snapshotCid: result.snapshotCid,
174
+ txHash: result.txHash,
175
+ returnTo: step.returnTo,
176
+ })
177
+ })
178
+ .catch((err: unknown) => {
179
+ if (cancelled) return
180
+ if (isStorageError(err)) {
181
+ setStep({
182
+ kind: 'token-transfer-storage',
183
+ identity: step.identity,
184
+ registry: step.registry,
185
+ targetHandle: step.targetHandle,
186
+ targetAddress: step.targetAddress,
187
+ error: pinataErrorText(err),
188
+ pinataJwt: step.pinataJwt,
189
+ returnTo: step.returnTo,
190
+ })
191
+ return
192
+ }
193
+ const message = err instanceof Error ? err.message : String(err)
194
+ const targetReturn: Step = {
195
+ kind: 'token-transfer-target',
196
+ identity: step.identity,
197
+ registry: step.registry,
198
+ previousTarget: step.targetHandle,
199
+ ...(step.returnTo ? { returnTo: step.returnTo } : {}),
200
+ ...(isWalletCancelled(err) ? {} : { error: message }),
201
+ }
202
+ if (isWalletCancelled(err)) {
203
+ setStep(targetReturn)
204
+ return
205
+ }
206
+ handleStepError(err, targetReturn)
207
+ })
208
+ return () => { cancelled = true }
209
+ }, [step])
210
+
211
+ useEffect(() => {
212
+ if (step.kind !== 'recovery-refetching') return
213
+ let cancelled = false
214
+ runRecoveryRefetch(step.identity, step.registry, callbacks)
215
+ .then(() => {
216
+ if (!cancelled) setContinuityReady(true)
217
+ })
218
+ .catch((err: unknown) => {
219
+ if (!cancelled) handleStepError(err, { kind: 'menu' })
220
+ })
221
+ return () => { cancelled = true }
222
+ }, [step])
223
+
224
+ useEffect(() => {
225
+ if (step.kind !== 'ens-records-tx') return
226
+ let cancelled = false
227
+ const ownerAddress = (step.ownerAddress ?? step.identity.ownerAddress ?? step.identity.address) as `0x${string}`
228
+ runUpdateEnsRecords({
229
+ fullName: step.fullName,
230
+ ownerAddress,
231
+ records: step.records,
232
+ currentRecords: step.currentRecords,
233
+ callbacks,
234
+ clearRecords: step.clearRecords,
235
+ purpose: step.clearRecords ? 'clear-ens-records' : undefined,
236
+ tokenChainId: step.registry.chainId,
237
+ })
238
+ .then(() => {
239
+ if (cancelled) return
240
+ triggerRebackup(step.returnTo ?? { kind: 'menu' }, step.clearRecords ? { ensName: '' } : { ensName: step.fullName })
241
+ })
242
+ .catch((err: unknown) => {
243
+ if (cancelled) return
244
+ handleStepError(err, { kind: 'edit-profile-ens', identity: step.identity, registry: step.registry, returnTo: step.returnTo })
245
+ })
246
+ return () => { cancelled = true }
247
+ }, [step])
248
+
249
+ useEffect(() => {
250
+ if (step.kind !== 'ens-setup-registry-tx') return
251
+ let cancelled = false
252
+ runEnsSetupRegistryTransaction({ setup: step.setup, callbacks, tokenChainId: step.registry.chainId })
253
+ .then(result => {
254
+ if (cancelled) return
255
+ setStep({
256
+ kind: 'ens-setup-records-tx',
257
+ identity: step.identity,
258
+ registry: step.registry,
259
+ setup: step.setup,
260
+ returnTo: step.returnTo,
261
+ ...(result ? { registryTxHash: result.txHash } : {}),
262
+ })
263
+ })
264
+ .catch((err: unknown) => {
265
+ if (cancelled) return
266
+ handleStepError(err, {
267
+ kind: 'edit-profile-ens',
268
+ identity: step.identity,
269
+ registry: step.registry,
270
+ returnTo: step.returnTo,
271
+ ...(step.setup.mode === 'advanced' ? { initialView: 'advanced' as const } : {}),
272
+ })
273
+ })
274
+ return () => { cancelled = true }
275
+ }, [step])
276
+
277
+ useEffect(() => {
278
+ if (step.kind !== 'ens-setup-records-tx') return
279
+ let cancelled = false
280
+ runEnsSetupRecordsTransaction({ setup: step.setup, callbacks, tokenChainId: step.registry.chainId })
281
+ .then(() => {
282
+ if (cancelled) return
283
+ const ensUpdates = step.setup.mode === 'advanced'
284
+ ? {
285
+ ensName: step.setup.fullName,
286
+ custodyMode: 'advanced' as const,
287
+ ownerAddress: step.setup.ownerAddress,
288
+ approvedOperatorWallets: [step.setup.operatorAddress],
289
+ activeOperatorAddress: step.setup.operatorAddress,
290
+ }
291
+ : {
292
+ ensName: step.setup.fullName,
293
+ custodyMode: 'simple' as const,
294
+ }
295
+ triggerRebackup(step.returnTo ?? { kind: 'menu' }, ensUpdates)
296
+ })
297
+ .catch((err: unknown) => {
298
+ if (cancelled) return
299
+ handleStepError(err, {
300
+ kind: 'edit-profile-ens',
301
+ identity: step.identity,
302
+ registry: step.registry,
303
+ returnTo: step.returnTo,
304
+ ...(step.setup.mode === 'advanced' ? { initialView: 'advanced' as const } : {}),
305
+ })
306
+ })
307
+ return () => { cancelled = true }
308
+ }, [step])
309
+ }