auramaxx 0.0.1
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 +77 -0
- package/apps/desktop-electron/main.js +428 -0
- package/bin/auramaxx.js +1063 -0
- package/docs/ADAPTERS.md +466 -0
- package/docs/AGENT_SETUP.md +159 -0
- package/docs/API.md +127 -0
- package/docs/APPS.md +199 -0
- package/docs/ARCHITECTURE.md +235 -0
- package/docs/AUTH.md +318 -0
- package/docs/BEST-PRACTICES.md +82 -0
- package/docs/CLI.md +141 -0
- package/docs/DESKTOP_ELECTRON.md +26 -0
- package/docs/DEVELOPING-APPS.md +453 -0
- package/docs/MCP.md +122 -0
- package/docs/PACKAGING_POLICY.md +19 -0
- package/docs/PERMISSION.md +137 -0
- package/docs/PROTOCOL.md +142 -0
- package/docs/README.md +50 -0
- package/docs/SKILLS.md +132 -0
- package/docs/TROUBLESHOOTING.md +376 -0
- package/docs/WORKSPACE.md +673 -0
- package/docs/agent-auth.md +14 -0
- package/docs/api/authentication.md +79 -0
- package/docs/api/secrets/api-keys.md +28 -0
- package/docs/api/secrets/credentials.md +80 -0
- package/docs/api/secrets/sharing.md +48 -0
- package/docs/api/system.md +41 -0
- package/docs/api/wallets/apps-strategies.md +66 -0
- package/docs/api/wallets/core.md +46 -0
- package/docs/api/wallets/data-portfolio.md +42 -0
- package/docs/aura-file.md +48 -0
- package/docs/core-concepts/FEATURES.md +114 -0
- package/docs/credentials.md +120 -0
- package/docs/external/HOW_TO_AURAMAXX/GETTING_SECRETS.md +33 -0
- package/docs/external/HOW_TO_AURAMAXX/README.md +45 -0
- package/docs/external/getting-started.md +10 -0
- package/docs/external/overview.md +19 -0
- package/docs/external/persona-paths.md +7 -0
- package/docs/external/share-secret.md +76 -0
- package/docs/external/why-aura.md +7 -0
- package/docs/security.md +227 -0
- package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -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 +28 -0
- package/package.json +167 -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/20260222090000_update_admin_ttl_default/migration.sql +10 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +447 -0
- package/public/logo.webp +0 -0
- package/scripts/add-app.js +245 -0
- package/server/abi/SwapHelper.json +438 -0
- package/server/cli/approval.ts +447 -0
- package/server/cli/commands/actions.ts +474 -0
- package/server/cli/commands/api.ts +220 -0
- package/server/cli/commands/apikey.ts +277 -0
- package/server/cli/commands/app.ts +204 -0
- package/server/cli/commands/auth.ts +464 -0
- package/server/cli/commands/cron.ts +24 -0
- package/server/cli/commands/diary.ts +274 -0
- package/server/cli/commands/doctor.ts +1247 -0
- package/server/cli/commands/env.ts +476 -0
- package/server/cli/commands/experimental.ts +69 -0
- package/server/cli/commands/init.ts +798 -0
- package/server/cli/commands/lock.ts +157 -0
- package/server/cli/commands/mcp.ts +285 -0
- package/server/cli/commands/quickhack.ts +86 -0
- package/server/cli/commands/release-check.ts +231 -0
- package/server/cli/commands/restore.ts +314 -0
- package/server/cli/commands/service.ts +320 -0
- package/server/cli/commands/shell-hook.ts +512 -0
- package/server/cli/commands/skill.ts +216 -0
- package/server/cli/commands/start.ts +139 -0
- package/server/cli/commands/status.ts +59 -0
- package/server/cli/commands/stop.ts +36 -0
- package/server/cli/commands/token.ts +180 -0
- package/server/cli/commands/unlock.ts +50 -0
- package/server/cli/commands/vault.ts +1323 -0
- package/server/cli/commands/wallet.ts +209 -0
- package/server/cli/index.ts +280 -0
- package/server/cli/lib/approval-poll.ts +94 -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 +280 -0
- package/server/cli/lib/dotenv-migrate.ts +116 -0
- package/server/cli/lib/dotenv-parser.ts +146 -0
- package/server/cli/lib/escalation.ts +57 -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/lock-unlock-helper.ts +71 -0
- package/server/cli/lib/process.ts +162 -0
- package/server/cli/lib/prompt.ts +294 -0
- package/server/cli/lib/theme.ts +240 -0
- package/server/cli/socket.ts +579 -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 +420 -0
- package/server/lib/adapters/factory.ts +119 -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 +419 -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 +258 -0
- package/server/lib/app-installer.ts +505 -0
- package/server/lib/app-tokens.ts +247 -0
- package/server/lib/approval-link.ts +27 -0
- package/server/lib/auth.ts +314 -0
- package/server/lib/auto-execute.ts +160 -0
- package/server/lib/batch.ts +242 -0
- package/server/lib/cold.ts +1048 -0
- package/server/lib/config.ts +408 -0
- package/server/lib/credential-access-audit.ts +85 -0
- package/server/lib/credential-access-policy.ts +111 -0
- package/server/lib/credential-health.ts +343 -0
- package/server/lib/credential-import.ts +608 -0
- package/server/lib/credential-scope.ts +102 -0
- package/server/lib/credential-shares.ts +190 -0
- package/server/lib/credential-transport.ts +533 -0
- package/server/lib/credential-vault.ts +77 -0
- package/server/lib/credentials.ts +422 -0
- package/server/lib/crypto.ts +8 -0
- package/server/lib/db.ts +58 -0
- package/server/lib/defaults.ts +386 -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/diary.ts +34 -0
- package/server/lib/dont-ask-again-policy.ts +41 -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 +114 -0
- package/server/lib/error.ts +20 -0
- package/server/lib/events.ts +217 -0
- package/server/lib/feature-flags.ts +93 -0
- package/server/lib/hot.ts +357 -0
- package/server/lib/human-action-summary.ts +80 -0
- package/server/lib/key-fingerprint.ts +28 -0
- package/server/lib/logger.ts +340 -0
- package/server/lib/network.ts +137 -0
- package/server/lib/notifications.ts +230 -0
- package/server/lib/oauth2-refresh.ts +241 -0
- package/server/lib/oursecret.ts +71 -0
- package/server/lib/passkey-credential.ts +360 -0
- package/server/lib/passkey.ts +68 -0
- package/server/lib/permissions.ts +299 -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 +297 -0
- package/server/lib/resolve-action.ts +328 -0
- package/server/lib/resolve.ts +36 -0
- package/server/lib/secret-gist-share.ts +296 -0
- package/server/lib/sessions.ts +634 -0
- package/server/lib/socket-path.ts +56 -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 +159 -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 +237 -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 +84 -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/update-check.ts +35 -0
- package/server/lib/verified-summary.ts +414 -0
- package/server/lib/view-registry.ts +80 -0
- package/server/mcp/profile-policy.ts +30 -0
- package/server/mcp/server.ts +1589 -0
- package/server/mcp/tools.ts +276 -0
- package/server/middleware/auth.ts +119 -0
- package/server/middleware/requestLogger.ts +84 -0
- package/server/routes/actions.ts +539 -0
- package/server/routes/adapters.ts +711 -0
- package/server/routes/addressbook.ts +113 -0
- package/server/routes/ai.ts +34 -0
- package/server/routes/apikeys.ts +343 -0
- package/server/routes/apps.ts +601 -0
- package/server/routes/auth.ts +406 -0
- package/server/routes/backup.ts +404 -0
- package/server/routes/batch.ts +270 -0
- package/server/routes/bookmarks.ts +162 -0
- package/server/routes/credential-shares.ts +380 -0
- package/server/routes/credential-vaults.ts +159 -0
- package/server/routes/credentials.ts +1782 -0
- package/server/routes/dashboard.ts +97 -0
- package/server/routes/defaults.ts +124 -0
- package/server/routes/flags.ts +11 -0
- package/server/routes/fund.ts +225 -0
- package/server/routes/heartbeat.ts +375 -0
- package/server/routes/import.ts +364 -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 +366 -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 +352 -0
- package/server/routes/swap-solana.ts +176 -0
- package/server/routes/swap.ts +356 -0
- package/server/routes/token.ts +247 -0
- package/server/routes/unlock.ts +467 -0
- package/server/routes/views.ts +41 -0
- package/server/routes/wallet-assets.ts +361 -0
- package/server/routes/wallet-transactions.ts +515 -0
- package/server/routes/wallet.ts +709 -0
- package/server/types.ts +146 -0
- package/shared/credential-field-schema.ts +248 -0
- package/skills/auramaxx/HEARTBEAT.md +78 -0
- package/skills/auramaxx/SKILL.md +745 -0
- package/skills/auramaxx/docs/AGENT_SETUP.md +155 -0
- package/skills/auramaxx/docs/API.md +127 -0
- package/skills/auramaxx/docs/AUTH.md +318 -0
- package/skills/auramaxx/docs/CLI.md +130 -0
- package/skills/auramaxx/docs/MCP.md +122 -0
- package/skills/auramaxx/docs/TROUBLESHOOTING.md +357 -0
- package/skills/auramaxx/docs/WORKSPACE.md +673 -0
- package/skills/auramaxx/docs/security.md +227 -0
- package/skills/task-lifecycle/SKILL.md +378 -0
- package/src/app/api/[...doc]/page.tsx +36 -0
- package/src/app/api/agent-requests/route.ts +30 -0
- package/src/app/api/apps/install/route.ts +132 -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/docs/plain/route.ts +74 -0
- package/src/app/api/events/route.ts +92 -0
- package/src/app/api/page.tsx +290 -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 +40 -0
- package/src/app/api/workspace/config/route.ts +121 -0
- package/src/app/api/workspace/import/route.ts +127 -0
- package/src/app/api/workspace/route.ts +116 -0
- package/src/app/app-legacy-do-not-use/page.tsx +2245 -0
- package/src/app/apple-icon.png +0 -0
- package/src/app/approve/[actionId]/page.tsx +409 -0
- package/src/app/docs/DocsPageContent.tsx +269 -0
- package/src/app/docs/[...doc]/page.tsx +41 -0
- package/src/app/docs/page.tsx +38 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +819 -0
- package/src/app/health/page.tsx +5 -0
- package/src/app/hello/page.tsx +102 -0
- package/src/app/icon.png +0 -0
- package/src/app/layout.tsx +39 -0
- package/src/app/page.tsx +1964 -0
- package/src/app/privacy/page.tsx +63 -0
- package/src/app/providers.tsx +87 -0
- package/src/app/share/[token]/page.tsx +295 -0
- package/src/app/terms/page.tsx +80 -0
- package/src/components/ChainSelector.tsx +44 -0
- package/src/components/HumanActionBar.tsx +697 -0
- package/src/components/NotificationDrawer.tsx +387 -0
- package/src/components/PasskeyEnrollmentPrompt.tsx +235 -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 +88 -0
- package/src/components/design-system/ChainIndicator.tsx +65 -0
- package/src/components/design-system/ChainSelector.tsx +147 -0
- package/src/components/design-system/ConfirmationModal.tsx +107 -0
- package/src/components/design-system/ConfirmationPopover.tsx +81 -0
- package/src/components/design-system/DownloadButton.tsx +149 -0
- package/src/components/design-system/Drawer.tsx +133 -0
- package/src/components/design-system/FilterDropdown.tsx +183 -0
- package/src/components/design-system/ItemPicker.tsx +157 -0
- package/src/components/design-system/Modal.tsx +296 -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 +65 -0
- package/src/components/design-system/TyvekCollapsibleSection.tsx +55 -0
- package/src/components/design-system/index.ts +14 -0
- package/src/components/docs/ClientSideMarkdown.tsx +51 -0
- package/src/components/docs/DocsSearchBar.tsx +118 -0
- package/src/components/docs/DocsThemeToggle.tsx +38 -0
- package/src/components/docs/PersistentDocGroup.tsx +91 -0
- package/src/components/docs/ShareUrlButton.tsx +33 -0
- package/src/components/docs/SidebarScrollMemory.tsx +56 -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/CreateViewModal.tsx +88 -0
- package/src/components/layout/LeftRail.tsx +114 -0
- package/src/components/layout/TabBar.tsx +284 -0
- package/src/components/layout/WalletSidebar.tsx +1030 -0
- package/src/components/layout/index.ts +6 -0
- package/src/components/marketing/AuraMaxxSpecOverlay.tsx +653 -0
- package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
- package/src/components/vault/ApiKeysConsole.tsx +1272 -0
- package/src/components/vault/AuditConsole.tsx +600 -0
- package/src/components/vault/CredentialDetail.tsx +625 -0
- package/src/components/vault/CredentialEmpty.tsx +55 -0
- package/src/components/vault/CredentialField.tsx +583 -0
- package/src/components/vault/CredentialForm.tsx +1484 -0
- package/src/components/vault/CredentialList.tsx +265 -0
- package/src/components/vault/CredentialRow.tsx +130 -0
- package/src/components/vault/CredentialShareModal.tsx +273 -0
- package/src/components/vault/CredentialVault.tsx +1662 -0
- package/src/components/vault/CredentialWalletWidget.tsx +103 -0
- package/src/components/vault/DocsConsole.tsx +113 -0
- package/src/components/vault/ImportCredentialsModal.tsx +578 -0
- package/src/components/vault/LargeTypeModal.tsx +88 -0
- package/src/components/vault/PasswordGenerator.tsx +232 -0
- package/src/components/vault/TOTPDisplay.tsx +108 -0
- package/src/components/vault/TotpSetupPanel.tsx +198 -0
- package/src/components/vault/VaultSidebar.tsx +881 -0
- package/src/components/vault/credentialFormName.ts +91 -0
- package/src/components/vault/hooks/useVaultKeyboardShortcuts.ts +69 -0
- package/src/components/vault/types.ts +56 -0
- package/src/context/AuthContext.tsx +365 -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 +4 -0
- package/src/hooks/useAgentActions.ts +552 -0
- package/src/hooks/useBalance.ts +103 -0
- package/src/hooks/useBalances.ts +129 -0
- package/src/hooks/useTheme.ts +156 -0
- package/src/instrumentation.ts +12 -0
- package/src/lib/api-docs.ts +154 -0
- package/src/lib/api.ts +474 -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/credential-field-schema.ts +11 -0
- package/src/lib/crypto.ts +112 -0
- package/src/lib/db.ts +21 -0
- package/src/lib/docs.ts +544 -0
- package/src/lib/events.ts +363 -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/totp-import.ts +57 -0
- package/src/lib/vault-crypto.ts +129 -0
- package/src/lib/view-registry.ts +57 -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 +170 -0
- package/tailwind.config.ts +99 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
import { AgentTokenPayload, TokenSession, LimitValue, SpentValue } from '../types';
|
|
2
|
+
import { prisma } from './db';
|
|
3
|
+
import { events } from './events';
|
|
4
|
+
import { getFundLimit } from './auth';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WALLET TOKEN SECURITY MODEL
|
|
8
|
+
* ===========================
|
|
9
|
+
*
|
|
10
|
+
* IN MEMORY (security-critical, resets on restart):
|
|
11
|
+
* - SIGNING_KEY: Random key for HMAC signing tokens
|
|
12
|
+
* - sessions Map: Active token sessions with spending tracking
|
|
13
|
+
* - revokedTokens Set: Tokens revoked this session
|
|
14
|
+
*
|
|
15
|
+
* IN DATABASE (for UI/logs only, NOT used for auth):
|
|
16
|
+
* - AgentToken table: Metadata for display (hash, agentId, limit, etc.)
|
|
17
|
+
* - Synced on create/spend/revoke but DB is just a mirror
|
|
18
|
+
*
|
|
19
|
+
* On server restart:
|
|
20
|
+
* - New SIGNING_KEY generated → ALL old tokens become invalid
|
|
21
|
+
* - Old DB records show as "inactive" (not in memory = can't sign)
|
|
22
|
+
* - This is a SECURITY FEATURE: restart = re-approve
|
|
23
|
+
*
|
|
24
|
+
* Token validation flow:
|
|
25
|
+
* 1. Agent sends token in Authorization header
|
|
26
|
+
* 2. Server verifies HMAC signature with in-memory SIGNING_KEY
|
|
27
|
+
* 3. If valid, checks spending limit in memory sessions Map
|
|
28
|
+
* 4. DB is NEVER consulted for auth decisions
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
// Limit types for per-permission tracking
|
|
32
|
+
export type LimitType = 'fund' | 'send' | 'swap';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolve a limit value, handling both plain numbers and address-keyed Records.
|
|
36
|
+
* - Plain number: return as-is (backward compat, single-currency)
|
|
37
|
+
* - Record<string, number>: look up by currency address
|
|
38
|
+
* - If currency is provided but limit is a plain number, return the number (backward compat)
|
|
39
|
+
*/
|
|
40
|
+
export function resolveLimit(
|
|
41
|
+
limitValue: LimitValue | undefined,
|
|
42
|
+
currency?: string
|
|
43
|
+
): number | undefined {
|
|
44
|
+
if (limitValue === undefined) return undefined;
|
|
45
|
+
if (typeof limitValue === 'number') return limitValue;
|
|
46
|
+
// Record<string, number> — look up by currency
|
|
47
|
+
if (currency && limitValue[currency] !== undefined) {
|
|
48
|
+
return limitValue[currency];
|
|
49
|
+
}
|
|
50
|
+
// No matching currency in record = unlimited for that currency
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get spent amount from a SpentValue, handling both plain numbers and address-keyed Records.
|
|
56
|
+
*/
|
|
57
|
+
function resolveSpent(spentValue: SpentValue | undefined, currency?: string): number {
|
|
58
|
+
if (spentValue === undefined) return 0;
|
|
59
|
+
if (typeof spentValue === 'number') return spentValue;
|
|
60
|
+
if (currency) return spentValue[currency] || 0;
|
|
61
|
+
// No currency specified on a Record — return aggregate spend for display
|
|
62
|
+
return Object.values(spentValue).reduce((total, value) => total + value, 0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Record spent amount into a SpentValue, handling both plain numbers and address-keyed Records.
|
|
67
|
+
* Mutates the session's spentByType in place.
|
|
68
|
+
*/
|
|
69
|
+
function addToSpent(
|
|
70
|
+
session: TokenSession,
|
|
71
|
+
limitType: LimitType,
|
|
72
|
+
amount: number,
|
|
73
|
+
currency?: string
|
|
74
|
+
): number {
|
|
75
|
+
if (!session.spentByType) {
|
|
76
|
+
session.spentByType = { fund: 0, send: 0, swap: 0 };
|
|
77
|
+
}
|
|
78
|
+
const current = session.spentByType[limitType];
|
|
79
|
+
|
|
80
|
+
if (currency && current !== undefined && typeof current === 'object') {
|
|
81
|
+
// Already a Record, add to the currency key
|
|
82
|
+
current[currency] = (current[currency] || 0) + amount;
|
|
83
|
+
return current[currency];
|
|
84
|
+
} else if (currency && (current === undefined || typeof current === 'number')) {
|
|
85
|
+
// Need to upgrade to Record if we have a currency
|
|
86
|
+
// But for backward compat, if no currency was previously tracked, just use the number
|
|
87
|
+
// Only upgrade if the limit is also a Record
|
|
88
|
+
const limitValue = session.token.limits?.[limitType];
|
|
89
|
+
if (typeof limitValue === 'object') {
|
|
90
|
+
// Upgrade to Record
|
|
91
|
+
const record: Record<string, number> = {};
|
|
92
|
+
record[currency] = (typeof current === 'number' ? 0 : 0) + amount;
|
|
93
|
+
session.spentByType[limitType] = record;
|
|
94
|
+
return record[currency];
|
|
95
|
+
}
|
|
96
|
+
// Limit is a plain number, keep spent as plain number
|
|
97
|
+
const newSpent = (typeof current === 'number' ? current : 0) + amount;
|
|
98
|
+
session.spentByType[limitType] = newSpent;
|
|
99
|
+
return newSpent;
|
|
100
|
+
} else {
|
|
101
|
+
// No currency, plain number
|
|
102
|
+
const newSpent = (typeof current === 'number' ? current : 0) + amount;
|
|
103
|
+
session.spentByType[limitType] = newSpent;
|
|
104
|
+
return newSpent;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// In-memory session tracking - resets on server restart
|
|
109
|
+
const sessions = new Map<string, TokenSession>();
|
|
110
|
+
|
|
111
|
+
// Revoked token hashes (in memory)
|
|
112
|
+
const revokedTokens = new Set<string>();
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Register a new token in memory AND database
|
|
116
|
+
* Called when human approves agent access
|
|
117
|
+
*/
|
|
118
|
+
export async function registerToken(
|
|
119
|
+
tokenHash: string,
|
|
120
|
+
token: AgentTokenPayload
|
|
121
|
+
): Promise<void> {
|
|
122
|
+
// Store in memory (authoritative for auth)
|
|
123
|
+
sessions.set(tokenHash, {
|
|
124
|
+
token,
|
|
125
|
+
spent: 0,
|
|
126
|
+
spentByType: { fund: 0, send: 0, swap: 0 },
|
|
127
|
+
credentialReads: 0,
|
|
128
|
+
tokenIssuedAt: token.iat,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Get fund limit for DB storage (backward compatibility)
|
|
132
|
+
const fundLimit = getFundLimit(token);
|
|
133
|
+
|
|
134
|
+
// Store in DB (for UI/logs only)
|
|
135
|
+
try {
|
|
136
|
+
await prisma.agentToken.upsert({
|
|
137
|
+
where: { tokenHash },
|
|
138
|
+
create: {
|
|
139
|
+
tokenHash,
|
|
140
|
+
agentId: token.agentId,
|
|
141
|
+
limit: fundLimit,
|
|
142
|
+
spent: 0,
|
|
143
|
+
permissions: JSON.stringify(token.permissions),
|
|
144
|
+
expiresAt: new Date(token.exp),
|
|
145
|
+
},
|
|
146
|
+
update: {
|
|
147
|
+
agentId: token.agentId,
|
|
148
|
+
limit: fundLimit,
|
|
149
|
+
spent: 0,
|
|
150
|
+
permissions: JSON.stringify(token.permissions),
|
|
151
|
+
expiresAt: new Date(token.exp),
|
|
152
|
+
isRevoked: false,
|
|
153
|
+
revokedAt: null,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
} catch (err) {
|
|
157
|
+
console.error('Failed to store token in DB (non-critical):', err);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Initialize or get a session for a token
|
|
163
|
+
*/
|
|
164
|
+
export function getSession(tokenHash: string, token: AgentTokenPayload): TokenSession {
|
|
165
|
+
let session = sessions.get(tokenHash);
|
|
166
|
+
if (!session) {
|
|
167
|
+
session = { token, spent: 0, spentByType: { fund: 0, send: 0, swap: 0 }, credentialReads: 0, tokenIssuedAt: token.iat };
|
|
168
|
+
sessions.set(tokenHash, session);
|
|
169
|
+
}
|
|
170
|
+
// Ensure spentByType exists (for backward compatibility with existing sessions)
|
|
171
|
+
if (!session.spentByType) {
|
|
172
|
+
session.spentByType = { fund: 0, send: 0, swap: 0 };
|
|
173
|
+
}
|
|
174
|
+
return session;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Check if token exists in memory (is valid for current server session)
|
|
179
|
+
*/
|
|
180
|
+
export function isActiveInMemory(tokenHash: string): boolean {
|
|
181
|
+
return sessions.has(tokenHash);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Record spending against a token's limit (legacy - uses fund limit)
|
|
186
|
+
*/
|
|
187
|
+
export async function recordSpend(tokenHash: string, amount: number): Promise<void> {
|
|
188
|
+
return recordSpendByType(tokenHash, 'fund', amount);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Record spending against a specific limit type.
|
|
193
|
+
* @param currency - Optional currency address for address-keyed limits (e.g., native token address)
|
|
194
|
+
*/
|
|
195
|
+
export async function recordSpendByType(
|
|
196
|
+
tokenHash: string,
|
|
197
|
+
limitType: LimitType,
|
|
198
|
+
amount: number,
|
|
199
|
+
currency?: string
|
|
200
|
+
): Promise<void> {
|
|
201
|
+
const session = sessions.get(tokenHash);
|
|
202
|
+
if (session) {
|
|
203
|
+
// Update the specific limit type (handles both plain number and Record)
|
|
204
|
+
addToSpent(session, limitType, amount, currency);
|
|
205
|
+
|
|
206
|
+
// Also update legacy spent field for fund
|
|
207
|
+
if (limitType === 'fund') {
|
|
208
|
+
session.spent += amount;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
emitSpentEvent(tokenHash, session, limitType, amount, currency);
|
|
212
|
+
|
|
213
|
+
// NOTE: All limit tracking is memory-only, consistent with the security model.
|
|
214
|
+
// On restart, SIGNING_KEY regenerates → all tokens invalid → agents re-approve with fresh limits.
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Check if a spend would exceed the token's limit (legacy - uses fund limit)
|
|
220
|
+
*/
|
|
221
|
+
export function checkLimit(tokenHash: string, token: AgentTokenPayload, amount: number): boolean {
|
|
222
|
+
return checkLimitByType(tokenHash, token, 'fund', amount);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Resolve the effective limit for a given type, handling fund fallback for send.
|
|
227
|
+
*/
|
|
228
|
+
function getEffectiveLimit(token: AgentTokenPayload, limitType: LimitType, currency?: string): number | undefined {
|
|
229
|
+
if (limitType === 'fund') {
|
|
230
|
+
const rawLimit = token.limits?.fund;
|
|
231
|
+
return rawLimit !== undefined ? resolveLimit(rawLimit, currency) : getFundLimit(token);
|
|
232
|
+
}
|
|
233
|
+
const rawLimit = token.limits?.[limitType];
|
|
234
|
+
let limit = resolveLimit(rawLimit, currency);
|
|
235
|
+
// Send defaults to fund limit if not explicitly set and fund limit exists
|
|
236
|
+
if (limit === undefined && limitType === 'send') {
|
|
237
|
+
const fundRaw = token.limits?.fund;
|
|
238
|
+
const fundLimit = fundRaw !== undefined ? resolveLimit(fundRaw, currency) : getFundLimit(token);
|
|
239
|
+
if (fundLimit !== undefined && fundLimit > 0) limit = fundLimit;
|
|
240
|
+
}
|
|
241
|
+
return limit;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Check if a spend would exceed a specific limit type
|
|
246
|
+
* Returns true if within limit, false if would exceed
|
|
247
|
+
* If no limit is set for that type, returns true (unlimited)
|
|
248
|
+
* @param currency - Optional currency address for address-keyed limits
|
|
249
|
+
*/
|
|
250
|
+
export function checkLimitByType(
|
|
251
|
+
tokenHash: string,
|
|
252
|
+
token: AgentTokenPayload,
|
|
253
|
+
limitType: LimitType,
|
|
254
|
+
amount: number,
|
|
255
|
+
currency?: string
|
|
256
|
+
): boolean {
|
|
257
|
+
const session = getSession(tokenHash, token);
|
|
258
|
+
const limit = getEffectiveLimit(token, limitType, currency);
|
|
259
|
+
|
|
260
|
+
// No limit set = unlimited
|
|
261
|
+
if (limit === undefined) {
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Check against spent
|
|
266
|
+
const spent = resolveSpent(session.spentByType?.[limitType], currency);
|
|
267
|
+
return spent + amount <= limit;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Atomically check limit AND reserve the amount in one synchronous call.
|
|
272
|
+
* Prevents TOCTOU race: two concurrent requests can't both pass the check
|
|
273
|
+
* because the deduction happens immediately (Node is single-threaded for sync code).
|
|
274
|
+
*
|
|
275
|
+
* Returns { ok: true } if reserved, or { ok: false, remaining } if limit exceeded.
|
|
276
|
+
* On tx failure, call releaseSpend() to roll back the reservation.
|
|
277
|
+
*/
|
|
278
|
+
export function reserveSpend(
|
|
279
|
+
tokenHash: string,
|
|
280
|
+
token: AgentTokenPayload,
|
|
281
|
+
limitType: LimitType,
|
|
282
|
+
amount: number,
|
|
283
|
+
currency?: string
|
|
284
|
+
): { ok: true } | { ok: false; remaining: number } {
|
|
285
|
+
const session = getSession(tokenHash, token);
|
|
286
|
+
const limit = getEffectiveLimit(token, limitType, currency);
|
|
287
|
+
|
|
288
|
+
// No limit set = unlimited, reserve (record) immediately
|
|
289
|
+
if (limit === undefined) {
|
|
290
|
+
addToSpent(session, limitType, amount, currency);
|
|
291
|
+
if (limitType === 'fund') session.spent += amount;
|
|
292
|
+
emitSpentEvent(tokenHash, session, limitType, amount, currency);
|
|
293
|
+
return { ok: true };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Check against spent
|
|
297
|
+
const spent = resolveSpent(session.spentByType?.[limitType], currency);
|
|
298
|
+
if (spent + amount > limit) {
|
|
299
|
+
const remaining = Math.max(0, limit - spent);
|
|
300
|
+
return { ok: false, remaining };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Reserve: deduct immediately
|
|
304
|
+
addToSpent(session, limitType, amount, currency);
|
|
305
|
+
if (limitType === 'fund') session.spent += amount;
|
|
306
|
+
emitSpentEvent(tokenHash, session, limitType, amount, currency);
|
|
307
|
+
return { ok: true };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Roll back a previously reserved spend (e.g., when a transaction fails).
|
|
312
|
+
*/
|
|
313
|
+
export function releaseSpend(
|
|
314
|
+
tokenHash: string,
|
|
315
|
+
limitType: LimitType,
|
|
316
|
+
amount: number,
|
|
317
|
+
currency?: string
|
|
318
|
+
): void {
|
|
319
|
+
const session = sessions.get(tokenHash);
|
|
320
|
+
if (!session) return;
|
|
321
|
+
addToSpent(session, limitType, -amount, currency);
|
|
322
|
+
if (limitType === 'fund') session.spent = Math.max(0, session.spent - amount);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Emit WebSocket tokenSpent event (shared by reserveSpend and recordSpendByType).
|
|
327
|
+
*/
|
|
328
|
+
function emitSpentEvent(
|
|
329
|
+
tokenHash: string,
|
|
330
|
+
session: TokenSession,
|
|
331
|
+
limitType: LimitType,
|
|
332
|
+
amount: number,
|
|
333
|
+
currency?: string
|
|
334
|
+
): void {
|
|
335
|
+
const limitValue = session.token.limits?.[limitType] ?? (limitType === 'fund' ? getFundLimit(session.token) : undefined);
|
|
336
|
+
const limit = resolveLimit(
|
|
337
|
+
typeof limitValue === 'number' || typeof limitValue === 'object' ? limitValue : undefined,
|
|
338
|
+
currency
|
|
339
|
+
);
|
|
340
|
+
const newSpent = resolveSpent(session.spentByType?.[limitType], currency);
|
|
341
|
+
const remaining = limit !== undefined ? Math.max(0, limit - newSpent) : undefined;
|
|
342
|
+
events.tokenSpent({ tokenHash, amount, newSpent, remaining: remaining ?? 0, limitType });
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get remaining allowance for a token (legacy - uses fund limit)
|
|
347
|
+
*/
|
|
348
|
+
export function getRemaining(tokenHash: string, token: AgentTokenPayload): number {
|
|
349
|
+
return getRemainingByType(tokenHash, token, 'fund');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Get remaining allowance for a specific limit type
|
|
354
|
+
* Returns Infinity if no limit is set
|
|
355
|
+
* @param currency - Optional currency address for address-keyed limits
|
|
356
|
+
*/
|
|
357
|
+
export function getRemainingByType(
|
|
358
|
+
tokenHash: string,
|
|
359
|
+
token: AgentTokenPayload,
|
|
360
|
+
limitType: LimitType,
|
|
361
|
+
currency?: string
|
|
362
|
+
): number {
|
|
363
|
+
const session = getSession(tokenHash, token);
|
|
364
|
+
const limit = getEffectiveLimit(token, limitType, currency);
|
|
365
|
+
|
|
366
|
+
// No limit set = unlimited
|
|
367
|
+
if (limit === undefined) {
|
|
368
|
+
return Infinity;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const spent = resolveSpent(session.spentByType?.[limitType], currency);
|
|
372
|
+
return Math.max(0, limit - spent);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Get session info for a token
|
|
377
|
+
*/
|
|
378
|
+
export function getSessionInfo(tokenHash: string): TokenSession | null {
|
|
379
|
+
return sessions.get(tokenHash) || null;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** Budget summary for hook AI context */
|
|
383
|
+
export interface SessionBudget {
|
|
384
|
+
limits: Record<string, number>;
|
|
385
|
+
spent: Record<string, number>;
|
|
386
|
+
remaining: Record<string, number>;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Get a simplified budget summary for a token session.
|
|
391
|
+
* Only includes limit types that have actual limits set.
|
|
392
|
+
* Missing keys = unlimited for that permission type.
|
|
393
|
+
*/
|
|
394
|
+
export function getSessionBudget(tokenHash: string): SessionBudget {
|
|
395
|
+
const session = sessions.get(tokenHash);
|
|
396
|
+
if (!session) return { limits: {}, spent: {}, remaining: {} };
|
|
397
|
+
|
|
398
|
+
const budget: SessionBudget = { limits: {}, spent: {}, remaining: {} };
|
|
399
|
+
|
|
400
|
+
for (const type of ['fund', 'send', 'swap'] as const) {
|
|
401
|
+
let limit: number | undefined;
|
|
402
|
+
if (type === 'fund') {
|
|
403
|
+
limit = session.token.limits?.fund !== undefined
|
|
404
|
+
? resolveLimit(session.token.limits.fund)
|
|
405
|
+
: (getFundLimit(session.token) || undefined);
|
|
406
|
+
} else {
|
|
407
|
+
limit = session.token.limits?.[type] !== undefined
|
|
408
|
+
? resolveLimit(session.token.limits[type])
|
|
409
|
+
: undefined;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (limit !== undefined && limit > 0) {
|
|
413
|
+
const spent = resolveSpent(session.spentByType?.[type]);
|
|
414
|
+
budget.limits[type] = limit;
|
|
415
|
+
budget.spent[type] = spent;
|
|
416
|
+
budget.remaining[type] = Math.max(0, limit - spent);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return budget;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// ─── Credential Access Tracking ─────────────────────────────────────
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Check if a token is allowed to read credentials.
|
|
427
|
+
* Enforces TTL (from iat) and maxReads limits.
|
|
428
|
+
*/
|
|
429
|
+
export function checkCredentialAccess(
|
|
430
|
+
tokenHash: string,
|
|
431
|
+
token: AgentTokenPayload
|
|
432
|
+
): { ok: true } | { ok: false; reason: string } {
|
|
433
|
+
const session = getSession(tokenHash, token);
|
|
434
|
+
const access = token.credentialAccess;
|
|
435
|
+
|
|
436
|
+
// Check TTL from iat
|
|
437
|
+
if (access?.ttl !== undefined && session.tokenIssuedAt) {
|
|
438
|
+
const elapsed = (Date.now() - session.tokenIssuedAt) / 1000;
|
|
439
|
+
if (elapsed > access.ttl) {
|
|
440
|
+
return { ok: false, reason: 'Credential access TTL expired' };
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Check maxReads
|
|
445
|
+
if (access?.maxReads !== undefined) {
|
|
446
|
+
const reads = session.credentialReads ?? 0;
|
|
447
|
+
if (reads >= access.maxReads) {
|
|
448
|
+
return { ok: false, reason: 'Credential read limit reached' };
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return { ok: true };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Record a successful credential read against the session.
|
|
457
|
+
*/
|
|
458
|
+
export function recordCredentialRead(tokenHash: string): void {
|
|
459
|
+
const session = sessions.get(tokenHash);
|
|
460
|
+
if (session) {
|
|
461
|
+
session.credentialReads = (session.credentialReads ?? 0) + 1;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Clear all sessions (for testing)
|
|
467
|
+
*/
|
|
468
|
+
export function clearSessions(): void {
|
|
469
|
+
sessions.clear();
|
|
470
|
+
revokedTokens.clear();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Revoke a token by its hash
|
|
475
|
+
*/
|
|
476
|
+
export async function revokeToken(tokenHash: string): Promise<boolean> {
|
|
477
|
+
const existed = sessions.has(tokenHash);
|
|
478
|
+
|
|
479
|
+
// Revoke in memory
|
|
480
|
+
revokedTokens.add(tokenHash);
|
|
481
|
+
sessions.delete(tokenHash);
|
|
482
|
+
|
|
483
|
+
// Emit WebSocket event if token existed
|
|
484
|
+
if (existed) {
|
|
485
|
+
events.tokenRevoked({ tokenHash });
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Sync to DB (P2025 = record not found — silently ignore during cleanup)
|
|
489
|
+
try {
|
|
490
|
+
await prisma.agentToken.update({
|
|
491
|
+
where: { tokenHash },
|
|
492
|
+
data: {
|
|
493
|
+
isRevoked: true,
|
|
494
|
+
revokedAt: new Date(),
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
} catch (err: unknown) {
|
|
498
|
+
const prismaError = err as { code?: string };
|
|
499
|
+
if (prismaError.code !== 'P2025') {
|
|
500
|
+
console.error('Failed to sync revoke to DB:', err);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return existed;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Revoke all active token sessions.
|
|
509
|
+
* Used by global lock flows to force full re-authentication.
|
|
510
|
+
*/
|
|
511
|
+
export async function revokeAllTokens(): Promise<number> {
|
|
512
|
+
const activeTokenHashes = Array.from(sessions.keys());
|
|
513
|
+
await Promise.all(activeTokenHashes.map((tokenHash) => revokeToken(tokenHash)));
|
|
514
|
+
return activeTokenHashes.length;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Check if a token is revoked
|
|
519
|
+
*/
|
|
520
|
+
export function isRevoked(tokenHash: string): boolean {
|
|
521
|
+
return revokedTokens.has(tokenHash);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* List all tokens from DB, marking which are active in memory
|
|
526
|
+
* Active = exists in memory with valid signing key
|
|
527
|
+
* Inactive = in DB but not in memory (server restarted, expired, revoked)
|
|
528
|
+
*/
|
|
529
|
+
export async function listTokensFromDb(): Promise<Array<{
|
|
530
|
+
tokenHash: string;
|
|
531
|
+
agentId: string;
|
|
532
|
+
createdAt: number;
|
|
533
|
+
limit: number;
|
|
534
|
+
spent: number;
|
|
535
|
+
remaining: number;
|
|
536
|
+
permissions: string[];
|
|
537
|
+
expiresAt: number;
|
|
538
|
+
isExpired: boolean;
|
|
539
|
+
isRevoked: boolean;
|
|
540
|
+
isActive: boolean; // true = valid in memory, false = DB record only
|
|
541
|
+
limits?: { fund?: number; send?: number; swap?: number };
|
|
542
|
+
spentByType?: { fund?: number; send?: number; swap?: number };
|
|
543
|
+
}>> {
|
|
544
|
+
const now = Date.now();
|
|
545
|
+
|
|
546
|
+
try {
|
|
547
|
+
const dbTokens = await prisma.agentToken.findMany({
|
|
548
|
+
orderBy: { createdAt: 'desc' },
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
return dbTokens.map(t => {
|
|
552
|
+
const isExpired = t.expiresAt.getTime() < now;
|
|
553
|
+
const isActiveInMem = sessions.has(t.tokenHash) && !revokedTokens.has(t.tokenHash);
|
|
554
|
+
const memSession = sessions.get(t.tokenHash);
|
|
555
|
+
const fundSpent = memSession ? resolveSpent(memSession.spentByType?.fund) : undefined;
|
|
556
|
+
|
|
557
|
+
// Use memory values if active, otherwise DB values
|
|
558
|
+
const spent = memSession && fundSpent !== undefined ? fundSpent : t.spent;
|
|
559
|
+
const limit = memSession ? getFundLimit(memSession.token) : t.limit;
|
|
560
|
+
|
|
561
|
+
return {
|
|
562
|
+
tokenHash: t.tokenHash,
|
|
563
|
+
agentId: t.agentId,
|
|
564
|
+
createdAt: t.createdAt.getTime(),
|
|
565
|
+
limit,
|
|
566
|
+
spent,
|
|
567
|
+
remaining: Math.max(0, limit - spent),
|
|
568
|
+
permissions: JSON.parse(t.permissions),
|
|
569
|
+
expiresAt: t.expiresAt.getTime(),
|
|
570
|
+
isExpired,
|
|
571
|
+
isRevoked: t.isRevoked || revokedTokens.has(t.tokenHash),
|
|
572
|
+
isActive: isActiveInMem && !isExpired,
|
|
573
|
+
limits: memSession?.token.limits,
|
|
574
|
+
spentByType: memSession?.spentByType,
|
|
575
|
+
};
|
|
576
|
+
});
|
|
577
|
+
} catch (err) {
|
|
578
|
+
console.error('Failed to list tokens from DB:', err);
|
|
579
|
+
return [];
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* List only in-memory sessions (legacy function for compatibility)
|
|
585
|
+
*/
|
|
586
|
+
export function listSessions(): Array<{
|
|
587
|
+
tokenHash: string;
|
|
588
|
+
agentId: string;
|
|
589
|
+
limit: number;
|
|
590
|
+
spent: number;
|
|
591
|
+
remaining: number;
|
|
592
|
+
permissions: string[];
|
|
593
|
+
expiresAt: number;
|
|
594
|
+
isExpired: boolean;
|
|
595
|
+
isRevoked: boolean;
|
|
596
|
+
limits?: { fund?: number; send?: number; swap?: number };
|
|
597
|
+
spentByType?: { fund?: number; send?: number; swap?: number };
|
|
598
|
+
}> {
|
|
599
|
+
const now = Date.now();
|
|
600
|
+
const result: Array<{
|
|
601
|
+
tokenHash: string;
|
|
602
|
+
agentId: string;
|
|
603
|
+
limit: number;
|
|
604
|
+
spent: number;
|
|
605
|
+
remaining: number;
|
|
606
|
+
permissions: string[];
|
|
607
|
+
expiresAt: number;
|
|
608
|
+
isExpired: boolean;
|
|
609
|
+
isRevoked: boolean;
|
|
610
|
+
limits?: { fund?: number; send?: number; swap?: number };
|
|
611
|
+
spentByType?: { fund?: number; send?: number; swap?: number };
|
|
612
|
+
}> = [];
|
|
613
|
+
|
|
614
|
+
sessions.forEach((session, tokenHash) => {
|
|
615
|
+
const isExpired = session.token.exp < now;
|
|
616
|
+
const fundLimit = getFundLimit(session.token);
|
|
617
|
+
const fundSpent = resolveSpent(session.spentByType?.fund);
|
|
618
|
+
result.push({
|
|
619
|
+
tokenHash,
|
|
620
|
+
agentId: session.token.agentId,
|
|
621
|
+
limit: fundLimit,
|
|
622
|
+
spent: fundSpent,
|
|
623
|
+
remaining: Math.max(0, fundLimit - fundSpent),
|
|
624
|
+
permissions: session.token.permissions,
|
|
625
|
+
expiresAt: session.token.exp,
|
|
626
|
+
isExpired,
|
|
627
|
+
isRevoked: revokedTokens.has(tokenHash),
|
|
628
|
+
limits: session.token.limits,
|
|
629
|
+
spentByType: session.spentByType,
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
return result;
|
|
634
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const DEFAULT_SOCKET_PORT = '4242';
|
|
2
|
+
|
|
3
|
+
function parsePort(raw?: string): string | undefined {
|
|
4
|
+
if (!raw) return undefined;
|
|
5
|
+
const trimmed = raw.trim();
|
|
6
|
+
if (!trimmed) return undefined;
|
|
7
|
+
if (/^\d+$/.test(trimmed)) return trimmed;
|
|
8
|
+
try {
|
|
9
|
+
const url = new URL(trimmed);
|
|
10
|
+
if (url.port) return url.port;
|
|
11
|
+
if (url.protocol === 'https:') return '443';
|
|
12
|
+
if (url.protocol === 'http:') return '80';
|
|
13
|
+
} catch {
|
|
14
|
+
// Ignore parse errors and use legacy path fallback.
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolveUid(uid?: number | string): number | string {
|
|
20
|
+
if (uid !== undefined && uid !== null) return uid;
|
|
21
|
+
return process.getuid?.() ?? 'unknown';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveAuraSocketPath(options: {
|
|
25
|
+
uid?: number | string;
|
|
26
|
+
serverUrl?: string;
|
|
27
|
+
serverPort?: string | number;
|
|
28
|
+
} = {}): string {
|
|
29
|
+
const explicit = process.env.AURA_SOCKET_PATH;
|
|
30
|
+
if (explicit && explicit.trim()) return explicit.trim();
|
|
31
|
+
|
|
32
|
+
const uid = resolveUid(options.uid);
|
|
33
|
+
const port =
|
|
34
|
+
parsePort(options.serverPort !== undefined ? String(options.serverPort) : undefined) ||
|
|
35
|
+
parsePort(options.serverUrl) ||
|
|
36
|
+
parsePort(process.env.WALLET_SERVER_PORT) ||
|
|
37
|
+
parsePort(process.env.WALLET_SERVER_URL);
|
|
38
|
+
|
|
39
|
+
if (port && port !== DEFAULT_SOCKET_PORT) {
|
|
40
|
+
return `/tmp/aura-cli-${uid}-${port}.sock`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return `/tmp/aura-cli-${uid}.sock`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resolveAuraSocketCandidates(options: {
|
|
47
|
+
uid?: number | string;
|
|
48
|
+
serverUrl?: string;
|
|
49
|
+
serverPort?: string | number;
|
|
50
|
+
} = {}): string[] {
|
|
51
|
+
const primary = resolveAuraSocketPath(options);
|
|
52
|
+
const uid = resolveUid(options.uid);
|
|
53
|
+
const legacy = `/tmp/aura-cli-${uid}.sock`;
|
|
54
|
+
if (primary === legacy) return [legacy];
|
|
55
|
+
return [primary, legacy];
|
|
56
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Connection } from '@solana/web3.js';
|
|
2
|
+
import { getRpcUrl } from '../config';
|
|
3
|
+
|
|
4
|
+
// Cached connections by chain
|
|
5
|
+
const connections = new Map<string, Connection>();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get a Solana Connection for the given chain.
|
|
9
|
+
* Caches connection instances (same pattern as EVM providers).
|
|
10
|
+
*/
|
|
11
|
+
export async function getSolanaConnection(chain: string = 'solana'): Promise<Connection> {
|
|
12
|
+
const cached = connections.get(chain);
|
|
13
|
+
if (cached) return cached;
|
|
14
|
+
|
|
15
|
+
const rpcUrl = await getRpcUrl(chain);
|
|
16
|
+
const connection = new Connection(rpcUrl, 'confirmed');
|
|
17
|
+
connections.set(chain, connection);
|
|
18
|
+
return connection;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Clear cached connections (for testing).
|
|
23
|
+
*/
|
|
24
|
+
export function clearSolanaConnections(): void {
|
|
25
|
+
connections.clear();
|
|
26
|
+
}
|