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,162 @@
1
+ import { Router, Request, Response } from 'express';
2
+ import { prisma } from '../lib/db';
3
+ import { upsertTokenMetadata } from '../lib/token-metadata';
4
+ import { safeJsonParse } from '../lib/token-search';
5
+ import { requireWalletAuth, optionalWalletAuth } from '../middleware/auth';
6
+ import { isAdmin, buildPermissionDenied } from '../lib/permissions';
7
+ import { getErrorMessage } from '../lib/error';
8
+
9
+ const router = Router();
10
+
11
+ // GET /bookmarks - List all bookmarked tokens (TrackedAsset where walletAddress is null)
12
+ router.get('/', optionalWalletAuth, async (req: Request, res: Response) => {
13
+ try {
14
+ const chain = req.query.chain as string | undefined;
15
+ const q = req.query.q as string | undefined;
16
+
17
+ const where: Record<string, unknown> = {
18
+ walletAddress: null,
19
+ };
20
+
21
+ if (chain) where.chain = chain;
22
+
23
+ if (q) {
24
+ where.OR = [
25
+ { symbol: { contains: q.toUpperCase() } },
26
+ { name: { contains: q.toLowerCase() } },
27
+ { tokenAddress: { contains: q.toLowerCase() } },
28
+ ];
29
+ }
30
+
31
+ const bookmarks = await prisma.trackedAsset.findMany({
32
+ where,
33
+ orderBy: { updatedAt: 'desc' },
34
+ });
35
+
36
+ // Enrich with TokenMetadata if available
37
+ const tokenKeys = bookmarks.map(b => ({ tokenAddress: b.tokenAddress, chain: b.chain }));
38
+ const metadata = tokenKeys.length > 0
39
+ ? await prisma.tokenMetadata.findMany({
40
+ where: {
41
+ OR: tokenKeys.map(k => ({
42
+ tokenAddress: k.tokenAddress,
43
+ chain: k.chain,
44
+ })),
45
+ },
46
+ })
47
+ : [];
48
+
49
+ const metaMap = new Map(metadata.map(m => [`${m.tokenAddress}:${m.chain}`, m]));
50
+
51
+ const enriched = bookmarks.map(b => {
52
+ const meta = metaMap.get(`${b.tokenAddress}:${b.chain}`);
53
+ return {
54
+ ...b,
55
+ symbol: meta?.symbol ?? b.symbol,
56
+ name: meta?.name ?? b.name,
57
+ decimals: meta?.decimals ?? b.decimals,
58
+ icon: meta?.icon ?? b.icon,
59
+ priceUsd: meta?.priceUsd ?? null,
60
+ marketCap: meta?.marketCap ?? null,
61
+ fdv: meta?.fdv ?? null,
62
+ liquidity: meta?.liquidity ?? null,
63
+ volume24h: meta?.volume24h ?? null,
64
+ dexId: meta?.dexId ?? null,
65
+ pairAddress: meta?.pairAddress ?? null,
66
+ websites: meta?.websites ? safeJsonParse(meta.websites, []) : [],
67
+ socials: meta?.socials ? safeJsonParse(meta.socials, []) : [],
68
+ };
69
+ });
70
+
71
+ res.json({ success: true, bookmarks: enriched });
72
+ } catch (error) {
73
+ const message = getErrorMessage(error);
74
+ res.status(400).json({ error: message });
75
+ }
76
+ });
77
+
78
+ // POST /bookmarks - Create a token bookmark
79
+ router.post('/', requireWalletAuth, async (req: Request, res: Response) => {
80
+ try {
81
+ const auth = req.auth!;
82
+
83
+ // Check permission
84
+ if (!isAdmin(auth)) {
85
+ const perms = auth.token.permissions;
86
+ if (!perms.includes('bookmark:write') && !perms.includes('admin:*')) {
87
+ res.status(403).json(buildPermissionDenied('Token does not have bookmark:write permission', ['bookmark:write'], auth.token.permissions));
88
+ return;
89
+ }
90
+ }
91
+
92
+ const { tokenAddress, chain = 'base' } = req.body;
93
+
94
+ if (!tokenAddress) {
95
+ res.status(400).json({ error: 'tokenAddress is required' });
96
+ return;
97
+ }
98
+
99
+ const normalizedToken = tokenAddress.toLowerCase();
100
+
101
+ // Find-or-create bookmark (Prisma can't upsert on compound unique with NULL)
102
+ let bookmark = await prisma.trackedAsset.findFirst({
103
+ where: { walletAddress: null, tokenAddress: normalizedToken, chain },
104
+ });
105
+
106
+ if (bookmark) {
107
+ bookmark = await prisma.trackedAsset.update({
108
+ where: { id: bookmark.id },
109
+ data: { updatedAt: new Date() },
110
+ });
111
+ } else {
112
+ bookmark = await prisma.trackedAsset.create({
113
+ data: {
114
+ walletAddress: null,
115
+ tokenAddress: normalizedToken,
116
+ chain,
117
+ },
118
+ });
119
+ }
120
+
121
+ // Seed TokenMetadata if not cached
122
+ upsertTokenMetadata(normalizedToken, chain);
123
+
124
+ res.json({ success: true, bookmark });
125
+ } catch (error) {
126
+ const message = getErrorMessage(error);
127
+ res.status(400).json({ error: message });
128
+ }
129
+ });
130
+
131
+ // DELETE /bookmarks/:id - Delete a bookmark
132
+ router.delete('/:id', requireWalletAuth, async (req: Request<{ id: string }>, res: Response) => {
133
+ try {
134
+ const auth = req.auth!;
135
+
136
+ // Check permission
137
+ if (!isAdmin(auth)) {
138
+ const perms = auth.token.permissions;
139
+ if (!perms.includes('bookmark:write') && !perms.includes('admin:*')) {
140
+ res.status(403).json(buildPermissionDenied('Token does not have bookmark:write permission', ['bookmark:write'], auth.token.permissions));
141
+ return;
142
+ }
143
+ }
144
+
145
+ const { id } = req.params;
146
+
147
+ const existing = await prisma.trackedAsset.findUnique({ where: { id } });
148
+ if (!existing || existing.walletAddress !== null) {
149
+ res.status(404).json({ error: 'Bookmark not found' });
150
+ return;
151
+ }
152
+
153
+ await prisma.trackedAsset.delete({ where: { id } });
154
+
155
+ res.json({ success: true });
156
+ } catch (error) {
157
+ const message = getErrorMessage(error);
158
+ res.status(400).json({ error: message });
159
+ }
160
+ });
161
+
162
+ export default router;
@@ -0,0 +1,380 @@
1
+ import { Request, Response, Router } from 'express';
2
+ import {
3
+ createCredentialShare,
4
+ consumeCredentialShare,
5
+ getCredentialShare,
6
+ getCredentialShareStatus,
7
+ ShareAccessMode,
8
+ ShareExpiresAfter,
9
+ } from '../lib/credential-shares';
10
+ import { getCredential, isValidCredentialId, readCredentialSecrets } from '../lib/credentials';
11
+ import { hasAnyPermission, isAdmin } from '../lib/permissions';
12
+ import { matchesScope } from '../lib/credential-scope';
13
+ import { CredentialFile } from '../types';
14
+ import { requireWalletAuth } from '../middleware/auth';
15
+ import { getErrorMessage } from '../lib/error';
16
+ import { createSecretGist, SecretGistError } from '../lib/secret-gist-share';
17
+
18
+ const router = Router();
19
+
20
+ const SHARE_EXPIRY_OPTIONS = new Set<ShareExpiresAfter>(['15m', '1h', '24h', '7d', '30d']);
21
+
22
+ class ShareRequestError extends Error {
23
+ status: number;
24
+
25
+ constructor(status: number, message: string) {
26
+ super(message);
27
+ this.name = 'ShareRequestError';
28
+ this.status = status;
29
+ }
30
+ }
31
+
32
+ function canReadCredential(req: Request, credential: CredentialFile): boolean {
33
+ const auth = req.auth!;
34
+ if (isAdmin(auth)) return true;
35
+ if (!hasAnyPermission(auth.token.permissions, ['secret:read'])) return false;
36
+ const scopes = auth.token.credentialAccess?.read || [];
37
+ return matchesScope(credential, scopes);
38
+ }
39
+
40
+ function parseShareBaseUrl(raw: unknown): string | null {
41
+ if (typeof raw !== 'string') return null;
42
+ const trimmed = raw.trim();
43
+ if (!trimmed) return null;
44
+ try {
45
+ const parsed = new URL(trimmed);
46
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return null;
47
+ return trimmed.replace(/\/+$/, '');
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+
53
+ function resolveFallbackShareUrl(req: Request, shareToken: string): string {
54
+ const forwardedProto = req.headers['x-forwarded-proto'];
55
+ const protocol = typeof forwardedProto === 'string'
56
+ ? forwardedProto.split(',')[0].trim()
57
+ : req.protocol || 'http';
58
+
59
+ const forwardedHost = req.headers['x-forwarded-host'];
60
+ const host = typeof forwardedHost === 'string'
61
+ ? forwardedHost.split(',')[0].trim()
62
+ : req.get('host');
63
+
64
+ if (!host) return `/credential-shares/${shareToken}`;
65
+ return `${protocol}://${host}/credential-shares/${shareToken}`;
66
+ }
67
+
68
+ function resolveShareUrl(req: Request, shareToken: string, shareBaseUrl: unknown): string {
69
+ const base = parseShareBaseUrl(shareBaseUrl);
70
+ if (base) {
71
+ return `${base}/share/${shareToken}`;
72
+ }
73
+ return resolveFallbackShareUrl(req, shareToken);
74
+ }
75
+
76
+ function toMetaFieldValue(raw: unknown): string | null {
77
+ if (raw === null || raw === undefined) return null;
78
+ if (typeof raw === 'string') return raw;
79
+ if (typeof raw === 'number' || typeof raw === 'boolean') return String(raw);
80
+ if (Array.isArray(raw)) {
81
+ const parts = raw
82
+ .map((item) => {
83
+ if (item === null || item === undefined) return '';
84
+ if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') return String(item);
85
+ try {
86
+ return JSON.stringify(item);
87
+ } catch {
88
+ return '';
89
+ }
90
+ })
91
+ .map((value) => value.trim())
92
+ .filter(Boolean);
93
+ return parts.join(',');
94
+ }
95
+ try {
96
+ return JSON.stringify(raw);
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+
102
+ function buildPlaintextGistFields(
103
+ credential: CredentialFile,
104
+ ): Array<{ key: string; value: string; sensitive?: boolean }> {
105
+ const fields: Array<{ key: string; value: string; sensitive?: boolean }> = [];
106
+ const seenKeys = new Set<string>();
107
+
108
+ for (const field of readCredentialSecrets(credential.id)) {
109
+ const key = String(field.key || '').trim();
110
+ const value = String(field.value || '').trim();
111
+ if (!key || !value) continue;
112
+ const normalized = key.toLowerCase();
113
+ if (seenKeys.has(normalized)) continue;
114
+ seenKeys.add(normalized);
115
+ fields.push({ key, value, sensitive: field.sensitive !== false });
116
+ }
117
+
118
+ for (const [key, value] of Object.entries(credential.meta || {})) {
119
+ const normalized = key.toLowerCase();
120
+ if (seenKeys.has(normalized)) continue;
121
+ const serialized = toMetaFieldValue(value);
122
+ if (!serialized || serialized.trim().length === 0) continue;
123
+ seenKeys.add(normalized);
124
+ fields.push({ key, value: serialized, sensitive: false });
125
+ }
126
+
127
+ return fields;
128
+ }
129
+
130
+ function parseShareRequest(req: Request): {
131
+ credential: CredentialFile;
132
+ credentialId: string;
133
+ expiresAfter: ShareExpiresAfter;
134
+ accessMode: ShareAccessMode;
135
+ oneTimeOnly: boolean;
136
+ password?: string;
137
+ createdBy: string;
138
+ } {
139
+ const auth = req.auth!;
140
+ if (!isAdmin(auth) && !hasAnyPermission(auth.token.permissions, ['secret:read'])) {
141
+ throw new ShareRequestError(403, 'secret:read permission required');
142
+ }
143
+
144
+ const credentialId = typeof req.body?.credentialId === 'string' ? req.body.credentialId.trim() : '';
145
+ if (!credentialId) {
146
+ throw new ShareRequestError(400, 'credentialId is required');
147
+ }
148
+ if (!isValidCredentialId(credentialId)) {
149
+ throw new ShareRequestError(400, 'credentialId format is invalid');
150
+ }
151
+
152
+ const credential = getCredential(credentialId);
153
+ if (!credential) {
154
+ throw new ShareRequestError(404, 'Credential not found');
155
+ }
156
+ if (!canReadCredential(req, credential)) {
157
+ throw new ShareRequestError(403, 'Credential read scope denied');
158
+ }
159
+
160
+ const expiresAfterRaw = typeof req.body?.expiresAfter === 'string' ? req.body.expiresAfter : '24h';
161
+ const expiresAfter = expiresAfterRaw as ShareExpiresAfter;
162
+ if (!SHARE_EXPIRY_OPTIONS.has(expiresAfter)) {
163
+ throw new ShareRequestError(400, 'expiresAfter must be one of: 15m, 1h, 24h, 7d, 30d');
164
+ }
165
+
166
+ const accessModeRaw = typeof req.body?.accessMode === 'string' ? req.body.accessMode : 'anyone';
167
+ const accessMode = accessModeRaw as ShareAccessMode;
168
+ if (accessMode !== 'anyone' && accessMode !== 'password') {
169
+ throw new ShareRequestError(400, 'accessMode must be either "anyone" or "password"');
170
+ }
171
+
172
+ const oneTimeOnly = req.body?.oneTimeOnly === true;
173
+ const password = typeof req.body?.password === 'string' ? req.body.password : undefined;
174
+ if (accessMode === 'password' && (!password || password.length === 0)) {
175
+ throw new ShareRequestError(400, 'password is required when accessMode is "password"');
176
+ }
177
+
178
+ return {
179
+ credential,
180
+ credentialId,
181
+ expiresAfter,
182
+ accessMode,
183
+ oneTimeOnly,
184
+ password,
185
+ createdBy: isAdmin(auth) ? 'admin' : auth.token.agentId,
186
+ };
187
+ }
188
+
189
+ function toShareResponse(share: ReturnType<typeof createCredentialShare>) {
190
+ return {
191
+ token: share.token,
192
+ credentialId: share.credentialId,
193
+ expiresAt: share.expiresAt,
194
+ accessMode: share.accessMode,
195
+ oneTimeOnly: share.oneTimeOnly,
196
+ };
197
+ }
198
+
199
+ // GET /credential-shares/:token - public share metadata
200
+ router.get('/:token', (req: Request<{ token: string }>, res: Response) => {
201
+ const share = getCredentialShare(req.params.token);
202
+ if (!share) {
203
+ res.status(404).json({ success: false, error: 'Share link not found' });
204
+ return;
205
+ }
206
+
207
+ const credential = getCredential(share.credentialId);
208
+ if (!credential) {
209
+ res.status(410).json({ success: false, error: 'Shared credential no longer exists', reason: 'credential_missing' });
210
+ return;
211
+ }
212
+
213
+ const status = getCredentialShareStatus(share);
214
+ if (status.isExpired) {
215
+ res.status(410).json({ success: false, error: 'Share link expired', reason: 'expired' });
216
+ return;
217
+ }
218
+ if (status.isAlreadyViewed) {
219
+ res.status(410).json({ success: false, error: 'Share link already used', reason: 'already_viewed' });
220
+ return;
221
+ }
222
+
223
+ res.json({
224
+ success: true,
225
+ share: {
226
+ token: share.token,
227
+ credentialId: share.credentialId,
228
+ credentialName: credential.name,
229
+ credentialType: credential.type,
230
+ expiresAt: share.expiresAt,
231
+ accessMode: share.accessMode,
232
+ passwordRequired: share.accessMode === 'password',
233
+ oneTimeOnly: share.oneTimeOnly,
234
+ viewCount: share.viewCount,
235
+ maxViews: share.oneTimeOnly ? 1 : null,
236
+ },
237
+ });
238
+ });
239
+
240
+ // POST /credential-shares/:token/read - public shared credential read
241
+ router.post('/:token/read', (req: Request<{ token: string }>, res: Response) => {
242
+ const password = typeof req.body?.password === 'string' ? req.body.password : undefined;
243
+ const result = consumeCredentialShare(req.params.token, password);
244
+
245
+ if (!result.ok) {
246
+ if (result.reason === 'not_found') {
247
+ res.status(404).json({ success: false, error: 'Share link not found', reason: 'not_found' });
248
+ return;
249
+ }
250
+ if (result.reason === 'expired') {
251
+ res.status(410).json({ success: false, error: 'Share link expired', reason: 'expired' });
252
+ return;
253
+ }
254
+ if (result.reason === 'already_viewed') {
255
+ res.status(410).json({ success: false, error: 'Share link already used', reason: 'already_viewed' });
256
+ return;
257
+ }
258
+ if (result.reason === 'password_required') {
259
+ res.status(401).json({ success: false, error: 'Share password required', reason: 'password_required' });
260
+ return;
261
+ }
262
+ res.status(401).json({ success: false, error: 'Invalid share password', reason: 'invalid_password' });
263
+ return;
264
+ }
265
+
266
+ const credential = getCredential(result.share.credentialId);
267
+ if (!credential) {
268
+ res.status(410).json({ success: false, error: 'Shared credential no longer exists', reason: 'credential_missing' });
269
+ return;
270
+ }
271
+
272
+ try {
273
+ const fields = readCredentialSecrets(credential.id);
274
+ res.json({
275
+ success: true,
276
+ credential: {
277
+ id: credential.id,
278
+ name: credential.name,
279
+ type: credential.type,
280
+ meta: credential.meta,
281
+ fields,
282
+ createdAt: credential.createdAt,
283
+ updatedAt: credential.updatedAt,
284
+ },
285
+ });
286
+ } catch (error) {
287
+ const message = getErrorMessage(error);
288
+ if (message.toLowerCase().includes('locked')) {
289
+ res.status(423).json({ success: false, error: 'Vault is locked', reason: 'vault_locked' });
290
+ return;
291
+ }
292
+ res.status(500).json({ success: false, error: message });
293
+ }
294
+ });
295
+
296
+ router.use(requireWalletAuth);
297
+
298
+ // POST /credential-shares/gist - create share + publish secret gist
299
+ router.post('/gist', async (req: Request, res: Response) => {
300
+ try {
301
+ const parsed = parseShareRequest(req);
302
+ const share = createCredentialShare({
303
+ credentialId: parsed.credentialId,
304
+ createdBy: parsed.createdBy,
305
+ expiresAfter: parsed.expiresAfter,
306
+ accessMode: parsed.accessMode,
307
+ password: parsed.password,
308
+ oneTimeOnly: parsed.oneTimeOnly,
309
+ });
310
+
311
+ const link = resolveShareUrl(req, share.token, req.body?.shareBaseUrl);
312
+ const fields = buildPlaintextGistFields(parsed.credential);
313
+ const gist = await createSecretGist({
314
+ credentialId: parsed.credential.id,
315
+ credentialName: parsed.credential.name,
316
+ credentialType: parsed.credential.type,
317
+ shareUrl: link,
318
+ accessMode: share.accessMode,
319
+ oneTimeOnly: share.oneTimeOnly,
320
+ expiresAfter: parsed.expiresAfter,
321
+ fields,
322
+ });
323
+
324
+ res.json({
325
+ success: true,
326
+ gist: {
327
+ url: gist.url,
328
+ marker: gist.marker,
329
+ identifier: gist.identifier,
330
+ title: gist.title,
331
+ },
332
+ share: toShareResponse(share),
333
+ link,
334
+ });
335
+ } catch (error) {
336
+ if (error instanceof ShareRequestError) {
337
+ res.status(error.status).json({ success: false, error: error.message });
338
+ return;
339
+ }
340
+ if (error instanceof SecretGistError) {
341
+ res.status(400).json({
342
+ success: false,
343
+ error: error.message,
344
+ code: error.code,
345
+ remediation: error.remediation,
346
+ detail: error.detail,
347
+ });
348
+ return;
349
+ }
350
+ res.status(400).json({ success: false, error: getErrorMessage(error) });
351
+ }
352
+ });
353
+
354
+ // POST /credential-shares - create a share link
355
+ router.post('/', (req: Request, res: Response) => {
356
+ try {
357
+ const parsed = parseShareRequest(req);
358
+ const share = createCredentialShare({
359
+ credentialId: parsed.credentialId,
360
+ createdBy: parsed.createdBy,
361
+ expiresAfter: parsed.expiresAfter,
362
+ accessMode: parsed.accessMode,
363
+ password: parsed.password,
364
+ oneTimeOnly: parsed.oneTimeOnly,
365
+ });
366
+
367
+ res.json({
368
+ success: true,
369
+ share: toShareResponse(share),
370
+ });
371
+ } catch (error) {
372
+ if (error instanceof ShareRequestError) {
373
+ res.status(error.status).json({ success: false, error: error.message });
374
+ return;
375
+ }
376
+ res.status(400).json({ success: false, error: getErrorMessage(error) });
377
+ }
378
+ });
379
+
380
+ export default router;
@@ -0,0 +1,159 @@
1
+ import { Request, Response, Router } from 'express';
2
+ import { createVault, deleteVault, getPrimaryVaultId, getPrimaryVaultPassword, listVaults, lockVault, type VaultMode } from '../lib/cold';
3
+ import { deleteCredential, listCredentials } from '../lib/credentials';
4
+ import { requireAdmin } from '../lib/permissions';
5
+ import { requireWalletAuth } from '../middleware/auth';
6
+ import { parseEncryptedPassword } from '../lib/transport';
7
+ import { HttpError, getErrorMessage } from '../lib/error';
8
+
9
+ const router = Router();
10
+
11
+ function resolvePassword(body: Record<string, unknown>): string {
12
+ const encrypted = body.encrypted;
13
+
14
+ if (typeof encrypted === 'string') {
15
+ return parseEncryptedPassword(encrypted);
16
+ }
17
+
18
+ throw new HttpError(400, 'Encrypted password is required');
19
+ }
20
+
21
+ function resolveVaultMode(body: Record<string, unknown>): Exclude<VaultMode, 'primary'> {
22
+ const mode = body.mode;
23
+ if (mode === undefined) return 'linked';
24
+ if (mode === 'linked' || mode === 'independent') return mode;
25
+ throw new HttpError(400, 'mode must be either "linked" or "independent"');
26
+ }
27
+
28
+ function resolveParentTarget(body: Record<string, unknown>): string | undefined {
29
+ const parentVaultId = body.parentVaultId;
30
+ if (typeof parentVaultId === 'string') {
31
+ const trimmed = parentVaultId.trim();
32
+ if (trimmed) return trimmed;
33
+ } else if (parentVaultId !== undefined && parentVaultId !== null && parentVaultId !== '') {
34
+ throw new HttpError(400, 'parentVaultId must be a string when provided');
35
+ }
36
+
37
+ // Backward compatibility: accept legacy linkedTo input.
38
+ const linkedTo = body.linkedTo;
39
+ if (linkedTo === undefined || linkedTo === null || linkedTo === '') return undefined;
40
+ if (typeof linkedTo === 'string') return linkedTo.trim();
41
+ throw new HttpError(400, 'linkedTo must be a string when provided');
42
+ }
43
+
44
+ // POST /vaults/credential — create credential vault (admin)
45
+ router.post('/', requireWalletAuth, requireAdmin, (req: Request, res: Response) => {
46
+ try {
47
+ const body = req.body as Record<string, unknown>;
48
+ const mode = resolveVaultMode(body);
49
+ const name = typeof req.body?.name === 'string' ? req.body.name.trim() : undefined;
50
+ const parentVaultId = resolveParentTarget(body);
51
+ if (mode === 'independent' && parentVaultId) {
52
+ throw new HttpError(400, 'independent vaults cannot set parentVaultId/linkedTo');
53
+ }
54
+
55
+ let password: string;
56
+ if (mode === 'linked') {
57
+ const primaryPassword = getPrimaryVaultPassword();
58
+ if (!primaryPassword) {
59
+ throw new HttpError(401, 'Primary vault must be unlocked to create linked vaults');
60
+ }
61
+ password = primaryPassword;
62
+ } else {
63
+ password = resolvePassword(body);
64
+ }
65
+
66
+ const created = createVault(password, name, { mode, parentVaultId });
67
+
68
+ res.json({
69
+ success: true,
70
+ vault: {
71
+ id: created.id,
72
+ name: created.name,
73
+ address: created.address,
74
+ solanaAddress: created.solanaAddress,
75
+ mode: created.mode,
76
+ parentVaultId: created.parentVaultId,
77
+ linkedTo: created.linkedTo,
78
+ isPrimary: false,
79
+ isUnlocked: true,
80
+ },
81
+ });
82
+ } catch (error) {
83
+ if (error instanceof HttpError) {
84
+ res.status(error.status).json({ success: false, error: error.message });
85
+ return;
86
+ }
87
+ res.status(400).json({ success: false, error: getErrorMessage(error) });
88
+ }
89
+ });
90
+
91
+ // GET /vaults/credential — list vaults + unlock status (admin)
92
+ router.get('/', requireWalletAuth, requireAdmin, (_req: Request, res: Response) => {
93
+ try {
94
+ const vaults = listVaults();
95
+ const counts = new Map<string, number>();
96
+ for (const cred of listCredentials()) {
97
+ counts.set(cred.vaultId, (counts.get(cred.vaultId) || 0) + 1);
98
+ }
99
+
100
+ res.json({
101
+ success: true,
102
+ vaults: vaults.map(vault => ({
103
+ ...vault,
104
+ credentialCount: counts.get(vault.id) || 0,
105
+ })),
106
+ });
107
+ } catch (error) {
108
+ res.status(500).json({ success: false, error: getErrorMessage(error) });
109
+ }
110
+ });
111
+
112
+ // POST /vaults/credential/:id/lock — lock vault (admin)
113
+ router.post('/:id/lock', requireWalletAuth, requireAdmin, (req: Request<{ id: string }>, res: Response) => {
114
+ try {
115
+ const { id } = req.params;
116
+ const exists = listVaults().some(vault => vault.id === id);
117
+ if (!exists) {
118
+ res.status(404).json({ success: false, error: 'Vault not found' });
119
+ return;
120
+ }
121
+
122
+ lockVault(id);
123
+ res.json({ success: true, message: `Vault ${id} locked` });
124
+ } catch (error) {
125
+ res.status(500).json({ success: false, error: getErrorMessage(error) });
126
+ }
127
+ });
128
+
129
+ // DELETE /vaults/credential/:id — delete vault + credentials (admin)
130
+ router.delete('/:id', requireWalletAuth, requireAdmin, (req: Request<{ id: string }>, res: Response) => {
131
+ try {
132
+ const { id } = req.params;
133
+ const exists = listVaults().some(vault => vault.id === id);
134
+ if (!exists) {
135
+ res.status(404).json({ success: false, error: 'Vault not found' });
136
+ return;
137
+ }
138
+ if (id === getPrimaryVaultId()) {
139
+ res.status(400).json({ success: false, error: 'Cannot delete primary vault from this endpoint' });
140
+ return;
141
+ }
142
+
143
+ const credentials = listCredentials({ vaultId: id });
144
+ for (const credential of credentials) {
145
+ deleteCredential(credential.id);
146
+ }
147
+ deleteVault(id);
148
+
149
+ res.json({
150
+ success: true,
151
+ message: `Vault ${id} deleted`,
152
+ deletedCredentials: credentials.length,
153
+ });
154
+ } catch (error) {
155
+ res.status(500).json({ success: false, error: getErrorMessage(error) });
156
+ }
157
+ });
158
+
159
+ export default router;