ethagent 3.3.4 → 4.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 (322) hide show
  1. package/.claude-plugin/marketplace.json +11 -0
  2. package/.claude-plugin/plugin.json +35 -0
  3. package/LICENSE +1 -1
  4. package/README.md +64 -104
  5. package/commands/ethagent.md +40 -0
  6. package/package.json +16 -16
  7. package/src/app/keybindings/KeybindingProvider.tsx +1 -6
  8. package/src/app/keybindings/types.ts +1 -6
  9. package/src/cli/ResetConfirmView.tsx +54 -53
  10. package/src/cli/demo.ts +86 -0
  11. package/src/cli/hookIo.ts +45 -0
  12. package/src/cli/main.tsx +94 -123
  13. package/src/cli/memoryGuard.ts +49 -0
  14. package/src/cli/reset.ts +28 -70
  15. package/src/cli/sessionStart.ts +33 -0
  16. package/src/cli/status.ts +46 -0
  17. package/src/cli/sync.ts +167 -0
  18. package/src/cli/syncAdapters/claude-code.ts +86 -0
  19. package/src/cli/syncAdapters/codex.ts +66 -0
  20. package/src/cli/syncAdapters/index.ts +45 -0
  21. package/src/cli/syncAdapters/managedBlock.ts +175 -0
  22. package/src/cli/syncAdapters/shared.ts +63 -0
  23. package/src/identity/continuity/envelopeParse.ts +20 -1
  24. package/src/identity/continuity/publicSkills.ts +3 -1
  25. package/src/identity/continuity/skills/publicSkillsSync.ts +2 -1
  26. package/src/identity/continuity/skills/scaffold.ts +5 -2
  27. package/src/identity/continuity/snapshots.ts +12 -5
  28. package/src/identity/continuity/storage/defaults.ts +20 -19
  29. package/src/identity/continuity/storage/status.ts +1 -1
  30. package/src/identity/ens/ensLookup/constants.ts +1 -1
  31. package/src/identity/manager/IdentityManager.tsx +33 -0
  32. package/src/identity/{hub → manager}/OperationalRoutes.tsx +37 -18
  33. package/src/identity/{hub → manager}/Routes.tsx +48 -34
  34. package/src/identity/{hub → manager}/continuity/ContinuityDashboardScreen.tsx +9 -19
  35. package/src/identity/{hub → manager}/continuity/RebackupStorageScreen.tsx +3 -3
  36. package/src/identity/manager/continuity/RecoveryConfirmScreen.tsx +102 -0
  37. package/src/identity/{hub → manager}/continuity/SavePromptScreen.tsx +2 -3
  38. package/src/identity/{hub → manager}/continuity/completion.ts +1 -1
  39. package/src/identity/{hub → manager}/continuity/effects.ts +1 -1
  40. package/src/identity/{hub → manager}/continuity/skills/DeleteSkillConfirmScreen.tsx +2 -2
  41. package/src/identity/{hub → manager}/continuity/skills/NewSkillScreen.tsx +0 -5
  42. package/src/identity/{hub → manager}/continuity/skills/NewSkillVisibilityScreen.tsx +4 -4
  43. package/src/identity/{hub → manager}/continuity/skills/SkillActionsScreen.tsx +6 -22
  44. package/src/identity/{hub → manager}/continuity/skills/SkillsTreeScreen.tsx +5 -17
  45. package/src/identity/{hub → manager}/continuity/snapshot.ts +1 -1
  46. package/src/identity/{hub → manager}/continuity/vault.ts +1 -1
  47. package/src/identity/{hub → manager}/create/CreateFlow.tsx +59 -32
  48. package/src/identity/{hub → manager}/create/effects.ts +19 -10
  49. package/src/identity/manager/create/importScan.ts +122 -0
  50. package/src/identity/{hub → manager}/custody/CustodyEditFlow.tsx +17 -61
  51. package/src/identity/{hub → manager}/custody/actions.ts +1 -15
  52. package/src/identity/{hub → manager}/custody/routes.tsx +20 -40
  53. package/src/identity/{hub → manager}/custody/transactions.ts +1 -0
  54. package/src/identity/{hub → manager}/custody/types.ts +1 -2
  55. package/src/identity/{hub → manager}/custody/useCustodyEffects.ts +1 -1
  56. package/src/identity/{hub → manager}/ens/EnsEditAdvancedScreens.tsx +2 -2
  57. package/src/identity/{hub → manager}/ens/EnsEditMaintenanceScreens.tsx +12 -23
  58. package/src/identity/{hub → manager}/ens/EnsEditReviewScreens.tsx +18 -42
  59. package/src/identity/{hub → manager}/ens/EnsEditRunners.tsx +1 -1
  60. package/src/identity/{hub → manager}/ens/EnsEditShared.tsx +0 -2
  61. package/src/identity/{hub → manager}/ens/EnsEditSimpleScreens.tsx +10 -19
  62. package/src/identity/{hub → manager}/ens/EnsFlow.tsx +133 -41
  63. package/src/identity/{hub → manager}/ens/EnsOperatorWalletsScreen.tsx +14 -19
  64. package/src/identity/{hub → manager}/ens/editCopy.ts +1 -14
  65. package/src/identity/{hub → manager}/profile/EditProfileFlow.tsx +99 -66
  66. package/src/identity/{hub → manager}/profile/effects.ts +1 -3
  67. package/src/identity/{hub → manager}/profile/operatorSave.ts +1 -1
  68. package/src/identity/{hub → manager}/profile/state.ts +1 -1
  69. package/src/identity/{hub/identityHubReducer.ts → manager/reducer.ts} +25 -26
  70. package/src/identity/{hub → manager}/restore/RestoreFlow.tsx +16 -24
  71. package/src/identity/{hub → manager}/restore/apply.ts +1 -1
  72. package/src/identity/{hub → manager}/restore/auth.ts +1 -1
  73. package/src/identity/{hub → manager}/restore/discover.ts +1 -1
  74. package/src/identity/{hub → manager}/restore/fetch.ts +1 -1
  75. package/src/identity/{hub → manager}/restore/restoreAdmin.ts +1 -1
  76. package/src/identity/{hub → manager}/restore/useRestoreEffects.ts +2 -9
  77. package/src/identity/{hub → manager}/settings/StorageCredentialScreen.tsx +10 -25
  78. package/src/identity/{hub → manager}/shared/components/DetailsScreen.tsx +5 -7
  79. package/src/identity/{hub → manager}/shared/components/ErrorScreen.tsx +6 -10
  80. package/src/identity/{hub → manager}/shared/components/FlowTimeline.tsx +4 -3
  81. package/src/identity/{hub → manager}/shared/components/IdentitySummary.tsx +19 -59
  82. package/src/identity/manager/shared/components/LazyMenu.tsx +147 -0
  83. package/src/identity/manager/shared/components/MenuScreen.tsx +220 -0
  84. package/src/identity/manager/shared/components/OperationCompleteScreen.tsx +28 -0
  85. package/src/identity/{hub → manager}/shared/components/UnlinkedIdentityScreen.tsx +9 -10
  86. package/src/identity/{hub → manager}/shared/components/WalletApprovalScreen.tsx +1 -2
  87. package/src/identity/manager/shared/components/Wordmark.tsx +54 -0
  88. package/src/identity/{hub → manager}/shared/components/menuFlagsFromReconciliation.ts +39 -15
  89. package/src/identity/{hub → manager}/shared/effects/profilePrep.ts +1 -1
  90. package/src/identity/manager/shared/effects/types.ts +30 -0
  91. package/src/identity/{hub → manager}/shared/model/copy.ts +0 -4
  92. package/src/identity/{hub → manager}/shared/model/errors.ts +32 -3
  93. package/src/identity/{hub → manager}/shared/model/network.ts +2 -2
  94. package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/hook.ts +5 -0
  95. package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/run.ts +1 -1
  96. package/src/identity/{hub/shared/reconciliation/useAgentReconciliation.ts → manager/shared/reconciliation/index.ts} +6 -0
  97. package/src/identity/{hub → manager}/shared/utils.ts +6 -10
  98. package/src/identity/{hub → manager}/transfer/TokenTransferFlow.tsx +3 -3
  99. package/src/identity/{hub → manager}/transfer/TokenTransferScreens.tsx +4 -10
  100. package/src/identity/{hub → manager}/transfer/effects.ts +1 -1
  101. package/src/identity/{hub → manager}/types.ts +5 -6
  102. package/src/identity/{hub/useIdentityHubContinuity.ts → manager/useContinuity.ts} +59 -27
  103. package/src/identity/{hub/useIdentityHubController.ts → manager/useController.ts} +38 -35
  104. package/src/identity/{hub/useIdentityHubSideEffects.ts → manager/useSideEffects.ts} +40 -4
  105. package/src/identity/registry/erc8004/discovery.ts +3 -17
  106. package/src/identity/registry/erc8004/utils.ts +1 -1
  107. package/src/identity/storage/ipfs.ts +21 -1
  108. package/src/identity/wallet/browserWallet/html.ts +10 -2
  109. package/src/identity/wallet/browserWallet/http.ts +18 -0
  110. package/src/identity/wallet/browserWallet/requestServer.ts +5 -1
  111. package/src/identity/wallet/browserWallet/requests.ts +10 -28
  112. package/src/identity/wallet/browserWallet/session.ts +26 -33
  113. package/src/identity/wallet/browserWallet/validation.ts +14 -0
  114. package/src/identity/wallet/browserWallet/walletPageSource.ts +22 -40
  115. package/src/identity/wallet/page/boot.ts +43 -0
  116. package/src/identity/wallet/page/config.ts +59 -0
  117. package/src/identity/wallet/page/constants.ts +12 -0
  118. package/src/identity/wallet/page/copy.ts +47 -68
  119. package/src/identity/wallet/page/css.ts +638 -0
  120. package/src/identity/wallet/page/{errorView.ts → errors.ts} +5 -14
  121. package/src/identity/wallet/page/{controller.ts → flow.ts} +4 -71
  122. package/src/identity/wallet/page/markup.ts +44 -34
  123. package/src/identity/wallet/page/{walletProvider.ts → provider.ts} +0 -3
  124. package/src/identity/wallet/page/resize.ts +95 -0
  125. package/src/identity/wallet/page/state.ts +135 -8
  126. package/src/identity/wallet/page/timeline.ts +161 -0
  127. package/src/identity/wallet/page/view.ts +22 -302
  128. package/src/storage/config.ts +30 -80
  129. package/src/storage/reset.ts +31 -0
  130. package/src/storage/secrets.ts +1 -16
  131. package/src/ui/Select.tsx +27 -5
  132. package/src/ui/Spinner.tsx +16 -15
  133. package/src/ui/Surface.tsx +21 -17
  134. package/src/ui/TextArea.tsx +173 -0
  135. package/src/ui/TextInput.tsx +31 -133
  136. package/src/ui/theme.ts +22 -13
  137. package/src/utils/clipboard.ts +0 -140
  138. package/src/app/FirstRun.tsx +0 -577
  139. package/src/app/FirstRunTimeline.tsx +0 -51
  140. package/src/app/firstRunConfig.ts +0 -26
  141. package/src/app/hooks/useCancelRequest.ts +0 -22
  142. package/src/app/hooks/useDoublePress.ts +0 -46
  143. package/src/app/hooks/useExitOnCtrlC.ts +0 -36
  144. package/src/auth/openaiOAuth/credentials.ts +0 -47
  145. package/src/auth/openaiOAuth/crypto.ts +0 -23
  146. package/src/auth/openaiOAuth/index.ts +0 -238
  147. package/src/auth/openaiOAuth/landingPage.ts +0 -116
  148. package/src/auth/openaiOAuth/listener.ts +0 -151
  149. package/src/auth/openaiOAuth/refresh.ts +0 -70
  150. package/src/auth/openaiOAuth/shared.ts +0 -115
  151. package/src/chat/ChatBottomPane.tsx +0 -296
  152. package/src/chat/ChatScreen.tsx +0 -1685
  153. package/src/chat/ConversationStack.tsx +0 -56
  154. package/src/chat/MessageList.tsx +0 -638
  155. package/src/chat/SessionStatus.tsx +0 -53
  156. package/src/chat/chatEnvironment.ts +0 -16
  157. package/src/chat/chatScreenUtils.ts +0 -194
  158. package/src/chat/chatSessionState.ts +0 -146
  159. package/src/chat/chatTurnContext.ts +0 -50
  160. package/src/chat/chatTurnOrchestrator.ts +0 -603
  161. package/src/chat/chatTurnRows.ts +0 -64
  162. package/src/chat/commands.ts +0 -494
  163. package/src/chat/continuityEditReview.ts +0 -42
  164. package/src/chat/display/DiffView.tsx +0 -193
  165. package/src/chat/display/SyntaxText.tsx +0 -192
  166. package/src/chat/display/toolCallDisplay.ts +0 -103
  167. package/src/chat/display/toolResultDisplay.ts +0 -19
  168. package/src/chat/input/ChatInput.tsx +0 -625
  169. package/src/chat/input/chatInputHelpers.ts +0 -62
  170. package/src/chat/input/chatInputState.ts +0 -247
  171. package/src/chat/input/chatPaste.ts +0 -49
  172. package/src/chat/input/imageRefs.ts +0 -30
  173. package/src/chat/input/inputRendering.tsx +0 -93
  174. package/src/chat/input/textCursor.ts +0 -212
  175. package/src/chat/messageMarkdown.ts +0 -220
  176. package/src/chat/messageRows.ts +0 -43
  177. package/src/chat/planImplementation.ts +0 -62
  178. package/src/chat/slashCommandHandlers.ts +0 -122
  179. package/src/chat/slashCommandViews.ts +0 -120
  180. package/src/chat/transcript/TranscriptView.tsx +0 -184
  181. package/src/chat/transcript/transcriptViewport.ts +0 -295
  182. package/src/chat/views/ContextLimitView.tsx +0 -95
  183. package/src/chat/views/ContinuityEditReviewView.tsx +0 -50
  184. package/src/chat/views/CopyPicker.tsx +0 -50
  185. package/src/chat/views/PermissionPrompt.tsx +0 -156
  186. package/src/chat/views/PermissionsView.tsx +0 -165
  187. package/src/chat/views/PlanApprovalView.tsx +0 -91
  188. package/src/chat/views/ResumeView.tsx +0 -273
  189. package/src/chat/views/RewindView.tsx +0 -412
  190. package/src/cli/preview.tsx +0 -14
  191. package/src/cli/updateNotice.ts +0 -54
  192. package/src/identity/continuity/privateEdit/apply.ts +0 -170
  193. package/src/identity/continuity/privateEdit/diff.ts +0 -6
  194. package/src/identity/continuity/privateEdit/files.ts +0 -23
  195. package/src/identity/continuity/privateEdit/types.ts +0 -28
  196. package/src/identity/continuity/privateEdit.ts +0 -46
  197. package/src/identity/hub/IdentityHub.tsx +0 -14
  198. package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +0 -104
  199. package/src/identity/hub/ens/effects.ts +0 -218
  200. package/src/identity/hub/shared/components/MenuScreen.tsx +0 -241
  201. package/src/identity/hub/shared/effects/types.ts +0 -53
  202. package/src/identity/hub/shared/reconciliation/index.ts +0 -14
  203. package/src/identity/wallet/page/grainient.ts +0 -278
  204. package/src/identity/wallet/page/html.ts +0 -28
  205. package/src/identity/wallet/page/styles/base.ts +0 -260
  206. package/src/identity/wallet/page/styles/components.ts +0 -262
  207. package/src/identity/wallet/page/styles/index.ts +0 -5
  208. package/src/identity/wallet/page/styles/responsive.ts +0 -247
  209. package/src/identity/wallet/page.tsx +0 -38
  210. package/src/mcp/approvals.ts +0 -113
  211. package/src/mcp/config.ts +0 -235
  212. package/src/mcp/manager.ts +0 -482
  213. package/src/mcp/managerHelpers.ts +0 -70
  214. package/src/mcp/names.ts +0 -19
  215. package/src/mcp/output.ts +0 -96
  216. package/src/models/ModelPicker.tsx +0 -1009
  217. package/src/models/catalog.ts +0 -327
  218. package/src/models/huggingface.ts +0 -712
  219. package/src/models/huggingfaceStorage.ts +0 -136
  220. package/src/models/llamacpp.ts +0 -848
  221. package/src/models/llamacppCommands.ts +0 -44
  222. package/src/models/llamacppConfig.ts +0 -34
  223. package/src/models/llamacppDiscovery.ts +0 -176
  224. package/src/models/llamacppOutput.ts +0 -65
  225. package/src/models/llamacppPreflight.ts +0 -158
  226. package/src/models/modelDisplay.ts +0 -180
  227. package/src/models/modelPickerCatalogFlow.ts +0 -56
  228. package/src/models/modelPickerCredentials.ts +0 -166
  229. package/src/models/modelPickerData.ts +0 -41
  230. package/src/models/modelPickerDisplay.tsx +0 -132
  231. package/src/models/modelPickerHfFlow.ts +0 -192
  232. package/src/models/modelPickerLocalRunnerFlow.ts +0 -115
  233. package/src/models/modelPickerOptions.ts +0 -457
  234. package/src/models/modelPickerTypes.ts +0 -69
  235. package/src/models/modelPickerUninstallFlow.ts +0 -48
  236. package/src/models/modelPickerViewHelpers.ts +0 -174
  237. package/src/models/modelRecommendation.ts +0 -139
  238. package/src/models/providerDisplay.ts +0 -16
  239. package/src/models/runtimeDetection.ts +0 -81
  240. package/src/models/uncensoredCatalog.ts +0 -86
  241. package/src/providers/anthropic.ts +0 -290
  242. package/src/providers/contracts.ts +0 -71
  243. package/src/providers/errors.ts +0 -80
  244. package/src/providers/gemini.ts +0 -391
  245. package/src/providers/openai-chat.ts +0 -474
  246. package/src/providers/openai-responses-format.ts +0 -177
  247. package/src/providers/openai-responses.ts +0 -306
  248. package/src/providers/openaiChatWire.ts +0 -124
  249. package/src/providers/registry.ts +0 -120
  250. package/src/providers/retry.ts +0 -58
  251. package/src/providers/sse.ts +0 -93
  252. package/src/runtime/compaction.ts +0 -395
  253. package/src/runtime/cwd.ts +0 -43
  254. package/src/runtime/providerTurn.ts +0 -38
  255. package/src/runtime/sessionMode.ts +0 -55
  256. package/src/runtime/systemPrompt.ts +0 -213
  257. package/src/runtime/textToolParser.ts +0 -161
  258. package/src/runtime/toolClaimGuards.ts +0 -143
  259. package/src/runtime/toolExecution.ts +0 -304
  260. package/src/runtime/toolIntent.ts +0 -143
  261. package/src/runtime/turn.ts +0 -369
  262. package/src/runtime/turnNudges.ts +0 -223
  263. package/src/runtime/turnTypes.ts +0 -86
  264. package/src/storage/factoryReset.ts +0 -127
  265. package/src/storage/history.ts +0 -58
  266. package/src/storage/permissions.ts +0 -76
  267. package/src/storage/rewind.ts +0 -266
  268. package/src/storage/sessionExport.ts +0 -49
  269. package/src/storage/sessions.ts +0 -495
  270. package/src/tools/bashSafety.ts +0 -186
  271. package/src/tools/bashTool.ts +0 -140
  272. package/src/tools/changeDirectoryTool.ts +0 -213
  273. package/src/tools/contracts.ts +0 -192
  274. package/src/tools/deleteFileTool.ts +0 -116
  275. package/src/tools/editTool.ts +0 -165
  276. package/src/tools/editUtils.ts +0 -170
  277. package/src/tools/fileDiff.ts +0 -261
  278. package/src/tools/listDirectoryTool.ts +0 -55
  279. package/src/tools/listSkillFilesTool.ts +0 -77
  280. package/src/tools/listSkillsTool.ts +0 -68
  281. package/src/tools/mcpResourceTools.ts +0 -95
  282. package/src/tools/permissionRules.ts +0 -85
  283. package/src/tools/privateContinuityEditTool.ts +0 -187
  284. package/src/tools/privateContinuityReadTool.ts +0 -106
  285. package/src/tools/readSkillTool.ts +0 -107
  286. package/src/tools/readTool.ts +0 -85
  287. package/src/tools/registry.ts +0 -103
  288. package/src/tools/writeFileTool.ts +0 -167
  289. package/src/ui/BrandSplash.tsx +0 -133
  290. package/src/ui/terminalTitle.ts +0 -30
  291. package/src/utils/images.ts +0 -140
  292. package/src/utils/markdownSegments.ts +0 -51
  293. package/src/utils/messages.ts +0 -37
  294. package/src/utils/withRetry.ts +0 -324
  295. /package/src/identity/{hub → manager}/continuity/state.ts +0 -0
  296. /package/src/identity/{hub → manager}/custody/helpers.ts +0 -0
  297. /package/src/identity/{hub → manager}/custody/preflight.ts +0 -0
  298. /package/src/identity/{hub → manager}/custody/state.ts +0 -0
  299. /package/src/identity/{hub → manager}/custody/useCustodyFlow.tsx +0 -0
  300. /package/src/identity/{hub → manager}/ens/EnsEditFlow.tsx +0 -0
  301. /package/src/identity/{hub → manager}/ens/advancedEnsValidation.ts +0 -0
  302. /package/src/identity/{hub → manager}/ens/state.ts +0 -0
  303. /package/src/identity/{hub → manager}/ens/transactions.ts +0 -0
  304. /package/src/identity/{hub → manager}/ens/types.ts +0 -0
  305. /package/src/identity/{hub → manager}/profile/identity.ts +0 -0
  306. /package/src/identity/{hub → manager}/restore/envelopes.ts +0 -0
  307. /package/src/identity/{hub → manager}/restore/helpers.ts +0 -0
  308. /package/src/identity/{hub → manager}/restore/recovery.ts +0 -0
  309. /package/src/identity/{hub → manager}/restore/resolve.ts +0 -0
  310. /package/src/identity/{hub → manager}/shared/components/BusyScreen.tsx +0 -0
  311. /package/src/identity/{hub → manager}/shared/components/NetworkScreen.tsx +0 -0
  312. /package/src/identity/{hub → manager}/shared/components/PinataJwtInput.tsx +0 -0
  313. /package/src/identity/{hub → manager}/shared/effects/receipts.ts +0 -0
  314. /package/src/identity/{hub → manager}/shared/effects/sync.ts +0 -0
  315. /package/src/identity/{hub → manager}/shared/model/format.ts +0 -0
  316. /package/src/identity/{hub → manager}/shared/operatorWallets.ts +0 -0
  317. /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/ownership.ts +0 -0
  318. /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/types.ts +0 -0
  319. /package/src/identity/{hub → manager}/shared/reconciliation/walletSetup.ts +0 -0
  320. /package/src/identity/{hub → manager}/shared/txGuard.ts +0 -0
  321. /package/src/identity/{hub → manager}/transfer/progress.ts +0 -0
  322. /package/src/identity/{hub → manager}/transfer/state.ts +0 -0
