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,368 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
4
|
+
import { useWebSocket } from '@/context/WebSocketContext';
|
|
5
|
+
import { WALLET_EVENTS, type WalletEvent, type TokenCreatedData, type TokenRevokedData, type TokenSpentData, type ActionCreatedData, type ActionResolvedData } from '@/lib/events';
|
|
6
|
+
import { api, Api } from '@/lib/api';
|
|
7
|
+
|
|
8
|
+
export interface HumanAction {
|
|
9
|
+
id: string;
|
|
10
|
+
type: string;
|
|
11
|
+
fromTier: string;
|
|
12
|
+
toAddress: string | null;
|
|
13
|
+
amount: string | null;
|
|
14
|
+
chain: string;
|
|
15
|
+
status: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
metadata?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AgentToken {
|
|
21
|
+
tokenHash: string;
|
|
22
|
+
agentId: string;
|
|
23
|
+
limit: number;
|
|
24
|
+
spent: number;
|
|
25
|
+
remaining: number;
|
|
26
|
+
permissions: string[];
|
|
27
|
+
expiresAt: number;
|
|
28
|
+
isExpired: boolean;
|
|
29
|
+
isRevoked: boolean;
|
|
30
|
+
isActive: boolean; // true = valid in memory, false = DB record only (server restarted)
|
|
31
|
+
isAdmin?: boolean; // true = admin token (UI session token)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface DashboardData {
|
|
35
|
+
requests: HumanAction[];
|
|
36
|
+
tokens: {
|
|
37
|
+
active: AgentToken[];
|
|
38
|
+
inactive: AgentToken[];
|
|
39
|
+
};
|
|
40
|
+
counts: {
|
|
41
|
+
pendingActions: number;
|
|
42
|
+
activeTokens: number;
|
|
43
|
+
inactiveTokens: number;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface UseAgentActionsOptions {
|
|
48
|
+
autoFetch?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface ApprovalResult {
|
|
52
|
+
success: boolean;
|
|
53
|
+
token?: string;
|
|
54
|
+
agentId?: string;
|
|
55
|
+
limit?: number;
|
|
56
|
+
permissions?: string[];
|
|
57
|
+
expiresIn?: number;
|
|
58
|
+
txHash?: string;
|
|
59
|
+
message?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface UseAgentActionsReturn {
|
|
63
|
+
requests: HumanAction[];
|
|
64
|
+
notifications: HumanAction[];
|
|
65
|
+
dismissNotification: (id: string) => void;
|
|
66
|
+
activeTokens: AgentToken[];
|
|
67
|
+
inactiveTokens: AgentToken[];
|
|
68
|
+
loading: boolean;
|
|
69
|
+
error: string | null;
|
|
70
|
+
counts: {
|
|
71
|
+
pendingActions: number;
|
|
72
|
+
activeTokens: number;
|
|
73
|
+
};
|
|
74
|
+
refresh: () => Promise<void>;
|
|
75
|
+
resolveAction: (id: string, approved: boolean) => Promise<ApprovalResult>;
|
|
76
|
+
revokeToken: (tokenHash: string) => Promise<boolean>;
|
|
77
|
+
actionLoading: string | null;
|
|
78
|
+
lastApprovalResult: ApprovalResult | null;
|
|
79
|
+
clearApprovalResult: () => void;
|
|
80
|
+
connected: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function useAgentActions(options: UseAgentActionsOptions = {}): UseAgentActionsReturn {
|
|
84
|
+
const { autoFetch = true } = options;
|
|
85
|
+
|
|
86
|
+
const [requests, setRequests] = useState<HumanAction[]>([]);
|
|
87
|
+
const [notifications, setNotifications] = useState<HumanAction[]>([]);
|
|
88
|
+
const [activeTokens, setActiveTokens] = useState<AgentToken[]>([]);
|
|
89
|
+
const [inactiveTokens, setInactiveTokens] = useState<AgentToken[]>([]);
|
|
90
|
+
const [loading, setLoading] = useState(true);
|
|
91
|
+
const [error, setError] = useState<string | null>(null);
|
|
92
|
+
const [actionLoading, setActionLoading] = useState<string | null>(null);
|
|
93
|
+
const [counts, setCounts] = useState({ pendingActions: 0, activeTokens: 0 });
|
|
94
|
+
const [lastApprovalResult, setLastApprovalResult] = useState<ApprovalResult | null>(null);
|
|
95
|
+
|
|
96
|
+
const { subscribe, connected } = useWebSocket();
|
|
97
|
+
const mountedRef = useRef(true);
|
|
98
|
+
|
|
99
|
+
const clearApprovalResult = useCallback(() => {
|
|
100
|
+
setLastApprovalResult(null);
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
const dismissNotification = useCallback((id: string) => {
|
|
104
|
+
setNotifications(prev => prev.filter(n => n.id !== id));
|
|
105
|
+
}, []);
|
|
106
|
+
|
|
107
|
+
const fetchDashboard = useCallback(async () => {
|
|
108
|
+
try {
|
|
109
|
+
const data = await api.get<{ success: boolean; error?: string } & DashboardData>(Api.AgentDashboard, '/agent-requests');
|
|
110
|
+
|
|
111
|
+
if (!mountedRef.current) return;
|
|
112
|
+
|
|
113
|
+
if (data.success) {
|
|
114
|
+
setRequests(data.requests || []);
|
|
115
|
+
setActiveTokens(data.tokens?.active || []);
|
|
116
|
+
setInactiveTokens(data.tokens?.inactive || []);
|
|
117
|
+
setCounts({
|
|
118
|
+
pendingActions: data.counts?.pendingActions || 0,
|
|
119
|
+
activeTokens: data.counts?.activeTokens || 0,
|
|
120
|
+
});
|
|
121
|
+
setError(null);
|
|
122
|
+
} else {
|
|
123
|
+
setError(data.error || 'Failed to fetch dashboard');
|
|
124
|
+
}
|
|
125
|
+
} catch (err) {
|
|
126
|
+
if (mountedRef.current) {
|
|
127
|
+
setError(err instanceof Error ? err.message : 'Failed to fetch dashboard');
|
|
128
|
+
}
|
|
129
|
+
} finally {
|
|
130
|
+
if (mountedRef.current) {
|
|
131
|
+
setLoading(false);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
136
|
+
const refresh = useCallback(async () => {
|
|
137
|
+
setLoading(true);
|
|
138
|
+
await fetchDashboard();
|
|
139
|
+
}, [fetchDashboard]);
|
|
140
|
+
|
|
141
|
+
const resolveAction = useCallback(async (id: string, approved: boolean): Promise<ApprovalResult> => {
|
|
142
|
+
setActionLoading(`resolve-${id}`);
|
|
143
|
+
try {
|
|
144
|
+
const data = await api.post<{
|
|
145
|
+
success: boolean;
|
|
146
|
+
error?: string;
|
|
147
|
+
token?: string;
|
|
148
|
+
agentId?: string;
|
|
149
|
+
limit?: number;
|
|
150
|
+
permissions?: string[];
|
|
151
|
+
expiresIn?: number;
|
|
152
|
+
txHash?: string;
|
|
153
|
+
message?: string;
|
|
154
|
+
}>(Api.Wallet, `/actions/${id}/resolve`, { approved });
|
|
155
|
+
|
|
156
|
+
if (data.success) {
|
|
157
|
+
const result: ApprovalResult = {
|
|
158
|
+
success: true,
|
|
159
|
+
token: data.token,
|
|
160
|
+
agentId: data.agentId,
|
|
161
|
+
limit: data.limit,
|
|
162
|
+
permissions: data.permissions,
|
|
163
|
+
expiresIn: data.expiresIn,
|
|
164
|
+
txHash: data.txHash,
|
|
165
|
+
message: data.message,
|
|
166
|
+
};
|
|
167
|
+
// Set lastApprovalResult when a token is returned
|
|
168
|
+
if (data.token) {
|
|
169
|
+
setLastApprovalResult(result);
|
|
170
|
+
}
|
|
171
|
+
await refresh();
|
|
172
|
+
return result;
|
|
173
|
+
} else {
|
|
174
|
+
setError(data.error || 'Action resolution failed');
|
|
175
|
+
return { success: false, message: data.error };
|
|
176
|
+
}
|
|
177
|
+
} catch (err) {
|
|
178
|
+
const message = err instanceof Error ? err.message : 'Action resolution failed';
|
|
179
|
+
setError(message);
|
|
180
|
+
return { success: false, message };
|
|
181
|
+
} finally {
|
|
182
|
+
setActionLoading(null);
|
|
183
|
+
}
|
|
184
|
+
}, [refresh]);
|
|
185
|
+
|
|
186
|
+
const revokeToken = useCallback(async (tokenHash: string): Promise<boolean> => {
|
|
187
|
+
setActionLoading(`revoke-${tokenHash}`);
|
|
188
|
+
try {
|
|
189
|
+
const data = await api.post<{ success: boolean; error?: string }>(Api.Wallet, '/actions/tokens/revoke', { tokenHash });
|
|
190
|
+
|
|
191
|
+
if (data.success) {
|
|
192
|
+
// WebSocket will update the UI, but refresh as fallback
|
|
193
|
+
await refresh();
|
|
194
|
+
return true;
|
|
195
|
+
} else {
|
|
196
|
+
setError(data.error || 'Revoke failed');
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
} catch (err) {
|
|
200
|
+
setError(err instanceof Error ? err.message : 'Revoke failed');
|
|
201
|
+
return false;
|
|
202
|
+
} finally {
|
|
203
|
+
setActionLoading(null);
|
|
204
|
+
}
|
|
205
|
+
}, [refresh]);
|
|
206
|
+
|
|
207
|
+
// Initial fetch
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
mountedRef.current = true;
|
|
210
|
+
if (autoFetch) {
|
|
211
|
+
refresh();
|
|
212
|
+
}
|
|
213
|
+
return () => {
|
|
214
|
+
mountedRef.current = false;
|
|
215
|
+
};
|
|
216
|
+
}, [autoFetch, refresh]);
|
|
217
|
+
|
|
218
|
+
// Subscribe to WebSocket events
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
// Token created - add to active tokens
|
|
221
|
+
const unsubTokenCreated = subscribe(WALLET_EVENTS.TOKEN_CREATED, (event) => {
|
|
222
|
+
const data = (event as WalletEvent).data as TokenCreatedData;
|
|
223
|
+
setActiveTokens((prev) => {
|
|
224
|
+
// Check if already exists
|
|
225
|
+
if (prev.some((t) => t.tokenHash === data.tokenHash)) return prev;
|
|
226
|
+
return [
|
|
227
|
+
{
|
|
228
|
+
tokenHash: data.tokenHash,
|
|
229
|
+
agentId: data.agentId,
|
|
230
|
+
limit: data.limit,
|
|
231
|
+
spent: 0,
|
|
232
|
+
remaining: data.limit,
|
|
233
|
+
permissions: data.permissions,
|
|
234
|
+
expiresAt: data.expiresAt,
|
|
235
|
+
isExpired: false,
|
|
236
|
+
isRevoked: false,
|
|
237
|
+
isActive: true,
|
|
238
|
+
},
|
|
239
|
+
...prev,
|
|
240
|
+
];
|
|
241
|
+
});
|
|
242
|
+
setCounts((prev) => ({ ...prev, activeTokens: prev.activeTokens + 1 }));
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Token revoked - move from active to inactive
|
|
246
|
+
const unsubTokenRevoked = subscribe(WALLET_EVENTS.TOKEN_REVOKED, (event) => {
|
|
247
|
+
const data = (event as WalletEvent).data as TokenRevokedData;
|
|
248
|
+
setActiveTokens((prev) => {
|
|
249
|
+
const token = prev.find((t) => t.tokenHash === data.tokenHash);
|
|
250
|
+
if (token) {
|
|
251
|
+
// Move to inactive
|
|
252
|
+
setInactiveTokens((inactive) => [
|
|
253
|
+
{ ...token, isRevoked: true, isActive: false },
|
|
254
|
+
...inactive,
|
|
255
|
+
]);
|
|
256
|
+
}
|
|
257
|
+
return prev.filter((t) => t.tokenHash !== data.tokenHash);
|
|
258
|
+
});
|
|
259
|
+
setCounts((prev) => ({
|
|
260
|
+
...prev,
|
|
261
|
+
activeTokens: Math.max(0, prev.activeTokens - 1),
|
|
262
|
+
}));
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Token spent - update spent/remaining
|
|
266
|
+
const unsubTokenSpent = subscribe(WALLET_EVENTS.TOKEN_SPENT, (event) => {
|
|
267
|
+
const data = (event as WalletEvent).data as TokenSpentData;
|
|
268
|
+
setActiveTokens((prev) =>
|
|
269
|
+
prev.map((t) =>
|
|
270
|
+
t.tokenHash === data.tokenHash
|
|
271
|
+
? { ...t, spent: data.newSpent, remaining: data.remaining }
|
|
272
|
+
: t
|
|
273
|
+
)
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Action created - route notify types to notifications, others to pending requests
|
|
278
|
+
const unsubActionCreated = subscribe(WALLET_EVENTS.ACTION_CREATED, (event) => {
|
|
279
|
+
const data = (event as WalletEvent).data as ActionCreatedData;
|
|
280
|
+
|
|
281
|
+
// Notify actions go to the notifications list, not pending requests
|
|
282
|
+
if (data.type === 'notify') {
|
|
283
|
+
setNotifications((prev) => {
|
|
284
|
+
if (prev.some((n) => n.id === data.id)) return prev;
|
|
285
|
+
return [
|
|
286
|
+
{
|
|
287
|
+
id: data.id,
|
|
288
|
+
type: data.type,
|
|
289
|
+
fromTier: 'system',
|
|
290
|
+
toAddress: null,
|
|
291
|
+
amount: null,
|
|
292
|
+
chain: 'base',
|
|
293
|
+
status: 'acknowledged',
|
|
294
|
+
createdAt: new Date().toISOString(),
|
|
295
|
+
metadata: JSON.stringify({
|
|
296
|
+
...data.metadata,
|
|
297
|
+
source: data.source,
|
|
298
|
+
summary: data.summary,
|
|
299
|
+
}),
|
|
300
|
+
},
|
|
301
|
+
...prev,
|
|
302
|
+
];
|
|
303
|
+
});
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
setRequests((prev) => {
|
|
308
|
+
if (prev.some((r) => r.id === data.id)) return prev;
|
|
309
|
+
return [
|
|
310
|
+
{
|
|
311
|
+
id: data.id,
|
|
312
|
+
type: data.type,
|
|
313
|
+
fromTier: 'system',
|
|
314
|
+
toAddress: null,
|
|
315
|
+
amount: null,
|
|
316
|
+
chain: 'base',
|
|
317
|
+
status: 'pending',
|
|
318
|
+
createdAt: new Date().toISOString(),
|
|
319
|
+
metadata: JSON.stringify({
|
|
320
|
+
...data.metadata,
|
|
321
|
+
source: data.source,
|
|
322
|
+
summary: data.summary,
|
|
323
|
+
expiresAt: data.expiresAt,
|
|
324
|
+
}),
|
|
325
|
+
},
|
|
326
|
+
...prev,
|
|
327
|
+
];
|
|
328
|
+
});
|
|
329
|
+
setCounts((prev) => ({ ...prev, pendingActions: prev.pendingActions + 1 }));
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Action resolved - remove from pending requests
|
|
333
|
+
const unsubActionResolved = subscribe(WALLET_EVENTS.ACTION_RESOLVED, (event) => {
|
|
334
|
+
const data = (event as WalletEvent).data as ActionResolvedData;
|
|
335
|
+
setRequests((prev) => prev.filter((r) => r.id !== data.id));
|
|
336
|
+
setCounts((prev) => ({
|
|
337
|
+
...prev,
|
|
338
|
+
pendingActions: Math.max(0, prev.pendingActions - 1),
|
|
339
|
+
}));
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
return () => {
|
|
343
|
+
unsubTokenCreated();
|
|
344
|
+
unsubTokenRevoked();
|
|
345
|
+
unsubTokenSpent();
|
|
346
|
+
unsubActionCreated();
|
|
347
|
+
unsubActionResolved();
|
|
348
|
+
};
|
|
349
|
+
}, [subscribe]);
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
requests,
|
|
353
|
+
notifications,
|
|
354
|
+
dismissNotification,
|
|
355
|
+
activeTokens,
|
|
356
|
+
inactiveTokens,
|
|
357
|
+
loading,
|
|
358
|
+
error,
|
|
359
|
+
counts,
|
|
360
|
+
refresh,
|
|
361
|
+
resolveAction,
|
|
362
|
+
revokeToken,
|
|
363
|
+
actionLoading,
|
|
364
|
+
lastApprovalResult,
|
|
365
|
+
clearApprovalResult,
|
|
366
|
+
connected,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
import { ethers } from 'ethers';
|
|
3
|
+
import { Connection, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js';
|
|
4
|
+
import { useAuth } from '@/context/AuthContext';
|
|
5
|
+
|
|
6
|
+
function isSolanaChain(chain: string): boolean {
|
|
7
|
+
return chain === 'solana' || chain === 'solana-devnet';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface UseBalanceResult {
|
|
11
|
+
balance: string | null;
|
|
12
|
+
balanceWei: bigint | null;
|
|
13
|
+
loading: boolean;
|
|
14
|
+
error: string | null;
|
|
15
|
+
refetch: () => Promise<void>;
|
|
16
|
+
/** Native currency symbol for the chain */
|
|
17
|
+
currency: string;
|
|
18
|
+
/** Number of decimals for the native currency */
|
|
19
|
+
decimals: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useBalance(address: string | undefined, chain?: string): UseBalanceResult {
|
|
23
|
+
const { getRpcUrl, getConfiguredChains } = useAuth();
|
|
24
|
+
const [balance, setBalance] = useState<string | null>(null);
|
|
25
|
+
const [balanceWei, setBalanceWei] = useState<bigint | null>(null);
|
|
26
|
+
const [loading, setLoading] = useState(false);
|
|
27
|
+
const [error, setError] = useState<string | null>(null);
|
|
28
|
+
|
|
29
|
+
// Track previous values to detect changes
|
|
30
|
+
const prevAddressRef = useRef<string>('');
|
|
31
|
+
const prevRpcUrlRef = useRef<string>('');
|
|
32
|
+
|
|
33
|
+
const chains = getConfiguredChains();
|
|
34
|
+
const defaultChain = Object.keys(chains)[0] || 'base';
|
|
35
|
+
const targetChain = chain || defaultChain;
|
|
36
|
+
|
|
37
|
+
const isSolana = isSolanaChain(targetChain);
|
|
38
|
+
const currency = isSolana ? 'SOL' : 'ETH';
|
|
39
|
+
const decimals = isSolana ? 9 : 18;
|
|
40
|
+
|
|
41
|
+
// Get current RPC URL for comparison
|
|
42
|
+
const rpcUrl = getRpcUrl(targetChain);
|
|
43
|
+
|
|
44
|
+
const fetchBalance = useCallback(async () => {
|
|
45
|
+
if (!address) {
|
|
46
|
+
setBalance(null);
|
|
47
|
+
setBalanceWei(null);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setLoading(true);
|
|
52
|
+
setError(null);
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const currentRpcUrl = getRpcUrl(targetChain);
|
|
56
|
+
|
|
57
|
+
if (isSolanaChain(targetChain)) {
|
|
58
|
+
// Solana balance fetch
|
|
59
|
+
const connection = new Connection(currentRpcUrl, 'confirmed');
|
|
60
|
+
const pubkey = new PublicKey(address);
|
|
61
|
+
const lamports = await connection.getBalance(pubkey);
|
|
62
|
+
setBalanceWei(BigInt(lamports));
|
|
63
|
+
setBalance((lamports / LAMPORTS_PER_SOL).toString());
|
|
64
|
+
} else {
|
|
65
|
+
// EVM balance fetch
|
|
66
|
+
const provider = new ethers.JsonRpcProvider(currentRpcUrl);
|
|
67
|
+
const wei = await provider.getBalance(address);
|
|
68
|
+
setBalanceWei(wei);
|
|
69
|
+
setBalance(ethers.formatEther(wei));
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.error('[useBalance] Failed to fetch balance:', err);
|
|
73
|
+
setError(err instanceof Error ? err.message : 'Failed to fetch balance');
|
|
74
|
+
setBalance(null);
|
|
75
|
+
setBalanceWei(null);
|
|
76
|
+
} finally {
|
|
77
|
+
setLoading(false);
|
|
78
|
+
}
|
|
79
|
+
}, [address, targetChain, getRpcUrl]);
|
|
80
|
+
|
|
81
|
+
// Refetch when address, chain, or RPC URL changes
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
const shouldRefetch =
|
|
84
|
+
address !== prevAddressRef.current ||
|
|
85
|
+
rpcUrl !== prevRpcUrlRef.current;
|
|
86
|
+
|
|
87
|
+
if (shouldRefetch) {
|
|
88
|
+
prevAddressRef.current = address || '';
|
|
89
|
+
prevRpcUrlRef.current = rpcUrl;
|
|
90
|
+
fetchBalance();
|
|
91
|
+
}
|
|
92
|
+
}, [address, rpcUrl, fetchBalance]);
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
balance,
|
|
96
|
+
balanceWei,
|
|
97
|
+
loading,
|
|
98
|
+
error,
|
|
99
|
+
refetch: fetchBalance,
|
|
100
|
+
currency,
|
|
101
|
+
decimals,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
import { ethers } from 'ethers';
|
|
3
|
+
import { useAuth } from '@/context/AuthContext';
|
|
4
|
+
|
|
5
|
+
interface BalanceMap {
|
|
6
|
+
[address: string]: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface UseBalancesResult {
|
|
10
|
+
balances: BalanceMap;
|
|
11
|
+
loading: boolean;
|
|
12
|
+
error: string | null;
|
|
13
|
+
refetch: () => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Hook to fetch ETH balances for multiple addresses using batch RPC calls
|
|
18
|
+
* Uses eth_getBalance with JSON-RPC batch request for efficiency
|
|
19
|
+
*/
|
|
20
|
+
export function useBalances(addresses: string[], chain?: string): UseBalancesResult {
|
|
21
|
+
const { getRpcUrl, getConfiguredChains } = useAuth();
|
|
22
|
+
const [balances, setBalances] = useState<BalanceMap>({});
|
|
23
|
+
const [loading, setLoading] = useState(false);
|
|
24
|
+
const [error, setError] = useState<string | null>(null);
|
|
25
|
+
|
|
26
|
+
// Track previous values to detect changes
|
|
27
|
+
const prevAddressesRef = useRef<string>('');
|
|
28
|
+
const prevRpcUrlRef = useRef<string>('');
|
|
29
|
+
|
|
30
|
+
const chains = getConfiguredChains();
|
|
31
|
+
const defaultChain = Object.keys(chains)[0] || 'base';
|
|
32
|
+
const targetChain = chain || defaultChain;
|
|
33
|
+
|
|
34
|
+
// Get current RPC URL for comparison
|
|
35
|
+
const rpcUrl = getRpcUrl(targetChain);
|
|
36
|
+
|
|
37
|
+
const fetchBalances = useCallback(async () => {
|
|
38
|
+
if (!addresses || addresses.length === 0) {
|
|
39
|
+
setBalances({});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const currentRpcUrl = getRpcUrl(targetChain);
|
|
44
|
+
|
|
45
|
+
setLoading(true);
|
|
46
|
+
setError(null);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Build batch request for all addresses
|
|
50
|
+
const batchRequest = addresses.map((address, index) => ({
|
|
51
|
+
jsonrpc: '2.0',
|
|
52
|
+
id: index,
|
|
53
|
+
method: 'eth_getBalance',
|
|
54
|
+
params: [address, 'latest'],
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
const response = await fetch(currentRpcUrl, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
|
+
body: JSON.stringify(batchRequest),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
throw new Error(`RPC request failed: ${response.status}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const results = await response.json();
|
|
68
|
+
|
|
69
|
+
// Handle both array response (batch) and single response
|
|
70
|
+
const resultsArray = Array.isArray(results) ? results : [results];
|
|
71
|
+
|
|
72
|
+
// Sort by id to match original order
|
|
73
|
+
resultsArray.sort((a, b) => a.id - b.id);
|
|
74
|
+
|
|
75
|
+
const newBalances: BalanceMap = {};
|
|
76
|
+
resultsArray.forEach((result, index) => {
|
|
77
|
+
const address = addresses[index];
|
|
78
|
+
if (result.result) {
|
|
79
|
+
const wei = BigInt(result.result);
|
|
80
|
+
newBalances[address.toLowerCase()] = ethers.formatEther(wei);
|
|
81
|
+
} else {
|
|
82
|
+
newBalances[address.toLowerCase()] = '0';
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
setBalances(newBalances);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error('[useBalances] Failed to fetch balances:', err);
|
|
89
|
+
setError(err instanceof Error ? err.message : 'Failed to fetch balances');
|
|
90
|
+
|
|
91
|
+
// Set all balances to '0' on error
|
|
92
|
+
const fallbackBalances: BalanceMap = {};
|
|
93
|
+
addresses.forEach(addr => {
|
|
94
|
+
fallbackBalances[addr.toLowerCase()] = '0';
|
|
95
|
+
});
|
|
96
|
+
setBalances(fallbackBalances);
|
|
97
|
+
} finally {
|
|
98
|
+
setLoading(false);
|
|
99
|
+
}
|
|
100
|
+
}, [addresses, targetChain, getRpcUrl]);
|
|
101
|
+
|
|
102
|
+
// Refetch when addresses, chain, or RPC URL changes
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
const addressKey = addresses.map(a => a.toLowerCase()).sort().join(',');
|
|
105
|
+
const shouldRefetch =
|
|
106
|
+
addressKey !== prevAddressesRef.current ||
|
|
107
|
+
rpcUrl !== prevRpcUrlRef.current;
|
|
108
|
+
|
|
109
|
+
if (shouldRefetch && addresses.length > 0) {
|
|
110
|
+
prevAddressesRef.current = addressKey;
|
|
111
|
+
prevRpcUrlRef.current = rpcUrl;
|
|
112
|
+
fetchBalances();
|
|
113
|
+
}
|
|
114
|
+
}, [addresses, rpcUrl, fetchBalances]);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
balances,
|
|
118
|
+
loading,
|
|
119
|
+
error,
|
|
120
|
+
refetch: fetchBalances,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Helper to get balance for a specific address from the balances map
|
|
126
|
+
*/
|
|
127
|
+
export function getBalance(balances: BalanceMap, address: string): string {
|
|
128
|
+
return balances[address.toLowerCase()] || '0';
|
|
129
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js instrumentation hook
|
|
3
|
+
* Sets up WebSocket server for real-time events
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export async function register() {
|
|
7
|
+
// Only run on server
|
|
8
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
9
|
+
const { setupWebSocketServer } = await import('./lib/websocket-setup');
|
|
10
|
+
setupWebSocketServer();
|
|
11
|
+
}
|
|
12
|
+
}
|