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,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron Server Entry Point
|
|
3
|
+
* Runs background jobs: balance sync, native price sync.
|
|
4
|
+
* Connects to the same DB as the wallet server.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { PrismaClient } from '@prisma/client';
|
|
8
|
+
import pino from 'pino';
|
|
9
|
+
import { getDbUrl } from '../lib/config';
|
|
10
|
+
import { preloadCache, getDefaultSync } from '../lib/defaults';
|
|
11
|
+
import { Scheduler } from './scheduler';
|
|
12
|
+
import { balanceSyncJob } from './jobs/balance-sync';
|
|
13
|
+
import { nativePriceJob } from './jobs/native-price';
|
|
14
|
+
import { incomingScanJob } from './jobs/incoming-scan';
|
|
15
|
+
import { orphanCleanupJob } from './jobs/orphan-cleanup';
|
|
16
|
+
import { strategyRunnerJob } from './jobs/strategy-runner';
|
|
17
|
+
import type { CronContext } from './job';
|
|
18
|
+
|
|
19
|
+
// Set DATABASE_URL before creating PrismaClient
|
|
20
|
+
process.env.DATABASE_URL = getDbUrl();
|
|
21
|
+
|
|
22
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
23
|
+
const isTest = process.env.NODE_ENV === 'test' || process.env.VITEST === 'true';
|
|
24
|
+
|
|
25
|
+
const log = pino({
|
|
26
|
+
level: isTest ? 'silent' : (process.env.LOG_LEVEL || 'info'),
|
|
27
|
+
...(isDev && !isTest
|
|
28
|
+
? {
|
|
29
|
+
transport: {
|
|
30
|
+
target: 'pino-pretty',
|
|
31
|
+
options: {
|
|
32
|
+
colorize: true,
|
|
33
|
+
translateTime: 'HH:MM:ss.l',
|
|
34
|
+
ignore: 'pid,hostname',
|
|
35
|
+
messageFormat: '[cron] {msg}',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
: {}),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const WS_BROADCAST_URL = process.env.WS_BROADCAST_URL ?? 'http://localhost:4748/broadcast';
|
|
43
|
+
|
|
44
|
+
async function emit(type: string, data: unknown): Promise<void> {
|
|
45
|
+
if (!WS_BROADCAST_URL) return;
|
|
46
|
+
|
|
47
|
+
const event = {
|
|
48
|
+
type,
|
|
49
|
+
timestamp: Date.now(),
|
|
50
|
+
source: 'cron',
|
|
51
|
+
data,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch(WS_BROADCAST_URL, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: { 'Content-Type': 'application/json' },
|
|
58
|
+
body: JSON.stringify(event),
|
|
59
|
+
});
|
|
60
|
+
if (!res.ok) {
|
|
61
|
+
log.warn({ status: res.status, type }, 'WebSocket broadcast failed');
|
|
62
|
+
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
log.debug({ err, type }, 'WebSocket broadcast unreachable');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function autoMigrate(): Promise<void> {
|
|
69
|
+
const { execSync } = await import('child_process');
|
|
70
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
71
|
+
try {
|
|
72
|
+
execSync('npx prisma migrate deploy', {
|
|
73
|
+
cwd: import.meta.dirname ? import.meta.dirname + '/../..' : process.cwd(),
|
|
74
|
+
env: { ...process.env, DATABASE_URL: dbUrl },
|
|
75
|
+
stdio: 'pipe',
|
|
76
|
+
});
|
|
77
|
+
log.info('Database migrations applied');
|
|
78
|
+
} catch {
|
|
79
|
+
log.debug('Database migration skipped (may already be up to date)');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function main(): Promise<void> {
|
|
84
|
+
log.info('AuraMaxx Cron Server starting...');
|
|
85
|
+
|
|
86
|
+
// Apply pending migrations
|
|
87
|
+
await autoMigrate();
|
|
88
|
+
|
|
89
|
+
// Create Prisma client
|
|
90
|
+
const prisma = new PrismaClient();
|
|
91
|
+
|
|
92
|
+
// Load system defaults into cache
|
|
93
|
+
await preloadCache().catch((err) => {
|
|
94
|
+
log.warn({ err }, 'Failed to preload defaults cache');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Build context
|
|
98
|
+
const ctx: CronContext = {
|
|
99
|
+
prisma,
|
|
100
|
+
broadcastUrl: WS_BROADCAST_URL,
|
|
101
|
+
emit,
|
|
102
|
+
defaults: {
|
|
103
|
+
get: <T>(key: string, fallback: T): T => getDefaultSync<T>(key, fallback),
|
|
104
|
+
},
|
|
105
|
+
log,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Build scheduler
|
|
109
|
+
const scheduler = new Scheduler();
|
|
110
|
+
scheduler.register(nativePriceJob);
|
|
111
|
+
scheduler.register(balanceSyncJob);
|
|
112
|
+
scheduler.register(incomingScanJob);
|
|
113
|
+
scheduler.register(orphanCleanupJob);
|
|
114
|
+
scheduler.register(strategyRunnerJob);
|
|
115
|
+
|
|
116
|
+
// Start all jobs
|
|
117
|
+
await scheduler.startAll(ctx);
|
|
118
|
+
|
|
119
|
+
log.info('Cron server running. Press Ctrl+C to stop.');
|
|
120
|
+
|
|
121
|
+
// Graceful shutdown
|
|
122
|
+
const shutdown = async (signal: string) => {
|
|
123
|
+
log.info({ signal }, 'Shutting down cron server...');
|
|
124
|
+
await scheduler.stopAll();
|
|
125
|
+
await prisma.$disconnect();
|
|
126
|
+
log.info('Cron server stopped');
|
|
127
|
+
process.exit(0);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
131
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
main().catch((err) => {
|
|
135
|
+
log.error({ err }, 'Cron server failed to start');
|
|
136
|
+
process.exit(1);
|
|
137
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CronJob interface and CronContext type for the cron server.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PrismaClient } from '@prisma/client';
|
|
6
|
+
import type { Logger } from 'pino';
|
|
7
|
+
|
|
8
|
+
export interface CronContext {
|
|
9
|
+
prisma: PrismaClient;
|
|
10
|
+
broadcastUrl: string;
|
|
11
|
+
emit: (type: string, data: unknown) => Promise<void>;
|
|
12
|
+
defaults: { get: <T>(key: string, fallback: T) => T };
|
|
13
|
+
log: Logger;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CronJob {
|
|
17
|
+
/** Unique job identifier */
|
|
18
|
+
id: string;
|
|
19
|
+
/** Human-readable name */
|
|
20
|
+
name: string;
|
|
21
|
+
/** SystemDefaults key for the interval */
|
|
22
|
+
intervalKey: string;
|
|
23
|
+
/** Fallback interval in ms if key not found */
|
|
24
|
+
defaultInterval: number;
|
|
25
|
+
/** Called once before the first run */
|
|
26
|
+
setup?(ctx: CronContext): Promise<void>;
|
|
27
|
+
/** The job's main work */
|
|
28
|
+
run(ctx: CronContext): Promise<void>;
|
|
29
|
+
/** Called on shutdown */
|
|
30
|
+
teardown?(): Promise<void>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Balance Sync Job
|
|
3
|
+
* Syncs ERC-20 token balances via Multicall3 and native ETH/SOL balances.
|
|
4
|
+
* Writes to TrackedAsset.lastBalance + NativeBalance table.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createPublicClient, http, erc20Abi, type Address, type MulticallResults } from 'viem';
|
|
8
|
+
import { base, mainnet } from 'viem/chains';
|
|
9
|
+
import { Connection, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js';
|
|
10
|
+
import type { CronJob, CronContext } from '../job';
|
|
11
|
+
import { getRpcUrl } from '../../lib/config';
|
|
12
|
+
import { getErrorMessage } from '../../lib/error';
|
|
13
|
+
|
|
14
|
+
// Multicall3 address (deployed on all major EVM chains)
|
|
15
|
+
const MULTICALL3 = '0xcA11bde05977b3631167028862bE2a173976CA11' as const;
|
|
16
|
+
|
|
17
|
+
// Map chain names to viem chain objects
|
|
18
|
+
const VIEM_CHAINS: Record<string, typeof base> = {
|
|
19
|
+
base,
|
|
20
|
+
ethereum: mainnet,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function isSolanaChain(chain: string): boolean {
|
|
24
|
+
return chain === 'solana' || chain === 'solana-devnet';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ─── EVM Balance Sync ─────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
async function syncEvmChain(
|
|
30
|
+
chain: string,
|
|
31
|
+
ctx: CronContext
|
|
32
|
+
): Promise<void> {
|
|
33
|
+
const viemChain = VIEM_CHAINS[chain];
|
|
34
|
+
const rpcUrl = await getRpcUrl(chain);
|
|
35
|
+
|
|
36
|
+
const client = createPublicClient({
|
|
37
|
+
chain: viemChain,
|
|
38
|
+
transport: http(rpcUrl),
|
|
39
|
+
batch: { multicall: true },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// 1. Get all tracked assets for this chain (skip bookmarks with null walletAddress)
|
|
43
|
+
const assets = await ctx.prisma.trackedAsset.findMany({
|
|
44
|
+
where: { chain, walletAddress: { not: null } },
|
|
45
|
+
select: {
|
|
46
|
+
id: true,
|
|
47
|
+
walletAddress: true,
|
|
48
|
+
tokenAddress: true,
|
|
49
|
+
lastBalance: true,
|
|
50
|
+
decimals: true,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// 2. Get all wallet addresses for this chain (for native balance)
|
|
55
|
+
const hotWallets = await ctx.prisma.hotWallet.findMany({
|
|
56
|
+
where: { chain },
|
|
57
|
+
select: { address: true },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const walletAddresses = hotWallets.map((w) => w.address as Address);
|
|
61
|
+
const maxPerCall = ctx.defaults.get<number>('sync.max_assets_per_call', 200);
|
|
62
|
+
|
|
63
|
+
// 3. Batch native balance reads via multicall getEthBalance
|
|
64
|
+
if (walletAddresses.length > 0) {
|
|
65
|
+
try {
|
|
66
|
+
const nativeContracts = walletAddresses.map((addr) => ({
|
|
67
|
+
address: MULTICALL3 as Address,
|
|
68
|
+
abi: [
|
|
69
|
+
{
|
|
70
|
+
inputs: [{ name: 'addr', type: 'address' }],
|
|
71
|
+
name: 'getEthBalance',
|
|
72
|
+
outputs: [{ name: 'balance', type: 'uint256' }],
|
|
73
|
+
stateMutability: 'view' as const,
|
|
74
|
+
type: 'function' as const,
|
|
75
|
+
},
|
|
76
|
+
] as const,
|
|
77
|
+
functionName: 'getEthBalance' as const,
|
|
78
|
+
args: [addr] as const,
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
const results = await client.multicall({
|
|
82
|
+
contracts: nativeContracts,
|
|
83
|
+
allowFailure: true,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const upserts: Promise<unknown>[] = [];
|
|
87
|
+
const changedBalances: { walletAddress: string; balance: string }[] = [];
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < walletAddresses.length; i++) {
|
|
90
|
+
const result = results[i];
|
|
91
|
+
if (result.status === 'success') {
|
|
92
|
+
const rawBalance = result.result as bigint;
|
|
93
|
+
// Format to full precision string (18 decimals)
|
|
94
|
+
const formatted = formatBalance(rawBalance, 18);
|
|
95
|
+
|
|
96
|
+
upserts.push(
|
|
97
|
+
ctx.prisma.nativeBalance.upsert({
|
|
98
|
+
where: {
|
|
99
|
+
walletAddress_chain: {
|
|
100
|
+
walletAddress: walletAddresses[i].toLowerCase(),
|
|
101
|
+
chain,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
create: {
|
|
105
|
+
walletAddress: walletAddresses[i].toLowerCase(),
|
|
106
|
+
chain,
|
|
107
|
+
balance: formatted,
|
|
108
|
+
},
|
|
109
|
+
update: { balance: formatted },
|
|
110
|
+
})
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
changedBalances.push({
|
|
114
|
+
walletAddress: walletAddresses[i].toLowerCase(),
|
|
115
|
+
balance: formatted,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await Promise.all(upserts);
|
|
121
|
+
|
|
122
|
+
// Emit balance:updated for native balances
|
|
123
|
+
if (changedBalances.length > 0) {
|
|
124
|
+
await ctx.emit('balance:updated', {
|
|
125
|
+
chain,
|
|
126
|
+
type: 'native',
|
|
127
|
+
balances: changedBalances,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
} catch (err) {
|
|
131
|
+
ctx.log.warn({ err, chain }, 'Native balance multicall failed');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 4. Batch ERC-20 balanceOf reads
|
|
136
|
+
if (assets.length > 0) {
|
|
137
|
+
// Chunk assets to respect max_assets_per_call
|
|
138
|
+
for (let offset = 0; offset < assets.length; offset += maxPerCall) {
|
|
139
|
+
const chunk = assets.slice(offset, offset + maxPerCall);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const contracts = chunk.map((asset) => ({
|
|
143
|
+
address: asset.tokenAddress as Address,
|
|
144
|
+
abi: erc20Abi,
|
|
145
|
+
functionName: 'balanceOf' as const,
|
|
146
|
+
args: [asset.walletAddress! as Address] as const,
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
const results = await client.multicall({
|
|
150
|
+
contracts,
|
|
151
|
+
allowFailure: true,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const now = new Date();
|
|
155
|
+
const updates: Promise<unknown>[] = [];
|
|
156
|
+
const changedAssets: { walletAddress: string; tokenAddress: string; balance: string }[] = [];
|
|
157
|
+
|
|
158
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
159
|
+
const result = results[i];
|
|
160
|
+
const asset = chunk[i];
|
|
161
|
+
|
|
162
|
+
if (result.status === 'success') {
|
|
163
|
+
const rawBalance = result.result as bigint;
|
|
164
|
+
const formatted = formatBalance(rawBalance, asset.decimals);
|
|
165
|
+
|
|
166
|
+
// Only update if balance changed
|
|
167
|
+
if (formatted !== asset.lastBalance) {
|
|
168
|
+
updates.push(
|
|
169
|
+
ctx.prisma.trackedAsset.update({
|
|
170
|
+
where: { id: asset.id },
|
|
171
|
+
data: { lastBalance: formatted, lastBalanceAt: now },
|
|
172
|
+
})
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
changedAssets.push({
|
|
176
|
+
walletAddress: asset.walletAddress!,
|
|
177
|
+
tokenAddress: asset.tokenAddress,
|
|
178
|
+
balance: formatted,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
await Promise.all(updates);
|
|
185
|
+
|
|
186
|
+
if (changedAssets.length > 0) {
|
|
187
|
+
await ctx.emit('balance:updated', {
|
|
188
|
+
chain,
|
|
189
|
+
type: 'token',
|
|
190
|
+
balances: changedAssets,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
} catch (err) {
|
|
194
|
+
ctx.log.warn({ err, chain, offset }, 'ERC-20 multicall chunk failed');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 5. Update sync state
|
|
200
|
+
await ctx.prisma.syncState.upsert({
|
|
201
|
+
where: { chain },
|
|
202
|
+
create: {
|
|
203
|
+
chain,
|
|
204
|
+
lastSyncAt: new Date(),
|
|
205
|
+
lastSyncStatus: 'ok',
|
|
206
|
+
syncCount: 1,
|
|
207
|
+
},
|
|
208
|
+
update: {
|
|
209
|
+
lastSyncAt: new Date(),
|
|
210
|
+
lastSyncStatus: 'ok',
|
|
211
|
+
lastError: null,
|
|
212
|
+
syncCount: { increment: 1 },
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ─── Solana Balance Sync ──────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
async function syncSolanaChain(
|
|
220
|
+
chain: string,
|
|
221
|
+
ctx: CronContext
|
|
222
|
+
): Promise<void> {
|
|
223
|
+
const rpcUrl = await getRpcUrl(chain);
|
|
224
|
+
const connection = new Connection(rpcUrl, 'confirmed');
|
|
225
|
+
|
|
226
|
+
// 1. Get all wallet addresses for this chain
|
|
227
|
+
const hotWallets = await ctx.prisma.hotWallet.findMany({
|
|
228
|
+
where: { chain },
|
|
229
|
+
select: { address: true },
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const walletAddresses = hotWallets.map((w) => w.address);
|
|
233
|
+
|
|
234
|
+
// 2. Batch native SOL balance
|
|
235
|
+
if (walletAddresses.length > 0) {
|
|
236
|
+
try {
|
|
237
|
+
const pubkeys = walletAddresses.map((a) => new PublicKey(a));
|
|
238
|
+
const accountInfos = await connection.getMultipleAccountsInfo(pubkeys);
|
|
239
|
+
|
|
240
|
+
const upserts: Promise<unknown>[] = [];
|
|
241
|
+
const changedBalances: { walletAddress: string; balance: string }[] = [];
|
|
242
|
+
|
|
243
|
+
for (let i = 0; i < walletAddresses.length; i++) {
|
|
244
|
+
const lamports = accountInfos[i]?.lamports || 0;
|
|
245
|
+
const balance = (lamports / LAMPORTS_PER_SOL).toString();
|
|
246
|
+
|
|
247
|
+
upserts.push(
|
|
248
|
+
ctx.prisma.nativeBalance.upsert({
|
|
249
|
+
where: {
|
|
250
|
+
walletAddress_chain: {
|
|
251
|
+
walletAddress: walletAddresses[i],
|
|
252
|
+
chain,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
create: { walletAddress: walletAddresses[i], chain, balance },
|
|
256
|
+
update: { balance },
|
|
257
|
+
})
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
changedBalances.push({ walletAddress: walletAddresses[i], balance });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
await Promise.all(upserts);
|
|
264
|
+
|
|
265
|
+
if (changedBalances.length > 0) {
|
|
266
|
+
await ctx.emit('balance:updated', {
|
|
267
|
+
chain,
|
|
268
|
+
type: 'native',
|
|
269
|
+
balances: changedBalances,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
} catch (err) {
|
|
273
|
+
ctx.log.warn({ err, chain }, 'Solana native balance fetch failed');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 3. SPL token balances — per wallet (getTokenAccountsByOwner returns all in one call)
|
|
278
|
+
const assets = await ctx.prisma.trackedAsset.findMany({
|
|
279
|
+
where: { chain, walletAddress: { not: null } },
|
|
280
|
+
select: {
|
|
281
|
+
id: true,
|
|
282
|
+
walletAddress: true,
|
|
283
|
+
tokenAddress: true,
|
|
284
|
+
lastBalance: true,
|
|
285
|
+
decimals: true,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
if (assets.length > 0) {
|
|
290
|
+
// Group assets by wallet
|
|
291
|
+
const assetsByWallet = new Map<string, typeof assets>();
|
|
292
|
+
for (const asset of assets) {
|
|
293
|
+
const wa = asset.walletAddress!; // guaranteed non-null by query filter
|
|
294
|
+
const list = assetsByWallet.get(wa) || [];
|
|
295
|
+
list.push(asset);
|
|
296
|
+
assetsByWallet.set(wa, list);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const { TOKEN_PROGRAM_ID } = await import('@solana/spl-token');
|
|
300
|
+
|
|
301
|
+
for (const [walletAddr, walletAssets] of assetsByWallet) {
|
|
302
|
+
try {
|
|
303
|
+
const pubkey = new PublicKey(walletAddr);
|
|
304
|
+
const tokenAccounts = await connection.getParsedTokenAccountsByOwner(
|
|
305
|
+
pubkey,
|
|
306
|
+
{ programId: TOKEN_PROGRAM_ID }
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// Build lookup: mint → balance
|
|
310
|
+
const balanceByMint = new Map<string, string>();
|
|
311
|
+
for (const { account } of tokenAccounts.value) {
|
|
312
|
+
const parsed = account.data.parsed?.info;
|
|
313
|
+
if (parsed?.mint && parsed?.tokenAmount?.uiAmountString) {
|
|
314
|
+
balanceByMint.set(parsed.mint, parsed.tokenAmount.uiAmountString);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const now = new Date();
|
|
319
|
+
const updates: Promise<unknown>[] = [];
|
|
320
|
+
const changedAssets: { walletAddress: string; tokenAddress: string; balance: string }[] = [];
|
|
321
|
+
|
|
322
|
+
for (const asset of walletAssets) {
|
|
323
|
+
const balance = balanceByMint.get(asset.tokenAddress) || '0';
|
|
324
|
+
|
|
325
|
+
if (balance !== asset.lastBalance) {
|
|
326
|
+
updates.push(
|
|
327
|
+
ctx.prisma.trackedAsset.update({
|
|
328
|
+
where: { id: asset.id },
|
|
329
|
+
data: { lastBalance: balance, lastBalanceAt: now },
|
|
330
|
+
})
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
changedAssets.push({
|
|
334
|
+
walletAddress: asset.walletAddress!,
|
|
335
|
+
tokenAddress: asset.tokenAddress,
|
|
336
|
+
balance,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
await Promise.all(updates);
|
|
342
|
+
|
|
343
|
+
if (changedAssets.length > 0) {
|
|
344
|
+
await ctx.emit('balance:updated', {
|
|
345
|
+
chain,
|
|
346
|
+
type: 'token',
|
|
347
|
+
balances: changedAssets,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
} catch (err) {
|
|
351
|
+
ctx.log.warn({ err, chain, wallet: walletAddr }, 'SPL token fetch failed');
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// 4. Update sync state
|
|
357
|
+
await ctx.prisma.syncState.upsert({
|
|
358
|
+
where: { chain },
|
|
359
|
+
create: {
|
|
360
|
+
chain,
|
|
361
|
+
lastSyncAt: new Date(),
|
|
362
|
+
lastSyncStatus: 'ok',
|
|
363
|
+
syncCount: 1,
|
|
364
|
+
},
|
|
365
|
+
update: {
|
|
366
|
+
lastSyncAt: new Date(),
|
|
367
|
+
lastSyncStatus: 'ok',
|
|
368
|
+
lastError: null,
|
|
369
|
+
syncCount: { increment: 1 },
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
375
|
+
|
|
376
|
+
function formatBalance(raw: bigint, decimals: number): string {
|
|
377
|
+
if (raw === 0n) return '0';
|
|
378
|
+
const divisor = 10n ** BigInt(decimals);
|
|
379
|
+
const whole = raw / divisor;
|
|
380
|
+
const fraction = raw % divisor;
|
|
381
|
+
if (fraction === 0n) return whole.toString();
|
|
382
|
+
const fractionStr = fraction.toString().padStart(decimals, '0').replace(/0+$/, '');
|
|
383
|
+
return `${whole}.${fractionStr}`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ─── Job Definition ───────────────────────────────────────────────────
|
|
387
|
+
|
|
388
|
+
export const balanceSyncJob: CronJob = {
|
|
389
|
+
id: 'balance-sync',
|
|
390
|
+
name: 'Balance Sync',
|
|
391
|
+
intervalKey: 'sync.background_interval',
|
|
392
|
+
defaultInterval: 120_000,
|
|
393
|
+
|
|
394
|
+
async run(ctx: CronContext): Promise<void> {
|
|
395
|
+
const enabled = ctx.defaults.get<boolean>('sync.enabled', true);
|
|
396
|
+
if (!enabled) return;
|
|
397
|
+
|
|
398
|
+
// Discover which chains have wallets
|
|
399
|
+
const chains = await ctx.prisma.hotWallet.groupBy({
|
|
400
|
+
by: ['chain'],
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
for (const { chain } of chains) {
|
|
404
|
+
try {
|
|
405
|
+
if (isSolanaChain(chain)) {
|
|
406
|
+
await syncSolanaChain(chain, ctx);
|
|
407
|
+
} else if (VIEM_CHAINS[chain]) {
|
|
408
|
+
await syncEvmChain(chain, ctx);
|
|
409
|
+
} else {
|
|
410
|
+
// Unknown chain — try as generic EVM
|
|
411
|
+
ctx.log.debug({ chain }, 'Skipping unknown chain (no viem config)');
|
|
412
|
+
}
|
|
413
|
+
} catch (err) {
|
|
414
|
+
ctx.log.error({ err, chain }, 'Balance sync failed for chain');
|
|
415
|
+
|
|
416
|
+
// Record error in sync state
|
|
417
|
+
await ctx.prisma.syncState.upsert({
|
|
418
|
+
where: { chain },
|
|
419
|
+
create: {
|
|
420
|
+
chain,
|
|
421
|
+
lastSyncAt: new Date(),
|
|
422
|
+
lastSyncStatus: 'error',
|
|
423
|
+
lastError: getErrorMessage(err),
|
|
424
|
+
syncCount: 1,
|
|
425
|
+
},
|
|
426
|
+
update: {
|
|
427
|
+
lastSyncAt: new Date(),
|
|
428
|
+
lastSyncStatus: 'error',
|
|
429
|
+
lastError: getErrorMessage(err),
|
|
430
|
+
syncCount: { increment: 1 },
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
};
|