+ $ + {accountEquity.toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} +
++ Showing total unrealized PnL from open positions +
+Entry
++ {formatPrice(tile.entry)} +
+Stop Loss
++ ${tile.stopLoss} +
+Take Profits
++ {formatTakeProfits()} +
++ Connect wallet and setup Hyperliquid to trade +
+ )} ++ Min: 0.0001 ETH +
+ + return ( + + ); +} diff --git a/src/apps/perps/components/PasteStrategyButton.tsx b/src/apps/perps/components/PasteStrategyButton.tsx new file mode 100644 index 00000000..3920a0fd --- /dev/null +++ b/src/apps/perps/components/PasteStrategyButton.tsx @@ -0,0 +1,135 @@ +import { useState } from 'react'; +import { Button } from './ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from './ui/dialog'; +import { Textarea } from './ui/textarea'; +import { Label } from './ui/label'; +import { ClipboardPaste } from 'lucide-react'; +import { toast } from 'sonner'; + +interface StrategyPayload { + orderSide: 'buy' | 'sell'; + ticker: string; + exchange: string; + entryPrice: number; + stopLoss: number; + tp1?: number; + tp2?: number; + tp3?: number; + tp4?: number; + tp5?: number; +} + +interface ParsedStrategy { + ticker: string; + side: 'long' | 'short'; + entryPrice: number; + stopLoss: number; + takeProfits: string; +} + +interface PasteStrategyButtonProps { + onStrategyPasted: (strategy: ParsedStrategy) => void; +} + +export function PasteStrategyButton({ + onStrategyPasted, +}: PasteStrategyButtonProps) { + const [isOpen, setIsOpen] = useState(false); + const [jsonInput, setJsonInput] = useState(''); + + const parseStrategy = (json: string): ParsedStrategy | null => { + try { + const payload: StrategyPayload = JSON.parse(json); + + // Validate required fields + if (!payload.ticker || !payload.orderSide || !payload.entryPrice) { + toast.error('Invalid strategy payload', { + description: + 'Missing required fields: ticker, orderSide, or entryPrice', + }); + return null; + } + + // Extract ticker symbol (remove USDT.P suffix) + const ticker = payload.ticker.replace(/USDT\.P$/i, ''); + + // Convert orderSide to long/short + const side = payload.orderSide === 'buy' ? 'long' : 'short'; + + // Collect take profits (tp1, tp2, tp3) + const tps: number[] = []; + if (payload.tp1) tps.push(payload.tp1); + if (payload.tp2) tps.push(payload.tp2); + if (payload.tp3) tps.push(payload.tp3); + + return { + ticker, + side, + entryPrice: payload.entryPrice, + stopLoss: payload.stopLoss, + takeProfits: tps.join(', '), + }; + } catch (error) { + toast.error('Invalid JSON', { + description: 'Please paste a valid JSON strategy payload', + }); + return null; + } + }; + + const handlePaste = () => { + const strategy = parseStrategy(jsonInput); + if (strategy) { + onStrategyPasted(strategy); + setIsOpen(false); + setJsonInput(''); + toast.success('Strategy loaded!', { + description: `${strategy.side.toUpperCase()} ${strategy.ticker} @ $${strategy.entryPrice}`, + }); + } + }; + + return ( + + ); +} diff --git a/src/apps/perps/components/PinSetupModal.tsx b/src/apps/perps/components/PinSetupModal.tsx new file mode 100644 index 00000000..9904a9e7 --- /dev/null +++ b/src/apps/perps/components/PinSetupModal.tsx @@ -0,0 +1,129 @@ +import { useState, useRef, useEffect } from 'react'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from './ui/dialog'; +import { Button } from './ui/button'; +import { Label } from './ui/label'; +import { Lock } from 'lucide-react'; +import { toast } from 'sonner'; +import { + InputOTP, + InputOTPGroup, + InputOTPSlot, +} from './ui/input-otp'; + +interface PinSetupModalProps { + isOpen: boolean; + onConfirm: (pin: string) => void; + onCancel: () => void; +} + +export function PinSetupModal({ isOpen, onConfirm, onCancel }: PinSetupModalProps) { + const [pin, setPin] = useState(''); + const [confirmPin, setConfirmPin] = useState(''); + const confirmInputRef = useRefSize
++ {formatPrice(size, 4)} +
+Entry Price
++ ${formatPrice(entryPx, 2)} +
++ Current Mark Price +
++ ${formatPrice(markPx, 2)} +
+Unrealized PnL
++ {formatPnl(pnl.pnlUsd)} +
++ ({formatPnl(pnl.pnlPct, true)}) +
+Stop Loss
++ ${formatPrice(stopLoss, 2)} +
++ Take Profit Targets +
++ No open positions +
+ ) : isLoading ? ( ++ No open orders +
+ ) : isLoading ? ( ++ {config.description} +
+| Coin | +Direction | +Price | +Time | +Size | +Value (USDC) | +Closed PnL | +Fee | +
|---|---|---|---|---|---|---|---|
|
+
+
+ |
+ + + {isBuy ? 'Buy' : 'Sell'} + + | ++ ${formatPrice(trade.px)} + | ++ {formatTime(trade.time)} + | ++ {formatNumber(trade.sz, 4)} + | ++ ${calculateTradeValue(trade.px, trade.sz)} + | ++ {trade.closedPnl ? ( + + {isPnlPositive ? '+' : ''}${formatNumber(trade.closedPnl)} + + ) : ( + - + )} + | ++ ${formatNumber(trade.fee || '0')} + | +