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,239 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
import { execFileSync } from 'child_process';
|
|
5
|
+
|
|
6
|
+
export type ProjectScopeCode =
|
|
7
|
+
| 'PROJECT_SCOPE_MISSING_AURA'
|
|
8
|
+
| 'PROJECT_SCOPE_INVALID_AURA'
|
|
9
|
+
| 'PROJECT_SCOPE_DENIED'
|
|
10
|
+
| 'PROJECT_SCOPE_OVERRIDE_USED';
|
|
11
|
+
|
|
12
|
+
export interface ScopeCandidate {
|
|
13
|
+
id?: string;
|
|
14
|
+
name: string;
|
|
15
|
+
vaultName: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ScopeDecision {
|
|
19
|
+
allowed: boolean;
|
|
20
|
+
code: ProjectScopeCode | null;
|
|
21
|
+
remediation: string;
|
|
22
|
+
normalizedIdentity: { vaultName: string | null; credentialName: string };
|
|
23
|
+
projectRoot: string | null;
|
|
24
|
+
auraFingerprint: string | null;
|
|
25
|
+
allowedCandidates: ScopeCandidate[];
|
|
26
|
+
overrideUsed?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ParsedRef {
|
|
30
|
+
vaultName: string | null;
|
|
31
|
+
credentialName: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalize(str: string): string {
|
|
35
|
+
return str.trim().toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseReference(ref: string): ParsedRef {
|
|
39
|
+
if (ref.startsWith('@')) {
|
|
40
|
+
const parts = ref.slice(1).split('/');
|
|
41
|
+
if (parts.length < 3 || !parts[0] || !parts[1]) {
|
|
42
|
+
throw new Error(`Invalid vault reference: ${ref}`);
|
|
43
|
+
}
|
|
44
|
+
return { vaultName: parts[0], credentialName: parts[1] };
|
|
45
|
+
}
|
|
46
|
+
const parts = ref.split('/');
|
|
47
|
+
if (parts.length < 2 || !parts[0]) {
|
|
48
|
+
throw new Error(`Invalid reference: ${ref}`);
|
|
49
|
+
}
|
|
50
|
+
return { vaultName: null, credentialName: parts[0] };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseAuraAllowlist(auraPath: string): ParsedRef[] {
|
|
54
|
+
const content = fs.readFileSync(auraPath, 'utf-8');
|
|
55
|
+
const refs: ParsedRef[] = [];
|
|
56
|
+
|
|
57
|
+
for (const rawLine of content.split('\n')) {
|
|
58
|
+
const line = rawLine.trim();
|
|
59
|
+
if (!line || line.startsWith('#')) continue;
|
|
60
|
+
const idx = line.indexOf('=');
|
|
61
|
+
if (idx === -1) throw new Error(`Invalid line: ${line}`);
|
|
62
|
+
const ref = line.slice(idx + 1).trim();
|
|
63
|
+
refs.push(parseReference(ref));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return refs;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function findNearestAura(startDir: string): string | null {
|
|
70
|
+
let current = startDir;
|
|
71
|
+
while (true) {
|
|
72
|
+
const candidate = path.join(current, '.aura');
|
|
73
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
74
|
+
const parent = path.dirname(current);
|
|
75
|
+
if (parent === current) return null;
|
|
76
|
+
current = parent;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolveGitRoot(startDir: string): string | null {
|
|
81
|
+
try {
|
|
82
|
+
const root = execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
83
|
+
cwd: startDir,
|
|
84
|
+
encoding: 'utf-8',
|
|
85
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
86
|
+
}).trim();
|
|
87
|
+
return root || null;
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resolveAuraPath(opts: { cwd: string; projectRootOverride?: string }): { projectRoot: string | null; auraPath: string | null } {
|
|
94
|
+
const explicit = opts.projectRootOverride || process.env.AURA_PROJECT_ROOT;
|
|
95
|
+
if (explicit) {
|
|
96
|
+
const root = path.resolve(explicit);
|
|
97
|
+
return { projectRoot: root, auraPath: path.join(root, '.aura') };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const gitRoot = resolveGitRoot(opts.cwd);
|
|
101
|
+
if (gitRoot) {
|
|
102
|
+
return { projectRoot: gitRoot, auraPath: path.join(gitRoot, '.aura') };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const nearestAura = findNearestAura(opts.cwd);
|
|
106
|
+
if (nearestAura) {
|
|
107
|
+
return { projectRoot: path.dirname(nearestAura), auraPath: nearestAura };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { projectRoot: null, auraPath: null };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function fingerprint(content: string): string {
|
|
114
|
+
return createHash('sha256').update(content).digest('hex').slice(0, 16);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isAllowed(candidate: ScopeCandidate, refs: ParsedRef[]): boolean {
|
|
118
|
+
const candName = normalize(candidate.name);
|
|
119
|
+
const candVault = candidate.vaultName ? normalize(candidate.vaultName) : null;
|
|
120
|
+
return refs.some((ref) => {
|
|
121
|
+
if (normalize(ref.credentialName) !== candName) return false;
|
|
122
|
+
if (!ref.vaultName) return true;
|
|
123
|
+
return candVault === normalize(ref.vaultName);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function evaluateProjectScopeAccess(input: {
|
|
128
|
+
surface: 'cli_vault_get' | 'cli_env' | 'mcp_get_secret';
|
|
129
|
+
requested: { vaultName: string | null; credentialName: string };
|
|
130
|
+
candidates: ScopeCandidate[];
|
|
131
|
+
actor?: string;
|
|
132
|
+
cwd?: string;
|
|
133
|
+
projectRootOverride?: string;
|
|
134
|
+
}): ScopeDecision {
|
|
135
|
+
const normalizedIdentity = {
|
|
136
|
+
vaultName: input.requested.vaultName,
|
|
137
|
+
credentialName: input.requested.credentialName,
|
|
138
|
+
};
|
|
139
|
+
const bypass = process.env.AURA_PROJECT_SCOPE_BYPASS === '1';
|
|
140
|
+
|
|
141
|
+
if (bypass) {
|
|
142
|
+
return {
|
|
143
|
+
allowed: true,
|
|
144
|
+
code: 'PROJECT_SCOPE_OVERRIDE_USED',
|
|
145
|
+
remediation: 'Unset AURA_PROJECT_SCOPE_BYPASS to restore strict project scoping.',
|
|
146
|
+
normalizedIdentity,
|
|
147
|
+
projectRoot: null,
|
|
148
|
+
auraFingerprint: null,
|
|
149
|
+
allowedCandidates: input.candidates,
|
|
150
|
+
overrideUsed: true,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const cwd = input.cwd || process.cwd();
|
|
155
|
+
const { projectRoot, auraPath } = resolveAuraPath({ cwd, projectRootOverride: input.projectRootOverride });
|
|
156
|
+
if (!auraPath || !fs.existsSync(auraPath)) {
|
|
157
|
+
return {
|
|
158
|
+
allowed: false,
|
|
159
|
+
code: 'PROJECT_SCOPE_MISSING_AURA',
|
|
160
|
+
remediation: 'Add a .aura file in the project root (or set AURA_PROJECT_ROOT / --project-root).',
|
|
161
|
+
normalizedIdentity,
|
|
162
|
+
projectRoot,
|
|
163
|
+
auraFingerprint: null,
|
|
164
|
+
allowedCandidates: [],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let refs: ParsedRef[];
|
|
169
|
+
let auraFingerprint: string;
|
|
170
|
+
try {
|
|
171
|
+
const content = fs.readFileSync(auraPath, 'utf-8');
|
|
172
|
+
auraFingerprint = fingerprint(content);
|
|
173
|
+
refs = parseAuraAllowlist(auraPath);
|
|
174
|
+
} catch {
|
|
175
|
+
return {
|
|
176
|
+
allowed: false,
|
|
177
|
+
code: 'PROJECT_SCOPE_INVALID_AURA',
|
|
178
|
+
remediation: 'Fix .aura syntax (ENV=@vault/name/field or ENV=name/field) and retry.',
|
|
179
|
+
normalizedIdentity,
|
|
180
|
+
projectRoot,
|
|
181
|
+
auraFingerprint: null,
|
|
182
|
+
allowedCandidates: [],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const allowedCandidates = input.candidates.filter((candidate) => isAllowed(candidate, refs));
|
|
187
|
+
if (allowedCandidates.length === 0) {
|
|
188
|
+
return {
|
|
189
|
+
allowed: false,
|
|
190
|
+
code: 'PROJECT_SCOPE_DENIED',
|
|
191
|
+
remediation: `Add '${input.requested.credentialName}' to .aura (or use an allowed credential).`,
|
|
192
|
+
normalizedIdentity,
|
|
193
|
+
projectRoot,
|
|
194
|
+
auraFingerprint,
|
|
195
|
+
allowedCandidates: [],
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!input.requested.vaultName && allowedCandidates.length > 1) {
|
|
200
|
+
return {
|
|
201
|
+
allowed: false,
|
|
202
|
+
code: 'PROJECT_SCOPE_DENIED',
|
|
203
|
+
remediation: `Credential '${input.requested.credentialName}' is mapped in multiple vaults. Re-run with explicit --vault.`,
|
|
204
|
+
normalizedIdentity,
|
|
205
|
+
projectRoot,
|
|
206
|
+
auraFingerprint,
|
|
207
|
+
allowedCandidates: [],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
allowed: true,
|
|
213
|
+
code: null,
|
|
214
|
+
remediation: '',
|
|
215
|
+
normalizedIdentity,
|
|
216
|
+
projectRoot,
|
|
217
|
+
auraFingerprint,
|
|
218
|
+
allowedCandidates,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function emitProjectScopeEvent(input: {
|
|
223
|
+
actor?: string;
|
|
224
|
+
surface: 'cli_vault_get' | 'cli_env' | 'mcp_get_secret';
|
|
225
|
+
requestedCredential: { vaultName: string | null; credentialName: string };
|
|
226
|
+
decision: ScopeDecision;
|
|
227
|
+
}): void {
|
|
228
|
+
const event = {
|
|
229
|
+
actor: input.actor || 'unknown',
|
|
230
|
+
surface: input.surface,
|
|
231
|
+
projectRoot: input.decision.projectRoot,
|
|
232
|
+
auraFingerprint: input.decision.auraFingerprint,
|
|
233
|
+
requestedCredential: input.requestedCredential,
|
|
234
|
+
code: input.decision.code,
|
|
235
|
+
timestamp: new Date().toISOString(),
|
|
236
|
+
overrideUsed: Boolean(input.decision.overrideUsed),
|
|
237
|
+
};
|
|
238
|
+
console.warn(`[project-scope] ${JSON.stringify(event)}`);
|
|
239
|
+
}
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracted resolve logic from `POST /actions/:id/resolve`.
|
|
3
|
+
*
|
|
4
|
+
* Called by both the HTTP route handler and the ApprovalRouter (direct in-process).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { prisma } from './db';
|
|
8
|
+
import { events, emitWalletEvent } from './events';
|
|
9
|
+
import { handleAppMessage } from './strategy/engine';
|
|
10
|
+
import { createToken, getTokenHash, escrowToken, type AgentTokenPayload } from './auth';
|
|
11
|
+
import { isValidAgentPubkey, normalizeAgentPubkey } from './credential-transport';
|
|
12
|
+
import { isUnlocked, getColdWalletAddress } from './cold';
|
|
13
|
+
import { normalizeAddress } from './address';
|
|
14
|
+
import { getDefault, getDefaultSync, parseRateLimit } from './defaults';
|
|
15
|
+
import { logger } from './logger';
|
|
16
|
+
import { getErrorMessage } from './error';
|
|
17
|
+
|
|
18
|
+
export interface ResolveActionOptions {
|
|
19
|
+
walletAccess?: string[];
|
|
20
|
+
limits?: Record<string, number>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ResolveActionResult {
|
|
24
|
+
success: boolean;
|
|
25
|
+
statusCode: 200 | 400 | 401 | 404;
|
|
26
|
+
data: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Track auto-execute callback depth per app to prevent infinite loops */
|
|
30
|
+
const callbackCounts = new Map<string, number[]>();
|
|
31
|
+
|
|
32
|
+
function canFireCallback(appId: string): boolean {
|
|
33
|
+
const { max: MAX_CALLBACKS, windowMs: CALLBACK_WINDOW_MS } = parseRateLimit(getDefaultSync('rate.app_callback', '3,120000'));
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
const timestamps = callbackCounts.get(appId) || [];
|
|
36
|
+
const recent = timestamps.filter(t => now - t < CALLBACK_WINDOW_MS);
|
|
37
|
+
if (recent.length >= MAX_CALLBACKS) return false;
|
|
38
|
+
recent.push(now);
|
|
39
|
+
callbackCounts.set(appId, recent);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function resolveAction(
|
|
44
|
+
actionId: string,
|
|
45
|
+
approved: boolean,
|
|
46
|
+
opts?: ResolveActionOptions,
|
|
47
|
+
): Promise<ResolveActionResult> {
|
|
48
|
+
const id = actionId;
|
|
49
|
+
const walletAccess = opts?.walletAccess;
|
|
50
|
+
const overrideLimits = opts?.limits;
|
|
51
|
+
|
|
52
|
+
if (typeof approved !== 'boolean') {
|
|
53
|
+
return { success: false, statusCode: 400, data: { success: false, error: 'approved (boolean) is required' } };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const request = await prisma.humanAction.findUnique({ where: { id } });
|
|
57
|
+
if (!request || request.status !== 'pending') {
|
|
58
|
+
return { success: false, statusCode: 404, data: { success: false, error: 'Action not found or already resolved' } };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (request.type === 'strategy:message') {
|
|
62
|
+
return { success: false, statusCode: 400, data: { success: false, error: 'Internal message jobs are not manually resolvable' } };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Handle rejection for all types
|
|
66
|
+
if (!approved) {
|
|
67
|
+
await prisma.humanAction.update({
|
|
68
|
+
where: { id },
|
|
69
|
+
data: { status: 'rejected', resolvedAt: new Date() },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
events.actionResolved({ id, type: request.type, approved: false, resolvedBy: 'dashboard' });
|
|
73
|
+
logger.actionResolved(id, request.type, false, 'dashboard');
|
|
74
|
+
|
|
75
|
+
// Notify app of rejection via app:emit
|
|
76
|
+
if (request.type === 'action') {
|
|
77
|
+
let meta: { agentId?: string } = {};
|
|
78
|
+
try { meta = JSON.parse(request.metadata || '{}'); } catch {}
|
|
79
|
+
emitWalletEvent('app:emit', {
|
|
80
|
+
strategyId: (meta.agentId || '').replace(/^app:/, ''),
|
|
81
|
+
channel: 'action:resolved',
|
|
82
|
+
data: { requestId: id, approved: false },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { success: true, statusCode: 200, data: { success: true, approved: false } };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// === APPROVAL ===
|
|
90
|
+
|
|
91
|
+
// Strategy approvals
|
|
92
|
+
if (request.type === 'strategy:approve') {
|
|
93
|
+
await prisma.humanAction.update({
|
|
94
|
+
where: { id },
|
|
95
|
+
data: { status: 'approved', resolvedAt: new Date() },
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
events.actionResolved({ id, type: request.type, approved: true, resolvedBy: 'dashboard' });
|
|
99
|
+
return { success: true, statusCode: 200, data: { success: true, approved: true } };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Auth / agent_access / action approvals — generate token
|
|
103
|
+
if (request.type === 'auth' || request.type === 'agent_access' || request.type === 'action') {
|
|
104
|
+
if (!isUnlocked()) {
|
|
105
|
+
return { success: false, statusCode: 401, data: { success: false, error: 'Wallet is locked. Unlock first.' } };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let metadata: {
|
|
109
|
+
agentId?: string;
|
|
110
|
+
limit?: number;
|
|
111
|
+
permissions?: string[];
|
|
112
|
+
ttl?: number;
|
|
113
|
+
secretHash?: string;
|
|
114
|
+
limits?: { fund?: number; send?: number; swap?: number };
|
|
115
|
+
walletAccess?: string[];
|
|
116
|
+
credentialAccess?: AgentTokenPayload['credentialAccess'];
|
|
117
|
+
pubkey?: string;
|
|
118
|
+
strategyId?: string;
|
|
119
|
+
summary?: string;
|
|
120
|
+
action?: { endpoint?: string; method?: string; body?: Record<string, unknown> };
|
|
121
|
+
} = {};
|
|
122
|
+
if (request.metadata) {
|
|
123
|
+
try { metadata = JSON.parse(request.metadata); } catch { /* ignore */ }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const agentId = metadata.agentId || `agent-${id.slice(0, 8)}`;
|
|
127
|
+
const defaultFundLimit = await getDefault<number>('limits.fund', 0.1);
|
|
128
|
+
const defaultSendLimit = await getDefault<number>('limits.send', 0.1);
|
|
129
|
+
const defaultSwapLimit = await getDefault<number>('limits.swap', 0.1);
|
|
130
|
+
const defaultPermissions = await getDefault<string[]>('permissions.default', ['wallet:create:hot', 'send:hot', 'swap', 'fund', 'action:create']);
|
|
131
|
+
const defaultTtl = await getDefault<number>('ttl.agent', 3600);
|
|
132
|
+
const limit = overrideLimits?.fund ?? metadata.limit ?? defaultFundLimit;
|
|
133
|
+
const permissions = metadata.permissions || defaultPermissions;
|
|
134
|
+
const ttl = metadata.ttl || defaultTtl;
|
|
135
|
+
let normalizedPubkey = metadata.pubkey;
|
|
136
|
+
if (normalizedPubkey) {
|
|
137
|
+
if (!isValidAgentPubkey(normalizedPubkey)) {
|
|
138
|
+
return { success: false, statusCode: 400, data: { success: false, error: 'Stored pubkey is invalid for token issuance' } };
|
|
139
|
+
}
|
|
140
|
+
normalizedPubkey = normalizeAgentPubkey(normalizedPubkey);
|
|
141
|
+
}
|
|
142
|
+
if (!normalizedPubkey) {
|
|
143
|
+
return { success: false, statusCode: 400, data: { success: false, error: 'pubkey is required when approving token issuance' } };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const finalWalletAccess = walletAccess
|
|
147
|
+
? walletAccess.map((addr: string) => normalizeAddress(addr))
|
|
148
|
+
: metadata.walletAccess;
|
|
149
|
+
|
|
150
|
+
// Build limits: per-token overrides > request metadata > system defaults
|
|
151
|
+
const baseLimits = { fund: limit, send: defaultSendLimit, swap: defaultSwapLimit };
|
|
152
|
+
const finalLimits = overrideLimits
|
|
153
|
+
? { ...baseLimits, ...overrideLimits }
|
|
154
|
+
: metadata.limits
|
|
155
|
+
? { ...baseLimits, ...metadata.limits }
|
|
156
|
+
: baseLimits;
|
|
157
|
+
|
|
158
|
+
const token = await createToken(agentId, limit, permissions, ttl, {
|
|
159
|
+
limits: finalLimits,
|
|
160
|
+
walletAccess: finalWalletAccess,
|
|
161
|
+
credentialAccess: metadata.credentialAccess,
|
|
162
|
+
agentPubkey: normalizedPubkey,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Escrow the raw token in memory — never store it in the DB
|
|
166
|
+
escrowToken(id, token);
|
|
167
|
+
|
|
168
|
+
// Update request status with tokenHash (not raw token) for audit/display
|
|
169
|
+
await prisma.humanAction.update({
|
|
170
|
+
where: { id },
|
|
171
|
+
data: {
|
|
172
|
+
status: 'approved',
|
|
173
|
+
resolvedAt: new Date(),
|
|
174
|
+
metadata: JSON.stringify({
|
|
175
|
+
...metadata,
|
|
176
|
+
tokenHash: getTokenHash(token),
|
|
177
|
+
limits: finalLimits,
|
|
178
|
+
walletAccess: finalWalletAccess,
|
|
179
|
+
pubkey: normalizedPubkey,
|
|
180
|
+
credentialAccess: metadata.credentialAccess,
|
|
181
|
+
}),
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Log the approval
|
|
186
|
+
await prisma.log.create({
|
|
187
|
+
data: {
|
|
188
|
+
walletAddress: getColdWalletAddress() || 'system',
|
|
189
|
+
title: 'Agent Access Approved',
|
|
190
|
+
description: `Generated token for ${agentId} with ${limit} ETH limit`,
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
events.tokenCreated({
|
|
195
|
+
tokenHash: getTokenHash(token),
|
|
196
|
+
agentId,
|
|
197
|
+
limit,
|
|
198
|
+
permissions,
|
|
199
|
+
expiresAt: Date.now() + ttl * 1000,
|
|
200
|
+
});
|
|
201
|
+
events.actionResolved({ id, type: request.type, approved: true, resolvedBy: 'dashboard' });
|
|
202
|
+
logger.actionResolved(id, request.type, true, 'dashboard');
|
|
203
|
+
|
|
204
|
+
// Notify app of approval via app:emit (always, even with auto-execute)
|
|
205
|
+
if (request.type === 'action') {
|
|
206
|
+
emitWalletEvent('app:emit', {
|
|
207
|
+
strategyId: (metadata.agentId || '').replace(/^app:/, ''),
|
|
208
|
+
channel: 'action:resolved',
|
|
209
|
+
data: { requestId: id, approved: true },
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Auto-execute pre-computed action if present in metadata
|
|
214
|
+
const hasAutoExecute = request.type === 'action' && metadata.action;
|
|
215
|
+
if (hasAutoExecute) {
|
|
216
|
+
const action = metadata.action as { endpoint?: string; method?: string; body?: Record<string, unknown> };
|
|
217
|
+
if (action.endpoint && action.method) {
|
|
218
|
+
try {
|
|
219
|
+
const actionUrl = `http://127.0.0.1:4242${action.endpoint}`;
|
|
220
|
+
const actionRes = await fetch(actionUrl, {
|
|
221
|
+
method: action.method,
|
|
222
|
+
headers: {
|
|
223
|
+
'Content-Type': 'application/json',
|
|
224
|
+
'Authorization': `Bearer ${token}`,
|
|
225
|
+
},
|
|
226
|
+
body: action.method === 'POST' && action.body
|
|
227
|
+
? JSON.stringify(action.body)
|
|
228
|
+
: undefined,
|
|
229
|
+
});
|
|
230
|
+
const actionResult = await actionRes.text();
|
|
231
|
+
let parsedResult: unknown;
|
|
232
|
+
try { parsedResult = JSON.parse(actionResult); } catch { parsedResult = actionResult; }
|
|
233
|
+
|
|
234
|
+
emitWalletEvent('app:emit', {
|
|
235
|
+
strategyId: (metadata.agentId || '').replace(/^app:/, ''),
|
|
236
|
+
channel: 'action:executed',
|
|
237
|
+
data: {
|
|
238
|
+
requestId: id,
|
|
239
|
+
approved: true,
|
|
240
|
+
action: { endpoint: action.endpoint, method: action.method },
|
|
241
|
+
status: actionRes.ok ? 'success' : 'error',
|
|
242
|
+
statusCode: actionRes.status,
|
|
243
|
+
result: parsedResult,
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
logger.actionResolved(id, 'action:auto-execute', actionRes.ok, 'system');
|
|
248
|
+
|
|
249
|
+
// Feed result back to app AI for a contextual follow-up message
|
|
250
|
+
const appId = (metadata.agentId || '').replace(/^app:/, '');
|
|
251
|
+
if (appId) {
|
|
252
|
+
const summary = metadata.summary || 'action';
|
|
253
|
+
const systemMsg = actionRes.ok
|
|
254
|
+
? `[SYSTEM] Action "${summary}" approved and executed successfully.\nResult: ${JSON.stringify(parsedResult).slice(0, 500)}\n\nIf there are more steps needed to complete the user's original request, use your tools NOW (wallet_api or request_human_action) to continue. Do not just describe what you will do — do it.`
|
|
255
|
+
: `[SYSTEM] Action "${summary}" approved but failed (${actionRes.status}).\nError: ${JSON.stringify(parsedResult).slice(0, 500)}\n\nInvestigate the error using wallet_api and retry with request_human_action if you can fix the issue. Do NOT just explain the error — try to fix it.`;
|
|
256
|
+
|
|
257
|
+
if (canFireCallback(appId)) {
|
|
258
|
+
handleAppMessage(appId, systemMsg).then(({ reply }) => {
|
|
259
|
+
if (reply) {
|
|
260
|
+
emitWalletEvent('app:emit', {
|
|
261
|
+
strategyId: appId,
|
|
262
|
+
channel: 'agent:message',
|
|
263
|
+
data: { message: reply },
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}).catch((err) => { logger.actionResolved(id, 'action:callback-error', false, getErrorMessage(err)); });
|
|
267
|
+
} else {
|
|
268
|
+
logger.actionResolved(id, 'action:callback-limit', true, `app:${appId}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
} catch (err) {
|
|
272
|
+
const errMsg = getErrorMessage(err);
|
|
273
|
+
emitWalletEvent('app:emit', {
|
|
274
|
+
strategyId: (metadata.agentId || '').replace(/^app:/, ''),
|
|
275
|
+
channel: 'action:executed',
|
|
276
|
+
data: {
|
|
277
|
+
requestId: id,
|
|
278
|
+
approved: true,
|
|
279
|
+
action: { endpoint: action.endpoint, method: action.method },
|
|
280
|
+
status: 'error',
|
|
281
|
+
error: errMsg,
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Feed error back to app AI
|
|
286
|
+
const appId = (metadata.agentId || '').replace(/^app:/, '');
|
|
287
|
+
if (appId) {
|
|
288
|
+
const summary = metadata.summary || 'action';
|
|
289
|
+
const systemMsg = `[SYSTEM] Action "${summary}" approved but execution failed.\nError: ${errMsg}\n\nInvestigate the error using wallet_api and retry with request_human_action if you can fix the issue. Do NOT just explain the error — try to fix it.`;
|
|
290
|
+
|
|
291
|
+
if (canFireCallback(appId)) {
|
|
292
|
+
handleAppMessage(appId, systemMsg).then(({ reply }) => {
|
|
293
|
+
if (reply) {
|
|
294
|
+
emitWalletEvent('app:emit', {
|
|
295
|
+
strategyId: appId,
|
|
296
|
+
channel: 'agent:message',
|
|
297
|
+
data: { message: reply },
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}).catch((err) => { logger.actionResolved(id, 'action:callback-error', false, getErrorMessage(err)); });
|
|
301
|
+
} else {
|
|
302
|
+
logger.actionResolved(id, 'action:callback-limit', false, `app:${appId}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
success: true,
|
|
311
|
+
statusCode: 200,
|
|
312
|
+
data: {
|
|
313
|
+
success: true,
|
|
314
|
+
token,
|
|
315
|
+
agentId,
|
|
316
|
+
limit,
|
|
317
|
+
limits: finalLimits,
|
|
318
|
+
permissions,
|
|
319
|
+
walletAccess: finalWalletAccess,
|
|
320
|
+
expiresIn: ttl,
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Permission update approvals — generate token with updated permissions
|
|
326
|
+
if (request.type === 'permission_update') {
|
|
327
|
+
if (!isUnlocked()) {
|
|
328
|
+
return { success: false, statusCode: 401, data: { success: false, error: 'Wallet is locked. Unlock first.' } };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let metadata: {
|
|
332
|
+
agentId?: string;
|
|
333
|
+
tokenHash?: string;
|
|
334
|
+
requestedPermissions?: string[];
|
|
335
|
+
requestedWalletAccess?: string[];
|
|
336
|
+
requestedLimits?: { fund?: number; send?: number; swap?: number };
|
|
337
|
+
requestedPubkey?: string;
|
|
338
|
+
secretHash?: string;
|
|
339
|
+
} = {};
|
|
340
|
+
if (request.metadata) {
|
|
341
|
+
try { metadata = JSON.parse(request.metadata); } catch { /* ignore */ }
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const agentId = metadata.agentId || `agent-${id.slice(0, 8)}`;
|
|
345
|
+
const newPermissions = overrideLimits?.permissions ?? metadata.requestedPermissions ?? [];
|
|
346
|
+
const newWalletAccess = walletAccess
|
|
347
|
+
? walletAccess.map((addr: string) => normalizeAddress(addr))
|
|
348
|
+
: metadata.requestedWalletAccess;
|
|
349
|
+
const newLimits = overrideLimits || metadata.requestedLimits;
|
|
350
|
+
let normalizedPubkey = metadata.requestedPubkey;
|
|
351
|
+
if (normalizedPubkey) {
|
|
352
|
+
if (!isValidAgentPubkey(normalizedPubkey)) {
|
|
353
|
+
return { success: false, statusCode: 400, data: { success: false, error: 'requestedPubkey is invalid' } };
|
|
354
|
+
}
|
|
355
|
+
normalizedPubkey = normalizeAgentPubkey(normalizedPubkey);
|
|
356
|
+
}
|
|
357
|
+
if (!normalizedPubkey) {
|
|
358
|
+
return { success: false, statusCode: 400, data: { success: false, error: 'requestedPubkey is required for token issuance' } };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const ttl = await getDefault<number>('ttl.agent', 3600);
|
|
362
|
+
const token = await createToken(agentId, newLimits?.fund ?? 0, newPermissions, ttl, {
|
|
363
|
+
limits: newLimits,
|
|
364
|
+
walletAccess: newWalletAccess,
|
|
365
|
+
agentPubkey: normalizedPubkey,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Escrow the raw token in memory — never store it in the DB
|
|
369
|
+
escrowToken(id, token);
|
|
370
|
+
|
|
371
|
+
await prisma.humanAction.update({
|
|
372
|
+
where: { id },
|
|
373
|
+
data: {
|
|
374
|
+
status: 'approved',
|
|
375
|
+
resolvedAt: new Date(),
|
|
376
|
+
metadata: JSON.stringify({
|
|
377
|
+
...metadata,
|
|
378
|
+
tokenHash: getTokenHash(token),
|
|
379
|
+
approvedPermissions: newPermissions,
|
|
380
|
+
approvedWalletAccess: newWalletAccess,
|
|
381
|
+
approvedLimits: newLimits,
|
|
382
|
+
requestedPubkey: normalizedPubkey,
|
|
383
|
+
}),
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
await prisma.log.create({
|
|
388
|
+
data: {
|
|
389
|
+
walletAddress: getColdWalletAddress() || 'system',
|
|
390
|
+
title: 'Permission Update Approved',
|
|
391
|
+
description: `Updated permissions for ${agentId}`,
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
events.tokenCreated({
|
|
396
|
+
tokenHash: getTokenHash(token),
|
|
397
|
+
agentId,
|
|
398
|
+
limit: newLimits?.fund ?? 0,
|
|
399
|
+
permissions: newPermissions,
|
|
400
|
+
expiresAt: Date.now() + ttl * 1000,
|
|
401
|
+
});
|
|
402
|
+
events.actionResolved({ id, type: request.type, approved: true, resolvedBy: 'dashboard' });
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
success: true,
|
|
406
|
+
statusCode: 200,
|
|
407
|
+
data: {
|
|
408
|
+
success: true,
|
|
409
|
+
token,
|
|
410
|
+
agentId,
|
|
411
|
+
permissions: newPermissions,
|
|
412
|
+
walletAccess: newWalletAccess,
|
|
413
|
+
limits: newLimits,
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// For other types (fund_transfer, etc.), update DB and emit event
|
|
419
|
+
await prisma.humanAction.update({
|
|
420
|
+
where: { id },
|
|
421
|
+
data: { status: approved ? 'approved' : 'rejected', resolvedAt: new Date() },
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
events.actionResolved({ id, type: request.type, approved, resolvedBy: 'dashboard' });
|
|
425
|
+
|
|
426
|
+
return { success: true, statusCode: 200, data: { success: true, approved } };
|
|
427
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ethers } from 'ethers';
|
|
2
|
+
import { getRpcUrl } from './config';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve an ENS name (.eth) to an Ethereum address.
|
|
6
|
+
* Uses ethers built-in provider.resolveName() which handles ENS natively.
|
|
7
|
+
* ENS resolution always uses Ethereum mainnet (ENS is deployed on L1).
|
|
8
|
+
*/
|
|
9
|
+
export async function resolveName(name: string): Promise<{ address: string; name: string }> {
|
|
10
|
+
if (!name || typeof name !== 'string') {
|
|
11
|
+
throw new Error('Name is required');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Only support .eth names for now (.sol is out of scope)
|
|
15
|
+
if (!name.endsWith('.eth')) {
|
|
16
|
+
throw new Error(`Unsupported name format: ${name}. Only .eth names are supported.`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ENS lives on Ethereum mainnet — always resolve against L1
|
|
20
|
+
const rpcUrl = await getRpcUrl('ethereum');
|
|
21
|
+
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
22
|
+
|
|
23
|
+
const address = await provider.resolveName(name);
|
|
24
|
+
if (!address) {
|
|
25
|
+
throw new Error(`Could not resolve: ${name}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { address, name };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if a string looks like an ENS name (contains a dot).
|
|
33
|
+
*/
|
|
34
|
+
export function looksLikeName(value: string): boolean {
|
|
35
|
+
return value.includes('.') && !value.startsWith('0x') && !value.match(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/);
|
|
36
|
+
}
|