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,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch RPC helper for fetching token balances and prices
|
|
3
|
+
* Minimizes RPC calls by batching all requests together
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ethers } from 'ethers';
|
|
7
|
+
import { Connection, PublicKey } from '@solana/web3.js';
|
|
8
|
+
|
|
9
|
+
export interface TokenData {
|
|
10
|
+
balance: string; // Formatted balance with decimals
|
|
11
|
+
balanceRaw: string; // Raw balance in smallest unit
|
|
12
|
+
priceInEth: number | null; // Price in ETH (null if no pool)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AssetInfo {
|
|
16
|
+
tokenAddress: string;
|
|
17
|
+
decimals: number;
|
|
18
|
+
poolAddress?: string | null;
|
|
19
|
+
poolVersion?: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ABI fragments for encoding/decoding
|
|
23
|
+
const BALANCE_OF_SELECTOR = '0x70a08231'; // balanceOf(address)
|
|
24
|
+
const GET_RESERVES_SELECTOR = '0x0902f1ac'; // getReserves() for V2
|
|
25
|
+
const SLOT0_SELECTOR = '0x3850c7bd'; // slot0() for V3
|
|
26
|
+
const TOKEN0_SELECTOR = '0x0dfe1681'; // token0() for determining price direction
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Encode balanceOf(address) call
|
|
30
|
+
*/
|
|
31
|
+
function encodeBalanceOf(walletAddress: string): string {
|
|
32
|
+
const addressParam = ethers.zeroPadValue(walletAddress, 32).slice(2);
|
|
33
|
+
return BALANCE_OF_SELECTOR + addressParam;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Encode getReserves() call for V2 pools
|
|
38
|
+
*/
|
|
39
|
+
function encodeGetReserves(): string {
|
|
40
|
+
return GET_RESERVES_SELECTOR;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Encode slot0() call for V3 pools
|
|
45
|
+
*/
|
|
46
|
+
function encodeSlot0(): string {
|
|
47
|
+
return SLOT0_SELECTOR;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Encode token0() call
|
|
52
|
+
*/
|
|
53
|
+
function encodeToken0(): string {
|
|
54
|
+
return TOKEN0_SELECTOR;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Execute batch JSON-RPC calls
|
|
59
|
+
*/
|
|
60
|
+
async function batchRpcCall(
|
|
61
|
+
rpcUrl: string,
|
|
62
|
+
calls: { to: string; data: string }[]
|
|
63
|
+
): Promise<(string | null)[]> {
|
|
64
|
+
if (calls.length === 0) return [];
|
|
65
|
+
|
|
66
|
+
const batch = calls.map((call, i) => ({
|
|
67
|
+
jsonrpc: '2.0',
|
|
68
|
+
id: i,
|
|
69
|
+
method: 'eth_call',
|
|
70
|
+
params: [{ to: call.to, data: call.data }, 'latest']
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const res = await fetch(rpcUrl, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: { 'Content-Type': 'application/json' },
|
|
77
|
+
body: JSON.stringify(batch)
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const results = await res.json();
|
|
81
|
+
|
|
82
|
+
// Sort by id and extract results, handling errors
|
|
83
|
+
return results
|
|
84
|
+
.sort((a: { id: number }, b: { id: number }) => a.id - b.id)
|
|
85
|
+
.map((r: { result?: string; error?: unknown }) => r.result || null);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error('[tokenData] Batch RPC failed:', err);
|
|
88
|
+
return calls.map(() => null);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Parse V2 reserves to get price
|
|
94
|
+
* Returns price of token in ETH
|
|
95
|
+
*/
|
|
96
|
+
function parseV2Price(
|
|
97
|
+
reservesData: string | null,
|
|
98
|
+
tokenIsToken0: boolean
|
|
99
|
+
): number | null {
|
|
100
|
+
if (!reservesData || reservesData === '0x') return null;
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
// getReserves() returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)
|
|
104
|
+
const reserve0 = BigInt('0x' + reservesData.slice(2, 66));
|
|
105
|
+
const reserve1 = BigInt('0x' + reservesData.slice(66, 130));
|
|
106
|
+
|
|
107
|
+
if (reserve0 === BigInt(0) || reserve1 === BigInt(0)) return null;
|
|
108
|
+
|
|
109
|
+
// Calculate price based on which token is token0
|
|
110
|
+
// If our token is token0, price = reserve1/reserve0 (ETH per token)
|
|
111
|
+
// If our token is token1, price = reserve0/reserve1
|
|
112
|
+
if (tokenIsToken0) {
|
|
113
|
+
return Number(reserve1) / Number(reserve0);
|
|
114
|
+
} else {
|
|
115
|
+
return Number(reserve0) / Number(reserve1);
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Parse V3 slot0 to get price
|
|
124
|
+
* Returns price of token in ETH
|
|
125
|
+
*/
|
|
126
|
+
function parseV3Price(
|
|
127
|
+
slot0Data: string | null,
|
|
128
|
+
tokenIsToken0: boolean
|
|
129
|
+
): number | null {
|
|
130
|
+
if (!slot0Data || slot0Data === '0x') return null;
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
// slot0() returns (sqrtPriceX96, tick, observationIndex, ...)
|
|
134
|
+
// sqrtPriceX96 is the first 32 bytes
|
|
135
|
+
const sqrtPriceX96 = BigInt('0x' + slot0Data.slice(2, 66));
|
|
136
|
+
|
|
137
|
+
if (sqrtPriceX96 === BigInt(0)) return null;
|
|
138
|
+
|
|
139
|
+
// Convert sqrtPriceX96 to price
|
|
140
|
+
// price = (sqrtPriceX96 / 2^96)^2
|
|
141
|
+
const Q96 = BigInt(2) ** BigInt(96);
|
|
142
|
+
const priceRatio = Number(sqrtPriceX96) / Number(Q96);
|
|
143
|
+
const price = priceRatio * priceRatio;
|
|
144
|
+
|
|
145
|
+
// If token is token0, this gives us token1/token0 (ETH per token if ETH is token1)
|
|
146
|
+
// If token is token1, we need to invert
|
|
147
|
+
if (tokenIsToken0) {
|
|
148
|
+
return price;
|
|
149
|
+
} else {
|
|
150
|
+
return price > 0 ? 1 / price : null;
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Batch fetch balances AND prices for multiple tokens
|
|
159
|
+
* Returns a Map of tokenAddress (lowercase) to TokenData
|
|
160
|
+
*/
|
|
161
|
+
export async function fetchTokenData(
|
|
162
|
+
walletAddress: string,
|
|
163
|
+
assets: AssetInfo[],
|
|
164
|
+
rpcUrl: string
|
|
165
|
+
): Promise<Map<string, TokenData>> {
|
|
166
|
+
const result = new Map<string, TokenData>();
|
|
167
|
+
|
|
168
|
+
if (assets.length === 0) return result;
|
|
169
|
+
|
|
170
|
+
// Build all RPC calls
|
|
171
|
+
const calls: { to: string; data: string; type: 'balance' | 'reserves' | 'slot0' | 'token0'; assetIndex: number }[] = [];
|
|
172
|
+
|
|
173
|
+
// Add balance calls for all tokens
|
|
174
|
+
for (let i = 0; i < assets.length; i++) {
|
|
175
|
+
calls.push({
|
|
176
|
+
to: assets[i].tokenAddress,
|
|
177
|
+
data: encodeBalanceOf(walletAddress),
|
|
178
|
+
type: 'balance',
|
|
179
|
+
assetIndex: i
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Add price calls for assets with pools
|
|
184
|
+
for (let i = 0; i < assets.length; i++) {
|
|
185
|
+
const asset = assets[i];
|
|
186
|
+
if (!asset.poolAddress) continue;
|
|
187
|
+
|
|
188
|
+
if (asset.poolVersion === 'v2') {
|
|
189
|
+
calls.push({
|
|
190
|
+
to: asset.poolAddress,
|
|
191
|
+
data: encodeGetReserves(),
|
|
192
|
+
type: 'reserves',
|
|
193
|
+
assetIndex: i
|
|
194
|
+
});
|
|
195
|
+
// Also need token0 to determine price direction
|
|
196
|
+
calls.push({
|
|
197
|
+
to: asset.poolAddress,
|
|
198
|
+
data: encodeToken0(),
|
|
199
|
+
type: 'token0',
|
|
200
|
+
assetIndex: i
|
|
201
|
+
});
|
|
202
|
+
} else if (asset.poolVersion === 'v3' || asset.poolVersion === 'v4') {
|
|
203
|
+
calls.push({
|
|
204
|
+
to: asset.poolAddress,
|
|
205
|
+
data: encodeSlot0(),
|
|
206
|
+
type: 'slot0',
|
|
207
|
+
assetIndex: i
|
|
208
|
+
});
|
|
209
|
+
// Also need token0 to determine price direction
|
|
210
|
+
calls.push({
|
|
211
|
+
to: asset.poolAddress,
|
|
212
|
+
data: encodeToken0(),
|
|
213
|
+
type: 'token0',
|
|
214
|
+
assetIndex: i
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Execute batch RPC
|
|
220
|
+
const rpcCalls = calls.map(c => ({ to: c.to, data: c.data }));
|
|
221
|
+
const responses = await batchRpcCall(rpcUrl, rpcCalls);
|
|
222
|
+
|
|
223
|
+
// Parse results
|
|
224
|
+
// First, organize results by asset and type
|
|
225
|
+
const balances: (string | null)[] = new Array(assets.length).fill(null);
|
|
226
|
+
const priceData: { reserves?: string | null; slot0?: string | null; token0?: string | null }[] =
|
|
227
|
+
assets.map(() => ({}));
|
|
228
|
+
|
|
229
|
+
for (let i = 0; i < calls.length; i++) {
|
|
230
|
+
const call = calls[i];
|
|
231
|
+
const response = responses[i];
|
|
232
|
+
|
|
233
|
+
switch (call.type) {
|
|
234
|
+
case 'balance':
|
|
235
|
+
balances[call.assetIndex] = response;
|
|
236
|
+
break;
|
|
237
|
+
case 'reserves':
|
|
238
|
+
priceData[call.assetIndex].reserves = response;
|
|
239
|
+
break;
|
|
240
|
+
case 'slot0':
|
|
241
|
+
priceData[call.assetIndex].slot0 = response;
|
|
242
|
+
break;
|
|
243
|
+
case 'token0':
|
|
244
|
+
priceData[call.assetIndex].token0 = response;
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Build result map
|
|
250
|
+
for (let i = 0; i < assets.length; i++) {
|
|
251
|
+
const asset = assets[i];
|
|
252
|
+
const balanceRaw = balances[i];
|
|
253
|
+
|
|
254
|
+
// Parse balance
|
|
255
|
+
let balance = '0';
|
|
256
|
+
let balanceRawStr = '0';
|
|
257
|
+
if (balanceRaw && balanceRaw !== '0x') {
|
|
258
|
+
try {
|
|
259
|
+
const balanceBigInt = BigInt(balanceRaw);
|
|
260
|
+
balanceRawStr = balanceBigInt.toString();
|
|
261
|
+
balance = ethers.formatUnits(balanceBigInt, asset.decimals);
|
|
262
|
+
} catch {
|
|
263
|
+
// Keep defaults
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Parse price
|
|
268
|
+
let priceInEth: number | null = null;
|
|
269
|
+
const pd = priceData[i];
|
|
270
|
+
|
|
271
|
+
if (pd.token0) {
|
|
272
|
+
// Determine if our token is token0
|
|
273
|
+
let tokenIsToken0 = false;
|
|
274
|
+
try {
|
|
275
|
+
const token0Address = '0x' + pd.token0.slice(26).toLowerCase();
|
|
276
|
+
tokenIsToken0 = token0Address === asset.tokenAddress.toLowerCase();
|
|
277
|
+
} catch {
|
|
278
|
+
// Assume false
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (asset.poolVersion === 'v2' && pd.reserves) {
|
|
282
|
+
priceInEth = parseV2Price(pd.reserves, tokenIsToken0);
|
|
283
|
+
} else if ((asset.poolVersion === 'v3' || asset.poolVersion === 'v4') && pd.slot0) {
|
|
284
|
+
priceInEth = parseV3Price(pd.slot0, tokenIsToken0);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
result.set(asset.tokenAddress.toLowerCase(), {
|
|
289
|
+
balance,
|
|
290
|
+
balanceRaw: balanceRawStr,
|
|
291
|
+
priceInEth
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Calculate USD value for a token
|
|
300
|
+
*/
|
|
301
|
+
export function calculateUsdValue(
|
|
302
|
+
balance: string,
|
|
303
|
+
priceInEth: number | null,
|
|
304
|
+
ethPrice: number | null
|
|
305
|
+
): number | null {
|
|
306
|
+
if (priceInEth === null || ethPrice === null) return null;
|
|
307
|
+
|
|
308
|
+
const balanceNum = parseFloat(balance);
|
|
309
|
+
if (isNaN(balanceNum) || balanceNum === 0) return null;
|
|
310
|
+
|
|
311
|
+
return balanceNum * priceInEth * ethPrice;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Format USD value for display
|
|
316
|
+
*/
|
|
317
|
+
export function formatUsdValue(usdValue: number | null): string {
|
|
318
|
+
if (usdValue === null) return '';
|
|
319
|
+
if (usdValue < 0.01) return '<$0.01';
|
|
320
|
+
if (usdValue < 1) return `$${usdValue.toFixed(2)}`;
|
|
321
|
+
if (usdValue < 1000) return `$${usdValue.toFixed(2)}`;
|
|
322
|
+
if (usdValue < 10000) return `$${usdValue.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
323
|
+
return `$${usdValue.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// SPL Token Program ID
|
|
327
|
+
const SPL_TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Fetch SPL token balances for Solana wallets.
|
|
331
|
+
* Uses getParsedTokenAccountsByOwner for a single batched RPC call.
|
|
332
|
+
* Returns Map keyed by mint address (case-sensitive base58).
|
|
333
|
+
*/
|
|
334
|
+
export async function fetchSolanaTokenData(
|
|
335
|
+
walletAddress: string,
|
|
336
|
+
assets: AssetInfo[],
|
|
337
|
+
rpcUrl: string
|
|
338
|
+
): Promise<Map<string, TokenData>> {
|
|
339
|
+
const result = new Map<string, TokenData>();
|
|
340
|
+
if (assets.length === 0) return result;
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
const connection = new Connection(rpcUrl, 'confirmed');
|
|
344
|
+
const ownerPubkey = new PublicKey(walletAddress);
|
|
345
|
+
|
|
346
|
+
const response = await connection.getParsedTokenAccountsByOwner(ownerPubkey, {
|
|
347
|
+
programId: SPL_TOKEN_PROGRAM_ID,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Build mint → balance map from all token accounts
|
|
351
|
+
const balanceByMint = new Map<string, { amount: string; uiAmount: string }>();
|
|
352
|
+
for (const { account } of response.value) {
|
|
353
|
+
const parsed = account.data.parsed;
|
|
354
|
+
if (parsed?.info?.mint) {
|
|
355
|
+
balanceByMint.set(parsed.info.mint, {
|
|
356
|
+
amount: parsed.info.tokenAmount.amount,
|
|
357
|
+
uiAmount: parsed.info.tokenAmount.uiAmountString || '0',
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for (const asset of assets) {
|
|
363
|
+
const tokenBalance = balanceByMint.get(asset.tokenAddress);
|
|
364
|
+
result.set(asset.tokenAddress, {
|
|
365
|
+
balance: tokenBalance?.uiAmount || '0',
|
|
366
|
+
balanceRaw: tokenBalance?.amount || '0',
|
|
367
|
+
priceInEth: null, // Solana on-chain pricing would need Jupiter API
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
} catch (err) {
|
|
371
|
+
console.error('[tokenData] Solana token fetch failed:', err);
|
|
372
|
+
for (const asset of assets) {
|
|
373
|
+
result.set(asset.tokenAddress, { balance: '0', balanceRaw: '0', priceInEth: null });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return result;
|
|
378
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser RSA-OAEP keypair lifecycle + HybridEnvelope decryption.
|
|
3
|
+
*
|
|
4
|
+
* The private key lives in memory only — lost on page reload (intentional).
|
|
5
|
+
* This forces re-unlock on refresh, matching 1Password's behavior.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
let privateKey: CryptoKey | null = null;
|
|
9
|
+
let publicKeyBase64: string | null = null;
|
|
10
|
+
|
|
11
|
+
function arrayBufferToBase64(buffer: ArrayBuffer): string {
|
|
12
|
+
const bytes = new Uint8Array(buffer);
|
|
13
|
+
let binary = '';
|
|
14
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
15
|
+
binary += String.fromCharCode(bytes[i]);
|
|
16
|
+
}
|
|
17
|
+
return btoa(binary);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function base64ToArrayBuffer(b64: string): ArrayBuffer {
|
|
21
|
+
const binary = atob(b64);
|
|
22
|
+
const bytes = new Uint8Array(binary.length);
|
|
23
|
+
for (let i = 0; i < binary.length; i++) {
|
|
24
|
+
bytes[i] = binary.charCodeAt(i);
|
|
25
|
+
}
|
|
26
|
+
return bytes.buffer;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generate an RSA-OAEP 2048-bit keypair.
|
|
31
|
+
* Private key is non-extractable and stored in module state.
|
|
32
|
+
* Returns the public key as SPKI base64 for sending to the server.
|
|
33
|
+
*/
|
|
34
|
+
export async function generateVaultKeypair(): Promise<{ publicKeyBase64: string }> {
|
|
35
|
+
const pair = await crypto.subtle.generateKey(
|
|
36
|
+
{
|
|
37
|
+
name: 'RSA-OAEP',
|
|
38
|
+
modulusLength: 2048,
|
|
39
|
+
publicExponent: new Uint8Array([1, 0, 1]),
|
|
40
|
+
hash: 'SHA-256',
|
|
41
|
+
},
|
|
42
|
+
false, // non-extractable private key
|
|
43
|
+
['decrypt'],
|
|
44
|
+
);
|
|
45
|
+
privateKey = pair.privateKey;
|
|
46
|
+
const spki = await crypto.subtle.exportKey('spki', pair.publicKey);
|
|
47
|
+
publicKeyBase64 = arrayBufferToBase64(spki);
|
|
48
|
+
return { publicKeyBase64 };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Returns the in-memory private key, or null after reload/lock. */
|
|
52
|
+
export function getVaultPrivateKey(): CryptoKey | null {
|
|
53
|
+
return privateKey;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Returns the cached public key base64, or null. */
|
|
57
|
+
export function getVaultPublicKeyBase64(): string | null {
|
|
58
|
+
return publicKeyBase64;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Discard both keys (called on lock). */
|
|
62
|
+
export function discardVaultKeypair(): void {
|
|
63
|
+
privateKey = null;
|
|
64
|
+
publicKeyBase64 = null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface HybridEnvelope {
|
|
68
|
+
v: number;
|
|
69
|
+
alg: string;
|
|
70
|
+
key: string;
|
|
71
|
+
iv: string;
|
|
72
|
+
tag: string;
|
|
73
|
+
data: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Decrypt a HybridEnvelope from the server's credential read endpoint.
|
|
78
|
+
*
|
|
79
|
+
* Always expects hybrid format: RSA-OAEP wrapped AES-256-GCM session key.
|
|
80
|
+
* WebCrypto expects the GCM auth tag appended to the ciphertext (unlike
|
|
81
|
+
* Node.js which stores it separately), so we concatenate data + tag.
|
|
82
|
+
*/
|
|
83
|
+
export async function decryptCredentialPayload(encryptedBase64: string): Promise<string> {
|
|
84
|
+
if (!privateKey) {
|
|
85
|
+
throw new Error('No vault keypair — unlock required');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Decode the outer base64 → JSON envelope
|
|
89
|
+
const envelopeJson = atob(encryptedBase64);
|
|
90
|
+
const envelope: HybridEnvelope = JSON.parse(envelopeJson);
|
|
91
|
+
|
|
92
|
+
if (envelope.v !== 1 || envelope.alg !== 'RSA-OAEP/AES-256-GCM') {
|
|
93
|
+
throw new Error(`Unsupported envelope: v=${envelope.v} alg=${envelope.alg}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 1. RSA-OAEP decrypt the wrapped AES session key
|
|
97
|
+
const wrappedKey = base64ToArrayBuffer(envelope.key);
|
|
98
|
+
const rawSessionKey = await crypto.subtle.decrypt(
|
|
99
|
+
{ name: 'RSA-OAEP' },
|
|
100
|
+
privateKey,
|
|
101
|
+
wrappedKey,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// 2. Import the AES-256-GCM session key
|
|
105
|
+
const aesKey = await crypto.subtle.importKey(
|
|
106
|
+
'raw',
|
|
107
|
+
rawSessionKey,
|
|
108
|
+
{ name: 'AES-GCM' },
|
|
109
|
+
false,
|
|
110
|
+
['decrypt'],
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// 3. Concatenate ciphertext + auth tag (WebCrypto expects tag appended)
|
|
114
|
+
const ciphertext = base64ToArrayBuffer(envelope.data);
|
|
115
|
+
const authTag = base64ToArrayBuffer(envelope.tag);
|
|
116
|
+
const combined = new Uint8Array(ciphertext.byteLength + authTag.byteLength);
|
|
117
|
+
combined.set(new Uint8Array(ciphertext), 0);
|
|
118
|
+
combined.set(new Uint8Array(authTag), ciphertext.byteLength);
|
|
119
|
+
|
|
120
|
+
// 4. AES-256-GCM decrypt
|
|
121
|
+
const iv = base64ToArrayBuffer(envelope.iv);
|
|
122
|
+
const plaintext = await crypto.subtle.decrypt(
|
|
123
|
+
{ name: 'AES-GCM', iv },
|
|
124
|
+
aesKey,
|
|
125
|
+
combined.buffer,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return new TextDecoder().decode(plaintext);
|
|
129
|
+
}
|