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,353 @@
1
+ import { Request, Response } from 'express';
2
+ import { ethers } from 'ethers';
3
+ import { getHotWallet, signWithHotWallet, tokenCanAccessWallet } from '../lib/hot';
4
+ import { getTempWallet, signWithTempWallet } from '../lib/temp';
5
+ import { isUnlocked } from '../lib/cold';
6
+ import { getRpcUrl } from '../lib/config';
7
+ import { logger } from '../lib/logger';
8
+ import {
9
+ getDexAdapter,
10
+ detectBestDex,
11
+ listDexes,
12
+ PoolKey
13
+ } from '../lib/dex';
14
+ import { getV4PoolKey, getKnownV4Hooks, detectV4PoolFromEvents } from '../lib/dex/uniswap';
15
+ import { isAdmin } from '../lib/permissions';
16
+ import { hasAnyPermission } from '../lib/permissions';
17
+ import { reserveSpend, releaseSpend, getRemainingByType } from '../lib/sessions';
18
+ import { getDefault } from '../lib/defaults';
19
+ import { getNativeAddress } from '../lib/address';
20
+ import { recordTransaction, autoTrackToken } from '../lib/transactions';
21
+ import { AuthInfo } from '../middleware/auth';
22
+ import { ChainConfig } from '../lib/config';
23
+
24
+ export async function handleEvmSwap(
25
+ req: Request,
26
+ res: Response,
27
+ auth: AuthInfo,
28
+ targetChain: string,
29
+ chainConfig: ChainConfig,
30
+ destinationChainId?: number,
31
+ targetChainOut?: string
32
+ ): Promise<void> {
33
+ const {
34
+ from,
35
+ token,
36
+ direction,
37
+ amount,
38
+ minOut,
39
+ slippage,
40
+ dex: requestedDex,
41
+ version: requestedVersion,
42
+ poolFee,
43
+ poolKey,
44
+ hook: requestedHook,
45
+ description: userDescription
46
+ } = req.body;
47
+
48
+ const provider = new ethers.JsonRpcProvider(await getRpcUrl(targetChain));
49
+
50
+ // Determine wallet type and verify ownership
51
+ const hotWallet = await getHotWallet(from);
52
+ const tempWallet = getTempWallet(from);
53
+
54
+ if (!hotWallet && !tempWallet) {
55
+ res.status(404).json({ error: 'Wallet not found' });
56
+ return;
57
+ }
58
+
59
+ if (hotWallet) {
60
+ // Check permission
61
+ if (!isAdmin(auth) && !hasAnyPermission(auth.token.permissions, ['swap'])) {
62
+ logger.permissionDenied('swap', auth.token.agentId, '/swap');
63
+ res.status(403).json({ error: 'Token does not have swap permission' });
64
+ return;
65
+ }
66
+
67
+ // Verify token can access this wallet
68
+ const canAccess = await tokenCanAccessWallet(auth.tokenHash, auth.token.walletAccess, from);
69
+ if (!isAdmin(auth) && !canAccess) {
70
+ logger.permissionDenied('wallet_access', auth.token.agentId, '/swap');
71
+ res.status(403).json({ error: 'Token does not have access to this wallet' });
72
+ return;
73
+ }
74
+
75
+ if (!isUnlocked()) {
76
+ logger.authFailed('Cold wallet locked', '/swap');
77
+ res.status(401).json({ error: 'Cold wallet must be unlocked to send from hot wallet' });
78
+ return;
79
+ }
80
+ } else if (tempWallet) {
81
+ // Check permission
82
+ if (!isAdmin(auth) && !hasAnyPermission(auth.token.permissions, ['swap'])) {
83
+ logger.permissionDenied('swap', auth.token.agentId, '/swap');
84
+ res.status(403).json({ error: 'Token does not have swap permission' });
85
+ return;
86
+ }
87
+ }
88
+
89
+ // Reserve spending atomically (prevents TOCTOU race between concurrent requests)
90
+ const currency = getNativeAddress(targetChain);
91
+ const evmSwapAmount = parseFloat(ethers.formatEther(BigInt(amount)));
92
+ const needsEvmLimit = !isAdmin(auth) && evmSwapAmount > 0;
93
+ if (needsEvmLimit) {
94
+ const reserve = reserveSpend(auth.tokenHash, auth.token, 'swap', evmSwapAmount, currency);
95
+ if (!reserve.ok) {
96
+ const remaining = getRemainingByType(auth.tokenHash, auth.token, 'swap', currency);
97
+ logger.limitExceeded(auth.token.agentId, 'swap', evmSwapAmount, remaining);
98
+ res.status(403).json({ error: 'Amount exceeds remaining swap limit', remaining: reserve.remaining, requested: evmSwapAmount });
99
+ return;
100
+ }
101
+ }
102
+
103
+ // Helper to roll back reserved spend on early exit or error
104
+ const evmRollback = () => {
105
+ if (needsEvmLimit) releaseSpend(auth.tokenHash, 'swap', evmSwapAmount, currency);
106
+ };
107
+
108
+ // Get DEX adapter
109
+ let adapter;
110
+ let detectedPool;
111
+ let version = requestedVersion;
112
+ let detectedFee = poolFee;
113
+ let detectedPoolKey = poolKey;
114
+
115
+ if (requestedDex) {
116
+ // Use specified DEX
117
+ adapter = getDexAdapter(requestedDex);
118
+ if (!adapter) {
119
+ evmRollback();
120
+ res.status(400).json({
121
+ error: `Unknown DEX: ${requestedDex}. Available: ${listDexes().join(', ')}`
122
+ });
123
+ return;
124
+ }
125
+
126
+ if (!adapter.supportsChain(chainConfig.chainId)) {
127
+ evmRollback();
128
+ res.status(400).json({ error: `${requestedDex} not supported on ${targetChain}` });
129
+ return;
130
+ }
131
+
132
+ // Detect pool if version not specified
133
+ if (!version) {
134
+ detectedPool = await adapter.detectPool(token, provider);
135
+ if (!detectedPool) {
136
+ evmRollback();
137
+ res.status(400).json({ error: `No ${requestedDex} pool found for this token` });
138
+ return;
139
+ }
140
+ version = detectedPool.version;
141
+ detectedFee = detectedPool.fee;
142
+ detectedPoolKey = detectedPool.poolKey;
143
+ }
144
+ } else {
145
+ // Auto-detect best DEX
146
+ const result = await detectBestDex(token, provider, chainConfig.chainId);
147
+ if (!result) {
148
+ evmRollback();
149
+ res.status(400).json({ error: 'No liquidity pool found for this token' });
150
+ return;
151
+ }
152
+ adapter = result.adapter;
153
+ detectedPool = result.pool;
154
+ // Only use detected version if user didn't specify one
155
+ if (!version) {
156
+ version = detectedPool.version;
157
+ }
158
+ if (!detectedFee) {
159
+ detectedFee = detectedPool.fee;
160
+ }
161
+ if (!detectedPoolKey) {
162
+ detectedPoolKey = detectedPool.poolKey;
163
+ }
164
+ }
165
+
166
+ // V4 requires poolKey (Uniswap-specific, skip for aggregators like Relay)
167
+ if (adapter.name === 'uniswap' && version === 'v4' && !detectedPoolKey) {
168
+ // 1. Try specified hook first
169
+ if (requestedHook) {
170
+ detectedPoolKey = getV4PoolKey(token, requestedHook);
171
+ if (!detectedPoolKey) {
172
+ evmRollback();
173
+ res.status(400).json({
174
+ error: `Unknown hook: ${requestedHook}. Known hooks: ${getKnownV4Hooks().join(', ')}, none`
175
+ });
176
+ return;
177
+ }
178
+ }
179
+
180
+ // 2. Try known hooks (clanker, zora, etc.)
181
+ if (!detectedPoolKey) {
182
+ for (const hookName of getKnownV4Hooks()) {
183
+ detectedPoolKey = getV4PoolKey(token, hookName);
184
+ if (detectedPoolKey) break;
185
+ }
186
+ }
187
+
188
+ // 3. Try no-hook pools with common fee/tickSpacing combos
189
+ if (!detectedPoolKey) {
190
+ detectedPoolKey = getV4PoolKey(token, 'none');
191
+ }
192
+
193
+ // 4. Event lookup fallback (slow, requires RPC)
194
+ if (!detectedPoolKey) {
195
+ detectedPoolKey = await detectV4PoolFromEvents(token, provider);
196
+ }
197
+
198
+ if (!detectedPoolKey) {
199
+ evmRollback();
200
+ res.status(400).json({
201
+ error: `V4 pool not found. Provide poolKey or specify a known hook. Known hooks: ${getKnownV4Hooks().join(', ')}, none`
202
+ });
203
+ return;
204
+ }
205
+ }
206
+
207
+ // Enforce slippage floors
208
+ const minSlippage = isAdmin(auth)
209
+ ? await getDefault<number>('swap.min_slippage_admin', 0.5)
210
+ : await getDefault<number>('swap.min_slippage_agent', 1.0);
211
+ let effectiveSlippage = slippage;
212
+ if (effectiveSlippage !== undefined && effectiveSlippage !== null) {
213
+ if (effectiveSlippage < minSlippage) {
214
+ effectiveSlippage = minSlippage;
215
+ }
216
+ }
217
+
218
+ // Calculate finalMinOut — amount is already in wei, use BigInt directly
219
+ let finalMinOut: string;
220
+ if (effectiveSlippage !== undefined && effectiveSlippage !== null) {
221
+ const amountBig = BigInt(amount);
222
+ const slippageBps = BigInt(Math.floor(effectiveSlippage * 100)); // basis points
223
+ const floorMinOut = amountBig - (amountBig * slippageBps / 10000n);
224
+
225
+ if (minOut && minOut !== '0') {
226
+ // Caller provided explicit minOut - enforce it's not below the slippage floor
227
+ const explicitMinOut = BigInt(minOut);
228
+ if (explicitMinOut < floorMinOut) {
229
+ evmRollback();
230
+ res.status(400).json({
231
+ error: `minOut too low: ${minOut} is below the ${effectiveSlippage}% slippage floor (min: ${floorMinOut.toString()})`
232
+ });
233
+ return;
234
+ }
235
+ finalMinOut = minOut;
236
+ } else {
237
+ finalMinOut = floorMinOut.toString();
238
+ }
239
+ } else if (minOut && minOut !== '0') {
240
+ // No slippage param but explicit minOut - validate against the floor slippage
241
+ const amountBig = BigInt(amount);
242
+ const floorBps = BigInt(Math.floor(minSlippage * 100));
243
+ const floorMinOut = amountBig - (amountBig * floorBps / 10000n);
244
+ const explicitMinOut = BigInt(minOut);
245
+ if (explicitMinOut < floorMinOut) {
246
+ evmRollback();
247
+ res.status(400).json({
248
+ error: `minOut too low: ${minOut} is below the ${minSlippage}% slippage floor (min: ${floorMinOut.toString()})`
249
+ });
250
+ return;
251
+ }
252
+ finalMinOut = minOut;
253
+ } else {
254
+ evmRollback();
255
+ res.status(400).json({ error: 'slippage is required (percentage, e.g. 1.0 for 1%)' });
256
+ return;
257
+ }
258
+
259
+ // Build the swap transaction
260
+ const swapTxData = await adapter.buildSwapTx({
261
+ token,
262
+ direction,
263
+ amount,
264
+ minOut: finalMinOut,
265
+ from,
266
+ chainId: chainConfig.chainId,
267
+ destinationChainId,
268
+ version,
269
+ fee: detectedFee,
270
+ poolKey: detectedPoolKey
271
+ });
272
+
273
+ // Build transaction object
274
+ const tx: ethers.TransactionRequest = {
275
+ from,
276
+ to: swapTxData.to,
277
+ data: swapTxData.data,
278
+ value: BigInt(swapTxData.value)
279
+ };
280
+
281
+ // Sign and send
282
+ let txHash: string;
283
+
284
+ try {
285
+ if (hotWallet) {
286
+ const result = await signWithHotWallet(from, tx, provider);
287
+ txHash = result.hash;
288
+ } else if (tempWallet) {
289
+ txHash = await signWithTempWallet(from, tx, provider);
290
+ } else {
291
+ evmRollback();
292
+ res.status(404).json({ error: 'Wallet not found' });
293
+ return;
294
+ }
295
+ } catch (err) {
296
+ evmRollback();
297
+ throw err;
298
+ }
299
+
300
+ // Log the swap
301
+ const versionStr = version ? version.toUpperCase() : '';
302
+ const description = userDescription || (direction === 'buy'
303
+ ? `Bought ${token} with ${amount} ETH via ${adapter.name} ${versionStr}`
304
+ : `Sold ${amount} tokens of ${token} for ETH via ${adapter.name} ${versionStr}`);
305
+
306
+ await recordTransaction({
307
+ walletAddress: from,
308
+ txHash,
309
+ type: 'swap',
310
+ amount: direction === 'buy' ? amount : undefined,
311
+ tokenAddress: token,
312
+ tokenAmount: direction === 'sell' ? amount : undefined,
313
+ from,
314
+ to: swapTxData.to || adapter.getRouterAddress(),
315
+ description,
316
+ chain: targetChain,
317
+ logTitle: `Swap ${direction === 'buy' ? 'Buy' : 'Sell'}`,
318
+ });
319
+
320
+ // Auto-track the swapped token (save pool info for price lookup)
321
+ await autoTrackToken({
322
+ walletAddress: from,
323
+ tokenAddress: token,
324
+ chain: targetChain,
325
+ poolAddress: detectedPool?.poolAddress,
326
+ poolVersion: detectedPool?.version,
327
+ });
328
+
329
+ // Log swap event
330
+ const agentId = !isAdmin(auth) ? auth.token.agentId : undefined;
331
+ const fromToken = direction === 'buy' ? 'ETH' : token.slice(0, 10);
332
+ const toToken = direction === 'buy' ? token.slice(0, 10) : 'ETH';
333
+ logger.swap(from, fromToken, toToken, amount, txHash, agentId);
334
+
335
+ const remaining = isAdmin(auth)
336
+ ? Infinity
337
+ : getRemainingByType(auth.tokenHash, auth.token, 'swap', currency);
338
+
339
+ res.json({
340
+ success: true,
341
+ hash: txHash,
342
+ from,
343
+ token,
344
+ direction,
345
+ amountIn: amount,
346
+ dex: adapter.name,
347
+ version,
348
+ chain: targetChain,
349
+ ...(targetChainOut && { chainOut: targetChainOut }),
350
+ router: adapter.getRouterAddress(),
351
+ remaining
352
+ });
353
+ }
@@ -0,0 +1,177 @@
1
+ import { Request, Response } from 'express';
2
+ import { PublicKey, VersionedTransaction } from '@solana/web3.js';
3
+ import { getHotWallet, tokenCanAccessWallet } from '../lib/hot';
4
+ import { hasTempWallet, getTempSolanaKeypair } from '../lib/temp';
5
+ import { isUnlocked } from '../lib/cold';
6
+ import { logger } from '../lib/logger';
7
+ import { isAdmin } from '../lib/permissions';
8
+ import { hasAnyPermission } from '../lib/permissions';
9
+ import { reserveSpend, releaseSpend, getRemainingByType } from '../lib/sessions';
10
+ import { getDefaultSync } from '../lib/defaults';
11
+ import { getNativeAddress, getNativeCurrency, NATIVE_ADDRESSES } from '../lib/address';
12
+ import { getSolanaConnection } from '../lib/solana/connection';
13
+ import { getSolanaKeypair } from '../lib/solana/wallet';
14
+ import { recordTransaction, autoTrackToken } from '../lib/transactions';
15
+ import { executeJupiterSwap } from '../lib/solana/jupiter';
16
+ import { AuthInfo } from '../middleware/auth';
17
+ import { ChainConfig } from '../lib/config';
18
+
19
+ export async function handleSolanaSwap(
20
+ req: Request,
21
+ res: Response,
22
+ auth: AuthInfo,
23
+ targetChain: string,
24
+ _chainConfig: ChainConfig
25
+ ): Promise<void> {
26
+ const { from, token, direction, amount, slippage, chainOut, description: userDescription } = req.body;
27
+
28
+ if (chainOut) {
29
+ res.status(400).json({ error: 'Cross-chain swaps are not supported on Solana' });
30
+ return;
31
+ }
32
+
33
+ const currency = getNativeAddress(targetChain);
34
+ const nativeCurrency = getNativeCurrency(targetChain);
35
+
36
+ // Determine wallet
37
+ const hotWallet = await getHotWallet(from);
38
+ const isTempWallet = hasTempWallet(from);
39
+
40
+ if (!hotWallet && !isTempWallet) {
41
+ res.status(404).json({ error: 'Wallet not found' });
42
+ return;
43
+ }
44
+
45
+ // Permission checks
46
+ if (!isAdmin(auth) && !hasAnyPermission(auth.token.permissions, ['swap'])) {
47
+ logger.permissionDenied('swap', auth.token.agentId, '/swap');
48
+ res.status(403).json({ error: 'Token does not have swap permission' });
49
+ return;
50
+ }
51
+
52
+ if (hotWallet) {
53
+ const canAccess = await tokenCanAccessWallet(auth.tokenHash, auth.token.walletAccess, from, targetChain);
54
+ if (!isAdmin(auth) && !canAccess) {
55
+ logger.permissionDenied('wallet_access', auth.token.agentId, '/swap');
56
+ res.status(403).json({ error: 'Token does not have access to this wallet' });
57
+ return;
58
+ }
59
+ if (!isUnlocked()) {
60
+ logger.authFailed('Cold wallet locked', '/swap');
61
+ res.status(401).json({ error: 'Cold wallet must be unlocked to send from hot wallet' });
62
+ return;
63
+ }
64
+ }
65
+
66
+ // Reserve spending atomically (prevents TOCTOU race between concurrent requests)
67
+ const swapAmountSol = Number(BigInt(amount)) / 1e9;
68
+ const needsSolLimit = !isAdmin(auth) && swapAmountSol > 0;
69
+ if (needsSolLimit) {
70
+ const reserve = reserveSpend(auth.tokenHash, auth.token, 'swap', swapAmountSol, currency);
71
+ if (!reserve.ok) {
72
+ logger.limitExceeded(auth.token.agentId, 'swap', swapAmountSol, reserve.remaining);
73
+ res.status(403).json({ error: 'Amount exceeds remaining swap limit', remaining: reserve.remaining, requested: swapAmountSol });
74
+ return;
75
+ }
76
+ }
77
+
78
+ // Jupiter swap
79
+ const connection = await getSolanaConnection(targetChain);
80
+ const inputMint = direction === 'buy' ? NATIVE_ADDRESSES.SOL : token;
81
+ const outputMint = direction === 'buy' ? token : NATIVE_ADDRESSES.SOL;
82
+
83
+ // Amount is already in lamports (buy) or raw token amount (sell)
84
+ const amountRaw = amount;
85
+
86
+ // Calculate slippage in bps
87
+ const effectiveSlippage = slippage ?? getDefaultSync<number>('swap.min_slippage_agent', 1.0);
88
+ const slippageBps = Math.round(effectiveSlippage * 100);
89
+
90
+ let userPubkey: PublicKey;
91
+ try {
92
+ userPubkey = new PublicKey(from);
93
+ } catch {
94
+ if (needsSolLimit) releaseSpend(auth.tokenHash, 'swap', swapAmountSol, currency);
95
+ res.status(400).json({ error: 'Invalid Solana address' });
96
+ return;
97
+ }
98
+
99
+ // Get signer
100
+ let signerKeypair;
101
+ if (hotWallet) {
102
+ signerKeypair = await getSolanaKeypair(from);
103
+ } else {
104
+ signerKeypair = getTempSolanaKeypair(from);
105
+ if (!signerKeypair) {
106
+ if (needsSolLimit) releaseSpend(auth.tokenHash, 'swap', swapAmountSol, currency);
107
+ res.status(404).json({ error: 'Temp Solana wallet not found' });
108
+ return;
109
+ }
110
+ }
111
+
112
+ let signature: string;
113
+ try {
114
+ const result = await executeJupiterSwap(
115
+ connection,
116
+ inputMint,
117
+ outputMint,
118
+ amountRaw,
119
+ slippageBps,
120
+ userPubkey,
121
+ async (tx: VersionedTransaction) => {
122
+ tx.sign([signerKeypair!]);
123
+ return tx;
124
+ }
125
+ );
126
+ signature = result.signature;
127
+ } catch (err) {
128
+ if (needsSolLimit) releaseSpend(auth.tokenHash, 'swap', swapAmountSol, currency);
129
+ throw err;
130
+ }
131
+
132
+ // Log
133
+ const description = userDescription || (direction === 'buy'
134
+ ? `Bought ${token} with ${amount} ${nativeCurrency} via Jupiter`
135
+ : `Sold ${amount} tokens of ${token} for ${nativeCurrency} via Jupiter`);
136
+
137
+ await recordTransaction({
138
+ walletAddress: from,
139
+ txHash: signature,
140
+ type: 'swap',
141
+ amount: direction === 'buy' ? amount : undefined,
142
+ tokenAddress: token,
143
+ tokenAmount: direction === 'sell' ? amount : undefined,
144
+ from,
145
+ to: 'jupiter',
146
+ description,
147
+ chain: targetChain,
148
+ logTitle: `Swap ${direction === 'buy' ? 'Buy' : 'Sell'}`,
149
+ });
150
+
151
+ await autoTrackToken({
152
+ walletAddress: from,
153
+ tokenAddress: token,
154
+ chain: targetChain,
155
+ });
156
+
157
+ const agentId = !isAdmin(auth) ? auth.token.agentId : undefined;
158
+ const fromToken = direction === 'buy' ? nativeCurrency : token.slice(0, 10);
159
+ const toToken = direction === 'buy' ? token.slice(0, 10) : nativeCurrency;
160
+ logger.swap(from, fromToken, toToken, amount, signature, agentId);
161
+
162
+ const remaining = isAdmin(auth)
163
+ ? Infinity
164
+ : getRemainingByType(auth.tokenHash, auth.token, 'swap', currency);
165
+
166
+ res.json({
167
+ success: true,
168
+ hash: signature,
169
+ from,
170
+ token,
171
+ direction,
172
+ amountIn: amount,
173
+ dex: 'jupiter',
174
+ chain: targetChain,
175
+ remaining
176
+ });
177
+ }