diff --git a/src/components/fee-compare-client.tsx b/src/components/fee-compare-client.tsx index efe79e2ef..9d3a203a2 100644 --- a/src/components/fee-compare-client.tsx +++ b/src/components/fee-compare-client.tsx @@ -120,10 +120,10 @@ function DirBadge({ dir }: { dir: string }) { const isOpen = d.includes("open"); const isLong = d.includes("long"); const cls = isOpen - ? isLong ? "bg-emerald-500/12 text-emerald-400" : "bg-red-400/12 text-red-400" - : isLong ? "bg-emerald-500/8 text-emerald-500/70" : "bg-red-400/8 text-red-400/70"; + ? isLong ? "bg-emerald-500/12 text-emerald-500" : "bg-red-400/12 text-red-400" + : isLong ? "bg-emerald-500/8 text-emerald-500/60" : "bg-red-400/8 text-red-400/60"; return ( - + {dir} ); @@ -131,22 +131,51 @@ function DirBadge({ dir }: { dir: string }) { function MakerBadge() { return ( - + maker ); } +function CheaperBadge() { + return ( + + + Cheaper + + ); +} + // ────────────────────────────────────────────────────────────────────── -// SummaryVsCard +// SummaryVsCard — always shows both platforms, simulated if needed // ────────────────────────────────────────────────────────────────────── function SummaryVsCard({ result }: { result: FeeCompareResult }) { const { hl, gains, comparison } = result; const hasHl = hl.fills > 0; const hasGains = gains.events > 0; - const hlWins = comparison.hlSavedVsGains > 1; - const gainsWins = comparison.gainsSavedVsHl > 1; + + // Determine what to show on the Gains side + // If no real Gains activity, use simulated cost for HL trades + const gainsDisplay = hasGains + ? { fee: gains.feesUsdc, bps: gains.avgFeeRateBps, real: true, label: `${gains.events} trades` } + : hasHl && comparison.gainsEquivForHlNotional > 0 + ? { + fee: comparison.gainsEquivForHlNotional, + bps: comparison.hlNotionalOnGains > 0 + ? (comparison.gainsEquivForHlNotional / comparison.hlNotionalOnGains) * 10000 + : 0, + real: false, + label: "simulated", + } + : null; + + // Winner logic — compare what was actually paid vs equiv on the other platform + const hlFeeComp = comparison.hlFeesOnGainsCoins > 0 ? comparison.hlFeesOnGainsCoins : hl.feesUsd; + const gainsFeeComp = gainsDisplay?.fee ?? 0; + const diff = Math.abs(hlFeeComp - gainsFeeComp); + const hlWins = gainsFeeComp > 0 && hlFeeComp < gainsFeeComp && diff > 0.5; + const gainsWins = gainsFeeComp > 0 && gainsFeeComp < hlFeeComp && diff > 0.5; if (!hasHl && !hasGains) { return ( @@ -159,132 +188,154 @@ function SummaryVsCard({ result }: { result: FeeCompareResult }) { return (
- {/* HL side */} -
-
- + {/* Hyperliquid side */} +
+
+
-

Hyperliquid

- {hasHl &&

{hl.fills} fills

} +

Hyperliquid

+ {hasHl &&

{hl.fills} fills

}
- {hlWins && ( - - Cheaper - - )} + {hlWins && }
+ {hasHl ? ( -
+
-

{fmtUsd(hl.feesUsd)}

-

{fmt(hl.avgFeeRateBps, 2)} bps avg

+

+ {fmtUsd(hl.feesUsd)} +

+

{fmt(hl.avgFeeRateBps, 2)} bps avg rate

-
-
-

Volume

-

{fmtUsd(hl.notionalUsd)}

+
+
+

Volume

+

{fmtUsd(hl.notionalUsd)}

-
-

Net cost

-

{fmtUsd(hl.netCostUsd)}

-

fees minus funding

+
+

Net cost

+

{fmtUsd(hl.netCostUsd)}

+

after funding

) : ( -

No activity

+

No Hyperliquid activity

)}
{/* VS divider */}
- VS +
+ VS +
{/* Gains side */} -
-
- +
+
+
-

Gains

- {hasGains &&

{gains.events} trades

} +

Gains

+ {gainsDisplay && ( +

+ {gainsDisplay.label} + {!gainsDisplay.real && ( + · est. + )} +

+ )}
- {gainsWins && ( - - Cheaper - - )} + {gainsWins && }
- {hasGains ? ( -
+ + {gainsDisplay ? ( +
-

{fmtUsd(gains.feesUsdc)}

-

{fmt(gains.avgFeeRateBps, 2)} bps avg

+

+ {fmtUsd(gainsDisplay.fee)} +

+

+ {fmt(gainsDisplay.bps, 2)} bps avg rate + {!gainsDisplay.real && " · live schedule"} +

-
-
-

Volume

-

{fmtUsd(gains.positionSizeUsdc)}

+ {!gainsDisplay.real && hasHl && ( +
+

Same volume on Gains

+

{fmtUsd(comparison.hlNotionalOnGains)}

+

+ {comparison.hlNotionalOnGains < hl.notionalUsd ? "coins listed on Gains only" : "all coins"} +

-
-

Events

-

{gains.events}

-

USDC collateral

+ )} + {gainsDisplay.real && ( +
+
+

Volume

+

{fmtUsd(gains.positionSizeUsdc)}

+
+
+

Events

+

{gains.events}

+

USDC collateral

+
-
+ )}
) : ( -

No activity

+

No Gains activity

)}
{/* Verdict bar */} - {(hasHl || hasGains) && ( -
- {hasHl && comparison.hlNotionalOnGains > 0 && ( -
-

- HL trades on Gains-listed coins at live Gains rates +

+ {hasHl && comparison.hlNotionalOnGains > 0 && ( +
+

+ Same trades at live Gains rates + {comparison.hlNotionalOnGains < hl.notionalUsd && " (Gains-listed coins only)"} +

+ {comparison.hlSavedVsGains > 0.5 ? ( +

+ Hyperliquid saved {fmtUsd(comparison.hlSavedVsGains)} + {comparison.hlCheaperMultiple && comparison.hlCheaperMultiple > 1.05 && ( + + ({fmt(comparison.hlCheaperMultiple, 1)}x cheaper) + + )}

- {comparison.hlSavedVsGains > 1 ? ( -

- HL saved {fmtUsd(comparison.hlSavedVsGains)} vs Gains - {comparison.hlCheaperMultiple && ( - ({fmt(comparison.hlCheaperMultiple, 1)}x cheaper) - )} -

- ) : comparison.hlSavedVsGains < -1 ? ( -

- HL overpaid {fmtUsd(Math.abs(comparison.hlSavedVsGains))} vs Gains -

- ) : ( -

Roughly equal cost

- )} -
- )} - {hasGains && ( -
0 ? "pt-2 border-t border-ink/6" : ""}`}> -

- Gains trades at Hyperliquid taker ({fmtBps(comparison.hlRoundTripRate)} RT) + ) : comparison.hlSavedVsGains < -0.5 ? ( +

+ Gains would save {fmtUsd(Math.abs(comparison.hlSavedVsGains))}

- {comparison.gainsSavedVsHl < -1 ? ( -

- HL would save {fmtUsd(Math.abs(comparison.gainsSavedVsHl))} -

- ) : comparison.gainsSavedVsHl > 1 ? ( -

- Gains overpaid {fmtUsd(comparison.gainsSavedVsHl)} vs HL -

- ) : ( -

Roughly equal cost

- )} -
- )} -
- )} + ) : ( +

Roughly equal cost

+ )} +
+ )} + {hasGains && ( +
0 ? "mt-3 pt-3 border-t border-ink/6" : ""}`}> +

+ Same Gains volume at Hyperliquid taker ({fmtBps(comparison.hlRoundTripRate)} RT) +

+ {comparison.gainsSavedVsHl < -0.5 ? ( +

+ Hyperliquid saves {fmtUsd(Math.abs(comparison.gainsSavedVsHl))} +

+ ) : comparison.gainsSavedVsHl > 0.5 ? ( +

+ Gains overpaid {fmtUsd(comparison.gainsSavedVsHl)} vs Hyperliquid +

+ ) : ( +

Roughly equal cost

+ )} +
+ )} +
); } @@ -299,29 +350,29 @@ function TopCoinsCard({ topCoins }: { topCoins: TopCoin[] }) {
-

Top markets

+

Top markets

{topCoins.map((c) => ( -
- {c.coin} +
+ {c.coin} {c.fills} fills
{fmtUsd(c.notional)} - {fmtUsd(c.fees)} + {fmtUsd(c.fees)} {c.gainsRoundTripRate !== null ? ( - + Gains {fmtBps(c.gainsRoundTripRate)} RT ) : ( - not on Gains + not on Gains )}
))} @@ -331,7 +382,7 @@ function TopCoinsCard({ topCoins }: { topCoins: TopCoin[] }) { } // ────────────────────────────────────────────────────────────────────── -// HlTradeTable +// HlTradeTable — bold the cheaper fee per row // ────────────────────────────────────────────────────────────────────── function HlTradeTable({ fills }: { fills: FillRow[] }) { @@ -346,7 +397,7 @@ function HlTradeTable({ fills }: { fills: FillRow[] }) {
-

Trade history

+

Trade history

{fills.length} fills

@@ -354,53 +405,68 @@ function HlTradeTable({ fills }: { fills: FillRow[] }) {
- - - - - - - - - + + + + + + + + + {rows.map((f, i) => { const gainsFee = f.gainsPerSide; const saved = gainsFee !== null ? gainsFee - f.hlFee : null; + const hlCheaper = gainsFee !== null && f.hlFee < gainsFee && Math.abs(f.hlFee - gainsFee) > 0.001; + const gainsCheaper = gainsFee !== null && gainsFee < f.hlFee && Math.abs(f.hlFee - gainsFee) > 0.001; const isOpen = f.closedPnl === 0 && !f.dir.toLowerCase().includes("close"); + return ( - - + - - - - + + + {/* HL fee — bold green if cheaper */} + + + {/* Gains fee — bold green if cheaper */} + - - @@ -414,7 +480,7 @@ function HlTradeTable({ fills }: { fills: FillRow[] }) { @@ -428,27 +494,29 @@ function HlTradeTable({ fills }: { fills: FillRow[] }) { // ────────────────────────────────────────────────────────────────────── function GainsCard({ gains, comparison }: { gains: FeeCompareResult["gains"]; comparison: FeeCompareResult["comparison"] }) { + const hlSaves = comparison.gainsSavedVsHl < -0.5; return (
-

Gains on-chain

+

Gains on-chain

Arbitrum
{[ { label: "Fees paid", value: fmtUsd(gains.feesUsdc), sub: fmt(gains.avgFeeRateBps, 2) + " bps avg" }, - { label: "Position size", value: fmtUsd(gains.positionSizeUsdc), sub: `${gains.events} events` }, - { label: "HL equiv cost", value: fmtUsd(comparison.hlEquivForGainsVolume), sub: fmtBps(comparison.hlRoundTripRate) + " taker RT" }, + { label: "Volume", value: fmtUsd(gains.positionSizeUsdc), sub: `${gains.events} events` }, + { label: "Hyperliquid equiv", value: fmtUsd(comparison.hlEquivForGainsVolume), sub: fmtBps(comparison.hlRoundTripRate) + " taker RT", winner: hlSaves }, { - label: comparison.gainsSavedVsHl < -1 ? "HL saves" : "Gains saves", - value: Math.abs(comparison.gainsSavedVsHl) > 1 ? fmtUsd(Math.abs(comparison.gainsSavedVsHl)) : "≈ $0", - accent: comparison.gainsSavedVsHl < -1, + label: hlSaves ? "Hyperliquid saves" : "Gains saves", + value: Math.abs(comparison.gainsSavedVsHl) > 0.5 ? fmtUsd(Math.abs(comparison.gainsSavedVsHl)) : "≈ $0", + accent: true, + winner: false, }, ].map((s) => ( -
+

{s.label}

-

{s.value}

+

{s.value}

{s.sub &&

{s.sub}

}
))} @@ -471,9 +539,9 @@ function Results({ result }: { result: FeeCompareResult }) {

Hyperliquid fees: exact fills from Hyperliquid API. Gains fees: on-chain{" "} FeesProcessed events from{" "} - 0xFF16...7f169 (Arbitrum). Gains simulation uses live - rates from backend-arbitrum.gains.trade. Hyperliquid - simulation uses official taker rate (3.5 bps/side). Funding and borrowing fees excluded. + 0xFF16...7f169 (Arbitrum). Simulated Gains costs use + live rates from backend-arbitrum.gains.trade. + Hyperliquid simulation uses official taker rate (3.5 bps/side). Funding and borrowing fees excluded.

); @@ -543,7 +611,7 @@ export function FeeCompareClient() { key={d} type="button" onClick={() => setDays(d)} - className={`rounded-md border px-2.5 py-1 text-[11px] font-medium uppercase tracking-[0.1em] transition-all ${ + className={`rounded-lg border px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.1em] transition-all ${ days === d ? "border-ink bg-ink text-paper" : "border-ink/15 bg-paper text-ink hover:border-ink/40" }`} > @@ -555,7 +623,7 @@ export function FeeCompareClient() { type="button" onClick={analyze} disabled={loading} - className="ml-auto flex items-center gap-2 rounded-xl bg-ink px-5 py-2.5 text-sm font-medium text-paper disabled:opacity-50 hover:opacity-90 transition-opacity" + className="ml-auto flex items-center gap-2 rounded-xl bg-ink px-5 py-2.5 text-sm font-semibold text-paper disabled:opacity-50 hover:opacity-90 transition-opacity" > {loading ? : } {loading ? "Analyzing..." : "Analyze wallet"}
DateMarketDirectionNotionalHL feeGains equivSavedPnL
DateMarketDirectionNotionalHL feeGains feeSavedPnL
{fmtDate(f.time)} + {fmtDate(f.time)}
- {f.coin} + {f.coin} {!f.isTaker && }
{fmtUsd(f.notional)}{fmtUsd(f.hlFee)} - {gainsFee !== null ? fmtUsd(gainsFee) : } + {fmtUsd(f.notional)} + {fmtUsd(f.hlFee)} + + + {saved !== null ? ( - saved > 0.001 ? +{fmtUsd(saved)} - : saved < -0.001 ? {fmtUsd(saved)} - : ≈ 0 - ) : } + saved > 0.001 ? ( + +{fmtUsd(saved)} + ) : saved < -0.001 ? ( + {fmtUsd(saved)} + ) : ( + ≈ 0 + ) + ) : } + + {isOpen ? ( open ) : f.closedPnl > 0 ? ( - +{fmtUsd(f.closedPnl)} + +{fmtUsd(f.closedPnl)} ) : f.closedPnl < 0 ? ( - {fmtUsd(f.closedPnl)} + {fmtUsd(f.closedPnl)} ) : ( - $0 + $0 )}