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,506 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incoming Asset Discovery Job
|
|
3
|
+
* ============================
|
|
4
|
+
* Scans on-chain Transfer events for incoming tokens to HotWallets.
|
|
5
|
+
* New tokens pass a 3-gate spam filter (value, liquidity, safety)
|
|
6
|
+
* before being auto-tracked. Already-tracked tokens just get a
|
|
7
|
+
* Transaction record.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Connection, PublicKey } from '@solana/web3.js';
|
|
11
|
+
import type { CronJob, CronContext } from '../job';
|
|
12
|
+
import { getRpcUrl } from '../../lib/config';
|
|
13
|
+
import { isSolanaChain } from '../../lib/address';
|
|
14
|
+
import { EVENT_SIGNATURES } from '../../lib/txhistory/signatures';
|
|
15
|
+
import { decodeLogs, type RawLog } from '../../lib/txhistory/decoder';
|
|
16
|
+
import { resolveTokenMetadataBatch } from '../../lib/txhistory/enricher';
|
|
17
|
+
import { getTokenPrice } from '../../lib/price';
|
|
18
|
+
import { searchTokens } from '../../lib/token-search';
|
|
19
|
+
import { getTokenSafety } from '../../lib/token-safety';
|
|
20
|
+
import { upsertTokenMetadata } from '../../lib/token-metadata';
|
|
21
|
+
import { getErrorMessage } from '../../lib/error';
|
|
22
|
+
|
|
23
|
+
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
/** Pad a 20-byte address to 32-byte topic for eth_getLogs topic filter */
|
|
26
|
+
function padAddress(address: string): string {
|
|
27
|
+
return '0x' + address.slice(2).toLowerCase().padStart(64, '0');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Format raw bigint token amount with decimals */
|
|
31
|
+
function formatAmount(raw: bigint, decimals: number): string {
|
|
32
|
+
if (raw === 0n) return '0';
|
|
33
|
+
const divisor = 10n ** BigInt(decimals);
|
|
34
|
+
const whole = raw / divisor;
|
|
35
|
+
const fraction = raw % divisor;
|
|
36
|
+
if (fraction === 0n) return whole.toString();
|
|
37
|
+
const fractionStr = fraction.toString().padStart(decimals, '0').replace(/0+$/, '');
|
|
38
|
+
return `${whole}.${fractionStr}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─── Token Processing Pipeline ────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
interface DiscoveredTransfer {
|
|
44
|
+
tokenAddress: string;
|
|
45
|
+
walletAddress: string;
|
|
46
|
+
amount: bigint;
|
|
47
|
+
from: string;
|
|
48
|
+
txHash: string;
|
|
49
|
+
blockNumber: bigint;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Process a discovered incoming transfer.
|
|
54
|
+
* If the token is already tracked, just write a Transaction record.
|
|
55
|
+
* If new, run the 3-gate spam filter before auto-tracking.
|
|
56
|
+
*/
|
|
57
|
+
async function processDiscoveredToken(
|
|
58
|
+
transfer: DiscoveredTransfer,
|
|
59
|
+
chain: string,
|
|
60
|
+
ctx: CronContext,
|
|
61
|
+
): Promise<void> {
|
|
62
|
+
const { tokenAddress, walletAddress, amount, from, txHash, blockNumber } = transfer;
|
|
63
|
+
|
|
64
|
+
// Check for existing Transaction with this txHash+chain (dedup)
|
|
65
|
+
const existingTx = await ctx.prisma.transaction.findUnique({
|
|
66
|
+
where: { txHash_chain: { txHash, chain } },
|
|
67
|
+
});
|
|
68
|
+
if (existingTx) return;
|
|
69
|
+
|
|
70
|
+
// Check if already tracked
|
|
71
|
+
const existing = await ctx.prisma.trackedAsset.findUnique({
|
|
72
|
+
where: {
|
|
73
|
+
walletAddress_tokenAddress_chain: { walletAddress, tokenAddress, chain },
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Resolve decimals for formatting
|
|
78
|
+
const tokenMap = await resolveTokenMetadataBatch([tokenAddress], chain);
|
|
79
|
+
const tokenInfo = tokenMap.get(tokenAddress);
|
|
80
|
+
const decimals = tokenInfo?.decimals ?? 18;
|
|
81
|
+
const symbol = tokenInfo?.symbol ?? 'UNKNOWN';
|
|
82
|
+
const formattedAmount = formatAmount(amount, decimals);
|
|
83
|
+
|
|
84
|
+
if (existing) {
|
|
85
|
+
// Already tracked — just write the Transaction record
|
|
86
|
+
await ctx.prisma.transaction.create({
|
|
87
|
+
data: {
|
|
88
|
+
walletAddress,
|
|
89
|
+
txHash,
|
|
90
|
+
type: 'receive',
|
|
91
|
+
status: 'confirmed',
|
|
92
|
+
tokenAddress,
|
|
93
|
+
tokenAmount: formattedAmount,
|
|
94
|
+
from,
|
|
95
|
+
to: walletAddress,
|
|
96
|
+
blockNumber: blockNumber <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(blockNumber) : null,
|
|
97
|
+
chain,
|
|
98
|
+
},
|
|
99
|
+
}).catch(() => {}); // unique constraint race
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// New token — run spam filter gates
|
|
104
|
+
|
|
105
|
+
// Gate 1: Value
|
|
106
|
+
const minValue = ctx.defaults.get<number>('discovery.min_value_usd', 0.5);
|
|
107
|
+
const priceResult = await getTokenPrice(tokenAddress, chain);
|
|
108
|
+
if (priceResult) {
|
|
109
|
+
const valueUsd = parseFloat(formattedAmount) * parseFloat(priceResult.priceUsd);
|
|
110
|
+
if (valueUsd < minValue) {
|
|
111
|
+
ctx.log.debug({ tokenAddress, chain, valueUsd, minValue }, 'Discovery: below value gate');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
// No price data — skip (can't verify value)
|
|
116
|
+
ctx.log.debug({ tokenAddress, chain }, 'Discovery: no price data, skipping');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Gate 2: Liquidity
|
|
121
|
+
const minLiquidity = ctx.defaults.get<number>('discovery.min_liquidity_usd', 1000);
|
|
122
|
+
const searchResults = await searchTokens(tokenAddress, { chain });
|
|
123
|
+
const bestResult = searchResults[0];
|
|
124
|
+
if (!bestResult || bestResult.liquidity < minLiquidity) {
|
|
125
|
+
ctx.log.debug({
|
|
126
|
+
tokenAddress,
|
|
127
|
+
chain,
|
|
128
|
+
liquidity: bestResult?.liquidity ?? 0,
|
|
129
|
+
minLiquidity,
|
|
130
|
+
}, 'Discovery: below liquidity gate');
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Gate 3: Safety
|
|
135
|
+
const safetyEnabled = ctx.defaults.get<boolean>('discovery.safety_enabled', true);
|
|
136
|
+
if (safetyEnabled) {
|
|
137
|
+
const safety = await getTokenSafety(tokenAddress, chain);
|
|
138
|
+
if (safety) {
|
|
139
|
+
if (safety.isHoneypot || parseFloat(safety.buyTax) > 50 || parseFloat(safety.sellTax) > 50) {
|
|
140
|
+
ctx.log.debug({ tokenAddress, chain, isHoneypot: safety.isHoneypot, buyTax: safety.buyTax, sellTax: safety.sellTax }, 'Discovery: failed safety gate');
|
|
141
|
+
// Still write metadata for manual lookup
|
|
142
|
+
upsertTokenMetadata(tokenAddress, chain, {
|
|
143
|
+
symbol: tokenInfo?.symbol,
|
|
144
|
+
name: tokenInfo?.name,
|
|
145
|
+
decimals,
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// safety === null → GoPlus doesn't have data, let it through
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// All gates passed — auto-track
|
|
154
|
+
ctx.log.info({ tokenAddress, chain, symbol, walletAddress }, 'Discovery: auto-tracking new token');
|
|
155
|
+
|
|
156
|
+
upsertTokenMetadata(tokenAddress, chain, {
|
|
157
|
+
symbol: tokenInfo?.symbol,
|
|
158
|
+
name: tokenInfo?.name,
|
|
159
|
+
decimals,
|
|
160
|
+
icon: tokenInfo?.icon,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
await ctx.prisma.trackedAsset.upsert({
|
|
164
|
+
where: {
|
|
165
|
+
walletAddress_tokenAddress_chain: { walletAddress, tokenAddress, chain },
|
|
166
|
+
},
|
|
167
|
+
create: {
|
|
168
|
+
walletAddress,
|
|
169
|
+
tokenAddress,
|
|
170
|
+
chain,
|
|
171
|
+
symbol: tokenInfo?.symbol,
|
|
172
|
+
name: tokenInfo?.name,
|
|
173
|
+
decimals,
|
|
174
|
+
icon: tokenInfo?.icon ?? bestResult?.imageUrl,
|
|
175
|
+
},
|
|
176
|
+
update: {},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
await ctx.prisma.transaction.create({
|
|
180
|
+
data: {
|
|
181
|
+
walletAddress,
|
|
182
|
+
txHash,
|
|
183
|
+
type: 'receive',
|
|
184
|
+
status: 'confirmed',
|
|
185
|
+
tokenAddress,
|
|
186
|
+
tokenAmount: formattedAmount,
|
|
187
|
+
from,
|
|
188
|
+
to: walletAddress,
|
|
189
|
+
blockNumber: blockNumber <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(blockNumber) : null,
|
|
190
|
+
chain,
|
|
191
|
+
},
|
|
192
|
+
}).catch(() => {}); // unique constraint race
|
|
193
|
+
|
|
194
|
+
await ctx.emit('asset:discovered', {
|
|
195
|
+
walletAddress,
|
|
196
|
+
tokenAddress,
|
|
197
|
+
chain,
|
|
198
|
+
symbol,
|
|
199
|
+
amount: formattedAmount,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ─── EVM Scanning ─────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
async function scanEvmChain(chain: string, ctx: CronContext): Promise<void> {
|
|
206
|
+
const rpcUrl = await getRpcUrl(chain);
|
|
207
|
+
const syncKey = `${chain}:discovery`;
|
|
208
|
+
|
|
209
|
+
// Get all hot wallet addresses for this chain
|
|
210
|
+
const hotWallets = await ctx.prisma.hotWallet.findMany({
|
|
211
|
+
where: { chain, hidden: false },
|
|
212
|
+
select: { address: true },
|
|
213
|
+
});
|
|
214
|
+
if (hotWallets.length === 0) return;
|
|
215
|
+
|
|
216
|
+
const walletAddresses = hotWallets.map((w) => w.address.toLowerCase());
|
|
217
|
+
const walletSet = new Set(walletAddresses);
|
|
218
|
+
|
|
219
|
+
// Read block cursor from SyncState
|
|
220
|
+
const syncState = await ctx.prisma.syncState.findUnique({ where: { chain: syncKey } });
|
|
221
|
+
const maxBlocksPerTick = ctx.defaults.get<number>('discovery.max_blocks_per_tick', 2000);
|
|
222
|
+
const maxInitialLookback = ctx.defaults.get<number>('discovery.max_initial_lookback', 302400);
|
|
223
|
+
|
|
224
|
+
// Get current block number
|
|
225
|
+
const blockRes = await fetch(rpcUrl, {
|
|
226
|
+
method: 'POST',
|
|
227
|
+
headers: { 'Content-Type': 'application/json' },
|
|
228
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_blockNumber', params: [] }),
|
|
229
|
+
signal: AbortSignal.timeout(10000),
|
|
230
|
+
});
|
|
231
|
+
const blockData = await blockRes.json();
|
|
232
|
+
const latestBlock = BigInt(blockData.result);
|
|
233
|
+
|
|
234
|
+
// Calculate scan range
|
|
235
|
+
let fromBlock: bigint;
|
|
236
|
+
if (syncState?.lastBlock) {
|
|
237
|
+
fromBlock = BigInt(syncState.lastBlock) + 1n;
|
|
238
|
+
} else {
|
|
239
|
+
fromBlock = latestBlock - BigInt(maxInitialLookback);
|
|
240
|
+
if (fromBlock < 0n) fromBlock = 0n;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const toBlock = fromBlock + BigInt(maxBlocksPerTick) - 1n < latestBlock
|
|
244
|
+
? fromBlock + BigInt(maxBlocksPerTick) - 1n
|
|
245
|
+
: latestBlock;
|
|
246
|
+
|
|
247
|
+
if (fromBlock > toBlock) return; // already caught up
|
|
248
|
+
|
|
249
|
+
// Build topic2 filter — pad wallet addresses for topic2 (receiver)
|
|
250
|
+
const paddedAddresses = walletAddresses.map(padAddress);
|
|
251
|
+
|
|
252
|
+
// eth_getLogs: Transfer events TO our wallets
|
|
253
|
+
const logsRes = await fetch(rpcUrl, {
|
|
254
|
+
method: 'POST',
|
|
255
|
+
headers: { 'Content-Type': 'application/json' },
|
|
256
|
+
body: JSON.stringify({
|
|
257
|
+
jsonrpc: '2.0',
|
|
258
|
+
id: 2,
|
|
259
|
+
method: 'eth_getLogs',
|
|
260
|
+
params: [{
|
|
261
|
+
fromBlock: '0x' + fromBlock.toString(16),
|
|
262
|
+
toBlock: '0x' + toBlock.toString(16),
|
|
263
|
+
topics: [
|
|
264
|
+
EVENT_SIGNATURES.TRANSFER,
|
|
265
|
+
null,
|
|
266
|
+
paddedAddresses.length === 1 ? paddedAddresses[0] : paddedAddresses,
|
|
267
|
+
],
|
|
268
|
+
}],
|
|
269
|
+
}),
|
|
270
|
+
signal: AbortSignal.timeout(30000),
|
|
271
|
+
});
|
|
272
|
+
const logsData = await logsRes.json();
|
|
273
|
+
|
|
274
|
+
if (logsData.error) {
|
|
275
|
+
ctx.log.warn({ chain, error: logsData.error }, 'Discovery: eth_getLogs RPC error');
|
|
276
|
+
// Update error state but don't advance cursor
|
|
277
|
+
await ctx.prisma.syncState.upsert({
|
|
278
|
+
where: { chain: syncKey },
|
|
279
|
+
create: { chain: syncKey, lastSyncStatus: 'error', lastError: logsData.error.message, syncCount: 1 },
|
|
280
|
+
update: { lastSyncStatus: 'error', lastError: logsData.error.message, syncCount: { increment: 1 } },
|
|
281
|
+
});
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const rawLogs: RawLog[] = (logsData.result || []).map((log: any) => ({
|
|
286
|
+
address: log.address,
|
|
287
|
+
topics: log.topics,
|
|
288
|
+
data: log.data,
|
|
289
|
+
transactionHash: log.transactionHash,
|
|
290
|
+
logIndex: parseInt(log.logIndex, 16),
|
|
291
|
+
blockNumber: BigInt(log.blockNumber),
|
|
292
|
+
}));
|
|
293
|
+
|
|
294
|
+
// Decode logs
|
|
295
|
+
const decoded = decodeLogs(rawLogs);
|
|
296
|
+
|
|
297
|
+
// Filter to ERC-20 transfers where `to` is one of our wallets
|
|
298
|
+
const incomingTransfers: DiscoveredTransfer[] = [];
|
|
299
|
+
for (const event of decoded) {
|
|
300
|
+
if (event.type !== 'transfer') continue;
|
|
301
|
+
const to = (event.params.to as string).toLowerCase();
|
|
302
|
+
if (!walletSet.has(to)) continue;
|
|
303
|
+
|
|
304
|
+
incomingTransfers.push({
|
|
305
|
+
tokenAddress: event.contractAddress,
|
|
306
|
+
walletAddress: to,
|
|
307
|
+
amount: event.params.amount as bigint,
|
|
308
|
+
from: (event.params.from as string).toLowerCase(),
|
|
309
|
+
txHash: event.txHash,
|
|
310
|
+
blockNumber: event.blockNumber,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Process each transfer
|
|
315
|
+
for (const transfer of incomingTransfers) {
|
|
316
|
+
try {
|
|
317
|
+
await processDiscoveredToken(transfer, chain, ctx);
|
|
318
|
+
} catch (err) {
|
|
319
|
+
ctx.log.debug({ err, txHash: transfer.txHash, token: transfer.tokenAddress }, 'Discovery: failed to process transfer');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Advance cursor
|
|
324
|
+
await ctx.prisma.syncState.upsert({
|
|
325
|
+
where: { chain: syncKey },
|
|
326
|
+
create: {
|
|
327
|
+
chain: syncKey,
|
|
328
|
+
lastSyncAt: new Date(),
|
|
329
|
+
lastSyncStatus: 'ok',
|
|
330
|
+
lastBlock: toBlock.toString(),
|
|
331
|
+
syncCount: 1,
|
|
332
|
+
},
|
|
333
|
+
update: {
|
|
334
|
+
lastSyncAt: new Date(),
|
|
335
|
+
lastSyncStatus: 'ok',
|
|
336
|
+
lastError: null,
|
|
337
|
+
lastBlock: toBlock.toString(),
|
|
338
|
+
syncCount: { increment: 1 },
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ─── Solana Scanning ──────────────────────────────────────────────────
|
|
344
|
+
|
|
345
|
+
async function scanSolanaChain(chain: string, ctx: CronContext): Promise<void> {
|
|
346
|
+
const rpcUrl = await getRpcUrl(chain);
|
|
347
|
+
const connection = new Connection(rpcUrl, 'confirmed');
|
|
348
|
+
const syncKey = `${chain}:discovery`;
|
|
349
|
+
|
|
350
|
+
const hotWallets = await ctx.prisma.hotWallet.findMany({
|
|
351
|
+
where: { chain, hidden: false },
|
|
352
|
+
select: { address: true },
|
|
353
|
+
});
|
|
354
|
+
if (hotWallets.length === 0) return;
|
|
355
|
+
|
|
356
|
+
const syncState = await ctx.prisma.syncState.findUnique({ where: { chain: syncKey } });
|
|
357
|
+
const lastSyncAt = syncState?.lastSyncAt ?? new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
358
|
+
|
|
359
|
+
for (const wallet of hotWallets) {
|
|
360
|
+
try {
|
|
361
|
+
const pubkey = new PublicKey(wallet.address);
|
|
362
|
+
const signatures = await connection.getSignaturesForAddress(pubkey, { limit: 50 });
|
|
363
|
+
|
|
364
|
+
// Filter to signatures newer than last scan
|
|
365
|
+
const newSigs = signatures.filter(
|
|
366
|
+
(s) => s.blockTime && s.blockTime * 1000 > lastSyncAt.getTime()
|
|
367
|
+
);
|
|
368
|
+
if (newSigs.length === 0) continue;
|
|
369
|
+
|
|
370
|
+
const sigStrings = newSigs.map((s) => s.signature);
|
|
371
|
+
const transactions = await connection.getParsedTransactions(sigStrings, {
|
|
372
|
+
maxSupportedTransactionVersion: 0,
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
for (let i = 0; i < transactions.length; i++) {
|
|
376
|
+
const tx = transactions[i];
|
|
377
|
+
if (!tx?.meta || tx.meta.err) continue;
|
|
378
|
+
|
|
379
|
+
// Extract incoming SPL token transfers
|
|
380
|
+
const innerInstructions = tx.meta.innerInstructions || [];
|
|
381
|
+
const allInstructions = [
|
|
382
|
+
...(tx.transaction.message.instructions || []),
|
|
383
|
+
...innerInstructions.flatMap((ii: any) => ii.instructions || []),
|
|
384
|
+
];
|
|
385
|
+
|
|
386
|
+
for (const ix of allInstructions) {
|
|
387
|
+
const parsed = (ix as any).parsed;
|
|
388
|
+
if (!parsed) continue;
|
|
389
|
+
|
|
390
|
+
// SPL token transfer/transferChecked
|
|
391
|
+
if (
|
|
392
|
+
(parsed.type === 'transfer' || parsed.type === 'transferChecked') &&
|
|
393
|
+
parsed.info
|
|
394
|
+
) {
|
|
395
|
+
const dest = parsed.info.destination || parsed.info.account;
|
|
396
|
+
const mint = parsed.info.mint;
|
|
397
|
+
const amountStr = parsed.info.tokenAmount?.uiAmountString || parsed.info.amount;
|
|
398
|
+
|
|
399
|
+
// Only process if destination is our wallet's token account
|
|
400
|
+
// For SPL, we check post token balances to match owner
|
|
401
|
+
const postBalances = tx.meta.postTokenBalances || [];
|
|
402
|
+
const isOurWallet = postBalances.some(
|
|
403
|
+
(b: any) =>
|
|
404
|
+
b.owner === wallet.address &&
|
|
405
|
+
b.mint === mint
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
if (!isOurWallet || !mint) continue;
|
|
409
|
+
|
|
410
|
+
const amount = BigInt(parsed.info.amount || '0');
|
|
411
|
+
if (amount === 0n) continue;
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
await processDiscoveredToken(
|
|
415
|
+
{
|
|
416
|
+
tokenAddress: mint,
|
|
417
|
+
walletAddress: wallet.address,
|
|
418
|
+
amount,
|
|
419
|
+
from: parsed.info.authority || parsed.info.source || '',
|
|
420
|
+
txHash: sigStrings[i],
|
|
421
|
+
blockNumber: BigInt(tx.slot),
|
|
422
|
+
},
|
|
423
|
+
chain,
|
|
424
|
+
ctx,
|
|
425
|
+
);
|
|
426
|
+
} catch (err) {
|
|
427
|
+
ctx.log.debug({ err, mint, wallet: wallet.address }, 'Discovery: failed Solana transfer');
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
} catch (err) {
|
|
433
|
+
ctx.log.warn({ err, chain, wallet: wallet.address }, 'Discovery: Solana wallet scan failed');
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Update sync state
|
|
438
|
+
await ctx.prisma.syncState.upsert({
|
|
439
|
+
where: { chain: syncKey },
|
|
440
|
+
create: {
|
|
441
|
+
chain: syncKey,
|
|
442
|
+
lastSyncAt: new Date(),
|
|
443
|
+
lastSyncStatus: 'ok',
|
|
444
|
+
syncCount: 1,
|
|
445
|
+
},
|
|
446
|
+
update: {
|
|
447
|
+
lastSyncAt: new Date(),
|
|
448
|
+
lastSyncStatus: 'ok',
|
|
449
|
+
lastError: null,
|
|
450
|
+
syncCount: { increment: 1 },
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// ─── Viem chain detection (same approach as balance-sync) ──────────────
|
|
456
|
+
|
|
457
|
+
const EVM_CHAINS = new Set(['base', 'ethereum', 'arbitrum', 'optimism', 'polygon']);
|
|
458
|
+
|
|
459
|
+
// ─── Job Definition ───────────────────────────────────────────────────
|
|
460
|
+
|
|
461
|
+
export const incomingScanJob: CronJob = {
|
|
462
|
+
id: 'incoming-scan',
|
|
463
|
+
name: 'Incoming Asset Discovery',
|
|
464
|
+
intervalKey: 'discovery.scan_interval',
|
|
465
|
+
defaultInterval: 60_000,
|
|
466
|
+
|
|
467
|
+
async run(ctx: CronContext): Promise<void> {
|
|
468
|
+
const enabled = ctx.defaults.get<boolean>('discovery.enabled', true);
|
|
469
|
+
if (!enabled) return;
|
|
470
|
+
|
|
471
|
+
// Discover which chains have wallets
|
|
472
|
+
const chains = await ctx.prisma.hotWallet.groupBy({ by: ['chain'] });
|
|
473
|
+
|
|
474
|
+
for (const { chain } of chains) {
|
|
475
|
+
try {
|
|
476
|
+
if (isSolanaChain(chain)) {
|
|
477
|
+
await scanSolanaChain(chain, ctx);
|
|
478
|
+
} else if (EVM_CHAINS.has(chain)) {
|
|
479
|
+
await scanEvmChain(chain, ctx);
|
|
480
|
+
} else {
|
|
481
|
+
ctx.log.debug({ chain }, 'Discovery: skipping unknown chain');
|
|
482
|
+
}
|
|
483
|
+
} catch (err) {
|
|
484
|
+
ctx.log.error({ err, chain }, 'Discovery: scan failed for chain');
|
|
485
|
+
|
|
486
|
+
const syncKey = `${chain}:discovery`;
|
|
487
|
+
await ctx.prisma.syncState.upsert({
|
|
488
|
+
where: { chain: syncKey },
|
|
489
|
+
create: {
|
|
490
|
+
chain: syncKey,
|
|
491
|
+
lastSyncAt: new Date(),
|
|
492
|
+
lastSyncStatus: 'error',
|
|
493
|
+
lastError: getErrorMessage(err),
|
|
494
|
+
syncCount: 1,
|
|
495
|
+
},
|
|
496
|
+
update: {
|
|
497
|
+
lastSyncAt: new Date(),
|
|
498
|
+
lastSyncStatus: 'error',
|
|
499
|
+
lastError: getErrorMessage(err),
|
|
500
|
+
syncCount: { increment: 1 },
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Price Sync Job
|
|
3
|
+
* Fetches ETH and SOL USD prices from CoinGecko free API.
|
|
4
|
+
* One HTTP call, two DB upserts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { CronJob, CronContext } from '../job';
|
|
8
|
+
|
|
9
|
+
const COINGECKO_URL =
|
|
10
|
+
'https://api.coingecko.com/api/v3/simple/price?ids=ethereum,solana&vs_currencies=usd';
|
|
11
|
+
|
|
12
|
+
export const nativePriceJob: CronJob = {
|
|
13
|
+
id: 'native-price',
|
|
14
|
+
name: 'Native Price Sync',
|
|
15
|
+
intervalKey: 'sync.active_interval',
|
|
16
|
+
defaultInterval: 15_000,
|
|
17
|
+
|
|
18
|
+
async run(ctx: CronContext): Promise<void> {
|
|
19
|
+
const enabled = ctx.defaults.get<boolean>('sync.enabled', true);
|
|
20
|
+
if (!enabled) return;
|
|
21
|
+
|
|
22
|
+
let data: { ethereum?: { usd?: number }; solana?: { usd?: number } };
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(COINGECKO_URL, {
|
|
26
|
+
signal: AbortSignal.timeout(10_000),
|
|
27
|
+
});
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
ctx.log.warn({ status: res.status }, 'CoinGecko returned non-OK status');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
data = await res.json();
|
|
33
|
+
} catch (err) {
|
|
34
|
+
ctx.log.warn({ err }, 'CoinGecko fetch failed (keeping stale price)');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ethPrice = data.ethereum?.usd;
|
|
39
|
+
const solPrice = data.solana?.usd;
|
|
40
|
+
|
|
41
|
+
const upserts: Promise<unknown>[] = [];
|
|
42
|
+
|
|
43
|
+
if (ethPrice != null) {
|
|
44
|
+
upserts.push(
|
|
45
|
+
ctx.prisma.nativePrice.upsert({
|
|
46
|
+
where: { currency: 'ETH' },
|
|
47
|
+
create: { currency: 'ETH', priceUsd: ethPrice.toString() },
|
|
48
|
+
update: { priceUsd: ethPrice.toString() },
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (solPrice != null) {
|
|
54
|
+
upserts.push(
|
|
55
|
+
ctx.prisma.nativePrice.upsert({
|
|
56
|
+
where: { currency: 'SOL' },
|
|
57
|
+
create: { currency: 'SOL', priceUsd: solPrice.toString() },
|
|
58
|
+
update: { priceUsd: solPrice.toString() },
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await Promise.all(upserts);
|
|
64
|
+
|
|
65
|
+
ctx.log.debug(
|
|
66
|
+
{ ethPrice, solPrice },
|
|
67
|
+
'Native prices updated'
|
|
68
|
+
);
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orphan Cleanup Job
|
|
3
|
+
* ------------------
|
|
4
|
+
* Rejects stale pending HumanAction records (strategy approvals, action tokens)
|
|
5
|
+
* that were left behind after crashes or restarts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { CronJob, CronContext } from '../job';
|
|
9
|
+
|
|
10
|
+
const STALE_THRESHOLD_MS = 15 * 60_000; // 15 minutes
|
|
11
|
+
|
|
12
|
+
async function cleanOrphanedActions(ctx: CronContext): Promise<number> {
|
|
13
|
+
const staleThreshold = new Date(Date.now() - STALE_THRESHOLD_MS);
|
|
14
|
+
const cleaned = await ctx.prisma.humanAction.updateMany({
|
|
15
|
+
where: {
|
|
16
|
+
status: 'pending',
|
|
17
|
+
type: { in: ['strategy:approve', 'action'] },
|
|
18
|
+
createdAt: { lt: staleThreshold },
|
|
19
|
+
},
|
|
20
|
+
data: { status: 'rejected', resolvedAt: new Date() },
|
|
21
|
+
});
|
|
22
|
+
return cleaned.count;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const orphanCleanupJob: CronJob = {
|
|
26
|
+
id: 'orphan-cleanup',
|
|
27
|
+
name: 'Orphan Cleanup',
|
|
28
|
+
intervalKey: 'cron.orphan_cleanup_interval',
|
|
29
|
+
defaultInterval: 5 * 60_000,
|
|
30
|
+
|
|
31
|
+
async setup(ctx) {
|
|
32
|
+
const count = await cleanOrphanedActions(ctx);
|
|
33
|
+
if (count > 0) ctx.log.warn({ count }, 'Cleaned orphaned pending actions on startup');
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
async run(ctx) {
|
|
37
|
+
const count = await cleanOrphanedActions(ctx);
|
|
38
|
+
if (count > 0) ctx.log.warn({ count }, 'Cleaned orphaned pending actions');
|
|
39
|
+
},
|
|
40
|
+
};
|