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,59 @@
1
+ import { ethers } from 'ethers';
2
+
3
+ // V4 PoolKey type (Uniswap V4 specific, but may be reused)
4
+ export interface PoolKey {
5
+ currency0: string;
6
+ currency1: string;
7
+ fee: number;
8
+ tickSpacing: number;
9
+ hooks: string;
10
+ }
11
+
12
+ // Pool detection result
13
+ export interface PoolInfo {
14
+ version: string; // e.g., "v2", "v3", "v4", "stable", "volatile"
15
+ fee?: number; // Fee tier if applicable
16
+ poolKey?: PoolKey; // V4 pool key if applicable
17
+ poolAddress?: string; // Pool/pair address if known
18
+ }
19
+
20
+ // Swap parameters (DEX-agnostic)
21
+ export interface SwapParams {
22
+ token: string;
23
+ direction: 'buy' | 'sell';
24
+ amount: string; // In wei (EVM) or lamports (Solana)
25
+ minOut: string;
26
+ from: string; // Sender wallet address
27
+ chainId: number; // EVM chain ID
28
+ destinationChainId?: number; // Cross-chain destination (defaults to chainId)
29
+ version?: string; // Pool version override
30
+ fee?: number; // Fee tier override
31
+ poolKey?: PoolKey; // V4 pool key override
32
+ }
33
+
34
+ // Transaction data result
35
+ export interface SwapTxData {
36
+ to: string;
37
+ data: string;
38
+ value: string; // In wei
39
+ }
40
+
41
+ // DEX Adapter interface
42
+ export interface DexAdapter {
43
+ name: string;
44
+
45
+ // Supported on this chain?
46
+ supportsChain(chainId: number): boolean;
47
+
48
+ // Detect if a pool exists for this token
49
+ detectPool(
50
+ token: string,
51
+ provider: ethers.Provider
52
+ ): Promise<PoolInfo | null>;
53
+
54
+ // Build swap transaction
55
+ buildSwapTx(params: SwapParams): Promise<SwapTxData>;
56
+
57
+ // Get the router/helper contract address
58
+ getRouterAddress(): string;
59
+ }
@@ -0,0 +1,370 @@
1
+ import { ethers } from 'ethers';
2
+ import { DexAdapter, PoolInfo, SwapParams, SwapTxData, PoolKey } from './types';
3
+ import SwapHelperABI from '../../abi/SwapHelper.json';
4
+
5
+ // Constants
6
+ export const SWAP_HELPER = '0xD28f98c89d6F88762377b400936b434731c8a61F'; // SwapHelperV2 (Universal Router)
7
+ export const WETH = '0x4200000000000000000000000000000000000006';
8
+
9
+ // Uniswap Factory addresses on Base
10
+ const V3_FACTORY = '0x33128a8fC17869897dcE68Ed026d694621f6FDfD';
11
+ const V2_FACTORY = '0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6';
12
+
13
+ // V4 PoolManager address fallback (query from SwapHelper.POOL_MANAGER() at runtime)
14
+ const V4_POOL_MANAGER_FALLBACK = '0x6Ab04E3376fB1d12cC0b27E6F2E7485CC8bFCb53';
15
+
16
+ // Initialize event topic for V4 PoolManager
17
+ const INITIALIZE_EVENT_TOPIC = '0x803151a295203f64f7e2ca2db584660e99eaf67eca6f05af1bf0707e7d38f2cf';
18
+
19
+ // V3 fee tiers to check (ordered by likelihood)
20
+ const V3_FEE_TIERS = [3000, 10000, 500] as const;
21
+
22
+ // Known V4 hooks (Base mainnet)
23
+ export const V4_HOOKS: Record<string, string> = {
24
+ clanker: '0x1F98400000000000000000000000000000000004',
25
+ 'clanker-dynamic-fee-v2': '0xd60D6B218116cFd801E28F78d011a203D2b068Cc',
26
+ 'clanker-static-fee-v2': '0xb429d62f8f3bFFb98CdB9569533eA23bF0Ba28CC',
27
+ 'clanker-4.0-a': '0x34a45c6B61876d739400Bd71228CbcbD4F53E8cC',
28
+ 'clanker-4.0-b': '0xDd5EeaFf7BD481AD55Db083062b13a3cdf0A68CC',
29
+ zora: '0xe2B4100DE1CD284Bd364f738d1354715515C90C0',
30
+ // doppler: per-token hooks, must provide poolKey manually
31
+ };
32
+
33
+ // Minimal V3 Factory ABI
34
+ const V3_FACTORY_ABI = [
35
+ 'function getPool(address tokenA, address tokenB, uint24 fee) view returns (address pool)'
36
+ ];
37
+
38
+ // Minimal V2 Factory ABI
39
+ const V2_FACTORY_ABI = [
40
+ 'function getPair(address tokenA, address tokenB) view returns (address pair)'
41
+ ];
42
+
43
+ // DYNAMIC_FEE_FLAG from Uniswap V4 LPFeeLibrary
44
+ const DYNAMIC_FEE_FLAG = 0x800000;
45
+
46
+ // Standard V4 pool params for known hooks
47
+ const V4_POOL_PARAMS: Record<string, { fee: number; tickSpacing: number }> = {
48
+ clanker: { fee: 10000, tickSpacing: 200 }, // 1% (legacy)
49
+ 'clanker-dynamic-fee-v2': { fee: DYNAMIC_FEE_FLAG, tickSpacing: 200 }, // v4.1.0
50
+ 'clanker-static-fee-v2': { fee: DYNAMIC_FEE_FLAG, tickSpacing: 200 }, // v4.1.0
51
+ 'clanker-4.0-a': { fee: DYNAMIC_FEE_FLAG, tickSpacing: 200 }, // v4.0.0 dynamic
52
+ 'clanker-4.0-b': { fee: DYNAMIC_FEE_FLAG, tickSpacing: 200 }, // v4.0.0 static
53
+ zora: { fee: 10000, tickSpacing: 200 }, // 1%
54
+ };
55
+
56
+ // Common no-hook pool params to try (ordered by likelihood)
57
+ const V4_NO_HOOK_PARAMS = [
58
+ { fee: 3000, tickSpacing: 60 }, // 0.3%
59
+ { fee: 10000, tickSpacing: 200 }, // 1%
60
+ { fee: 500, tickSpacing: 10 }, // 0.05%
61
+ ];
62
+
63
+ /**
64
+ * Get list of known V4 hook names
65
+ */
66
+ export function getKnownV4Hooks(): string[] {
67
+ return Object.keys(V4_HOOKS);
68
+ }
69
+
70
+ /**
71
+ * Get the PoolManager address from SwapHelper contract
72
+ */
73
+ async function getPoolManagerAddress(provider: ethers.Provider): Promise<string> {
74
+ const swapHelper = new ethers.Contract(
75
+ SWAP_HELPER,
76
+ ['function POOL_MANAGER() view returns (address)'],
77
+ provider
78
+ );
79
+ try {
80
+ return await swapHelper.POOL_MANAGER();
81
+ } catch {
82
+ return V4_POOL_MANAGER_FALLBACK;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Build a PoolKey for known V4 hooks or no-hook pools
88
+ *
89
+ * @param token - Token address
90
+ * @param hookName - Hook name ('clanker', 'zora', etc.) or 'none' for no-hook pools
91
+ * @returns PoolKey or null if hook not found
92
+ */
93
+ export function getV4PoolKey(token: string, hookName?: string): PoolKey | null {
94
+ if (!hookName) return null;
95
+
96
+ const name = hookName.toLowerCase();
97
+
98
+ // Handle 'none' - return first no-hook params (caller can iterate if needed)
99
+ if (name === 'none') {
100
+ const params = V4_NO_HOOK_PARAMS[0];
101
+ const [currency0, currency1] = token.toLowerCase() < WETH.toLowerCase()
102
+ ? [token, WETH]
103
+ : [WETH, token];
104
+
105
+ return {
106
+ currency0,
107
+ currency1,
108
+ fee: params.fee,
109
+ tickSpacing: params.tickSpacing,
110
+ hooks: ethers.ZeroAddress
111
+ };
112
+ }
113
+
114
+ const hookAddress = V4_HOOKS[name];
115
+ const params = V4_POOL_PARAMS[name];
116
+
117
+ if (!hookAddress || !params) return null;
118
+
119
+ const [currency0, currency1] = token.toLowerCase() < WETH.toLowerCase()
120
+ ? [token, WETH]
121
+ : [WETH, token];
122
+
123
+ return {
124
+ currency0,
125
+ currency1,
126
+ fee: params.fee,
127
+ tickSpacing: params.tickSpacing,
128
+ hooks: hookAddress
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Get all possible no-hook PoolKeys for a token
134
+ */
135
+ export function getV4NoHookPoolKeys(token: string): PoolKey[] {
136
+ const [currency0, currency1] = token.toLowerCase() < WETH.toLowerCase()
137
+ ? [token, WETH]
138
+ : [WETH, token];
139
+
140
+ return V4_NO_HOOK_PARAMS.map(params => ({
141
+ currency0,
142
+ currency1,
143
+ fee: params.fee,
144
+ tickSpacing: params.tickSpacing,
145
+ hooks: ethers.ZeroAddress
146
+ }));
147
+ }
148
+
149
+ /**
150
+ * Detect V4 pool by querying PoolManager Initialize events
151
+ *
152
+ * @param token - Token address to search for
153
+ * @param provider - Ethers provider
154
+ * @returns PoolKey if found, null otherwise
155
+ */
156
+ export async function detectV4PoolFromEvents(
157
+ token: string,
158
+ provider: ethers.Provider
159
+ ): Promise<PoolKey | null> {
160
+ try {
161
+ const poolManager = await getPoolManagerAddress(provider);
162
+ const tokenPadded = ethers.zeroPadValue(token.toLowerCase(), 32);
163
+
164
+ // Query with token as currency0
165
+ const logs0 = await provider.getLogs({
166
+ address: poolManager,
167
+ topics: [
168
+ INITIALIZE_EVENT_TOPIC,
169
+ null, // poolId - any
170
+ tokenPadded, // currency0
171
+ null // currency1 - any
172
+ ],
173
+ fromBlock: 0
174
+ });
175
+
176
+ // Query with token as currency1
177
+ const logs1 = await provider.getLogs({
178
+ address: poolManager,
179
+ topics: [
180
+ INITIALIZE_EVENT_TOPIC,
181
+ null,
182
+ null,
183
+ tokenPadded // currency1
184
+ ],
185
+ fromBlock: 0
186
+ });
187
+
188
+ const allLogs = [...logs0, ...logs1];
189
+ if (allLogs.length === 0) return null;
190
+
191
+ // Parse the first matching event
192
+ const log = allLogs[0];
193
+
194
+ // Extract currency0 and currency1 from indexed topics
195
+ const currency0 = ethers.getAddress('0x' + log.topics[2].slice(26));
196
+ const currency1 = ethers.getAddress('0x' + log.topics[3].slice(26));
197
+
198
+ // Decode the data field: fee (uint24), tickSpacing (int24), hooks (address), sqrtPriceX96 (uint160), tick (int24)
199
+ const decoded = ethers.AbiCoder.defaultAbiCoder().decode(
200
+ ['uint24', 'int24', 'address', 'uint160', 'int24'],
201
+ log.data
202
+ );
203
+ const [fee, tickSpacing, hooks] = decoded;
204
+
205
+ return {
206
+ currency0,
207
+ currency1,
208
+ fee: Number(fee),
209
+ tickSpacing: Number(tickSpacing),
210
+ hooks
211
+ };
212
+ } catch {
213
+ // RPC errors, parsing errors, etc. - return null
214
+ return null;
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Uniswap adapter for V2/V3/V4 swaps via SwapHelper contract
220
+ */
221
+ export const uniswapAdapter: DexAdapter = {
222
+ name: 'uniswap',
223
+
224
+ supportsChain(chainId: number): boolean {
225
+ // SwapHelper is deployed on Base
226
+ return chainId === 8453;
227
+ },
228
+
229
+ async detectPool(
230
+ token: string,
231
+ provider: ethers.Provider
232
+ ): Promise<PoolInfo | null> {
233
+ // Priority: V4 with known hooks -> V3 -> V2 -> other V4 pools
234
+
235
+ // 1. Try V4 with known hooks first
236
+ const v4PoolKey = await detectV4PoolFromEvents(token, provider);
237
+ if (v4PoolKey) {
238
+ // Check if this pool uses a known hook
239
+ const knownHookAddresses = Object.values(V4_HOOKS).map(h => h.toLowerCase());
240
+ const hasKnownHook = v4PoolKey.hooks !== ethers.ZeroAddress &&
241
+ knownHookAddresses.includes(v4PoolKey.hooks.toLowerCase());
242
+
243
+ if (hasKnownHook) {
244
+ return { version: 'v4', poolKey: v4PoolKey };
245
+ }
246
+ }
247
+
248
+ // 2. Check V3 pools
249
+ const v3Factory = new ethers.Contract(V3_FACTORY, V3_FACTORY_ABI, provider);
250
+ for (const fee of V3_FEE_TIERS) {
251
+ try {
252
+ const pool = await v3Factory.getPool(WETH, token, fee);
253
+ if (pool && pool !== ethers.ZeroAddress) {
254
+ return { version: 'v3', fee, poolAddress: pool };
255
+ }
256
+ } catch {
257
+ // Pool doesn't exist for this fee tier
258
+ }
259
+ }
260
+
261
+ // 3. Check V2 pair
262
+ const v2Factory = new ethers.Contract(V2_FACTORY, V2_FACTORY_ABI, provider);
263
+ try {
264
+ const pair = await v2Factory.getPair(WETH, token);
265
+ if (pair && pair !== ethers.ZeroAddress) {
266
+ return { version: 'v2', poolAddress: pair };
267
+ }
268
+ } catch {
269
+ // No V2 pair
270
+ }
271
+
272
+ // 4. Return other V4 pool (unknown hooks or no hooks) as fallback
273
+ if (v4PoolKey) {
274
+ return { version: 'v4', poolKey: v4PoolKey };
275
+ }
276
+
277
+ return null;
278
+ },
279
+
280
+ buildSwapTx(params: SwapParams): SwapTxData {
281
+ const { token, direction, amount, minOut, version, fee, poolKey } = params;
282
+
283
+ const swapHelper = new ethers.Interface(SwapHelperABI);
284
+
285
+ if (direction === 'buy') {
286
+ return buildBuyTx(swapHelper, token, amount, minOut, version || 'v3', fee, poolKey);
287
+ } else {
288
+ return buildSellTx(swapHelper, token, amount, minOut, version || 'v3', fee, poolKey);
289
+ }
290
+ },
291
+
292
+ getRouterAddress(): string {
293
+ return SWAP_HELPER;
294
+ }
295
+ };
296
+
297
+ function buildBuyTx(
298
+ iface: ethers.Interface,
299
+ token: string,
300
+ amount: string,
301
+ minOut: string,
302
+ version: string,
303
+ poolFee?: number,
304
+ poolKey?: PoolKey
305
+ ): SwapTxData {
306
+ const valueWei = BigInt(amount);
307
+ const minOutBn = BigInt(minOut);
308
+
309
+ let data: string;
310
+
311
+ if (version === 'v2') {
312
+ data = iface.encodeFunctionData('snipeV2', [token, minOutBn]);
313
+ } else if (version === 'v3') {
314
+ const fee = poolFee || 3000;
315
+ data = iface.encodeFunctionData('snipeV3', [token, fee, minOutBn]);
316
+ } else if (version === 'v4') {
317
+ if (!poolKey) throw new Error('V4 pool key required');
318
+ data = iface.encodeFunctionData('snipeV4', [
319
+ [poolKey.currency0, poolKey.currency1, poolKey.fee, poolKey.tickSpacing, poolKey.hooks],
320
+ minOutBn
321
+ ]);
322
+ } else {
323
+ throw new Error(`Invalid Uniswap version: ${version}`);
324
+ }
325
+
326
+ return {
327
+ to: SWAP_HELPER,
328
+ data,
329
+ value: valueWei.toString()
330
+ };
331
+ }
332
+
333
+ function buildSellTx(
334
+ iface: ethers.Interface,
335
+ token: string,
336
+ amount: string,
337
+ minOut: string,
338
+ version: string,
339
+ poolFee?: number,
340
+ poolKey?: PoolKey
341
+ ): SwapTxData {
342
+ const amountIn = BigInt(amount);
343
+ const minOutBn = BigInt(minOut);
344
+
345
+ let data: string;
346
+
347
+ if (version === 'v2') {
348
+ data = iface.encodeFunctionData('sellV2', [token, amountIn, minOutBn]);
349
+ } else if (version === 'v3') {
350
+ const fee = poolFee || 3000;
351
+ data = iface.encodeFunctionData('sellV3', [token, fee, amountIn, minOutBn]);
352
+ } else if (version === 'v4') {
353
+ if (!poolKey) throw new Error('V4 pool key required');
354
+ data = iface.encodeFunctionData('sellV4', [
355
+ [poolKey.currency0, poolKey.currency1, poolKey.fee, poolKey.tickSpacing, poolKey.hooks],
356
+ amountIn,
357
+ minOutBn
358
+ ]);
359
+ } else {
360
+ throw new Error(`Invalid Uniswap version: ${version}`);
361
+ }
362
+
363
+ return {
364
+ to: SWAP_HELPER,
365
+ data,
366
+ value: '0'
367
+ };
368
+ }
369
+
370
+ export default uniswapAdapter;
@@ -0,0 +1,36 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { RunFingerprint } from './contracts';
4
+
5
+ export interface SummaryArtifact {
6
+ runId: string;
7
+ status: 'passed' | 'failed';
8
+ scenarioId: string;
9
+ runFingerprint: RunFingerprint;
10
+ }
11
+
12
+ export interface ReplayManifest {
13
+ runId: string;
14
+ scenarioId: string;
15
+ runFingerprint: RunFingerprint;
16
+ replayCommand: string;
17
+ }
18
+
19
+ export function writeArtifacts(baseDir: string, summary: SummaryArtifact): { summaryPath: string; manifestPath: string } {
20
+ fs.mkdirSync(baseDir, { recursive: true });
21
+
22
+ const summaryPath = path.join(baseDir, 'summary.json');
23
+ fs.writeFileSync(summaryPath, JSON.stringify(summary, null, 2));
24
+
25
+ const manifest: ReplayManifest = {
26
+ runId: summary.runId,
27
+ scenarioId: summary.scenarioId,
28
+ runFingerprint: summary.runFingerprint,
29
+ replayCommand: `npx tsx server/tests/e2e-agent/runner.ts replay --run ${summary.runId}`,
30
+ };
31
+
32
+ const manifestPath = path.join(baseDir, 'replay.manifest.json');
33
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
34
+
35
+ return { summaryPath, manifestPath };
36
+ }
@@ -0,0 +1,112 @@
1
+ import { z } from 'zod';
2
+
3
+ export const E2E_AGENT_ERROR_CODES = {
4
+ schemaAssertion: 'E_SCHEMA_ASSERTION',
5
+ egressPolicy: 'E_EGRESS_POLICY',
6
+ clockDrift: 'E_CLOCK_DRIFT',
7
+ budgetDuration: 'E_BUDGET_DURATION',
8
+ budgetSteps: 'E_BUDGET_STEPS',
9
+ budgetToolCalls: 'E_BUDGET_TOOL_CALLS',
10
+ budgetTokens: 'E_BUDGET_TOKENS',
11
+ } as const;
12
+
13
+ const assertionBaseSchema = z.object({
14
+ id: z.string().min(1),
15
+ type: z.enum(['ui', 'api', 'db']),
16
+ op: z.enum(['equals', 'contains', 'exists', 'not_exists', 'gt', 'gte', 'lt', 'lte']),
17
+ });
18
+
19
+ const uiAssertionSchema = assertionBaseSchema.extend({
20
+ type: z.literal('ui'),
21
+ selector: z.string().min(1),
22
+ expected: z.union([z.string(), z.number(), z.boolean()]).optional(),
23
+ });
24
+
25
+ const apiAssertionSchema = assertionBaseSchema.extend({
26
+ type: z.literal('api'),
27
+ endpoint: z.string().min(1),
28
+ path: z.string().min(1),
29
+ expected: z.union([z.string(), z.number(), z.boolean()]).optional(),
30
+ });
31
+
32
+ const dbAssertionSchema = assertionBaseSchema.extend({
33
+ type: z.literal('db'),
34
+ query: z.string().min(1),
35
+ expected: z.union([z.string(), z.number(), z.boolean()]).optional(),
36
+ });
37
+
38
+ export const assertionSchema = z.discriminatedUnion('type', [
39
+ uiAssertionSchema,
40
+ apiAssertionSchema,
41
+ dbAssertionSchema,
42
+ ]);
43
+
44
+ export const budgetSchema = z.object({
45
+ maxDurationSec: z.number().int().positive(),
46
+ maxSteps: z.number().int().positive(),
47
+ maxToolCalls: z.number().int().positive(),
48
+ maxTokens: z.number().int().positive(),
49
+ });
50
+
51
+ export const scenarioSchema = z.object({
52
+ id: z.string().min(1),
53
+ title: z.string().min(1),
54
+ mode: z.enum(['agent-hybrid', 'scripted']),
55
+ clock: z.object({
56
+ baseTimeIso: z.string().datetime(),
57
+ }),
58
+ budget: budgetSchema.partial().optional(),
59
+ assertions: z.array(assertionSchema).min(1),
60
+ });
61
+
62
+ export type Assertion = z.infer<typeof assertionSchema>;
63
+ export type Budget = z.infer<typeof budgetSchema>;
64
+ export type Scenario = z.infer<typeof scenarioSchema>;
65
+
66
+ export type Lane = 'pr-smoke' | 'nightly';
67
+
68
+ export interface LaneBudgetCaps {
69
+ maxDurationSec: number;
70
+ maxSteps: number;
71
+ maxToolCalls: number;
72
+ maxTokens: number;
73
+ }
74
+
75
+ export const REPO_DEFAULT_BUDGET: Budget = {
76
+ maxDurationSec: 90,
77
+ maxSteps: 15,
78
+ maxToolCalls: 12,
79
+ maxTokens: 12000,
80
+ };
81
+
82
+ export const LANE_BUDGET_CAPS: Record<Lane, LaneBudgetCaps> = {
83
+ 'pr-smoke': {
84
+ maxDurationSec: 120,
85
+ maxSteps: 20,
86
+ maxToolCalls: 16,
87
+ maxTokens: 16000,
88
+ },
89
+ nightly: {
90
+ maxDurationSec: 240,
91
+ maxSteps: 40,
92
+ maxToolCalls: 30,
93
+ maxTokens: 40000,
94
+ },
95
+ };
96
+
97
+ export interface ClockProbe {
98
+ runnerMs: number;
99
+ serverMs: number;
100
+ browserMs: number;
101
+ fixtureMs: number;
102
+ }
103
+
104
+ export interface RunFingerprint {
105
+ scenarioId: string;
106
+ lane: Lane;
107
+ mode: Scenario['mode'];
108
+ clockBaseTimeIso: string;
109
+ schemaVersion: string;
110
+ runnerVersion: string;
111
+ gitCommit: string;
112
+ }
@@ -0,0 +1,135 @@
1
+ import { parse as parseYaml } from 'yaml';
2
+ import {
3
+ Budget,
4
+ ClockProbe,
5
+ E2E_AGENT_ERROR_CODES,
6
+ LANE_BUDGET_CAPS,
7
+ Lane,
8
+ REPO_DEFAULT_BUDGET,
9
+ Scenario,
10
+ scenarioSchema,
11
+ } from './contracts';
12
+
13
+ export class E2EAgentValidationError extends Error {
14
+ constructor(public readonly code: string, message: string) {
15
+ super(message);
16
+ this.name = 'E2EAgentValidationError';
17
+ }
18
+ }
19
+
20
+ export function parseScenarioDocument(input: string): unknown {
21
+ try {
22
+ return JSON.parse(input);
23
+ } catch {
24
+ return parseYaml(input);
25
+ }
26
+ }
27
+
28
+ export function validateScenario(raw: unknown): Scenario {
29
+ const parsed = scenarioSchema.safeParse(raw);
30
+ if (!parsed.success) {
31
+ throw new E2EAgentValidationError(
32
+ E2E_AGENT_ERROR_CODES.schemaAssertion,
33
+ parsed.error.issues.map((issue) => issue.message).join('; ')
34
+ );
35
+ }
36
+
37
+ return parsed.data;
38
+ }
39
+
40
+ export function applyBudgetPolicy(scenarioBudget: Partial<Budget> | undefined, lane: Lane): Budget {
41
+ const merged: Budget = {
42
+ ...REPO_DEFAULT_BUDGET,
43
+ ...(scenarioBudget ?? {}),
44
+ };
45
+
46
+ const laneCaps = LANE_BUDGET_CAPS[lane];
47
+
48
+ if (merged.maxDurationSec > laneCaps.maxDurationSec) {
49
+ throw new E2EAgentValidationError(
50
+ E2E_AGENT_ERROR_CODES.budgetDuration,
51
+ `maxDurationSec (${merged.maxDurationSec}) exceeds lane cap (${laneCaps.maxDurationSec})`
52
+ );
53
+ }
54
+
55
+ if (merged.maxSteps > laneCaps.maxSteps) {
56
+ throw new E2EAgentValidationError(
57
+ E2E_AGENT_ERROR_CODES.budgetSteps,
58
+ `maxSteps (${merged.maxSteps}) exceeds lane cap (${laneCaps.maxSteps})`
59
+ );
60
+ }
61
+
62
+ if (merged.maxToolCalls > laneCaps.maxToolCalls) {
63
+ throw new E2EAgentValidationError(
64
+ E2E_AGENT_ERROR_CODES.budgetToolCalls,
65
+ `maxToolCalls (${merged.maxToolCalls}) exceeds lane cap (${laneCaps.maxToolCalls})`
66
+ );
67
+ }
68
+
69
+ if (merged.maxTokens > laneCaps.maxTokens) {
70
+ throw new E2EAgentValidationError(
71
+ E2E_AGENT_ERROR_CODES.budgetTokens,
72
+ `maxTokens (${merged.maxTokens}) exceeds lane cap (${laneCaps.maxTokens})`
73
+ );
74
+ }
75
+
76
+ return merged;
77
+ }
78
+
79
+ export function enforceClockDrift(probe: ClockProbe): void {
80
+ const values = [probe.runnerMs, probe.serverMs, probe.browserMs, probe.fixtureMs];
81
+ const drift = Math.max(...values) - Math.min(...values);
82
+
83
+ if (drift > 50) {
84
+ throw new E2EAgentValidationError(
85
+ E2E_AGENT_ERROR_CODES.clockDrift,
86
+ `Clock drift ${drift}ms exceeded tolerance`
87
+ );
88
+ }
89
+ }
90
+
91
+ export function enforceEgressPolicy(hostname: string, allowedHosts: readonly string[]): void {
92
+ if (!allowedHosts.includes(hostname)) {
93
+ throw new E2EAgentValidationError(
94
+ E2E_AGENT_ERROR_CODES.egressPolicy,
95
+ `Blocked outbound host: ${hostname}`
96
+ );
97
+ }
98
+ }
99
+
100
+ export interface RuntimeUsage {
101
+ durationSec: number;
102
+ steps: number;
103
+ toolCalls: number;
104
+ tokens: number;
105
+ }
106
+
107
+ export function enforceRuntimeBudget(usage: RuntimeUsage, budget: Budget): void {
108
+ if (usage.durationSec > budget.maxDurationSec) {
109
+ throw new E2EAgentValidationError(
110
+ E2E_AGENT_ERROR_CODES.budgetDuration,
111
+ `durationSec ${usage.durationSec} exceeded ${budget.maxDurationSec}`
112
+ );
113
+ }
114
+
115
+ if (usage.steps > budget.maxSteps) {
116
+ throw new E2EAgentValidationError(
117
+ E2E_AGENT_ERROR_CODES.budgetSteps,
118
+ `steps ${usage.steps} exceeded ${budget.maxSteps}`
119
+ );
120
+ }
121
+
122
+ if (usage.toolCalls > budget.maxToolCalls) {
123
+ throw new E2EAgentValidationError(
124
+ E2E_AGENT_ERROR_CODES.budgetToolCalls,
125
+ `toolCalls ${usage.toolCalls} exceeded ${budget.maxToolCalls}`
126
+ );
127
+ }
128
+
129
+ if (usage.tokens > budget.maxTokens) {
130
+ throw new E2EAgentValidationError(
131
+ E2E_AGENT_ERROR_CODES.budgetTokens,
132
+ `tokens ${usage.tokens} exceeded ${budget.maxTokens}`
133
+ );
134
+ }
135
+ }