@@ -26,3 +26,21 @@ export function respondJson(res: http.ServerResponse, status: number, body: Reco
26
26
  })
27
27
  res.end(JSON.stringify(body))
28
28
  }
29
+
30
+ function isLoopbackHost(host: string | undefined): boolean {
31
+ if (!host) return true
32
+ const hostname = host.replace(/:\d+$/, '').replace(/^\[|\]$/g, '')
33
+ return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1'
34
+ }
35
+
36
+ export function isAllowedWalletOrigin(req: http.IncomingMessage): boolean {
37
+ const origin = req.headers.origin
38
+ if (origin && origin !== 'null') {
39
+ try {
40
+ if (!isLoopbackHost(new URL(origin).host)) return false
41
+ } catch {
42
+ return false
43
+ }
44
+ }
45
+ return isLoopbackHost(req.headers.host)
46
+ }
@@ -1,7 +1,7 @@
1
1
  import http from 'node:http'
2
2
  import { randomUUID } from 'node:crypto'
3
3
  import { walletPage } from './html.js'
4
- import { readJson, respondHtml, respondJson } from './http.js'
4
+ import { isAllowedWalletOrigin, readJson, respondHtml, respondJson } from './http.js'
5
5
  import { BrowserWalletError, type ReadyHandler } from './types.js'
