auramaxx 1.0.0-alpha.4

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 (363) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +112 -0
  3. package/bin/aurawallet.js +121 -0
  4. package/docs/ADAPTERS.md +467 -0
  5. package/docs/API.md +2679 -0
  6. package/docs/APPS.md +198 -0
  7. package/docs/ARCHITECTURE.md +350 -0
  8. package/docs/AUTH.md +698 -0
  9. package/docs/BEST-PRACTICES.md +121 -0
  10. package/docs/CLI.md +61 -0
  11. package/docs/DEVELOPING-APPS.md +452 -0
  12. package/docs/EXTENSION.md +97 -0
  13. package/docs/JOBS.md +33 -0
  14. package/docs/MCP.md +76 -0
  15. package/docs/PROTOCOL.md +142 -0
  16. package/docs/SETUP.md +219 -0
  17. package/docs/WORKSPACE.md +672 -0
  18. package/docs/agent-auth.md +63 -0
  19. package/docs/aura-file.md +48 -0
  20. package/docs/credentials.md +53 -0
  21. package/docs/external/getting-started.md +65 -0
  22. package/docs/external/overview.md +45 -0
  23. package/docs/external/use-cases.md +48 -0
  24. package/docs/external/why-aura.md +35 -0
  25. package/docs/jobs/connect-agent.md +77 -0
  26. package/docs/jobs/migrate-from-dotenv.md +79 -0
  27. package/docs/jobs/recover-from-lockout.md +72 -0
  28. package/docs/jobs/secure-ci.md +63 -0
  29. package/docs/oauth2.md +42 -0
  30. package/docs/passkeys.md +60 -0
  31. package/docs/security.md +540 -0
  32. package/docs/specs/aura-open-protocol.md +61 -0
  33. package/docs/specs/aura-provider-plugin.md +24 -0
  34. package/docs/specs/aura-registry-model.md +31 -0
  35. package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
  36. package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
  37. package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
  38. package/docs/specs/fixtures/valid-basic.aura +4 -0
  39. package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
  40. package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
  41. package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
  42. package/docs/totp.md +40 -0
  43. package/docs/wallet/AI.md +508 -0
  44. package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
  45. package/docs/wallet/README.md +47 -0
  46. package/docs/wallet/STRATEGY.md +89 -0
  47. package/next.config.ts +21 -0
  48. package/package.json +151 -0
  49. package/postcss.config.mjs +8 -0
  50. package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
  51. package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
  52. package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
  53. package/prisma/migrations/migration_lock.toml +3 -0
  54. package/prisma/schema.prisma +447 -0
  55. package/public/logo-chevron.svg +31 -0
  56. package/public/logo-concentric.svg +31 -0
  57. package/public/logo-crosshatch.svg +39 -0
  58. package/public/logo-dashed.svg +39 -0
  59. package/public/logo-horizontal.svg +31 -0
  60. package/public/logo-m56.svg +64 -0
  61. package/public/logo.webp +0 -0
  62. package/scripts/add-app.js +245 -0
  63. package/scripts/init.sh +57 -0
  64. package/scripts/migrate-apikeys-to-credentials.ts +35 -0
  65. package/scripts/sandbox-agent-flow.sh +235 -0
  66. package/scripts/sandbox.sh +175 -0
  67. package/scripts/validate-job-docs.mjs +125 -0
  68. package/server/abi/SwapHelper.json +438 -0
  69. package/server/cli/approval.ts +447 -0
  70. package/server/cli/commands/app.ts +204 -0
  71. package/server/cli/commands/cron.ts +24 -0
  72. package/server/cli/commands/doctor.ts +1007 -0
  73. package/server/cli/commands/env.ts +456 -0
  74. package/server/cli/commands/init.ts +752 -0
  75. package/server/cli/commands/mcp.ts +125 -0
  76. package/server/cli/commands/restore.ts +314 -0
  77. package/server/cli/commands/shell-hook.ts +468 -0
  78. package/server/cli/commands/start.ts +62 -0
  79. package/server/cli/commands/status.ts +59 -0
  80. package/server/cli/commands/stop.ts +14 -0
  81. package/server/cli/commands/token.ts +180 -0
  82. package/server/cli/commands/unlock.ts +49 -0
  83. package/server/cli/commands/vault.ts +417 -0
  84. package/server/cli/index.ts +328 -0
  85. package/server/cli/lib/aura-parser.ts +64 -0
  86. package/server/cli/lib/credential-create.ts +74 -0
  87. package/server/cli/lib/credential-resolve.ts +254 -0
  88. package/server/cli/lib/dotenv-migrate.ts +116 -0
  89. package/server/cli/lib/dotenv-parser.ts +146 -0
  90. package/server/cli/lib/http.ts +91 -0
  91. package/server/cli/lib/init-steps.ts +76 -0
  92. package/server/cli/lib/local-agent-trust.ts +45 -0
  93. package/server/cli/lib/process.ts +136 -0
  94. package/server/cli/lib/prompt.ts +85 -0
  95. package/server/cli/lib/theme.ts +240 -0
  96. package/server/cli/socket.ts +570 -0
  97. package/server/cli/transport-client.ts +50 -0
  98. package/server/cron/index.ts +137 -0
  99. package/server/cron/job.ts +31 -0
  100. package/server/cron/jobs/balance-sync.ts +436 -0
  101. package/server/cron/jobs/incoming-scan.ts +506 -0
  102. package/server/cron/jobs/native-price.ts +70 -0
  103. package/server/cron/jobs/orphan-cleanup.ts +40 -0
  104. package/server/cron/jobs/strategy-runner.ts +175 -0
  105. package/server/cron/scheduler.ts +125 -0
  106. package/server/index.ts +406 -0
  107. package/server/lib/adapters/factory.ts +110 -0
  108. package/server/lib/adapters/index.ts +19 -0
  109. package/server/lib/adapters/router.ts +297 -0
  110. package/server/lib/adapters/telegram.ts +645 -0
  111. package/server/lib/adapters/types.ts +89 -0
  112. package/server/lib/adapters/webhook.ts +95 -0
  113. package/server/lib/address.ts +49 -0
  114. package/server/lib/agent-auth/contracts.ts +1194 -0
  115. package/server/lib/agent-profiles.ts +328 -0
  116. package/server/lib/ai.ts +285 -0
  117. package/server/lib/api-registry/contracts.ts +86 -0
  118. package/server/lib/api-registry/validation.ts +172 -0
  119. package/server/lib/apikey-migration.ts +189 -0
  120. package/server/lib/app-installer.ts +505 -0
  121. package/server/lib/app-tokens.ts +247 -0
  122. package/server/lib/auth.ts +314 -0
  123. package/server/lib/batch.ts +242 -0
  124. package/server/lib/cold.ts +874 -0
  125. package/server/lib/config.ts +381 -0
  126. package/server/lib/credential-access-audit.ts +85 -0
  127. package/server/lib/credential-access-policy.ts +110 -0
  128. package/server/lib/credential-health.ts +343 -0
  129. package/server/lib/credential-import.ts +487 -0
  130. package/server/lib/credential-scope.ts +87 -0
  131. package/server/lib/credential-shares.ts +190 -0
  132. package/server/lib/credential-transport.ts +342 -0
  133. package/server/lib/credential-vault.ts +77 -0
  134. package/server/lib/credentials.ts +333 -0
  135. package/server/lib/crypto.ts +8 -0
  136. package/server/lib/db.ts +15 -0
  137. package/server/lib/defaults.ts +366 -0
  138. package/server/lib/dex/index.ts +80 -0
  139. package/server/lib/dex/relay.ts +235 -0
  140. package/server/lib/dex/types.ts +59 -0
  141. package/server/lib/dex/uniswap.ts +370 -0
  142. package/server/lib/e2e-agent/artifacts.ts +36 -0
  143. package/server/lib/e2e-agent/contracts.ts +112 -0
  144. package/server/lib/e2e-agent/validation.ts +135 -0
  145. package/server/lib/encrypt.ts +128 -0
  146. package/server/lib/error.ts +20 -0
  147. package/server/lib/events.ts +205 -0
  148. package/server/lib/hot.ts +357 -0
  149. package/server/lib/key-fingerprint.ts +28 -0
  150. package/server/lib/logger.ts +331 -0
  151. package/server/lib/network.ts +137 -0
  152. package/server/lib/notifications.ts +219 -0
  153. package/server/lib/oauth2-refresh.ts +241 -0
  154. package/server/lib/oursecret.ts +54 -0
  155. package/server/lib/passkey-credential.ts +360 -0
  156. package/server/lib/passkey.ts +68 -0
  157. package/server/lib/permissions.ts +248 -0
  158. package/server/lib/pino.ts +24 -0
  159. package/server/lib/policy-preview.ts +138 -0
  160. package/server/lib/price.ts +338 -0
  161. package/server/lib/prices.ts +34 -0
  162. package/server/lib/project-scope.ts +239 -0
  163. package/server/lib/resolve-action.ts +427 -0
  164. package/server/lib/resolve.ts +36 -0
  165. package/server/lib/sessions.ts +632 -0
  166. package/server/lib/solana/connection.ts +26 -0
  167. package/server/lib/solana/jupiter.ts +128 -0
  168. package/server/lib/solana/transfer.ts +108 -0
  169. package/server/lib/solana/wallet.ts +136 -0
  170. package/server/lib/strategy/emits.ts +21 -0
  171. package/server/lib/strategy/engine.ts +1305 -0
  172. package/server/lib/strategy/executor.ts +115 -0
  173. package/server/lib/strategy/hook-context.ts +158 -0
  174. package/server/lib/strategy/hooks.ts +990 -0
  175. package/server/lib/strategy/index.ts +28 -0
  176. package/server/lib/strategy/installer.ts +305 -0
  177. package/server/lib/strategy/loader.ts +256 -0
  178. package/server/lib/strategy/message.ts +235 -0
  179. package/server/lib/strategy/repository.ts +218 -0
  180. package/server/lib/strategy/session-logger.ts +693 -0
  181. package/server/lib/strategy/sources.ts +288 -0
  182. package/server/lib/strategy/state.ts +189 -0
  183. package/server/lib/strategy/templates.ts +403 -0
  184. package/server/lib/strategy/tick.ts +404 -0
  185. package/server/lib/strategy/types.ts +230 -0
  186. package/server/lib/swap.ts +3 -0
  187. package/server/lib/temp.ts +86 -0
  188. package/server/lib/token-metadata.ts +86 -0
  189. package/server/lib/token-safety.ts +200 -0
  190. package/server/lib/token-search.ts +444 -0
  191. package/server/lib/totp.ts +194 -0
  192. package/server/lib/transactions.ts +123 -0
  193. package/server/lib/transport.ts +75 -0
  194. package/server/lib/txhistory/decoder.ts +262 -0
  195. package/server/lib/txhistory/enricher.ts +652 -0
  196. package/server/lib/txhistory/index.ts +391 -0
  197. package/server/lib/txhistory/signatures.ts +59 -0
  198. package/server/lib/verified-summary.ts +421 -0
  199. package/server/mcp/profile-policy.ts +30 -0
  200. package/server/mcp/server.ts +619 -0
  201. package/server/mcp/tools.ts +523 -0
  202. package/server/middleware/auth.ts +119 -0
  203. package/server/middleware/requestLogger.ts +84 -0
  204. package/server/routes/actions.ts +459 -0
  205. package/server/routes/adapters.ts +703 -0
  206. package/server/routes/addressbook.ts +113 -0
  207. package/server/routes/ai.ts +34 -0
  208. package/server/routes/apikeys.ts +295 -0
  209. package/server/routes/apps.ts +601 -0
  210. package/server/routes/auth.ts +457 -0
  211. package/server/routes/backup.ts +340 -0
  212. package/server/routes/batch.ts +270 -0
  213. package/server/routes/bookmarks.ts +162 -0
  214. package/server/routes/credential-shares.ts +198 -0
  215. package/server/routes/credential-vaults.ts +154 -0
  216. package/server/routes/credentials.ts +1290 -0
  217. package/server/routes/dashboard.ts +71 -0
  218. package/server/routes/defaults.ts +124 -0
  219. package/server/routes/fund.ts +229 -0
  220. package/server/routes/import.ts +352 -0
  221. package/server/routes/launch.ts +665 -0
  222. package/server/routes/lock.ts +54 -0
  223. package/server/routes/logs.ts +68 -0
  224. package/server/routes/nuke.ts +111 -0
  225. package/server/routes/passkey-credentials.ts +99 -0
  226. package/server/routes/passkey.ts +346 -0
  227. package/server/routes/portfolio.ts +217 -0
  228. package/server/routes/price.ts +63 -0
  229. package/server/routes/resolve.ts +31 -0
  230. package/server/routes/security.ts +45 -0
  231. package/server/routes/send-evm.ts +241 -0
  232. package/server/routes/send-solana.ts +281 -0
  233. package/server/routes/send.ts +178 -0
  234. package/server/routes/setup.ts +210 -0
  235. package/server/routes/strategy.ts +894 -0
  236. package/server/routes/swap-evm.ts +353 -0
  237. package/server/routes/swap-solana.ts +177 -0
  238. package/server/routes/swap.ts +356 -0
  239. package/server/routes/token.ts +247 -0
  240. package/server/routes/unlock.ts +403 -0
  241. package/server/routes/wallet-assets.ts +361 -0
  242. package/server/routes/wallet-transactions.ts +515 -0
  243. package/server/routes/wallet.ts +710 -0
  244. package/server/types.ts +146 -0
  245. package/skills/aurawallet/SKILL.md +739 -0
  246. package/skills/aurawallet-setup/SKILL.md +74 -0
  247. package/skills/security-review/SKILL.md +148 -0
  248. package/src/app/api/agent-requests/route.ts +30 -0
  249. package/src/app/api/apps/install/route.ts +126 -0
  250. package/src/app/api/apps/manifests/route.ts +16 -0
  251. package/src/app/api/apps/static/[...path]/route.ts +57 -0
  252. package/src/app/api/events/route.ts +92 -0
  253. package/src/app/api/page.tsx +212 -0
  254. package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
  255. package/src/app/api/workspace/[id]/apps/route.ts +81 -0
  256. package/src/app/api/workspace/[id]/export/route.ts +67 -0
  257. package/src/app/api/workspace/[id]/route.ts +168 -0
  258. package/src/app/api/workspace/auth.ts +34 -0
  259. package/src/app/api/workspace/config/route.ts +106 -0
  260. package/src/app/api/workspace/import/route.ts +127 -0
  261. package/src/app/api/workspace/route.ts +116 -0
  262. package/src/app/app/page.tsx +2122 -0
  263. package/src/app/apple-icon.png +0 -0
  264. package/src/app/docs/page.tsx +178 -0
  265. package/src/app/favicon.ico +0 -0
  266. package/src/app/globals.css +572 -0
  267. package/src/app/health/page.tsx +5 -0
  268. package/src/app/hello/page.tsx +15 -0
  269. package/src/app/icon.png +0 -0
  270. package/src/app/layout.tsx +34 -0
  271. package/src/app/page.tsx +986 -0
  272. package/src/app/providers.tsx +90 -0
  273. package/src/app/share/[token]/page.tsx +295 -0
  274. package/src/components/ChainSelector.tsx +144 -0
  275. package/src/components/HumanActionBar.tsx +695 -0
  276. package/src/components/NotificationDrawer.tsx +129 -0
  277. package/src/components/apps/AgentKeysApp.tsx +490 -0
  278. package/src/components/apps/App.tsx +153 -0
  279. package/src/components/apps/AppGrid.tsx +15 -0
  280. package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
  281. package/src/components/apps/DraggableApp.tsx +562 -0
  282. package/src/components/apps/IFrameApp.tsx +73 -0
  283. package/src/components/apps/LogsApp.tsx +360 -0
  284. package/src/components/apps/SendApp.tsx +394 -0
  285. package/src/components/apps/SetupWizardApp.tsx +1004 -0
  286. package/src/components/apps/SystemDefaultsApp.tsx +845 -0
  287. package/src/components/apps/ThirdPartyApp.tsx +428 -0
  288. package/src/components/apps/TokenApp.tsx +319 -0
  289. package/src/components/apps/TransactionsApp.tsx +438 -0
  290. package/src/components/apps/WalletDetailApp.tsx +1505 -0
  291. package/src/components/apps/index.ts +13 -0
  292. package/src/components/design-system/Button.tsx +53 -0
  293. package/src/components/design-system/ChainIndicator.tsx +65 -0
  294. package/src/components/design-system/ChainSelector.tsx +137 -0
  295. package/src/components/design-system/ConfirmationModal.tsx +106 -0
  296. package/src/components/design-system/ConfirmationPopover.tsx +81 -0
  297. package/src/components/design-system/Drawer.tsx +123 -0
  298. package/src/components/design-system/FilterDropdown.tsx +72 -0
  299. package/src/components/design-system/Modal.tsx +206 -0
  300. package/src/components/design-system/Popover.tsx +142 -0
  301. package/src/components/design-system/TextInput.tsx +85 -0
  302. package/src/components/design-system/Toggle.tsx +58 -0
  303. package/src/components/design-system/index.ts +11 -0
  304. package/src/components/docs/DocsThemeToggle.tsx +49 -0
  305. package/src/components/health/CredentialHealthDashboard.tsx +214 -0
  306. package/src/components/icons/ChainIcons.tsx +72 -0
  307. package/src/components/layout/AppStoreDrawer.tsx +369 -0
  308. package/src/components/layout/ContentArea.tsx +21 -0
  309. package/src/components/layout/TabBar.tsx +278 -0
  310. package/src/components/layout/WalletSidebar.tsx +1033 -0
  311. package/src/components/layout/index.ts +4 -0
  312. package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
  313. package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
  314. package/src/components/vault/ApiKeysConsole.tsx +1080 -0
  315. package/src/components/vault/AuditConsole.tsx +584 -0
  316. package/src/components/vault/CredentialDetail.tsx +455 -0
  317. package/src/components/vault/CredentialEmpty.tsx +55 -0
  318. package/src/components/vault/CredentialField.tsx +361 -0
  319. package/src/components/vault/CredentialForm.tsx +1212 -0
  320. package/src/components/vault/CredentialList.tsx +165 -0
  321. package/src/components/vault/CredentialRow.tsx +97 -0
  322. package/src/components/vault/CredentialShareModal.tsx +178 -0
  323. package/src/components/vault/CredentialVault.tsx +754 -0
  324. package/src/components/vault/CredentialWalletWidget.tsx +103 -0
  325. package/src/components/vault/ImportCredentialsModal.tsx +515 -0
  326. package/src/components/vault/LargeTypeModal.tsx +64 -0
  327. package/src/components/vault/PasswordGenerator.tsx +224 -0
  328. package/src/components/vault/TOTPDisplay.tsx +123 -0
  329. package/src/components/vault/VaultSidebar.tsx +413 -0
  330. package/src/components/vault/types.ts +54 -0
  331. package/src/context/AuthContext.tsx +337 -0
  332. package/src/context/PriceContext.tsx +113 -0
  333. package/src/context/ThemeContext.tsx +164 -0
  334. package/src/context/WebSocketContext.tsx +269 -0
  335. package/src/context/WorkspaceContext.tsx +668 -0
  336. package/src/hooks/index.ts +3 -0
  337. package/src/hooks/useAgentActions.ts +368 -0
  338. package/src/hooks/useBalance.ts +103 -0
  339. package/src/hooks/useBalances.ts +129 -0
  340. package/src/instrumentation.ts +12 -0
  341. package/src/lib/api.ts +449 -0
  342. package/src/lib/app-loader.ts +148 -0
  343. package/src/lib/app-registry.ts +178 -0
  344. package/src/lib/app-sdk.ts +157 -0
  345. package/src/lib/audit-console-adapter.ts +151 -0
  346. package/src/lib/auth-client.ts +75 -0
  347. package/src/lib/config.ts +74 -0
  348. package/src/lib/crypto.ts +112 -0
  349. package/src/lib/db.ts +21 -0
  350. package/src/lib/docs.ts +390 -0
  351. package/src/lib/events.ts +361 -0
  352. package/src/lib/pino.ts +24 -0
  353. package/src/lib/theme-handlers.ts +168 -0
  354. package/src/lib/theme.ts +351 -0
  355. package/src/lib/tokenData.ts +378 -0
  356. package/src/lib/vault-crypto.ts +129 -0
  357. package/src/lib/websocket-server.ts +302 -0
  358. package/src/lib/websocket-setup.ts +79 -0
  359. package/src/lib/wordlist.ts +2050 -0
  360. package/src/lib/workspace-handlers.ts +285 -0
  361. package/start.sh +80 -0
  362. package/tailwind.config.ts +99 -0
  363. package/tsconfig.json +42 -0
