auramaxx 1.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +26 -0
- package/README.md +112 -0
- package/bin/aurawallet.js +121 -0
- package/docs/ADAPTERS.md +467 -0
- package/docs/API.md +2679 -0
- package/docs/APPS.md +198 -0
- package/docs/ARCHITECTURE.md +350 -0
- package/docs/AUTH.md +698 -0
- package/docs/BEST-PRACTICES.md +121 -0
- package/docs/CLI.md +61 -0
- package/docs/DEVELOPING-APPS.md +452 -0
- package/docs/EXTENSION.md +97 -0
- package/docs/JOBS.md +33 -0
- package/docs/MCP.md +76 -0
- package/docs/PROTOCOL.md +142 -0
- package/docs/SETUP.md +219 -0
- package/docs/WORKSPACE.md +672 -0
- package/docs/agent-auth.md +63 -0
- package/docs/aura-file.md +48 -0
- package/docs/credentials.md +53 -0
- package/docs/external/getting-started.md +65 -0
- package/docs/external/overview.md +45 -0
- package/docs/external/use-cases.md +48 -0
- package/docs/external/why-aura.md +35 -0
- package/docs/jobs/connect-agent.md +77 -0
- package/docs/jobs/migrate-from-dotenv.md +79 -0
- package/docs/jobs/recover-from-lockout.md +72 -0
- package/docs/jobs/secure-ci.md +63 -0
- package/docs/oauth2.md +42 -0
- package/docs/passkeys.md +60 -0
- package/docs/security.md +540 -0
- package/docs/specs/aura-open-protocol.md +61 -0
- package/docs/specs/aura-provider-plugin.md +24 -0
- package/docs/specs/aura-registry-model.md +31 -0
- package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
- package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
- package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
- package/docs/specs/fixtures/valid-basic.aura +4 -0
- package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
- package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
- package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
- package/docs/totp.md +40 -0
- package/docs/wallet/AI.md +508 -0
- package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
- package/docs/wallet/README.md +47 -0
- package/docs/wallet/STRATEGY.md +89 -0
- package/next.config.ts +21 -0
- package/package.json +151 -0
- package/postcss.config.mjs +8 -0
- package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
- package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
- package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +447 -0
- package/public/logo-chevron.svg +31 -0
- package/public/logo-concentric.svg +31 -0
- package/public/logo-crosshatch.svg +39 -0
- package/public/logo-dashed.svg +39 -0
- package/public/logo-horizontal.svg +31 -0
- package/public/logo-m56.svg +64 -0
- package/public/logo.webp +0 -0
- package/scripts/add-app.js +245 -0
- package/scripts/init.sh +57 -0
- package/scripts/migrate-apikeys-to-credentials.ts +35 -0
- package/scripts/sandbox-agent-flow.sh +235 -0
- package/scripts/sandbox.sh +175 -0
- package/scripts/validate-job-docs.mjs +125 -0
- package/server/abi/SwapHelper.json +438 -0
- package/server/cli/approval.ts +447 -0
- package/server/cli/commands/app.ts +204 -0
- package/server/cli/commands/cron.ts +24 -0
- package/server/cli/commands/doctor.ts +1007 -0
- package/server/cli/commands/env.ts +456 -0
- package/server/cli/commands/init.ts +752 -0
- package/server/cli/commands/mcp.ts +125 -0
- package/server/cli/commands/restore.ts +314 -0
- package/server/cli/commands/shell-hook.ts +468 -0
- package/server/cli/commands/start.ts +62 -0
- package/server/cli/commands/status.ts +59 -0
- package/server/cli/commands/stop.ts +14 -0
- package/server/cli/commands/token.ts +180 -0
- package/server/cli/commands/unlock.ts +49 -0
- package/server/cli/commands/vault.ts +417 -0
- package/server/cli/index.ts +328 -0
- package/server/cli/lib/aura-parser.ts +64 -0
- package/server/cli/lib/credential-create.ts +74 -0
- package/server/cli/lib/credential-resolve.ts +254 -0
- package/server/cli/lib/dotenv-migrate.ts +116 -0
- package/server/cli/lib/dotenv-parser.ts +146 -0
- package/server/cli/lib/http.ts +91 -0
- package/server/cli/lib/init-steps.ts +76 -0
- package/server/cli/lib/local-agent-trust.ts +45 -0
- package/server/cli/lib/process.ts +136 -0
- package/server/cli/lib/prompt.ts +85 -0
- package/server/cli/lib/theme.ts +240 -0
- package/server/cli/socket.ts +570 -0
- package/server/cli/transport-client.ts +50 -0
- package/server/cron/index.ts +137 -0
- package/server/cron/job.ts +31 -0
- package/server/cron/jobs/balance-sync.ts +436 -0
- package/server/cron/jobs/incoming-scan.ts +506 -0
- package/server/cron/jobs/native-price.ts +70 -0
- package/server/cron/jobs/orphan-cleanup.ts +40 -0
- package/server/cron/jobs/strategy-runner.ts +175 -0
- package/server/cron/scheduler.ts +125 -0
- package/server/index.ts +406 -0
- package/server/lib/adapters/factory.ts +110 -0
- package/server/lib/adapters/index.ts +19 -0
- package/server/lib/adapters/router.ts +297 -0
- package/server/lib/adapters/telegram.ts +645 -0
- package/server/lib/adapters/types.ts +89 -0
- package/server/lib/adapters/webhook.ts +95 -0
- package/server/lib/address.ts +49 -0
- package/server/lib/agent-auth/contracts.ts +1194 -0
- package/server/lib/agent-profiles.ts +328 -0
- package/server/lib/ai.ts +285 -0
- package/server/lib/api-registry/contracts.ts +86 -0
- package/server/lib/api-registry/validation.ts +172 -0
- package/server/lib/apikey-migration.ts +189 -0
- package/server/lib/app-installer.ts +505 -0
- package/server/lib/app-tokens.ts +247 -0
- package/server/lib/auth.ts +314 -0
- package/server/lib/batch.ts +242 -0
- package/server/lib/cold.ts +874 -0
- package/server/lib/config.ts +381 -0
- package/server/lib/credential-access-audit.ts +85 -0
- package/server/lib/credential-access-policy.ts +110 -0
- package/server/lib/credential-health.ts +343 -0
- package/server/lib/credential-import.ts +487 -0
- package/server/lib/credential-scope.ts +87 -0
- package/server/lib/credential-shares.ts +190 -0
- package/server/lib/credential-transport.ts +342 -0
- package/server/lib/credential-vault.ts +77 -0
- package/server/lib/credentials.ts +333 -0
- package/server/lib/crypto.ts +8 -0
- package/server/lib/db.ts +15 -0
- package/server/lib/defaults.ts +366 -0
- package/server/lib/dex/index.ts +80 -0
- package/server/lib/dex/relay.ts +235 -0
- package/server/lib/dex/types.ts +59 -0
- package/server/lib/dex/uniswap.ts +370 -0
- package/server/lib/e2e-agent/artifacts.ts +36 -0
- package/server/lib/e2e-agent/contracts.ts +112 -0
- package/server/lib/e2e-agent/validation.ts +135 -0
- package/server/lib/encrypt.ts +128 -0
- package/server/lib/error.ts +20 -0
- package/server/lib/events.ts +205 -0
- package/server/lib/hot.ts +357 -0
- package/server/lib/key-fingerprint.ts +28 -0
- package/server/lib/logger.ts +331 -0
- package/server/lib/network.ts +137 -0
- package/server/lib/notifications.ts +219 -0
- package/server/lib/oauth2-refresh.ts +241 -0
- package/server/lib/oursecret.ts +54 -0
- package/server/lib/passkey-credential.ts +360 -0
- package/server/lib/passkey.ts +68 -0
- package/server/lib/permissions.ts +248 -0
- package/server/lib/pino.ts +24 -0
- package/server/lib/policy-preview.ts +138 -0
- package/server/lib/price.ts +338 -0
- package/server/lib/prices.ts +34 -0
- package/server/lib/project-scope.ts +239 -0
- package/server/lib/resolve-action.ts +427 -0
- package/server/lib/resolve.ts +36 -0
- package/server/lib/sessions.ts +632 -0
- package/server/lib/solana/connection.ts +26 -0
- package/server/lib/solana/jupiter.ts +128 -0
- package/server/lib/solana/transfer.ts +108 -0
- package/server/lib/solana/wallet.ts +136 -0
- package/server/lib/strategy/emits.ts +21 -0
- package/server/lib/strategy/engine.ts +1305 -0
- package/server/lib/strategy/executor.ts +115 -0
- package/server/lib/strategy/hook-context.ts +158 -0
- package/server/lib/strategy/hooks.ts +990 -0
- package/server/lib/strategy/index.ts +28 -0
- package/server/lib/strategy/installer.ts +305 -0
- package/server/lib/strategy/loader.ts +256 -0
- package/server/lib/strategy/message.ts +235 -0
- package/server/lib/strategy/repository.ts +218 -0
- package/server/lib/strategy/session-logger.ts +693 -0
- package/server/lib/strategy/sources.ts +288 -0
- package/server/lib/strategy/state.ts +189 -0
- package/server/lib/strategy/templates.ts +403 -0
- package/server/lib/strategy/tick.ts +404 -0
- package/server/lib/strategy/types.ts +230 -0
- package/server/lib/swap.ts +3 -0
- package/server/lib/temp.ts +86 -0
- package/server/lib/token-metadata.ts +86 -0
- package/server/lib/token-safety.ts +200 -0
- package/server/lib/token-search.ts +444 -0
- package/server/lib/totp.ts +194 -0
- package/server/lib/transactions.ts +123 -0
- package/server/lib/transport.ts +75 -0
- package/server/lib/txhistory/decoder.ts +262 -0
- package/server/lib/txhistory/enricher.ts +652 -0
- package/server/lib/txhistory/index.ts +391 -0
- package/server/lib/txhistory/signatures.ts +59 -0
- package/server/lib/verified-summary.ts +421 -0
- package/server/mcp/profile-policy.ts +30 -0
- package/server/mcp/server.ts +619 -0
- package/server/mcp/tools.ts +523 -0
- package/server/middleware/auth.ts +119 -0
- package/server/middleware/requestLogger.ts +84 -0
- package/server/routes/actions.ts +459 -0
- package/server/routes/adapters.ts +703 -0
- package/server/routes/addressbook.ts +113 -0
- package/server/routes/ai.ts +34 -0
- package/server/routes/apikeys.ts +295 -0
- package/server/routes/apps.ts +601 -0
- package/server/routes/auth.ts +457 -0
- package/server/routes/backup.ts +340 -0
- package/server/routes/batch.ts +270 -0
- package/server/routes/bookmarks.ts +162 -0
- package/server/routes/credential-shares.ts +198 -0
- package/server/routes/credential-vaults.ts +154 -0
- package/server/routes/credentials.ts +1290 -0
- package/server/routes/dashboard.ts +71 -0
- package/server/routes/defaults.ts +124 -0
- package/server/routes/fund.ts +229 -0
- package/server/routes/import.ts +352 -0
- package/server/routes/launch.ts +665 -0
- package/server/routes/lock.ts +54 -0
- package/server/routes/logs.ts +68 -0
- package/server/routes/nuke.ts +111 -0
- package/server/routes/passkey-credentials.ts +99 -0
- package/server/routes/passkey.ts +346 -0
- package/server/routes/portfolio.ts +217 -0
- package/server/routes/price.ts +63 -0
- package/server/routes/resolve.ts +31 -0
- package/server/routes/security.ts +45 -0
- package/server/routes/send-evm.ts +241 -0
- package/server/routes/send-solana.ts +281 -0
- package/server/routes/send.ts +178 -0
- package/server/routes/setup.ts +210 -0
- package/server/routes/strategy.ts +894 -0
- package/server/routes/swap-evm.ts +353 -0
- package/server/routes/swap-solana.ts +177 -0
- package/server/routes/swap.ts +356 -0
- package/server/routes/token.ts +247 -0
- package/server/routes/unlock.ts +403 -0
- package/server/routes/wallet-assets.ts +361 -0
- package/server/routes/wallet-transactions.ts +515 -0
- package/server/routes/wallet.ts +710 -0
- package/server/types.ts +146 -0
- package/skills/aurawallet/SKILL.md +739 -0
- package/skills/aurawallet-setup/SKILL.md +74 -0
- package/skills/security-review/SKILL.md +148 -0
- package/src/app/api/agent-requests/route.ts +30 -0
- package/src/app/api/apps/install/route.ts +126 -0
- package/src/app/api/apps/manifests/route.ts +16 -0
- package/src/app/api/apps/static/[...path]/route.ts +57 -0
- package/src/app/api/events/route.ts +92 -0
- package/src/app/api/page.tsx +212 -0
- package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
- package/src/app/api/workspace/[id]/apps/route.ts +81 -0
- package/src/app/api/workspace/[id]/export/route.ts +67 -0
- package/src/app/api/workspace/[id]/route.ts +168 -0
- package/src/app/api/workspace/auth.ts +34 -0
- package/src/app/api/workspace/config/route.ts +106 -0
- package/src/app/api/workspace/import/route.ts +127 -0
- package/src/app/api/workspace/route.ts +116 -0
- package/src/app/app/page.tsx +2122 -0
- package/src/app/apple-icon.png +0 -0
- package/src/app/docs/page.tsx +178 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +572 -0
- package/src/app/health/page.tsx +5 -0
- package/src/app/hello/page.tsx +15 -0
- package/src/app/icon.png +0 -0
- package/src/app/layout.tsx +34 -0
- package/src/app/page.tsx +986 -0
- package/src/app/providers.tsx +90 -0
- package/src/app/share/[token]/page.tsx +295 -0
- package/src/components/ChainSelector.tsx +144 -0
- package/src/components/HumanActionBar.tsx +695 -0
- package/src/components/NotificationDrawer.tsx +129 -0
- package/src/components/apps/AgentKeysApp.tsx +490 -0
- package/src/components/apps/App.tsx +153 -0
- package/src/components/apps/AppGrid.tsx +15 -0
- package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
- package/src/components/apps/DraggableApp.tsx +562 -0
- package/src/components/apps/IFrameApp.tsx +73 -0
- package/src/components/apps/LogsApp.tsx +360 -0
- package/src/components/apps/SendApp.tsx +394 -0
- package/src/components/apps/SetupWizardApp.tsx +1004 -0
- package/src/components/apps/SystemDefaultsApp.tsx +845 -0
- package/src/components/apps/ThirdPartyApp.tsx +428 -0
- package/src/components/apps/TokenApp.tsx +319 -0
- package/src/components/apps/TransactionsApp.tsx +438 -0
- package/src/components/apps/WalletDetailApp.tsx +1505 -0
- package/src/components/apps/index.ts +13 -0
- package/src/components/design-system/Button.tsx +53 -0
- package/src/components/design-system/ChainIndicator.tsx +65 -0
- package/src/components/design-system/ChainSelector.tsx +137 -0
- package/src/components/design-system/ConfirmationModal.tsx +106 -0
- package/src/components/design-system/ConfirmationPopover.tsx +81 -0
- package/src/components/design-system/Drawer.tsx +123 -0
- package/src/components/design-system/FilterDropdown.tsx +72 -0
- package/src/components/design-system/Modal.tsx +206 -0
- package/src/components/design-system/Popover.tsx +142 -0
- package/src/components/design-system/TextInput.tsx +85 -0
- package/src/components/design-system/Toggle.tsx +58 -0
- package/src/components/design-system/index.ts +11 -0
- package/src/components/docs/DocsThemeToggle.tsx +49 -0
- package/src/components/health/CredentialHealthDashboard.tsx +214 -0
- package/src/components/icons/ChainIcons.tsx +72 -0
- package/src/components/layout/AppStoreDrawer.tsx +369 -0
- package/src/components/layout/ContentArea.tsx +21 -0
- package/src/components/layout/TabBar.tsx +278 -0
- package/src/components/layout/WalletSidebar.tsx +1033 -0
- package/src/components/layout/index.ts +4 -0
- package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
- package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
- package/src/components/vault/ApiKeysConsole.tsx +1080 -0
- package/src/components/vault/AuditConsole.tsx +584 -0
- package/src/components/vault/CredentialDetail.tsx +455 -0
- package/src/components/vault/CredentialEmpty.tsx +55 -0
- package/src/components/vault/CredentialField.tsx +361 -0
- package/src/components/vault/CredentialForm.tsx +1212 -0
- package/src/components/vault/CredentialList.tsx +165 -0
- package/src/components/vault/CredentialRow.tsx +97 -0
- package/src/components/vault/CredentialShareModal.tsx +178 -0
- package/src/components/vault/CredentialVault.tsx +754 -0
- package/src/components/vault/CredentialWalletWidget.tsx +103 -0
- package/src/components/vault/ImportCredentialsModal.tsx +515 -0
- package/src/components/vault/LargeTypeModal.tsx +64 -0
- package/src/components/vault/PasswordGenerator.tsx +224 -0
- package/src/components/vault/TOTPDisplay.tsx +123 -0
- package/src/components/vault/VaultSidebar.tsx +413 -0
- package/src/components/vault/types.ts +54 -0
- package/src/context/AuthContext.tsx +337 -0
- package/src/context/PriceContext.tsx +113 -0
- package/src/context/ThemeContext.tsx +164 -0
- package/src/context/WebSocketContext.tsx +269 -0
- package/src/context/WorkspaceContext.tsx +668 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useAgentActions.ts +368 -0
- package/src/hooks/useBalance.ts +103 -0
- package/src/hooks/useBalances.ts +129 -0
- package/src/instrumentation.ts +12 -0
- package/src/lib/api.ts +449 -0
- package/src/lib/app-loader.ts +148 -0
- package/src/lib/app-registry.ts +178 -0
- package/src/lib/app-sdk.ts +157 -0
- package/src/lib/audit-console-adapter.ts +151 -0
- package/src/lib/auth-client.ts +75 -0
- package/src/lib/config.ts +74 -0
- package/src/lib/crypto.ts +112 -0
- package/src/lib/db.ts +21 -0
- package/src/lib/docs.ts +390 -0
- package/src/lib/events.ts +361 -0
- package/src/lib/pino.ts +24 -0
- package/src/lib/theme-handlers.ts +168 -0
- package/src/lib/theme.ts +351 -0
- package/src/lib/tokenData.ts +378 -0
- package/src/lib/vault-crypto.ts +129 -0
- package/src/lib/websocket-server.ts +302 -0
- package/src/lib/websocket-setup.ts +79 -0
- package/src/lib/wordlist.ts +2050 -0
- package/src/lib/workspace-handlers.ts +285 -0
- package/start.sh +80 -0
- package/tailwind.config.ts +99 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
import { PublicKey, VersionedTransaction } from '@solana/web3.js';
|
|
3
|
+
import { getMint } from '@solana/spl-token';
|
|
4
|
+
import { getHotWallet, tokenCanAccessWallet } from '../lib/hot';
|
|
5
|
+
import { getTempSolanaKeypair, hasTempWallet } from '../lib/temp';
|
|
6
|
+
import { isUnlocked, getSolanaColdAddress, listVaults } from '../lib/cold';
|
|
7
|
+
import { reserveSpend, releaseSpend } from '../lib/sessions';
|
|
8
|
+
import { logger } from '../lib/logger';
|
|
9
|
+
import { hasAnyPermission, isAdmin } from '../lib/permissions';
|
|
10
|
+
import { getNativeAddress, getNativeCurrency } from '../lib/address';
|
|
11
|
+
import { getSolanaConnection } from '../lib/solana/connection';
|
|
12
|
+
import { getSolanaKeypair } from '../lib/solana/wallet';
|
|
13
|
+
import { buildSolTransfer, buildSplTransfer, sendSolanaTransaction } from '../lib/solana/transfer';
|
|
14
|
+
import { getErrorMessage, HttpError } from '../lib/error';
|
|
15
|
+
import { recordTransaction, autoTrackToken } from '../lib/transactions';
|
|
16
|
+
import type { AuthInfo } from '../middleware/auth';
|
|
17
|
+
import type { ChainConfig } from '../lib/config';
|
|
18
|
+
|
|
19
|
+
export async function handleSolanaSend(
|
|
20
|
+
req: Request,
|
|
21
|
+
res: Response,
|
|
22
|
+
auth: AuthInfo,
|
|
23
|
+
targetChain: string,
|
|
24
|
+
chainConfig: ChainConfig
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
try {
|
|
27
|
+
const {
|
|
28
|
+
from,
|
|
29
|
+
amount,
|
|
30
|
+
value,
|
|
31
|
+
data,
|
|
32
|
+
chain,
|
|
33
|
+
tokenAddress,
|
|
34
|
+
transaction: rawTransaction,
|
|
35
|
+
description: userDescription
|
|
36
|
+
} = req.body;
|
|
37
|
+
|
|
38
|
+
// 'to' may have been resolved by the dispatcher (ENS resolution)
|
|
39
|
+
const to = req.body.to;
|
|
40
|
+
const rawValue = amount || value;
|
|
41
|
+
|
|
42
|
+
// Parse value for limit checks
|
|
43
|
+
let valueWei = BigInt(0);
|
|
44
|
+
if (rawValue) {
|
|
45
|
+
valueWei = BigInt(rawValue);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 'to' is required for simple sends, but not for raw VersionedTransaction
|
|
49
|
+
if (!rawTransaction && !to) {
|
|
50
|
+
res.status(400).json({ error: 'to address is required for Solana sends' });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const currency = getNativeAddress(targetChain);
|
|
55
|
+
const nativeCurrency = getNativeCurrency(targetChain);
|
|
56
|
+
|
|
57
|
+
// Determine wallet type
|
|
58
|
+
const hotWallet = await getHotWallet(from);
|
|
59
|
+
const isTempWallet = hasTempWallet(from);
|
|
60
|
+
|
|
61
|
+
if (!hotWallet && !isTempWallet) {
|
|
62
|
+
res.status(404).json({ error: 'Wallet not found' });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Permission checks
|
|
67
|
+
if (hotWallet) {
|
|
68
|
+
if (!isAdmin(auth) && !hasAnyPermission(auth.token.permissions, ['send:hot'])) {
|
|
69
|
+
logger.permissionDenied('send:hot', auth.token.agentId, '/send');
|
|
70
|
+
res.status(403).json({ error: 'Token does not have send:hot permission' });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const canAccess = await tokenCanAccessWallet(auth.tokenHash, auth.token.walletAccess, from, targetChain);
|
|
74
|
+
if (!isAdmin(auth) && !canAccess) {
|
|
75
|
+
logger.permissionDenied('wallet_access', auth.token.agentId, '/send');
|
|
76
|
+
res.status(403).json({ error: 'Token does not have access to this wallet' });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
} else if (isTempWallet) {
|
|
80
|
+
if (!isAdmin(auth) && !hasAnyPermission(auth.token.permissions, ['send:temp'])) {
|
|
81
|
+
logger.permissionDenied('send:temp', auth.token.agentId, '/send');
|
|
82
|
+
res.status(403).json({ error: 'Token does not have send:temp permission' });
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!isUnlocked()) {
|
|
88
|
+
logger.authFailed('Cold wallet locked', '/send');
|
|
89
|
+
res.status(401).json({ error: 'Cold wallet must be unlocked to send from hot wallet' });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Get signer keypair (shared by both raw tx and simple transfer paths)
|
|
94
|
+
let signerKeypair;
|
|
95
|
+
if (hotWallet) {
|
|
96
|
+
signerKeypair = await getSolanaKeypair(from);
|
|
97
|
+
} else {
|
|
98
|
+
signerKeypair = getTempSolanaKeypair(from);
|
|
99
|
+
if (!signerKeypair) {
|
|
100
|
+
res.status(404).json({ error: 'Temp Solana wallet not found' });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// --- Raw VersionedTransaction path ---
|
|
106
|
+
if (rawTransaction) {
|
|
107
|
+
if (typeof rawTransaction !== 'string') {
|
|
108
|
+
res.status(400).json({ error: 'transaction must be a base64-encoded string' });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let tx: VersionedTransaction;
|
|
113
|
+
try {
|
|
114
|
+
tx = VersionedTransaction.deserialize(Buffer.from(rawTransaction, 'base64'));
|
|
115
|
+
} catch (err) {
|
|
116
|
+
res.status(400).json({ error: 'Invalid transaction: failed to deserialize VersionedTransaction' });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const connection = await getSolanaConnection(targetChain);
|
|
121
|
+
|
|
122
|
+
// Sign and submit
|
|
123
|
+
tx.sign([signerKeypair]);
|
|
124
|
+
const signature = await connection.sendRawTransaction(tx.serialize(), {
|
|
125
|
+
skipPreflight: false,
|
|
126
|
+
maxRetries: 2,
|
|
127
|
+
});
|
|
128
|
+
await connection.confirmTransaction(signature, 'confirmed');
|
|
129
|
+
|
|
130
|
+
// Log
|
|
131
|
+
const description = userDescription || `Program transaction from ${from}`;
|
|
132
|
+
await recordTransaction({
|
|
133
|
+
walletAddress: from,
|
|
134
|
+
txHash: signature,
|
|
135
|
+
type: 'contract',
|
|
136
|
+
amount: '0',
|
|
137
|
+
from,
|
|
138
|
+
to: to || undefined,
|
|
139
|
+
description,
|
|
140
|
+
chain: targetChain,
|
|
141
|
+
logTitle: 'Program Transaction',
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
res.json({
|
|
145
|
+
success: true,
|
|
146
|
+
hash: signature,
|
|
147
|
+
from,
|
|
148
|
+
chain: targetChain,
|
|
149
|
+
type: 'program'
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// --- SPL Token transfer path ---
|
|
155
|
+
if (tokenAddress) {
|
|
156
|
+
const connection = await getSolanaConnection(targetChain);
|
|
157
|
+
const fromPubkey = new PublicKey(from);
|
|
158
|
+
const toPubkey = new PublicKey(to!);
|
|
159
|
+
const mint = new PublicKey(tokenAddress);
|
|
160
|
+
|
|
161
|
+
// Resolve decimals from on-chain mint account
|
|
162
|
+
const mintInfo = await getMint(connection, mint);
|
|
163
|
+
const decimals = mintInfo.decimals;
|
|
164
|
+
|
|
165
|
+
// Token sends skip native spending limits (spending tokens, not SOL)
|
|
166
|
+
let txHash: string;
|
|
167
|
+
try {
|
|
168
|
+
const tx = await buildSplTransfer(connection, fromPubkey, toPubkey, mint, BigInt(rawValue || '0'), decimals);
|
|
169
|
+
txHash = await sendSolanaTransaction(connection, tx, signerKeypair);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
throw err;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Log
|
|
175
|
+
const tokenAmountStr = rawValue || '0';
|
|
176
|
+
const description = userDescription || `Sent ${tokenAmountStr} tokens of ${tokenAddress} to ${to}`;
|
|
177
|
+
await recordTransaction({
|
|
178
|
+
walletAddress: from,
|
|
179
|
+
txHash,
|
|
180
|
+
type: 'send',
|
|
181
|
+
tokenAddress,
|
|
182
|
+
tokenAmount: tokenAmountStr,
|
|
183
|
+
from,
|
|
184
|
+
to: to!,
|
|
185
|
+
description,
|
|
186
|
+
chain: targetChain,
|
|
187
|
+
logTitle: 'Token Send',
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
await autoTrackToken({
|
|
191
|
+
walletAddress: from,
|
|
192
|
+
tokenAddress,
|
|
193
|
+
chain: targetChain,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const agentId = !isAdmin(auth) ? auth.token.agentId : undefined;
|
|
197
|
+
logger.send(from, to!, `${tokenAmountStr} tokens`, txHash, agentId);
|
|
198
|
+
|
|
199
|
+
res.json({
|
|
200
|
+
success: true,
|
|
201
|
+
hash: txHash,
|
|
202
|
+
from,
|
|
203
|
+
to,
|
|
204
|
+
tokenAddress,
|
|
205
|
+
tokenAmount: tokenAmountStr,
|
|
206
|
+
chain: targetChain
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// --- Simple SOL transfer path ---
|
|
212
|
+
|
|
213
|
+
// Check if send is going to any vault's Solana address (vault bypass)
|
|
214
|
+
const solColdAddress = getSolanaColdAddress();
|
|
215
|
+
let isSendToVault = solColdAddress && to === solColdAddress;
|
|
216
|
+
if (!isSendToVault) {
|
|
217
|
+
const vaults = listVaults();
|
|
218
|
+
isSendToVault = vaults.some(v => v.solanaAddress === to);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// For Solana, convert lamports to SOL for limit checks (9 decimals, not 18)
|
|
222
|
+
const valueSol = Number(valueWei) / 1e9;
|
|
223
|
+
|
|
224
|
+
// Reserve spending atomically (prevents TOCTOU race between concurrent requests)
|
|
225
|
+
const needsLimit = !isAdmin(auth) && !isSendToVault && valueSol > 0;
|
|
226
|
+
if (needsLimit) {
|
|
227
|
+
const reserve = reserveSpend(auth.tokenHash, auth.token, 'send', valueSol, currency);
|
|
228
|
+
if (!reserve.ok) {
|
|
229
|
+
logger.limitExceeded(auth.token.agentId, 'send', valueSol, reserve.remaining);
|
|
230
|
+
res.status(403).json({ error: 'Amount exceeds remaining send limit', remaining: reserve.remaining, requested: valueSol });
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let txHash: string;
|
|
236
|
+
try {
|
|
237
|
+
const connection = await getSolanaConnection(targetChain);
|
|
238
|
+
const fromPubkey = new PublicKey(from);
|
|
239
|
+
const toPubkey = new PublicKey(to!);
|
|
240
|
+
|
|
241
|
+
// Build SOL transfer -- pass lamports directly
|
|
242
|
+
const tx = await buildSolTransfer(connection, fromPubkey, toPubkey, Number(valueWei));
|
|
243
|
+
|
|
244
|
+
txHash = await sendSolanaTransaction(connection, tx, signerKeypair);
|
|
245
|
+
} catch (err) {
|
|
246
|
+
if (needsLimit) releaseSpend(auth.tokenHash, 'send', valueSol, currency);
|
|
247
|
+
throw err;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Log
|
|
251
|
+
const description = userDescription || `Sent ${valueSol} ${nativeCurrency} to ${to}`;
|
|
252
|
+
await recordTransaction({
|
|
253
|
+
walletAddress: from,
|
|
254
|
+
txHash,
|
|
255
|
+
type: 'send',
|
|
256
|
+
amount: valueSol.toString(),
|
|
257
|
+
from,
|
|
258
|
+
to: to!,
|
|
259
|
+
description,
|
|
260
|
+
chain: targetChain,
|
|
261
|
+
logTitle: 'Send Transaction',
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
if (!data) {
|
|
265
|
+
const agentId = !isAdmin(auth) ? auth.token.agentId : undefined;
|
|
266
|
+
logger.send(from, to!, valueSol.toString(), txHash, agentId);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
res.json({
|
|
270
|
+
success: true,
|
|
271
|
+
hash: txHash,
|
|
272
|
+
from,
|
|
273
|
+
to,
|
|
274
|
+
amount: valueSol.toString(),
|
|
275
|
+
chain: targetChain
|
|
276
|
+
});
|
|
277
|
+
} catch (error) {
|
|
278
|
+
if (error instanceof HttpError) { res.status(error.status).json({ error: error.message }); return; }
|
|
279
|
+
res.status(400).json({ error: getErrorMessage(error) });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { Router, Request, Response } from 'express';
|
|
2
|
+
import { ethers } from 'ethers';
|
|
3
|
+
import { resolveChain, getRpcUrl } from '../lib/config';
|
|
4
|
+
import { requireWalletAuth } from '../middleware/auth';
|
|
5
|
+
import { isSolanaChain } from '../lib/address';
|
|
6
|
+
import { resolveName, looksLikeName } from '../lib/resolve';
|
|
7
|
+
import { getErrorMessage, HttpError } from '../lib/error';
|
|
8
|
+
import { handleSolanaSend } from './send-solana';
|
|
9
|
+
import { handleEvmSend } from './send-evm';
|
|
10
|
+
|
|
11
|
+
const router = Router();
|
|
12
|
+
|
|
13
|
+
// POST /send - Generic transaction endpoint
|
|
14
|
+
// Supports simple ETH sends and complex contract calls
|
|
15
|
+
//
|
|
16
|
+
// Simple send: { from, to, amount: "100000000000000000", chain } // amount in wei or lamports
|
|
17
|
+
// Contract call: { from, to, value, data, gasLimit, ... }
|
|
18
|
+
//
|
|
19
|
+
// Requires Bearer token authentication
|
|
20
|
+
//
|
|
21
|
+
// Spending limit enforced for agent tokens (send limit).
|
|
22
|
+
// Sends to the cold wallet address bypass the limit (returning funds to vault).
|
|
23
|
+
// Admin tokens bypass all limits.
|
|
24
|
+
router.post('/', requireWalletAuth, async (req: Request, res: Response) => {
|
|
25
|
+
try {
|
|
26
|
+
const {
|
|
27
|
+
from,
|
|
28
|
+
to: rawTo,
|
|
29
|
+
amount, // Amount in wei (EVM) or lamports (Solana)
|
|
30
|
+
value, // Advanced mode: wei (alias for amount)
|
|
31
|
+
data,
|
|
32
|
+
tokenAddress, // Optional: ERC-20/SPL token contract address
|
|
33
|
+
transaction: rawTransaction, // Solana: base64-encoded VersionedTransaction
|
|
34
|
+
} = req.body;
|
|
35
|
+
|
|
36
|
+
const auth = req.auth!;
|
|
37
|
+
|
|
38
|
+
if (!from || typeof from !== 'string') {
|
|
39
|
+
res.status(400).json({ error: 'from address is required' });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Token sends require 'to'
|
|
44
|
+
if (tokenAddress && !rawTo) {
|
|
45
|
+
res.status(400).json({ error: 'to address is required for token sends' });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Resolve ENS names (e.g. "vitalik.eth" -> "0x...")
|
|
50
|
+
let to = rawTo;
|
|
51
|
+
if (to && typeof to === 'string' && looksLikeName(to)) {
|
|
52
|
+
try {
|
|
53
|
+
const resolved = await resolveName(to);
|
|
54
|
+
to = resolved.address;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
const msg = getErrorMessage(err);
|
|
57
|
+
res.status(400).json({ error: msg });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 'to' is optional for contract deployment
|
|
63
|
+
if (to && typeof to !== 'string') {
|
|
64
|
+
res.status(400).json({ error: 'to must be a valid address' });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// For simple sends, 'to' is required (raw Solana transactions don't need 'to')
|
|
69
|
+
if (!data && !rawTransaction && !to) {
|
|
70
|
+
res.status(400).json({ error: 'to address is required for simple sends' });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Parse value - amount must be in wei (EVM) or lamports (Solana).
|
|
75
|
+
// Callers must convert before calling.
|
|
76
|
+
let valueWei = BigInt(0);
|
|
77
|
+
let valueEth = 0;
|
|
78
|
+
const rawValue = amount || value;
|
|
79
|
+
|
|
80
|
+
if (rawValue) {
|
|
81
|
+
if (typeof rawValue === 'string') {
|
|
82
|
+
valueWei = BigInt(rawValue);
|
|
83
|
+
} else if (typeof rawValue === 'number') {
|
|
84
|
+
valueWei = BigInt(rawValue);
|
|
85
|
+
}
|
|
86
|
+
valueEth = parseFloat(ethers.formatEther(valueWei)); // for limits only
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// For simple native sends without data, require some value (raw Solana transactions carry value in instructions)
|
|
90
|
+
// Token sends use amount as token units, not native currency, so skip this check
|
|
91
|
+
if (!data && !rawTransaction && !tokenAddress && valueEth <= 0) {
|
|
92
|
+
res.status(400).json({ error: 'amount is required for simple sends' });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// For token sends, require amount
|
|
97
|
+
if (tokenAddress && (!rawValue || BigInt(rawValue) <= 0n)) {
|
|
98
|
+
res.status(400).json({ error: 'amount is required for token sends' });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Validate data if provided
|
|
103
|
+
if (data && typeof data === 'string' && !data.startsWith('0x')) {
|
|
104
|
+
res.status(400).json({ error: 'data must be hex-encoded (start with 0x)' });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const { targetChain, chainConfig } = resolveChain(req.body.chain);
|
|
109
|
+
|
|
110
|
+
// Mutate req.body.to so handlers see the resolved address
|
|
111
|
+
req.body.to = to;
|
|
112
|
+
|
|
113
|
+
// Dispatch to chain-specific handler
|
|
114
|
+
if (isSolanaChain(targetChain)) {
|
|
115
|
+
return handleSolanaSend(req, res, auth, targetChain, chainConfig);
|
|
116
|
+
}
|
|
117
|
+
return handleEvmSend(req, res, auth, targetChain, chainConfig);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error instanceof HttpError) { res.status(error.status).json({ error: error.message }); return; }
|
|
120
|
+
res.status(400).json({ error: getErrorMessage(error) });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// POST /send/estimate - Estimate gas for a transaction
|
|
125
|
+
router.post('/estimate', async (req: Request, res: Response) => {
|
|
126
|
+
try {
|
|
127
|
+
const { from, to, amount, value, data, chain } = req.body;
|
|
128
|
+
|
|
129
|
+
if (!from || typeof from !== 'string') {
|
|
130
|
+
res.status(400).json({ error: 'from address is required' });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const { targetChain } = resolveChain(chain);
|
|
135
|
+
|
|
136
|
+
const provider = new ethers.JsonRpcProvider(await getRpcUrl(targetChain));
|
|
137
|
+
|
|
138
|
+
// Build transaction for estimation
|
|
139
|
+
const tx: ethers.TransactionRequest = { from };
|
|
140
|
+
if (to) tx.to = to;
|
|
141
|
+
|
|
142
|
+
const rawValue = amount || value;
|
|
143
|
+
if (rawValue) {
|
|
144
|
+
tx.value = BigInt(rawValue);
|
|
145
|
+
}
|
|
146
|
+
if (data) tx.data = data;
|
|
147
|
+
|
|
148
|
+
// Estimate gas
|
|
149
|
+
const gasEstimate = await provider.estimateGas(tx);
|
|
150
|
+
const feeData = await provider.getFeeData();
|
|
151
|
+
|
|
152
|
+
const response: Record<string, unknown> = {
|
|
153
|
+
success: true,
|
|
154
|
+
gasLimit: gasEstimate.toString(),
|
|
155
|
+
gasPrice: feeData.gasPrice?.toString() || null,
|
|
156
|
+
maxFeePerGas: feeData.maxFeePerGas?.toString() || null,
|
|
157
|
+
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas?.toString() || null
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Calculate estimated cost
|
|
161
|
+
if (feeData.maxFeePerGas) {
|
|
162
|
+
const estimatedCost = gasEstimate * feeData.maxFeePerGas;
|
|
163
|
+
response.estimatedCostWei = estimatedCost.toString();
|
|
164
|
+
response.estimatedCostEth = ethers.formatEther(estimatedCost);
|
|
165
|
+
} else if (feeData.gasPrice) {
|
|
166
|
+
const estimatedCost = gasEstimate * feeData.gasPrice;
|
|
167
|
+
response.estimatedCostWei = estimatedCost.toString();
|
|
168
|
+
response.estimatedCostEth = ethers.formatEther(estimatedCost);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
res.json(response);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error instanceof HttpError) { res.status(error.status).json({ error: error.message }); return; }
|
|
174
|
+
res.status(400).json({ error: getErrorMessage(error) });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
export default router;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { Router, Request, Response } from 'express';
|
|
2
|
+
import {
|
|
3
|
+
createColdWallet,
|
|
4
|
+
hasColdWallet,
|
|
5
|
+
isUnlocked,
|
|
6
|
+
getColdWalletAddress,
|
|
7
|
+
createVault,
|
|
8
|
+
importVault,
|
|
9
|
+
listVaults,
|
|
10
|
+
rotatePrimaryVaultPassword,
|
|
11
|
+
} from '../lib/cold';
|
|
12
|
+
import { createAdminToken } from '../lib/auth';
|
|
13
|
+
import { parseEncryptedPassword } from '../lib/transport';
|
|
14
|
+
import { prisma } from '../lib/db';
|
|
15
|
+
import { loadConfig } from '../lib/config';
|
|
16
|
+
import { logger } from '../lib/logger';
|
|
17
|
+
import { requireWalletAuth } from '../middleware/auth';
|
|
18
|
+
import { requireAdmin } from '../lib/permissions';
|
|
19
|
+
import { isValidAgentPubkey, normalizeAgentPubkey } from '../lib/credential-transport';
|
|
20
|
+
import { getErrorMessage, HttpError } from '../lib/error';
|
|
21
|
+
|
|
22
|
+
const router = Router();
|
|
23
|
+
|
|
24
|
+
// POST /setup - Create cold wallet with encrypted password
|
|
25
|
+
router.post('/', async (req: Request, res: Response) => {
|
|
26
|
+
try {
|
|
27
|
+
const pubkey = typeof req.body?.pubkey === 'string' ? req.body.pubkey : '';
|
|
28
|
+
if (!pubkey.trim()) {
|
|
29
|
+
res.status(400).json({ error: 'pubkey is required' });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!isValidAgentPubkey(pubkey)) {
|
|
33
|
+
res.status(400).json({ error: 'pubkey must be a valid RSA public key (PEM or base64)' });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const normalizedPubkey = normalizeAgentPubkey(pubkey);
|
|
37
|
+
const password = parseEncryptedPassword(req.body.encrypted);
|
|
38
|
+
|
|
39
|
+
const result = createColdWallet(password);
|
|
40
|
+
|
|
41
|
+
// Log the setup event
|
|
42
|
+
logger.setup(result.address);
|
|
43
|
+
|
|
44
|
+
// Create admin token (vault is auto-unlocked after creation)
|
|
45
|
+
const token = await createAdminToken(normalizedPubkey);
|
|
46
|
+
|
|
47
|
+
res.json({
|
|
48
|
+
success: true,
|
|
49
|
+
address: result.address,
|
|
50
|
+
mnemonic: result.mnemonic,
|
|
51
|
+
token,
|
|
52
|
+
message: 'Cold wallet created. SAVE YOUR MNEMONIC SECURELY. It will not be shown again.'
|
|
53
|
+
});
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error instanceof HttpError) { res.status(error.status).json({ error: error.message }); return; }
|
|
56
|
+
res.status(400).json({ error: getErrorMessage(error) });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// GET /setup - Check setup status (includes adapter/key status for agents)
|
|
61
|
+
router.get('/', async (_req: Request, res: Response) => {
|
|
62
|
+
try {
|
|
63
|
+
const hasWallet = hasColdWallet();
|
|
64
|
+
const unlocked = isUnlocked();
|
|
65
|
+
const address = getColdWalletAddress();
|
|
66
|
+
const config = loadConfig();
|
|
67
|
+
|
|
68
|
+
// Check which API keys are configured
|
|
69
|
+
const apiKeys = await prisma.apiKey.findMany({
|
|
70
|
+
where: { isActive: true },
|
|
71
|
+
select: { service: true },
|
|
72
|
+
});
|
|
73
|
+
const apiKeyServices = new Set(apiKeys.map((k) => k.service));
|
|
74
|
+
|
|
75
|
+
// Check adapter config
|
|
76
|
+
let telegramEnabled = false;
|
|
77
|
+
let webhookEnabled = false;
|
|
78
|
+
try {
|
|
79
|
+
const appConfig = await prisma.appConfig.findUnique({ where: { id: 'global' } });
|
|
80
|
+
if (appConfig?.adapterConfig) {
|
|
81
|
+
const parsed = JSON.parse(appConfig.adapterConfig);
|
|
82
|
+
const adapters: Array<{ type: string; enabled: boolean }> = parsed.adapters || [];
|
|
83
|
+
telegramEnabled = adapters.some((a) => a.type === 'telegram' && a.enabled);
|
|
84
|
+
webhookEnabled = adapters.some((a) => a.type === 'webhook' && a.enabled);
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
// Ignore parse errors
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
res.json({
|
|
91
|
+
hasWallet,
|
|
92
|
+
unlocked,
|
|
93
|
+
address,
|
|
94
|
+
adapters: {
|
|
95
|
+
telegram: telegramEnabled,
|
|
96
|
+
webhook: webhookEnabled,
|
|
97
|
+
},
|
|
98
|
+
apiKeys: {
|
|
99
|
+
alchemy: apiKeyServices.has('alchemy'),
|
|
100
|
+
anthropic: apiKeyServices.has('anthropic'),
|
|
101
|
+
},
|
|
102
|
+
defaultChain: config.defaultChain,
|
|
103
|
+
});
|
|
104
|
+
} catch (error) {
|
|
105
|
+
res.status(500).json({ error: getErrorMessage(error) });
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// POST /setup/vault - Create additional vault (requires admin + unlocked primary)
|
|
110
|
+
router.post('/vault', requireWalletAuth, requireAdmin, (req: Request, res: Response) => {
|
|
111
|
+
try {
|
|
112
|
+
const { name } = req.body;
|
|
113
|
+
const password = parseEncryptedPassword(req.body.encrypted);
|
|
114
|
+
|
|
115
|
+
if (!isUnlocked()) {
|
|
116
|
+
res.status(401).json({ error: 'Primary vault must be unlocked to create additional vaults' });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const result = createVault(password, name);
|
|
121
|
+
|
|
122
|
+
logger.setup(result.address);
|
|
123
|
+
|
|
124
|
+
res.json({
|
|
125
|
+
success: true,
|
|
126
|
+
id: result.id,
|
|
127
|
+
address: result.address,
|
|
128
|
+
solanaAddress: result.solanaAddress,
|
|
129
|
+
mnemonic: result.mnemonic,
|
|
130
|
+
name: result.name,
|
|
131
|
+
message: 'Vault created. SAVE YOUR MNEMONIC SECURELY. It will not be shown again.'
|
|
132
|
+
});
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error instanceof HttpError) { res.status(error.status).json({ error: error.message }); return; }
|
|
135
|
+
res.status(400).json({ error: getErrorMessage(error) });
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// POST /setup/vault/import - Import vault from seed (requires admin + unlocked primary)
|
|
140
|
+
router.post('/vault/import', requireWalletAuth, requireAdmin, (req: Request, res: Response) => {
|
|
141
|
+
try {
|
|
142
|
+
const { mnemonic, name } = req.body;
|
|
143
|
+
|
|
144
|
+
if (!mnemonic || typeof mnemonic !== 'string') {
|
|
145
|
+
res.status(400).json({ error: 'Seed phrase (mnemonic) is required' });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const password = parseEncryptedPassword(req.body.encrypted);
|
|
150
|
+
|
|
151
|
+
if (!isUnlocked()) {
|
|
152
|
+
res.status(401).json({ error: 'Primary vault must be unlocked to import additional vaults' });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const result = importVault(mnemonic, password, name);
|
|
157
|
+
|
|
158
|
+
logger.setup(result.address);
|
|
159
|
+
|
|
160
|
+
res.json({
|
|
161
|
+
success: true,
|
|
162
|
+
id: result.id,
|
|
163
|
+
address: result.address,
|
|
164
|
+
solanaAddress: result.solanaAddress,
|
|
165
|
+
name: result.name,
|
|
166
|
+
message: 'Vault imported successfully'
|
|
167
|
+
});
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (error instanceof HttpError) { res.status(error.status).json({ error: error.message }); return; }
|
|
170
|
+
res.status(400).json({ error: getErrorMessage(error) });
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// GET /setup/vaults - List all vaults
|
|
175
|
+
router.get('/vaults', (_req: Request, res: Response) => {
|
|
176
|
+
const vaults = listVaults();
|
|
177
|
+
res.json({ vaults });
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// POST /setup/password - Rotate primary vault password (admin)
|
|
181
|
+
router.post('/password', requireWalletAuth, requireAdmin, (req: Request, res: Response) => {
|
|
182
|
+
try {
|
|
183
|
+
const currentPassword = parseEncryptedPassword(req.body.currentEncrypted);
|
|
184
|
+
const newPassword = parseEncryptedPassword(req.body.newEncrypted);
|
|
185
|
+
|
|
186
|
+
if (newPassword.length < 8) {
|
|
187
|
+
res.status(400).json({ error: 'Password must be at least 8 characters' });
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const changed = rotatePrimaryVaultPassword(currentPassword, newPassword);
|
|
192
|
+
if (!changed) {
|
|
193
|
+
res.status(401).json({ error: 'Invalid current password' });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
res.json({
|
|
198
|
+
success: true,
|
|
199
|
+
message: 'Primary vault password updated',
|
|
200
|
+
});
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (error instanceof HttpError) {
|
|
203
|
+
res.status(error.status).json({ error: error.message });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
res.status(400).json({ error: getErrorMessage(error) });
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
export default router;
|