Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions ui/.example.env

This file was deleted.

2 changes: 2 additions & 0 deletions ui/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

/local
69 changes: 52 additions & 17 deletions ui/bun.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@
"@solana/wallet-adapter-wallets": "^0.19.37",
"@solana/web3.js": "1.98.0",
"@tanstack/react-query": "^5.85.5",
"axios": "^1.12.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"immer": "^10.1.1",
"lightweight-charts": "^5.0.8",
"lucide-react": "^0.536.0",
"next": "15.4.6",
"next": "15.4.10",
"pino-pretty": "^10.2.3",
"react": "19.1.0",
"react-dom": "19.1.0",
Expand All @@ -48,4 +49,4 @@
"tw-animate-css": "^1.3.6",
"typescript": "^5"
}
}
}
18 changes: 17 additions & 1 deletion ui/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import React from "react";
import React, { useState } from "react";
import Link from "next/link";
import { useWallet } from "@solana/wallet-adapter-react";
import {
Expand All @@ -10,10 +10,12 @@ import {
CardTitle,
} from "../components/ui/card";
import { Button } from "../components/ui/button";
import { BuilderOnboardingDialog, BuilderOnboardingStatus } from "../components/user";
import { User, ArrowUpDown, TrendingUp, Wallet } from "lucide-react";

export default function Home() {
const { connected, publicKey } = useWallet();
const [onboardingOpen, setOnboardingOpen] = useState(false);

const features = [
{
Expand Down Expand Up @@ -71,6 +73,11 @@ export default function Home() {
</div>
)}
</div>

{/* Builder Onboarding Status */}
<div className="max-w-md mx-auto mb-8">
<BuilderOnboardingStatus onOpenDialog={() => setOnboardingOpen(true)} />
</div>
</div>

{/* Features Grid */}
Expand Down Expand Up @@ -106,6 +113,15 @@ export default function Home() {
);
})}
</div>

{/* Builder Onboarding Dialog */}
<BuilderOnboardingDialog
open={onboardingOpen}
onOpenChange={setOnboardingOpen}
onComplete={() => {
console.log('Builder onboarding completed!');
}}
/>
</div>
);
}
5 changes: 4 additions & 1 deletion ui/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ export { default as Header } from './layout/Header';
export { PageLayout } from './layout/PageLayout';

// Wallet Components
export { default as WalletButton } from './wallet/WalletButton';
export { default as WalletButton } from './wallet/WalletButton';

// User Components
export * from './user';
147 changes: 147 additions & 0 deletions ui/src/components/perps/BuilderFeeInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import React, { useState } from 'react';
import { Info, Settings } from 'lucide-react';
import { Card, CardContent, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui';

interface BuilderFeeInfoProps {
feeInfo: {
feeAmount: string;
feeBps: string;
feePercentage: string;
formattedFee: string;
} | null;
canUseBuilderCodes: boolean;
isOnboardingComplete: boolean;
useSwift: boolean;
orderType: string;
}

export function BuilderFeeInfo({
feeInfo,
canUseBuilderCodes,
isOnboardingComplete,
useSwift,
orderType,
}: BuilderFeeInfoProps) {
const [showDetails, setShowDetails] = useState(false);

const shouldShowBuilderInfo = useSwift && (orderType === "market" || orderType === "limit");

if (!shouldShowBuilderInfo) {
return null;
}

return (
<div className="space-y-2">
{/* Builder Fee Display */}
{feeInfo && canUseBuilderCodes && (
<Card className="border-yellow-500/20 bg-yellow-900/5">
<CardContent className="p-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-sm text-yellow-400">Builder Fee:</span>
<span className="text-sm font-medium text-white">
{feeInfo.formattedFee}
</span>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => setShowDetails(!showDetails)}
className="text-gray-400 hover:text-white transition-colors"
>
<Info className="h-3 w-3" />
</button>
</TooltipTrigger>
<TooltipContent>
<p>Builder fees help optimize your order routing and execution</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<span className="text-xs text-gray-400">
{feeInfo.feePercentage}%
</span>
</div>

{showDetails && (
<div className="mt-2 pt-2 border-t border-yellow-500/20">
<div className="text-xs text-gray-300 space-y-1">
<div className="flex justify-between">
<span>Fee Rate:</span>
<span>{feeInfo.feeBps} bps</span>
</div>
<div className="flex justify-between">
<span>Fee Amount:</span>
<span>${feeInfo.feeAmount}</span>
</div>
<div className="flex justify-between">
<span>Percentage:</span>
<span>{feeInfo.feePercentage}%</span>
</div>
</div>
<p className="text-xs text-gray-400 mt-2">
This fee enables premium order routing through our builder network,
potentially improving fill rates and reducing slippage.
</p>
</div>
)}
</CardContent>
</Card>
)}

{/* Onboarding Notice */}
{!isOnboardingComplete && (
<Card className="border-orange-500/20 bg-orange-900/5">
<CardContent className="p-3">
<div className="flex items-center gap-2">
<Settings className="h-4 w-4 text-orange-400" />
<div className="flex-1">
<p className="text-sm text-orange-400 font-medium">
Setup Required for Optimized Routing
</p>
<p className="text-xs text-gray-400">
Complete builder onboarding to enable premium order routing features
</p>
</div>
</div>
</CardContent>
</Card>
)}

{/* Swift Info */}
{useSwift && isOnboardingComplete && (
<div className="flex items-center gap-2 text-xs text-gray-400">
<div className="w-2 h-2 bg-green-400 rounded-full"></div>
<span>Swift routing enabled with builder optimization</span>
</div>
)}
</div>
);
}

