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,220 @@
1
+ import {
2
+ createPublicClient,
3
+ encodeFunctionData,
4
+ fallback,
5
+ getAddress,
6
+ http,
7
+ namehash,
8
+ type Address,
9
+ type Hex,
10
+ type PublicClient,
11
+ } from 'viem'
12
+ import { mainnet } from 'viem/chains'
13
+ import type { EthagentIdentity } from '../../../storage/config.js'
14
+ import type { Erc8004RegistryConfig } from '../../registry/erc8004.js'
15
+ import { ENS_AUTOMATION_RESOLVER_ABI } from '../../ens/ensAutomation.js'
16
+ import { encodeApprove, encodeApprovalRevoke, readDelegation } from '../../ens/resolverDelegation.js'
17
+ import { readResolverAddress } from '../../ens/ensLookup.js'
18
+ import { normalizeApprovedOperatorWallets } from '../operatorWallets.js'
19
+ import { readCustodyMode } from '../model/custody.js'
20
+ import { readOwnerAddressField } from '../../identityCompat.js'
21
+
22
+ export type ApprovalDiff = {
23
+ added: Address[]
24
+ removed: Address[]
25
+ }
26
+
27
+ export function computeApprovalDiff(
28
+ beforeApproved: ReadonlyArray<{ address: string }>,
29
+ afterApproved: ReadonlyArray<{ address: string }>,
30
+ ): ApprovalDiff {
31
+ const before = new Set(beforeApproved.map(record => record.address.toLowerCase()))
32
+ const after = new Set(afterApproved.map(record => record.address.toLowerCase()))
33
+ const added: Address[] = []
34
+ const removed: Address[] = []
35
+ for (const record of afterApproved) {
36
+ if (!before.has(record.address.toLowerCase())) {
37
+ added.push(getAddress(record.address))
38
+ }
39
+ }
40
+ for (const record of beforeApproved) {
41
+ if (!after.has(record.address.toLowerCase())) {
42
+ removed.push(getAddress(record.address))
43
+ }
44
+ }
45
+ return { added, removed }
46
+ }
47
+
48
+ type ResolverApprovalCalls = {
49
+ resolverAddress: Address
50
+ data: Hex
51
+ calls: Hex[]
52
+ added: Address[]
53
+ removed: Address[]
54
+ }
55
+
56
+ export async function encodeResolverApprovalChanges(args: {
57
+ ensName: string
58
+ diff: ApprovalDiff
59
+ }): Promise<ResolverApprovalCalls | null> {
60
+ if (args.diff.added.length === 0 && args.diff.removed.length === 0) return null
61
+ const resolverAddress = await readResolverAddress(args.ensName)
62
+ if (!resolverAddress) {
63
+ throw new Error(`no resolver set on ${args.ensName} - finish ENS subdomain setup first`)
64
+ }
65
+ const node = namehash(args.ensName)
66
+ const calls: Hex[] = []
67
+ for (const address of args.diff.added) calls.push(encodeApprove(node, address))
68
+ for (const address of args.diff.removed) calls.push(encodeApprovalRevoke(node, address))
69
+ const data = calls.length === 1
70
+ ? calls[0]!
71
+ : encodeFunctionData({
72
+ abi: ENS_AUTOMATION_RESOLVER_ABI,
73
+ functionName: 'multicall',
74
+ args: [calls],
75
+ })
76
+ return {
77
+ resolverAddress,
78
+ data,
79
+ calls,
80
+ added: args.diff.added,
81
+ removed: args.diff.removed,
82
+ }
83
+ }
84
+
85
+ export type RecordsFixPlanItem =
86
+ | { kind: 'missing-approval'; address: Address }
87
+ | { kind: 'stale-approval'; address: Address }
88
+ | { kind: 'no-resolver' }
89
+ | { kind: 'no-ens' }
90
+
91
+ export type RecordsFixPlan = {
92
+ identityAgentId: string
93
+ ensName: string | undefined
94
+ items: RecordsFixPlanItem[]
95
+ }
96
+
97
+ type ReconcileArgs = {
98
+ identity: EthagentIdentity
99
+ registry: Erc8004RegistryConfig
100
+ client?: Pick<PublicClient, 'readContract'>
101
+ }
102
+
103
+ export async function reconcileWalletSetup(
104
+ args: ReconcileArgs,
105
+ ): Promise<RecordsFixPlan> {
106
+ const baseState = (args.identity.state ?? {}) as Record<string, unknown>
107
+ const ensName = typeof baseState.ensName === 'string' ? baseState.ensName.trim() : ''
108
+ const custodyMode = readCustodyMode(baseState)
109
+ const approved = normalizeApprovedOperatorWallets(baseState.approvedOperatorWallets)
110
+ const items: RecordsFixPlanItem[] = []
111
+
112
+ if (custodyMode !== 'advanced' || approved.length === 0) {
113
+ return { identityAgentId: String(args.identity.agentId ?? ''), ensName: ensName || undefined, items }
114
+ }
115
+ if (!ensName) {
116
+ items.push({ kind: 'no-ens' })
117
+ return { identityAgentId: String(args.identity.agentId ?? ''), ensName: undefined, items }
118
+ }
119
+
120
+ let resolverAddress: Address | null
121
+ try {
122
+ resolverAddress = await readResolverAddress(ensName)
123
+ } catch {
124
+ resolverAddress = null
125
+ }
126
+ if (!resolverAddress) {
127
+ items.push({ kind: 'no-resolver' })
128
+ return { identityAgentId: String(args.identity.agentId ?? ''), ensName, items }
129
+ }
130
+
131
+ const ownerAddress = readOwnerAddressField(baseState) ?? args.identity.ownerAddress ?? args.identity.address
132
+ const node = namehash(ensName)
133
+
134
+ for (const record of approved) {
135
+ const isApproved = await readDelegation({
136
+ client: args.client ?? createReadClient(),
137
+ resolverAddress,
138
+ ownerAddress: getAddress(ownerAddress),
139
+ node,
140
+ delegateAddress: getAddress(record.address),
141
+ })
142
+ if (!isApproved) {
143
+ items.push({ kind: 'missing-approval', address: getAddress(record.address) })
144
+ }
145
+ }
146
+
147
+ return { identityAgentId: String(args.identity.agentId ?? ''), ensName, items }
148
+ }
149
+
150
+ function createReadClient(): Pick<PublicClient, 'readContract'> {
151
+ const transports = [
152
+ http('https://ethereum.publicnode.com', { retryCount: 0, timeout: 8_000 }),
153
+ http('https://eth.llamarpc.com', { retryCount: 0, timeout: 8_000 }),
154
+ ]
155
+ return createPublicClient({
156
+ chain: mainnet,
157
+ transport: fallback(transports, { retryCount: 0 }),
158
+ })
159
+ }
160
+
161
+ type VerifyResolverApprovalsArgs = {
162
+ ensName: string
163
+ ownerAddress: Address
164
+ resolverAddress: Address
165
+ added: ReadonlyArray<Address>
166
+ removed: ReadonlyArray<Address>
167
+ client?: Pick<PublicClient, 'readContract'>
168
+ }
169
+
170
+ export async function verifyResolverApprovalsLanded(args: VerifyResolverApprovalsArgs): Promise<void> {
171
+ if (args.added.length === 0 && args.removed.length === 0) return
172
+ const client = args.client ?? createReadClient()
173
+ const node = namehash(args.ensName)
174
+ const owner = getAddress(args.ownerAddress)
175
+ for (const address of args.added) {
176
+ const delegate = getAddress(address)
177
+ const isApproved = await readDelegation({
178
+ client,
179
+ resolverAddress: args.resolverAddress,
180
+ ownerAddress: owner,
181
+ node,
182
+ delegateAddress: delegate,
183
+ })
184
+ if (!isApproved) {
185
+ throw new Error(`Resolver delegation didn't land for operator wallet ${delegate}. Your wallet may have rejected the inner operation; retry by selecting Fix Records again.`)
186
+ }
187
+ }
188
+ for (const address of args.removed) {
189
+ const delegate = getAddress(address)
190
+ const isApproved = await readDelegation({
191
+ client,
192
+ resolverAddress: args.resolverAddress,
193
+ ownerAddress: owner,
194
+ node,
195
+ delegateAddress: delegate,
196
+ })
197
+ if (isApproved) {
198
+ throw new Error(`Resolver revocation didn't land for operator wallet ${delegate}. Your wallet may have rejected the inner operation; retry by selecting Fix Records again.`)
199
+ }
200
+ }
201
+ }
202
+
203
+ export function fixPlanRequiresOwnerWallet(plan: RecordsFixPlan): boolean {
204
+ return plan.items.some(item =>
205
+ item.kind === 'missing-approval' || item.kind === 'stale-approval',
206
+ )
207
+ }
208
+
209
+ export function describeFixPlanItem(item: RecordsFixPlanItem): string {
210
+ switch (item.kind) {
211
+ case 'missing-approval':
212
+ return `operator wallet ${item.address} has no onchain resolver approval, record writes will be rejected`
213
+ case 'stale-approval':
214
+ return `operator wallet ${item.address} has an onchain approval that no longer matches the operator set`
215
+ case 'no-resolver':
216
+ return 'ENS subdomain has no resolver set yet, finish ENS setup before authorizing operator wallets onchain'
217
+ case 'no-ens':
218
+ return 'advanced mode requires an ENS subdomain so operator wallets can sign profile updates onchain'
219
+ }
220
+ }
@@ -0,0 +1,51 @@
1
+ export type TxGuardKind =
2
+ | 'rebackup'
3
+ | 'public-profile'
4
+ | 'vault-deploy'
5
+ | 'vault-deposit'
6
+ | 'vault-unwrap'
7
+ | 'vault-withdraw'
8
+
9
+ export class TxGuardBusyError extends Error {
10
+ readonly kind: TxGuardKind
11
+ constructor(kind: TxGuardKind, message: string) {
12
+ super(message)
13
+ this.name = 'TxGuardBusyError'
14
+ this.kind = kind
15
+ }
16
+ }
17
+
18
+ const inFlight = new Set<TxGuardKind>()
19
+
20
+ export function isTxGuardBusy(kind: TxGuardKind): boolean {
21
+ return inFlight.has(kind)
22
+ }
23
+
24
+ export function acquireTxGuard(kind: TxGuardKind): void {
25
+ inFlight.add(kind)
26
+ }
27
+
28
+ export function releaseTxGuard(kind: TxGuardKind): void {
29
+ inFlight.delete(kind)
30
+ }
31
+
32
+ export function resetTxGuardForTest(): void {
33
+ inFlight.clear()
34
+ }
35
+
36
+ export function txGuardBusyMessage(kind: TxGuardKind): string {
37
+ switch (kind) {
38
+ case 'rebackup':
39
+ return 'A snapshot save is already in flight. Wait for it to complete before retrying.'
40
+ case 'public-profile':
41
+ return 'A public profile update is already in flight. Wait for it to complete before retrying.'
42
+ case 'vault-deploy':
43
+ return 'A vault deploy transaction is already in flight. Wait for it to complete before retrying.'
44
+ case 'vault-deposit':
45
+ return 'A vault deposit transaction is already in flight. Wait for it to complete before retrying.'
46
+ case 'vault-unwrap':
47
+ return 'A vault unwrap transaction is already in flight. Wait for it to complete before retrying.'
48
+ case 'vault-withdraw':
49
+ return 'A token withdraw transaction is already in flight. Wait for it to complete before retrying.'
50
+ }
51
+ }
@@ -0,0 +1,17 @@
1
+ import type { EthagentConfig, EthagentIdentity } from '../../storage/config.js'
2
+
3
+ export type IdentityHubResult =
4
+ | { kind: 'token'; identity: EthagentIdentity }
5
+ | { kind: 'updated'; config: EthagentConfig; message: string }
6
+ | { kind: 'skip' }
7
+ | { kind: 'cancel' }
8
+
9
+ export type IdentityHubInitialAction = 'create' | 'load' | 'settings' | 'save-snapshot' | 'save-prompt'
10
+
11
+ export type IdentityHubProps = {
12
+ mode: 'first-run' | 'manage'
13
+ config?: EthagentConfig
14
+ initialAction?: IdentityHubInitialAction
15
+ onComplete: (result: IdentityHubResult) => void
16
+ onConfigChange?: (config: EthagentConfig) => void
17
+ }
@@ -0,0 +1,136 @@
1
+ import { useEffect, useState } from 'react'
2
+ import type { EthagentIdentity } from '../../storage/config.js'
3
+ import { catFromIpfs, DEFAULT_IPFS_API_URL } from '../storage/ipfs.js'
4
+ import {
5
+ continuityVaultRef,
6
+ continuityVaultStatus,
7
+ continuityWorkingTreeStatus,
8
+ ensurePublicSkillsFile,
9
+ type ContinuityWorkingTreeStatus,
10
+ } from '../continuity/storage.js'
11
+ import { openFileInEditor } from '../continuity/editor.js'
12
+ import { exportLocalBackup } from '../continuity/localBackup.js'
13
+ import { listPublishedContinuitySnapshots } from '../continuity/snapshots.js'
14
+ import type { Step } from './identityHubReducer.js'
15
+
16
+ type UseIdentityHubContinuityArgs = {
17
+ identity: EthagentIdentity | undefined
18
+ step: Step
19
+ setStep: (step: Step) => void
20
+ handleStepError: (err: unknown, backStep: Step, softCancel?: Step) => void
21
+ }
22
+
23
+ export function useIdentityHubContinuity({
24
+ identity,
25
+ step,
26
+ setStep,
27
+ handleStepError,
28
+ }: UseIdentityHubContinuityArgs): {
29
+ continuityReady: boolean
30
+ setContinuityReady: (ready: boolean) => void
31
+ workingStatus: ContinuityWorkingTreeStatus | null
32
+ openContinuityFile: (kind: 'soul' | 'memory' | 'skills') => Promise<void>
33
+ exportLocalBackupZip: () => Promise<void>
34
+ } {
35
+ const [continuityReady, setContinuityReady] = useState<boolean>(false)
36
+ const [workingStatus, setWorkingStatus] = useState<ContinuityWorkingTreeStatus | null>(null)
37
+
38
+ useEffect(() => {
39
+ let cancelled = false
40
+ if (!identity) {
41
+ setContinuityReady(false)
42
+ return
43
+ }
44
+ if (!step.kind.startsWith('continuity') && step.kind !== 'details' && step.kind !== 'menu') return
45
+ continuityVaultStatus(identity)
46
+ .then(status => { if (!cancelled) setContinuityReady(status.ready) })
47
+ .catch(() => { if (!cancelled) setContinuityReady(false) })
48
+ return () => { cancelled = true }
49
+ }, [identity, step.kind])
50
+
51
+ useEffect(() => {
52
+ let cancelled = false
53
+ if (!identity) return
54
+ if (
55
+ step.kind !== 'menu'
56
+ && step.kind !== 'continuity-private'
57
+ && step.kind !== 'continuity-public'
58
+ && step.kind !== 'save-prompt'
59
+ && step.kind !== 'rebackup-confirm'
60
+ ) return
61
+
62
+ const checkStatus = async () => {
63
+ try {
64
+ const [latest] = await listPublishedContinuitySnapshots(identity, 1)
65
+ const status = await continuityWorkingTreeStatus(identity, latest)
66
+ if (cancelled) return
67
+ setWorkingStatus(status)
68
+ } catch {
69
+ if (cancelled) return
70
+ setWorkingStatus(null)
71
+ }
72
+ }
73
+
74
+ void checkStatus()
75
+
76
+ return () => {
77
+ cancelled = true
78
+ }
79
+ }, [identity, step.kind])
80
+
81
+ const openContinuityFile = async (kind: 'soul' | 'memory' | 'skills'): Promise<void> => {
82
+ if (!identity) return
83
+ try {
84
+ if (kind === 'skills') {
85
+ await ensurePublicSkillsFile(identity, {
86
+ fallback: () => readPublishedPublicSkills(identity),
87
+ })
88
+ }
89
+ const ref = continuityVaultRef(identity)
90
+ const file = kind === 'soul' ? ref.soulPath : kind === 'memory' ? ref.memoryPath : ref.publicSkillsPath
91
+ const result = await openFileInEditor(file)
92
+ const displayName = kind === 'soul' ? 'SOUL.md' : kind === 'memory' ? 'MEMORY.md' : 'skills.json'
93
+ const message = result.ok
94
+ ? `opened ${displayName} with ${result.method}.`
95
+ : `open failed: ${result.error}`
96
+ setStep({ kind: 'continuity-private', notice: message, editorOpened: result.ok })
97
+ } catch (err: unknown) {
98
+ handleStepError(err, { kind: 'continuity-private' })
99
+ }
100
+ }
101
+
102
+ const exportLocalBackupZip = async (): Promise<void> => {
103
+ if (!identity) return
104
+ try {
105
+ await ensurePublicSkillsFile(identity, {
106
+ fallback: () => readPublishedPublicSkills(identity),
107
+ })
108
+ const result = await exportLocalBackup(identity)
109
+ const message = result.ok
110
+ ? `Saved local backup to ${result.path}`
111
+ : result.cancelled
112
+ ? 'Backup cancelled'
113
+ : `Backup failed: ${result.error}`
114
+ setStep({ kind: 'continuity-private', notice: message })
115
+ } catch (err: unknown) {
116
+ handleStepError(err, { kind: 'continuity-private' })
117
+ }
118
+ }
119
+
120
+ return {
121
+ continuityReady,
122
+ setContinuityReady,
123
+ workingStatus,
124
+ openContinuityFile,
125
+ exportLocalBackupZip,
126
+ }
127
+ }
128
+
129
+ async function readPublishedPublicSkills(identity: EthagentIdentity): Promise<string> {
130
+ const cid = identity.publicSkills?.cid
131
+ if (!cid) throw new Error('No saved public skills CID')
132
+ return new TextDecoder().decode(await catFromIpfs(
133
+ identity.backup?.ipfsApiUrl ?? DEFAULT_IPFS_API_URL,
134
+ cid,
135
+ ))
136
+ }