6
6
  import {
7
7
  assertSessionToken,
@@ -43,6 +43,10 @@ export function startBrowserWalletServer<T>(args: {
43
43
 
44
44
  const handleRequest = async (req: http.IncomingMessage, res: http.ServerResponse): Promise<void> => {
45
45
  const url = new URL(req.url ?? '/', 'http://127.0.0.1')
46
+ if (req.method === 'POST' && !isAllowedWalletOrigin(req)) {
47
+ respondJson(res, 403, { ok: false, error: 'forbidden origin' })
48
+ return
49
+ }
46
50
  if (req.method === 'GET' && (url.pathname === '/' || url.pathname === '/ethagent')) {
47
51
  respondHtml(res, walletPage(args.title, sessionToken, args.payload))
48
52
  return
@@ -1,4 +1,3 @@
1
- import { recoverAddressFromSignature } from '../../crypto/eth.js'
2
1
  import { normalizeWalletPayloadPurpose } from '../walletPurposeCompat.js'
3
2
  import { startBrowserWalletServer } from './requestServer.js'
4
3
  import type {
@@ -12,10 +11,11 @@ import type {
12
11
  TransactionRequest,
13
12
  } from './types.js'
14
13
  import {
15
- accountMismatchError,
14
+ assertExpectedAccount,
16
15
  chainIdHex,
17
16
  parseAccount,
18
17
  parseHex,
18
+ verifyRecoveredAccount,
19
19
  } from './validation.js'
20
20
 
21
21
  export async function requestBrowserWalletAccount(args: AccountRequest = {}): Promise<BrowserWalletAccount> {
@@ -55,9 +55,7 @@ export async function requestBrowserWalletSignature(args: SignatureRequest): Pro
55
55
  }),
56
56
  prepare: body => {
57
57
  const account = parseAccount(body.account)
58
- if (args.expectedAccount && account.toLowerCase() !== args.expectedAccount.toLowerCase()) {
59
- throw accountMismatchError(account, args.expectedAccount, args.purpose)
60
- }
58
+ assertExpectedAccount(account, args.expectedAccount, args.purpose)
61
59
  const message = args.messageForAccount ? args.messageForAccount(account) : args.message!
62
60
  return { message }
63
61
  },
@@ -65,13 +63,8 @@ export async function requestBrowserWalletSignature(args: SignatureRequest): Pro
65
63
  const account = parseAccount(body.account)
66
64
  const message = typeof body.message === 'string' ? body.message : ''
67
65
  const signature = parseHex(body.signature, 'wallet signature')
68
- if (args.expectedAccount && account.toLowerCase() !== args.expectedAccount.toLowerCase()) {
69
- throw accountMismatchError(account, args.expectedAccount, args.purpose)
70
- }
71
- const recovered = recoverAddressFromSignature(message, signature)
72
- if (recovered.toLowerCase() !== account.toLowerCase()) {
73
- throw new Error('Wallet signature does not match connected account')
74
- }
66
+ assertExpectedAccount(account, args.expectedAccount, args.purpose)
67
+ verifyRecoveredAccount(message, signature, account)
75
68
  return { account, message, signature }
76
69
  },
77
70
  })
@@ -101,9 +94,7 @@ export async function sendBrowserWalletTransaction(args: TransactionRequest): Pr
101
94
  }),
