From 73b60350e638de2be31ba6f6af84324a9ee0b79f Mon Sep 17 00:00:00 2001 From: alex luu Date: Tue, 17 Mar 2026 20:34:59 -0400 Subject: [PATCH 01/18] feat: add privy --- .env.example | 3 + package.json | 3 + .../trade/components/wallet-dialog.tsx | 373 +----------------- src/components/trade/header/user-menu.tsx | 33 +- .../trade/mobile/mobile-account-view.tsx | 22 +- .../trade/mobile/mobile-trade-view.tsx | 12 +- .../trade/positions/send-dialog.tsx | 4 +- .../trade/tradebox/account-panel.tsx | 15 +- src/components/trade/tradebox/trade-panel.tsx | 18 +- src/config/constants.ts | 2 + src/config/privy.ts | 15 + src/config/wagmi.ts | 51 +-- src/domain/trade/balances.ts | 18 +- src/domain/trade/order/derive.ts | 16 +- src/domain/trade/order/size.ts | 14 + src/domain/trade/swap.ts | 17 +- src/hooks/trade/use-asset-leverage.ts | 24 +- src/hooks/use-privy-wagmi-sync.ts | 26 ++ src/lib/hyperliquid/clients.ts | 4 +- .../exchange/useExchangeUserSetAbstraction.ts | 37 ++ .../hooks/info/useInfoUserAbstraction.ts | 62 +++ src/lib/hyperliquid/hooks/useTradingGuard.ts | 5 +- src/lib/hyperliquid/signing/types.ts | 8 +- .../signing/use-agent-registration.ts | 14 +- src/lib/trade/use-button-content.ts | 2 + src/lib/wallet-utils.ts | 49 +-- src/locales/ar/messages.po | 54 +-- src/locales/en/messages.po | 54 +-- src/locales/es/messages.po | 54 +-- src/locales/fr/messages.po | 54 +-- src/locales/hi/messages.po | 54 +-- src/locales/zh/messages.po | 54 +-- src/providers/root.tsx | 27 +- vite.config.ts | 21 +- 34 files changed, 527 insertions(+), 692 deletions(-) create mode 100644 .env.example create mode 100644 src/config/privy.ts create mode 100644 src/hooks/use-privy-wagmi-sync.ts create mode 100644 src/lib/hyperliquid/hooks/exchange/useExchangeUserSetAbstraction.ts create mode 100644 src/lib/hyperliquid/hooks/info/useInfoUserAbstraction.ts diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..8a6cf76d --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +VITE_PRIVY_APP_ID=your-privy-app-id +# Set to "true" for Hyperliquid testnet, "false" for mainnet +VITE_HYPERLIQUID_TESTNET=true diff --git a/package.json b/package.json index 8f64fec1..7700fecc 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,8 @@ "@lingui/react": "5.7.0", "@nktkas/hyperliquid": "0.31.0", "@phosphor-icons/react": "2.1.10", + "@privy-io/react-auth": "^3.17.0", + "@privy-io/wagmi": "^4.0.2", "@tanstack/react-query": "5.66.5", "@tanstack/react-router": "1.132.0", "@tanstack/react-router-ssr-query": "1.131.7", @@ -49,6 +51,7 @@ "tw-animate-css": "1.4.0", "vaul": "1.1.2", "viem": "2.42.1", + "vite-plugin-node-polyfills": "^0.25.0", "wagmi": "3.1.0", "zod": "4.3.4", "zustand": "5.0.9" diff --git a/src/components/trade/components/wallet-dialog.tsx b/src/components/trade/components/wallet-dialog.tsx index 29c74a31..c171ee12 100644 --- a/src/components/trade/components/wallet-dialog.tsx +++ b/src/components/trade/components/wallet-dialog.tsx @@ -1,24 +1,5 @@ -import { Trans } from "@lingui/react/macro"; -import { - ArrowSquareOutIcon, - FlaskIcon, - QuestionIcon, - ShieldIcon, - SpinnerGapIcon, - WalletIcon, - WarningCircleIcon, -} from "@phosphor-icons/react"; -import { useMemo, useState } from "react"; -import type { Address } from "viem"; -import { isAddress } from "viem"; -import { type Connector, useConnect, useConnectors } from "wagmi"; -import { mock } from "wagmi/connectors"; -import { Button } from "@/components/ui/button"; -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { MOCK_WALLETS } from "@/config/wagmi"; -import { cn } from "@/lib/cn"; -import { getLastUsedWallet, getWalletInfo, isMockConnector, setLastUsedWallet } from "@/lib/wallet-utils"; +import { useLogin } from "@privy-io/react-auth"; +import { useEffect } from "react"; interface Props { open: boolean; @@ -26,346 +7,16 @@ interface Props { } export function WalletDialog({ open, onOpenChange }: Props) { - const connectors = useConnectors(); - const { mutateAsync: connectAsync, isPending, error } = useConnect(); - const [connectingId, setConnectingId] = useState(null); - const [showHelp, setShowHelp] = useState(false); - const [lastUsedWallet] = useState(() => getLastUsedWallet()); - const [customAddress, setCustomAddress] = useState(""); - const [customAddressError, setCustomAddressError] = useState(null); - - const { mockConnectors, regularConnectors } = useMemo(() => { - const mocks: Connector[] = []; - const regular: Connector[] = []; - - for (const connector of connectors) { - if (isMockConnector(connector)) { - mocks.push(connector); - } else { - regular.push(connector); - } - } - - return { mockConnectors: mocks, regularConnectors: regular }; - }, [connectors]); - - const availableConnectors = useMemo(() => { - const sortByPriority = (a: Connector, b: Connector) => { - if (lastUsedWallet) { - if (a.id === lastUsedWallet) return -1; - if (b.id === lastUsedWallet) return 1; - } - const priorityA = getWalletInfo(a).priority ?? 50; - const priorityB = getWalletInfo(b).priority ?? 50; - return priorityA - priorityB; - }; - - const popular = regularConnectors.filter((c) => getWalletInfo(c).popular).sort(sortByPriority); - const other = regularConnectors.filter((c) => !getWalletInfo(c).popular).sort(sortByPriority); - return { popular, other, all: regularConnectors }; - }, [regularConnectors, lastUsedWallet]); - - const handleConnect = async (connector: Connector) => { - setConnectingId(connector.uid); - setLastUsedWallet(connector.id); - try { - await connectAsync({ connector }); - onOpenChange(false); - } finally { - setConnectingId(null); - } - }; - - const handleCustomAddressConnect = async () => { - const trimmed = customAddress.trim(); - if (!trimmed) { - setCustomAddressError("Please enter an address"); - return; - } - if (!isAddress(trimmed)) { - setCustomAddressError("Invalid Ethereum address"); - return; - } - setCustomAddressError(null); - - const mockWalletIndex = MOCK_WALLETS.findIndex((w) => w.address.toLowerCase() === trimmed.toLowerCase()); - - if (mockWalletIndex !== -1 && mockConnectors[mockWalletIndex]) { - handleConnect(mockConnectors[mockWalletIndex]); - } else { - const customMockConnector = mock({ - accounts: [trimmed as Address], - features: { reconnect: true }, - }); - setConnectingId("custom-mock"); - try { - await connectAsync({ connector: customMockConnector }); - onOpenChange(false); - } catch { - setCustomAddressError("Failed to connect with custom address"); - } finally { - setConnectingId(null); - } + const { login } = useLogin({ + onComplete: () => onOpenChange(false), + onError: () => onOpenChange(false), + }); + + useEffect(() => { + if (open) { + login(); } - }; - - const hasConnectors = availableConnectors.all.length > 0 || mockConnectors.length > 0; - - return ( - - -
- - - - Connect Wallet - - - Connect your wallet to start trading on Hyperliquid - - -
- -
- {availableConnectors.popular.length > 0 && ( -
-

- Popular -

-
- {availableConnectors.popular.map((connector) => { - const walletInfo = getWalletInfo(connector); - const Icon = walletInfo.icon; - const isConnecting = connectingId === connector.uid; - - return ( - - ); - })} -
-
- )} - - {availableConnectors.other.length > 0 && ( -
-

- Other Options -

-
- {availableConnectors.other.map((connector) => { - const walletInfo = getWalletInfo(connector); - const Icon = walletInfo.icon; - const isConnecting = connectingId === connector.uid; - - return ( - - ); - })} -
-
- )} - - {mockConnectors.length > 0 && ( -
-

- Mock Wallet (Testing) -

-
- {mockConnectors.map((connector, index) => { - const config = MOCK_WALLETS[index]; - const isConnecting = connectingId === connector.uid; - - return ( - - ); - })} -
-
-
- { - setCustomAddress(e.target.value); - setCustomAddressError(null); - }} - className="font-mono text-xs" - /> - -
- {customAddressError &&

{customAddressError}

} -
-
- )} - - {!hasConnectors && ( -
-
- -
-
-

- No wallets found -

-

- Install a wallet extension to continue -

-
-
- )} - - {error && ( -
- -

{error.message}

-
- )} -
- -
- + }, [open, login]); - {showHelp && ( -
-
- -
-

- Secure & Private -

-

- Only you control your funds. No email or password required. -

-
-
-
- -
-

- What is a wallet? -

-

- A crypto wallet lets you store and manage your digital assets securely. -

-
-
- -
- )} -
-
-
- ); + return null; } diff --git a/src/components/trade/header/user-menu.tsx b/src/components/trade/header/user-menu.tsx index aea5ba19..5ec00257 100644 --- a/src/components/trade/header/user-menu.tsx +++ b/src/components/trade/header/user-menu.tsx @@ -8,6 +8,7 @@ import { SpinnerGapIcon, WalletIcon, } from "@phosphor-icons/react"; +import { useLogin, useLogout, usePrivy } from "@privy-io/react-auth"; import { useEffect, useState } from "react"; import { useConnection, useDisconnect, useEnsName } from "wagmi"; import { Button } from "@/components/ui/button"; @@ -20,7 +21,6 @@ import { } from "@/components/ui/dropdown-menu"; import { useCopyToClipboard } from "@/hooks/ui/use-copy-to-clipboard"; import { shortenAddress } from "@/lib/format"; -import { WalletDialog } from "../components/wallet-dialog"; function CopyAddressMenuItem({ address }: { address: string }) { const { copied, copy } = useCopyToClipboard(); @@ -46,15 +46,25 @@ function CopyAddressMenuItem({ address }: { address: string }) { export function UserMenu() { const { address, isConnected, isConnecting } = useConnection(); - const disconnect = useDisconnect(); + const { authenticated } = usePrivy(); + const { login } = useLogin(); + const { logout } = useLogout(); + const { disconnect } = useDisconnect(); const { data: ensName } = useEnsName({ address }); - const [isOpen, setIsOpen] = useState(false); const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); + function handleLogout() { + if (authenticated) { + logout(); + } else { + disconnect(); + } + } + if (!mounted || isConnecting) { return ( - - + ); } @@ -95,11 +102,7 @@ export function UserMenu() { - disconnect.mutate()} - > + Disconnect diff --git a/src/components/trade/mobile/mobile-account-view.tsx b/src/components/trade/mobile/mobile-account-view.tsx index 0431d981..d2896520 100644 --- a/src/components/trade/mobile/mobile-account-view.tsx +++ b/src/components/trade/mobile/mobile-account-view.tsx @@ -1,5 +1,5 @@ import { ArrowSquareOutIcon, CopyIcon, LightningIcon, SignOutIcon, WalletIcon } from "@phosphor-icons/react"; -import { useState } from "react"; +import { useLogin, useLogout, usePrivy } from "@privy-io/react-auth"; import { useConnection, useDisconnect } from "wagmi"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -11,7 +11,6 @@ import { cn } from "@/lib/cn"; import { formatPercent, formatUSD } from "@/lib/format"; import { toNumber, toNumberOrZero } from "@/lib/trade/numbers"; import { useDepositModalActions } from "@/stores/use-global-modal-store"; -import { WalletDialog } from "../components/wallet-dialog"; import { MobileBottomNavSpacer } from "./mobile-bottom-nav"; const ACCOUNT_TEXT = UI_TEXT.ACCOUNT_PANEL; @@ -22,11 +21,21 @@ interface MobileAccountViewProps { export function MobileAccountView({ className }: MobileAccountViewProps) { const { address, isConnected } = useConnection(); - const disconnect = useDisconnect(); + const { authenticated } = usePrivy(); + const { login } = useLogin(); + const { logout } = useLogout(); + const { disconnect } = useDisconnect(); const { perpSummary, perpPositions, isLoading } = useAccountBalances(); - const [walletDialogOpen, setWalletDialogOpen] = useState(false); + function handleLogout() { + if (authenticated) { + logout(); + } else { + disconnect(); + } + } + const { copied, copy } = useCopyToClipboard(); const { open: openDepositModal } = useDepositModalActions(); @@ -64,7 +73,7 @@ export function MobileAccountView({ className }: MobileAccountViewProps) { - ); } @@ -113,7 +121,7 @@ export function MobileAccountView({ className }: MobileAccountViewProps) { */ -export function createLazyComponent, K extends keyof T>( +export function createLazyComponent, K extends keyof T>( importer: () => Promise, exportName: K, ) { diff --git a/src/lib/trade/use-order-validation.ts b/src/lib/trade/use-order-validation.ts index 7d4e0b6a..918cb7b1 100644 --- a/src/lib/trade/use-order-validation.ts +++ b/src/lib/trade/use-order-validation.ts @@ -56,7 +56,12 @@ export function perpInput(base: BaseOrderInput, perp: PerpOrderFields): PerpVali return { ...base, ...perp, isSpotMarket: false }; } -function toResult(result: { valid: boolean; errors: { message: string }[]; canSubmit: boolean; needsApproval: boolean }): ValidationResult { +function toResult(result: { + valid: boolean; + errors: { message: string }[]; + canSubmit: boolean; + needsApproval: boolean; +}): ValidationResult { return { valid: result.valid, errors: result.errors.map((e) => e.message), From 95e2b95cef4cce8f1988adf1ae8545b7027c14f7 Mon Sep 17 00:00:00 2001 From: alex luu Date: Tue, 17 Mar 2026 21:52:09 -0400 Subject: [PATCH 08/18] fix: tests --- src/lib/test-setup/lingui-macro-stub.ts | 7 +++++++ vitest.config.ts | 12 +++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/lib/test-setup/lingui-macro-stub.ts diff --git a/src/lib/test-setup/lingui-macro-stub.ts b/src/lib/test-setup/lingui-macro-stub.ts new file mode 100644 index 00000000..33df89b1 --- /dev/null +++ b/src/lib/test-setup/lingui-macro-stub.ts @@ -0,0 +1,7 @@ +/** + * Stub for @lingui/core/macro in tests. Avoids requiring babel-plugin-macros at runtime. + * Template tag returns the literal string (no translation). + */ +export function t(strings: TemplateStringsArray, ...values: unknown[]): string { + return strings.reduce((acc, s, i) => acc + s + (values[i] ?? ""), ""); +} diff --git a/vitest.config.ts b/vitest.config.ts index ba7485eb..a999d9e4 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,8 +1,18 @@ +import { lingui } from "@lingui/vite-plugin"; +import path from "node:path"; import { defineConfig } from "vitest/config"; import viteTsConfigPaths from "vite-tsconfig-paths"; export default defineConfig({ - plugins: [viteTsConfigPaths({ projects: ["./tsconfig.json"] })], + plugins: [ + viteTsConfigPaths({ projects: ["./tsconfig.json"] }), + lingui(), + ], + resolve: { + alias: { + "@lingui/core/macro": path.resolve(__dirname, "src/lib/test-setup/lingui-macro-stub.ts"), + }, + }, test: { environment: "node", }, From b26da97a9c2e3ef6dcd59806aa0631eb4b61e5bf Mon Sep 17 00:00:00 2001 From: alex luu Date: Thu, 19 Mar 2026 12:43:05 -0400 Subject: [PATCH 09/18] fix(hooks): source nvm in lingui-extract to use correct node version --- lefthook.yml | 2 +- src/components/trade/tradebox/trade-panel.tsx | 2 +- src/styles.css | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lefthook.yml b/lefthook.yml index 6a900e01..cbfbaf18 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -6,7 +6,7 @@ pre-commit: run: npx biome check --write --staged --no-errors-on-unmatched && git add {staged_files} lingui-extract: glob: "src/**/*.{ts,tsx}" - run: pnpm i18n:extract && git add src/locales/**/*.po + run: bash -c '. "$NVM_DIR/nvm.sh" && npx lingui extract' && git add src/locales/**/*.po # Commit message validation commit-msg: diff --git a/src/components/trade/tradebox/trade-panel.tsx b/src/components/trade/tradebox/trade-panel.tsx index 8a68193c..f5227e03 100644 --- a/src/components/trade/tradebox/trade-panel.tsx +++ b/src/components/trade/tradebox/trade-panel.tsx @@ -501,7 +501,7 @@ export function TradePanel() { size="lg" onClick={buttonContent.action} disabled={buttonContent.disabled} - className={cn("w-full")} + className={cn("w-full", !isConnected && "text-fill-900")} aria-label={buttonContent.text} > {(isSubmitting || isRegistering) && } diff --git a/src/styles.css b/src/styles.css index d35bfaac..2bac1a16 100644 --- a/src/styles.css +++ b/src/styles.css @@ -101,10 +101,10 @@ --border-50: #F6F7F8; /* Primary */ - --primary-default: #2563EB; - --primary-hover: #1D4ED8; - --primary-active: #1E40AF; - --primary-muted: #92B1F5; + --primary-default: #005562; + --primary-hover: #006B7A; + --primary-active: #004A56; + --primary-muted: #E8FBFD; /* Market Up */ --market-up-600: #3C7D3E; @@ -178,10 +178,10 @@ --border-50: #222833; /* Primary */ - --primary-default: #4F7DFF; - --primary-hover: #3B6CF6; - --primary-active: #2F5CE0; - --primary-muted: #385194; + --primary-default: #D5F7FB; + --primary-hover: #B8EFF5; + --primary-active: #9AE7F0; + --primary-muted: #1A3D40; /* Market Up */ --market-up-600: #4CAF6A; From e892e5dc16bcabe5db436a7fe5556385bbe76dd8 Mon Sep 17 00:00:00 2001 From: alex luu Date: Thu, 19 Mar 2026 12:53:38 -0400 Subject: [PATCH 10/18] feat: update the white color --- src/components/trade/tradebox/trade-panel.tsx | 2 +- src/styles.css | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/trade/tradebox/trade-panel.tsx b/src/components/trade/tradebox/trade-panel.tsx index f5227e03..5e6175a4 100644 --- a/src/components/trade/tradebox/trade-panel.tsx +++ b/src/components/trade/tradebox/trade-panel.tsx @@ -501,7 +501,7 @@ export function TradePanel() { size="lg" onClick={buttonContent.action} disabled={buttonContent.disabled} - className={cn("w-full", !isConnected && "text-fill-900")} + className={cn("w-full", !isConnected && "text-primary-text")} aria-label={buttonContent.text} > {(isSubmitting || isRegistering) && } diff --git a/src/styles.css b/src/styles.css index 2bac1a16..2c8183c0 100644 --- a/src/styles.css +++ b/src/styles.css @@ -105,6 +105,7 @@ --primary-hover: #006B7A; --primary-active: #004A56; --primary-muted: #E8FBFD; + --primary-text: #D5F7FB; /* Market Up */ --market-up-600: #3C7D3E; @@ -182,6 +183,7 @@ --primary-hover: #B8EFF5; --primary-active: #9AE7F0; --primary-muted: #1A3D40; + --primary-text: #131619; /* Market Up */ --market-up-600: #4CAF6A; @@ -267,6 +269,7 @@ --color-primary-hover: var(--primary-hover); --color-primary-active: var(--primary-active); --color-primary-muted: var(--primary-muted); + --color-primary-text: var(--primary-text); /* Market */ --color-market-up-600: var(--market-up-600); From 21f8859f91604023d40738a649ddfb51b6a96283 Mon Sep 17 00:00:00 2001 From: alex luu Date: Thu, 19 Mar 2026 13:03:07 -0400 Subject: [PATCH 11/18] fix: revert lefthook --- lefthook.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lefthook.yml b/lefthook.yml index cbfbaf18..6a900e01 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -6,7 +6,7 @@ pre-commit: run: npx biome check --write --staged --no-errors-on-unmatched && git add {staged_files} lingui-extract: glob: "src/**/*.{ts,tsx}" - run: bash -c '. "$NVM_DIR/nvm.sh" && npx lingui extract' && git add src/locales/**/*.po + run: pnpm i18n:extract && git add src/locales/**/*.po # Commit message validation commit-msg: From 2d11f8aeea33ccbc9182f6b30e6451506dfedf1e Mon Sep 17 00:00:00 2001 From: alex luu Date: Thu, 19 Mar 2026 17:04:49 -0400 Subject: [PATCH 12/18] fix: update colors --- src/components/trade/tradebox/trade-panel.tsx | 3 +-- src/components/ui/button.tsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/trade/tradebox/trade-panel.tsx b/src/components/trade/tradebox/trade-panel.tsx index 5e6175a4..6092ca27 100644 --- a/src/components/trade/tradebox/trade-panel.tsx +++ b/src/components/trade/tradebox/trade-panel.tsx @@ -13,7 +13,6 @@ import { buildOrderPlan } from "@/domain/trade/order-intent"; import { formatPriceForOrder, formatSizeForOrder, throwIfResponseError } from "@/domain/trade/orders"; import { useFeeRates } from "@/hooks/trade/use-fee-rates"; import { useOrderEntryData } from "@/hooks/trade/use-order-entry-data"; -import { cn } from "@/lib/cn"; import { useAgentRegistration, useAgentStatus, useSelectedMarketInfo, useUserPositions } from "@/lib/hyperliquid"; import { useExchangeOrder } from "@/lib/hyperliquid/hooks/exchange/useExchangeOrder"; import { useExchangeTwapOrder } from "@/lib/hyperliquid/hooks/exchange/useExchangeTwapOrder"; @@ -501,7 +500,7 @@ export function TradePanel() { size="lg" onClick={buttonContent.action} disabled={buttonContent.disabled} - className={cn("w-full", !isConnected && "text-primary-text")} + className="w-full" aria-label={buttonContent.text} > {(isSubmitting || isRegistering) && } diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 5dabb642..55cd1ef3 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -35,7 +35,7 @@ const buttonVariants = cva( { variant: "contained", tone: "accent", - class: "bg-primary-default text-white hover:bg-primary-hover active:bg-primary-active", + class: "bg-primary-default text-primary-text hover:bg-primary-hover active:bg-primary-active", }, { variant: "outlined", From 079fec901d49d89c4a7353205cc7ceacc107e78b Mon Sep 17 00:00:00 2001 From: alex luu Date: Fri, 20 Mar 2026 18:14:44 -0400 Subject: [PATCH 13/18] feat: support wallets --- src/components/trade/header/top-nav.tsx | 26 ------------ .../trade/mobile/mobile-trade-view.tsx | 23 ++--------- src/components/trade/tradebox/trade-panel.tsx | 15 +++---- src/config/privy.ts | 4 +- src/config/wagmi.ts | 5 ++- src/hooks/use-privy-wagmi-sync.ts | 17 ++++++-- src/lib/hyperliquid/hooks/useClients.ts | 40 ++++++++++++++----- src/locales/ar/messages.po | 21 +++++----- src/locales/en/messages.po | 21 +++++----- src/locales/es/messages.po | 21 +++++----- src/locales/fr/messages.po | 21 +++++----- src/locales/hi/messages.po | 21 +++++----- src/locales/zh/messages.po | 21 +++++----- 13 files changed, 118 insertions(+), 138 deletions(-) diff --git a/src/components/trade/header/top-nav.tsx b/src/components/trade/header/top-nav.tsx index 20567067..0543b586 100644 --- a/src/components/trade/header/top-nav.tsx +++ b/src/components/trade/header/top-nav.tsx @@ -13,20 +13,6 @@ import { UserMenu } from "./user-menu"; const SCOPE_NAV_ITEMS = [ { scope: "all" as const, label: All, to: "/", activeClass: "text-text-950 font-medium" }, { scope: "perp" as const, label: Perp, to: "/perp", activeClass: "text-scope-perp font-medium" }, - { scope: "spot" as const, label: Spot, to: "/spot", activeClass: "text-scope-spot font-medium" }, - { - scope: "builders-perp" as const, - label: Builders, - to: "/builders-perp", - activeClass: "text-scope-builders font-medium", - }, -] as const; - -const STATIC_NAV_ITEMS = [ - { key: "vaults", label: Vaults }, - { key: "portfolio", label: Portfolio }, - { key: "staking", label: Staking }, - { key: "leaderboard", label: Leaderboard }, ] as const; function getScopeAccentClass(scope: string): string { @@ -81,18 +67,6 @@ export function TopNav() { {item.label} ))} -
- {STATIC_NAV_ITEMS.map((item) => ( - - ))}
diff --git a/src/components/trade/mobile/mobile-trade-view.tsx b/src/components/trade/mobile/mobile-trade-view.tsx index c2cead5e..d7a78140 100644 --- a/src/components/trade/mobile/mobile-trade-view.tsx +++ b/src/components/trade/mobile/mobile-trade-view.tsx @@ -1,13 +1,12 @@ import { CaretDownIcon, SpinnerGapIcon, TrendDownIcon, TrendUpIcon } from "@phosphor-icons/react"; import { useLogin } from "@privy-io/react-auth"; import { useEffect, useMemo, useState } from "react"; -import { useConnection, useSwitchChain, useWalletClient } from "wagmi"; +import { useConnection } from "wagmi"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Slider, type SliderMark } from "@/components/ui/slider"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { FALLBACK_VALUE_PLACEHOLDER, ORDER_MIN_NOTIONAL_USD, UI_TEXT } from "@/config/constants"; -import { ARBITRUM_CHAIN_ID } from "@/config/contracts"; import { getBaseQuoteFromPairName } from "@/domain/market"; import { formatPriceForOrder, formatSizeForOrder, throwIfResponseError } from "@/domain/trade/orders"; import { useAccountBalances } from "@/hooks/trade/use-account-balances"; @@ -46,12 +45,8 @@ interface MobileTradeViewProps { export function MobileTradeView({ className }: MobileTradeViewProps) { const { address, isConnected } = useConnection(); - const { data: walletClient, isLoading: isWalletLoading, error: walletClientError } = useWalletClient(); - const { switchChain, isPending: isSwitchingChain } = useSwitchChain(); const { login } = useLogin(); - const needsChainSwitch = !!walletClientError && walletClientError.message.includes("does not match"); - const { data: market } = useSelectedMarketInfo(); const { scope } = useExchangeScope(); const { setSelectedMarket } = useMarketActions(); @@ -68,7 +63,7 @@ export function MobileTradeView({ className }: MobileTradeViewProps) { const { isReady: isAgentApproved } = useAgentStatus(); const { register: registerAgent, status: registerStatus } = useAgentRegistration(); - const canApprove = !!walletClient && !!address; + const canApprove = !!address; const isRegistering = registerStatus === "switching_account_mode" || registerStatus === "approving_fee" || @@ -149,14 +144,12 @@ export function MobileTradeView({ className }: MobileTradeViewProps) { return side === "buy" ? price - buffer : price + buffer; })(); - const canSign = isAgentApproved || !!walletClient; + const canSign = isAgentApproved || !!address; const validation = useMemo(() => { const errors: string[] = []; if (!isConnected) return { valid: false, errors: [ORDER_TEXT.ERROR_NOT_CONNECTED], canSubmit: false, needsApproval: false }; - if (isWalletLoading) - return { valid: false, errors: [ORDER_TEXT.ERROR_LOADING_WALLET], canSubmit: false, needsApproval: false }; if (availableBalance <= 0) return { valid: false, errors: [ORDER_TEXT.ERROR_NO_BALANCE], canSubmit: false, needsApproval: false }; if (!market) return { valid: false, errors: [ORDER_TEXT.ERROR_NO_MARKET], canSubmit: false, needsApproval: false }; @@ -174,7 +167,6 @@ export function MobileTradeView({ className }: MobileTradeViewProps) { return { valid: errors.length === 0, errors, canSubmit: errors.length === 0, needsApproval: false }; }, [ isConnected, - isWalletLoading, availableBalance, market, isMarketExecution, @@ -218,8 +210,6 @@ export function MobileTradeView({ className }: MobileTradeViewProps) { if (markPx > 0) setLimitPriceInput(markPx.toFixed(szDecimalsToPriceDecimals(market?.szDecimals ?? 4))); }; - const handleSwitchChain = () => switchChain({ chainId: ARBITRUM_CHAIN_ID }); - const handleApprove = async () => { if (isRegistering) return; setApprovalError(null); @@ -290,13 +280,6 @@ export function MobileTradeView({ className }: MobileTradeViewProps) { disabled: false, variant: "cyan" as const, }; - if (needsChainSwitch) - return { - text: isSwitchingChain ? ORDER_TEXT.BUTTON_SWITCHING : ORDER_TEXT.BUTTON_SWITCH_CHAIN, - action: handleSwitchChain, - disabled: isSwitchingChain, - variant: "cyan" as const, - }; if (availableBalance <= 0) return { text: ORDER_TEXT.BUTTON_DEPOSIT, diff --git a/src/components/trade/tradebox/trade-panel.tsx b/src/components/trade/tradebox/trade-panel.tsx index 6092ca27..db6bf171 100644 --- a/src/components/trade/tradebox/trade-panel.tsx +++ b/src/components/trade/tradebox/trade-panel.tsx @@ -2,7 +2,7 @@ import { t } from "@lingui/core/macro"; import { SpinnerGapIcon } from "@phosphor-icons/react"; import { useLogin } from "@privy-io/react-auth"; import { useCallback, useEffect, useId, useMemo, useState } from "react"; -import { useConnection, useSwitchChain, useWalletClient } from "wagmi"; +import { useConnection } from "wagmi"; import { Button } from "@/components/ui/button"; import { DEFAULT_QUOTE_TOKEN, isUsdStablecoin, TWAP_MINUTES_MAX, TWAP_MINUTES_MIN } from "@/config/constants"; import { APPROVAL_ERROR_DISMISS_MS } from "@/config/time"; @@ -66,10 +66,7 @@ export function TradePanel() { const tpSlId = useId(); const { address, isConnected } = useConnection(); - const { data: walletClient, isLoading: isWalletLoading, error: walletClientError } = useWalletClient(); - const switchChain = useSwitchChain(); const { login } = useLogin(); - const needsChainSwitch = !!walletClientError && walletClientError.message.includes("does not match"); const { data: market } = useSelectedMarketInfo(); @@ -200,11 +197,11 @@ export function TradePanel() { const needsAgentApproval = !isAgentReady; const isReadyToTrade = isAgentReady; - const canApprove = !!walletClient && !!address; + const canApprove = !!address; const baseInput = { isConnected, - isWalletLoading, + isWalletLoading: false, availableBalance, hasMarket: !!market, hasAssetIndex: typeof market?.assetId === "number", @@ -420,9 +417,9 @@ export function TradePanel() { const buttonContent = useButtonContent({ isConnected, - needsChainSwitch, - isSwitchingChain: switchChain.isPending, - switchChain: (chainId) => switchChain.mutate({ chainId }), + needsChainSwitch: false, + isSwitchingChain: false, + switchChain: () => {}, availableBalance, validation, isAgentLoading, diff --git a/src/config/privy.ts b/src/config/privy.ts index eee0851c..e37cd90c 100644 --- a/src/config/privy.ts +++ b/src/config/privy.ts @@ -1,5 +1,5 @@ import type { PrivyClientConfig } from "@privy-io/react-auth"; -import { arbitrum } from "wagmi/chains"; +import { arbitrum, arbitrumSepolia } from "wagmi/chains"; export const privyConfig: PrivyClientConfig = { loginMethods: ["email", "google", "wallet"], @@ -11,5 +11,5 @@ export const privyConfig: PrivyClientConfig = { appearance: { showWalletLoginFirst: false, }, - supportedChains: [arbitrum], + supportedChains: [arbitrum, arbitrumSepolia], }; diff --git a/src/config/wagmi.ts b/src/config/wagmi.ts index e9859f54..40ec3297 100644 --- a/src/config/wagmi.ts +++ b/src/config/wagmi.ts @@ -1,10 +1,11 @@ import { createConfig } from "@privy-io/wagmi"; import { http } from "wagmi"; -import { arbitrum } from "wagmi/chains"; +import { arbitrum, arbitrumSepolia } from "wagmi/chains"; export const config = createConfig({ - chains: [arbitrum], + chains: [arbitrum, arbitrumSepolia], transports: { [arbitrum.id]: http(), + [arbitrumSepolia.id]: http(), }, }); diff --git a/src/hooks/use-privy-wagmi-sync.ts b/src/hooks/use-privy-wagmi-sync.ts index 6051affa..4ca74972 100644 --- a/src/hooks/use-privy-wagmi-sync.ts +++ b/src/hooks/use-privy-wagmi-sync.ts @@ -1,5 +1,5 @@ import { usePrivy } from "@privy-io/react-auth"; -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import { useConnection, useDisconnect } from "wagmi"; /** @@ -11,16 +11,25 @@ import { useConnection, useDisconnect } from "wagmi"; * * This hook detects that mismatch once Privy is ready and force-disconnects * wagmi so both layers agree on the unauthenticated state. + * + * The check runs only when Privy becomes ready (once on init), not reactively + * on auth/connection changes — otherwise it would disconnect wagmi mid-login + * before Privy finishes the wallet authentication handshake. */ export function usePrivyWagmiSync() { const { ready, authenticated } = usePrivy(); const { isConnected } = useConnection(); - const { disconnect } = useDisconnect(); + const { mutate: disconnect } = useDisconnect(); + + const authenticatedRef = useRef(authenticated); + const isConnectedRef = useRef(isConnected); + authenticatedRef.current = authenticated; + isConnectedRef.current = isConnected; useEffect(() => { if (!ready) return; - if (!authenticated && isConnected) { + if (!authenticatedRef.current && isConnectedRef.current) { disconnect(); } - }, [ready, authenticated, isConnected, disconnect]); + }, [ready, disconnect]); } diff --git a/src/lib/hyperliquid/hooks/useClients.ts b/src/lib/hyperliquid/hooks/useClients.ts index 9fa17ab9..73da46c2 100644 --- a/src/lib/hyperliquid/hooks/useClients.ts +++ b/src/lib/hyperliquid/hooks/useClients.ts @@ -1,10 +1,11 @@ import type { ExchangeClient, InfoClient, SubscriptionClient } from "@nktkas/hyperliquid"; +import { useWallets } from "@privy-io/react-auth"; import { useMemo } from "react"; -import { useConnection, useWalletClient } from "wagmi"; +import { useConnection } from "wagmi"; +import { arbitrum, arbitrumSepolia } from "wagmi/chains"; import { createExchangeClient } from "@/lib/hyperliquid/clients"; import { useHyperliquid } from "@/lib/hyperliquid/provider"; import { useAgentWallet } from "@/lib/hyperliquid/signing/use-agent-wallet"; -import { toHyperliquidWallet } from "@/lib/hyperliquid/wallet"; export interface HyperliquidClients { info: InfoClient; @@ -16,10 +17,10 @@ export interface HyperliquidClients { } export function useHyperliquidClients(): HyperliquidClients { - const { info, subscription } = useHyperliquid(); + const { info, subscription, env } = useHyperliquid(); const { signer, isReady: agentReady } = useAgentWallet(); const { address } = useConnection(); - const { data: walletClient } = useWalletClient(); + const { wallets } = useWallets(); const trading = useMemo(() => { if (!signer || !agentReady) return null; @@ -27,11 +28,32 @@ export function useHyperliquidClients(): HyperliquidClients { }, [signer, agentReady]); const user = useMemo(() => { - if (!walletClient || !address) return null; - const wallet = toHyperliquidWallet(walletClient, address); - if (!wallet) return null; - return createExchangeClient(wallet); - }, [walletClient, address]); + if (!address) return null; + const privyWallet = wallets.find((w) => w.address.toLowerCase() === address.toLowerCase()); + if (!privyWallet) return null; + const chain = env === "Testnet" ? arbitrumSepolia : arbitrum; + return createExchangeClient({ + address, + // Required for SDK to detect this as AbstractViemJsonRpcAccount and call getChainId() + // for signatureChainId. Without these, SDK falls back to chainId 1 (mainnet), which + // causes switchChain(1) and Hyperliquid signature validation to fail. + getAddresses: async () => [address], + getChainId: async () => chain.id, + signTypedData: async (params) => { + const provider = await privyWallet.getEthereumProvider(); + // Switch chain only if needed — avoids redundant popups on subsequent signing steps + // MetaMask v11+ rejects eth_signTypedData_v4 if domain chainId ≠ active chain + const currentChainId = parseInt((await provider.request({ method: "eth_chainId" })) as string, 16); + if (currentChainId !== chain.id) { + await privyWallet.switchChain(chain.id); + } + return provider.request({ + method: "eth_signTypedData_v4", + params: [address, JSON.stringify(params)], + }) as Promise<`0x${string}`>; + }, + }); + }, [address, wallets, env]); return { info, diff --git a/src/locales/ar/messages.po b/src/locales/ar/messages.po index 5aaf4710..894c93d9 100644 --- a/src/locales/ar/messages.po +++ b/src/locales/ar/messages.po @@ -205,8 +205,8 @@ msgstr "" #~ msgstr "" #: src/components/trade/header/top-nav.tsx -msgid "Builders" -msgstr "" +#~ msgid "Builders" +#~ msgstr "" #: src/components/trade/positions/twap-tab.tsx msgid "buy" @@ -793,8 +793,8 @@ msgstr "معزول" #~ msgstr "التأخير:" #: src/components/trade/header/top-nav.tsx -msgid "Leaderboard" -msgstr "لوحة المتصدرين" +#~ msgid "Leaderboard" +#~ msgstr "لوحة المتصدرين" #: src/components/trade/components/wallet-dialog.tsx #~ msgid "Learn more" @@ -1298,8 +1298,8 @@ msgstr "الربح والخسارة" #~ msgstr "" #: src/components/trade/header/top-nav.tsx -msgid "Portfolio" -msgstr "المحفظة" +#~ msgid "Portfolio" +#~ msgstr "المحفظة" #: src/components/trade/positions/funding-tab.tsx #: src/components/trade/tradebox/trade-form-fields.tsx @@ -1680,7 +1680,6 @@ msgstr "مرتب حسب {0}" #~ msgid "spot" #~ msgstr "فوري" -#: src/components/trade/header/top-nav.tsx #: src/components/trade/positions/balances-tab.tsx #: src/components/trade/positions/transfer-dialog.tsx #: src/components/trade/positions/transfer-dialog.tsx @@ -1701,8 +1700,8 @@ msgid "Spread" msgstr "الفارق" #: src/components/trade/header/top-nav.tsx -msgid "Staking" -msgstr "التخزين" +#~ msgid "Staking" +#~ msgstr "التخزين" #: src/lib/errors/definitions/scale.ts msgid "Start and end must differ" @@ -2059,8 +2058,8 @@ msgstr "القيمة بالدولار" #~ msgstr "القيمة" #: src/components/trade/header/top-nav.tsx -msgid "Vaults" -msgstr "الخزائن" +#~ msgid "Vaults" +#~ msgstr "الخزائن" #: src/lib/trade/use-button-content.ts msgid "Verifying..." diff --git a/src/locales/en/messages.po b/src/locales/en/messages.po index b36b8b43..3a41fdf8 100644 --- a/src/locales/en/messages.po +++ b/src/locales/en/messages.po @@ -205,8 +205,8 @@ msgstr "Builder Fee" #~ msgstr "Builder must have at least 100 USDC in perps account value" #: src/components/trade/header/top-nav.tsx -msgid "Builders" -msgstr "Builders" +#~ msgid "Builders" +#~ msgstr "Builders" #: src/components/trade/positions/twap-tab.tsx msgid "buy" @@ -797,8 +797,8 @@ msgstr "Isolated" #~ msgstr "Latency:" #: src/components/trade/header/top-nav.tsx -msgid "Leaderboard" -msgstr "Leaderboard" +#~ msgid "Leaderboard" +#~ msgstr "Leaderboard" #: src/components/trade/components/wallet-dialog.tsx #~ msgid "Learn more" @@ -1302,8 +1302,8 @@ msgstr "PNL" #~ msgstr "Popular" #: src/components/trade/header/top-nav.tsx -msgid "Portfolio" -msgstr "Portfolio" +#~ msgid "Portfolio" +#~ msgstr "Portfolio" #: src/components/trade/positions/funding-tab.tsx #: src/components/trade/tradebox/trade-form-fields.tsx @@ -1684,7 +1684,6 @@ msgstr "Sorted by {0}" #~ msgid "spot" #~ msgstr "spot" -#: src/components/trade/header/top-nav.tsx #: src/components/trade/positions/balances-tab.tsx #: src/components/trade/positions/transfer-dialog.tsx #: src/components/trade/positions/transfer-dialog.tsx @@ -1705,8 +1704,8 @@ msgid "Spread" msgstr "Spread" #: src/components/trade/header/top-nav.tsx -msgid "Staking" -msgstr "Staking" +#~ msgid "Staking" +#~ msgstr "Staking" #: src/lib/errors/definitions/scale.ts msgid "Start and end must differ" @@ -2067,8 +2066,8 @@ msgstr "USD Value" #~ msgstr "Value" #: src/components/trade/header/top-nav.tsx -msgid "Vaults" -msgstr "Vaults" +#~ msgid "Vaults" +#~ msgstr "Vaults" #: src/lib/trade/use-button-content.ts msgid "Verifying..." diff --git a/src/locales/es/messages.po b/src/locales/es/messages.po index 6135324d..a279ca24 100644 --- a/src/locales/es/messages.po +++ b/src/locales/es/messages.po @@ -205,8 +205,8 @@ msgstr "" #~ msgstr "" #: src/components/trade/header/top-nav.tsx -msgid "Builders" -msgstr "" +#~ msgid "Builders" +#~ msgstr "" #: src/components/trade/positions/twap-tab.tsx msgid "buy" @@ -793,8 +793,8 @@ msgstr "Aislado" #~ msgstr "Latencia:" #: src/components/trade/header/top-nav.tsx -msgid "Leaderboard" -msgstr "Clasificación" +#~ msgid "Leaderboard" +#~ msgstr "Clasificación" #: src/components/trade/components/wallet-dialog.tsx #~ msgid "Learn more" @@ -1298,8 +1298,8 @@ msgstr "PNL" #~ msgstr "" #: src/components/trade/header/top-nav.tsx -msgid "Portfolio" -msgstr "Portafolio" +#~ msgid "Portfolio" +#~ msgstr "Portafolio" #: src/components/trade/positions/funding-tab.tsx #: src/components/trade/tradebox/trade-form-fields.tsx @@ -1680,7 +1680,6 @@ msgstr "Ordenado por {0}" #~ msgid "spot" #~ msgstr "spot" -#: src/components/trade/header/top-nav.tsx #: src/components/trade/positions/balances-tab.tsx #: src/components/trade/positions/transfer-dialog.tsx #: src/components/trade/positions/transfer-dialog.tsx @@ -1701,8 +1700,8 @@ msgid "Spread" msgstr "Diferencial" #: src/components/trade/header/top-nav.tsx -msgid "Staking" -msgstr "Staking" +#~ msgid "Staking" +#~ msgstr "Staking" #: src/lib/errors/definitions/scale.ts msgid "Start and end must differ" @@ -2059,8 +2058,8 @@ msgstr "Valor en USD" #~ msgstr "Valor" #: src/components/trade/header/top-nav.tsx -msgid "Vaults" -msgstr "Bóvedas" +#~ msgid "Vaults" +#~ msgstr "Bóvedas" #: src/lib/trade/use-button-content.ts msgid "Verifying..." diff --git a/src/locales/fr/messages.po b/src/locales/fr/messages.po index b3126b61..f5604fcc 100644 --- a/src/locales/fr/messages.po +++ b/src/locales/fr/messages.po @@ -205,8 +205,8 @@ msgstr "" #~ msgstr "" #: src/components/trade/header/top-nav.tsx -msgid "Builders" -msgstr "" +#~ msgid "Builders" +#~ msgstr "" #: src/components/trade/positions/twap-tab.tsx msgid "buy" @@ -793,8 +793,8 @@ msgstr "Isolé" #~ msgstr "Latence :" #: src/components/trade/header/top-nav.tsx -msgid "Leaderboard" -msgstr "Classement" +#~ msgid "Leaderboard" +#~ msgstr "Classement" #: src/components/trade/components/wallet-dialog.tsx #~ msgid "Learn more" @@ -1298,8 +1298,8 @@ msgstr "PNL" #~ msgstr "" #: src/components/trade/header/top-nav.tsx -msgid "Portfolio" -msgstr "Portefeuille" +#~ msgid "Portfolio" +#~ msgstr "Portefeuille" #: src/components/trade/positions/funding-tab.tsx #: src/components/trade/tradebox/trade-form-fields.tsx @@ -1680,7 +1680,6 @@ msgstr "Trié par {0}" #~ msgid "spot" #~ msgstr "spot" -#: src/components/trade/header/top-nav.tsx #: src/components/trade/positions/balances-tab.tsx #: src/components/trade/positions/transfer-dialog.tsx #: src/components/trade/positions/transfer-dialog.tsx @@ -1701,8 +1700,8 @@ msgid "Spread" msgstr "Écart" #: src/components/trade/header/top-nav.tsx -msgid "Staking" -msgstr "Staking" +#~ msgid "Staking" +#~ msgstr "Staking" #: src/lib/errors/definitions/scale.ts msgid "Start and end must differ" @@ -2059,8 +2058,8 @@ msgstr "Valeur en USD" #~ msgstr "Valeur" #: src/components/trade/header/top-nav.tsx -msgid "Vaults" -msgstr "Coffres" +#~ msgid "Vaults" +#~ msgstr "Coffres" #: src/lib/trade/use-button-content.ts msgid "Verifying..." diff --git a/src/locales/hi/messages.po b/src/locales/hi/messages.po index 84464b55..84ca6c6b 100644 --- a/src/locales/hi/messages.po +++ b/src/locales/hi/messages.po @@ -205,8 +205,8 @@ msgstr "" #~ msgstr "" #: src/components/trade/header/top-nav.tsx -msgid "Builders" -msgstr "" +#~ msgid "Builders" +#~ msgstr "" #: src/components/trade/positions/twap-tab.tsx msgid "buy" @@ -793,8 +793,8 @@ msgstr "आइसोलेटेड" #~ msgstr "लेटेंसी:" #: src/components/trade/header/top-nav.tsx -msgid "Leaderboard" -msgstr "लीडरबोर्ड" +#~ msgid "Leaderboard" +#~ msgstr "लीडरबोर्ड" #: src/components/trade/components/wallet-dialog.tsx #~ msgid "Learn more" @@ -1298,8 +1298,8 @@ msgstr "PNL" #~ msgstr "" #: src/components/trade/header/top-nav.tsx -msgid "Portfolio" -msgstr "पोर्टफोलियो" +#~ msgid "Portfolio" +#~ msgstr "पोर्टफोलियो" #: src/components/trade/positions/funding-tab.tsx #: src/components/trade/tradebox/trade-form-fields.tsx @@ -1680,7 +1680,6 @@ msgstr "{0} से क्रमबद्ध" #~ msgid "spot" #~ msgstr "स्पॉट" -#: src/components/trade/header/top-nav.tsx #: src/components/trade/positions/balances-tab.tsx #: src/components/trade/positions/transfer-dialog.tsx #: src/components/trade/positions/transfer-dialog.tsx @@ -1701,8 +1700,8 @@ msgid "Spread" msgstr "स्प्रेड" #: src/components/trade/header/top-nav.tsx -msgid "Staking" -msgstr "स्टेकिंग" +#~ msgid "Staking" +#~ msgstr "स्टेकिंग" #: src/lib/errors/definitions/scale.ts msgid "Start and end must differ" @@ -2059,8 +2058,8 @@ msgstr "USD मूल्य" #~ msgstr "वैल्यू" #: src/components/trade/header/top-nav.tsx -msgid "Vaults" -msgstr "वॉल्ट" +#~ msgid "Vaults" +#~ msgstr "वॉल्ट" #: src/lib/trade/use-button-content.ts msgid "Verifying..." diff --git a/src/locales/zh/messages.po b/src/locales/zh/messages.po index 20510c35..c7148c8b 100644 --- a/src/locales/zh/messages.po +++ b/src/locales/zh/messages.po @@ -205,8 +205,8 @@ msgstr "" #~ msgstr "" #: src/components/trade/header/top-nav.tsx -msgid "Builders" -msgstr "" +#~ msgid "Builders" +#~ msgstr "" #: src/components/trade/positions/twap-tab.tsx msgid "buy" @@ -793,8 +793,8 @@ msgstr "逐仓" #~ msgstr "延迟:" #: src/components/trade/header/top-nav.tsx -msgid "Leaderboard" -msgstr "排行榜" +#~ msgid "Leaderboard" +#~ msgstr "排行榜" #: src/components/trade/components/wallet-dialog.tsx #~ msgid "Learn more" @@ -1298,8 +1298,8 @@ msgstr "盈亏" #~ msgstr "" #: src/components/trade/header/top-nav.tsx -msgid "Portfolio" -msgstr "投资组合" +#~ msgid "Portfolio" +#~ msgstr "投资组合" #: src/components/trade/positions/funding-tab.tsx #: src/components/trade/tradebox/trade-form-fields.tsx @@ -1680,7 +1680,6 @@ msgstr "已按 {0} 排序" #~ msgid "spot" #~ msgstr "现货" -#: src/components/trade/header/top-nav.tsx #: src/components/trade/positions/balances-tab.tsx #: src/components/trade/positions/transfer-dialog.tsx #: src/components/trade/positions/transfer-dialog.tsx @@ -1701,8 +1700,8 @@ msgid "Spread" msgstr "价差" #: src/components/trade/header/top-nav.tsx -msgid "Staking" -msgstr "质押" +#~ msgid "Staking" +#~ msgstr "质押" #: src/lib/errors/definitions/scale.ts msgid "Start and end must differ" @@ -2059,8 +2058,8 @@ msgstr "美元价值" #~ msgstr "价值" #: src/components/trade/header/top-nav.tsx -msgid "Vaults" -msgstr "金库" +#~ msgid "Vaults" +#~ msgstr "金库" #: src/lib/trade/use-button-content.ts msgid "Verifying..." From b4d6d15f6e6bb1ebfd5afd40f66914ad89f83129 Mon Sep 17 00:00:00 2001 From: alex luu Date: Fri, 20 Mar 2026 18:25:36 -0400 Subject: [PATCH 14/18] fix: remove dead code --- src/components/trade/tradebox/trade-panel.tsx | 4 ---- src/lib/errors/stacks/perp-order.ts | 8 +------- src/lib/errors/stacks/spot-order.ts | 8 +------- src/lib/trade/use-button-content.ts | 15 --------------- src/lib/trade/use-order-validation.ts | 1 - src/locales/ar/messages.po | 2 -- src/locales/en/messages.po | 2 -- src/locales/es/messages.po | 2 -- src/locales/fr/messages.po | 2 -- src/locales/hi/messages.po | 2 -- src/locales/zh/messages.po | 2 -- 11 files changed, 2 insertions(+), 46 deletions(-) diff --git a/src/components/trade/tradebox/trade-panel.tsx b/src/components/trade/tradebox/trade-panel.tsx index db6bf171..fb83e9a6 100644 --- a/src/components/trade/tradebox/trade-panel.tsx +++ b/src/components/trade/tradebox/trade-panel.tsx @@ -201,7 +201,6 @@ export function TradePanel() { const baseInput = { isConnected, - isWalletLoading: false, availableBalance, hasMarket: !!market, hasAssetIndex: typeof market?.assetId === "number", @@ -417,9 +416,6 @@ export function TradePanel() { const buttonContent = useButtonContent({ isConnected, - needsChainSwitch: false, - isSwitchingChain: false, - switchChain: () => {}, availableBalance, validation, isAgentLoading, diff --git a/src/lib/errors/stacks/perp-order.ts b/src/lib/errors/stacks/perp-order.ts index 0d435736..a7a93a2e 100644 --- a/src/lib/errors/stacks/perp-order.ts +++ b/src/lib/errors/stacks/perp-order.ts @@ -1,9 +1,5 @@ import { noBalanceValidator } from "../definitions/balance"; -import { - signerNotReadyValidator, - walletLoadingValidator, - walletNotConnectedValidator, -} from "../definitions/connection"; +import { signerNotReadyValidator, walletNotConnectedValidator } from "../definitions/connection"; import { marketNotReadyValidator, noMarketValidator, noMarkPriceValidator } from "../definitions/market"; import { enterLimitPriceValidator, @@ -33,7 +29,6 @@ import { runValidators, type ValidationError, type Validator } from "../types"; export interface PerpOrderContext extends OrderInputContext, TpSlContext, TriggerContext, ScaleContext, TwapContext { isConnected: boolean; - isWalletLoading: boolean; isReadyToTrade: boolean; needsAgentApproval: boolean; availableBalance: number; @@ -51,7 +46,6 @@ export interface PerpOrderValidationResult { const perpOrderValidators: Validator[] = [ walletNotConnectedValidator, - walletLoadingValidator, noBalanceValidator, noMarketValidator, marketNotReadyValidator, diff --git a/src/lib/errors/stacks/spot-order.ts b/src/lib/errors/stacks/spot-order.ts index cd127ce0..8b36bece 100644 --- a/src/lib/errors/stacks/spot-order.ts +++ b/src/lib/errors/stacks/spot-order.ts @@ -1,9 +1,5 @@ import { noBalanceValidator } from "../definitions/balance"; -import { - signerNotReadyValidator, - walletLoadingValidator, - walletNotConnectedValidator, -} from "../definitions/connection"; +import { signerNotReadyValidator, walletNotConnectedValidator } from "../definitions/connection"; import { marketNotReadyValidator, noMarketValidator } from "../definitions/market"; import { insufficientBaseBalanceValidator, @@ -20,7 +16,6 @@ import { runValidators, type ValidationError, type Validator } from "../types"; export interface SpotOrderContext extends SpotInputContext, SpotBalanceContext { isConnected: boolean; - isWalletLoading: boolean; isReadyToTrade: boolean; needsAgentApproval: boolean; availableBalance: number; @@ -37,7 +32,6 @@ export interface SpotOrderValidationResult { const spotOrderValidators: Validator[] = [ walletNotConnectedValidator, - walletLoadingValidator, noBalanceValidator, noMarketValidator, marketNotReadyValidator, diff --git a/src/lib/trade/use-button-content.ts b/src/lib/trade/use-button-content.ts index d5f6146f..6e07c7a7 100644 --- a/src/lib/trade/use-button-content.ts +++ b/src/lib/trade/use-button-content.ts @@ -1,14 +1,10 @@ import { t } from "@lingui/core/macro"; import { useMemo } from "react"; -import { ARBITRUM_CHAIN_ID } from "@/config/contracts"; import type { RegistrationStatus } from "@/lib/hyperliquid/signing/types"; import type { ButtonContent, Side, ValidationResult } from "@/lib/trade/types"; interface ButtonContentInput { isConnected: boolean; - needsChainSwitch: boolean; - isSwitchingChain: boolean; - switchChain: (chainId: number) => void; availableBalance: number; validation: ValidationResult; isAgentLoading: boolean; @@ -52,14 +48,6 @@ export function useButtonContent(input: ButtonContentInput): ButtonContent { variant: "cyan", }; } - if (input.needsChainSwitch) { - return { - text: input.isSwitchingChain ? t`Switching...` : t`Switch to Arbitrum`, - action: () => input.switchChain(ARBITRUM_CHAIN_ID), - disabled: input.isSwitchingChain, - variant: "cyan", - }; - } if (input.validation.needsApproval) { return { text: registerText, @@ -84,9 +72,6 @@ export function useButtonContent(input: ButtonContentInput): ButtonContent { }; }, [ input.isConnected, - input.needsChainSwitch, - input.isSwitchingChain, - input.switchChain, input.availableBalance, input.validation.needsApproval, input.validation.canSubmit, diff --git a/src/lib/trade/use-order-validation.ts b/src/lib/trade/use-order-validation.ts index 918cb7b1..c36d5db9 100644 --- a/src/lib/trade/use-order-validation.ts +++ b/src/lib/trade/use-order-validation.ts @@ -4,7 +4,6 @@ import type { Side, ValidationResult } from "@/lib/trade/types"; export interface BaseOrderInput { isConnected: boolean; - isWalletLoading: boolean; availableBalance: number; hasMarket: boolean; hasAssetIndex: boolean; diff --git a/src/locales/ar/messages.po b/src/locales/ar/messages.po index 894c93d9..32d7f42b 100644 --- a/src/locales/ar/messages.po +++ b/src/locales/ar/messages.po @@ -1791,7 +1791,6 @@ msgid "Switch to {displayName} market" msgstr "" #: src/components/trade/tradebox/deposit-modal.tsx -#: src/lib/trade/use-button-content.ts msgid "Switch to Arbitrum" msgstr "التبديل إلى Arbitrum" @@ -1808,7 +1807,6 @@ msgid "Switch to light mode" msgstr "التبديل إلى الوضع الفاتح" #: src/components/trade/tradebox/deposit-modal.tsx -#: src/lib/trade/use-button-content.ts msgid "Switching..." msgstr "جاري التبديل..." diff --git a/src/locales/en/messages.po b/src/locales/en/messages.po index 3a41fdf8..713a453d 100644 --- a/src/locales/en/messages.po +++ b/src/locales/en/messages.po @@ -1795,7 +1795,6 @@ msgid "Switch to {displayName} market" msgstr "Switch to {displayName} market" #: src/components/trade/tradebox/deposit-modal.tsx -#: src/lib/trade/use-button-content.ts msgid "Switch to Arbitrum" msgstr "Switch to Arbitrum" @@ -1812,7 +1811,6 @@ msgid "Switch to light mode" msgstr "Switch to light mode" #: src/components/trade/tradebox/deposit-modal.tsx -#: src/lib/trade/use-button-content.ts msgid "Switching..." msgstr "Switching..." diff --git a/src/locales/es/messages.po b/src/locales/es/messages.po index a279ca24..ba93ad43 100644 --- a/src/locales/es/messages.po +++ b/src/locales/es/messages.po @@ -1791,7 +1791,6 @@ msgid "Switch to {displayName} market" msgstr "" #: src/components/trade/tradebox/deposit-modal.tsx -#: src/lib/trade/use-button-content.ts msgid "Switch to Arbitrum" msgstr "Cambiar a Arbitrum" @@ -1808,7 +1807,6 @@ msgid "Switch to light mode" msgstr "Cambiar a modo claro" #: src/components/trade/tradebox/deposit-modal.tsx -#: src/lib/trade/use-button-content.ts msgid "Switching..." msgstr "Cambiando..." diff --git a/src/locales/fr/messages.po b/src/locales/fr/messages.po index f5604fcc..33dfa793 100644 --- a/src/locales/fr/messages.po +++ b/src/locales/fr/messages.po @@ -1791,7 +1791,6 @@ msgid "Switch to {displayName} market" msgstr "" #: src/components/trade/tradebox/deposit-modal.tsx -#: src/lib/trade/use-button-content.ts msgid "Switch to Arbitrum" msgstr "Passer à Arbitrum" @@ -1808,7 +1807,6 @@ msgid "Switch to light mode" msgstr "Passer en mode clair" #: src/components/trade/tradebox/deposit-modal.tsx -#: src/lib/trade/use-button-content.ts msgid "Switching..." msgstr "Changement..." diff --git a/src/locales/hi/messages.po b/src/locales/hi/messages.po index 84ca6c6b..655a0957 100644 --- a/src/locales/hi/messages.po +++ b/src/locales/hi/messages.po @@ -1791,7 +1791,6 @@ msgid "Switch to {displayName} market" msgstr "" #: src/components/trade/tradebox/deposit-modal.tsx -#: src/lib/trade/use-button-content.ts msgid "Switch to Arbitrum" msgstr "Arbitrum पर स्विच करें" @@ -1808,7 +1807,6 @@ msgid "Switch to light mode" msgstr "लाइट मोड में बदलें" #: src/components/trade/tradebox/deposit-modal.tsx -#: src/lib/trade/use-button-content.ts msgid "Switching..." msgstr "स्विच हो रहा है..." diff --git a/src/locales/zh/messages.po b/src/locales/zh/messages.po index c7148c8b..45cf8b1e 100644 --- a/src/locales/zh/messages.po +++ b/src/locales/zh/messages.po @@ -1791,7 +1791,6 @@ msgid "Switch to {displayName} market" msgstr "" #: src/components/trade/tradebox/deposit-modal.tsx -#: src/lib/trade/use-button-content.ts msgid "Switch to Arbitrum" msgstr "切换到 Arbitrum" @@ -1808,7 +1807,6 @@ msgid "Switch to light mode" msgstr "切换到浅色模式" #: src/components/trade/tradebox/deposit-modal.tsx -#: src/lib/trade/use-button-content.ts msgid "Switching..." msgstr "切换中..." From 6f469a8661a1a882eba9b1caf7844a171031b65c Mon Sep 17 00:00:00 2001 From: alex luu Date: Sat, 21 Mar 2026 14:22:46 -0400 Subject: [PATCH 15/18] feat: add faucet --- server/routes/api/faucet/[...path].ts | 16 ++ .../trade/components/global-modals.tsx | 2 + src/components/trade/header/top-nav.tsx | 42 ++- src/components/trade/header/user-menu.tsx | 6 +- .../trade/mobile/mobile-account-view.tsx | 24 +- .../trade/mobile/mobile-trade-view.tsx | 5 +- .../trade/tradebox/account-panel.tsx | 22 +- .../trade/tradebox/faucet-modal.tsx | 265 ++++++++++++++++++ src/components/trade/tradebox/trade-panel.tsx | 5 +- src/lib/faucet/use-faucet-claim.ts | 94 +++++++ src/locales/ar/messages.po | 63 +++++ src/locales/en/messages.po | 63 +++++ src/locales/es/messages.po | 63 +++++ src/locales/fr/messages.po | 63 +++++ src/locales/hi/messages.po | 63 +++++ src/locales/zh/messages.po | 63 +++++ src/stores/use-global-modal-store.ts | 14 + 17 files changed, 845 insertions(+), 28 deletions(-) create mode 100644 server/routes/api/faucet/[...path].ts create mode 100644 src/components/trade/tradebox/faucet-modal.tsx create mode 100644 src/lib/faucet/use-faucet-claim.ts diff --git a/server/routes/api/faucet/[...path].ts b/server/routes/api/faucet/[...path].ts new file mode 100644 index 00000000..0f2b516d --- /dev/null +++ b/server/routes/api/faucet/[...path].ts @@ -0,0 +1,16 @@ +const FAUCET_ORIGIN = "https://usdh.com"; + +export default defineEventHandler(async (event: any) => { + const url = getRequestURL(event); + const targetUrl = `${FAUCET_ORIGIN}${url.pathname}${url.search}`; + + const body = event.method !== "GET" ? await readBody(event) : undefined; + + const response = await fetch(targetUrl, { + method: event.method, + headers: { "Content-Type": "application/json" }, + body: body ? JSON.stringify(body) : undefined, + }); + + return response.json(); +}); diff --git a/src/components/trade/components/global-modals.tsx b/src/components/trade/components/global-modals.tsx index 930e0125..0c74cf13 100644 --- a/src/components/trade/components/global-modals.tsx +++ b/src/components/trade/components/global-modals.tsx @@ -2,6 +2,7 @@ import { Suspense } from "react"; import { createLazyComponent } from "@/lib/lazy"; const DepositModal = createLazyComponent(() => import("../tradebox/deposit-modal"), "DepositModal"); +const FaucetModal = createLazyComponent(() => import("../tradebox/faucet-modal"), "FaucetModal"); const GlobalSettingsDialog = createLazyComponent(() => import("./global-settings-dialog"), "GlobalSettingsDialog"); const SpotSwapModal = createLazyComponent(() => import("./spot-swap-modal"), "SpotSwapModal"); const CommandMenu = createLazyComponent(() => import("./command-menu"), "CommandMenu"); @@ -10,6 +11,7 @@ export function GlobalModals() { return ( + diff --git a/src/components/trade/header/top-nav.tsx b/src/components/trade/header/top-nav.tsx index 0543b586..6f581b81 100644 --- a/src/components/trade/header/top-nav.tsx +++ b/src/components/trade/header/top-nav.tsx @@ -1,12 +1,19 @@ import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; -import { DownloadSimpleIcon, GearIcon, TerminalIcon } from "@phosphor-icons/react"; +import { DownloadSimpleIcon, DropIcon, GearIcon, TerminalIcon } from "@phosphor-icons/react"; import { Link } from "@tanstack/react-router"; import { useConnection } from "wagmi"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/cn"; import { useExchangeScope } from "@/providers/exchange-scope"; -import { useDepositModalActions, useSettingsDialogActions } from "@/stores/use-global-modal-store"; +import { + useDepositModalActions, + useFaucetModalActions, + useSettingsDialogActions, +} from "@/stores/use-global-modal-store"; + +const isTestnet = import.meta.env.VITE_HYPERLIQUID_TESTNET === "true"; + import { ThemeToggle } from "./theme-toggle"; import { UserMenu } from "./user-menu"; @@ -30,6 +37,7 @@ function getScopeAccentClass(scope: string): string { export function TopNav() { const { open: openDepositModal } = useDepositModalActions(); + const { open: openFaucetModal } = useFaucetModalActions(); const { open: openSettingsDialog } = useSettingsDialogActions(); const { isConnected } = useConnection(); const { scope } = useExchangeScope(); @@ -71,16 +79,26 @@ export function TopNav() {
- {isConnected && ( - - )} + {isConnected && + (isTestnet ? ( + + ) : ( + + ))}
diff --git a/src/components/trade/header/user-menu.tsx b/src/components/trade/header/user-menu.tsx index 5ec00257..be2b7e3d 100644 --- a/src/components/trade/header/user-menu.tsx +++ b/src/components/trade/header/user-menu.tsx @@ -46,7 +46,7 @@ function CopyAddressMenuItem({ address }: { address: string }) { export function UserMenu() { const { address, isConnected, isConnecting } = useConnection(); - const { authenticated } = usePrivy(); + const { authenticated, ready } = usePrivy(); const { login } = useLogin(); const { logout } = useLogout(); const { disconnect } = useDisconnect(); @@ -65,7 +65,7 @@ export function UserMenu() { } } - if (!mounted || isConnecting) { + if (!mounted || !ready || isConnecting) { return ( diff --git a/src/components/trade/mobile/mobile-account-view.tsx b/src/components/trade/mobile/mobile-account-view.tsx index d2896520..ae850e54 100644 --- a/src/components/trade/mobile/mobile-account-view.tsx +++ b/src/components/trade/mobile/mobile-account-view.tsx @@ -1,4 +1,11 @@ -import { ArrowSquareOutIcon, CopyIcon, LightningIcon, SignOutIcon, WalletIcon } from "@phosphor-icons/react"; +import { + ArrowSquareOutIcon, + CopyIcon, + LightningIcon, + SignOutIcon, + SpinnerGapIcon, + WalletIcon, +} from "@phosphor-icons/react"; import { useLogin, useLogout, usePrivy } from "@privy-io/react-auth"; import { useConnection, useDisconnect } from "wagmi"; import { Badge } from "@/components/ui/badge"; @@ -21,7 +28,7 @@ interface MobileAccountViewProps { export function MobileAccountView({ className }: MobileAccountViewProps) { const { address, isConnected } = useConnection(); - const { authenticated } = usePrivy(); + const { authenticated, ready } = usePrivy(); const { login } = useLogin(); const { logout } = useLogout(); const { disconnect } = useDisconnect(); @@ -57,6 +64,17 @@ export function MobileAccountView({ className }: MobileAccountViewProps) { return sum + (pnl ?? 0); }, 0); + if (!ready) { + return ( +
+
+ +
+ +
+ ); + } + if (!isConnected) { return (
@@ -73,7 +91,7 @@ export function MobileAccountView({ className }: MobileAccountViewProps) { - + {isTestnet ? ( + + ) : ( + + )}
)}
diff --git a/src/components/trade/tradebox/faucet-modal.tsx b/src/components/trade/tradebox/faucet-modal.tsx new file mode 100644 index 00000000..979c4dfa --- /dev/null +++ b/src/components/trade/tradebox/faucet-modal.tsx @@ -0,0 +1,265 @@ +import { t } from "@lingui/core/macro"; +import { Trans } from "@lingui/react/macro"; +import { Turnstile, type TurnstileInstance } from "@marsidev/react-turnstile"; +import { + CheckCircleIcon, + ClockIcon, + CurrencyDollarIcon, + DropIcon, + SpinnerGapIcon, + WalletIcon, + WarningCircleIcon, +} from "@phosphor-icons/react"; +import { useRef, useState } from "react"; +import { useConnection } from "wagmi"; +import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { InfoRow } from "@/components/ui/info-row"; +import { cn } from "@/lib/cn"; +import { useFaucetClaim } from "@/lib/faucet/use-faucet-claim"; +import { useFaucetModalActions, useFaucetModalOpen } from "@/stores/use-global-modal-store"; + +const TURNSTILE_SITE_KEY = "0x4AAAAAACFqV43jRR_HRsJ2"; + +interface StepProps { + label: string; + active: boolean; + done: boolean; +} + +function Step({ label, active, done }: StepProps) { + return ( +
+ {done ? ( + + ) : active ? ( + + ) : ( +
+ )} + {label} +
+ ); +} + +function ClaimProgress({ status }: { status: string }) { + const steps = [ + { key: "verifying-captcha", label: t`Verifying captcha` }, + { key: "verifying-balance", label: t`Checking balance` }, + { key: "claiming", label: t`Claiming USDH` }, + ]; + const activeIdx = steps.findIndex((s) => s.key === status); + + return ( +
+ {steps.map((step, i) => ( + + ))} +
+ ); +} + +export function FaucetModal() { + const open = useFaucetModalOpen(); + const { close } = useFaucetModalActions(); + const { address } = useConnection(); + const { status, error, result, claim, reset } = useFaucetClaim(); + const [turnstileToken, setTurnstileToken] = useState(null); + const turnstileRef = useRef(null); + + const isProcessing = status === "verifying-captcha" || status === "verifying-balance" || status === "claiming"; + + function handleClose() { + reset(); + setTurnstileToken(null); + close(); + } + + function handleClaim() { + if (!turnstileToken || !address) return; + claim(turnstileToken, address); + } + + function handleRetry() { + reset(); + setTurnstileToken(null); + turnstileRef.current?.reset(); + } + + if (status === "success") { + return ( + + + + + Faucet + + +
+
+ +
+
+

+ USDH claimed! +

+

+ {result?.amount} USDH{" "} + sent to your account +

+
+ +
+
+
+ ); + } + + if (status === "error") { + return ( + + + + + Faucet + + +
+
+ +
+
+

+ Claim failed +

+ {error &&

{error}

} +
+
+ + +
+
+
+
+ ); + } + + if (isProcessing) { + return ( + {}}> + + + + Faucet + + +
+
+
+
+ +
+
+ +
+ +
+ ); + } + + return ( + + + + + Faucet + + + +
+ {!address ? ( +
+
+ +
+
+

+ Wallet not connected +

+

+ Connect your wallet to claim USDH +

+
+
+ ) : ( + <> +
+ + + Amount + + } + value="1,000 USDH" + valueClassName="font-medium" + /> + + + Requirement + + } + value={t`$5+ USDC balance`} + /> + + + Cooldown + + } + value={t`24 hours`} + /> +
+ +
+ setTurnstileToken(null)} + onError={() => setTurnstileToken(null)} + /> +
+ + + + )} +
+
+
+ ); +} diff --git a/src/components/trade/tradebox/trade-panel.tsx b/src/components/trade/tradebox/trade-panel.tsx index fb83e9a6..d9e0b10e 100644 --- a/src/components/trade/tradebox/trade-panel.tsx +++ b/src/components/trade/tradebox/trade-panel.tsx @@ -1,6 +1,6 @@ import { t } from "@lingui/core/macro"; import { SpinnerGapIcon } from "@phosphor-icons/react"; -import { useLogin } from "@privy-io/react-auth"; +import { useLogin, usePrivy } from "@privy-io/react-auth"; import { useCallback, useEffect, useId, useMemo, useState } from "react"; import { useConnection } from "wagmi"; import { Button } from "@/components/ui/button"; @@ -67,6 +67,7 @@ export function TradePanel() { const { address, isConnected } = useConnection(); const { login } = useLogin(); + const { authenticated } = usePrivy(); const { data: market } = useSelectedMarketInfo(); @@ -423,7 +424,7 @@ export function TradePanel() { canApprove, side, isSubmitting, - onConnectWallet: login, + onConnectWallet: () => !authenticated && login(), onDeposit: () => openDepositModal("deposit"), onRegister: handleRegister, onSubmit: handleSubmit, diff --git a/src/lib/faucet/use-faucet-claim.ts b/src/lib/faucet/use-faucet-claim.ts new file mode 100644 index 00000000..e68a1322 --- /dev/null +++ b/src/lib/faucet/use-faucet-claim.ts @@ -0,0 +1,94 @@ +import { useState } from "react"; + +type FaucetStatus = "idle" | "verifying-captcha" | "verifying-balance" | "claiming" | "success" | "error"; + +interface FaucetResult { + amount: string; + txHash?: string; +} + +interface UseFaucetClaimReturn { + status: FaucetStatus; + error: string | null; + result: FaucetResult | null; + claim: (turnstileToken: string, address: string) => Promise; + reset: () => void; +} + +async function postFaucet(path: string, body: Record): Promise { + const res = await fetch(`/api/faucet/${path}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + return res.json(); +} + +export function useFaucetClaim(): UseFaucetClaimReturn { + const [status, setStatus] = useState("idle"); + const [error, setError] = useState(null); + const [result, setResult] = useState(null); + + async function claim(turnstileToken: string, address: string) { + setStatus("verifying-captcha"); + setError(null); + setResult(null); + + try { + const turnstileData = await postFaucet<{ success: boolean; sessionToken?: string; error?: string }>( + "verify-turnstile", + { token: turnstileToken }, + ); + if (!turnstileData.success || !turnstileData.sessionToken) + throw new Error(turnstileData.error || "Captcha verification failed"); + const sessionToken = turnstileData.sessionToken; + + setStatus("verifying-balance"); + const balanceData = await postFaucet<{ + success: boolean; + hasMinimumBalance?: boolean; + totalBalance?: string; + required?: string; + error?: string; + }>("verify-balance", { address, sessionToken }); + if (!balanceData.success) throw new Error(balanceData.error || "Balance check failed"); + if (!balanceData.hasMinimumBalance) + throw new Error(`Insufficient balance: $${balanceData.totalBalance} (need $${balanceData.required})`); + + setStatus("claiming"); + const claimData = await postFaucet<{ + success: boolean; + amount?: string; + txHash?: string; + error?: string; + nextClaimTime?: number; + }>("claim", { + recipientAddress: address, + sessionToken, + authMethod: "wallet", + walletAddress: address, + }); + if (!claimData.success) { + if (claimData.nextClaimTime) { + const hours = Math.max(1, Math.ceil((claimData.nextClaimTime * 1000 - Date.now()) / (1000 * 60 * 60))); + throw new Error(`Cooldown active. Try again in ~${hours}h`); + } + throw new Error(claimData.error || "Claim failed"); + } + + setResult({ amount: claimData.amount || "1,000", txHash: claimData.txHash }); + setStatus("success"); + } catch (err) { + setError(err instanceof Error ? err.message : "Something went wrong"); + setStatus("error"); + } + } + + function reset() { + setStatus("idle"); + setError(null); + setResult(null); + } + + return { status, error, result, claim, reset }; +} diff --git a/src/locales/ar/messages.po b/src/locales/ar/messages.po index 32d7f42b..2e3093c5 100644 --- a/src/locales/ar/messages.po +++ b/src/locales/ar/messages.po @@ -17,6 +17,14 @@ msgstr "" msgid "% filled" msgstr "% مكتمل" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "$5+ USDC balance" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "24 hours" +msgstr "" + #: src/components/trade/market-overview.tsx msgid "24H" msgstr "" @@ -107,6 +115,7 @@ msgstr "" #: src/components/trade/positions/send-dialog.tsx #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Amount" msgstr "" @@ -226,6 +235,7 @@ msgstr "شراء" #: src/components/trade/positions/twap-tab.tsx #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx #: src/components/trade/tradebox/margin-mode-dialog.tsx msgid "Cancel" @@ -277,6 +287,22 @@ msgstr "" msgid "Chart" msgstr "الرسم البياني" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Checking balance" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claim 1,000 USDH" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claim failed" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claiming USDH" +msgstr "" + #: src/components/trade/positions/position-actions-dropdown.tsx #: src/components/ui/dialog.tsx #: src/components/ui/sheet.tsx @@ -335,6 +361,10 @@ msgstr "ربط المحفظة" msgid "Connect wallet to view account" msgstr "اربط المحفظة لعرض الحساب" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Connect your wallet to claim USDH" +msgstr "" + #: src/components/pages/builder-page.tsx #~ msgid "Connect your wallet to register a builder code" #~ msgstr "" @@ -391,6 +421,10 @@ msgstr "" msgid "Connecting..." msgstr "جارٍ الاتصال..." +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Cooldown" +msgstr "" + #: src/hooks/ui/use-copy-to-clipboard.ts msgid "Copied to clipboard" msgstr "" @@ -504,6 +538,7 @@ msgstr "يعرض الشريط الجانبي للجوال." #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Done" msgstr "" @@ -664,6 +699,16 @@ msgstr "" #~ msgid "Failed to switch margin mode" #~ msgstr "" +#: src/components/trade/header/top-nav.tsx +#: src/components/trade/tradebox/account-panel.tsx +#: src/components/trade/tradebox/account-panel.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Faucet" +msgstr "" + #: src/components/trade/positions/history-tab.tsx msgid "Fee" msgstr "الرسوم" @@ -1394,12 +1439,17 @@ msgstr "" msgid "Remove from favorites" msgstr "إزالة من المفضلة" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Requirement" +msgstr "" + #: src/components/pages/builder-page.tsx #~ msgid "Requirements:" #~ msgstr "" #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx msgid "Retry" msgstr "" @@ -1517,6 +1567,10 @@ msgstr "" msgid "sent to Hyperliquid" msgstr "" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "sent to your account" +msgstr "" + #: src/components/trade/tradebox/trade-form-fields.tsx #~ msgid "Set {p}%" #~ msgstr "تعيين {p}%" @@ -2051,6 +2105,10 @@ msgstr "تحديث مباشر" msgid "USD Value" msgstr "القيمة بالدولار" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "USDH claimed!" +msgstr "" + #: src/components/trade/positions/positions-tab.tsx #~ msgid "Value" #~ msgstr "القيمة" @@ -2059,6 +2117,10 @@ msgstr "القيمة بالدولار" #~ msgid "Vaults" #~ msgstr "الخزائن" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Verifying captcha" +msgstr "" + #: src/lib/trade/use-button-content.ts msgid "Verifying..." msgstr "جارٍ التحقق..." @@ -2092,6 +2154,7 @@ msgstr "في انتظار الصفقات..." #~ msgstr "عميل المحفظة غير جاهز" #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Wallet not connected" msgstr "المحفظة غير متصلة" diff --git a/src/locales/en/messages.po b/src/locales/en/messages.po index 713a453d..7ae91607 100644 --- a/src/locales/en/messages.po +++ b/src/locales/en/messages.po @@ -17,6 +17,14 @@ msgstr "" msgid "% filled" msgstr "% filled" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "$5+ USDC balance" +msgstr "$5+ USDC balance" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "24 hours" +msgstr "24 hours" + #: src/components/trade/market-overview.tsx msgid "24H" msgstr "24H" @@ -107,6 +115,7 @@ msgstr "All cross positions share the same cross margin as collateral. In the ev #: src/components/trade/positions/send-dialog.tsx #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Amount" msgstr "Amount" @@ -226,6 +235,7 @@ msgstr "Buy" #: src/components/trade/positions/twap-tab.tsx #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx #: src/components/trade/tradebox/margin-mode-dialog.tsx msgid "Cancel" @@ -277,6 +287,22 @@ msgstr "Change leverage" msgid "Chart" msgstr "Chart" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Checking balance" +msgstr "Checking balance" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claim 1,000 USDH" +msgstr "Claim 1,000 USDH" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claim failed" +msgstr "Claim failed" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claiming USDH" +msgstr "Claiming USDH" + #: src/components/trade/positions/position-actions-dropdown.tsx #: src/components/ui/dialog.tsx #: src/components/ui/sheet.tsx @@ -335,6 +361,10 @@ msgstr "Connect Wallet" msgid "Connect wallet to view account" msgstr "Connect wallet to view account" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Connect your wallet to claim USDH" +msgstr "Connect your wallet to claim USDH" + #: src/components/pages/builder-page.tsx #~ msgid "Connect your wallet to register a builder code" #~ msgstr "Connect your wallet to register a builder code" @@ -391,6 +421,10 @@ msgstr "Connecting" msgid "Connecting..." msgstr "Connecting..." +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Cooldown" +msgstr "Cooldown" + #: src/hooks/ui/use-copy-to-clipboard.ts msgid "Copied to clipboard" msgstr "Copied to clipboard" @@ -504,6 +538,7 @@ msgstr "Displays the mobile sidebar." #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Done" msgstr "Done" @@ -664,6 +699,16 @@ msgstr "Failed to reverse position" #~ msgid "Failed to switch margin mode" #~ msgstr "Failed to switch margin mode" +#: src/components/trade/header/top-nav.tsx +#: src/components/trade/tradebox/account-panel.tsx +#: src/components/trade/tradebox/account-panel.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Faucet" +msgstr "Faucet" + #: src/components/trade/positions/history-tab.tsx msgid "Fee" msgstr "Fee" @@ -1398,12 +1443,17 @@ msgstr "Remove {displayName} from favorites" msgid "Remove from favorites" msgstr "Remove from favorites" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Requirement" +msgstr "Requirement" + #: src/components/pages/builder-page.tsx #~ msgid "Requirements:" #~ msgstr "Requirements:" #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx msgid "Retry" msgstr "Retry" @@ -1521,6 +1571,10 @@ msgstr "Sending..." msgid "sent to Hyperliquid" msgstr "sent to Hyperliquid" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "sent to your account" +msgstr "sent to your account" + #: src/components/trade/tradebox/trade-form-fields.tsx #~ msgid "Set {p}%" #~ msgstr "Set {p}%" @@ -2059,6 +2113,10 @@ msgstr "Updated live" msgid "USD Value" msgstr "USD Value" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "USDH claimed!" +msgstr "USDH claimed!" + #: src/components/trade/positions/positions-tab.tsx #~ msgid "Value" #~ msgstr "Value" @@ -2067,6 +2125,10 @@ msgstr "USD Value" #~ msgid "Vaults" #~ msgstr "Vaults" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Verifying captcha" +msgstr "Verifying captcha" + #: src/lib/trade/use-button-content.ts msgid "Verifying..." msgstr "Verifying..." @@ -2100,6 +2162,7 @@ msgstr "Waiting for trades..." #~ msgstr "Wallet client not ready" #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Wallet not connected" msgstr "Wallet not connected" diff --git a/src/locales/es/messages.po b/src/locales/es/messages.po index ba93ad43..a78b5aae 100644 --- a/src/locales/es/messages.po +++ b/src/locales/es/messages.po @@ -17,6 +17,14 @@ msgstr "" msgid "% filled" msgstr "% ejecutado" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "$5+ USDC balance" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "24 hours" +msgstr "" + #: src/components/trade/market-overview.tsx msgid "24H" msgstr "" @@ -107,6 +115,7 @@ msgstr "" #: src/components/trade/positions/send-dialog.tsx #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Amount" msgstr "" @@ -226,6 +235,7 @@ msgstr "Comprar" #: src/components/trade/positions/twap-tab.tsx #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx #: src/components/trade/tradebox/margin-mode-dialog.tsx msgid "Cancel" @@ -277,6 +287,22 @@ msgstr "" msgid "Chart" msgstr "Gráfico" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Checking balance" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claim 1,000 USDH" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claim failed" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claiming USDH" +msgstr "" + #: src/components/trade/positions/position-actions-dropdown.tsx #: src/components/ui/dialog.tsx #: src/components/ui/sheet.tsx @@ -335,6 +361,10 @@ msgstr "Conectar billetera" msgid "Connect wallet to view account" msgstr "Conecta billetera para ver cuenta" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Connect your wallet to claim USDH" +msgstr "" + #: src/components/pages/builder-page.tsx #~ msgid "Connect your wallet to register a builder code" #~ msgstr "" @@ -391,6 +421,10 @@ msgstr "" msgid "Connecting..." msgstr "Conectando..." +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Cooldown" +msgstr "" + #: src/hooks/ui/use-copy-to-clipboard.ts msgid "Copied to clipboard" msgstr "" @@ -504,6 +538,7 @@ msgstr "Muestra la barra lateral móvil." #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Done" msgstr "" @@ -664,6 +699,16 @@ msgstr "" #~ msgid "Failed to switch margin mode" #~ msgstr "" +#: src/components/trade/header/top-nav.tsx +#: src/components/trade/tradebox/account-panel.tsx +#: src/components/trade/tradebox/account-panel.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Faucet" +msgstr "" + #: src/components/trade/positions/history-tab.tsx msgid "Fee" msgstr "Comisión" @@ -1394,12 +1439,17 @@ msgstr "" msgid "Remove from favorites" msgstr "Quitar de favoritos" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Requirement" +msgstr "" + #: src/components/pages/builder-page.tsx #~ msgid "Requirements:" #~ msgstr "" #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx msgid "Retry" msgstr "" @@ -1517,6 +1567,10 @@ msgstr "" msgid "sent to Hyperliquid" msgstr "" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "sent to your account" +msgstr "" + #: src/components/trade/tradebox/trade-form-fields.tsx #~ msgid "Set {p}%" #~ msgstr "Establecer {p}%" @@ -2051,6 +2105,10 @@ msgstr "Actualizado en vivo" msgid "USD Value" msgstr "Valor en USD" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "USDH claimed!" +msgstr "" + #: src/components/trade/positions/positions-tab.tsx #~ msgid "Value" #~ msgstr "Valor" @@ -2059,6 +2117,10 @@ msgstr "Valor en USD" #~ msgid "Vaults" #~ msgstr "Bóvedas" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Verifying captcha" +msgstr "" + #: src/lib/trade/use-button-content.ts msgid "Verifying..." msgstr "" @@ -2092,6 +2154,7 @@ msgstr "Esperando operaciones..." #~ msgstr "Cliente de billetera no está listo" #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Wallet not connected" msgstr "Billetera no conectada" diff --git a/src/locales/fr/messages.po b/src/locales/fr/messages.po index 33dfa793..d75b7640 100644 --- a/src/locales/fr/messages.po +++ b/src/locales/fr/messages.po @@ -17,6 +17,14 @@ msgstr "" msgid "% filled" msgstr "% exécuté" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "$5+ USDC balance" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "24 hours" +msgstr "" + #: src/components/trade/market-overview.tsx msgid "24H" msgstr "" @@ -107,6 +115,7 @@ msgstr "" #: src/components/trade/positions/send-dialog.tsx #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Amount" msgstr "" @@ -226,6 +235,7 @@ msgstr "Acheter" #: src/components/trade/positions/twap-tab.tsx #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx #: src/components/trade/tradebox/margin-mode-dialog.tsx msgid "Cancel" @@ -277,6 +287,22 @@ msgstr "" msgid "Chart" msgstr "Graphique" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Checking balance" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claim 1,000 USDH" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claim failed" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claiming USDH" +msgstr "" + #: src/components/trade/positions/position-actions-dropdown.tsx #: src/components/ui/dialog.tsx #: src/components/ui/sheet.tsx @@ -335,6 +361,10 @@ msgstr "Connecter le portefeuille" msgid "Connect wallet to view account" msgstr "Connectez le portefeuille pour voir le compte" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Connect your wallet to claim USDH" +msgstr "" + #: src/components/pages/builder-page.tsx #~ msgid "Connect your wallet to register a builder code" #~ msgstr "" @@ -391,6 +421,10 @@ msgstr "" msgid "Connecting..." msgstr "Connexion..." +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Cooldown" +msgstr "" + #: src/hooks/ui/use-copy-to-clipboard.ts msgid "Copied to clipboard" msgstr "" @@ -504,6 +538,7 @@ msgstr "Affiche la barre latérale mobile." #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Done" msgstr "" @@ -664,6 +699,16 @@ msgstr "" #~ msgid "Failed to switch margin mode" #~ msgstr "" +#: src/components/trade/header/top-nav.tsx +#: src/components/trade/tradebox/account-panel.tsx +#: src/components/trade/tradebox/account-panel.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Faucet" +msgstr "" + #: src/components/trade/positions/history-tab.tsx msgid "Fee" msgstr "Frais" @@ -1394,12 +1439,17 @@ msgstr "" msgid "Remove from favorites" msgstr "Retirer des favoris" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Requirement" +msgstr "" + #: src/components/pages/builder-page.tsx #~ msgid "Requirements:" #~ msgstr "" #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx msgid "Retry" msgstr "" @@ -1517,6 +1567,10 @@ msgstr "" msgid "sent to Hyperliquid" msgstr "" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "sent to your account" +msgstr "" + #: src/components/trade/tradebox/trade-form-fields.tsx #~ msgid "Set {p}%" #~ msgstr "Définir {p}%" @@ -2051,6 +2105,10 @@ msgstr "Mise à jour en direct" msgid "USD Value" msgstr "Valeur en USD" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "USDH claimed!" +msgstr "" + #: src/components/trade/positions/positions-tab.tsx #~ msgid "Value" #~ msgstr "Valeur" @@ -2059,6 +2117,10 @@ msgstr "Valeur en USD" #~ msgid "Vaults" #~ msgstr "Coffres" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Verifying captcha" +msgstr "" + #: src/lib/trade/use-button-content.ts msgid "Verifying..." msgstr "" @@ -2092,6 +2154,7 @@ msgstr "En attente des transactions..." #~ msgstr "Client du portefeuille non prêt" #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Wallet not connected" msgstr "Portefeuille non connecté" diff --git a/src/locales/hi/messages.po b/src/locales/hi/messages.po index 655a0957..51ef4f7c 100644 --- a/src/locales/hi/messages.po +++ b/src/locales/hi/messages.po @@ -17,6 +17,14 @@ msgstr "" msgid "% filled" msgstr "% पूर्ण" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "$5+ USDC balance" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "24 hours" +msgstr "" + #: src/components/trade/market-overview.tsx msgid "24H" msgstr "" @@ -107,6 +115,7 @@ msgstr "" #: src/components/trade/positions/send-dialog.tsx #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Amount" msgstr "" @@ -226,6 +235,7 @@ msgstr "खरीदें" #: src/components/trade/positions/twap-tab.tsx #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx #: src/components/trade/tradebox/margin-mode-dialog.tsx msgid "Cancel" @@ -277,6 +287,22 @@ msgstr "" msgid "Chart" msgstr "चार्ट" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Checking balance" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claim 1,000 USDH" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claim failed" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claiming USDH" +msgstr "" + #: src/components/trade/positions/position-actions-dropdown.tsx #: src/components/ui/dialog.tsx #: src/components/ui/sheet.tsx @@ -335,6 +361,10 @@ msgstr "वॉलेट कनेक्ट करें" msgid "Connect wallet to view account" msgstr "खाता देखने के लिए वॉलेट कनेक्ट करें" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Connect your wallet to claim USDH" +msgstr "" + #: src/components/pages/builder-page.tsx #~ msgid "Connect your wallet to register a builder code" #~ msgstr "" @@ -391,6 +421,10 @@ msgstr "" msgid "Connecting..." msgstr "कनेक्ट हो रहा है..." +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Cooldown" +msgstr "" + #: src/hooks/ui/use-copy-to-clipboard.ts msgid "Copied to clipboard" msgstr "" @@ -504,6 +538,7 @@ msgstr "मोबाइल साइडबार दिखाता है।" #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Done" msgstr "" @@ -664,6 +699,16 @@ msgstr "" #~ msgid "Failed to switch margin mode" #~ msgstr "" +#: src/components/trade/header/top-nav.tsx +#: src/components/trade/tradebox/account-panel.tsx +#: src/components/trade/tradebox/account-panel.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Faucet" +msgstr "" + #: src/components/trade/positions/history-tab.tsx msgid "Fee" msgstr "शुल्क" @@ -1394,12 +1439,17 @@ msgstr "" msgid "Remove from favorites" msgstr "पसंदीदा से हटाएं" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Requirement" +msgstr "" + #: src/components/pages/builder-page.tsx #~ msgid "Requirements:" #~ msgstr "" #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx msgid "Retry" msgstr "" @@ -1517,6 +1567,10 @@ msgstr "" msgid "sent to Hyperliquid" msgstr "" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "sent to your account" +msgstr "" + #: src/components/trade/tradebox/trade-form-fields.tsx #~ msgid "Set {p}%" #~ msgstr "{p}% सेट करें" @@ -2051,6 +2105,10 @@ msgstr "लाइव अपडेट" msgid "USD Value" msgstr "USD मूल्य" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "USDH claimed!" +msgstr "" + #: src/components/trade/positions/positions-tab.tsx #~ msgid "Value" #~ msgstr "वैल्यू" @@ -2059,6 +2117,10 @@ msgstr "USD मूल्य" #~ msgid "Vaults" #~ msgstr "वॉल्ट" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Verifying captcha" +msgstr "" + #: src/lib/trade/use-button-content.ts msgid "Verifying..." msgstr "" @@ -2092,6 +2154,7 @@ msgstr "ट्रेड की प्रतीक्षा..." #~ msgstr "वॉलेट क्लाइंट तैयार नहीं है" #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Wallet not connected" msgstr "वॉलेट कनेक्ट नहीं है" diff --git a/src/locales/zh/messages.po b/src/locales/zh/messages.po index 45cf8b1e..5c1091e6 100644 --- a/src/locales/zh/messages.po +++ b/src/locales/zh/messages.po @@ -17,6 +17,14 @@ msgstr "" msgid "% filled" msgstr "% 已成交" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "$5+ USDC balance" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "24 hours" +msgstr "" + #: src/components/trade/market-overview.tsx msgid "24H" msgstr "" @@ -107,6 +115,7 @@ msgstr "" #: src/components/trade/positions/send-dialog.tsx #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Amount" msgstr "" @@ -226,6 +235,7 @@ msgstr "买入" #: src/components/trade/positions/twap-tab.tsx #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx #: src/components/trade/tradebox/margin-mode-dialog.tsx msgid "Cancel" @@ -277,6 +287,22 @@ msgstr "" msgid "Chart" msgstr "图表" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Checking balance" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claim 1,000 USDH" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claim failed" +msgstr "" + +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Claiming USDH" +msgstr "" + #: src/components/trade/positions/position-actions-dropdown.tsx #: src/components/ui/dialog.tsx #: src/components/ui/sheet.tsx @@ -335,6 +361,10 @@ msgstr "连接钱包" msgid "Connect wallet to view account" msgstr "连接钱包查看账户" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Connect your wallet to claim USDH" +msgstr "" + #: src/components/pages/builder-page.tsx #~ msgid "Connect your wallet to register a builder code" #~ msgstr "" @@ -391,6 +421,10 @@ msgstr "" msgid "Connecting..." msgstr "连接中..." +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Cooldown" +msgstr "" + #: src/hooks/ui/use-copy-to-clipboard.ts msgid "Copied to clipboard" msgstr "" @@ -504,6 +538,7 @@ msgstr "显示移动端侧边栏。" #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Done" msgstr "" @@ -664,6 +699,16 @@ msgstr "" #~ msgid "Failed to switch margin mode" #~ msgstr "" +#: src/components/trade/header/top-nav.tsx +#: src/components/trade/tradebox/account-panel.tsx +#: src/components/trade/tradebox/account-panel.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Faucet" +msgstr "" + #: src/components/trade/positions/history-tab.tsx msgid "Fee" msgstr "手续费" @@ -1394,12 +1439,17 @@ msgstr "" msgid "Remove from favorites" msgstr "从收藏中移除" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Requirement" +msgstr "" + #: src/components/pages/builder-page.tsx #~ msgid "Requirements:" #~ msgstr "" #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx msgid "Retry" msgstr "" @@ -1517,6 +1567,10 @@ msgstr "" msgid "sent to Hyperliquid" msgstr "" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "sent to your account" +msgstr "" + #: src/components/trade/tradebox/trade-form-fields.tsx #~ msgid "Set {p}%" #~ msgstr "设置 {p}%" @@ -2051,6 +2105,10 @@ msgstr "实时更新" msgid "USD Value" msgstr "美元价值" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "USDH claimed!" +msgstr "" + #: src/components/trade/positions/positions-tab.tsx #~ msgid "Value" #~ msgstr "价值" @@ -2059,6 +2117,10 @@ msgstr "美元价值" #~ msgid "Vaults" #~ msgstr "金库" +#: src/components/trade/tradebox/faucet-modal.tsx +msgid "Verifying captcha" +msgstr "" + #: src/lib/trade/use-button-content.ts msgid "Verifying..." msgstr "" @@ -2092,6 +2154,7 @@ msgstr "等待成交记录..." #~ msgstr "钱包客户端未就绪" #: src/components/trade/tradebox/deposit-modal.tsx +#: src/components/trade/tradebox/faucet-modal.tsx msgid "Wallet not connected" msgstr "钱包未连接" diff --git a/src/stores/use-global-modal-store.ts b/src/stores/use-global-modal-store.ts index 51cb24f1..3738cbf6 100644 --- a/src/stores/use-global-modal-store.ts +++ b/src/stores/use-global-modal-store.ts @@ -8,6 +8,7 @@ type GlobalModal = | { type: "settings" } | { type: "swap"; fromToken: string; toToken?: string } | { type: "commandMenu" } + | { type: "faucet" } | null; interface DepositActions { @@ -31,12 +32,18 @@ interface CommandMenuActions { close: () => void; } +interface FaucetActions { + open: () => void; + close: () => void; +} + interface GlobalModalState { modal: GlobalModal; depositActions: DepositActions; settingsActions: SettingsActions; swapActions: SwapActions; commandMenuActions: CommandMenuActions; + faucetActions: FaucetActions; } const useGlobalModalStore = create((set) => { @@ -61,6 +68,10 @@ const useGlobalModalStore = create((set) => { open: () => set({ modal: { type: "commandMenu" } }), close, }, + faucetActions: { + open: () => set({ modal: { type: "faucet" } }), + close, + }, }; }); @@ -81,3 +92,6 @@ export const useSwapModalActions = () => useGlobalModalStore((s) => s.swapAction export const useCommandMenuOpen = () => useGlobalModalStore((s) => s.modal?.type === "commandMenu"); export const useCommandMenuActions = () => useGlobalModalStore((s) => s.commandMenuActions); + +export const useFaucetModalOpen = () => useGlobalModalStore((s) => s.modal?.type === "faucet"); +export const useFaucetModalActions = () => useGlobalModalStore((s) => s.faucetActions); From ef1b36d8cb0dc580c1f235c78ddf381d2e7df772 Mon Sep 17 00:00:00 2001 From: alex luu Date: Sat, 21 Mar 2026 14:37:18 -0400 Subject: [PATCH 16/18] feat: add turnstil --- .env.example | 2 ++ src/components/trade/tradebox/faucet-modal.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 8a6cf76d..14b85c21 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ VITE_PRIVY_APP_ID=your-privy-app-id # Set to "true" for Hyperliquid testnet, "false" for mainnet VITE_HYPERLIQUID_TESTNET=true + +TURNSTILE_SITE_KEY=use-turnstile-key-from-usdh \ No newline at end of file diff --git a/src/components/trade/tradebox/faucet-modal.tsx b/src/components/trade/tradebox/faucet-modal.tsx index 979c4dfa..9baef7db 100644 --- a/src/components/trade/tradebox/faucet-modal.tsx +++ b/src/components/trade/tradebox/faucet-modal.tsx @@ -19,7 +19,7 @@ import { cn } from "@/lib/cn"; import { useFaucetClaim } from "@/lib/faucet/use-faucet-claim"; import { useFaucetModalActions, useFaucetModalOpen } from "@/stores/use-global-modal-store"; -const TURNSTILE_SITE_KEY = "0x4AAAAAACFqV43jRR_HRsJ2"; +const TURNSTILE_SITE_KEY = import.meta.env.VITE_TURNSTILE_SITE_KEY; interface StepProps { label: string; From 30028769dcb0a934802f107b963ad97baff527fe Mon Sep 17 00:00:00 2001 From: alex luu Date: Mon, 23 Mar 2026 12:08:19 -0400 Subject: [PATCH 17/18] feat: remove email signin --- src/config/privy.ts | 2 +- src/hooks/use-privy-wagmi-sync.ts | 42 ++++++++++++++++++++++++++++++- src/styles.css | 1 + vite.config.ts | 3 +++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/config/privy.ts b/src/config/privy.ts index e37cd90c..1d715dd7 100644 --- a/src/config/privy.ts +++ b/src/config/privy.ts @@ -2,7 +2,7 @@ import type { PrivyClientConfig } from "@privy-io/react-auth"; import { arbitrum, arbitrumSepolia } from "wagmi/chains"; export const privyConfig: PrivyClientConfig = { - loginMethods: ["email", "google", "wallet"], + loginMethods: ["google", "wallet"], embeddedWallets: { ethereum: { createOnLogin: "users-without-wallets", diff --git a/src/hooks/use-privy-wagmi-sync.ts b/src/hooks/use-privy-wagmi-sync.ts index 4ca74972..5ad1a19b 100644 --- a/src/hooks/use-privy-wagmi-sync.ts +++ b/src/hooks/use-privy-wagmi-sync.ts @@ -2,6 +2,16 @@ import { usePrivy } from "@privy-io/react-auth"; import { useEffect, useRef } from "react"; import { useConnection, useDisconnect } from "wagmi"; +const PRIVY_READY_TIMEOUT_MS = 8_000; +const EMBEDDED_WALLET_PROXY_TIMEOUT_MS = 5_000; + +function clearPrivyStorage() { + const keysToRemove = Object.keys(localStorage).filter((k) => k.startsWith("privy:") || k.startsWith("privy-")); + for (const key of keysToRemove) { + localStorage.removeItem(key); + } +} + /** * Keeps Privy auth and wagmi wallet state in sync. * @@ -15,9 +25,19 @@ import { useConnection, useDisconnect } from "wagmi"; * The check runs only when Privy becomes ready (once on init), not reactively * on auth/connection changes — otherwise it would disconnect wagmi mid-login * before Privy finishes the wallet authentication handshake. + * + * If Privy does not become ready within PRIVY_READY_TIMEOUT_MS, its stored + * session data is cleared and the page reloads. This recovers from stale or + * corrupted tokens that silently block initialization. + * + * If Privy is ready+authenticated but wagmi stays disconnected beyond + * EMBEDDED_WALLET_PROXY_TIMEOUT_MS, the embedded wallet proxy failed to + * initialize (typically Firefox dynamic state partitioning isolating the + * auth.privy.io iframe's storage). Logging out and reloading clears the + * broken embedded wallet state so the next login initializes fresh. */ export function usePrivyWagmiSync() { - const { ready, authenticated } = usePrivy(); + const { ready, authenticated, logout } = usePrivy(); const { isConnected } = useConnection(); const { mutate: disconnect } = useDisconnect(); @@ -32,4 +52,24 @@ export function usePrivyWagmiSync() { disconnect(); } }, [ready, disconnect]); + + useEffect(() => { + if (ready) return; + const timer = setTimeout(() => { + clearPrivyStorage(); + window.location.reload(); + }, PRIVY_READY_TIMEOUT_MS); + return () => clearTimeout(timer); + }, [ready]); + + useEffect(() => { + if (!ready || !authenticated || isConnected) return; + const timer = setTimeout(() => { + logout().finally(() => { + clearPrivyStorage(); + window.location.reload(); + }); + }, EMBEDDED_WALLET_PROXY_TIMEOUT_MS); + return () => clearTimeout(timer); + }, [ready, authenticated, isConnected, logout]); } diff --git a/src/styles.css b/src/styles.css index 2c8183c0..4835db8d 100644 --- a/src/styles.css +++ b/src/styles.css @@ -472,3 +472,4 @@ } } } + diff --git a/vite.config.ts b/vite.config.ts index 75ec10c5..eb1c89ed 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -145,6 +145,9 @@ const config = defineConfig({ }, }, routeRules: { + '/api/faucet/**': { + proxy: 'https://usdh.com/api/faucet/**', + }, '/assets/**': { headers: { 'cache-control': 'public, max-age=31536000, immutable' }, }, From 022cc78a90a146430d37a8045a878fbe6d78efe6 Mon Sep 17 00:00:00 2001 From: alex luu Date: Tue, 24 Mar 2026 22:47:42 -0400 Subject: [PATCH 18/18] feat: integrate points --- .env.example | 5 +- .../trade/components/global-modals.tsx | 2 + src/components/trade/header/top-nav.tsx | 45 ++- .../trade/tradebox/points-modal.tsx | 344 ++++++++++++++++++ src/locales/ar/messages.po | 66 +++- src/locales/en/messages.po | 66 +++- src/locales/es/messages.po | 66 +++- src/locales/fr/messages.po | 66 +++- src/locales/hi/messages.po | 66 +++- src/locales/zh/messages.po | 66 +++- src/stores/use-global-modal-store.ts | 14 + 11 files changed, 771 insertions(+), 35 deletions(-) create mode 100644 src/components/trade/tradebox/points-modal.tsx diff --git a/.env.example b/.env.example index 14b85c21..3d51cbaf 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,7 @@ VITE_PRIVY_APP_ID=your-privy-app-id # Set to "true" for Hyperliquid testnet, "false" for mainnet VITE_HYPERLIQUID_TESTNET=true -TURNSTILE_SITE_KEY=use-turnstile-key-from-usdh \ No newline at end of file +VITE_TURNSTILE_SITE_KEY=use-turnstile-key-from-usdh + +# Hypermiles points API +VITE_HYPERMILES_API_URL=http://localhost:3001 \ No newline at end of file diff --git a/src/components/trade/components/global-modals.tsx b/src/components/trade/components/global-modals.tsx index 0c74cf13..421a3225 100644 --- a/src/components/trade/components/global-modals.tsx +++ b/src/components/trade/components/global-modals.tsx @@ -6,6 +6,7 @@ const FaucetModal = createLazyComponent(() => import("../tradebox/faucet-modal") const GlobalSettingsDialog = createLazyComponent(() => import("./global-settings-dialog"), "GlobalSettingsDialog"); const SpotSwapModal = createLazyComponent(() => import("./spot-swap-modal"), "SpotSwapModal"); const CommandMenu = createLazyComponent(() => import("./command-menu"), "CommandMenu"); +const PointsModal = createLazyComponent(() => import("../tradebox/points-modal"), "PointsModal"); export function GlobalModals() { return ( @@ -15,6 +16,7 @@ export function GlobalModals() { + ); } diff --git a/src/components/trade/header/top-nav.tsx b/src/components/trade/header/top-nav.tsx index 6f581b81..06f00281 100644 --- a/src/components/trade/header/top-nav.tsx +++ b/src/components/trade/header/top-nav.tsx @@ -1,6 +1,6 @@ import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; -import { DownloadSimpleIcon, DropIcon, GearIcon, TerminalIcon } from "@phosphor-icons/react"; +import { DownloadSimpleIcon, DropIcon, GearIcon, TerminalIcon, TrophyIcon } from "@phosphor-icons/react"; import { Link } from "@tanstack/react-router"; import { useConnection } from "wagmi"; import { Button } from "@/components/ui/button"; @@ -9,6 +9,7 @@ import { useExchangeScope } from "@/providers/exchange-scope"; import { useDepositModalActions, useFaucetModalActions, + usePointsModalActions, useSettingsDialogActions, } from "@/stores/use-global-modal-store"; @@ -38,6 +39,7 @@ function getScopeAccentClass(scope: string): string { export function TopNav() { const { open: openDepositModal } = useDepositModalActions(); const { open: openFaucetModal } = useFaucetModalActions(); + const { open: openPointsModal } = usePointsModalActions(); const { open: openSettingsDialog } = useSettingsDialogActions(); const { isConnected } = useConnection(); const { scope } = useExchangeScope(); @@ -79,26 +81,37 @@ export function TopNav() {
- {isConnected && - (isTestnet ? ( + {isConnected && ( + <> - ) : ( - - ))} + {isTestnet ? ( + + ) : ( + + )} + + )}
diff --git a/src/components/trade/tradebox/points-modal.tsx b/src/components/trade/tradebox/points-modal.tsx new file mode 100644 index 00000000..9be2c766 --- /dev/null +++ b/src/components/trade/tradebox/points-modal.tsx @@ -0,0 +1,344 @@ +import { Trans } from "@lingui/react/macro"; +import { + ArrowRightIcon, + CheckIcon, + CopyIcon, + LightningIcon, + SpinnerGapIcon, + TrophyIcon, + UsersIcon, + WarningCircleIcon, +} from "@phosphor-icons/react"; +import { usePrivy } from "@privy-io/react-auth"; +import { useEffect, useId, useState } from "react"; +import { useConnection } from "wagmi"; +import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { InfoRow } from "@/components/ui/info-row"; +import { Input } from "@/components/ui/input"; +import { useCopyToClipboard } from "@/hooks/ui/use-copy-to-clipboard"; +import { usePointsModalActions, usePointsModalOpen } from "@/stores/use-global-modal-store"; + +const API_URL = import.meta.env.VITE_HYPERMILES_API_URL; + +type ModalView = "loading" | "signup" | "summary" | "error"; + +interface UserPoints { + walletAddress: string; + totalPoints: string; + rank: number; + referralCode: string; + firstOrderAt: string | null; + lastOrderAt: string | null; +} + +interface EarnMethodProps { + icon: React.ReactNode; + title: React.ReactNode; + description: React.ReactNode; +} + +function EarnMethod({ icon, title, description }: EarnMethodProps) { + return ( +
+
+ {icon} +
+
+

{title}

+

{description}

+
+
+ ); +} + +function CopyableCode({ code }: { code: string }) { + const { copied, copy } = useCopyToClipboard(); + + return ( + + ); +} + +function HowToEarn() { + return ( +
+

+ How to earn +

+
+ } + title={Trade} + description={Earn points for every trade you execute on Hyperliquid} + /> + } + title={Refer friends} + description={Share your referral code and earn bonus points when they trade} + /> +
+
+ ); +} + +export function PointsModal() { + const open = usePointsModalOpen(); + const { close } = usePointsModalActions(); + const { address } = useConnection(); + const { user } = usePrivy(); + + const [view, setView] = useState("loading"); + const [userPoints, setUserPoints] = useState(null); + const [referralCode, setReferralCode] = useState(""); + const [error, setError] = useState(null); + const [submitting, setSubmitting] = useState(false); + const referralInputId = useId(); + + function fetchPoints() { + if (!address) return; + setView("loading"); + setError(null); + setReferralCode(""); + + fetch(`${API_URL}/user_points?user_address=${address}`) + .then((res) => { + if (res.ok) return res.json(); + if (res.status === 404) return null; + throw new Error("Failed to fetch points"); + }) + .then((data) => { + if (data) { + setUserPoints(data); + setView("summary"); + } else { + setView("signup"); + } + }) + .catch(() => { + setError("Unable to connect to points service"); + setView("error"); + }); + } + + // biome-ignore lint/correctness/useExhaustiveDependencies: fetchPoints is intentionally excluded to avoid re-creating on every render + useEffect(() => { + if (!open || !address) return; + fetchPoints(); + }, [open, address]); + + function handleClose() { + close(); + setView("loading"); + setUserPoints(null); + setError(null); + setReferralCode(""); + setSubmitting(false); + } + + async function handleSignup(withReferral: boolean) { + if (!address) return; + setSubmitting(true); + setError(null); + + const body: Record = { walletAddress: address }; + if (user?.id) { + body.privyUserId = user.id; + } + if (withReferral && referralCode.trim()) { + body.referralCode = referralCode.trim(); + } + + try { + const res = await fetch(`${API_URL}/users`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + + if (!res.ok) { + const data = await res.json().catch(() => null); + throw new Error(data?.error ?? "Signup failed"); + } + + const created = await res.json(); + setUserPoints({ + walletAddress: created.walletAddress, + totalPoints: created.totalPoints ?? "0", + rank: 0, + referralCode: created.referralCode, + firstOrderAt: null, + lastOrderAt: null, + }); + setView("summary"); + } catch (err) { + setError(err instanceof Error ? err.message : "Signup failed"); + } finally { + setSubmitting(false); + } + } + + if (view === "summary" && userPoints) { + return ( + + + + + Hypermiles + + + +
+
+
+
+

+ Total Points +

+

{userPoints.totalPoints}

+
+ {userPoints.rank > 0 && ( +
+ + #{userPoints.rank} +
+ )} +
+
+ +
+ Your Referral Code} + value={} + /> +
+ + +
+
+
+ ); + } + + if (view === "error") { + return ( + + + + + Hypermiles + + +
+
+ +
+

{error}

+
+ + +
+
+
+
+ ); + } + + if (view === "signup") { + return ( + + + + + Join Hypermiles + + + +
+

+ Earn points every time you trade on Hyperliquid. Refer friends to earn even more. +

+ + + +
+ + setReferralCode(e.target.value)} + placeholder="Enter code" + disabled={submitting} + /> + {error &&

{error}

} +
+ +
+ + {referralCode.trim() && ( + + )} +
+
+
+
+ ); + } + + return ( + + + + + Hypermiles + + +
+ +

+ Loading points... +

+
+
+
+ ); +} diff --git a/src/locales/ar/messages.po b/src/locales/ar/messages.po index 2e3093c5..14eff57a 100644 --- a/src/locales/ar/messages.po +++ b/src/locales/ar/messages.po @@ -304,6 +304,7 @@ msgid "Claiming USDH" msgstr "" #: src/components/trade/positions/position-actions-dropdown.tsx +#: src/components/trade/tradebox/points-modal.tsx #: src/components/ui/dialog.tsx #: src/components/ui/sheet.tsx msgid "Close" @@ -546,6 +547,14 @@ msgstr "" msgid "Duration (Minutes)" msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Earn points every time you trade on Hyperliquid. Refer friends to earn even more." +msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Earn points for every trade you execute on Hyperliquid" +msgstr "" + #: src/components/trade/positions/positions-tab.tsx msgid "Edit TP/SL" msgstr "" @@ -760,6 +769,10 @@ msgstr "" #~ msgid "Gas:" #~ msgstr "الغاز:" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Get Started" +msgstr "" + #: src/components/ui/pagination.tsx #~ msgid "Go to next page" #~ msgstr "الذهاب للصفحة التالية" @@ -777,6 +790,16 @@ msgstr "الذهاب إلى منصة التداول" msgid "Hide small" msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "How to earn" +msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +#: src/components/trade/tradebox/points-modal.tsx +#: src/components/trade/tradebox/points-modal.tsx +msgid "Hypermiles" +msgstr "" + #: src/components/trade/tradebox/account-panel.tsx msgid "In Orders" msgstr "" @@ -833,6 +856,10 @@ msgstr "" msgid "Isolated" msgstr "معزول" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Join Hypermiles" +msgstr "" + #: src/components/trade/footer/footer-bar.tsx #~ msgid "Latency:" #~ msgstr "التأخير:" @@ -919,6 +946,10 @@ msgstr "جارٍ تحميل الأوامر المفتوحة..." msgid "Loading order history..." msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Loading points..." +msgstr "" + #: src/components/trade/positions/positions-tab.tsx msgid "Loading positions..." msgstr "جاري تحميل المراكز..." @@ -1338,6 +1369,10 @@ msgstr "" msgid "PNL" msgstr "الربح والخسارة" +#: src/components/trade/header/top-nav.tsx +msgid "Points" +msgstr "" + #: src/components/trade/components/wallet-dialog.tsx #~ msgid "Popular" #~ msgstr "" @@ -1422,6 +1457,14 @@ msgstr "" msgid "Reduce Only" msgstr "تقليل فقط" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Refer friends" +msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Referral Code (optional)" +msgstr "" + #: src/components/pages/builder-page.tsx #~ msgid "Register Another" #~ msgstr "" @@ -1451,6 +1494,7 @@ msgstr "" #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx +#: src/components/trade/tradebox/points-modal.tsx msgid "Retry" msgstr "" @@ -1600,6 +1644,10 @@ msgstr "" msgid "Settings" msgstr "الإعدادات" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Share your referral code and earn bonus points when they trade" +msgstr "" + #: src/components/trade/positions/position-limit-close-modal.tsx #: src/components/trade/positions/position-tpsl-modal.tsx #: src/components/trade/positions/positions-tab.tsx @@ -1682,6 +1730,10 @@ msgstr "الحجم" msgid "Size exceeds position" msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Skip referral" +msgstr "" + #: src/components/trade/positions/orders-tab.tsx #~ msgid "SL" #~ msgstr "" @@ -1945,6 +1997,10 @@ msgstr "تبديل وضع الحجم" msgid "Total" msgstr "الإجمالي" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Total Points" +msgstr "" + #: src/components/trade/positions/twap-tab.tsx #~ msgid "Total Size" #~ msgstr "الحجم الإجمالي" @@ -1980,9 +2036,9 @@ msgstr "" msgid "TP/SL" msgstr "TP/SL" -#: src/components/trade/header/top-nav.tsx -#~ msgid "Trade" -#~ msgstr "تداول" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Trade" +msgstr "تداول" #: src/components/trade/positions/history-tab.tsx #~ msgid "Trade ClockCounterClockwise" @@ -2203,3 +2259,7 @@ msgstr "" #: src/components/pages/builder-page.tsx #~ msgid "You can revoke permissions at any time by registering a new approval with 0% fee rate. Builder codes are processed entirely onchain as part of the fee logic." #~ msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Your Referral Code" +msgstr "" diff --git a/src/locales/en/messages.po b/src/locales/en/messages.po index 7ae91607..edb9e674 100644 --- a/src/locales/en/messages.po +++ b/src/locales/en/messages.po @@ -304,6 +304,7 @@ msgid "Claiming USDH" msgstr "Claiming USDH" #: src/components/trade/positions/position-actions-dropdown.tsx +#: src/components/trade/tradebox/points-modal.tsx #: src/components/ui/dialog.tsx #: src/components/ui/sheet.tsx msgid "Close" @@ -546,6 +547,14 @@ msgstr "Done" msgid "Duration (Minutes)" msgstr "Duration (Minutes)" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Earn points every time you trade on Hyperliquid. Refer friends to earn even more." +msgstr "Earn points every time you trade on Hyperliquid. Refer friends to earn even more." + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Earn points for every trade you execute on Hyperliquid" +msgstr "Earn points for every trade you execute on Hyperliquid" + #: src/components/trade/positions/positions-tab.tsx msgid "Edit TP/SL" msgstr "Edit TP/SL" @@ -760,6 +769,10 @@ msgstr "Funding History" #~ msgid "Gas:" #~ msgstr "Gas:" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Get Started" +msgstr "Get Started" + #: src/components/ui/pagination.tsx #~ msgid "Go to next page" #~ msgstr "Go to next page" @@ -777,6 +790,16 @@ msgstr "Go to trading terminal" msgid "Hide small" msgstr "Hide small" +#: src/components/trade/tradebox/points-modal.tsx +msgid "How to earn" +msgstr "How to earn" + +#: src/components/trade/tradebox/points-modal.tsx +#: src/components/trade/tradebox/points-modal.tsx +#: src/components/trade/tradebox/points-modal.tsx +msgid "Hypermiles" +msgstr "Hypermiles" + #: src/components/trade/tradebox/account-panel.tsx msgid "In Orders" msgstr "In Orders" @@ -833,6 +856,10 @@ msgstr "Invalid amount" msgid "Isolated" msgstr "Isolated" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Join Hypermiles" +msgstr "Join Hypermiles" + #: src/components/trade/components/global-settings-dialog.tsx #~ msgid "Language" #~ msgstr "Language" @@ -923,6 +950,10 @@ msgstr "Loading open orders..." msgid "Loading order history..." msgstr "Loading order history..." +#: src/components/trade/tradebox/points-modal.tsx +msgid "Loading points..." +msgstr "Loading points..." + #: src/components/trade/positions/positions-tab.tsx msgid "Loading positions..." msgstr "Loading positions..." @@ -1342,6 +1373,10 @@ msgstr "Place Limit Close" msgid "PNL" msgstr "PNL" +#: src/components/trade/header/top-nav.tsx +msgid "Points" +msgstr "Points" + #: src/components/trade/components/wallet-dialog.tsx #~ msgid "Popular" #~ msgstr "Popular" @@ -1426,6 +1461,14 @@ msgstr "Reduce" msgid "Reduce Only" msgstr "Reduce Only" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Refer friends" +msgstr "Refer friends" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Referral Code (optional)" +msgstr "Referral Code (optional)" + #: src/components/pages/builder-page.tsx #~ msgid "Register Another" #~ msgstr "Register Another" @@ -1455,6 +1498,7 @@ msgstr "Requirement" #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx +#: src/components/trade/tradebox/points-modal.tsx msgid "Retry" msgstr "Retry" @@ -1604,6 +1648,10 @@ msgstr "Set to {p}%" msgid "Settings" msgstr "Settings" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Share your referral code and earn bonus points when they trade" +msgstr "Share your referral code and earn bonus points when they trade" + #: src/components/trade/positions/position-limit-close-modal.tsx #: src/components/trade/positions/position-tpsl-modal.tsx #: src/components/trade/positions/positions-tab.tsx @@ -1686,6 +1734,10 @@ msgstr "Size" msgid "Size exceeds position" msgstr "Size exceeds position" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Skip referral" +msgstr "Skip referral" + #: src/components/trade/positions/orders-tab.tsx #~ msgid "SL" #~ msgstr "SL" @@ -1949,6 +2001,10 @@ msgstr "Toggle size mode" msgid "Total" msgstr "Total" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Total Points" +msgstr "Total Points" + #: src/components/trade/positions/twap-tab.tsx #~ msgid "Total Size" #~ msgstr "Total Size" @@ -1984,9 +2040,9 @@ msgstr "TP Price" msgid "TP/SL" msgstr "TP/SL" -#: src/components/trade/header/top-nav.tsx -#~ msgid "Trade" -#~ msgstr "Trade" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Trade" +msgstr "Trade" #: src/components/trade/positions/history-tab.tsx #~ msgid "Trade ClockCounterClockwise" @@ -2211,3 +2267,7 @@ msgstr "Yes" #: src/components/pages/builder-page.tsx #~ msgid "You can revoke permissions at any time by registering a new approval with 0% fee rate. Builder codes are processed entirely onchain as part of the fee logic." #~ msgstr "You can revoke permissions at any time by registering a new approval with 0% fee rate. Builder codes are processed entirely onchain as part of the fee logic." + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Your Referral Code" +msgstr "Your Referral Code" diff --git a/src/locales/es/messages.po b/src/locales/es/messages.po index a78b5aae..13f8a9fa 100644 --- a/src/locales/es/messages.po +++ b/src/locales/es/messages.po @@ -304,6 +304,7 @@ msgid "Claiming USDH" msgstr "" #: src/components/trade/positions/position-actions-dropdown.tsx +#: src/components/trade/tradebox/points-modal.tsx #: src/components/ui/dialog.tsx #: src/components/ui/sheet.tsx msgid "Close" @@ -546,6 +547,14 @@ msgstr "" msgid "Duration (Minutes)" msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Earn points every time you trade on Hyperliquid. Refer friends to earn even more." +msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Earn points for every trade you execute on Hyperliquid" +msgstr "" + #: src/components/trade/positions/positions-tab.tsx msgid "Edit TP/SL" msgstr "" @@ -760,6 +769,10 @@ msgstr "" #~ msgid "Gas:" #~ msgstr "Gas:" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Get Started" +msgstr "" + #: src/components/ui/pagination.tsx #~ msgid "Go to next page" #~ msgstr "Ir a página siguiente" @@ -777,6 +790,16 @@ msgstr "Ir al terminal de trading" msgid "Hide small" msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "How to earn" +msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +#: src/components/trade/tradebox/points-modal.tsx +#: src/components/trade/tradebox/points-modal.tsx +msgid "Hypermiles" +msgstr "" + #: src/components/trade/tradebox/account-panel.tsx msgid "In Orders" msgstr "" @@ -833,6 +856,10 @@ msgstr "" msgid "Isolated" msgstr "Aislado" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Join Hypermiles" +msgstr "" + #: src/components/trade/footer/footer-bar.tsx #~ msgid "Latency:" #~ msgstr "Latencia:" @@ -919,6 +946,10 @@ msgstr "Cargando órdenes abiertas..." msgid "Loading order history..." msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Loading points..." +msgstr "" + #: src/components/trade/positions/positions-tab.tsx msgid "Loading positions..." msgstr "Cargando posiciones..." @@ -1338,6 +1369,10 @@ msgstr "" msgid "PNL" msgstr "PNL" +#: src/components/trade/header/top-nav.tsx +msgid "Points" +msgstr "" + #: src/components/trade/components/wallet-dialog.tsx #~ msgid "Popular" #~ msgstr "" @@ -1422,6 +1457,14 @@ msgstr "" msgid "Reduce Only" msgstr "Solo reducir" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Refer friends" +msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Referral Code (optional)" +msgstr "" + #: src/components/pages/builder-page.tsx #~ msgid "Register Another" #~ msgstr "" @@ -1451,6 +1494,7 @@ msgstr "" #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx +#: src/components/trade/tradebox/points-modal.tsx msgid "Retry" msgstr "" @@ -1600,6 +1644,10 @@ msgstr "" msgid "Settings" msgstr "Configuración" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Share your referral code and earn bonus points when they trade" +msgstr "" + #: src/components/trade/positions/position-limit-close-modal.tsx #: src/components/trade/positions/position-tpsl-modal.tsx #: src/components/trade/positions/positions-tab.tsx @@ -1682,6 +1730,10 @@ msgstr "Tamaño" msgid "Size exceeds position" msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Skip referral" +msgstr "" + #: src/components/trade/positions/orders-tab.tsx #~ msgid "SL" #~ msgstr "" @@ -1945,6 +1997,10 @@ msgstr "Cambiar modo de tamaño" msgid "Total" msgstr "Total" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Total Points" +msgstr "" + #: src/components/trade/positions/twap-tab.tsx #~ msgid "Total Size" #~ msgstr "Tamaño total" @@ -1980,9 +2036,9 @@ msgstr "" msgid "TP/SL" msgstr "TP/SL" -#: src/components/trade/header/top-nav.tsx -#~ msgid "Trade" -#~ msgstr "Operar" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Trade" +msgstr "Operar" #: src/components/trade/positions/history-tab.tsx #~ msgid "Trade ClockCounterClockwise" @@ -2203,3 +2259,7 @@ msgstr "" #: src/components/pages/builder-page.tsx #~ msgid "You can revoke permissions at any time by registering a new approval with 0% fee rate. Builder codes are processed entirely onchain as part of the fee logic." #~ msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Your Referral Code" +msgstr "" diff --git a/src/locales/fr/messages.po b/src/locales/fr/messages.po index d75b7640..e4eea9de 100644 --- a/src/locales/fr/messages.po +++ b/src/locales/fr/messages.po @@ -304,6 +304,7 @@ msgid "Claiming USDH" msgstr "" #: src/components/trade/positions/position-actions-dropdown.tsx +#: src/components/trade/tradebox/points-modal.tsx #: src/components/ui/dialog.tsx #: src/components/ui/sheet.tsx msgid "Close" @@ -546,6 +547,14 @@ msgstr "" msgid "Duration (Minutes)" msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Earn points every time you trade on Hyperliquid. Refer friends to earn even more." +msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Earn points for every trade you execute on Hyperliquid" +msgstr "" + #: src/components/trade/positions/positions-tab.tsx msgid "Edit TP/SL" msgstr "" @@ -760,6 +769,10 @@ msgstr "" #~ msgid "Gas:" #~ msgstr "Gas :" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Get Started" +msgstr "" + #: src/components/ui/pagination.tsx #~ msgid "Go to next page" #~ msgstr "Aller à la page suivante" @@ -777,6 +790,16 @@ msgstr "Aller au terminal de trading" msgid "Hide small" msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "How to earn" +msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +#: src/components/trade/tradebox/points-modal.tsx +#: src/components/trade/tradebox/points-modal.tsx +msgid "Hypermiles" +msgstr "" + #: src/components/trade/tradebox/account-panel.tsx msgid "In Orders" msgstr "" @@ -833,6 +856,10 @@ msgstr "" msgid "Isolated" msgstr "Isolé" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Join Hypermiles" +msgstr "" + #: src/components/trade/footer/footer-bar.tsx #~ msgid "Latency:" #~ msgstr "Latence :" @@ -919,6 +946,10 @@ msgstr "Chargement des ordres ouverts..." msgid "Loading order history..." msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Loading points..." +msgstr "" + #: src/components/trade/positions/positions-tab.tsx msgid "Loading positions..." msgstr "Chargement des positions..." @@ -1338,6 +1369,10 @@ msgstr "" msgid "PNL" msgstr "PNL" +#: src/components/trade/header/top-nav.tsx +msgid "Points" +msgstr "" + #: src/components/trade/components/wallet-dialog.tsx #~ msgid "Popular" #~ msgstr "" @@ -1422,6 +1457,14 @@ msgstr "" msgid "Reduce Only" msgstr "Réduction uniquement" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Refer friends" +msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Referral Code (optional)" +msgstr "" + #: src/components/pages/builder-page.tsx #~ msgid "Register Another" #~ msgstr "" @@ -1451,6 +1494,7 @@ msgstr "" #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx +#: src/components/trade/tradebox/points-modal.tsx msgid "Retry" msgstr "" @@ -1600,6 +1644,10 @@ msgstr "" msgid "Settings" msgstr "Paramètres" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Share your referral code and earn bonus points when they trade" +msgstr "" + #: src/components/trade/positions/position-limit-close-modal.tsx #: src/components/trade/positions/position-tpsl-modal.tsx #: src/components/trade/positions/positions-tab.tsx @@ -1682,6 +1730,10 @@ msgstr "Taille" msgid "Size exceeds position" msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Skip referral" +msgstr "" + #: src/components/trade/positions/orders-tab.tsx #~ msgid "SL" #~ msgstr "" @@ -1945,6 +1997,10 @@ msgstr "Basculer le mode de taille" msgid "Total" msgstr "Total" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Total Points" +msgstr "" + #: src/components/trade/positions/twap-tab.tsx #~ msgid "Total Size" #~ msgstr "Taille totale" @@ -1980,9 +2036,9 @@ msgstr "" msgid "TP/SL" msgstr "TP/SL" -#: src/components/trade/header/top-nav.tsx -#~ msgid "Trade" -#~ msgstr "Trader" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Trade" +msgstr "Trader" #: src/components/trade/positions/history-tab.tsx #~ msgid "Trade ClockCounterClockwise" @@ -2203,3 +2259,7 @@ msgstr "" #: src/components/pages/builder-page.tsx #~ msgid "You can revoke permissions at any time by registering a new approval with 0% fee rate. Builder codes are processed entirely onchain as part of the fee logic." #~ msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Your Referral Code" +msgstr "" diff --git a/src/locales/hi/messages.po b/src/locales/hi/messages.po index 51ef4f7c..cb14f823 100644 --- a/src/locales/hi/messages.po +++ b/src/locales/hi/messages.po @@ -304,6 +304,7 @@ msgid "Claiming USDH" msgstr "" #: src/components/trade/positions/position-actions-dropdown.tsx +#: src/components/trade/tradebox/points-modal.tsx #: src/components/ui/dialog.tsx #: src/components/ui/sheet.tsx msgid "Close" @@ -546,6 +547,14 @@ msgstr "" msgid "Duration (Minutes)" msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Earn points every time you trade on Hyperliquid. Refer friends to earn even more." +msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Earn points for every trade you execute on Hyperliquid" +msgstr "" + #: src/components/trade/positions/positions-tab.tsx msgid "Edit TP/SL" msgstr "" @@ -760,6 +769,10 @@ msgstr "" #~ msgid "Gas:" #~ msgstr "गैस:" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Get Started" +msgstr "" + #: src/components/ui/pagination.tsx #~ msgid "Go to next page" #~ msgstr "अगले पृष्ठ पर जाएं" @@ -777,6 +790,16 @@ msgstr "ट्रेडिंग टर्मिनल पर जाएं" msgid "Hide small" msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "How to earn" +msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +#: src/components/trade/tradebox/points-modal.tsx +#: src/components/trade/tradebox/points-modal.tsx +msgid "Hypermiles" +msgstr "" + #: src/components/trade/tradebox/account-panel.tsx msgid "In Orders" msgstr "" @@ -833,6 +856,10 @@ msgstr "" msgid "Isolated" msgstr "आइसोलेटेड" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Join Hypermiles" +msgstr "" + #: src/components/trade/footer/footer-bar.tsx #~ msgid "Latency:" #~ msgstr "लेटेंसी:" @@ -919,6 +946,10 @@ msgstr "खुले ऑर्डर लोड हो रहे हैं..." msgid "Loading order history..." msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Loading points..." +msgstr "" + #: src/components/trade/positions/positions-tab.tsx msgid "Loading positions..." msgstr "पोजीशन लोड हो रहे हैं..." @@ -1338,6 +1369,10 @@ msgstr "" msgid "PNL" msgstr "PNL" +#: src/components/trade/header/top-nav.tsx +msgid "Points" +msgstr "" + #: src/components/trade/components/wallet-dialog.tsx #~ msgid "Popular" #~ msgstr "" @@ -1422,6 +1457,14 @@ msgstr "" msgid "Reduce Only" msgstr "केवल कम करें" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Refer friends" +msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Referral Code (optional)" +msgstr "" + #: src/components/pages/builder-page.tsx #~ msgid "Register Another" #~ msgstr "" @@ -1451,6 +1494,7 @@ msgstr "" #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx +#: src/components/trade/tradebox/points-modal.tsx msgid "Retry" msgstr "" @@ -1600,6 +1644,10 @@ msgstr "" msgid "Settings" msgstr "सेटिंग्स" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Share your referral code and earn bonus points when they trade" +msgstr "" + #: src/components/trade/positions/position-limit-close-modal.tsx #: src/components/trade/positions/position-tpsl-modal.tsx #: src/components/trade/positions/positions-tab.tsx @@ -1682,6 +1730,10 @@ msgstr "आकार" msgid "Size exceeds position" msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Skip referral" +msgstr "" + #: src/components/trade/positions/orders-tab.tsx #~ msgid "SL" #~ msgstr "" @@ -1945,6 +1997,10 @@ msgstr "साइज मोड टॉगल करें" msgid "Total" msgstr "कुल" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Total Points" +msgstr "" + #: src/components/trade/positions/twap-tab.tsx #~ msgid "Total Size" #~ msgstr "कुल आकार" @@ -1980,9 +2036,9 @@ msgstr "" msgid "TP/SL" msgstr "TP/SL" -#: src/components/trade/header/top-nav.tsx -#~ msgid "Trade" -#~ msgstr "ट्रेड" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Trade" +msgstr "ट्रेड" #: src/components/trade/positions/history-tab.tsx #~ msgid "Trade ClockCounterClockwise" @@ -2203,3 +2259,7 @@ msgstr "" #: src/components/pages/builder-page.tsx #~ msgid "You can revoke permissions at any time by registering a new approval with 0% fee rate. Builder codes are processed entirely onchain as part of the fee logic." #~ msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Your Referral Code" +msgstr "" diff --git a/src/locales/zh/messages.po b/src/locales/zh/messages.po index 5c1091e6..f3c1e02a 100644 --- a/src/locales/zh/messages.po +++ b/src/locales/zh/messages.po @@ -304,6 +304,7 @@ msgid "Claiming USDH" msgstr "" #: src/components/trade/positions/position-actions-dropdown.tsx +#: src/components/trade/tradebox/points-modal.tsx #: src/components/ui/dialog.tsx #: src/components/ui/sheet.tsx msgid "Close" @@ -546,6 +547,14 @@ msgstr "" msgid "Duration (Minutes)" msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Earn points every time you trade on Hyperliquid. Refer friends to earn even more." +msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Earn points for every trade you execute on Hyperliquid" +msgstr "" + #: src/components/trade/positions/positions-tab.tsx msgid "Edit TP/SL" msgstr "" @@ -760,6 +769,10 @@ msgstr "" #~ msgid "Gas:" #~ msgstr "Gas:" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Get Started" +msgstr "" + #: src/components/ui/pagination.tsx #~ msgid "Go to next page" #~ msgstr "下一页" @@ -777,6 +790,16 @@ msgstr "前往交易终端" msgid "Hide small" msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "How to earn" +msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +#: src/components/trade/tradebox/points-modal.tsx +#: src/components/trade/tradebox/points-modal.tsx +msgid "Hypermiles" +msgstr "" + #: src/components/trade/tradebox/account-panel.tsx msgid "In Orders" msgstr "" @@ -833,6 +856,10 @@ msgstr "" msgid "Isolated" msgstr "逐仓" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Join Hypermiles" +msgstr "" + #: src/components/trade/footer/footer-bar.tsx #~ msgid "Latency:" #~ msgstr "延迟:" @@ -919,6 +946,10 @@ msgstr "加载未成交订单中..." msgid "Loading order history..." msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Loading points..." +msgstr "" + #: src/components/trade/positions/positions-tab.tsx msgid "Loading positions..." msgstr "加载仓位中..." @@ -1338,6 +1369,10 @@ msgstr "" msgid "PNL" msgstr "盈亏" +#: src/components/trade/header/top-nav.tsx +msgid "Points" +msgstr "" + #: src/components/trade/components/wallet-dialog.tsx #~ msgid "Popular" #~ msgstr "" @@ -1422,6 +1457,14 @@ msgstr "" msgid "Reduce Only" msgstr "只减仓" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Refer friends" +msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Referral Code (optional)" +msgstr "" + #: src/components/pages/builder-page.tsx #~ msgid "Register Another" #~ msgstr "" @@ -1451,6 +1494,7 @@ msgstr "" #: src/components/trade/tradebox/deposit-modal.tsx #: src/components/trade/tradebox/faucet-modal.tsx #: src/components/trade/tradebox/leverage-control.tsx +#: src/components/trade/tradebox/points-modal.tsx msgid "Retry" msgstr "" @@ -1600,6 +1644,10 @@ msgstr "" msgid "Settings" msgstr "设置" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Share your referral code and earn bonus points when they trade" +msgstr "" + #: src/components/trade/positions/position-limit-close-modal.tsx #: src/components/trade/positions/position-tpsl-modal.tsx #: src/components/trade/positions/positions-tab.tsx @@ -1682,6 +1730,10 @@ msgstr "数量" msgid "Size exceeds position" msgstr "" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Skip referral" +msgstr "" + #: src/components/trade/positions/orders-tab.tsx #~ msgid "SL" #~ msgstr "" @@ -1945,6 +1997,10 @@ msgstr "切换数量模式" msgid "Total" msgstr "合计" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Total Points" +msgstr "" + #: src/components/trade/positions/twap-tab.tsx #~ msgid "Total Size" #~ msgstr "总数量" @@ -1980,9 +2036,9 @@ msgstr "" msgid "TP/SL" msgstr "止盈止损" -#: src/components/trade/header/top-nav.tsx -#~ msgid "Trade" -#~ msgstr "交易" +#: src/components/trade/tradebox/points-modal.tsx +msgid "Trade" +msgstr "交易" #: src/components/trade/positions/history-tab.tsx #~ msgid "Trade ClockCounterClockwise" @@ -2203,3 +2259,7 @@ msgstr "" #: src/components/pages/builder-page.tsx #~ msgid "You can revoke permissions at any time by registering a new approval with 0% fee rate. Builder codes are processed entirely onchain as part of the fee logic." #~ msgstr "" + +#: src/components/trade/tradebox/points-modal.tsx +msgid "Your Referral Code" +msgstr "" diff --git a/src/stores/use-global-modal-store.ts b/src/stores/use-global-modal-store.ts index 3738cbf6..cd7f01a1 100644 --- a/src/stores/use-global-modal-store.ts +++ b/src/stores/use-global-modal-store.ts @@ -9,6 +9,7 @@ type GlobalModal = | { type: "swap"; fromToken: string; toToken?: string } | { type: "commandMenu" } | { type: "faucet" } + | { type: "points" } | null; interface DepositActions { @@ -37,6 +38,11 @@ interface FaucetActions { close: () => void; } +interface PointsActions { + open: () => void; + close: () => void; +} + interface GlobalModalState { modal: GlobalModal; depositActions: DepositActions; @@ -44,6 +50,7 @@ interface GlobalModalState { swapActions: SwapActions; commandMenuActions: CommandMenuActions; faucetActions: FaucetActions; + pointsActions: PointsActions; } const useGlobalModalStore = create((set) => { @@ -72,6 +79,10 @@ const useGlobalModalStore = create((set) => { open: () => set({ modal: { type: "faucet" } }), close, }, + pointsActions: { + open: () => set({ modal: { type: "points" } }), + close, + }, }; }); @@ -95,3 +106,6 @@ export const useCommandMenuActions = () => useGlobalModalStore((s) => s.commandM export const useFaucetModalOpen = () => useGlobalModalStore((s) => s.modal?.type === "faucet"); export const useFaucetModalActions = () => useGlobalModalStore((s) => s.faucetActions); + +export const usePointsModalOpen = () => useGlobalModalStore((s) => s.modal?.type === "points"); +export const usePointsModalActions = () => useGlobalModalStore((s) => s.pointsActions);