auramaxx 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (418) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +77 -0
  3. package/apps/desktop-electron/main.js +428 -0
  4. package/bin/auramaxx.js +1063 -0
  5. package/docs/ADAPTERS.md +466 -0
  6. package/docs/AGENT_SETUP.md +159 -0
  7. package/docs/API.md +127 -0
  8. package/docs/APPS.md +199 -0
  9. package/docs/ARCHITECTURE.md +235 -0
  10. package/docs/AUTH.md +318 -0
  11. package/docs/BEST-PRACTICES.md +82 -0
  12. package/docs/CLI.md +141 -0
  13. package/docs/DESKTOP_ELECTRON.md +26 -0
  14. package/docs/DEVELOPING-APPS.md +453 -0
  15. package/docs/MCP.md +122 -0
  16. package/docs/PACKAGING_POLICY.md +19 -0
  17. package/docs/PERMISSION.md +137 -0
  18. package/docs/PROTOCOL.md +142 -0
  19. package/docs/README.md +50 -0
  20. package/docs/SKILLS.md +132 -0
  21. package/docs/TROUBLESHOOTING.md +376 -0
  22. package/docs/WORKSPACE.md +673 -0
  23. package/docs/agent-auth.md +14 -0
  24. package/docs/api/authentication.md +79 -0
  25. package/docs/api/secrets/api-keys.md +28 -0
  26. package/docs/api/secrets/credentials.md +80 -0
  27. package/docs/api/secrets/sharing.md +48 -0
  28. package/docs/api/system.md +41 -0
  29. package/docs/api/wallets/apps-strategies.md +66 -0
  30. package/docs/api/wallets/core.md +46 -0
  31. package/docs/api/wallets/data-portfolio.md +42 -0
  32. package/docs/aura-file.md +48 -0
  33. package/docs/core-concepts/FEATURES.md +114 -0
  34. package/docs/credentials.md +120 -0
  35. package/docs/external/HOW_TO_AURAMAXX/GETTING_SECRETS.md +33 -0
  36. package/docs/external/HOW_TO_AURAMAXX/README.md +45 -0
  37. package/docs/external/getting-started.md +10 -0
  38. package/docs/external/overview.md +19 -0
  39. package/docs/external/persona-paths.md +7 -0
  40. package/docs/external/share-secret.md +76 -0
  41. package/docs/external/why-aura.md +7 -0
  42. package/docs/security.md +227 -0
  43. package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
  44. package/docs/wallet/AI.md +508 -0
  45. package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
  46. package/docs/wallet/README.md +47 -0
  47. package/docs/wallet/STRATEGY.md +89 -0
  48. package/next.config.ts +28 -0
  49. package/package.json +167 -0
  50. package/postcss.config.mjs +8 -0
  51. package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
  52. package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
  53. package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
  54. package/prisma/migrations/20260222090000_update_admin_ttl_default/migration.sql +10 -0
  55. package/prisma/migrations/migration_lock.toml +3 -0
  56. package/prisma/schema.prisma +447 -0
  57. package/public/logo.webp +0 -0
  58. package/scripts/add-app.js +245 -0
  59. package/server/abi/SwapHelper.json +438 -0
  60. package/server/cli/approval.ts +447 -0
  61. package/server/cli/commands/actions.ts +474 -0
  62. package/server/cli/commands/api.ts +220 -0
  63. package/server/cli/commands/apikey.ts +277 -0
  64. package/server/cli/commands/app.ts +204 -0
  65. package/server/cli/commands/auth.ts +464 -0
  66. package/server/cli/commands/cron.ts +24 -0
  67. package/server/cli/commands/diary.ts +274 -0
  68. package/server/cli/commands/doctor.ts +1247 -0
  69. package/server/cli/commands/env.ts +476 -0
  70. package/server/cli/commands/experimental.ts +69 -0
  71. package/server/cli/commands/init.ts +798 -0
  72. package/server/cli/commands/lock.ts +157 -0
  73. package/server/cli/commands/mcp.ts +285 -0
  74. package/server/cli/commands/quickhack.ts +86 -0
  75. package/server/cli/commands/release-check.ts +231 -0
  76. package/server/cli/commands/restore.ts +314 -0
  77. package/server/cli/commands/service.ts +320 -0
  78. package/server/cli/commands/shell-hook.ts +512 -0
  79. package/server/cli/commands/skill.ts +216 -0
  80. package/server/cli/commands/start.ts +139 -0
  81. package/server/cli/commands/status.ts +59 -0
  82. package/server/cli/commands/stop.ts +36 -0
  83. package/server/cli/commands/token.ts +180 -0
  84. package/server/cli/commands/unlock.ts +50 -0
  85. package/server/cli/commands/vault.ts +1323 -0
  86. package/server/cli/commands/wallet.ts +209 -0
  87. package/server/cli/index.ts +280 -0
  88. package/server/cli/lib/approval-poll.ts +94 -0
  89. package/server/cli/lib/aura-parser.ts +64 -0
  90. package/server/cli/lib/credential-create.ts +74 -0
  91. package/server/cli/lib/credential-resolve.ts +280 -0
  92. package/server/cli/lib/dotenv-migrate.ts +116 -0
  93. package/server/cli/lib/dotenv-parser.ts +146 -0
  94. package/server/cli/lib/escalation.ts +57 -0
  95. package/server/cli/lib/http.ts +91 -0
  96. package/server/cli/lib/init-steps.ts +76 -0
  97. package/server/cli/lib/local-agent-trust.ts +45 -0
  98. package/server/cli/lib/lock-unlock-helper.ts +71 -0
  99. package/server/cli/lib/process.ts +162 -0
  100. package/server/cli/lib/prompt.ts +294 -0
  101. package/server/cli/lib/theme.ts +240 -0
  102. package/server/cli/socket.ts +579 -0
  103. package/server/cli/transport-client.ts +50 -0
  104. package/server/cron/index.ts +137 -0
  105. package/server/cron/job.ts +31 -0
  106. package/server/cron/jobs/balance-sync.ts +436 -0
  107. package/server/cron/jobs/incoming-scan.ts +506 -0
  108. package/server/cron/jobs/native-price.ts +70 -0
  109. package/server/cron/jobs/orphan-cleanup.ts +40 -0
  110. package/server/cron/jobs/strategy-runner.ts +175 -0
  111. package/server/cron/scheduler.ts +125 -0
  112. package/server/index.ts +420 -0
  113. package/server/lib/adapters/factory.ts +119 -0
  114. package/server/lib/adapters/index.ts +19 -0
  115. package/server/lib/adapters/router.ts +297 -0
  116. package/server/lib/adapters/telegram.ts +645 -0
  117. package/server/lib/adapters/types.ts +89 -0
  118. package/server/lib/adapters/webhook.ts +95 -0
  119. package/server/lib/address.ts +49 -0
  120. package/server/lib/agent-auth/contracts.ts +1194 -0
  121. package/server/lib/agent-profiles.ts +419 -0
  122. package/server/lib/ai.ts +285 -0
  123. package/server/lib/api-registry/contracts.ts +86 -0
  124. package/server/lib/api-registry/validation.ts +172 -0
  125. package/server/lib/apikey-migration.ts +258 -0
  126. package/server/lib/app-installer.ts +505 -0
  127. package/server/lib/app-tokens.ts +247 -0
  128. package/server/lib/approval-link.ts +27 -0
  129. package/server/lib/auth.ts +314 -0
  130. package/server/lib/auto-execute.ts +160 -0
  131. package/server/lib/batch.ts +242 -0
  132. package/server/lib/cold.ts +1048 -0
  133. package/server/lib/config.ts +408 -0
  134. package/server/lib/credential-access-audit.ts +85 -0
  135. package/server/lib/credential-access-policy.ts +111 -0
  136. package/server/lib/credential-health.ts +343 -0
  137. package/server/lib/credential-import.ts +608 -0
  138. package/server/lib/credential-scope.ts +102 -0
  139. package/server/lib/credential-shares.ts +190 -0
  140. package/server/lib/credential-transport.ts +533 -0
  141. package/server/lib/credential-vault.ts +77 -0
  142. package/server/lib/credentials.ts +422 -0
  143. package/server/lib/crypto.ts +8 -0
  144. package/server/lib/db.ts +58 -0
  145. package/server/lib/defaults.ts +386 -0
  146. package/server/lib/dex/index.ts +80 -0
  147. package/server/lib/dex/relay.ts +235 -0
  148. package/server/lib/dex/types.ts +59 -0
  149. package/server/lib/dex/uniswap.ts +370 -0
  150. package/server/lib/diary.ts +34 -0
  151. package/server/lib/dont-ask-again-policy.ts +41 -0
  152. package/server/lib/e2e-agent/artifacts.ts +36 -0
  153. package/server/lib/e2e-agent/contracts.ts +112 -0
  154. package/server/lib/e2e-agent/validation.ts +135 -0
  155. package/server/lib/encrypt.ts +114 -0
  156. package/server/lib/error.ts +20 -0
  157. package/server/lib/events.ts +217 -0
  158. package/server/lib/feature-flags.ts +93 -0
  159. package/server/lib/hot.ts +357 -0
  160. package/server/lib/human-action-summary.ts +80 -0
  161. package/server/lib/key-fingerprint.ts +28 -0
  162. package/server/lib/logger.ts +340 -0
  163. package/server/lib/network.ts +137 -0
  164. package/server/lib/notifications.ts +230 -0
  165. package/server/lib/oauth2-refresh.ts +241 -0
  166. package/server/lib/oursecret.ts +71 -0
  167. package/server/lib/passkey-credential.ts +360 -0
  168. package/server/lib/passkey.ts +68 -0
  169. package/server/lib/permissions.ts +299 -0
  170. package/server/lib/pino.ts +24 -0
  171. package/server/lib/policy-preview.ts +138 -0
  172. package/server/lib/price.ts +338 -0
  173. package/server/lib/prices.ts +34 -0
  174. package/server/lib/project-scope.ts +297 -0
  175. package/server/lib/resolve-action.ts +328 -0
  176. package/server/lib/resolve.ts +36 -0
  177. package/server/lib/secret-gist-share.ts +296 -0
  178. package/server/lib/sessions.ts +634 -0
  179. package/server/lib/socket-path.ts +56 -0
  180. package/server/lib/solana/connection.ts +26 -0
  181. package/server/lib/solana/jupiter.ts +128 -0
  182. package/server/lib/solana/transfer.ts +108 -0
  183. package/server/lib/solana/wallet.ts +136 -0
  184. package/server/lib/strategy/emits.ts +21 -0
  185. package/server/lib/strategy/engine.ts +1305 -0
  186. package/server/lib/strategy/executor.ts +115 -0
  187. package/server/lib/strategy/hook-context.ts +159 -0
  188. package/server/lib/strategy/hooks.ts +990 -0
  189. package/server/lib/strategy/index.ts +28 -0
  190. package/server/lib/strategy/installer.ts +305 -0
  191. package/server/lib/strategy/loader.ts +256 -0
  192. package/server/lib/strategy/message.ts +237 -0
  193. package/server/lib/strategy/repository.ts +218 -0
  194. package/server/lib/strategy/session-logger.ts +693 -0
  195. package/server/lib/strategy/sources.ts +288 -0
  196. package/server/lib/strategy/state.ts +189 -0
  197. package/server/lib/strategy/templates.ts +403 -0
  198. package/server/lib/strategy/tick.ts +404 -0
  199. package/server/lib/strategy/types.ts +230 -0
  200. package/server/lib/swap.ts +3 -0
  201. package/server/lib/temp.ts +86 -0
  202. package/server/lib/token-metadata.ts +86 -0
  203. package/server/lib/token-safety.ts +200 -0
  204. package/server/lib/token-search.ts +444 -0
  205. package/server/lib/totp.ts +194 -0
  206. package/server/lib/transactions.ts +123 -0
  207. package/server/lib/transport.ts +84 -0
  208. package/server/lib/txhistory/decoder.ts +262 -0
  209. package/server/lib/txhistory/enricher.ts +652 -0
  210. package/server/lib/txhistory/index.ts +391 -0
  211. package/server/lib/txhistory/signatures.ts +59 -0
  212. package/server/lib/update-check.ts +35 -0
  213. package/server/lib/verified-summary.ts +414 -0
  214. package/server/lib/view-registry.ts +80 -0
  215. package/server/mcp/profile-policy.ts +30 -0
  216. package/server/mcp/server.ts +1589 -0
  217. package/server/mcp/tools.ts +276 -0
  218. package/server/middleware/auth.ts +119 -0
  219. package/server/middleware/requestLogger.ts +84 -0
  220. package/server/routes/actions.ts +539 -0
  221. package/server/routes/adapters.ts +711 -0
  222. package/server/routes/addressbook.ts +113 -0
  223. package/server/routes/ai.ts +34 -0
  224. package/server/routes/apikeys.ts +343 -0
  225. package/server/routes/apps.ts +601 -0
  226. package/server/routes/auth.ts +406 -0
  227. package/server/routes/backup.ts +404 -0
  228. package/server/routes/batch.ts +270 -0
  229. package/server/routes/bookmarks.ts +162 -0
  230. package/server/routes/credential-shares.ts +380 -0
  231. package/server/routes/credential-vaults.ts +159 -0
  232. package/server/routes/credentials.ts +1782 -0
  233. package/server/routes/dashboard.ts +97 -0
  234. package/server/routes/defaults.ts +124 -0
  235. package/server/routes/flags.ts +11 -0
  236. package/server/routes/fund.ts +225 -0
  237. package/server/routes/heartbeat.ts +375 -0
  238. package/server/routes/import.ts +364 -0
  239. package/server/routes/launch.ts +665 -0
  240. package/server/routes/lock.ts +54 -0
  241. package/server/routes/logs.ts +68 -0
  242. package/server/routes/nuke.ts +111 -0
  243. package/server/routes/passkey-credentials.ts +99 -0
  244. package/server/routes/passkey.ts +366 -0
  245. package/server/routes/portfolio.ts +217 -0
  246. package/server/routes/price.ts +63 -0
  247. package/server/routes/resolve.ts +31 -0
  248. package/server/routes/security.ts +45 -0
  249. package/server/routes/send-evm.ts +241 -0
  250. package/server/routes/send-solana.ts +281 -0
  251. package/server/routes/send.ts +178 -0
  252. package/server/routes/setup.ts +210 -0
  253. package/server/routes/strategy.ts +894 -0
  254. package/server/routes/swap-evm.ts +352 -0
  255. package/server/routes/swap-solana.ts +176 -0
  256. package/server/routes/swap.ts +356 -0
  257. package/server/routes/token.ts +247 -0
  258. package/server/routes/unlock.ts +467 -0
  259. package/server/routes/views.ts +41 -0
  260. package/server/routes/wallet-assets.ts +361 -0
  261. package/server/routes/wallet-transactions.ts +515 -0
  262. package/server/routes/wallet.ts +709 -0
  263. package/server/types.ts +146 -0
  264. package/shared/credential-field-schema.ts +248 -0
  265. package/skills/auramaxx/HEARTBEAT.md +78 -0
  266. package/skills/auramaxx/SKILL.md +745 -0
  267. package/skills/auramaxx/docs/AGENT_SETUP.md +155 -0
  268. package/skills/auramaxx/docs/API.md +127 -0
  269. package/skills/auramaxx/docs/AUTH.md +318 -0
  270. package/skills/auramaxx/docs/CLI.md +130 -0
  271. package/skills/auramaxx/docs/MCP.md +122 -0
  272. package/skills/auramaxx/docs/TROUBLESHOOTING.md +357 -0
  273. package/skills/auramaxx/docs/WORKSPACE.md +673 -0
  274. package/skills/auramaxx/docs/security.md +227 -0
  275. package/skills/task-lifecycle/SKILL.md +378 -0
  276. package/src/app/api/[...doc]/page.tsx +36 -0
  277. package/src/app/api/agent-requests/route.ts +30 -0
  278. package/src/app/api/apps/install/route.ts +132 -0
  279. package/src/app/api/apps/manifests/route.ts +16 -0
  280. package/src/app/api/apps/static/[...path]/route.ts +57 -0
  281. package/src/app/api/docs/plain/route.ts +74 -0
  282. package/src/app/api/events/route.ts +92 -0
  283. package/src/app/api/page.tsx +290 -0
  284. package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
  285. package/src/app/api/workspace/[id]/apps/route.ts +81 -0
  286. package/src/app/api/workspace/[id]/export/route.ts +67 -0
  287. package/src/app/api/workspace/[id]/route.ts +168 -0
  288. package/src/app/api/workspace/auth.ts +40 -0
  289. package/src/app/api/workspace/config/route.ts +121 -0
  290. package/src/app/api/workspace/import/route.ts +127 -0
  291. package/src/app/api/workspace/route.ts +116 -0
  292. package/src/app/app-legacy-do-not-use/page.tsx +2245 -0
  293. package/src/app/apple-icon.png +0 -0
  294. package/src/app/approve/[actionId]/page.tsx +409 -0
  295. package/src/app/docs/DocsPageContent.tsx +269 -0
  296. package/src/app/docs/[...doc]/page.tsx +41 -0
  297. package/src/app/docs/page.tsx +38 -0
  298. package/src/app/favicon.ico +0 -0
  299. package/src/app/globals.css +819 -0
  300. package/src/app/health/page.tsx +5 -0
  301. package/src/app/hello/page.tsx +102 -0
  302. package/src/app/icon.png +0 -0
  303. package/src/app/layout.tsx +39 -0
  304. package/src/app/page.tsx +1964 -0
  305. package/src/app/privacy/page.tsx +63 -0
  306. package/src/app/providers.tsx +87 -0
  307. package/src/app/share/[token]/page.tsx +295 -0
  308. package/src/app/terms/page.tsx +80 -0
  309. package/src/components/ChainSelector.tsx +44 -0
  310. package/src/components/HumanActionBar.tsx +697 -0
  311. package/src/components/NotificationDrawer.tsx +387 -0
  312. package/src/components/PasskeyEnrollmentPrompt.tsx +235 -0
  313. package/src/components/apps/AgentKeysApp.tsx +490 -0
  314. package/src/components/apps/App.tsx +153 -0
  315. package/src/components/apps/AppGrid.tsx +15 -0
  316. package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
  317. package/src/components/apps/DraggableApp.tsx +562 -0
  318. package/src/components/apps/IFrameApp.tsx +73 -0
  319. package/src/components/apps/LogsApp.tsx +360 -0
  320. package/src/components/apps/SendApp.tsx +394 -0
  321. package/src/components/apps/SetupWizardApp.tsx +1004 -0
  322. package/src/components/apps/SystemDefaultsApp.tsx +845 -0
  323. package/src/components/apps/ThirdPartyApp.tsx +428 -0
  324. package/src/components/apps/TokenApp.tsx +319 -0
  325. package/src/components/apps/TransactionsApp.tsx +438 -0
  326. package/src/components/apps/WalletDetailApp.tsx +1505 -0
  327. package/src/components/apps/index.ts +13 -0
  328. package/src/components/design-system/Button.tsx +88 -0
  329. package/src/components/design-system/ChainIndicator.tsx +65 -0
  330. package/src/components/design-system/ChainSelector.tsx +147 -0
  331. package/src/components/design-system/ConfirmationModal.tsx +107 -0
  332. package/src/components/design-system/ConfirmationPopover.tsx +81 -0
  333. package/src/components/design-system/DownloadButton.tsx +149 -0
  334. package/src/components/design-system/Drawer.tsx +133 -0
  335. package/src/components/design-system/FilterDropdown.tsx +183 -0
  336. package/src/components/design-system/ItemPicker.tsx +157 -0
  337. package/src/components/design-system/Modal.tsx +296 -0
  338. package/src/components/design-system/Popover.tsx +142 -0
  339. package/src/components/design-system/TextInput.tsx +85 -0
  340. package/src/components/design-system/Toggle.tsx +65 -0
  341. package/src/components/design-system/TyvekCollapsibleSection.tsx +55 -0
  342. package/src/components/design-system/index.ts +14 -0
  343. package/src/components/docs/ClientSideMarkdown.tsx +51 -0
  344. package/src/components/docs/DocsSearchBar.tsx +118 -0
  345. package/src/components/docs/DocsThemeToggle.tsx +38 -0
  346. package/src/components/docs/PersistentDocGroup.tsx +91 -0
  347. package/src/components/docs/ShareUrlButton.tsx +33 -0
  348. package/src/components/docs/SidebarScrollMemory.tsx +56 -0
  349. package/src/components/health/CredentialHealthDashboard.tsx +214 -0
  350. package/src/components/icons/ChainIcons.tsx +72 -0
  351. package/src/components/layout/AppStoreDrawer.tsx +369 -0
  352. package/src/components/layout/ContentArea.tsx +21 -0
  353. package/src/components/layout/CreateViewModal.tsx +88 -0
  354. package/src/components/layout/LeftRail.tsx +114 -0
  355. package/src/components/layout/TabBar.tsx +284 -0
  356. package/src/components/layout/WalletSidebar.tsx +1030 -0
  357. package/src/components/layout/index.ts +6 -0
  358. package/src/components/marketing/AuraMaxxSpecOverlay.tsx +653 -0
  359. package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
  360. package/src/components/vault/ApiKeysConsole.tsx +1272 -0
  361. package/src/components/vault/AuditConsole.tsx +600 -0
  362. package/src/components/vault/CredentialDetail.tsx +625 -0
  363. package/src/components/vault/CredentialEmpty.tsx +55 -0
  364. package/src/components/vault/CredentialField.tsx +583 -0
  365. package/src/components/vault/CredentialForm.tsx +1484 -0
  366. package/src/components/vault/CredentialList.tsx +265 -0
  367. package/src/components/vault/CredentialRow.tsx +130 -0
  368. package/src/components/vault/CredentialShareModal.tsx +273 -0
  369. package/src/components/vault/CredentialVault.tsx +1662 -0
  370. package/src/components/vault/CredentialWalletWidget.tsx +103 -0
  371. package/src/components/vault/DocsConsole.tsx +113 -0
  372. package/src/components/vault/ImportCredentialsModal.tsx +578 -0
  373. package/src/components/vault/LargeTypeModal.tsx +88 -0
  374. package/src/components/vault/PasswordGenerator.tsx +232 -0
  375. package/src/components/vault/TOTPDisplay.tsx +108 -0
  376. package/src/components/vault/TotpSetupPanel.tsx +198 -0
  377. package/src/components/vault/VaultSidebar.tsx +881 -0
  378. package/src/components/vault/credentialFormName.ts +91 -0
  379. package/src/components/vault/hooks/useVaultKeyboardShortcuts.ts +69 -0
  380. package/src/components/vault/types.ts +56 -0
  381. package/src/context/AuthContext.tsx +365 -0
  382. package/src/context/PriceContext.tsx +113 -0
  383. package/src/context/ThemeContext.tsx +164 -0
  384. package/src/context/WebSocketContext.tsx +269 -0
  385. package/src/context/WorkspaceContext.tsx +668 -0
  386. package/src/hooks/index.ts +4 -0
  387. package/src/hooks/useAgentActions.ts +552 -0
  388. package/src/hooks/useBalance.ts +103 -0
  389. package/src/hooks/useBalances.ts +129 -0
  390. package/src/hooks/useTheme.ts +156 -0
  391. package/src/instrumentation.ts +12 -0
  392. package/src/lib/api-docs.ts +154 -0
  393. package/src/lib/api.ts +474 -0
  394. package/src/lib/app-loader.ts +148 -0
  395. package/src/lib/app-registry.ts +178 -0
  396. package/src/lib/app-sdk.ts +157 -0
  397. package/src/lib/audit-console-adapter.ts +151 -0
  398. package/src/lib/auth-client.ts +75 -0
  399. package/src/lib/config.ts +74 -0
  400. package/src/lib/credential-field-schema.ts +11 -0
  401. package/src/lib/crypto.ts +112 -0
  402. package/src/lib/db.ts +21 -0
  403. package/src/lib/docs.ts +544 -0
  404. package/src/lib/events.ts +363 -0
  405. package/src/lib/pino.ts +24 -0
  406. package/src/lib/theme-handlers.ts +168 -0
  407. package/src/lib/theme.ts +351 -0
  408. package/src/lib/tokenData.ts +378 -0
  409. package/src/lib/totp-import.ts +57 -0
  410. package/src/lib/vault-crypto.ts +129 -0
  411. package/src/lib/view-registry.ts +57 -0
  412. package/src/lib/websocket-server.ts +302 -0
  413. package/src/lib/websocket-setup.ts +79 -0
  414. package/src/lib/wordlist.ts +2050 -0
  415. package/src/lib/workspace-handlers.ts +285 -0
  416. package/start.sh +170 -0
  417. package/tailwind.config.ts +99 -0
  418. package/tsconfig.json +42 -0