@@ -0,0 +1,337 @@
1
+ 'use client';
2
+
3
+ import { createContext, useContext, useState, useCallback, ReactNode, useEffect } from 'react';
4
+ import { api, Api } from '@/lib/api';
5
+ import { discardVaultKeypair } from '@/lib/vault-crypto';
6
+
7
+ export interface ApiKey {
8
+ id: string;
9
+ service: string;
10
+ name: string;
11
+ key: string;
12
+ keyMasked: string;
13
+ createdAt?: string;
14
+ }
15
+
16
+ export interface ChainConfig {
17
+ rpc: string;
18
+ chainId: number;
19
+ explorer: string;
20
+ }
21
+
22
+ // Alchemy RPC paths by chain
23
+ const ALCHEMY_PATHS: Record<string, { path: string; chainId: number; explorer: string }> = {
24
+ base: { path: 'base-mainnet', chainId: 8453, explorer: 'https://basescan.org' },
25
+ ethereum: { path: 'eth-mainnet', chainId: 1, explorer: 'https://etherscan.io' },
26
+ arbitrum: { path: 'arb-mainnet', chainId: 42161, explorer: 'https://arbiscan.io' },
27
+ optimism: { path: 'opt-mainnet', chainId: 10, explorer: 'https://optimistic.etherscan.io' },
28
+ solana: { path: 'solana-mainnet', chainId: 0, explorer: 'https://solscan.io' },
29
+ 'solana-devnet': { path: 'solana-devnet', chainId: 0, explorer: 'https://solscan.io/?cluster=devnet' },
30
+ };
31
+
32
+ // Public RPC fallbacks (default chains: base, ethereum, and solana)
33
+ const PUBLIC_RPCS: Record<string, ChainConfig> = {
34
+ base: { rpc: 'https://mainnet.base.org', chainId: 8453, explorer: 'https://basescan.org' },
35
+ ethereum: { rpc: 'https://eth.llamarpc.com', chainId: 1, explorer: 'https://etherscan.io' },
36
+ solana: { rpc: 'https://api.mainnet-beta.solana.com', chainId: 0, explorer: 'https://solscan.io' },
37
+ 'solana-devnet': { rpc: 'https://api.devnet.solana.com', chainId: 0, explorer: 'https://solscan.io/?cluster=devnet' },
38
+ };
39
+
40
+ interface AuthContextValue {
41
+ token: string | null;
42
+ isUnlocked: boolean;
43
+ setToken: (token: string | null) => void;
44
+ clearToken: () => void;
45
+ apiKeys: ApiKey[];
46
+ apiKeysLoading: boolean;
47
+ refreshApiKeys: () => Promise<void>;
48
+ getApiKey: (service: string, name?: string) => string | null;
49
+ getRpcUrl: (chain?: string) => string;
50
+ getChainConfig: (chain?: string) => ChainConfig;
51
+ // Chain overrides (custom RPC URLs stored in DB)
52
+ chainOverrides: Record<string, ChainConfig>;
53
+ chainOverridesLoading: boolean;
54
+ refreshChainOverrides: () => Promise<void>;
55
+ saveChainOverride: (chain: string, config: ChainConfig) => Promise<void>;
56
+ removeChainOverride: (chain: string) => Promise<void>;
57
+ // All configured chains (public defaults + DB overrides)
58
+ getConfiguredChains: () => Record<string, ChainConfig>;
59
+ }
60
+
61
+ const AuthContext = createContext<AuthContextValue | null>(null);
62
+
63
+ // Storage key for persisting token across page reloads
64
+ const TOKEN_STORAGE_KEY = 'aurawallet_admin_token';
65
+
66
+ interface AuthProviderProps {
67
+ children: ReactNode;
68
+ }
69
+
70
+ export function AuthProvider({ children }: AuthProviderProps) {
71
+ const [token, setTokenState] = useState<string | null>(null);
72
+ const [apiKeys, setApiKeys] = useState<ApiKey[]>([]);
73
+ const [apiKeysLoading, setApiKeysLoading] = useState(false);
74
+ const [chainOverrides, setChainOverrides] = useState<Record<string, ChainConfig>>({});
75
+ const [chainOverridesLoading, setChainOverridesLoading] = useState(false);
76
+
77
+ // Load token from sessionStorage on mount
78
+ useEffect(() => {
79
+ if (typeof window !== 'undefined') {
80
+ const stored = sessionStorage.getItem(TOKEN_STORAGE_KEY);
81
+ if (stored) {
82
+ setTokenState(stored);
83
+ }
84
+ }
85
+ }, []);
86
+
87
+ // Fetch API keys when token is available
88
+ const refreshApiKeys = useCallback(async () => {
89
+ if (!token) {
90
+ setApiKeys([]);
91
+ return;
92
+ }
93
+ setApiKeysLoading(true);
94
+ try {
95
+ const data = await api.get<{ success: boolean; apiKeys: ApiKey[] }>(Api.Wallet, '/apikeys');
96
+ if (data.success && data.apiKeys) {
97
+ setApiKeys(data.apiKeys);
98
+ }
99
+ } catch (err) {
100
+ // If the token is invalid/expired (server restarted, new SIGNING_KEY), clear it
101
+ const status = (err as Error & { status?: number }).status;
102
+ if (status === 401 || status === 403) {
103
+ setTokenState(null);
104
+ if (typeof window !== 'undefined') {
105
+ sessionStorage.removeItem(TOKEN_STORAGE_KEY);
106
+ }
107
+ setApiKeys([]);
108
+ }
109
+ console.error('[AuthContext] Failed to fetch API keys:', err);
110
+ } finally {
111
+ setApiKeysLoading(false);
112
+ }
113
+ }, [token]);
114
+
115
+ // Load API keys when token changes
116
+ useEffect(() => {
117
+ if (token) {
118
+ refreshApiKeys();
119
+ } else {
120
+ setApiKeys([]);
121
+ }
122
+ }, [token, refreshApiKeys]);
123
+
124
+ // Fetch chain overrides from workspace config
125
+ const refreshChainOverrides = useCallback(async () => {
126
+ if (!token) {
127
+ setChainOverrides({});
128
+ setChainOverridesLoading(false);
129
+ return;
130
+ }
131
+
132
+ setChainOverridesLoading(true);
133
+ try {
134
+ const data = await api.get<{ success: boolean; config: { chainOverrides: Record<string, ChainConfig> } }>(
135
+ Api.Workspace,
136
+ '/workspace/config'
137
+ );
138
+ if (data.success && data.config) {
139
+ setChainOverrides(data.config.chainOverrides || {});
140
+ }
141
+ } catch (err) {
142
+ const status = (err as Error & { status?: number }).status;
143
+ if (status === 401 || status === 403) {
144
+ setChainOverrides({});
145
+ } else {
146
+ console.error('[AuthContext] Failed to fetch chain overrides:', err);
147
+ }
148
+ } finally {
149
+ setChainOverridesLoading(false);
150
+ }
151
+ }, [token]);
152
+
153
+ // Load chain overrides when auth state changes
154
+ useEffect(() => {
155
+ if (token) {
156
+ refreshChainOverrides();
157
+ return;
158
+ }
159
+ setChainOverrides({});
160
+ setChainOverridesLoading(false);
161
+ }, [token, refreshChainOverrides]);
162
+
163
+ // Save a chain override
164
+ const saveChainOverride = useCallback(async (chain: string, config: ChainConfig) => {
165
+ const newOverrides = { ...chainOverrides, [chain]: config };
166
+ try {
167
+ const data = await api.post<{ success: boolean; config: { chainOverrides: Record<string, ChainConfig> } }>(
168
+ Api.Workspace,
169
+ '/workspace/config',
170
+ { chainOverrides: newOverrides }
171
+ );
172
+ if (data.success && data.config) {
173
+ setChainOverrides(data.config.chainOverrides || {});
174
+ }
175
+ } catch (err) {
176
+ console.error('[AuthContext] Failed to save chain override:', err);
177
+ throw err;
178
+ }
179
+ }, [chainOverrides]);
180
+
181
+ // Remove a chain override
182
+ const removeChainOverride = useCallback(async (chain: string) => {
183
+ const newOverrides = { ...chainOverrides };
184
+ delete newOverrides[chain];
185
+ try {
186
+ const data = await api.post<{ success: boolean; config: { chainOverrides: Record<string, ChainConfig> } }>(
187
+ Api.Workspace,
188
+ '/workspace/config',
189
+ { chainOverrides: newOverrides }
190
+ );
191
+ if (data.success && data.config) {
192
+ setChainOverrides(data.config.chainOverrides || {});
193
+ }
194
+ } catch (err) {
195
+ console.error('[AuthContext] Failed to remove chain override:', err);
196
+ throw err;
197
+ }
198
+ }, [chainOverrides]);
199
+
200
+ const setToken = useCallback((newToken: string | null) => {
201
+ setTokenState(newToken);
202
+ if (typeof window !== 'undefined') {
203
+ if (newToken) {
204
+ sessionStorage.setItem(TOKEN_STORAGE_KEY, newToken);
205
+ } else {
206
+ sessionStorage.removeItem(TOKEN_STORAGE_KEY);
207
+ }
208
+ }
209
+ }, []);
210
+
211
+ const clearToken = useCallback(() => {
212
+ discardVaultKeypair();
213
+ setToken(null);
214
+ }, [setToken]);
215
+
216
+ // Get an API key by service and optional name
217
+ const getApiKey = useCallback((service: string, name?: string): string | null => {
218
+ const key = apiKeys.find(k =>
219
+ k.service.toLowerCase() === service.toLowerCase() &&
220
+ (!name || k.name.toLowerCase() === name.toLowerCase())
221
+ );
222
+ return key?.key || null;
223
+ }, [apiKeys]);
224
+
225
+ // Get RPC URL for a chain: override → Alchemy → public fallback
226
+ const getRpcUrl = useCallback((chain: string = 'base'): string => {
227
+ // 1. Check for custom override
228
+ if (chainOverrides[chain]?.rpc) {
229
+ return chainOverrides[chain].rpc;
230
+ }
231
+
232
+ // 2. Check for Alchemy API key
233
+ const alchemyKey = getApiKey('alchemy');
234
+ const alchemyConfig = ALCHEMY_PATHS[chain];
235
+ if (alchemyKey && alchemyConfig) {
236
+ return `https://${alchemyConfig.path}.g.alchemy.com/v2/${alchemyKey}`;
237
+ }
238
+
239
+ // 3. Fallback to public RPC
240
+ return PUBLIC_RPCS[chain]?.rpc || PUBLIC_RPCS.base.rpc;
241
+ }, [chainOverrides, getApiKey]);
242
+
243
+ // Get full chain config: override → Alchemy → public fallback
244
+ const getChainConfig = useCallback((chain: string = 'base'): ChainConfig => {
245
+ // 1. Check for custom override
246
+ if (chainOverrides[chain]) {
247
+ return chainOverrides[chain];
248
+ }
249
+
250
+ // 2. Check for Alchemy API key
251
+ const alchemyKey = getApiKey('alchemy');
252
+ const alchemyConfig = ALCHEMY_PATHS[chain];
253
+ if (alchemyKey && alchemyConfig) {
254
+ return {
255
+ rpc: `https://${alchemyConfig.path}.g.alchemy.com/v2/${alchemyKey}`,
256
+ chainId: alchemyConfig.chainId,
257
+ explorer: alchemyConfig.explorer,
258
+ };
259
+ }
260
+
261
+ // 3. Fallback to public RPC
262
+ return PUBLIC_RPCS[chain] || PUBLIC_RPCS.base;
263
+ }, [chainOverrides, getApiKey]);
264
+
265
+ // Get all configured chains (merges overrides, Alchemy-supported, and public RPCs)
266
+ // Get all configured chains: public defaults + DB overrides
267
+ // RPC resolution for each: DB custom RPC → Alchemy → public fallback
268
+ const getConfiguredChains = useCallback((): Record<string, ChainConfig> => {
269
+ const result: Record<string, ChainConfig> = {};
270
+ const alchemyKey = getApiKey('alchemy');
271
+
272
+ // Helper to resolve RPC URL for a chain
273
+ const resolveRpc = (chain: string, baseConfig: ChainConfig): string => {
274
+ // 1. If chain has custom RPC in DB, use it
275
+ if (chainOverrides[chain]?.rpc) {
276
+ return chainOverrides[chain].rpc;
277
+ }
278
+ // 2. If Alchemy key exists and chain is supported, use Alchemy
279
+ const alchemyConfig = ALCHEMY_PATHS[chain];
280
+ if (alchemyKey && alchemyConfig) {
281
+ return `https://${alchemyConfig.path}.g.alchemy.com/v2/${alchemyKey}`;
282
+ }
283
+ // 3. Fallback to hardcoded public RPC
284
+ return baseConfig.rpc;
285
+ };
286
+
287
+ // Add public defaults (base, ethereum) with resolved RPCs
288
+ for (const [chain, config] of Object.entries(PUBLIC_RPCS)) {
289
+ result[chain] = {
290
+ ...config,
291
+ rpc: resolveRpc(chain, config),
292
+ };
293
+ }
294
+
295
+ // Add any user-added chains from DB (not in PUBLIC_RPCS)
296
+ for (const [chain, config] of Object.entries(chainOverrides)) {
297
+ if (!PUBLIC_RPCS[chain]) {
298
+ result[chain] = config;
299
+ }
300
+ }
301
+
302
+ return result;
303
+ }, [chainOverrides, getApiKey]);
304
+
305
+ const value: AuthContextValue = {
306
+ token,
307
+ isUnlocked: !!token,
308
+ setToken,
309
+ clearToken,
310
+ apiKeys,
311
+ apiKeysLoading,
312
+ refreshApiKeys,
313
+ getApiKey,
314
+ getRpcUrl,
315
+ getChainConfig,
316
+ chainOverrides,
317
+ chainOverridesLoading,
318
+ refreshChainOverrides,
319
+ saveChainOverride,
320
+ removeChainOverride,
321
+ getConfiguredChains,
322
+ };
323
+
324
+ return (
325
+ <AuthContext.Provider value={value}>
326
+ {children}
327
+ </AuthContext.Provider>
328
+ );
329
+ }
330
+
331
+ export function useAuth(): AuthContextValue {
332
+ const context = useContext(AuthContext);
333
+ if (!context) {
334
+ throw new Error('useAuth must be used within an AuthProvider');
335
+ }
336
+ return context;
337
+ }
@@ -0,0 +1,113 @@
1
+ 'use client';
2
+
3
+ import React, { createContext, useContext, useEffect, useState, useCallback } from 'react';
4
+ import { api, Api } from '@/lib/api';
5
+
6
+ interface PriceContextType {
7
+ ethPrice: number | null;
8
+ solPrice: number | null;
9
+ loading: boolean;
10
+ error: string | null;
11
+ lastUpdated: Date | null;
12
+ refresh: () => void;
13
+ formatUsd: (ethAmount: string | number | undefined) => string;
14
+ formatUsdForChain: (amount: string | number | undefined, chain: string) => string;
15
+ }
16
+
17
+ const PriceContext = createContext<PriceContextType>({
18
+ ethPrice: null,
19
+ solPrice: null,
20
+ loading: true,
21
+ error: null,
22
+ lastUpdated: null,
23
+ refresh: () => {},
24
+ formatUsd: () => '',
25
+ formatUsdForChain: () => '',
26
+ });
27
+
28
+ export const usePrice = () => useContext(PriceContext);
29
+
30
+ const REFRESH_INTERVAL = 60000; // 60 seconds
31
+
32
+ interface PriceResponse {
33
+ success: boolean;
34
+ priceUsd?: string;
35
+ error?: string;
36
+ }
37
+
38
+ function isSolanaChain(chain: string): boolean {
39
+ return chain === 'solana' || chain === 'solana-devnet';
40
+ }
41
+
42
+ export const PriceProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
43
+ const [ethPrice, setEthPrice] = useState<number | null>(null);
44
+ const [solPrice, setSolPrice] = useState<number | null>(null);
45
+ const [loading, setLoading] = useState(true);
46
+ const [error, setError] = useState<string | null>(null);
47
+ const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
48
+
49
+ const fetchAllPrices = useCallback(async () => {
50
+ try {
51
+ const [ethRes, solRes] = await Promise.all([
52
+ api.get<PriceResponse>(Api.Wallet, '/price/native', { chain: 'base' }).catch(() => null),
53
+ api.get<PriceResponse>(Api.Wallet, '/price/native', { chain: 'solana' }).catch(() => null),
54
+ ]);
55
+
56
+ if (ethRes?.success && ethRes.priceUsd) {
57
+ const price = parseFloat(ethRes.priceUsd);
58
+ if (!isNaN(price)) setEthPrice(price);
59
+ }
60
+
61
+ if (solRes?.success && solRes.priceUsd) {
62
+ const price = parseFloat(solRes.priceUsd);
63
+ if (!isNaN(price)) setSolPrice(price);
64
+ }
65
+
66
+ setLastUpdated(new Date());
67
+ setError(null);
68
+ } catch (err) {
69
+ setError(err instanceof Error ? err.message : 'Price fetch failed');
70
+ } finally {
71
+ setLoading(false);
72
+ }
73
+ }, []);
74
+
75
+ useEffect(() => {
76
+ fetchAllPrices();
77
+ const interval = setInterval(fetchAllPrices, REFRESH_INTERVAL);
78
+ return () => clearInterval(interval);
79
+ }, [fetchAllPrices]);
80
+
81
+ const formatAmountToUsd = useCallback((amount: string | number | undefined, price: number | null): string => {
82
+ if (amount === undefined || amount === null || amount === '') return '';
83
+ if (price === null) return '';
84
+
85
+ const numStr = String(amount).replace(/[^0-9.-]/g, '');
86
+ const num = parseFloat(numStr);
87
+
88
+ if (isNaN(num) || num === 0) return '$0';
89
+
90
+ const usd = num * price;
91
+
92
+ if (usd < 0.01) return '<$0.01';
93
+ if (usd < 1) return `$${usd.toFixed(2)}`;
94
+ if (usd < 1000) return `$${usd.toFixed(2)}`;
95
+ if (usd < 10000) return `$${usd.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
96
+ return `$${usd.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`;
97
+ }, []);
98
+
99
+ const formatUsd = useCallback((ethAmount: string | number | undefined): string => {
100
+ return formatAmountToUsd(ethAmount, ethPrice);
101
+ }, [ethPrice, formatAmountToUsd]);
102
+
103
+ const formatUsdForChain = useCallback((amount: string | number | undefined, chain: string): string => {
104
+ const price = isSolanaChain(chain) ? solPrice : ethPrice;
105
+ return formatAmountToUsd(amount, price);
106
+ }, [ethPrice, solPrice, formatAmountToUsd]);
107
+
108
+ return (
109
+ <PriceContext.Provider value={{ ethPrice, solPrice, loading, error, lastUpdated, refresh: fetchAllPrices, formatUsd, formatUsdForChain }}>
110
+ {children}
111
+ </PriceContext.Provider>
112
+ );
113
+ };
@@ -0,0 +1,164 @@
1
+ 'use client';
2
+
3
+ import {
4
+ createContext,
5
+ useContext,
6
+ useEffect,
7
+ useState,
8
+ useCallback,
9
+ ReactNode,
10
+ } from 'react';
11
+ import {
12
+ Theme,
13
+ DEFAULT_LIGHT_THEME,
14
+ getThemeById,
15
+ applyThemeToCSSVariables,
16
+ createThemeWithAccent,
17
+ } from '@/lib/theme';
18
+ import { useWebSocket } from './WebSocketContext';
19
+ import { THEME_EVENTS, createThemeEvent } from '@/lib/events';
20
+
21
+ interface ThemeContextValue {
22
+ theme: Theme;
23
+ mode: 'light' | 'dark';
24
+ accent: string;
25
+ setMode: (mode: 'light' | 'dark') => void;
26
+ setAccent: (accent: string) => void;
27
+ toggleMode: () => void;
28
+ }
29
+
30
+ const ThemeContext = createContext<ThemeContextValue | null>(null);
31
+
32
+ interface ThemeProviderProps {
33
+ children: ReactNode;
34
+ }
35
+
36
+ export function ThemeProvider({ children }: ThemeProviderProps) {
37
+ const { subscribe, send, connected } = useWebSocket();
38
+
39
+ const [mode, setModeState] = useState<'light' | 'dark'>('light');
40
+ const [accent, setAccentState] = useState('#ccff00');
41
+ const [theme, setTheme] = useState<Theme>(DEFAULT_LIGHT_THEME);
42
+ const [initialized, setInitialized] = useState(false);
43
+
44
+ // Request theme from server on connect
45
+ useEffect(() => {
46
+ if (connected && !initialized) {
47
+ const requestId = `theme-init-${Date.now()}`;
48
+ send(
49
+ createThemeEvent(
50
+ THEME_EVENTS.THEME_REQUEST,
51
+ { requestId },
52
+ 'ui'
53
+ )
54
+ );
55
+ }
56
+ }, [connected, initialized, send]);
57
+
58
+ // Subscribe to theme events
59
+ useEffect(() => {
60
+ const unsubscribe = subscribe(THEME_EVENTS.THEME_RESPONSE, (event) => {
61
+ if (event.type === THEME_EVENTS.THEME_RESPONSE) {
62
+ const data = event.data as {
63
+ activeThemeId: string;
64
+ accentColor: string;
65
+ mode: 'light' | 'dark';
66
+ };
67
+ setModeState(data.mode);
68
+ setAccentState(data.accentColor);
69
+ setInitialized(true);
70
+ }
71
+ });
72
+
73
+ return unsubscribe;
74
+ }, [subscribe]);
75
+
76
+ // Subscribe to mode changes from other clients/agents
77
+ useEffect(() => {
78
+ const unsubscribe = subscribe(THEME_EVENTS.THEME_MODE_CHANGED, (event) => {
79
+ if (event.type === THEME_EVENTS.THEME_MODE_CHANGED) {
80
+ const data = event.data as { mode: 'light' | 'dark' };
81
+ setModeState(data.mode);
82
+ }
83
+ });
84
+
85
+ return unsubscribe;
86
+ }, [subscribe]);
87
+
88
+ // Subscribe to accent changes from other clients/agents
89
+ useEffect(() => {
90
+ const unsubscribe = subscribe(THEME_EVENTS.THEME_ACCENT_CHANGED, (event) => {
91
+ if (event.type === THEME_EVENTS.THEME_ACCENT_CHANGED) {
92
+ const data = event.data as { accent: string };
93
+ setAccentState(data.accent);
94
+ }
95
+ });
96
+
97
+ return unsubscribe;
98
+ }, [subscribe]);
99
+
100
+ // Update theme object when mode or accent changes
101
+ useEffect(() => {
102
+ const baseTheme = getThemeById(mode);
103
+ const newTheme = createThemeWithAccent(baseTheme, accent);
104
+ setTheme(newTheme);
105
+ applyThemeToCSSVariables(newTheme);
106
+ }, [mode, accent]);
107
+
108
+ // Set mode and broadcast to other clients
109
+ const setMode = useCallback(
110
+ (newMode: 'light' | 'dark') => {
111
+ setModeState(newMode);
112
+ send(
113
+ createThemeEvent(
114
+ THEME_EVENTS.THEME_MODE_CHANGED,
115
+ { mode: newMode },
116
+ 'ui'
117
+ )
118
+ );
119
+ },
120
+ [send]
121
+ );
122
+
123
+ // Set accent and broadcast to other clients
124
+ const setAccent = useCallback(
125
+ (newAccent: string) => {
126
+ setAccentState(newAccent);
127
+ send(
128
+ createThemeEvent(
129
+ THEME_EVENTS.THEME_ACCENT_CHANGED,
130
+ { accent: newAccent },
131
+ 'ui'
132
+ )
133
+ );
134
+ },
135
+ [send]
136
+ );
137
+
138
+ // Toggle between light and dark mode
139
+ const toggleMode = useCallback(() => {
140
+ const newMode = mode === 'light' ? 'dark' : 'light';
141
+ setMode(newMode);
142
+ }, [mode, setMode]);
143
+
144
+ const value: ThemeContextValue = {
145
+ theme,
146
+ mode,
147
+ accent,
148
+ setMode,
149
+ setAccent,
150
+ toggleMode,
151
+ };
152
+
153
+ return (
154
+ <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
155
+ );
156
+ }
157
+
158
+ export function useTheme(): ThemeContextValue {
159
+ const context = useContext(ThemeContext);
160
+ if (!context) {
161
+ throw new Error('useTheme must be used within a ThemeProvider');
162
+ }
163
+ return context;
164
+ }