From 4018d1fea8be0fddce0110e032ee7d394dce6cfc Mon Sep 17 00:00:00 2001 From: alex luu Date: Wed, 1 Apr 2026 16:37:05 -0400 Subject: [PATCH 1/3] fix: update send to work with unified --- .../trade/positions/send-dialog.tsx | 69 ++++++++++++++----- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/src/components/trade/positions/send-dialog.tsx b/src/components/trade/positions/send-dialog.tsx index d0d2c4e8..82071442 100644 --- a/src/components/trade/positions/send-dialog.tsx +++ b/src/components/trade/positions/send-dialog.tsx @@ -1,7 +1,8 @@ import { t } from "@lingui/core/macro"; import { PaperPlaneTiltIcon, SpinnerGapIcon, WarningCircleIcon } from "@phosphor-icons/react"; import { useCallback, useMemo, useState } from "react"; -import { isAddress } from "viem"; +import { type Address, isAddress } from "viem"; +import { useConnection } from "wagmi"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; @@ -15,6 +16,7 @@ import { cn } from "@/lib/cn"; import { formatToken } from "@/lib/format"; import { useExchangeSendAsset } from "@/lib/hyperliquid/hooks/exchange"; import { useExchangeSpotSend } from "@/lib/hyperliquid/hooks/exchange/useExchangeSpotSend"; +import { useInfoUserAbstraction } from "@/lib/hyperliquid/hooks/info/useInfoUserAbstraction"; import { useSpotTokens } from "@/lib/hyperliquid/markets/use-spot-tokens"; import { floorToString, limitDecimalInput } from "@/lib/trade/numbers"; import { AssetDisplay } from "../components/asset-display"; @@ -40,11 +42,18 @@ export function SendDialog({ const [amount, setAmount] = useState(""); const [error, setError] = useState(null); + const { address } = useConnection(); const { getToken } = useSpotTokens(); const { mutateAsync: sendAsset, isPending: isSendAssetPending } = useExchangeSendAsset(); const { mutateAsync: spotSend, isPending: isSpotSendPending } = useExchangeSpotSend(); + const { data: abstractionMode, isLoading: isAbstractionLoading } = useInfoUserAbstraction( + address as Address | undefined, + ); const { perpSummary, spotBalances } = useAccountBalances(); + const isUnifiedAccount = abstractionMode !== "default" && abstractionMode !== undefined; + const effectiveAccountType = isUnifiedAccount ? "spot" : accountType; + const isPending = isSendAssetPending || isSpotSendPending; const availableSpotTokens = useMemo((): BalanceRow[] => { @@ -66,11 +75,11 @@ export function SendDialog({ }, [spotBalances]); const tokenOptions = useMemo(() => { - if (accountType === "perp") { + if (effectiveAccountType === "perp") { return [DEFAULT_QUOTE_TOKEN]; } return availableSpotTokens.map((b) => b.asset); - }, [accountType, availableSpotTokens]); + }, [effectiveAccountType, availableSpotTokens]); const tokenInfo = useMemo(() => getToken(selectedToken), [getToken, selectedToken]); const tokenId = useMemo(() => { @@ -81,18 +90,18 @@ export function SendDialog({ const decimals = useMemo(() => getToken(selectedToken)?.transferDecimals ?? 2, [getToken, selectedToken]); const availableBalance = useMemo(() => { - if (accountType === "perp") { + if (effectiveAccountType === "perp") { return getPerpAvailable(perpSummary?.accountValue, perpSummary?.totalMarginUsed); } const balance = spotBalances?.find((b) => b.coin === selectedToken); return getAvailableFromTotals(balance?.total, balance?.hold); - }, [accountType, perpSummary, spotBalances, selectedToken]); + }, [effectiveAccountType, perpSummary, spotBalances, selectedToken]); const availableBalanceStr = useMemo(() => floorToString(availableBalance, decimals), [availableBalance, decimals]); const isValidDestination = isAddress(destination); const isValidAmount = isAmountWithinBalance(amount, availableBalance); - const canSend = isValidDestination && isValidAmount && !!tokenId && !isPending; + const canSend = isValidDestination && isValidAmount && !!tokenId && !isPending && !isAbstractionLoading; function handleAccountTypeChange(value: AccountType) { setAccountType(value); @@ -122,7 +131,20 @@ export function SendDialog({ setError(null); try { - if (accountType === "perp") { + console.log("[SendDialog] sending", { tokenId, destination, amount, isUnifiedAccount }); + if (isUnifiedAccount) { + // Unified accounts block usdSend and spotSend. + // USDC (collateral) can land in recipient's perp DEX (destinationDex:""). + // Other spot tokens must target recipient's spot (destinationDex:"spot"). + const result = await sendAsset({ + destination, + sourceDex: "spot", + destinationDex: selectedToken === DEFAULT_QUOTE_TOKEN ? "" : "spot", + token: tokenId, + amount, + }); + console.log("[SendDialog] sendAsset result", result); + } else if (effectiveAccountType === "perp") { await sendAsset({ destination, sourceDex: "", @@ -144,7 +166,18 @@ export function SendDialog({ const message = err instanceof Error ? err.message : t`Send failed`; setError(message); } - }, [accountType, amount, canSend, destination, onOpenChange, sendAsset, spotSend, tokenId]); + }, [ + effectiveAccountType, + amount, + canSend, + destination, + isUnifiedAccount, + onOpenChange, + selectedToken, + sendAsset, + spotSend, + tokenId, + ]); function handleOpenChange(newOpen: boolean) { if (!newOpen) { @@ -178,15 +211,17 @@ export function SendDialog({
- + {!isUnifiedAccount && ( + + )}