@@ -0,0 +1,552 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
4
+ import { useWebSocket } from '@/context/WebSocketContext';
5
+ import { WALLET_EVENTS, type WalletEvent, type TokenCreatedData, type TokenRevokedData, type TokenSpentData, type ActionCreatedData, type ActionResolvedData } from '@/lib/events';
6
+ import { api, Api } from '@/lib/api';
7
+
8
+ export interface HumanAction {
9
+ id: string;
10
+ type: string;
11
+ fromTier: string;
12
+ toAddress: string | null;
13
+ amount: string | null;
14
+ chain: string;
15
+ status: string;
16
+ createdAt: string;
17
+ metadata?: string;
18
+ rawPayload?: string | null;
19
+ humanSummary?: {
20
+ actionLabel: string;
21
+ oneLiner: string;
22
+ can: string[];
23
+ cannot: string[];
24
+ scope: string[];
25
+ expiresIn: string;
26
+ riskHint: string;
27
+ profileLabel?: string;
28
+ };
29
+ }
30
+
31
+ export interface AgentToken {
32
+ tokenHash: string;
33
+ agentId: string;
34
+ limit: number;
35
+ spent: number;
36
+ remaining: number;
37
+ permissions: string[];
38
+ expiresAt: number;
39
+ isExpired: boolean;
40
+ isRevoked: boolean;
41
+ isActive: boolean; // true = valid in memory, false = DB record only (server restarted)
42
+ isAdmin?: boolean; // true = admin token (UI session token)
43
+ }
44
+
45
+ interface DashboardData {
46
+ requests: HumanAction[];
47
+ history?: HumanAction[];
48
+ tokens: {
49
+ active: AgentToken[];
50
+ inactive: AgentToken[];
51
+ };
52
+ counts: {
53
+ pendingActions: number;
54
+ historyActions?: number;
55
+ activeTokens: number;
56
+ inactiveTokens: number;
57
+ };
58
+ }
59
+
60
+ interface UseAgentActionsOptions {
61
+ autoFetch?: boolean;
62
+ }
63
+
64
+ const LOCAL_RACE_GRACE_MS = 15_000;
65
+ const MAX_HISTORY_ITEMS = 80;
66
+ const DISMISSED_NOTIFICATION_STORAGE_KEY = 'auramaxx:dismissed-notification-ids';
67
+
68
+ function parseMeta(metadata?: string): Record<string, unknown> {
69
+ if (!metadata) return {};
70
+ try {
71
+ const parsed = JSON.parse(metadata);
72
+ return parsed && typeof parsed === 'object' ? parsed : {};
73
+ } catch {
74
+ return {};
75
+ }
76
+ }
77
+
78
+ function prependUniqueAction(list: HumanAction[], next: HumanAction, max = MAX_HISTORY_ITEMS): HumanAction[] {
79
+ const deduped = [next, ...list.filter((item) => item.id !== next.id)];
80
+ return deduped.slice(0, max);
81
+ }
82
+
83
+ function sortNewestFirst(actions: HumanAction[]): HumanAction[] {
84
+ return [...actions].sort((a, b) => {
85
+ const aTs = new Date(a.createdAt).getTime();
86
+ const bTs = new Date(b.createdAt).getTime();
87
+ return bTs - aTs;
88
+ });
89
+ }
90
+
91
+ function loadDismissedNotificationIds(): Set<string> {
92
+ if (typeof window === 'undefined') return new Set();
93
+ try {
94
+ const raw = window.localStorage.getItem(DISMISSED_NOTIFICATION_STORAGE_KEY);
95
+ if (!raw) return new Set();
96
+ const parsed = JSON.parse(raw);
97
+ if (!Array.isArray(parsed)) return new Set();
98
+ return new Set(parsed.filter((item): item is string => typeof item === 'string' && item.length > 0));
99
+ } catch {
100
+ return new Set();
101
+ }
102
+ }
103
+
104
+ function normalizeNotificationId(id: unknown): string {
105
+ if (typeof id === 'string') return id;
106
+ if (typeof id === 'number') return String(id);
107
+ return '';
108
+ }
109
+
110
+ function mergePendingRequests(server: HumanAction[], local: HumanAction[]): HumanAction[] {
111
+ const now = Date.now();
112
+ const merged = new Map<string, HumanAction>();
113
+
114
+ server
115
+ .filter((action) => action.status === 'pending')
116
+ .forEach((action) => merged.set(action.id, action));
117
+
118
+ local
119
+ .filter((action) => action.status === 'pending')
120
+ .filter((action) => !merged.has(action.id))
121
+ .filter((action) => now - new Date(action.createdAt).getTime() <= LOCAL_RACE_GRACE_MS)
122
+ .forEach((action) => merged.set(action.id, action));
123
+
124
+ return sortNewestFirst(Array.from(merged.values()));
125
+ }
126
+
127
+ function mergeActionHistory(server: HumanAction[], local: HumanAction[]): HumanAction[] {
128
+ const now = Date.now();
129
+ const merged = new Map<string, HumanAction>();
130
+
131
+ server
132
+ .filter((action) => action.status !== 'pending')
133
+ .forEach((action) => merged.set(action.id, action));
134
+
135
+ local
136
+ .filter((action) => action.status !== 'pending')
137
+ .filter((action) => !merged.has(action.id))
138
+ .filter((action) => now - new Date(action.createdAt).getTime() <= LOCAL_RACE_GRACE_MS)
139
+ .forEach((action) => merged.set(action.id, action));
140
+
141
+ return sortNewestFirst(Array.from(merged.values())).slice(0, MAX_HISTORY_ITEMS);
142
+ }
143
+
144
+ interface ApprovalResult {
145
+ success: boolean;
146
+ token?: string;
147
+ agentId?: string;
148
+ limit?: number;
149
+ permissions?: string[];
150
+ expiresIn?: number;
151
+ txHash?: string;
152
+ message?: string;
153
+ }
154
+
155
+ interface UseAgentActionsReturn {
156
+ requests: HumanAction[];
157
+ notifications: HumanAction[];
158
+ dismissNotification: (id: string) => void;
159
+ activeTokens: AgentToken[];
160
+ inactiveTokens: AgentToken[];
161
+ loading: boolean;
162
+ error: string | null;
163
+ counts: {
164
+ pendingActions: number;
165
+ activeTokens: number;
166
+ };
167
+ refresh: () => Promise<void>;
168
+ resolveAction: (id: string, approved: boolean) => Promise<ApprovalResult>;
169
+ revokeToken: (tokenHash: string) => Promise<boolean>;
170
+ actionLoading: string | null;
171
+ lastApprovalResult: ApprovalResult | null;
172
+ clearApprovalResult: () => void;
173
+ connected: boolean;
174
+ }
175
+
176
+ export function useAgentActions(options: UseAgentActionsOptions = {}): UseAgentActionsReturn {
177
+ const { autoFetch = true } = options;
178
+
179
+ const [requests, setRequests] = useState<HumanAction[]>([]);
180
+ const [actionHistory, setActionHistory] = useState<HumanAction[]>([]);
181
+ const [alertNotifications, setAlertNotifications] = useState<HumanAction[]>([]);
182
+ const [dismissedNotificationIds, setDismissedNotificationIds] = useState<Set<string>>(() => loadDismissedNotificationIds());
183
+ const [activeTokens, setActiveTokens] = useState<AgentToken[]>([]);
184
+ const [inactiveTokens, setInactiveTokens] = useState<AgentToken[]>([]);
185
+ const [loading, setLoading] = useState(true);
186
+ const [error, setError] = useState<string | null>(null);
187
+ const [actionLoading, setActionLoading] = useState<string | null>(null);
188
+ const [counts, setCounts] = useState({ pendingActions: 0, activeTokens: 0 });
189
+ const [lastApprovalResult, setLastApprovalResult] = useState<ApprovalResult | null>(null);
190
+
191
+ const { subscribe, connected } = useWebSocket();
192
+ const mountedRef = useRef(true);
193
+ const requestsRef = useRef<HumanAction[]>([]);
194
+
195
+ const clearApprovalResult = useCallback(() => {
196
+ setLastApprovalResult(null);
197
+ }, []);
198
+
199
+ const dismissNotification = useCallback((id: string) => {
200
+ const normalizedId = normalizeNotificationId(id);
201
+ if (!normalizedId) return;
202
+ setDismissedNotificationIds((prev) => {
203
+ const next = new Set(prev);
204
+ next.add(normalizedId);
205
+ return next;
206
+ });
207
+ setAlertNotifications((prev) => prev.filter((n) => normalizeNotificationId(n.id) !== normalizedId));
208
+ }, []);
209
+
210
+ useEffect(() => {
211
+ requestsRef.current = requests;
212
+ }, [requests]);
213
+
214
+ useEffect(() => {
215
+ if (typeof window === 'undefined') return;
216
+ try {
217
+ if (dismissedNotificationIds.size === 0) {
218
+ window.localStorage.removeItem(DISMISSED_NOTIFICATION_STORAGE_KEY);
219
+ return;
220
+ }
221
+ window.localStorage.setItem(
222
+ DISMISSED_NOTIFICATION_STORAGE_KEY,
223
+ JSON.stringify(Array.from(dismissedNotificationIds)),
224
+ );
225
+ } catch {
226
+ // Ignore localStorage failures (private mode / quota).
227
+ }
228
+ }, [dismissedNotificationIds]);
229
+
230
+ const fetchDashboard = useCallback(async () => {
231
+ try {
232
+ const data = await api.get<{ success: boolean; error?: string } & DashboardData>(Api.AgentDashboard, '/agent-requests');
233
+
234
+ if (!mountedRef.current) return;
235
+
236
+ if (data.success) {
237
+ setRequests((prev) => mergePendingRequests(data.requests || [], prev));
238
+ setActionHistory((prev) => mergeActionHistory(data.history || [], prev));
239
+ setActiveTokens(data.tokens?.active || []);
240
+ setInactiveTokens(data.tokens?.inactive || []);
241
+ setCounts({
242
+ pendingActions: data.counts?.pendingActions || 0,
243
+ activeTokens: data.counts?.activeTokens || 0,
244
+ });
245
+ setError(null);
246
+ } else {
247
+ setError(data.error || 'Failed to fetch dashboard');
248
+ }
249
+ } catch (err) {
250
+ if (mountedRef.current) {
251
+ setError(err instanceof Error ? err.message : 'Failed to fetch dashboard');
252
+ }
253
+ } finally {
254
+ if (mountedRef.current) {
255
+ setLoading(false);
256
+ }
257
+ }
258
+ }, []);
259
+
260
+ const refresh = useCallback(async () => {
261
+ setLoading(true);
262
+ await fetchDashboard();
263
+ }, [fetchDashboard]);
264
+
265
+ const resolveAction = useCallback(async (id: string, approved: boolean): Promise<ApprovalResult> => {
266
+ setActionLoading(`resolve-${id}`);
267
+ try {
268
+ const data = await api.post<{
269
+ success: boolean;
270
+ error?: string;
271
+ token?: string;
272
+ agentId?: string;
273
+ limit?: number;
274
+ permissions?: string[];
275
+ expiresIn?: number;
276
+ txHash?: string;
277
+ message?: string;
278
+ }>(Api.Wallet, `/actions/${id}/resolve`, { approved });
279
+
280
+ if (data.success) {
281
+ const result: ApprovalResult = {
282
+ success: true,
283
+ token: data.token,
284
+ agentId: data.agentId,
285
+ limit: data.limit,
286
+ permissions: data.permissions,
287
+ expiresIn: data.expiresIn,
288
+ txHash: data.txHash,
289
+ message: data.message,
290
+ };
291
+ // Set lastApprovalResult when a token is returned
292
+ if (data.token) {
293
+ setLastApprovalResult(result);
294
+ }
295
+ await refresh();
296
+ return result;
297
+ } else {
298
+ setError(data.error || 'Action resolution failed');
299
+ return { success: false, message: data.error };
300
+ }
301
+ } catch (err) {
302
+ const message = err instanceof Error ? err.message : 'Action resolution failed';
303
+ setError(message);
304
+ return { success: false, message };
305
+ } finally {
306
+ setActionLoading(null);
307
+ }
308
+ }, [refresh]);
309
+
310
+ const revokeToken = useCallback(async (tokenHash: string): Promise<boolean> => {
311
+ setActionLoading(`revoke-${tokenHash}`);
312
+ try {
313
+ const data = await api.post<{ success: boolean; error?: string }>(Api.Wallet, '/actions/tokens/revoke', { tokenHash });
314
+
315
+ if (data.success) {
316
+ // WebSocket will update the UI, but refresh as fallback
317
+ await refresh();
318
+ return true;
319
+ } else {
320
+ setError(data.error || 'Revoke failed');
321
+ return false;
322
+ }
323
+ } catch (err) {
324
+ setError(err instanceof Error ? err.message : 'Revoke failed');
325
+ return false;
326
+ } finally {
327
+ setActionLoading(null);
328
+ }
329
+ }, [refresh]);
330
+
331
+ // Initial fetch
332
+ useEffect(() => {
333
+ mountedRef.current = true;
334
+ if (autoFetch) {
335
+ refresh();
336
+ }
337
+ return () => {
338
+ mountedRef.current = false;
339
+ };
340
+ }, [autoFetch, refresh]);
341
+
342
+ // Re-sync on websocket reconnect to avoid missing any action events.
343
+ useEffect(() => {
344
+ if (!autoFetch || !connected) return;
345
+ void fetchDashboard();
346
+ }, [autoFetch, connected, fetchDashboard]);
347
+
348
+ // Periodic reconciliation catches missed broadcasts and keeps UI fresh.
349
+ useEffect(() => {
350
+ if (!autoFetch) return;
351
+ const intervalMs = connected ? 2000 : 3000;
352
+ const interval = setInterval(() => {
353
+ void fetchDashboard();
354
+ }, intervalMs);
355
+ return () => clearInterval(interval);
356
+ }, [autoFetch, connected, fetchDashboard]);
357
+
358
+ // Subscribe to WebSocket events
359
+ useEffect(() => {
360
+ // Token created - add to active tokens
361
+ const unsubTokenCreated = subscribe(WALLET_EVENTS.TOKEN_CREATED, (event) => {
362
+ const data = (event as WalletEvent).data as TokenCreatedData;
363
+ setActiveTokens((prev) => {
364
+ // Check if already exists
365
+ if (prev.some((t) => t.tokenHash === data.tokenHash)) return prev;
366
+ return [
367
+ {
368
+ tokenHash: data.tokenHash,
369
+ agentId: data.agentId,
370
+ limit: data.limit,
371
+ spent: 0,
372
+ remaining: data.limit,
373
+ permissions: data.permissions,
374
+ expiresAt: data.expiresAt,
375
+ isExpired: false,
376
+ isRevoked: false,
377
+ isActive: true,
378
+ },
379
+ ...prev,
380
+ ];
381
+ });
382
+ setCounts((prev) => ({ ...prev, activeTokens: prev.activeTokens + 1 }));
383
+ });
384
+
385
+ // Token revoked - move from active to inactive
386
+ const unsubTokenRevoked = subscribe(WALLET_EVENTS.TOKEN_REVOKED, (event) => {
387
+ const data = (event as WalletEvent).data as TokenRevokedData;
388
+ setActiveTokens((prev) => {
389
+ const token = prev.find((t) => t.tokenHash === data.tokenHash);
390
+ if (token) {
391
+ // Move to inactive
392
+ setInactiveTokens((inactive) => [
393
+ { ...token, isRevoked: true, isActive: false },
394
+ ...inactive,
395
+ ]);
396
+ }
397
+ return prev.filter((t) => t.tokenHash !== data.tokenHash);
398
+ });
399
+ setCounts((prev) => ({
400
+ ...prev,
401
+ activeTokens: Math.max(0, prev.activeTokens - 1),
402
+ }));
403
+ });
404
+
405
+ // Token spent - update spent/remaining
406
+ const unsubTokenSpent = subscribe(WALLET_EVENTS.TOKEN_SPENT, (event) => {
407
+ const data = (event as WalletEvent).data as TokenSpentData;
408
+ setActiveTokens((prev) =>
409
+ prev.map((t) =>
410
+ t.tokenHash === data.tokenHash
411
+ ? { ...t, spent: data.newSpent, remaining: data.remaining }
412
+ : t
413
+ )
414
+ );
415
+ });
416
+
417
+ // Action created - route notify types to notifications, others to pending requests
418
+ const unsubActionCreated = subscribe(WALLET_EVENTS.ACTION_CREATED, (event) => {
419
+ const walletEvent = event as WalletEvent;
420
+ const data = walletEvent.data as ActionCreatedData;
421
+ const createdAtIso = new Date(walletEvent.timestamp || Date.now()).toISOString();
422
+
423
+ // Notify actions go to the notifications list, not pending requests
424
+ if (data.type === 'notify') {
425
+ setAlertNotifications((prev) => {
426
+ if (prev.some((n) => n.id === data.id)) return prev;
427
+ return prependUniqueAction(prev, {
428
+ id: data.id,
429
+ type: data.type,
430
+ fromTier: 'system',
431
+ toAddress: null,
432
+ amount: null,
433
+ chain: 'base',
434
+ status: 'acknowledged',
435
+ createdAt: createdAtIso,
436
+ metadata: JSON.stringify({
437
+ ...data.metadata,
438
+ source: data.source,
439
+ summary: data.summary,
440
+ }),
441
+ });
442
+ });
443
+ return;
444
+ }
445
+
446
+ setRequests((prev) => {
447
+ if (prev.some((r) => r.id === data.id)) return prev;
448
+ return prependUniqueAction(prev, {
449
+ id: data.id,
450
+ type: data.type,
451
+ fromTier: 'system',
452
+ toAddress: null,
453
+ amount: null,
454
+ chain: 'base',
455
+ status: 'pending',
456
+ createdAt: createdAtIso,
457
+ metadata: JSON.stringify({
458
+ ...data.metadata,
459
+ source: data.source,
460
+ summary: data.summary,
461
+ expiresAt: data.expiresAt,
462
+ }),
463
+ });
464
+ });
465
+ setActionHistory((prev) => prev.filter((action) => action.id !== data.id));
466
+ setCounts((prev) => ({ ...prev, pendingActions: prev.pendingActions + 1 }));
467
+ });
468
+
469
+ // Action resolved - remove from pending and move into local history stream
470
+ const unsubActionResolved = subscribe(WALLET_EVENTS.ACTION_RESOLVED, (event) => {
471
+ const walletEvent = event as WalletEvent;
472
+ const data = walletEvent.data as ActionResolvedData;
473
+ const request = requestsRef.current.find((r) => r.id === data.id);
474
+ if (!request) {
475
+ // Missed a prior event (or loaded after resolution) - refresh once for consistency.
476
+ void fetchDashboard();
477
+ return;
478
+ }
479
+
480
+ const resolvedAtIso = new Date(walletEvent.timestamp || Date.now()).toISOString();
481
+ const requestMeta = parseMeta(request.metadata);
482
+ const resolvedAction: HumanAction = {
483
+ ...request,
484
+ status: data.approved ? 'approved' : 'rejected',
485
+ createdAt: resolvedAtIso,
486
+ metadata: JSON.stringify({
487
+ ...requestMeta,
488
+ approved: data.approved,
489
+ resolvedBy: data.resolvedBy,
490
+ resolvedAt: resolvedAtIso,
491
+ }),
492
+ };
493
+
494
+ setRequests((prev) => prev.filter((r) => r.id !== data.id));
495
+ setActionHistory((prev) => prependUniqueAction(prev, resolvedAction));
496
+ setCounts((prev) => ({
497
+ ...prev,
498
+ pendingActions: Math.max(0, prev.pendingActions - 1),
499
+ }));
500
+ });
501
+
502
+ return () => {
503
+ unsubTokenCreated();
504
+ unsubTokenRevoked();
505
+ unsubTokenSpent();
506
+ unsubActionCreated();
507
+ unsubActionResolved();
508
+ };
509
+ }, [fetchDashboard, subscribe]);
510
+
511
+ const notifications = useMemo(() => {
512
+ const map = new Map<string, HumanAction>();
513
+
514
+ const include = (action: HumanAction) => {
515
+ const notificationId = normalizeNotificationId(action.id);
516
+ if (!notificationId || dismissedNotificationIds.has(notificationId)) return;
517
+ const current = map.get(notificationId);
518
+ if (!current) {
519
+ map.set(notificationId, action);
520
+ return;
521
+ }
522
+ // Prefer pending actions over history/alerts for same id.
523
+ if (current.status !== 'pending' && action.status === 'pending') {
524
+ map.set(notificationId, action);
525
+ }
526
+ };
527
+
528
+ requests.forEach(include);
529
+ actionHistory.forEach(include);
530
+ alertNotifications.forEach(include);
531
+
532
+ return sortNewestFirst(Array.from(map.values()));
533
+ }, [actionHistory, alertNotifications, dismissedNotificationIds, requests]);
534
+
535
+ return {
536
+ requests,
537
+ notifications,
538
+ dismissNotification,
539
+ activeTokens,
540
+ inactiveTokens,
541
+ loading,
542
+ error,
543
+ counts,
544
+ refresh,
545
+ resolveAction,
546
+ revokeToken,
547
+ actionLoading,
548
+ lastApprovalResult,
549
+ clearApprovalResult,
550
+ connected,
551
+ };
552
+ }
@@ -0,0 +1,103 @@
1
+ import { useState, useEffect, useCallback, useRef } from 'react';
2
+ import { ethers } from 'ethers';
3
+ import { Connection, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js';
4
+ import { useAuth } from '@/context/AuthContext';
5
+
6
+ function isSolanaChain(chain: string): boolean {
7
+ return chain === 'solana' || chain === 'solana-devnet';
8
+ }
9
+
10
+ interface UseBalanceResult {
11
+ balance: string | null;
12
+ balanceWei: bigint | null;
13
+ loading: boolean;
14
+ error: string | null;
15
+ refetch: () => Promise<void>;
16
+ /** Native currency symbol for the chain */
17
+ currency: string;
18
+ /** Number of decimals for the native currency */
19
+ decimals: number;
20
+ }
21
+
22
+ export function useBalance(address: string | undefined, chain?: string): UseBalanceResult {
23
+ const { getRpcUrl, getConfiguredChains } = useAuth();
24
+ const [balance, setBalance] = useState<string | null>(null);
25
+ const [balanceWei, setBalanceWei] = useState<bigint | null>(null);
26
+ const [loading, setLoading] = useState(false);
27
+ const [error, setError] = useState<string | null>(null);
28
+
29
+ // Track previous values to detect changes
30
+ const prevAddressRef = useRef<string>('');
31
+ const prevRpcUrlRef = useRef<string>('');
32
+
33
+ const chains = getConfiguredChains();
34
+ const defaultChain = Object.keys(chains)[0] || 'base';
35
+ const targetChain = chain || defaultChain;
36
+
37
+ const isSolana = isSolanaChain(targetChain);
38
+ const currency = isSolana ? 'SOL' : 'ETH';
39
+ const decimals = isSolana ? 9 : 18;
40
+
41
+ // Get current RPC URL for comparison
42
+ const rpcUrl = getRpcUrl(targetChain);
43
+
44
+ const fetchBalance = useCallback(async () => {
45
+ if (!address) {
46
+ setBalance(null);
47
+ setBalanceWei(null);
48
+ return;
49
+ }
50
+
51
+ setLoading(true);
52
+ setError(null);
53
+
54
+ try {
55
+ const currentRpcUrl = getRpcUrl(targetChain);
56
+
57
+ if (isSolanaChain(targetChain)) {
58
+ // Solana balance fetch
59
+ const connection = new Connection(currentRpcUrl, 'confirmed');
60
+ const pubkey = new PublicKey(address);
61
+ const lamports = await connection.getBalance(pubkey);
62
+ setBalanceWei(BigInt(lamports));
63
+ setBalance((lamports / LAMPORTS_PER_SOL).toString());
64
+ } else {
65
+ // EVM balance fetch
66
+ const provider = new ethers.JsonRpcProvider(currentRpcUrl);
67
+ const wei = await provider.getBalance(address);
68
+ setBalanceWei(wei);
69
+ setBalance(ethers.formatEther(wei));
70
+ }
71
+ } catch (err) {
72
+ console.error('[useBalance] Failed to fetch balance:', err);
73
+ setError(err instanceof Error ? err.message : 'Failed to fetch balance');
74
+ setBalance(null);
75
+ setBalanceWei(null);
76
+ } finally {
77
+ setLoading(false);
78
+ }
79
+ }, [address, targetChain, getRpcUrl]);
80
+
81
+ // Refetch when address, chain, or RPC URL changes
82
+ useEffect(() => {
83
+ const shouldRefetch =
84
+ address !== prevAddressRef.current ||
85
+ rpcUrl !== prevRpcUrlRef.current;
86
+
87
+ if (shouldRefetch) {
88
+ prevAddressRef.current = address || '';
89
+ prevRpcUrlRef.current = rpcUrl;
90
+ fetchBalance();
91
+ }
92
+ }, [address, rpcUrl, fetchBalance]);
93
+
94
+ return {
95
+ balance,
96
+ balanceWei,
97
+ loading,
98
+ error,
99
+ refetch: fetchBalance,
100
+ currency,
101
+ decimals,
102
+ };
103
+ }