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,247 @@
1
+ /**
2
+ * App Token Registry
3
+ * =====================
4
+ * Central registry for all app tokens. Creates tokens on server start,
5
+ * provides token lookup for strategy engine and REST endpoints.
6
+ *
7
+ * Token lifecycle:
8
+ * Server start → createAppTokens() for all installed apps
9
+ * Approve flow → createAppToken(id) creates/replaces token
10
+ * Revoke flow → revokeAppToken(id) removes token
11
+ * Strategy enable → getAppToken(id) reads from registry
12
+ */
13
+
14
+ import fs from 'fs';
15
+ import path from 'path';
16
+ import { parse as parseYaml } from 'yaml';
17
+ import { createToken, getTokenHash } from './auth';
18
+ import { setToken, clearToken } from './strategy/state';
19
+ import { revokeToken } from './sessions';
20
+ import { getDefaultSync, onDefaultChanged } from './defaults';
21
+ import { prisma } from './db';
22
+
23
+ /** appId → raw token */
24
+ const tokens = new Map<string, string>();
25
+
26
+ /** appId → token hash */
27
+ const tokenHashes = new Map<string, string>();
28
+
29
+ interface AppManifest {
30
+ id: string;
31
+ permissions: string[];
32
+ limits?: { fund?: number; send?: number };
33
+ sources?: Array<{ key?: string }>;
34
+ }
35
+
36
+ /**
37
+ * Load all app manifests from apps/ directory.
38
+ * Returns ALL apps (not just strategies with ticker/jobs).
39
+ */
40
+ function loadAllAppManifests(): AppManifest[] {
41
+ const appsDir = path.join(process.cwd(), 'apps');
42
+ if (!fs.existsSync(appsDir)) return [];
43
+
44
+ const apps: AppManifest[] = [];
45
+
46
+ for (const entry of fs.readdirSync(appsDir, { withFileTypes: true })) {
47
+ if (!entry.isDirectory()) continue;
48
+ const mdPath = path.join(appsDir, entry.name, 'app.md');
49
+ if (!fs.existsSync(mdPath)) continue;
50
+
51
+ const raw = fs.readFileSync(mdPath, 'utf-8');
52
+ const match = raw.match(/^---\n([\s\S]*?)\n---/);
53
+ if (!match) continue;
54
+
55
+ let manifest: Record<string, unknown>;
56
+ try {
57
+ manifest = parseYaml(match[1]);
58
+ } catch (err) {
59
+ console.error(`[app-tokens] Failed to parse ${mdPath}:`, err);
60
+ continue;
61
+ }
62
+
63
+ if (!manifest) continue;
64
+
65
+ apps.push({
66
+ id: entry.name,
67
+ permissions: (manifest.permissions as string[]) || [],
68
+ limits: manifest.limits as AppManifest['limits'],
69
+ sources: manifest.sources as AppManifest['sources'],
70
+ });
71
+ }
72
+
73
+ return apps;
74
+ }
75
+
76
+ /**
77
+ * Create tokens for all installed apps on startup.
78
+ * - Apps with permissions/limits → require HumanAction approval → token with approved perms + app:storage
79
+ * - Apps without declared permissions/limits → token uses default permissions
80
+ * - Needs approval but missing → no token (log warning)
81
+ */
82
+ export async function createAppTokens(): Promise<void> {
83
+ const manifests = loadAllAppManifests();
84
+
85
+ // Register built-in __system_chat__ app (system chat widget needs admin permissions)
86
+ manifests.push({
87
+ id: '__system_chat__',
88
+ permissions: ['admin:*'],
89
+ });
90
+
91
+ if (manifests.length === 0) return;
92
+
93
+ // Load all approvals
94
+ const approvals = await prisma.humanAction.findMany({ where: { type: 'app:approve', status: 'approved' } });
95
+ const approvalMap = new Map(approvals.map(a => {
96
+ try {
97
+ const meta = JSON.parse(a.metadata || '{}');
98
+ return [meta.appId as string, { permissions: meta.permissions, limits: meta.limits }] as const;
99
+ } catch { return null; }
100
+ }).filter((x): x is [string, { permissions: unknown; limits: unknown }] => x !== null));
101
+
102
+ for (const manifest of manifests) {
103
+ const hasPermissionsOrLimits = manifest.permissions.length > 0 || manifest.limits;
104
+
105
+ if (hasPermissionsOrLimits) {
106
+ const approval = approvalMap.get(manifest.id);
107
+ if (!approval) {
108
+ console.warn(`[app-tokens] ${manifest.id}: needs approval, skipping token creation`);
109
+ continue;
110
+ }
111
+
112
+ try {
113
+ const approvedPermissions = (approval.permissions || []) as string[];
114
+ const approvedLimits = approval.limits as { fund?: number; send?: number } | undefined;
115
+ await createAppToken(manifest.id, approvedPermissions, manifest, approvedLimits);
116
+ } catch (err) {
117
+ console.error(`[app-tokens] ${manifest.id}: failed to create token:`, err);
118
+ }
119
+ } else {
120
+ // App without declared permissions/limits
121
+ try {
122
+ await createAppToken(manifest.id, [], manifest);
123
+ } catch (err) {
124
+ console.error(`[app-tokens] ${manifest.id}: failed to create minimal token:`, err);
125
+ }
126
+ }
127
+ }
128
+
129
+ console.log(`[app-tokens] Created tokens for ${tokens.size}/${manifests.length} apps`);
130
+ }
131
+
132
+ /**
133
+ * Create or replace token for a single app.
134
+ * Returns the token string, or null if creation failed.
135
+ */
136
+ export async function createAppToken(
137
+ appId: string,
138
+ permissions?: string[],
139
+ manifest?: AppManifest,
140
+ limits?: { fund?: number; send?: number },
141
+ ): Promise<string | null> {
142
+ // If no permissions provided, look up approval
143
+ if (!permissions) {
144
+ const approvalRecords = await prisma.humanAction.findMany({
145
+ where: { type: 'app:approve', status: 'approved' },
146
+ });
147
+ const approval = approvalRecords.find(a => {
148
+ try { return JSON.parse(a.metadata || '{}').appId === appId; } catch { return false; }
149
+ });
150
+ if (approval) {
151
+ try {
152
+ const meta = JSON.parse(approval.metadata || '{}');
153
+ permissions = (meta.permissions || []) as string[];
154
+ limits = meta.limits as { fund?: number; send?: number } | undefined;
155
+ } catch {
156
+ permissions = [];
157
+ }
158
+ } else {
159
+ permissions = [];
160
+ }
161
+ }
162
+
163
+ // agent-chat: respect permission tier setting
164
+ if (appId === 'agent-chat') {
165
+ const tier = getDefaultSync<string>('permissions.agent_tier', 'admin');
166
+ if (tier === 'admin') {
167
+ permissions = ['admin:*'];
168
+ }
169
+ // else: keep declared permissions (wallet:list, action:create)
170
+ }
171
+
172
+ // Apps that omit permissions inherit system defaults.
173
+ if (!permissions || permissions.length === 0) {
174
+ permissions = getDefaultSync<string[]>(
175
+ 'permissions.default',
176
+ ['wallet:create:hot', 'send:hot', 'swap', 'fund', 'action:create'],
177
+ );
178
+ }
179
+
180
+ // Always include app:storage
181
+ const allPerms = new Set(permissions);
182
+ allPerms.add('app:storage');
183
+
184
+ // Auto-add app:accesskey if any source uses a key field
185
+ if (manifest?.sources?.some(s => s.key)) {
186
+ allPerms.add('app:accesskey');
187
+ }
188
+
189
+ try {
190
+ const appTtl = getDefaultSync<number>('ttl.app', 86400);
191
+ const token = await createToken(
192
+ `app:${appId}`,
193
+ limits?.fund || 0,
194
+ Array.from(allPerms),
195
+ appTtl,
196
+ { limits },
197
+ );
198
+
199
+ const hash = getTokenHash(token);
200
+ tokens.set(appId, token);
201
+ tokenHashes.set(appId, hash);
202
+ setToken(appId, token);
203
+
204
+ console.log(`[app-tokens] ${appId}: token created (hash=${hash.slice(0, 8)}...)`);
205
+ return token;
206
+ } catch (err) {
207
+ console.error(`[app-tokens] ${appId}: createToken failed:`, err);
208
+ return null;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Revoke token for a app.
214
+ * Removes from registry AND adds to revokedTokens set so the token
215
+ * can no longer be used for API calls.
216
+ */
217
+ export async function revokeAppToken(appId: string): Promise<void> {
218
+ const hash = tokenHashes.get(appId);
219
+ tokens.delete(appId);
220
+ tokenHashes.delete(appId);
221
+ clearToken(appId);
222
+ if (hash) {
223
+ await revokeToken(hash);
224
+ }
225
+ console.log(`[app-tokens] ${appId}: token revoked`);
226
+ }
227
+
228
+ /**
229
+ * Get existing token for a app.
230
+ */
231
+ export function getAppToken(appId: string): string | undefined {
232
+ return tokens.get(appId);
233
+ }
234
+
235
+ /**
236
+ * Get token hash for a app.
237
+ */
238
+ export function getAppTokenHash(appId: string): string | undefined {
239
+ return tokenHashes.get(appId);
240
+ }
241
+
242
+ // ─── Tier Change Listener ────────────────────────────────────────────
243
+ // When agent tier changes, revoke + recreate the agent-chat app token
244
+ onDefaultChanged('permissions.agent_tier', async () => {
245
+ await revokeAppToken('agent-chat');
246
+ await createAppToken('agent-chat');
247
+ });
@@ -0,0 +1,314 @@
1
+ import { randomBytes, createHmac, timingSafeEqual } from 'crypto';
2
+ import { AgentTokenPayload } from '../types';
3
+ import { registerToken, revokeToken } from './sessions';
4
+ import { expandPermissions } from './permissions';
5
+ import { getDefaultSync } from './defaults';
6
+
7
+ // Random 32-byte signing key generated on startup - never persisted
8
+ const SIGNING_KEY = randomBytes(32);
9
+
10
+ // Track admin token hashes for revocation on lock
11
+ const adminTokenHashes: Set<string> = new Set();
12
+
13
+ // Re-export types for convenience
14
+ export type { AgentTokenPayload };
15
+
16
+ /**
17
+ * Create an admin token for UI access
18
+ * Admin tokens are regular tokens with admin:* permission
19
+ * Multiple admin tokens can exist simultaneously
20
+ */
21
+ export async function createAdminToken(agentPubkey: string): Promise<string> {
22
+ const token = await createToken(
23
+ 'admin',
24
+ 0, // No spending limit for admin
25
+ ['admin:*'],
26
+ getDefaultSync<number>('ttl.admin', 604800), // Default 7 day TTL (effectively until lock/restart)
27
+ { agentPubkey }
28
+ );
29
+
30
+ adminTokenHashes.add(getTokenHash(token));
31
+ return token;
32
+ }
33
+
34
+ /**
35
+ * Revoke all admin tokens (called on lock)
36
+ */
37
+ export function revokeAdminTokens(): void {
38
+ for (const hash of adminTokenHashes) {
39
+ revokeToken(hash);
40
+ }
41
+ adminTokenHashes.clear();
42
+ }
43
+
44
+ /**
45
+ * Get admin token hashes (for testing/logging)
46
+ */
47
+ export function getAdminTokenHashes(): string[] {
48
+ return Array.from(adminTokenHashes);
49
+ }
50
+
51
+ /**
52
+ * Create a signed approval token for an agent
53
+ * Also registers it in memory + DB for tracking
54
+ */
55
+ export async function createToken(
56
+ agentId: string,
57
+ limit: number,
58
+ permissions: string[],
59
+ ttlSeconds: number = 3600,
60
+ options?: {
61
+ limits?: AgentTokenPayload['limits'];
62
+ walletAccess?: string[];
63
+ credentialAccess?: AgentTokenPayload['credentialAccess'];
64
+ agentPubkey?: string;
65
+ }
66
+ ): Promise<string> {
67
+ // Expand permissions for consistency
68
+ const expandedPermissions = expandPermissions(permissions);
69
+
70
+ const payload: AgentTokenPayload = {
71
+ agentId,
72
+ permissions: expandedPermissions,
73
+ exp: Date.now() + ttlSeconds * 1000,
74
+ iat: Date.now(),
75
+ // Legacy limit field for backward compatibility
76
+ limit,
77
+ };
78
+
79
+ // Add per-permission limits if provided, otherwise use legacy limit for fund
80
+ if (options?.limits) {
81
+ payload.limits = options.limits;
82
+ } else if (limit > 0) {
83
+ // Map legacy limit to fund limit
84
+ payload.limits = { fund: limit };
85
+ }
86
+
87
+ // Add wallet access grants if provided
88
+ if (options?.walletAccess && options.walletAccess.length > 0) {
89
+ payload.walletAccess = options.walletAccess.map(addr => addr.toLowerCase());
90
+ }
91
+
92
+ // Add credential access — use explicit value, or populate from defaults for secret:* tokens
93
+ if (options?.credentialAccess) {
94
+ payload.credentialAccess = options.credentialAccess;
95
+ } else if (
96
+ expandedPermissions.includes('secret:read' as never) ||
97
+ expandedPermissions.includes('secret:write' as never)
98
+ ) {
99
+ payload.credentialAccess = {
100
+ read: getDefaultSync<string[]>('defaults.credential.access.read', ['*']),
101
+ write: getDefaultSync<string[]>('defaults.credential.access.write', ['*']),
102
+ excludeFields: ['password', 'cvv'], // Exclude sensitive fields by default
103
+ };
104
+ }
105
+
106
+ // Add agent public key if provided
107
+ if (options?.agentPubkey) {
108
+ payload.agentPubkey = options.agentPubkey;
109
+ }
110
+
111
+ const payloadStr = Buffer.from(JSON.stringify(payload)).toString('base64url');
112
+ const signature = createHmac('sha256', SIGNING_KEY)
113
+ .update(payloadStr)
114
+ .digest('base64url');
115
+
116
+ const token = `${payloadStr}.${signature}`;
117
+
118
+ // Register token in memory + DB
119
+ const tokenHash = getTokenHash(token);
120
+ await registerToken(tokenHash, payload);
121
+
122
+ return token;
123
+ }
124
+
125
+ /**
126
+ * Validate a signed token and return the payload if valid
127
+ * Works for both agent tokens and admin tokens (admin is just a permission)
128
+ */
129
+ export function validateToken(token: string): AgentTokenPayload | null {
130
+ const parts = token.split('.');
131
+ if (parts.length !== 2) {
132
+ return null;
133
+ }
134
+
135
+ const [payloadStr, signature] = parts;
136
+
137
+ // Verify signature
138
+ const expectedSignature = createHmac('sha256', SIGNING_KEY)
139
+ .update(payloadStr)
140
+ .digest('base64url');
141
+
142
+ const sigBuf = Buffer.from(signature, 'base64url');
143
+ const expectedBuf = Buffer.from(expectedSignature, 'base64url');
144
+ if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) {
145
+ return null;
146
+ }
147
+
148
+ // Parse and validate payload
149
+ try {
150
+ const payload = JSON.parse(
151
+ Buffer.from(payloadStr, 'base64url').toString('utf-8')
152
+ );
153
+
154
+ // Must have agentId and exp
155
+ if (!payload.agentId || !payload.exp) {
156
+ return null;
157
+ }
158
+
159
+ // Check expiry
160
+ if (payload.exp < Date.now()) {
161
+ return null;
162
+ }
163
+
164
+ // Ensure permissions array exists
165
+ if (!Array.isArray(payload.permissions)) {
166
+ payload.permissions = [];
167
+ }
168
+
169
+ return payload as AgentTokenPayload;
170
+ } catch {
171
+ return null;
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Get a hash of the token for use as a session key
177
+ */
178
+ export function getTokenHash(token: string): string {
179
+ return createHmac('sha256', SIGNING_KEY)
180
+ .update(token)
181
+ .digest('hex')
182
+ .slice(0, 16);
183
+ }
184
+
185
+ /**
186
+ * Get the fund limit from a token (supports both legacy and new format)
187
+ */
188
+ export function getFundLimit(token: AgentTokenPayload): number {
189
+ const fundLimit = token.limits?.fund;
190
+
191
+ // New format: plain number (legacy/legacy-compatible single-currency)
192
+ if (typeof fundLimit === 'number') {
193
+ return fundLimit;
194
+ }
195
+
196
+ // Address-keyed fund limits are per-currency and require explicit currency context
197
+ // for enforcement in checkLimitByType/getSessionBudget.
198
+ // Treat missing default-currency fund limit as zero here to keep behavior fail-safe.
199
+ if (fundLimit && typeof fundLimit === 'object') {
200
+ return 0;
201
+ }
202
+
203
+ // Fall back to legacy limit
204
+ return token.limit || 0;
205
+ }
206
+
207
+ /**
208
+ * Get a specific limit from a token
209
+ */
210
+ export function getLimit(token: AgentTokenPayload, limitType: 'fund' | 'send' | 'swap'): number | undefined {
211
+ return token.limits?.[limitType];
212
+ }
213
+
214
+ /**
215
+ * Check if a token has wallet access to a specific address
216
+ */
217
+ export function hasWalletAccess(token: AgentTokenPayload, address: string): boolean {
218
+ if (!token.walletAccess) return false;
219
+ return token.walletAccess.includes(address.toLowerCase());
220
+ }
221
+
222
+ // In-memory token escrow: raw tokens never touch the DB
223
+ interface EscrowEntry { token: string; expiresAt: number; }
224
+ const tokenEscrow = new Map<string, EscrowEntry>();
225
+ const ESCROW_TTL_MS = 5 * 60 * 1000; // 5 minutes
226
+
227
+ export function escrowToken(requestId: string, token: string): void {
228
+ tokenEscrow.set(requestId, { token, expiresAt: Date.now() + ESCROW_TTL_MS });
229
+ }
230
+
231
+ export function claimEscrowedToken(requestId: string): string | null {
232
+ const entry = tokenEscrow.get(requestId);
233
+ if (!entry) return null;
234
+ tokenEscrow.delete(requestId);
235
+ if (entry.expiresAt < Date.now()) return null; // expired
236
+ return entry.token;
237
+ }
238
+
239
+ // Periodic sweep of expired escrow entries
240
+ const escrowSweepInterval = setInterval(() => {
241
+ const now = Date.now();
242
+ for (const [id, entry] of tokenEscrow) {
243
+ if (entry.expiresAt < now) tokenEscrow.delete(id);
244
+ }
245
+ }, 60_000);
246
+ escrowSweepInterval.unref(); // Don't keep process alive
247
+
248
+ export function clearEscrowSweep(): void {
249
+ clearInterval(escrowSweepInterval);
250
+ }
251
+
252
+ /**
253
+ * Regenerate the signing key (for testing)
254
+ * WARNING: This invalidates all existing tokens
255
+ */
256
+ export function regenerateSigningKey(): void {
257
+ // This is a no-op since SIGNING_KEY is const
258
+ // In production, restarting the server achieves this
259
+ // For tests, we just note that this would invalidate tokens
260
+ }
261
+
262
+ /**
263
+ * Create a signed token synchronously (for testing only)
264
+ * Does NOT register in DB - use createToken() for production
265
+ */
266
+ export function createTokenSync(payload: {
267
+ agentId: string;
268
+ permissions: string[];
269
+ exp: number;
270
+ iat?: number;
271
+ limits?: AgentTokenPayload['limits'];
272
+ walletAccess?: string[];
273
+ credentialAccess?: AgentTokenPayload['credentialAccess'];
274
+ agentPubkey?: string;
275
+ }): string {
276
+ const fullPayload: AgentTokenPayload = {
277
+ agentId: payload.agentId,
278
+ permissions: payload.permissions,
279
+ exp: payload.exp,
280
+ iat: payload.iat ?? Date.now(),
281
+ };
282
+
283
+ if (payload.limits) {
284
+ fullPayload.limits = payload.limits;
285
+ }
286
+
287
+ if (payload.walletAccess && payload.walletAccess.length > 0) {
288
+ fullPayload.walletAccess = payload.walletAccess.map(addr => addr.toLowerCase());
289
+ }
290
+
291
+ if (payload.credentialAccess) {
292
+ fullPayload.credentialAccess = payload.credentialAccess;
293
+ } else if (
294
+ payload.permissions.includes('secret:read') ||
295
+ payload.permissions.includes('secret:write')
296
+ ) {
297
+ fullPayload.credentialAccess = {
298
+ read: getDefaultSync<string[]>('defaults.credential.access.read', ['*']),
299
+ write: getDefaultSync<string[]>('defaults.credential.access.write', ['*']),
300
+ excludeFields: ['password', 'cvv'], // Exclude sensitive fields by default
301
+ };
302
+ }
303
+
304
+ if (payload.agentPubkey) {
305
+ fullPayload.agentPubkey = payload.agentPubkey;
306
+ }
307
+
308
+ const payloadStr = Buffer.from(JSON.stringify(fullPayload)).toString('base64url');
309
+ const signature = createHmac('sha256', SIGNING_KEY)
310
+ .update(payloadStr)
311
+ .digest('base64url');
312
+
313
+ return `${payloadStr}.${signature}`;
314
+ }