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.
- package/LICENSE +26 -0
- package/README.md +112 -0
- package/bin/aurawallet.js +121 -0
- package/docs/ADAPTERS.md +467 -0
- package/docs/API.md +2679 -0
- package/docs/APPS.md +198 -0
- package/docs/ARCHITECTURE.md +350 -0
- package/docs/AUTH.md +698 -0
- package/docs/BEST-PRACTICES.md +121 -0
- package/docs/CLI.md +61 -0
- package/docs/DEVELOPING-APPS.md +452 -0
- package/docs/EXTENSION.md +97 -0
- package/docs/JOBS.md +33 -0
- package/docs/MCP.md +76 -0
- package/docs/PROTOCOL.md +142 -0
- package/docs/SETUP.md +219 -0
- package/docs/WORKSPACE.md +672 -0
- package/docs/agent-auth.md +63 -0
- package/docs/aura-file.md +48 -0
- package/docs/credentials.md +53 -0
- package/docs/external/getting-started.md +65 -0
- package/docs/external/overview.md +45 -0
- package/docs/external/use-cases.md +48 -0
- package/docs/external/why-aura.md +35 -0
- package/docs/jobs/connect-agent.md +77 -0
- package/docs/jobs/migrate-from-dotenv.md +79 -0
- package/docs/jobs/recover-from-lockout.md +72 -0
- package/docs/jobs/secure-ci.md +63 -0
- package/docs/oauth2.md +42 -0
- package/docs/passkeys.md +60 -0
- package/docs/security.md +540 -0
- package/docs/specs/aura-open-protocol.md +61 -0
- package/docs/specs/aura-provider-plugin.md +24 -0
- package/docs/specs/aura-registry-model.md +31 -0
- package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
- package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
- package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
- package/docs/specs/fixtures/valid-basic.aura +4 -0
- package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
- package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
- package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
- package/docs/totp.md +40 -0
- package/docs/wallet/AI.md +508 -0
- package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
- package/docs/wallet/README.md +47 -0
- package/docs/wallet/STRATEGY.md +89 -0
- package/next.config.ts +21 -0
- package/package.json +151 -0
- package/postcss.config.mjs +8 -0
- package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
- package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
- package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +447 -0
- package/public/logo-chevron.svg +31 -0
- package/public/logo-concentric.svg +31 -0
- package/public/logo-crosshatch.svg +39 -0
- package/public/logo-dashed.svg +39 -0
- package/public/logo-horizontal.svg +31 -0
- package/public/logo-m56.svg +64 -0
- package/public/logo.webp +0 -0
- package/scripts/add-app.js +245 -0
- package/scripts/init.sh +57 -0
- package/scripts/migrate-apikeys-to-credentials.ts +35 -0
- package/scripts/sandbox-agent-flow.sh +235 -0
- package/scripts/sandbox.sh +175 -0
- package/scripts/validate-job-docs.mjs +125 -0
- package/server/abi/SwapHelper.json +438 -0
- package/server/cli/approval.ts +447 -0
- package/server/cli/commands/app.ts +204 -0
- package/server/cli/commands/cron.ts +24 -0
- package/server/cli/commands/doctor.ts +1007 -0
- package/server/cli/commands/env.ts +456 -0
- package/server/cli/commands/init.ts +752 -0
- package/server/cli/commands/mcp.ts +125 -0
- package/server/cli/commands/restore.ts +314 -0
- package/server/cli/commands/shell-hook.ts +468 -0
- package/server/cli/commands/start.ts +62 -0
- package/server/cli/commands/status.ts +59 -0
- package/server/cli/commands/stop.ts +14 -0
- package/server/cli/commands/token.ts +180 -0
- package/server/cli/commands/unlock.ts +49 -0
- package/server/cli/commands/vault.ts +417 -0
- package/server/cli/index.ts +328 -0
- package/server/cli/lib/aura-parser.ts +64 -0
- package/server/cli/lib/credential-create.ts +74 -0
- package/server/cli/lib/credential-resolve.ts +254 -0
- package/server/cli/lib/dotenv-migrate.ts +116 -0
- package/server/cli/lib/dotenv-parser.ts +146 -0
- package/server/cli/lib/http.ts +91 -0
- package/server/cli/lib/init-steps.ts +76 -0
- package/server/cli/lib/local-agent-trust.ts +45 -0
- package/server/cli/lib/process.ts +136 -0
- package/server/cli/lib/prompt.ts +85 -0
- package/server/cli/lib/theme.ts +240 -0
- package/server/cli/socket.ts +570 -0
- package/server/cli/transport-client.ts +50 -0
- package/server/cron/index.ts +137 -0
- package/server/cron/job.ts +31 -0
- package/server/cron/jobs/balance-sync.ts +436 -0
- package/server/cron/jobs/incoming-scan.ts +506 -0
- package/server/cron/jobs/native-price.ts +70 -0
- package/server/cron/jobs/orphan-cleanup.ts +40 -0
- package/server/cron/jobs/strategy-runner.ts +175 -0
- package/server/cron/scheduler.ts +125 -0
- package/server/index.ts +406 -0
- package/server/lib/adapters/factory.ts +110 -0
- package/server/lib/adapters/index.ts +19 -0
- package/server/lib/adapters/router.ts +297 -0
- package/server/lib/adapters/telegram.ts +645 -0
- package/server/lib/adapters/types.ts +89 -0
- package/server/lib/adapters/webhook.ts +95 -0
- package/server/lib/address.ts +49 -0
- package/server/lib/agent-auth/contracts.ts +1194 -0
- package/server/lib/agent-profiles.ts +328 -0
- package/server/lib/ai.ts +285 -0
- package/server/lib/api-registry/contracts.ts +86 -0
- package/server/lib/api-registry/validation.ts +172 -0
- package/server/lib/apikey-migration.ts +189 -0
- package/server/lib/app-installer.ts +505 -0
- package/server/lib/app-tokens.ts +247 -0
- package/server/lib/auth.ts +314 -0
- package/server/lib/batch.ts +242 -0
- package/server/lib/cold.ts +874 -0
- package/server/lib/config.ts +381 -0
- package/server/lib/credential-access-audit.ts +85 -0
- package/server/lib/credential-access-policy.ts +110 -0
- package/server/lib/credential-health.ts +343 -0
- package/server/lib/credential-import.ts +487 -0
- package/server/lib/credential-scope.ts +87 -0
- package/server/lib/credential-shares.ts +190 -0
- package/server/lib/credential-transport.ts +342 -0
- package/server/lib/credential-vault.ts +77 -0
- package/server/lib/credentials.ts +333 -0
- package/server/lib/crypto.ts +8 -0
- package/server/lib/db.ts +15 -0
- package/server/lib/defaults.ts +366 -0
- package/server/lib/dex/index.ts +80 -0
- package/server/lib/dex/relay.ts +235 -0
- package/server/lib/dex/types.ts +59 -0
- package/server/lib/dex/uniswap.ts +370 -0
- package/server/lib/e2e-agent/artifacts.ts +36 -0
- package/server/lib/e2e-agent/contracts.ts +112 -0
- package/server/lib/e2e-agent/validation.ts +135 -0
- package/server/lib/encrypt.ts +128 -0
- package/server/lib/error.ts +20 -0
- package/server/lib/events.ts +205 -0
- package/server/lib/hot.ts +357 -0
- package/server/lib/key-fingerprint.ts +28 -0
- package/server/lib/logger.ts +331 -0
- package/server/lib/network.ts +137 -0
- package/server/lib/notifications.ts +219 -0
- package/server/lib/oauth2-refresh.ts +241 -0
- package/server/lib/oursecret.ts +54 -0
- package/server/lib/passkey-credential.ts +360 -0
- package/server/lib/passkey.ts +68 -0
- package/server/lib/permissions.ts +248 -0
- package/server/lib/pino.ts +24 -0
- package/server/lib/policy-preview.ts +138 -0
- package/server/lib/price.ts +338 -0
- package/server/lib/prices.ts +34 -0
- package/server/lib/project-scope.ts +239 -0
- package/server/lib/resolve-action.ts +427 -0
- package/server/lib/resolve.ts +36 -0
- package/server/lib/sessions.ts +632 -0
- package/server/lib/solana/connection.ts +26 -0
- package/server/lib/solana/jupiter.ts +128 -0
- package/server/lib/solana/transfer.ts +108 -0
- package/server/lib/solana/wallet.ts +136 -0
- package/server/lib/strategy/emits.ts +21 -0
- package/server/lib/strategy/engine.ts +1305 -0
- package/server/lib/strategy/executor.ts +115 -0
- package/server/lib/strategy/hook-context.ts +158 -0
- package/server/lib/strategy/hooks.ts +990 -0
- package/server/lib/strategy/index.ts +28 -0
- package/server/lib/strategy/installer.ts +305 -0
- package/server/lib/strategy/loader.ts +256 -0
- package/server/lib/strategy/message.ts +235 -0
- package/server/lib/strategy/repository.ts +218 -0
- package/server/lib/strategy/session-logger.ts +693 -0
- package/server/lib/strategy/sources.ts +288 -0
- package/server/lib/strategy/state.ts +189 -0
- package/server/lib/strategy/templates.ts +403 -0
- package/server/lib/strategy/tick.ts +404 -0
- package/server/lib/strategy/types.ts +230 -0
- package/server/lib/swap.ts +3 -0
- package/server/lib/temp.ts +86 -0
- package/server/lib/token-metadata.ts +86 -0
- package/server/lib/token-safety.ts +200 -0
- package/server/lib/token-search.ts +444 -0
- package/server/lib/totp.ts +194 -0
- package/server/lib/transactions.ts +123 -0
- package/server/lib/transport.ts +75 -0
- package/server/lib/txhistory/decoder.ts +262 -0
- package/server/lib/txhistory/enricher.ts +652 -0
- package/server/lib/txhistory/index.ts +391 -0
- package/server/lib/txhistory/signatures.ts +59 -0
- package/server/lib/verified-summary.ts +421 -0
- package/server/mcp/profile-policy.ts +30 -0
- package/server/mcp/server.ts +619 -0
- package/server/mcp/tools.ts +523 -0
- package/server/middleware/auth.ts +119 -0
- package/server/middleware/requestLogger.ts +84 -0
- package/server/routes/actions.ts +459 -0
- package/server/routes/adapters.ts +703 -0
- package/server/routes/addressbook.ts +113 -0
- package/server/routes/ai.ts +34 -0
- package/server/routes/apikeys.ts +295 -0
- package/server/routes/apps.ts +601 -0
- package/server/routes/auth.ts +457 -0
- package/server/routes/backup.ts +340 -0
- package/server/routes/batch.ts +270 -0
- package/server/routes/bookmarks.ts +162 -0
- package/server/routes/credential-shares.ts +198 -0
- package/server/routes/credential-vaults.ts +154 -0
- package/server/routes/credentials.ts +1290 -0
- package/server/routes/dashboard.ts +71 -0
- package/server/routes/defaults.ts +124 -0
- package/server/routes/fund.ts +229 -0
- package/server/routes/import.ts +352 -0
- package/server/routes/launch.ts +665 -0
- package/server/routes/lock.ts +54 -0
- package/server/routes/logs.ts +68 -0
- package/server/routes/nuke.ts +111 -0
- package/server/routes/passkey-credentials.ts +99 -0
- package/server/routes/passkey.ts +346 -0
- package/server/routes/portfolio.ts +217 -0
- package/server/routes/price.ts +63 -0
- package/server/routes/resolve.ts +31 -0
- package/server/routes/security.ts +45 -0
- package/server/routes/send-evm.ts +241 -0
- package/server/routes/send-solana.ts +281 -0
- package/server/routes/send.ts +178 -0
- package/server/routes/setup.ts +210 -0
- package/server/routes/strategy.ts +894 -0
- package/server/routes/swap-evm.ts +353 -0
- package/server/routes/swap-solana.ts +177 -0
- package/server/routes/swap.ts +356 -0
- package/server/routes/token.ts +247 -0
- package/server/routes/unlock.ts +403 -0
- package/server/routes/wallet-assets.ts +361 -0
- package/server/routes/wallet-transactions.ts +515 -0
- package/server/routes/wallet.ts +710 -0
- package/server/types.ts +146 -0
- package/skills/aurawallet/SKILL.md +739 -0
- package/skills/aurawallet-setup/SKILL.md +74 -0
- package/skills/security-review/SKILL.md +148 -0
- package/src/app/api/agent-requests/route.ts +30 -0
- package/src/app/api/apps/install/route.ts +126 -0
- package/src/app/api/apps/manifests/route.ts +16 -0
- package/src/app/api/apps/static/[...path]/route.ts +57 -0
- package/src/app/api/events/route.ts +92 -0
- package/src/app/api/page.tsx +212 -0
- package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
- package/src/app/api/workspace/[id]/apps/route.ts +81 -0
- package/src/app/api/workspace/[id]/export/route.ts +67 -0
- package/src/app/api/workspace/[id]/route.ts +168 -0
- package/src/app/api/workspace/auth.ts +34 -0
- package/src/app/api/workspace/config/route.ts +106 -0
- package/src/app/api/workspace/import/route.ts +127 -0
- package/src/app/api/workspace/route.ts +116 -0
- package/src/app/app/page.tsx +2122 -0
- package/src/app/apple-icon.png +0 -0
- package/src/app/docs/page.tsx +178 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +572 -0
- package/src/app/health/page.tsx +5 -0
- package/src/app/hello/page.tsx +15 -0
- package/src/app/icon.png +0 -0
- package/src/app/layout.tsx +34 -0
- package/src/app/page.tsx +986 -0
- package/src/app/providers.tsx +90 -0
- package/src/app/share/[token]/page.tsx +295 -0
- package/src/components/ChainSelector.tsx +144 -0
- package/src/components/HumanActionBar.tsx +695 -0
- package/src/components/NotificationDrawer.tsx +129 -0
- package/src/components/apps/AgentKeysApp.tsx +490 -0
- package/src/components/apps/App.tsx +153 -0
- package/src/components/apps/AppGrid.tsx +15 -0
- package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
- package/src/components/apps/DraggableApp.tsx +562 -0
- package/src/components/apps/IFrameApp.tsx +73 -0
- package/src/components/apps/LogsApp.tsx +360 -0
- package/src/components/apps/SendApp.tsx +394 -0
- package/src/components/apps/SetupWizardApp.tsx +1004 -0
- package/src/components/apps/SystemDefaultsApp.tsx +845 -0
- package/src/components/apps/ThirdPartyApp.tsx +428 -0
- package/src/components/apps/TokenApp.tsx +319 -0
- package/src/components/apps/TransactionsApp.tsx +438 -0
- package/src/components/apps/WalletDetailApp.tsx +1505 -0
- package/src/components/apps/index.ts +13 -0
- package/src/components/design-system/Button.tsx +53 -0
- package/src/components/design-system/ChainIndicator.tsx +65 -0
- package/src/components/design-system/ChainSelector.tsx +137 -0
- package/src/components/design-system/ConfirmationModal.tsx +106 -0
- package/src/components/design-system/ConfirmationPopover.tsx +81 -0
- package/src/components/design-system/Drawer.tsx +123 -0
- package/src/components/design-system/FilterDropdown.tsx +72 -0
- package/src/components/design-system/Modal.tsx +206 -0
- package/src/components/design-system/Popover.tsx +142 -0
- package/src/components/design-system/TextInput.tsx +85 -0
- package/src/components/design-system/Toggle.tsx +58 -0
- package/src/components/design-system/index.ts +11 -0
- package/src/components/docs/DocsThemeToggle.tsx +49 -0
- package/src/components/health/CredentialHealthDashboard.tsx +214 -0
- package/src/components/icons/ChainIcons.tsx +72 -0
- package/src/components/layout/AppStoreDrawer.tsx +369 -0
- package/src/components/layout/ContentArea.tsx +21 -0
- package/src/components/layout/TabBar.tsx +278 -0
- package/src/components/layout/WalletSidebar.tsx +1033 -0
- package/src/components/layout/index.ts +4 -0
- package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
- package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
- package/src/components/vault/ApiKeysConsole.tsx +1080 -0
- package/src/components/vault/AuditConsole.tsx +584 -0
- package/src/components/vault/CredentialDetail.tsx +455 -0
- package/src/components/vault/CredentialEmpty.tsx +55 -0
- package/src/components/vault/CredentialField.tsx +361 -0
- package/src/components/vault/CredentialForm.tsx +1212 -0
- package/src/components/vault/CredentialList.tsx +165 -0
- package/src/components/vault/CredentialRow.tsx +97 -0
- package/src/components/vault/CredentialShareModal.tsx +178 -0
- package/src/components/vault/CredentialVault.tsx +754 -0
- package/src/components/vault/CredentialWalletWidget.tsx +103 -0
- package/src/components/vault/ImportCredentialsModal.tsx +515 -0
- package/src/components/vault/LargeTypeModal.tsx +64 -0
- package/src/components/vault/PasswordGenerator.tsx +224 -0
- package/src/components/vault/TOTPDisplay.tsx +123 -0
- package/src/components/vault/VaultSidebar.tsx +413 -0
- package/src/components/vault/types.ts +54 -0
- package/src/context/AuthContext.tsx +337 -0
- package/src/context/PriceContext.tsx +113 -0
- package/src/context/ThemeContext.tsx +164 -0
- package/src/context/WebSocketContext.tsx +269 -0
- package/src/context/WorkspaceContext.tsx +668 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useAgentActions.ts +368 -0
- package/src/hooks/useBalance.ts +103 -0
- package/src/hooks/useBalances.ts +129 -0
- package/src/instrumentation.ts +12 -0
- package/src/lib/api.ts +449 -0
- package/src/lib/app-loader.ts +148 -0
- package/src/lib/app-registry.ts +178 -0
- package/src/lib/app-sdk.ts +157 -0
- package/src/lib/audit-console-adapter.ts +151 -0
- package/src/lib/auth-client.ts +75 -0
- package/src/lib/config.ts +74 -0
- package/src/lib/crypto.ts +112 -0
- package/src/lib/db.ts +21 -0
- package/src/lib/docs.ts +390 -0
- package/src/lib/events.ts +361 -0
- package/src/lib/pino.ts +24 -0
- package/src/lib/theme-handlers.ts +168 -0
- package/src/lib/theme.ts +351 -0
- package/src/lib/tokenData.ts +378 -0
- package/src/lib/vault-crypto.ts +129 -0
- package/src/lib/websocket-server.ts +302 -0
- package/src/lib/websocket-setup.ts +79 -0
- package/src/lib/wordlist.ts +2050 -0
- package/src/lib/workspace-handlers.ts +285 -0
- package/start.sh +80 -0
- package/tailwind.config.ts +99 -0
- 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
|
+
}
|