102
95
  complete: body => {
103
96
  const account = parseAccount(body.account)
104
- if (account.toLowerCase() !== args.expectedAccount.toLowerCase()) {
105
- throw accountMismatchError(account, args.expectedAccount, args.purpose)
106
- }
97
+ assertExpectedAccount(account, args.expectedAccount, args.purpose)
107
98
  return { account, txHash: parseHex(body.txHash, 'transaction hash') }
108
99
  },
109
100
  })
@@ -140,9 +131,7 @@ export async function requestBrowserWalletSignatureAndTransaction<TPrepared>(
140
131
  }),
141
132
  prepare: body => {
142
133
  const account = parseAccount(body.account)
143
- if (args.expectedAccount && account.toLowerCase() !== args.expectedAccount.toLowerCase()) {
144
- throw accountMismatchError(account, args.expectedAccount, args.purpose)
145
- }
134
+ assertExpectedAccount(account, args.expectedAccount, args.purpose)
146
135
  const message = args.messageForAccount ? args.messageForAccount(account) : args.message!
147
136
  return { message }
148
137
  },
@@ -150,13 +139,8 @@ export async function requestBrowserWalletSignatureAndTransaction<TPrepared>(
150
139
  const account = parseAccount(body.account)
151
140
  const message = typeof body.message === 'string' ? body.message : ''
152
141
  const signature = parseHex(body.signature, 'wallet signature')
153
- if (args.expectedAccount && account.toLowerCase() !== args.expectedAccount.toLowerCase()) {
154
- throw accountMismatchError(account, args.expectedAccount, args.purpose)
155
- }
156
- const recovered = recoverAddressFromSignature(message, signature)
157
- if (recovered.toLowerCase() !== account.toLowerCase()) {
158
- throw new Error('Wallet signature does not match connected account')
159
- }
142
+ assertExpectedAccount(account, args.expectedAccount, args.purpose)
143
+ verifyRecoveredAccount(message, signature, account)
160
144
  const next = await args.prepareTransaction({ account, message, signature })
161
145
  prepared = {
162
146
  account,
@@ -176,9 +160,7 @@ export async function requestBrowserWalletSignatureAndTransaction<TPrepared>(
176
160
  complete: body => {
177
161
  if (!prepared) throw new Error('Wallet transaction was not prepared')
178
162
  const account = parseAccount(body.account)
179
- if (account.toLowerCase() !== prepared.account.toLowerCase()) {
180
- throw accountMismatchError(account, prepared.account, args.purpose)
181
- }
163
+ assertExpectedAccount(account, prepared.account, args.purpose)
182
164
  return {
183
165
  account,
184
166
  message: prepared.message,
@@ -1,9 +1,10 @@
1
1
  import http from 'node:http'
2
2
  import { randomUUID } from 'node:crypto'
3
- import { recoverAddressFromSignature } from '../../crypto/eth.js'
4
3
  import { normalizeWalletPayloadPurpose } from '../walletPurposeCompat.js'
5
4
  import { walletPage } from './html.js'
6
- import { readJson, respondHtml, respondJson } from './http.js'
5
+ import { isAllowedWalletOrigin, readJson, respondHtml, respondJson } from './http.js'
6
+
7
+ const SSE_DRAIN_MS = 250
7
8
  import {
8
9
  BrowserWalletError,
9
10
  type BrowserWalletSession,
@@ -15,11 +16,12 @@ import {
15
16
  type SignAndTransactionRequest,
16
17
  } from './types.js'
17
18
  import {
18
- accountMismatchError,
19
+ assertExpectedAccount,
19
20
  chainIdHex,
20
21
  parseAccount,
21
22
  parseBrowserWalletErrorBody,
22
23
  parseHex,
24
+ verifyRecoveredAccount,
23
25
  } from './validation.js'
24
26
 
25
27
  export async function openBrowserWalletSession(args: {
@@ -47,6 +49,10 @@ export async function openBrowserWalletSession(args: {
47
49
 
48
50
  const handleRequest = async (req: http.IncomingMessage, res: http.ServerResponse): Promise<void> => {
49
51
  const url = new URL(req.url ?? '/', 'http://127.0.0.1')
52
+ if ((req.method === 'POST' || url.pathname === '/events') && !isAllowedWalletOrigin(req)) {
53
+ respondJson(res, 403, { ok: false, error: 'forbidden origin' })
54
+ return
55
+ }
50
56
  if (req.method === 'GET' && (url.pathname === '/' || url.pathname === '/ethagent')) {
51
57
  respondHtml(res, walletPage(title, '', { kind: 'session-wait' }))
52
58
  return
@@ -98,10 +104,15 @@ export async function openBrowserWalletSession(args: {
98
104
  return
99
105
  }
100
106
  const finished = pending
107
+ try {
108
+ finished.resolve(body)
109
+ } catch (err) {
110
+ respondJson(res, 400, { ok: false, error: (err as Error).message })
111
+ return
112
+ }
101
113
  pending = null
102
- respondJson(res, 200, { ok: true })
103
114
  clearTimeout(finished.timeout)
104
- finished.resolve(body)
115
+ respondJson(res, 200, { ok: true })
105
116
  return
106
117
  }
107
118
  if (req.method === 'POST' && url.pathname === '/cancel') {
@@ -167,7 +178,7 @@ export async function openBrowserWalletSession(args: {
167
178
  ...(opts.prepare ? { prepare: opts.prepare } : {}),
168
179
  ...(opts.prepareTransaction ? { prepareTransaction: opts.prepareTransaction } : {}),
169
180
  resolve: body => {
170
- try { resolve(opts.complete(body)) } catch (err) { reject(err) }
181
+ resolve(opts.complete(body))
171
182
  },
172
183
  reject,
173
184
  timeout,
@@ -194,9 +205,7 @@ export async function openBrowserWalletSession(args: {
194
205
  ...(req.timeoutMs !== undefined ? { timeoutMs: req.timeoutMs } : {}),
195
206
  prepare: body => {
196
207
  const account = parseAccount(body.account)
197
- if (req.expectedAccount && account.toLowerCase() !== req.expectedAccount.toLowerCase()) {
198
- throw accountMismatchError(account, req.expectedAccount, req.purpose)
199
- }
208
+ assertExpectedAccount(account, req.expectedAccount, req.purpose)
200
209
  const message = req.messageForAccount ? req.messageForAccount(account) : req.message!
201
210
  return { message }
202
211
  },
@@ -204,13 +213,8 @@ export async function openBrowserWalletSession(args: {
204
213
  const account = parseAccount(body.account)
205
214
  const message = typeof body.message === 'string' ? body.message : ''
206
215
  const signature = parseHex(body.signature, 'wallet signature')
207
- if (req.expectedAccount && account.toLowerCase() !== req.expectedAccount.toLowerCase()) {
208
- throw accountMismatchError(account, req.expectedAccount, req.purpose)
209
- }
210
- const recovered = recoverAddressFromSignature(message, signature)
211
- if (recovered.toLowerCase() !== account.toLowerCase()) {
212
- throw new Error('Wallet signature does not match connected account')
213
- }
216
+ assertExpectedAccount(account, req.expectedAccount, req.purpose)
217
+ verifyRecoveredAccount(message, signature, account)
214
218
  return { account, message, signature }
215
219
  },
216
220
  })
@@ -229,9 +233,7 @@ export async function openBrowserWalletSession(args: {
229
233
  ...(req.timeoutMs !== undefined ? { timeoutMs: req.timeoutMs } : {}),
230
234
  complete: body => {
231
235
  const account = parseAccount(body.account)
232
- if (account.toLowerCase() !== req.expectedAccount.toLowerCase()) {
233
- throw accountMismatchError(account, req.expectedAccount, req.purpose)
234
- }
236
+ assertExpectedAccount(account, req.expectedAccount, req.purpose)
235
237
  return { account, txHash: parseHex(body.txHash, 'transaction hash') }
236
238
  },
237
239
  }),
@@ -262,9 +264,7 @@ export async function openBrowserWalletSession(args: {
262
264
  ...(req.timeoutMs !== undefined ? { timeoutMs: req.timeoutMs } : {}),
263
265
  prepare: body => {
264
266
  const account = parseAccount(body.account)
265
- if (req.expectedAccount && account.toLowerCase() !== req.expectedAccount.toLowerCase()) {
266
- throw accountMismatchError(account, req.expectedAccount, req.purpose)
267
- }
267
+ assertExpectedAccount(account, req.expectedAccount, req.purpose)
268
268
  const message = req.messageForAccount ? req.messageForAccount(account) : req.message!
269
269
  return { message }
270
270
  },
@@ -272,13 +272,8 @@ export async function openBrowserWalletSession(args: {
272
272
  const account = parseAccount(body.account)
273
273
  const message = typeof body.message === 'string' ? body.message : ''
274
274
  const signature = parseHex(body.signature, 'wallet signature')
275
- if (req.expectedAccount && account.toLowerCase() !== req.expectedAccount.toLowerCase()) {
276
- throw accountMismatchError(account, req.expectedAccount, req.purpose)
277
- }
278
- const recovered = recoverAddressFromSignature(message, signature)
279
- if (recovered.toLowerCase() !== account.toLowerCase()) {
280
- throw new Error('Wallet signature does not match connected account')
281
- }
275
+ assertExpectedAccount(account, req.expectedAccount, req.purpose)
276
+ verifyRecoveredAccount(message, signature, account)
282
277
  const next = await req.prepareTransaction({ account, message, signature })
283
278
  prepared = {
284
279
  account,
@@ -296,9 +291,7 @@ export async function openBrowserWalletSession(args: {
296
291
  complete: body => {
297
292
  if (!prepared) throw new Error('Wallet transaction was not prepared')
298
293
  const account = parseAccount(body.account)
299
- if (account.toLowerCase() !== prepared.account.toLowerCase()) {
300
- throw accountMismatchError(account, prepared.account, req.purpose)
301
- }
294
+ assertExpectedAccount(account, prepared.account, req.purpose)
302
295
  return {
303
296
  account,
304
297
  message: prepared.message,
@@ -314,7 +307,7 @@ export async function openBrowserWalletSession(args: {
314
307
  closed = true
315
308
  if (pending) failPending(new Error('wallet session closed before request completed'))
316
309
  pushEvent('done', {})
317
- await new Promise(resolve => setTimeout(resolve, 250))
310
+ await new Promise(resolve => setTimeout(resolve, SSE_DRAIN_MS))
318
311
  for (const res of sseClients) {
319
312
  try { res.end() } catch { }
320
313
  }
@@ -1,7 +1,21 @@
1
1
  import { getAddress, type Address, type Hex } from 'viem'
2
+ import { recoverAddressFromSignature } from '../../crypto/eth.js'
2
3
  import { normalizeWalletPurposeValue } from '../walletPurposeCompat.js'
3
4
  import type { BrowserWalletErrorPayload, WalletPurpose } from './types.js'
4
5
 
6
+ export function assertExpectedAccount(account: Address, expectedAccount: Address | undefined, purpose?: WalletPurpose): void {
7
+ if (expectedAccount && account.toLowerCase() !== expectedAccount.toLowerCase()) {
8
+ throw accountMismatchError(account, expectedAccount, purpose)
9
+ }
10
+ }
11
+
12
+ export function verifyRecoveredAccount(message: string, signature: Hex, account: Address): void {
13
+ const recovered = recoverAddressFromSignature(message, signature)
14
+ if (recovered.toLowerCase() !== account.toLowerCase()) {
15
+ throw new Error('Wallet signature does not match connected account')
16
+ }
17
+ }
18
+
5
19
  export function parseBrowserWalletErrorBody(body: Record<string, unknown>): BrowserWalletErrorPayload {
6
20
  const message = typeof body.message === 'string' && body.message.trim() ? body.message.trim() : 'Wallet request failed'
7
21
  const code = typeof body.code === 'string' && body.code.trim() ? body.code.trim() : undefined
@@ -2,39 +2,28 @@ import { existsSync, readFileSync } from 'node:fs'
2
2
  import { dirname, join } from 'node:path'
3
3
  import { fileURLToPath } from 'node:url'
4
4
 
5
- const WALLET_PAGE_ENTRY_FILE = 'page.tsx'
6
- const WALLET_PAGE_MODULE_FILES = [
7
- join('page', 'types.ts'),
8
- join('page', 'html.ts'),
9
- join('page', 'constants.ts'),
10
- join('page', 'styles', 'base.ts'),
11
- join('page', 'styles', 'components.ts'),
12
- join('page', 'styles', 'responsive.ts'),
13
- join('page', 'styles', 'index.ts'),
14
- join('page', 'markup.ts'),
15
- join('page', 'grainient.ts'),
16
- join('page', 'state.ts'),
17
- join('page', 'copy.ts'),
18
- join('page', 'errorView.ts'),
19
- join('page', 'walletProvider.ts'),
20
- join('page', 'view.ts'),
21
- join('page', 'controller.ts'),
22
- ] as const
5
+ const WALLET_PAGE_DIR = 'page'
23
6
 
24
- export function walletPageSourceFiles(fromUrl = import.meta.url): string[] {
25
- const sourceRoot = locateWalletPageSourceRoot(fromUrl)
26
- return [
27
- ...WALLET_PAGE_MODULE_FILES.map(file => join(sourceRoot, file)),
28
- join(sourceRoot, WALLET_PAGE_ENTRY_FILE),
29
- ]
30
- }
31
-
32
- export function walletPageSourceFile(relativeFile: string, fromUrl = import.meta.url): string {
33
- return join(locateWalletPageSourceRoot(fromUrl), relativeFile)
34
- }
7
+ const WALLET_PAGE_PARTS = [
8
+ 'types.ts',
9
+ 'constants.ts',
10
+ 'css.ts',
11
+ 'markup.ts',
12
+ 'config.ts',
13
+ 'copy.ts',
14
+ 'errors.ts',
15
+ 'provider.ts',
16
+ 'view.ts',
17
+ 'resize.ts',
18
+ 'timeline.ts',
19
+ 'state.ts',
20
+ 'flow.ts',
21
+ 'boot.ts',
22
+ ]
35
23
 
36
24
  export function loadWalletPageRawSource(fromUrl = import.meta.url): string {
37
- return walletPageSourceFiles(fromUrl).map(file => readFileSync(file, 'utf8')).join('\n')
25
+ const dir = locateWalletPageSourceRoot(fromUrl)
26
+ return WALLET_PAGE_PARTS.map(part => readFileSync(join(dir, part), 'utf8')).join('\n')
38
27
  }
39
28
 
40
29
  export function loadWalletPageSource(fromUrl = import.meta.url): string {
@@ -61,25 +50,18 @@ export function stripWalletModuleSyntax(source: string): string {
61
50
 
62
51
  function locateWalletPageSourceRoot(fromUrl: string): string {
63
52
  for (const candidate of walletPageSourceRootCandidates(fromUrl)) {
64
- if (hasWalletPageSourceFiles(candidate)) return candidate
53
+ if (existsSync(join(candidate, WALLET_PAGE_PARTS[0]!))) return candidate
65
54
  }
66
55
  throw new Error('could not locate browser wallet page source files')
67
56
  }
68
57
 
69
58
  function walletPageSourceRootCandidates(fromUrl: string): string[] {
70
59
  const start = dirname(fileURLToPath(fromUrl))
71
- const candidates = [join(start, '..')]
60
+ const candidates = [join(start, '..', WALLET_PAGE_DIR)]
72
61
  for (let dir = start; ; dir = dirname(dir)) {
73
- candidates.push(join(dir, 'src', 'identity', 'wallet'))
62
+ candidates.push(join(dir, 'src', 'identity', 'wallet', WALLET_PAGE_DIR))
74
63
  const parent = dirname(dir)
75
64
  if (parent === dir) break
76
65
  }
77
66
  return Array.from(new Set(candidates))
78
67
  }
79
-
80
- function hasWalletPageSourceFiles(sourceRoot: string): boolean {
81
- return [
82
- ...WALLET_PAGE_MODULE_FILES,
83
- WALLET_PAGE_ENTRY_FILE,
84
- ].every(file => existsSync(join(sourceRoot, file)))
85
- }
@@ -0,0 +1,43 @@
1
+ function attachWalletHandlers(): void {
2
+ if (handlersAttached) return
3
+ handlersAttached = true
4
+ approve.onclick = runWalletFlow
5
+ cancel.onclick = cancelFlow
6
+ window.addEventListener('keydown', (e) => {
7
+ if (e.key === 'Escape') {
8
+ if (!escapeAllowed()) { e.preventDefault(); return }
9
+ e.preventDefault()
10
+ cancelFlow()
11
+ } else if (e.key === 'Enter') {
12
+ if (!approve.hidden && !approve.disabled) { e.preventDefault(); runWalletFlow(); return }
13
+ }
14
+ })
15
+ }
16
+
17
+ export function bootWallet(): void {
18
+ initializeViewElements()
19
+ attachWalletHandlers()
20
+ applyFlowChrome()
21
+ setupCardResize()
22
+ if (!window.__WALLET_PREVIEW__) {
23
+ if (document.readyState === 'loading') {
24
+ document.addEventListener('DOMContentLoaded', bootWallet, { once: true })
25
+ } else {
26
+ if (config && config.kind === 'session-wait') {
27
+ startSessionMode()
28
+ } else {
29
+ runWalletFlow()
30
+ }
31
+ }
32
+ } else {
33
+ window.__walletPreview = {
34
+ setState,
35
+ setConfig: (c: Partial<WalletConfig>) => { Object.assign(config, c); applyFlowChrome() },
36
+ }
37
+ runWalletFlow()
38
+ }
39
+ }
40
+
41
+ injectStylesAndMarkup()
42
+
43
+ bootWallet()
@@ -0,0 +1,59 @@
1
+ export const config: WalletConfig =
2
+ (window.__WALLET_CONFIG__ as WalletConfig) || {
3
+ sessionToken: 'preview',
4
+ kind: 'sign',
5
+ chainIdHex: '0xaa36a7',
6
+ message: 'identity proof for 0x9F2a???BC4e',
7
+ }
8
+
9
+ export const CHAINS: Record<string, { name: string }> = {
10
+ "0x1": { name: "Ethereum Mainnet" },
11
+ "0xaa36a7": { name: "Sepolia" },
12
+ "0x2105": { name: "Base" },
13
+ "0x14a34": { name: "Base Sepolia" },
14
+ };
15
+
16
+ export interface FlowCopy {
17
+ accent: string;
18
+ tabTitle: string;
19
+ label: string;
20
+ title: string;
21
+ subtitle: string;
22
+ detail: string | null;
23
+ }
24
+
25
+ export const FLOW_COPY: Record<string, FlowCopy> = {
26
+ account: { accent: "sign", tabTitle: "Connect Wallet", label: "Connection Request", title: "Connect Wallet", subtitle: "Shares your wallet address with the agent. No signature or transaction.", detail: null },
27
+ sign: { accent: "sign", tabTitle: "Sign Message", label: "Signature Request", title: "Sign Message", subtitle: "Signs a message to prove wallet ownership. No transaction.", detail: "message" },
28
+ "sign-transaction": { accent: "transaction", tabTitle: "Sign Snapshot", label: "Snapshot Signature", title: "Sign Snapshot", subtitle: "Signs your encrypted agent snapshot for restore access.", detail: null },
29
+ transaction: { accent: "transaction", tabTitle: "Submit Transaction", label: "Onchain Transaction", title: "Submit Transaction", subtitle: "Submits one onchain transaction from your wallet.", detail: "registry" },
30
+ };
31
+
32
+ export const TRANSACTION_TITLES: Record<string, string> = {
33
+ "register-agent": "Mint Agent Token",
34
+ "create-agent": "Create Agent",
35
+ "update-ens-records": "Submit With ENS Controller Wallet",
36
+ "clear-ens-records": "Submit With ENS Controller Wallet",
37
+ "create-simple-ens-subdomain": "Submit With Connected Wallet",
38
+ "set-simple-ens-records": "Submit With Connected Wallet",
39
+ "create-agent-ens-subdomain": "Owner Wallet Required",
40
+ "set-agent-ens-records": "Owner Wallet Required",
41
+ "publish-transfer-snapshot": "Sender Wallet: Publish Snapshot",
42
+ };
43
+
44
+ export function transactionPurposeTitle(): string {
45
+ const key = config.purpose || "";
46
+ const fromPurpose = PURPOSE_COPY[key]?.flowTitle;
47
+ const explicit = TRANSACTION_TITLES[key];
48
+ return fromPurpose || explicit || FLOW_COPY.transaction!.title;
49
+ }
50
+
51
+ export const STATE_TITLES = {
52
+ connecting: "Connecting Wallet",
53
+ approveSign: "Sign Message",
54
+ preparingTransaction: "Preparing Transaction",
55
+ approveTransaction: "Review Transaction",
56
+ error: "Wallet Error",
57
+ cancelled: "Cancelled",
58
+ default: "Wallet Action",
59
+ };
@@ -1,3 +1,15 @@
1
+ export const glyphs = {
2
+ ellipsis: "…",
3
+ };
4
+
5
+ export function escapeHtml(value: unknown): string {
6
+ return String(value == null ? "" : value)
7
+ .replaceAll("&", "&amp;")
8
+ .replaceAll("<", "&lt;")
9
+ .replaceAll(">", "&gt;")
10
+ .replaceAll("\"", "&quot;");
11
+ }
12
+
1
13
  export const CLOSE_DELAY_MS = 10000
2
14
  export const TX_CLOSE_DELAY_MS = 10000
3
15
  export const CANCEL_CLOSE_DELAY_MS = 10000