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,444 @@
1
+ /**
2
+ * Token search by ticker/name via DexScreener with CoinGecko fallback.
3
+ *
4
+ * Local-first: checks TokenMetadata + bookmarks in the database before
5
+ * hitting external APIs. Local matches are prepended to results.
6
+ *
7
+ * DexScreener: primary external source, returns pairs with liquidity data.
8
+ * CoinGecko: fallback when DexScreener fails — search → coin details (two-step).
9
+ *
10
+ * In-memory cache with 5-minute TTL. No DB writes (except TokenMetadata seeding elsewhere).
11
+ */
12
+
13
+ import { prisma } from './db';
14
+
15
+ const PERSIST_LIQUIDITY_THRESHOLD = 100;
16
+
17
+ /** Safely parse a JSON string, returning fallback on failure. */
18
+ export function safeJsonParse<T>(value: string | null | undefined, fallback: T): T {
19
+ if (!value) return fallback;
20
+ try {
21
+ return JSON.parse(value);
22
+ } catch {
23
+ return fallback;
24
+ }
25
+ }
26
+
27
+ export interface TokenSearchResult {
28
+ address: string;
29
+ chain: string;
30
+ symbol: string;
31
+ name: string;
32
+ priceUsd: string | null;
33
+ liquidity: number;
34
+ volume24h: number;
35
+ marketCap: number | null;
36
+ fdv: number | null;
37
+ imageUrl: string | null;
38
+ websites: string[];
39
+ socials: { type: string; url: string }[];
40
+ dexId: string;
41
+ pairAddress: string;
42
+ }
43
+
44
+ interface CacheEntry {
45
+ results: TokenSearchResult[];
46
+ fetchedAt: number;
47
+ }
48
+
49
+ const CACHE_TTL_MS = 5 * 60_000; // 5 minutes
50
+
51
+ const searchCache = new Map<string, CacheEntry>();
52
+
53
+ /** Clear cache — exposed for tests */
54
+ export function clearTokenSearchCache(): void {
55
+ searchCache.clear();
56
+ }
57
+
58
+ /** Look up a token by address+chain in the in-memory search cache. */
59
+ export function lookupCachedToken(
60
+ address: string,
61
+ chain: string,
62
+ ): { symbol?: string; name?: string; icon?: string } | null {
63
+ const addrLower = address.toLowerCase();
64
+ const now = Date.now();
65
+ for (const entry of searchCache.values()) {
66
+ if (now - entry.fetchedAt >= CACHE_TTL_MS) continue;
67
+ for (const r of entry.results) {
68
+ if (r.address.toLowerCase() === addrLower && r.chain === chain) {
69
+ return {
70
+ ...(r.symbol && { symbol: r.symbol }),
71
+ ...(r.name && { name: r.name }),
72
+ ...(r.imageUrl && { icon: r.imageUrl }),
73
+ };
74
+ }
75
+ }
76
+ }
77
+ return null;
78
+ }
79
+
80
+ // CoinGecko platform ID → our chain name
81
+ const COINGECKO_CHAIN_MAP: Record<string, string> = {
82
+ 'ethereum': 'ethereum',
83
+ 'base': 'base',
84
+ 'solana': 'solana',
85
+ 'polygon-pos': 'polygon',
86
+ 'arbitrum-one': 'arbitrum',
87
+ 'optimistic-ethereum': 'optimism',
88
+ 'binance-smart-chain': 'bsc',
89
+ 'avalanche': 'avalanche',
90
+ };
91
+
92
+ /**
93
+ * Search local TokenMetadata + bookmarks (TrackedAsset with null walletAddress).
94
+ * Returns matches by symbol, name, or address. Free and instant.
95
+ */
96
+ async function searchLocal(
97
+ query: string,
98
+ chain: string,
99
+ limit: number,
100
+ ): Promise<TokenSearchResult[]> {
101
+ try {
102
+ const q = query.toLowerCase();
103
+ const qUpper = query.toUpperCase();
104
+ const isAddress = q.startsWith('0x') && q.length >= 10;
105
+
106
+ const where: Record<string, unknown> = {
107
+ OR: [
108
+ { symbol: { contains: qUpper } },
109
+ { name: { contains: q } },
110
+ ...(isAddress ? [{ tokenAddress: { contains: q } }] : []),
111
+ ],
112
+ };
113
+ if (chain) where.chain = chain;
114
+
115
+ const rows = await prisma.tokenMetadata.findMany({
116
+ where,
117
+ take: limit,
118
+ orderBy: { lastAccessedAt: 'desc' },
119
+ });
120
+
121
+ return rows.map((r) => ({
122
+ address: r.tokenAddress,
123
+ chain: r.chain,
124
+ symbol: r.symbol || '',
125
+ name: r.name || '',
126
+ priceUsd: r.priceUsd ?? null,
127
+ liquidity: r.liquidity ?? 0,
128
+ volume24h: r.volume24h ?? 0,
129
+ marketCap: r.marketCap ?? null,
130
+ fdv: r.fdv ?? null,
131
+ imageUrl: r.icon || null,
132
+ websites: safeJsonParse<string[]>(r.websites, []),
133
+ socials: safeJsonParse<{ type: string; url: string }[]>(r.socials, []),
134
+ dexId: r.dexId || 'local',
135
+ pairAddress: r.pairAddress || '',
136
+ }));
137
+ } catch {
138
+ return [];
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Search tokens by ticker/name. Returns deduplicated results sorted by liquidity.
144
+ * Checks local TokenMetadata first, then DexScreener, then CoinGecko.
145
+ * Local matches are prepended to external results.
146
+ */
147
+ export async function searchTokens(
148
+ query: string,
149
+ options?: { chain?: string; limit?: number },
150
+ ): Promise<TokenSearchResult[]> {
151
+ const chain = options?.chain || '';
152
+ const limit = Math.min(Math.max(options?.limit || 10, 1), 50);
153
+ const cacheKey = `${query.toLowerCase()}:${chain}:${limit}`;
154
+
155
+ // Check cache
156
+ const cached = searchCache.get(cacheKey);
157
+ if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
158
+ return cached.results;
159
+ }
160
+
161
+ // 1. Check local TokenMetadata first (instant, free)
162
+ const localResults = await searchLocal(query, chain, limit);
163
+
164
+ // 2. Try DexScreener
165
+ const dexResults = await fetchDexScreener(query, chain, limit);
166
+ if (dexResults) {
167
+ const merged = mergeResults(localResults, dexResults, limit);
168
+ return cacheAndReturn(cacheKey, merged);
169
+ }
170
+
171
+ // 3. Fallback: CoinGecko
172
+ const cgResults = await fetchCoinGecko(query, chain, limit);
173
+ const merged = mergeResults(localResults, cgResults, limit);
174
+ return cacheAndReturn(cacheKey, merged);
175
+ }
176
+
177
+ /**
178
+ * Merge local results with external results.
179
+ * Local matches come first, external results fill remaining slots.
180
+ * Deduplicates by tokenAddress+chain (local wins).
181
+ */
182
+ function mergeResults(
183
+ local: TokenSearchResult[],
184
+ external: TokenSearchResult[],
185
+ limit: number,
186
+ ): TokenSearchResult[] {
187
+ if (local.length === 0) return external.slice(0, limit);
188
+
189
+ const seen = new Set(local.map((r) => `${r.address.toLowerCase()}:${r.chain}`));
190
+ const merged = [...local];
191
+
192
+ for (const ext of external) {
193
+ const key = `${ext.address.toLowerCase()}:${ext.chain}`;
194
+ if (seen.has(key)) {
195
+ // Enrich local entry with external data (price, liquidity, etc.)
196
+ const idx = merged.findIndex(
197
+ (r) => r.address.toLowerCase() === ext.address.toLowerCase() && r.chain === ext.chain,
198
+ );
199
+ if (idx !== -1) {
200
+ merged[idx] = { ...ext, dexId: ext.dexId };
201
+ }
202
+ continue;
203
+ }
204
+ seen.add(key);
205
+ merged.push(ext);
206
+ }
207
+
208
+ return merged.slice(0, limit);
209
+ }
210
+
211
+ /**
212
+ * DexScreener search: returns pairs with full liquidity data.
213
+ * Returns null on failure (triggers fallback).
214
+ */
215
+ async function fetchDexScreener(
216
+ query: string,
217
+ chain: string,
218
+ limit: number,
219
+ ): Promise<TokenSearchResult[] | null> {
220
+ try {
221
+ const res = await fetch(
222
+ `https://api.dexscreener.com/latest/dex/search?q=${encodeURIComponent(query)}`,
223
+ { signal: AbortSignal.timeout(5000) },
224
+ );
225
+ if (!res.ok) return null;
226
+
227
+ const data = await res.json();
228
+ const pairs: any[] = data.pairs || [];
229
+ if (pairs.length === 0) return null;
230
+
231
+ // Filter by chain if specified
232
+ const filtered = chain
233
+ ? pairs.filter((p: any) => p.chainId === chain)
234
+ : pairs;
235
+
236
+ // Group by baseToken.address + chainId → pick pair with highest liquidity
237
+ const groups = new Map<string, any>();
238
+ for (const pair of filtered) {
239
+ const addr = pair.baseToken?.address;
240
+ if (!addr) continue;
241
+ const key = `${addr.toLowerCase()}:${pair.chainId}`;
242
+ const existing = groups.get(key);
243
+ if (!existing || (pair.liquidity?.usd || 0) > (existing.liquidity?.usd || 0)) {
244
+ groups.set(key, pair);
245
+ }
246
+ }
247
+
248
+ // Sort by liquidity descending, apply limit
249
+ const sorted = Array.from(groups.values())
250
+ .sort((a, b) => (b.liquidity?.usd || 0) - (a.liquidity?.usd || 0))
251
+ .slice(0, limit);
252
+
253
+ return sorted.map(mapDexScreenerPair);
254
+ } catch {
255
+ return null;
256
+ }
257
+ }
258
+
259
+ function mapDexScreenerPair(pair: any): TokenSearchResult {
260
+ return {
261
+ address: pair.baseToken.address,
262
+ chain: pair.chainId,
263
+ symbol: pair.baseToken.symbol,
264
+ name: pair.baseToken.name,
265
+ priceUsd: pair.priceUsd || null,
266
+ liquidity: pair.liquidity?.usd || 0,
267
+ volume24h: pair.volume?.h24 || 0,
268
+ marketCap: pair.marketCap ?? null,
269
+ fdv: pair.fdv ?? null,
270
+ imageUrl: pair.info?.imageUrl || null,
271
+ websites: (pair.info?.websites || []).map((w: any) => w.url || w).filter(Boolean),
272
+ socials: (pair.info?.socials || []).filter((s: any) => s.url),
273
+ dexId: pair.dexId,
274
+ pairAddress: pair.pairAddress,
275
+ };
276
+ }
277
+
278
+ /**
279
+ * CoinGecko fallback: two-step search → coin details.
280
+ * Step 1: GET /search?query=X → coin IDs
281
+ * Step 2: GET /coins/{id} for top results → addresses + market data
282
+ *
283
+ * Limited to 5 detail fetches to respect CoinGecko rate limits (5-15 req/min free tier).
284
+ */
285
+ async function fetchCoinGecko(
286
+ query: string,
287
+ chain: string,
288
+ limit: number,
289
+ ): Promise<TokenSearchResult[]> {
290
+ try {
291
+ // Step 1: Search for coin IDs
292
+ const searchRes = await fetch(
293
+ `https://api.coingecko.com/api/v3/search?query=${encodeURIComponent(query)}`,
294
+ { signal: AbortSignal.timeout(5000) },
295
+ );
296
+ if (!searchRes.ok) return [];
297
+
298
+ const searchData = await searchRes.json();
299
+ const coins: any[] = searchData.coins || [];
300
+ if (coins.length === 0) return [];
301
+
302
+ // Take top 5 to stay within rate limits
303
+ const topCoins = coins.slice(0, 5);
304
+
305
+ // Step 2: Fetch details for each coin (in parallel)
306
+ const details = await Promise.all(
307
+ topCoins.map((coin) => fetchCoinGeckoDetails(coin.id)),
308
+ );
309
+
310
+ // Flatten: each coin may have multiple platform addresses
311
+ const results: TokenSearchResult[] = [];
312
+ for (const detail of details) {
313
+ if (!detail) continue;
314
+ for (const result of detail) {
315
+ // Apply chain filter
316
+ if (chain && result.chain !== chain) continue;
317
+ results.push(result);
318
+ }
319
+ }
320
+
321
+ // Sort by market cap (best proxy for liquidity from CoinGecko)
322
+ results.sort((a, b) => (b.marketCap || 0) - (a.marketCap || 0));
323
+
324
+ return results.slice(0, limit);
325
+ } catch {
326
+ return [];
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Fetch coin details from CoinGecko and map to TokenSearchResult[].
332
+ * Returns one result per platform (chain) the token is deployed on.
333
+ */
334
+ async function fetchCoinGeckoDetails(coinId: string): Promise<TokenSearchResult[] | null> {
335
+ try {
336
+ const res = await fetch(
337
+ `https://api.coingecko.com/api/v3/coins/${encodeURIComponent(coinId)}?localization=false&tickers=false&community_data=false&developer_data=false&sparkline=false`,
338
+ { signal: AbortSignal.timeout(5000) },
339
+ );
340
+ if (!res.ok) return null;
341
+
342
+ const data = await res.json();
343
+ const platforms: Record<string, string> = data.platforms || {};
344
+ const results: TokenSearchResult[] = [];
345
+
346
+ // Extract common fields
347
+ const symbol: string = data.symbol?.toUpperCase() || '';
348
+ const name: string = data.name || '';
349
+ const priceUsd = data.market_data?.current_price?.usd?.toString() || null;
350
+ const volume24h = data.market_data?.total_volume?.usd || 0;
351
+ const marketCap = data.market_data?.market_cap?.usd ?? null;
352
+ const fdv = data.market_data?.fully_diluted_valuation?.usd ?? null;
353
+ const imageUrl = data.image?.large || data.image?.small || null;
354
+ const websites = (data.links?.homepage || []).filter(Boolean);
355
+ const socials: { type: string; url: string }[] = [];
356
+ if (data.links?.twitter_screen_name) {
357
+ socials.push({ type: 'twitter', url: `https://twitter.com/${data.links.twitter_screen_name}` });
358
+ }
359
+ if (data.links?.telegram_channel_identifier) {
360
+ socials.push({ type: 'telegram', url: `https://t.me/${data.links.telegram_channel_identifier}` });
361
+ }
362
+
363
+ for (const [platformId, address] of Object.entries(platforms)) {
364
+ if (!address) continue;
365
+ const chainName = COINGECKO_CHAIN_MAP[platformId] || platformId;
366
+
367
+ results.push({
368
+ address,
369
+ chain: chainName,
370
+ symbol,
371
+ name,
372
+ priceUsd,
373
+ liquidity: 0, // CoinGecko doesn't provide liquidity
374
+ volume24h,
375
+ marketCap,
376
+ fdv,
377
+ imageUrl,
378
+ websites,
379
+ socials,
380
+ dexId: 'coingecko',
381
+ pairAddress: '',
382
+ });
383
+ }
384
+
385
+ return results;
386
+ } catch {
387
+ return null;
388
+ }
389
+ }
390
+
391
+ /**
392
+ * Fire-and-forget persist market data from search results to TokenMetadata.
393
+ * Skips tokens with low liquidity and local-only results (no new data).
394
+ */
395
+ function persistSearchMarketData(results: TokenSearchResult[]): void {
396
+ for (const r of results) {
397
+ if (r.dexId === 'local') continue;
398
+ if (r.liquidity < PERSIST_LIQUIDITY_THRESHOLD && !r.marketCap) continue;
399
+
400
+ const websites = r.websites.length > 0 ? JSON.stringify(r.websites) : null;
401
+ const socials = r.socials.length > 0 ? JSON.stringify(r.socials) : null;
402
+
403
+ prisma.tokenMetadata.upsert({
404
+ where: { tokenAddress_chain: { tokenAddress: r.address, chain: r.chain } },
405
+ create: {
406
+ tokenAddress: r.address,
407
+ chain: r.chain,
408
+ symbol: r.symbol || undefined,
409
+ name: r.name || undefined,
410
+ icon: r.imageUrl || undefined,
411
+ priceUsd: r.priceUsd,
412
+ marketCap: r.marketCap,
413
+ fdv: r.fdv,
414
+ liquidity: r.liquidity || null,
415
+ volume24h: r.volume24h || null,
416
+ dexId: r.dexId,
417
+ pairAddress: r.pairAddress || null,
418
+ websites,
419
+ socials,
420
+ },
421
+ update: {
422
+ symbol: r.symbol || undefined,
423
+ name: r.name || undefined,
424
+ icon: r.imageUrl || undefined,
425
+ priceUsd: r.priceUsd,
426
+ marketCap: r.marketCap,
427
+ fdv: r.fdv,
428
+ liquidity: r.liquidity || null,
429
+ volume24h: r.volume24h || null,
430
+ dexId: r.dexId,
431
+ pairAddress: r.pairAddress || null,
432
+ websites,
433
+ socials,
434
+ lastAccessedAt: new Date(),
435
+ },
436
+ }).catch(() => {});
437
+ }
438
+ }
439
+
440
+ function cacheAndReturn(key: string, results: TokenSearchResult[]): TokenSearchResult[] {
441
+ searchCache.set(key, { results, fetchedAt: Date.now() });
442
+ persistSearchMarketData(results);
443
+ return results;
444
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * TOTP Code Generation
3
+ * ====================
4
+ * Generates RFC 6238 TOTP codes from a base32-encoded shared secret.
5
+ */
6
+
7
+ import * as OTPAuth from 'otpauth';
8
+
9
+ export interface TOTPResult {
10
+ code: string;
11
+ remaining: number; // seconds until next code
12
+ }
13
+
14
+ /**
15
+ * Generate a TOTP code from a base32-encoded secret.
16
+ * Honors algorithm, digits, and period from OtpauthParams if provided;
17
+ * falls back to RFC 6238 defaults: SHA-1, 6 digits, 30-second period.
18
+ */
19
+ export function generateTOTP(
20
+ secret: string,
21
+ issuerOrParams?: string | Partial<OtpauthParams>,
22
+ label?: string,
23
+ ): TOTPResult {
24
+ const explicitParams: Partial<OtpauthParams> =
25
+ typeof issuerOrParams === 'object' && issuerOrParams
26
+ ? issuerOrParams
27
+ : {};
28
+
29
+ const normalizedSecretInput = typeof secret === 'string' ? secret.trim() : secret;
30
+
31
+ // Backward-compatible support for raw otpauth:// URIs stored as the secret.
32
+ // This lets callers pass a full URI directly and still honor query parameters
33
+ // like algorithm/digits/period without pre-parsing.
34
+ const parsedFromUri =
35
+ typeof normalizedSecretInput === 'string' && normalizedSecretInput.startsWith('otpauth://')
36
+ ? parseOtpauthUri(normalizedSecretInput)
37
+ : undefined;
38
+
39
+ const merged: Partial<OtpauthParams> = {
40
+ ...parsedFromUri,
41
+ ...explicitParams,
42
+ };
43
+
44
+ const normalized = normalizeOtpauthParams(merged);
45
+ const normalizedSecret = normalizeBase32Secret((normalized.secret || normalizedSecretInput));
46
+ const issuer = typeof issuerOrParams === 'string' ? issuerOrParams : normalized.issuer;
47
+
48
+ const totp = new OTPAuth.TOTP({
49
+ issuer: issuer || 'AuraWallet',
50
+ label: label || normalized.label || 'default',
51
+ algorithm: normalized.algorithm,
52
+ digits: normalized.digits,
53
+ period: normalized.period,
54
+ secret: OTPAuth.Secret.fromBase32(normalizedSecret),
55
+ });
56
+
57
+ const code = totp.generate();
58
+ const now = Math.floor(Date.now() / 1000);
59
+ const remaining = normalized.period - (now % normalized.period);
60
+
61
+ return { code, remaining };
62
+ }
63
+
64
+ /**
65
+ * Parse an otpauth:// URI into its components.
66
+ * Format: otpauth://totp/Label?secret=BASE32&issuer=Example&algorithm=SHA1&digits=6&period=30
67
+ */
68
+ export interface OtpauthParams {
69
+ secret: string;
70
+ issuer?: string;
71
+ algorithm?: string;
72
+ digits?: number;
73
+ period?: number;
74
+ label?: string;
75
+ }
76
+
77
+ const DEFAULT_TOTP_PARAMS = {
78
+ algorithm: 'SHA1',
79
+ digits: 6,
80
+ period: 30,
81
+ } as const;
82
+
83
+ const MIN_TOTP_DIGITS = 6;
84
+ const MAX_TOTP_DIGITS = 8;
85
+ const MIN_TOTP_PERIOD = 15;
86
+ const MAX_TOTP_PERIOD = 60;
87
+ const VALID_ALGORITHMS = new Set(['SHA1', 'SHA256', 'SHA512']);
88
+
89
+ function normalizeOtpauthParams(params: Partial<OtpauthParams>): Required<Pick<OtpauthParams, 'algorithm' | 'digits' | 'period'>> & Omit<OtpauthParams, 'algorithm' | 'digits' | 'period'> {
90
+ const algorithm = params.algorithm || DEFAULT_TOTP_PARAMS.algorithm;
91
+ const digits = params.digits || DEFAULT_TOTP_PARAMS.digits;
92
+ const period = params.period || DEFAULT_TOTP_PARAMS.period;
93
+
94
+ if (!VALID_ALGORITHMS.has(algorithm)) {
95
+ throw new Error(`Unsupported TOTP algorithm: ${algorithm}`);
96
+ }
97
+
98
+ if (!Number.isInteger(digits) || digits < MIN_TOTP_DIGITS || digits > MAX_TOTP_DIGITS) {
99
+ throw new Error(`Invalid TOTP digits: ${digits}`);
100
+ }
101
+
102
+ if (!Number.isInteger(period) || period < MIN_TOTP_PERIOD || period > MAX_TOTP_PERIOD) {
103
+ throw new Error(`Invalid TOTP period: ${period}`);
104
+ }
105
+
106
+ return {
107
+ ...params,
108
+ algorithm,
109
+ digits,
110
+ period,
111
+ };
112
+ }
113
+
114
+ function normalizeBase32Secret(secret: string): string {
115
+ const normalizedSecret = secret.replace(/\s+/g, '').toUpperCase();
116
+
117
+ if (normalizedSecret.length < 8) {
118
+ throw new Error('Invalid TOTP secret');
119
+ }
120
+
121
+ if (!/^[A-Z2-7]+$/.test(normalizedSecret)) {
122
+ throw new Error('Invalid TOTP secret');
123
+ }
124
+
125
+ return normalizedSecret;
126
+ }
127
+
128
+ export function parseOtpauthUri(uri: string): OtpauthParams {
129
+ const url = new URL(uri);
130
+ if (url.protocol !== 'otpauth:') throw new Error('Not an otpauth URI');
131
+ if (url.hostname !== 'totp') throw new Error('Only TOTP is supported');
132
+
133
+ const secret = url.searchParams.get('secret');
134
+ if (!secret) throw new Error('Missing secret parameter');
135
+
136
+ // Label is the path component (after /totp/)
137
+ const label = decodeURIComponent(url.pathname.replace(/^\//, ''));
138
+
139
+ const parsed: Partial<OtpauthParams> = {
140
+ secret: normalizeBase32Secret(secret),
141
+ issuer: url.searchParams.get('issuer') || undefined,
142
+ algorithm: url.searchParams.get('algorithm') || undefined,
143
+ digits: url.searchParams.has('digits') ? parseInt(url.searchParams.get('digits')!, 10) : undefined,
144
+ period: url.searchParams.has('period') ? parseInt(url.searchParams.get('period')!, 10) : undefined,
145
+ label: label || undefined,
146
+ };
147
+
148
+ return normalizeOtpauthParams(parsed);
149
+
150
+ }
151
+
152
+ /**
153
+ * Validate a TOTP code against a secret.
154
+ * Allows ±1 time step window for clock drift.
155
+ * Honors algorithm/digits/period from params if provided.
156
+ */
157
+ export function validateTOTP(secret: string, code: string, params?: Partial<OtpauthParams>): boolean {
158
+ const explicitParams: Partial<OtpauthParams> =
159
+ typeof params === 'object' && params ? params : {};
160
+
161
+ const normalizedSecretInput = typeof secret === 'string' ? secret.trim() : secret;
162
+
163
+ const parsedFromUri =
164
+ typeof normalizedSecretInput === 'string' && normalizedSecretInput.startsWith('otpauth://')
165
+ ? parseOtpauthUri(normalizedSecretInput)
166
+ : undefined;
167
+
168
+ const merged: Partial<OtpauthParams> = {
169
+ ...parsedFromUri,
170
+ ...explicitParams,
171
+ };
172
+
173
+ const normalized = normalizeOtpauthParams(merged);
174
+ const normalizedSecret = normalizeBase32Secret((normalized.secret || normalizedSecretInput));
175
+
176
+ const totp = new OTPAuth.TOTP({
177
+ algorithm: normalized.algorithm,
178
+ digits: normalized.digits,
179
+ period: normalized.period,
180
+ secret: OTPAuth.Secret.fromBase32(normalizedSecret),
181
+ });
182
+
183
+ const delta = totp.validate({ token: code, window: 1 });
184
+ return delta !== null;
185
+ }
186
+
187
+ /**
188
+ * Find a TOTP field in a list of credential fields.
189
+ * Checks both 'totp' and 'otp' keys for backward compatibility
190
+ * with 1Password imports that used 'otp'.
191
+ */
192
+ export function findTotpField<T extends { key: string }>(fields: T[]): T | undefined {
193
+ return fields.find(f => f.key === 'totp') || fields.find(f => f.key === 'otp');
194
+ }