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,138 @@
|
|
|
1
|
+
import type { ResolveProfileInput, ResolvedProfilePolicy } from './agent-profiles';
|
|
2
|
+
|
|
3
|
+
export type DenyCode =
|
|
4
|
+
| 'DENY_PERMISSION_MISSING'
|
|
5
|
+
| 'DENY_CREDENTIAL_READ_SCOPE'
|
|
6
|
+
| 'DENY_CREDENTIAL_WRITE_SCOPE'
|
|
7
|
+
| 'DENY_EXCLUDED_FIELD'
|
|
8
|
+
| 'DENY_MAX_READS_EXCEEDED'
|
|
9
|
+
| 'DENY_RATE_LIMIT';
|
|
10
|
+
|
|
11
|
+
export interface PolicyPreviewV1 {
|
|
12
|
+
version: 'v1';
|
|
13
|
+
request: {
|
|
14
|
+
profile: string;
|
|
15
|
+
profileVersion: string;
|
|
16
|
+
overrides?: ResolveProfileInput['overrides'];
|
|
17
|
+
};
|
|
18
|
+
effectivePolicy: {
|
|
19
|
+
permissions: string[];
|
|
20
|
+
credentialAccess: {
|
|
21
|
+
read: string[];
|
|
22
|
+
write: string[];
|
|
23
|
+
excludeFields: string[];
|
|
24
|
+
maxReads: number | null;
|
|
25
|
+
};
|
|
26
|
+
ttlSeconds: number;
|
|
27
|
+
maxReads: number | null;
|
|
28
|
+
rateBudget: {
|
|
29
|
+
state: 'none' | 'inherited' | 'explicit';
|
|
30
|
+
requests: number | null;
|
|
31
|
+
windowSeconds: number | null;
|
|
32
|
+
source: 'none' | 'profile' | 'override';
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
effectivePolicyHash: string;
|
|
36
|
+
overrideDelta: string[];
|
|
37
|
+
warnings: string[];
|
|
38
|
+
denyExamples: Array<{ code: DenyCode; message: string }>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function canonicalizeEffectivePolicy(policy: ResolvedProfilePolicy): PolicyPreviewV1['effectivePolicy'] {
|
|
42
|
+
return {
|
|
43
|
+
permissions: Array.from(new Set(policy.permissions)).sort(),
|
|
44
|
+
credentialAccess: {
|
|
45
|
+
read: Array.from(new Set(policy.credentialAccess.read || [])).sort(),
|
|
46
|
+
write: Array.from(new Set(policy.credentialAccess.write || [])).sort(),
|
|
47
|
+
excludeFields: Array.from(new Set(policy.credentialAccess.excludeFields || [])).sort(),
|
|
48
|
+
maxReads: typeof policy.credentialAccess.maxReads === 'number' ? policy.credentialAccess.maxReads : null,
|
|
49
|
+
},
|
|
50
|
+
ttlSeconds: policy.ttlSeconds,
|
|
51
|
+
maxReads: typeof policy.credentialAccess.maxReads === 'number' ? policy.credentialAccess.maxReads : null,
|
|
52
|
+
rateBudget: {
|
|
53
|
+
state: 'none',
|
|
54
|
+
requests: null,
|
|
55
|
+
windowSeconds: null,
|
|
56
|
+
source: 'none',
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const DENY_ORDER: DenyCode[] = [
|
|
62
|
+
'DENY_PERMISSION_MISSING',
|
|
63
|
+
'DENY_CREDENTIAL_READ_SCOPE',
|
|
64
|
+
'DENY_CREDENTIAL_WRITE_SCOPE',
|
|
65
|
+
'DENY_EXCLUDED_FIELD',
|
|
66
|
+
'DENY_MAX_READS_EXCEEDED',
|
|
67
|
+
'DENY_RATE_LIMIT',
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const DENY_MESSAGES: Record<DenyCode, string> = {
|
|
71
|
+
DENY_PERMISSION_MISSING: 'Operation denied: required permission is not granted by this policy.',
|
|
72
|
+
DENY_CREDENTIAL_READ_SCOPE: 'Credential read denied: target credential is outside allowed read scopes.',
|
|
73
|
+
DENY_CREDENTIAL_WRITE_SCOPE: 'Credential write denied: target credential is outside allowed write scopes.',
|
|
74
|
+
DENY_EXCLUDED_FIELD: 'Field access denied: requested field is explicitly excluded by policy.',
|
|
75
|
+
DENY_MAX_READS_EXCEEDED: 'Read denied: token maxReads budget would be exceeded.',
|
|
76
|
+
DENY_RATE_LIMIT: 'Rate limit denied: request would exceed configured token rate budget.',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export function buildDenyExamples(): Array<{ code: DenyCode; message: string }> {
|
|
80
|
+
return DENY_ORDER.map((code) => ({ code, message: DENY_MESSAGES[code] }));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function buildPolicyPreviewV1(
|
|
84
|
+
input: ResolveProfileInput,
|
|
85
|
+
resolved: ResolvedProfilePolicy,
|
|
86
|
+
): PolicyPreviewV1 {
|
|
87
|
+
const profileVersion = input.profileVersion || resolved.profile.version;
|
|
88
|
+
return {
|
|
89
|
+
version: 'v1',
|
|
90
|
+
request: {
|
|
91
|
+
profile: input.profileId,
|
|
92
|
+
profileVersion,
|
|
93
|
+
...(input.overrides ? { overrides: input.overrides } : {}),
|
|
94
|
+
},
|
|
95
|
+
effectivePolicy: canonicalizeEffectivePolicy(resolved),
|
|
96
|
+
effectivePolicyHash: resolved.effectivePolicyHash,
|
|
97
|
+
overrideDelta: [...resolved.overrideDelta],
|
|
98
|
+
warnings: [...resolved.warnings],
|
|
99
|
+
denyExamples: buildDenyExamples(),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface PreviewErrorV1 {
|
|
104
|
+
version: 'v1';
|
|
105
|
+
code:
|
|
106
|
+
| 'ERR_UNAUTHORIZED'
|
|
107
|
+
| 'ERR_FORBIDDEN'
|
|
108
|
+
| 'ERR_PROFILE_NOT_FOUND'
|
|
109
|
+
| 'ERR_PROFILE_VERSION_UNSUPPORTED'
|
|
110
|
+
| 'ERR_OVERRIDE_INVALID'
|
|
111
|
+
| 'ERR_RESOLUTION_FAILED';
|
|
112
|
+
error: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function mapPreviewError(code: string): { status: number; error: PreviewErrorV1 } {
|
|
116
|
+
if (code === 'AGENT_PROFILE_NOT_FOUND') {
|
|
117
|
+
return {
|
|
118
|
+
status: 404,
|
|
119
|
+
error: { version: 'v1', code: 'ERR_PROFILE_NOT_FOUND', error: 'Requested profile does not exist.' },
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (code === 'AGENT_PROFILE_VERSION_UNSUPPORTED') {
|
|
123
|
+
return {
|
|
124
|
+
status: 409,
|
|
125
|
+
error: { version: 'v1', code: 'ERR_PROFILE_VERSION_UNSUPPORTED', error: 'Requested profile version is unsupported.' },
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (code.startsWith('AGENT_PROFILE_')) {
|
|
129
|
+
return {
|
|
130
|
+
status: 422,
|
|
131
|
+
error: { version: 'v1', code: 'ERR_OVERRIDE_INVALID', error: 'Profile overrides are invalid for the selected profile.' },
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
status: 500,
|
|
136
|
+
error: { version: 'v1', code: 'ERR_RESOLUTION_FAILED', error: 'Failed to resolve policy preview.' },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token price lookup with cascading fallback:
|
|
3
|
+
* DexScreener → CoinGecko → Alchemy (if key exists)
|
|
4
|
+
*
|
|
5
|
+
* In-memory cache with 60-second TTL. No DB writes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { isSolanaChain, normalizeAddress, getNativeCurrency } from './address';
|
|
9
|
+
import { getAlchemyKey, ALCHEMY_PATHS } from './config';
|
|
10
|
+
import { getEthToUsd, getSolToUsd } from './prices';
|
|
11
|
+
|
|
12
|
+
export interface PriceResult {
|
|
13
|
+
priceUsd: string;
|
|
14
|
+
source: string;
|
|
15
|
+
cached: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface CacheEntry {
|
|
19
|
+
priceUsd: string;
|
|
20
|
+
source: string;
|
|
21
|
+
fetchedAt: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const CACHE_TTL_MS = 60_000; // 60 seconds
|
|
25
|
+
|
|
26
|
+
const priceCache = new Map<string, CacheEntry>();
|
|
27
|
+
|
|
28
|
+
/** Clear cache — exposed for tests */
|
|
29
|
+
export function clearPriceCache(): void {
|
|
30
|
+
priceCache.clear();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// CoinGecko platform ID mapping
|
|
34
|
+
const COINGECKO_PLATFORMS: Record<string, string> = {
|
|
35
|
+
base: 'base',
|
|
36
|
+
ethereum: 'ethereum',
|
|
37
|
+
solana: 'solana',
|
|
38
|
+
polygon: 'polygon-pos',
|
|
39
|
+
arbitrum: 'arbitrum-one',
|
|
40
|
+
optimism: 'optimistic-ethereum',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get USD price for a token. Returns null if no source has a price.
|
|
45
|
+
*/
|
|
46
|
+
export async function getTokenPrice(address: string, chain: string): Promise<PriceResult | null> {
|
|
47
|
+
// Native token shortcut — use existing cached prices from cron
|
|
48
|
+
if (address === 'native') {
|
|
49
|
+
return getNativePrice(chain);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const normalized = normalizeAddress(address, chain);
|
|
53
|
+
const cacheKey = `${chain}:${normalized}`;
|
|
54
|
+
|
|
55
|
+
// Check cache
|
|
56
|
+
const cached = priceCache.get(cacheKey);
|
|
57
|
+
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
58
|
+
return { priceUsd: cached.priceUsd, source: cached.source, cached: true };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Cascading fallback
|
|
62
|
+
let result = await fetchDexScreener(normalized, chain);
|
|
63
|
+
if (!result) result = await fetchCoinGecko(normalized, chain);
|
|
64
|
+
if (!result) result = await fetchAlchemy(normalized, chain);
|
|
65
|
+
|
|
66
|
+
if (!result) return null;
|
|
67
|
+
|
|
68
|
+
// Cache the result
|
|
69
|
+
priceCache.set(cacheKey, {
|
|
70
|
+
priceUsd: result.priceUsd,
|
|
71
|
+
source: result.source,
|
|
72
|
+
fetchedAt: Date.now(),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return { ...result, cached: false };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Batch price lookup — efficient for portfolio valuation.
|
|
80
|
+
* Uses CoinGecko batch (comma-separated per platform) first,
|
|
81
|
+
* then DexScreener in parallel for misses, then Alchemy batch.
|
|
82
|
+
*/
|
|
83
|
+
export async function getTokenPrices(
|
|
84
|
+
tokens: { address: string; chain: string }[],
|
|
85
|
+
): Promise<Map<string, PriceResult>> {
|
|
86
|
+
const results = new Map<string, PriceResult>();
|
|
87
|
+
if (tokens.length === 0) return results;
|
|
88
|
+
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
const misses: { address: string; chain: string; normalized: string; cacheKey: string }[] = [];
|
|
91
|
+
|
|
92
|
+
// 1. Check cache
|
|
93
|
+
for (const { address, chain } of tokens) {
|
|
94
|
+
const normalized = normalizeAddress(address, chain);
|
|
95
|
+
const cacheKey = `${chain}:${normalized}`;
|
|
96
|
+
const cached = priceCache.get(cacheKey);
|
|
97
|
+
if (cached && now - cached.fetchedAt < CACHE_TTL_MS) {
|
|
98
|
+
results.set(cacheKey, { priceUsd: cached.priceUsd, source: cached.source, cached: true });
|
|
99
|
+
} else {
|
|
100
|
+
misses.push({ address, chain, normalized, cacheKey });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (misses.length === 0) return results;
|
|
105
|
+
|
|
106
|
+
// 2. CoinGecko batch — group by platform, comma-separate addresses
|
|
107
|
+
const remaining = await batchCoinGecko(misses, results);
|
|
108
|
+
|
|
109
|
+
// 3. DexScreener in parallel for CoinGecko misses
|
|
110
|
+
const afterDex = await batchDexScreener(remaining, results);
|
|
111
|
+
|
|
112
|
+
// 4. Alchemy batch for remaining misses
|
|
113
|
+
await batchAlchemy(afterDex, results);
|
|
114
|
+
|
|
115
|
+
return results;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** CoinGecko batch: one API call per platform with comma-separated addresses */
|
|
119
|
+
async function batchCoinGecko(
|
|
120
|
+
tokens: { address: string; chain: string; normalized: string; cacheKey: string }[],
|
|
121
|
+
results: Map<string, PriceResult>,
|
|
122
|
+
): Promise<typeof tokens> {
|
|
123
|
+
// Group by platform
|
|
124
|
+
const byPlatform = new Map<string, typeof tokens>();
|
|
125
|
+
const unsupported: typeof tokens = [];
|
|
126
|
+
|
|
127
|
+
for (const t of tokens) {
|
|
128
|
+
const platformId = COINGECKO_PLATFORMS[t.chain];
|
|
129
|
+
if (!platformId) {
|
|
130
|
+
unsupported.push(t);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const group = byPlatform.get(platformId) || [];
|
|
134
|
+
group.push(t);
|
|
135
|
+
byPlatform.set(platformId, group);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const remaining = [...unsupported];
|
|
139
|
+
|
|
140
|
+
await Promise.all(
|
|
141
|
+
Array.from(byPlatform.entries()).map(async ([platformId, group]) => {
|
|
142
|
+
const addresses = group.map((t) => t.normalized).join(',');
|
|
143
|
+
try {
|
|
144
|
+
const res = await fetch(
|
|
145
|
+
`https://api.coingecko.com/api/v3/simple/token_price/${platformId}?contract_addresses=${addresses}&vs_currencies=usd`,
|
|
146
|
+
{ signal: AbortSignal.timeout(5000) },
|
|
147
|
+
);
|
|
148
|
+
if (!res.ok) {
|
|
149
|
+
remaining.push(...group);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const data = await res.json();
|
|
153
|
+
|
|
154
|
+
for (const t of group) {
|
|
155
|
+
const price = data[t.normalized.toLowerCase()]?.usd;
|
|
156
|
+
if (price !== undefined && price !== null) {
|
|
157
|
+
const entry = { priceUsd: price.toString(), source: 'coingecko' as const };
|
|
158
|
+
results.set(t.cacheKey, { ...entry, cached: false });
|
|
159
|
+
priceCache.set(t.cacheKey, { ...entry, fetchedAt: Date.now() });
|
|
160
|
+
} else {
|
|
161
|
+
remaining.push(t);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
remaining.push(...group);
|
|
166
|
+
}
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
return remaining;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** DexScreener: one API call per token, run in parallel */
|
|
174
|
+
async function batchDexScreener(
|
|
175
|
+
tokens: { address: string; chain: string; normalized: string; cacheKey: string }[],
|
|
176
|
+
results: Map<string, PriceResult>,
|
|
177
|
+
): Promise<typeof tokens> {
|
|
178
|
+
if (tokens.length === 0) return [];
|
|
179
|
+
|
|
180
|
+
const remaining: typeof tokens = [];
|
|
181
|
+
|
|
182
|
+
await Promise.all(
|
|
183
|
+
tokens.map(async (t) => {
|
|
184
|
+
const result = await fetchDexScreener(t.normalized, t.chain);
|
|
185
|
+
if (result) {
|
|
186
|
+
results.set(t.cacheKey, { ...result, cached: false });
|
|
187
|
+
priceCache.set(t.cacheKey, { ...result, fetchedAt: Date.now() });
|
|
188
|
+
} else {
|
|
189
|
+
remaining.push(t);
|
|
190
|
+
}
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
return remaining;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Alchemy batch: one API call with multiple addresses (EVM only) */
|
|
198
|
+
async function batchAlchemy(
|
|
199
|
+
tokens: { address: string; chain: string; normalized: string; cacheKey: string }[],
|
|
200
|
+
results: Map<string, PriceResult>,
|
|
201
|
+
): Promise<void> {
|
|
202
|
+
if (tokens.length === 0) return;
|
|
203
|
+
|
|
204
|
+
const apiKey = await getAlchemyKey();
|
|
205
|
+
if (!apiKey) return;
|
|
206
|
+
|
|
207
|
+
// Filter to EVM only
|
|
208
|
+
const evmTokens = tokens.filter((t) => !isSolanaChain(t.chain) && ALCHEMY_PATHS[t.chain]);
|
|
209
|
+
if (evmTokens.length === 0) return;
|
|
210
|
+
|
|
211
|
+
const addressPayload = evmTokens.map((t) => ({
|
|
212
|
+
network: ALCHEMY_PATHS[t.chain].path,
|
|
213
|
+
address: t.normalized,
|
|
214
|
+
}));
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const res = await fetch(
|
|
218
|
+
`https://api.g.alchemy.com/prices/v1/${apiKey}/tokens/by-address`,
|
|
219
|
+
{
|
|
220
|
+
method: 'POST',
|
|
221
|
+
headers: { 'Content-Type': 'application/json' },
|
|
222
|
+
body: JSON.stringify({ addresses: addressPayload }),
|
|
223
|
+
signal: AbortSignal.timeout(5000),
|
|
224
|
+
},
|
|
225
|
+
);
|
|
226
|
+
if (!res.ok) return;
|
|
227
|
+
const data = await res.json();
|
|
228
|
+
|
|
229
|
+
for (let i = 0; i < evmTokens.length; i++) {
|
|
230
|
+
const priceEntry = data.data?.[i]?.prices?.find(
|
|
231
|
+
(p: any) => p.currency === 'usd' || p.currency === 'USD',
|
|
232
|
+
);
|
|
233
|
+
if (priceEntry?.value) {
|
|
234
|
+
const entry = { priceUsd: priceEntry.value, source: 'alchemy' as const };
|
|
235
|
+
results.set(evmTokens[i].cacheKey, { ...entry, cached: false });
|
|
236
|
+
priceCache.set(evmTokens[i].cacheKey, { ...entry, fetchedAt: Date.now() });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
} catch {
|
|
240
|
+
// Alchemy failed — prices just won't be available for these tokens
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function getNativePrice(chain: string): Promise<PriceResult | null> {
|
|
245
|
+
const currency = getNativeCurrency(chain);
|
|
246
|
+
const price = currency === 'SOL' ? await getSolToUsd() : await getEthToUsd();
|
|
247
|
+
if (price === null) return null;
|
|
248
|
+
return { priceUsd: price.toString(), source: 'cache', cached: true };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* DexScreener: free, 300 req/min, no key needed
|
|
253
|
+
*/
|
|
254
|
+
async function fetchDexScreener(address: string, chain: string): Promise<{ priceUsd: string; source: string } | null> {
|
|
255
|
+
try {
|
|
256
|
+
const res = await fetch(
|
|
257
|
+
`https://api.dexscreener.com/latest/dex/tokens/${address}`,
|
|
258
|
+
{ signal: AbortSignal.timeout(5000) },
|
|
259
|
+
);
|
|
260
|
+
if (!res.ok) return null;
|
|
261
|
+
const data = await res.json();
|
|
262
|
+
|
|
263
|
+
const pairs = data.pairs
|
|
264
|
+
?.filter((p: any) => p.chainId === chain)
|
|
265
|
+
?.sort((a: any, b: any) => (b.liquidity?.usd || 0) - (a.liquidity?.usd || 0));
|
|
266
|
+
|
|
267
|
+
const best = pairs?.[0];
|
|
268
|
+
if (!best?.priceUsd) return null;
|
|
269
|
+
|
|
270
|
+
return { priceUsd: best.priceUsd, source: 'dexscreener' };
|
|
271
|
+
} catch {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* CoinGecko: free tier, 5-15 req/min, no key needed
|
|
278
|
+
*/
|
|
279
|
+
async function fetchCoinGecko(address: string, chain: string): Promise<{ priceUsd: string; source: string } | null> {
|
|
280
|
+
const platformId = COINGECKO_PLATFORMS[chain];
|
|
281
|
+
if (!platformId) return null;
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const res = await fetch(
|
|
285
|
+
`https://api.coingecko.com/api/v3/simple/token_price/${platformId}?contract_addresses=${address}&vs_currencies=usd`,
|
|
286
|
+
{ signal: AbortSignal.timeout(5000) },
|
|
287
|
+
);
|
|
288
|
+
if (!res.ok) return null;
|
|
289
|
+
const data = await res.json();
|
|
290
|
+
|
|
291
|
+
const key = address.toLowerCase();
|
|
292
|
+
const price = data[key]?.usd;
|
|
293
|
+
if (price === undefined || price === null) return null;
|
|
294
|
+
|
|
295
|
+
return { priceUsd: price.toString(), source: 'coingecko' };
|
|
296
|
+
} catch {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Alchemy: requires API key, EVM only
|
|
303
|
+
*/
|
|
304
|
+
async function fetchAlchemy(address: string, chain: string): Promise<{ priceUsd: string; source: string } | null> {
|
|
305
|
+
// Skip for Solana chains — Alchemy price API is EVM only
|
|
306
|
+
if (isSolanaChain(chain)) return null;
|
|
307
|
+
|
|
308
|
+
const apiKey = await getAlchemyKey();
|
|
309
|
+
if (!apiKey) return null;
|
|
310
|
+
|
|
311
|
+
const alchemyConfig = ALCHEMY_PATHS[chain];
|
|
312
|
+
if (!alchemyConfig) return null;
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const res = await fetch(
|
|
316
|
+
`https://api.g.alchemy.com/prices/v1/${apiKey}/tokens/by-address`,
|
|
317
|
+
{
|
|
318
|
+
method: 'POST',
|
|
319
|
+
headers: { 'Content-Type': 'application/json' },
|
|
320
|
+
body: JSON.stringify({
|
|
321
|
+
addresses: [{ network: alchemyConfig.path, address }],
|
|
322
|
+
}),
|
|
323
|
+
signal: AbortSignal.timeout(5000),
|
|
324
|
+
},
|
|
325
|
+
);
|
|
326
|
+
if (!res.ok) return null;
|
|
327
|
+
const data = await res.json();
|
|
328
|
+
|
|
329
|
+
const priceEntry = data.data?.[0]?.prices?.find(
|
|
330
|
+
(p: any) => p.currency === 'usd' || p.currency === 'USD',
|
|
331
|
+
);
|
|
332
|
+
if (!priceEntry?.value) return null;
|
|
333
|
+
|
|
334
|
+
return { priceUsd: priceEntry.value, source: 'alchemy' };
|
|
335
|
+
} catch {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Price helpers — read cached native currency prices from DB.
|
|
3
|
+
* Prices are written by the cron server's native-price job.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { prisma } from './db';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get cached ETH→USD price. Returns null if no cached price exists.
|
|
10
|
+
*/
|
|
11
|
+
export async function getEthToUsd(): Promise<number | null> {
|
|
12
|
+
try {
|
|
13
|
+
const row = await prisma.nativePrice.findUnique({ where: { currency: 'ETH' } });
|
|
14
|
+
if (!row) return null;
|
|
15
|
+
const price = parseFloat(row.priceUsd);
|
|
16
|
+
return isNaN(price) ? null : price;
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get cached SOL→USD price. Returns null if no cached price exists.
|
|
24
|
+
*/
|
|
25
|
+
export async function getSolToUsd(): Promise<number | null> {
|
|
26
|
+
try {
|
|
27
|
+
const row = await prisma.nativePrice.findUnique({ where: { currency: 'SOL' } });
|
|
28
|
+
if (!row) return null;
|
|
29
|
+
const price = parseFloat(row.priceUsd);
|
|
30
|
+
return isNaN(price) ? null : price;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|