/**
* Compact inline builder fee display for order previews
*/
interface InlineBuilderFeeProps {
feeInfo: {
formattedFee: string;
} | null;
canUseBuilderCodes: boolean;
}

export function InlineBuilderFee({ feeInfo, canUseBuilderCodes }: InlineBuilderFeeProps) {
if (!feeInfo || !canUseBuilderCodes) {
return null;
}

return (
<div className="mt-2 pt-2 border-t border-gray-600">
<p className="text-yellow-400 text-sm">
• Builder Fee: {feeInfo.formattedFee}
</p>
<p className="text-gray-500 text-xs">
Fee helps optimize your order routing
</p>
</div>
);
}
64 changes: 62 additions & 2 deletions ui/src/components/perps/PerpTradeForm/PerpTradeForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import React from "react";
import { PerpMarketConfig, PositionDirection } from "@drift-labs/sdk";
import { PerpMarketConfig, PositionDirection, BigNum, BASE_PRECISION_EXP, PRICE_PRECISION_EXP, ZERO } from "@drift-labs/sdk";
import { Card, CardContent, CardHeader, CardTitle } from "../../ui/card";
import { Button } from "../../ui/button";
import { FormInput } from "../../ui/form-input";
Expand All @@ -12,7 +12,10 @@ import {
AssetSizeType,
PerpOrderType,
} from "./hooks/usePerpTrading";
import { ENUM_UTILS } from "@drift-labs/common";
import { ENUM_UTILS, MarketId } from "@drift-labs/common";
import { useMarkPriceStore } from "@/stores/MarkPriceStore";
import { useOraclePriceStore } from "@/stores/OraclePriceStore";
import { BuilderFeeInfo, InlineBuilderFee } from "../BuilderFeeInfo";

interface PerpTradeFormProps {
perpMarketConfigs: PerpMarketConfig[];
Expand All @@ -38,6 +41,7 @@ export function PerpTradeForm({
useSwift,
isLoading,
selectedMarketConfig,
minOrderSize,
setOrderType,
setDirection,
setSizeType,
Expand All @@ -52,15 +56,26 @@ export function PerpTradeForm({
setUseSwift,
handleSubmit,
canSubmit,
builderFeeInfo,
isOnboardingComplete,
canUseBuilderCodes,
} = usePerpTrading({ perpMarketConfigs, selectedMarketIndex });

const selectedMarketId = MarketId.createPerpMarket(selectedMarketIndex);
const markPrice = useMarkPriceStore((s) => s.lookup[selectedMarketId.key]?.markPrice ?? ZERO);
const oraclePrice = useOraclePriceStore((s) => s.lookup[selectedMarketId.key]?.price ?? ZERO);

// Use mark price if available, otherwise fallback to oracle price
const currentPrice = !markPrice.eq(ZERO) ? markPrice : oraclePrice;

const isLongSide = ENUM_UTILS.match(direction, PositionDirection.LONG);

const onSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await handleSubmit();
};


if (perpMarketConfigs.length === 0) {
return (
<Card>
Expand Down Expand Up @@ -91,6 +106,15 @@ export function PerpTradeForm({
</CardHeader>
<CardContent>
<form onSubmit={onSubmit} className="space-y-6">
{/* Builder Fee Information */}
<BuilderFeeInfo
feeInfo={builderFeeInfo}
canUseBuilderCodes={canUseBuilderCodes}
isOnboardingComplete={isOnboardingComplete}
useSwift={useSwift}
orderType={orderType}
/>

{/* Position Side */}
<div className="space-y-2">
<div className="flex bg-gray-800 rounded-lg p-1">
Expand Down Expand Up @@ -173,6 +197,26 @@ export function PerpTradeForm({
required
/>
</div>

{/* Minimum Order Size Info */}
{selectedMarketConfig && !minOrderSize.eq(ZERO) && (
<div className="text-sm text-gray-400">
{sizeType === "base" ? (
<>Minimum order size: {BigNum.from(minOrderSize, BASE_PRECISION_EXP).prettyPrint()} {selectedMarketConfig.baseAssetSymbol}</>
) : !currentPrice.eq(ZERO) ? (
<>
Minimum order size: {BigNum.from(minOrderSize, BASE_PRECISION_EXP).prettyPrint()} {selectedMarketConfig.baseAssetSymbol}
{" (≈$"}
{BigNum.from(minOrderSize, BASE_PRECISION_EXP)
.mul(BigNum.from(currentPrice, PRICE_PRECISION_EXP))
.prettyPrint()}
{")"}
</>
) : (
<>Minimum order size: {BigNum.from(minOrderSize, BASE_PRECISION_EXP).prettyPrint()} {selectedMarketConfig.baseAssetSymbol} (price loading...)</>
)}
</div>
)}

{/* Price Inputs Based on Order Type */}
<div className="grid md:grid-cols-2 gap-4">
Expand Down Expand Up @@ -364,6 +408,22 @@ export function PerpTradeForm({
<p className="text-orange-400">• Reduce Only</p>
)}
{postOnly && <p className="text-purple-400">• Post Only</p>}
{useSwift && <p className="text-blue-400">• Using Swift</p>}

{/* Builder Fee Information */}
<InlineBuilderFee
feeInfo={builderFeeInfo}
canUseBuilderCodes={canUseBuilderCodes}
/>

{/* Onboarding Notice */}
{useSwift && !isOnboardingComplete && (orderType === "market" || orderType === "limit") && (
<div className="mt-2 pt-2 border-t border-yellow-600/30">
<p className="text-yellow-400 text-sm">
• Complete builder setup for optimized routing
</p>
</div>
)}
</div>
</div>
</div>
Expand Down
Loading