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,652 @@
1
+ /**
2
+ * Token & Pool Metadata Enricher
3
+ * ===============================
4
+ * Resolves token/pool metadata from DB cache or on-chain,
5
+ * then enriches decoded events into human-readable summaries.
6
+ */
7
+
8
+ import { createPublicClient, http, erc20Abi, type Address } from 'viem';
9
+ import { base, mainnet } from 'viem/chains';
10
+ import { getRpcUrl } from '../config';
11
+ import { prisma } from '../db';
12
+ import { KNOWN_CONTRACTS, EVENT_SIGNATURES } from './signatures';
13
+ import type { DecodedEvent } from './decoder';
14
+
15
+ /** Serialize event params for JSON — converts BigInt values to strings */
16
+ function safeParams(params: Record<string, unknown>): Record<string, string> {
17
+ const out: Record<string, string> = {};
18
+ for (const [k, v] of Object.entries(params)) {
19
+ out[k] = typeof v === 'bigint' ? v.toString() : String(v);
20
+ }
21
+ return out;
22
+ }
23
+
24
+ // --- Types ---
25
+
26
+ export interface TokenInfo {
27
+ address: string;
28
+ symbol: string;
29
+ name: string;
30
+ decimals: number;
31
+ icon?: string;
32
+ }
33
+
34
+ export interface PoolInfo {
35
+ address: string;
36
+ token0: string;
37
+ token1: string;
38
+ fee?: number;
39
+ dex?: string;
40
+ }
41
+
42
+ export interface EnrichedTransaction {
43
+ type: string;
44
+ summary: string;
45
+ txHash: string;
46
+ blockNumber: string; // stringified bigint
47
+ timestamp?: number;
48
+ protocol?: string;
49
+ details: Record<string, unknown>;
50
+ }
51
+
52
+ // Map chain names to viem chain objects
53
+ const VIEM_CHAINS: Record<string, typeof base> = {
54
+ base,
55
+ ethereum: mainnet,
56
+ };
57
+
58
+ // --- Token Resolution ---
59
+
60
+ export async function resolveTokenMetadataBatch(
61
+ addresses: string[],
62
+ chain: string,
63
+ ): Promise<Map<string, TokenInfo>> {
64
+ const result = new Map<string, TokenInfo>();
65
+ if (addresses.length === 0) return result;
66
+
67
+ const normalized = addresses.map(a => a.toLowerCase());
68
+ const unique = [...new Set(normalized)];
69
+
70
+ // 1. Check DB cache
71
+ const cached = await prisma.tokenMetadata.findMany({
72
+ where: {
73
+ tokenAddress: { in: unique },
74
+ chain,
75
+ },
76
+ });
77
+
78
+ const uncached: string[] = [];
79
+ for (const addr of unique) {
80
+ const hit = cached.find(c => c.tokenAddress === addr);
81
+ if (hit) {
82
+ result.set(addr, {
83
+ address: addr,
84
+ symbol: hit.symbol || 'UNKNOWN',
85
+ name: hit.name || 'Unknown Token',
86
+ decimals: hit.decimals,
87
+ icon: hit.icon || undefined,
88
+ });
89
+ } else {
90
+ uncached.push(addr);
91
+ }
92
+ }
93
+
94
+ if (uncached.length === 0) return result;
95
+
96
+ // 2. Multicall for cache misses
97
+ try {
98
+ const rpcUrl = await getRpcUrl(chain);
99
+ const viemChain = VIEM_CHAINS[chain];
100
+ const client = createPublicClient({
101
+ chain: viemChain,
102
+ transport: http(rpcUrl),
103
+ batch: { multicall: true },
104
+ });
105
+
106
+ const contracts = uncached.flatMap(addr => [
107
+ { address: addr as Address, abi: erc20Abi, functionName: 'symbol' as const },
108
+ { address: addr as Address, abi: erc20Abi, functionName: 'name' as const },
109
+ { address: addr as Address, abi: erc20Abi, functionName: 'decimals' as const },
110
+ ]);
111
+
112
+ const multicallResults = await client.multicall({
113
+ contracts,
114
+ allowFailure: true,
115
+ });
116
+
117
+ for (let i = 0; i < uncached.length; i++) {
118
+ const addr = uncached[i];
119
+ const symbolResult = multicallResults[i * 3];
120
+ const nameResult = multicallResults[i * 3 + 1];
121
+ const decimalsResult = multicallResults[i * 3 + 2];
122
+
123
+ const symbol = symbolResult.status === 'success' ? String(symbolResult.result) : 'UNKNOWN';
124
+ const name = nameResult.status === 'success' ? String(nameResult.result) : 'Unknown Token';
125
+ const decimals = decimalsResult.status === 'success' ? Number(decimalsResult.result) : 18;
126
+
127
+ const info: TokenInfo = { address: addr, symbol, name, decimals };
128
+ result.set(addr, info);
129
+
130
+ // Write to DB cache (fire-and-forget)
131
+ prisma.tokenMetadata.upsert({
132
+ where: { tokenAddress_chain: { tokenAddress: addr, chain } },
133
+ create: { tokenAddress: addr, chain, symbol, name, decimals },
134
+ update: { symbol, name, decimals },
135
+ }).catch(() => {});
136
+ }
137
+
138
+ // Fire-and-forget: DexScreener enrichment for icon
139
+ enrichFromDexScreener(uncached, chain).catch(() => {});
140
+ } catch {
141
+ // RPC failure — fill with defaults
142
+ for (const addr of uncached) {
143
+ if (!result.has(addr)) {
144
+ result.set(addr, { address: addr, symbol: 'UNKNOWN', name: 'Unknown Token', decimals: 18 });
145
+ }
146
+ }
147
+ }
148
+
149
+ return result;
150
+ }
151
+
152
+ /** Fire-and-forget DexScreener enrichment for token icons */
153
+ async function enrichFromDexScreener(addresses: string[], chain: string): Promise<void> {
154
+ for (const addr of addresses) {
155
+ try {
156
+ const res = await fetch(
157
+ `https://api.dexscreener.com/latest/dex/tokens/${addr}`,
158
+ { signal: AbortSignal.timeout(5000) },
159
+ );
160
+ if (!res.ok) continue;
161
+ const data = await res.json();
162
+ const pairs: any[] = data.pairs || [];
163
+ if (pairs.length === 0) continue;
164
+
165
+ const info = pairs[0].baseToken;
166
+ if (!info) continue;
167
+
168
+ const icon = (pairs[0] as any).info?.imageUrl || null;
169
+ if (icon) {
170
+ await prisma.tokenMetadata.update({
171
+ where: { tokenAddress_chain: { tokenAddress: addr, chain } },
172
+ data: { icon },
173
+ }).catch(() => {});
174
+ }
175
+ } catch {
176
+ // Non-critical
177
+ }
178
+ }
179
+ }
180
+
181
+ // --- Pool Resolution ---
182
+
183
+ const POOL_ABI = [
184
+ { inputs: [], name: 'token0', outputs: [{ type: 'address' }], stateMutability: 'view', type: 'function' },
185
+ { inputs: [], name: 'token1', outputs: [{ type: 'address' }], stateMutability: 'view', type: 'function' },
186
+ { inputs: [], name: 'fee', outputs: [{ type: 'uint24' }], stateMutability: 'view', type: 'function' },
187
+ ] as const;
188
+
189
+ export async function resolvePoolMetadata(
190
+ poolAddress: string,
191
+ chain: string,
192
+ ): Promise<PoolInfo | null> {
193
+ const addr = poolAddress.toLowerCase();
194
+
195
+ // 1. Check DB cache
196
+ const cached = await prisma.poolMetadata.findUnique({
197
+ where: { poolAddress_chain: { poolAddress: addr, chain } },
198
+ });
199
+ if (cached) {
200
+ return {
201
+ address: addr,
202
+ token0: cached.token0 || '',
203
+ token1: cached.token1 || '',
204
+ fee: cached.fee ?? undefined,
205
+ dex: cached.dex ?? undefined,
206
+ };
207
+ }
208
+
209
+ // 2. Multicall for pool contract
210
+ try {
211
+ const rpcUrl = await getRpcUrl(chain);
212
+ const viemChain = VIEM_CHAINS[chain];
213
+ const client = createPublicClient({
214
+ chain: viemChain,
215
+ transport: http(rpcUrl),
216
+ batch: { multicall: true },
217
+ });
218
+
219
+ const results = await client.multicall({
220
+ contracts: [
221
+ { address: addr as Address, abi: POOL_ABI, functionName: 'token0' },
222
+ { address: addr as Address, abi: POOL_ABI, functionName: 'token1' },
223
+ { address: addr as Address, abi: POOL_ABI, functionName: 'fee' },
224
+ ],
225
+ allowFailure: true,
226
+ });
227
+
228
+ const token0 = results[0].status === 'success' ? (results[0].result as string).toLowerCase() : null;
229
+ const token1 = results[1].status === 'success' ? (results[1].result as string).toLowerCase() : null;
230
+ const fee = results[2].status === 'success' ? Number(results[2].result) : null;
231
+
232
+ if (!token0 || !token1) return null;
233
+
234
+ const dex = fee !== null ? 'uniswap_v3' : 'uniswap_v2';
235
+ const pool: PoolInfo = { address: addr, token0, token1, fee: fee ?? undefined, dex };
236
+
237
+ // Write to cache (immutable)
238
+ prisma.poolMetadata.create({
239
+ data: { poolAddress: addr, chain, token0, token1, fee, dex },
240
+ }).catch(() => {});
241
+
242
+ return pool;
243
+ } catch {
244
+ return null;
245
+ }
246
+ }
247
+
248
+ export async function resolveV4PoolId(
249
+ poolId: string,
250
+ chain: string,
251
+ ): Promise<PoolInfo | null> {
252
+ // 1. Check DB cache (use poolId as poolAddress key)
253
+ const cached = await prisma.poolMetadata.findUnique({
254
+ where: { poolAddress_chain: { poolAddress: poolId, chain } },
255
+ });
256
+ if (cached) {
257
+ return {
258
+ address: poolId,
259
+ token0: cached.token0 || '',
260
+ token1: cached.token1 || '',
261
+ fee: cached.fee ?? undefined,
262
+ dex: 'uniswap_v4',
263
+ };
264
+ }
265
+
266
+ // 2. Look for Initialize event on PoolManager
267
+ const contracts = KNOWN_CONTRACTS[chain];
268
+ if (!contracts?.v4PoolManager) return null;
269
+
270
+ try {
271
+ const rpcUrl = await getRpcUrl(chain);
272
+ const viemChain = VIEM_CHAINS[chain];
273
+ const client = createPublicClient({
274
+ chain: viemChain,
275
+ transport: http(rpcUrl),
276
+ });
277
+
278
+ const logs = await client.getLogs({
279
+ address: contracts.v4PoolManager as Address,
280
+ event: {
281
+ type: 'event',
282
+ name: 'Initialize',
283
+ inputs: [
284
+ { name: 'id', type: 'bytes32', indexed: true },
285
+ { name: 'currency0', type: 'address', indexed: true },
286
+ { name: 'currency1', type: 'address', indexed: true },
287
+ { name: 'fee', type: 'uint24', indexed: false },
288
+ { name: 'tickSpacing', type: 'int24', indexed: false },
289
+ { name: 'hooks', type: 'address', indexed: false },
290
+ { name: 'sqrtPriceX96', type: 'uint160', indexed: false },
291
+ { name: 'tick', type: 'int24', indexed: false },
292
+ ],
293
+ },
294
+ args: { id: poolId as `0x${string}` },
295
+ fromBlock: 0n,
296
+ });
297
+
298
+ if (logs.length === 0) return null;
299
+
300
+ const initLog = logs[0];
301
+ const token0 = (initLog.args.currency0 as string).toLowerCase();
302
+ const token1 = (initLog.args.currency1 as string).toLowerCase();
303
+ const fee = Number(initLog.args.fee);
304
+
305
+ const pool: PoolInfo = { address: poolId, token0, token1, fee, dex: 'uniswap_v4' };
306
+
307
+ // Cache (immutable)
308
+ prisma.poolMetadata.create({
309
+ data: { poolAddress: poolId, chain, token0, token1, fee, dex: 'uniswap_v4' },
310
+ }).catch(() => {});
311
+
312
+ return pool;
313
+ } catch {
314
+ return null;
315
+ }
316
+ }
317
+
318
+ // --- Enrichment ---
319
+
320
+ function formatAmount(amount: bigint, decimals: number): string {
321
+ const divisor = 10n ** BigInt(decimals);
322
+ const whole = amount / divisor;
323
+ const remainder = amount % divisor;
324
+
325
+ if (remainder === 0n) return whole.toString();
326
+
327
+ const fracStr = remainder.toString().padStart(decimals, '0');
328
+ // Trim trailing zeros, keep up to 6 significant decimals
329
+ const trimmed = fracStr.replace(/0+$/, '').slice(0, 6);
330
+ return trimmed ? `${whole}.${trimmed}` : whole.toString();
331
+ }
332
+
333
+ function shortenAddress(addr: string): string {
334
+ return `${addr.slice(0, 6)}...${addr.slice(-4)}`;
335
+ }
336
+
337
+ export async function enrichEvents(
338
+ events: DecodedEvent[],
339
+ address: string,
340
+ chain: string,
341
+ ): Promise<EnrichedTransaction[]> {
342
+ const addr = address.toLowerCase();
343
+ const wethAddress = KNOWN_CONTRACTS[chain]?.weth?.toLowerCase();
344
+
345
+ // 1. Collect all unique token and pool addresses
346
+ const tokenAddresses = new Set<string>();
347
+ const poolAddresses: string[] = [];
348
+ const v4PoolIds: string[] = [];
349
+
350
+ for (const ev of events) {
351
+ if (ev.type === 'transfer' || ev.type === 'transfer_nft') {
352
+ tokenAddresses.add(ev.contractAddress);
353
+ } else if (ev.type === 'approval') {
354
+ tokenAddresses.add(ev.contractAddress);
355
+ } else if (ev.type === 'swap_v2') {
356
+ poolAddresses.push(ev.contractAddress);
357
+ } else if (ev.type === 'swap_v3') {
358
+ poolAddresses.push(ev.contractAddress);
359
+ } else if (ev.type === 'swap_v4') {
360
+ const poolId = ev.params.poolId as string;
361
+ if (poolId) v4PoolIds.push(poolId);
362
+ }
363
+ }
364
+
365
+ // 2. Batch-resolve metadata
366
+ const tokenMap = await resolveTokenMetadataBatch([...tokenAddresses], chain);
367
+
368
+ const poolMap = new Map<string, PoolInfo>();
369
+ for (const pa of poolAddresses) {
370
+ const info = await resolvePoolMetadata(pa, chain);
371
+ if (info) {
372
+ poolMap.set(pa, info);
373
+ // Also resolve pool tokens
374
+ if (info.token0) tokenAddresses.add(info.token0);
375
+ if (info.token1) tokenAddresses.add(info.token1);
376
+ }
377
+ }
378
+
379
+ for (const pid of v4PoolIds) {
380
+ const info = await resolveV4PoolId(pid, chain);
381
+ if (info) {
382
+ poolMap.set(pid, info);
383
+ if (info.token0) tokenAddresses.add(info.token0);
384
+ if (info.token1) tokenAddresses.add(info.token1);
385
+ }
386
+ }
387
+
388
+ // Resolve newly-discovered pool tokens
389
+ const additionalTokens = [...tokenAddresses].filter(a => !tokenMap.has(a));
390
+ if (additionalTokens.length > 0) {
391
+ const extra = await resolveTokenMetadataBatch(additionalTokens, chain);
392
+ for (const [k, v] of extra) tokenMap.set(k, v);
393
+ }
394
+
395
+ // Helper to get symbol
396
+ const getSymbol = (addr: string): string => {
397
+ if (addr === wethAddress) return 'WETH';
398
+ return tokenMap.get(addr)?.symbol || shortenAddress(addr);
399
+ };
400
+ const getDecimals = (addr: string): number => {
401
+ return tokenMap.get(addr)?.decimals || 18;
402
+ };
403
+
404
+ // 3. Build enriched transactions
405
+ const result: EnrichedTransaction[] = [];
406
+
407
+ for (const ev of events) {
408
+ try {
409
+ switch (ev.type) {
410
+ case 'transfer': {
411
+ const from = ev.params.from as string;
412
+ const to = ev.params.to as string;
413
+ const amount = ev.params.amount as bigint;
414
+ const decimals = getDecimals(ev.contractAddress);
415
+ const symbol = getSymbol(ev.contractAddress);
416
+ const formatted = formatAmount(amount, decimals);
417
+ const isIncoming = to === addr;
418
+ const direction = isIncoming ? 'in' : 'out';
419
+ const counterparty = isIncoming ? from : to;
420
+ const summary = isIncoming
421
+ ? `Received ${formatted} ${symbol} from ${shortenAddress(counterparty)}`
422
+ : `Sent ${formatted} ${symbol} to ${shortenAddress(counterparty)}`;
423
+
424
+ result.push({
425
+ type: 'transfer',
426
+ summary,
427
+ txHash: ev.txHash,
428
+ blockNumber: ev.blockNumber.toString(),
429
+ details: { from, to, amount: formatted, symbol, direction, tokenAddress: ev.contractAddress },
430
+ });
431
+ break;
432
+ }
433
+
434
+ case 'transfer_nft': {
435
+ const from = ev.params.from as string;
436
+ const to = ev.params.to as string;
437
+ const tokenId = ev.params.tokenId as bigint;
438
+ const isIncoming = to === addr;
439
+ const counterparty = isIncoming ? from : to;
440
+ const summary = isIncoming
441
+ ? `Received NFT #${tokenId} from ${shortenAddress(counterparty)}`
442
+ : `Sent NFT #${tokenId} to ${shortenAddress(counterparty)}`;
443
+
444
+ result.push({
445
+ type: 'transfer_nft',
446
+ summary,
447
+ txHash: ev.txHash,
448
+ blockNumber: ev.blockNumber.toString(),
449
+ details: { from, to, tokenId: tokenId.toString(), direction: isIncoming ? 'in' : 'out', contractAddress: ev.contractAddress },
450
+ });
451
+ break;
452
+ }
453
+
454
+ case 'approval': {
455
+ const spender = ev.params.spender as string;
456
+ const symbol = getSymbol(ev.contractAddress);
457
+ const summary = `Approved ${shortenAddress(spender)} to spend ${symbol}`;
458
+ result.push({
459
+ type: 'approval',
460
+ summary,
461
+ txHash: ev.txHash,
462
+ blockNumber: ev.blockNumber.toString(),
463
+ details: { owner: ev.params.owner, spender, symbol, tokenAddress: ev.contractAddress },
464
+ });
465
+ break;
466
+ }
467
+
468
+ case 'approval_nft': {
469
+ const spender = ev.params.spender as string;
470
+ const tokenId = ev.params.tokenId as bigint;
471
+ const summary = `Approved ${shortenAddress(spender)} for NFT #${tokenId}`;
472
+ result.push({
473
+ type: 'approval_nft',
474
+ summary,
475
+ txHash: ev.txHash,
476
+ blockNumber: ev.blockNumber.toString(),
477
+ details: { owner: ev.params.owner, spender, tokenId: tokenId.toString(), contractAddress: ev.contractAddress },
478
+ });
479
+ break;
480
+ }
481
+
482
+ case 'swap_v2': {
483
+ const pool = poolMap.get(ev.contractAddress);
484
+ if (!pool) {
485
+ result.push({
486
+ type: 'swap_v2',
487
+ summary: `Swapped on ${shortenAddress(ev.contractAddress)}`,
488
+ txHash: ev.txHash,
489
+ blockNumber: ev.blockNumber.toString(),
490
+ protocol: 'uniswap_v2',
491
+ details: safeParams(ev.params),
492
+ });
493
+ break;
494
+ }
495
+
496
+ const a0In = ev.params.amount0In as bigint;
497
+ const a1In = ev.params.amount1In as bigint;
498
+ const a0Out = ev.params.amount0Out as bigint;
499
+ const a1Out = ev.params.amount1Out as bigint;
500
+
501
+ const tokenIn = a0In > 0n ? pool.token0 : pool.token1;
502
+ const tokenOut = a0Out > 0n ? pool.token0 : pool.token1;
503
+ const amountIn = a0In > 0n ? a0In : a1In;
504
+ const amountOut = a0Out > 0n ? a0Out : a1Out;
505
+
506
+ const symbolIn = getSymbol(tokenIn);
507
+ const symbolOut = getSymbol(tokenOut);
508
+ const formattedIn = formatAmount(amountIn, getDecimals(tokenIn));
509
+ const formattedOut = formatAmount(amountOut, getDecimals(tokenOut));
510
+
511
+ result.push({
512
+ type: 'swap',
513
+ summary: `Swapped ${formattedIn} ${symbolIn} for ${formattedOut} ${symbolOut}`,
514
+ txHash: ev.txHash,
515
+ blockNumber: ev.blockNumber.toString(),
516
+ protocol: 'uniswap_v2',
517
+ details: { tokenIn, tokenOut, amountIn: formattedIn, amountOut: formattedOut, symbolIn, symbolOut, pool: ev.contractAddress },
518
+ });
519
+ break;
520
+ }
521
+
522
+ case 'swap_v3': {
523
+ const pool = poolMap.get(ev.contractAddress);
524
+ if (!pool) {
525
+ result.push({
526
+ type: 'swap_v3',
527
+ summary: `Swapped on ${shortenAddress(ev.contractAddress)}`,
528
+ txHash: ev.txHash,
529
+ blockNumber: ev.blockNumber.toString(),
530
+ protocol: 'uniswap_v3',
531
+ details: safeParams(ev.params),
532
+ });
533
+ break;
534
+ }
535
+
536
+ // V3 uses signed amounts: positive = sent to pool, negative = received from pool
537
+ const amount0 = ev.params.amount0 as bigint;
538
+ const amount1 = ev.params.amount1 as bigint;
539
+
540
+ const tokenIn = amount0 > 0n ? pool.token0 : pool.token1;
541
+ const tokenOut = amount0 > 0n ? pool.token1 : pool.token0;
542
+ const amountIn = amount0 > 0n ? amount0 : amount1;
543
+ const amountOut = amount0 > 0n ? -amount1 : -amount0;
544
+
545
+ const symbolIn = getSymbol(tokenIn);
546
+ const symbolOut = getSymbol(tokenOut);
547
+ const formattedIn = formatAmount(amountIn, getDecimals(tokenIn));
548
+ const formattedOut = formatAmount(amountOut, getDecimals(tokenOut));
549
+
550
+ result.push({
551
+ type: 'swap',
552
+ summary: `Swapped ${formattedIn} ${symbolIn} for ${formattedOut} ${symbolOut}`,
553
+ txHash: ev.txHash,
554
+ blockNumber: ev.blockNumber.toString(),
555
+ protocol: 'uniswap_v3',
556
+ details: { tokenIn, tokenOut, amountIn: formattedIn, amountOut: formattedOut, symbolIn, symbolOut, pool: ev.contractAddress },
557
+ });
558
+ break;
559
+ }
560
+
561
+ case 'swap_v4': {
562
+ const poolId = ev.params.poolId as string;
563
+ const pool = poolMap.get(poolId);
564
+ if (!pool) {
565
+ // Serialize params — raw BigInt values (amount0, sqrtPriceX96, etc.) can't be JSON-serialized
566
+ const safeParams: Record<string, string> = {};
567
+ for (const [k, v] of Object.entries(ev.params)) {
568
+ safeParams[k] = typeof v === 'bigint' ? v.toString() : String(v);
569
+ }
570
+ result.push({
571
+ type: 'swap_v4',
572
+ summary: `Swapped on V4 pool`,
573
+ txHash: ev.txHash,
574
+ blockNumber: ev.blockNumber.toString(),
575
+ protocol: 'uniswap_v4',
576
+ details: safeParams,
577
+ });
578
+ break;
579
+ }
580
+
581
+ const amount0 = ev.params.amount0 as bigint;
582
+ const amount1 = ev.params.amount1 as bigint;
583
+
584
+ const tokenIn = amount0 > 0n ? pool.token0 : pool.token1;
585
+ const tokenOut = amount0 > 0n ? pool.token1 : pool.token0;
586
+ const amountIn = amount0 > 0n ? amount0 : amount1;
587
+ const amountOut = amount0 > 0n ? -amount1 : -amount0;
588
+
589
+ const symbolIn = getSymbol(tokenIn);
590
+ const symbolOut = getSymbol(tokenOut);
591
+ const formattedIn = formatAmount(amountIn, getDecimals(tokenIn));
592
+ const formattedOut = formatAmount(amountOut, getDecimals(tokenOut));
593
+
594
+ result.push({
595
+ type: 'swap',
596
+ summary: `Swapped ${formattedIn} ${symbolIn} for ${formattedOut} ${symbolOut}`,
597
+ txHash: ev.txHash,
598
+ blockNumber: ev.blockNumber.toString(),
599
+ protocol: 'uniswap_v4',
600
+ details: { tokenIn, tokenOut, amountIn: formattedIn, amountOut: formattedOut, symbolIn, symbolOut, poolId },
601
+ });
602
+ break;
603
+ }
604
+
605
+ case 'weth_deposit': {
606
+ const wad = ev.params.wad as bigint;
607
+ const formatted = formatAmount(wad, 18);
608
+ result.push({
609
+ type: 'wrap',
610
+ summary: `Wrapped ${formatted} ETH`,
611
+ txHash: ev.txHash,
612
+ blockNumber: ev.blockNumber.toString(),
613
+ details: { amount: formatted, direction: 'wrap' },
614
+ });
615
+ break;
616
+ }
617
+
618
+ case 'weth_withdrawal': {
619
+ const wad = ev.params.wad as bigint;
620
+ const formatted = formatAmount(wad, 18);
621
+ result.push({
622
+ type: 'unwrap',
623
+ summary: `Unwrapped ${formatted} WETH`,
624
+ txHash: ev.txHash,
625
+ blockNumber: ev.blockNumber.toString(),
626
+ details: { amount: formatted, direction: 'unwrap' },
627
+ });
628
+ break;
629
+ }
630
+
631
+ default:
632
+ result.push({
633
+ type: ev.type,
634
+ summary: `Unknown event on ${shortenAddress(ev.contractAddress)}`,
635
+ txHash: ev.txHash,
636
+ blockNumber: ev.blockNumber.toString(),
637
+ details: safeParams(ev.params),
638
+ });
639
+ }
640
+ } catch {
641
+ result.push({
642
+ type: ev.type,
643
+ summary: `Event on ${shortenAddress(ev.contractAddress)}`,
644
+ txHash: ev.txHash,
645
+ blockNumber: ev.blockNumber.toString(),
646
+ details: safeParams(ev.params),
647
+ });
648
+ }
649
+ }
650
+
651
+ return result;
652
+ }