Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mfi-trading): tradingbox v2 #1003

Merged
merged 27 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9bebec7
feat: store integration yield page & mrgnclient helper functions (wip)
k0beLeenders Dec 5, 2024
1cca08f
feat: initial work
borcherd Dec 11, 2024
c5eb15d
feat: refactor stats
borcherd Dec 11, 2024
f8efa69
feat: ui improvements
borcherd Dec 11, 2024
b20d20c
feat: tx simulation
borcherd Dec 11, 2024
0ab395d
feat: various ui fixes & simulation finetuning
borcherd Dec 12, 2024
8c62a3f
feat: tx execution
borcherd Dec 12, 2024
9e1835b
chore: remove logs
borcherd Dec 12, 2024
cddee18
feat: qa changes
borcherd Dec 12, 2024
4e56051
chores: QA fixes & todo implementations
borcherd Dec 13, 2024
db024ec
feat: TODO's & QA fixes
borcherd Dec 13, 2024
ee0da2f
chore: styling changes
borcherd Dec 13, 2024
6749a6c
chore: remove logs
borcherd Dec 13, 2024
aea0473
feat: taillored function to looping & updated formatted
borcherd Dec 13, 2024
2e45be1
chore: remove log
borcherd Dec 13, 2024
e943068
chore: use provided tradeside
borcherd Dec 13, 2024
890f671
feat: updated onComplete and added short-long restriction check
borcherd Dec 13, 2024
d7ed08b
feat: tx fetching & simulation refactor & qa changes
borcherd Dec 13, 2024
4318c16
feat: update stats
borcherd Dec 13, 2024
a90ca74
chore: add url to oracle img
borcherd Dec 13, 2024
5eaed23
feat: better handeling for new accounts & seperated account tx
k0beLeenders Dec 13, 2024
4ccbf66
chore: styling changes
borcherd Dec 13, 2024
89679e0
feat: tx account creation work **WIP**
borcherd Dec 13, 2024
fbc32c4
fix: account tx not being set
k0beLeenders Dec 13, 2024
2b44094
fix: small improvements & refactor
k0beLeenders Dec 14, 2024
600510f
fix: added marginfi account creation
k0beLeenders Dec 14, 2024
8224aee
fix: ata creation missing due to missing metadata
k0beLeenders Dec 14, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -123,26 +123,28 @@ const YieldItem = ({
<div className={cn("grid gap-4items-center", className, connected ? "grid-cols-7" : "grid-cols-6")}>
<div className="flex items-center gap-2">
<Image
src={bank.meta.tokenLogoUri}
alt={bank.meta.tokenSymbol}
src={pool.tokenBank.meta.tokenLogoUri}
alt={pool.tokenBank.meta.tokenSymbol}
width={24}
height={24}
className="rounded-full"
/>
{bank.meta.tokenSymbol}
{pool.tokenBank.meta.tokenSymbol}
</div>
<div className="flex flex-col xl:gap-2 xl:flex-row xl:items-baseline">
<span className="text-xl">{numeralFormatter(bank.info.state.totalDeposits)}</span>
<span className="text-xl">{numeralFormatter(pool.tokenBank.info.state.totalDeposits)}</span>
<span className="text-sm text-muted-foreground">
{usdFormatter.format(bank.info.state.totalDeposits * bank.info.oraclePrice.priceRealtime.price.toNumber())}
{usdFormatter.format(
pool.tokenBank.info.state.totalDeposits * pool.tokenBank.info.oraclePrice.priceRealtime.price.toNumber()
)}
</span>
</div>

<div className="text-mrgn-success text-right w-32">
{percentFormatter.format(aprToApy(bank.info.state.lendingRate))}
{percentFormatter.format(aprToApy(pool.tokenBank.info.state.lendingRate))}
</div>
<div className="text-mrgn-warning text-right w-32">
{percentFormatter.format(aprToApy(bank.info.state.borrowingRate))}
{percentFormatter.format(aprToApy(pool.tokenBank.info.state.borrowingRate))}
</div>
<div className="flex justify-center">
<Link href="https://x.com/marginfi" target="_blank">
Expand All @@ -159,8 +161,8 @@ const YieldItem = ({
<div className="pl-2 text-lg flex flex-col xl:gap-1 xl:flex-row xl:items-baseline">
{isProvidingLiquidity && bank.isActive && (
<>
{numeralFormatter(bank.position.amount)}
<span className="text-muted-foreground text-sm">{bank.meta.tokenSymbol}</span>
{numeralFormatter(pool.tokenBank.position.amount)}
<span className="text-muted-foreground text-sm">{pool.tokenBank.meta.tokenSymbol}</span>
</>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { IconLoader2 } from "@tabler/icons-react";
import React from "react";

import { WalletButton } from "~/components/wallet-v2";
import { Button } from "~/components/ui/button";
import { cn } from "@mrgnlabs/mrgn-utils";

type ActionButtonProps = {
isLoading: boolean;
isEnabled: boolean;
buttonLabel: string;
connected?: boolean;
handleAction: () => void;
tradeState: "long" | "short";
};

export const ActionButton = ({
isLoading,
isEnabled,
buttonLabel,
connected = false,
handleAction,
tradeState,
}: ActionButtonProps) => {
if (!connected) {
return <WalletButton className="w-full py-5 bg-muted-foreground" showWalletInfo={false} />;
}

return (
<Button
disabled={isLoading || !isEnabled}
className={cn("w-full", tradeState === "long" && "bg-success", tradeState === "short" && "bg-error")}
onClick={handleAction}
>
{isLoading ? <IconLoader2 className="animate-spin" /> : buttonLabel}
</Button>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./action-button";
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from "react";

import { IconCheck, IconX } from "@tabler/icons-react";

import { IconLoader } from "~/components/ui/icons";
import { SimulationStatus } from "~/components/common/trade-box-v2/utils";

type ActionSimulationStatusProps = {
simulationStatus: SimulationStatus;
hasErrorMessages: boolean;
isActive: boolean;
};

enum SimulationCompleteStatus {
NULL = "NULL",
LOADING = "LOADING",
SUCCESS = "SUCCESS",
ERROR = "ERROR",
}

const ActionSimulationStatus = ({
simulationStatus,
hasErrorMessages = false,
isActive = false,
}: ActionSimulationStatusProps) => {
const [simulationCompleteStatus, setSimulationCompleteStatus] = React.useState<SimulationCompleteStatus>(
SimulationCompleteStatus.NULL
);
const [isNewSimulation, setIsNewSimulation] = React.useState(false);

React.useEffect(() => {
if (simulationStatus === SimulationStatus.SIMULATING || simulationStatus === SimulationStatus.PREPARING) {
setSimulationCompleteStatus(SimulationCompleteStatus.LOADING);
setIsNewSimulation(false);
} else if (hasErrorMessages && !isNewSimulation) {
setSimulationCompleteStatus(SimulationCompleteStatus.ERROR);
} else if (simulationStatus === SimulationStatus.COMPLETE && !isNewSimulation) {
setSimulationCompleteStatus(SimulationCompleteStatus.SUCCESS);
}
}, [simulationStatus, hasErrorMessages, isNewSimulation]);

React.useEffect(() => {
if (!isActive) {
setIsNewSimulation(true);
setSimulationCompleteStatus(SimulationCompleteStatus.NULL);
}
}, [isActive]);

if (!isActive) {
return <div />; // Return empty div to align the settings button
}

return (
<div>
{simulationCompleteStatus === SimulationCompleteStatus.LOADING && (
<p className="text-xs text-muted-foreground/75 flex items-center gap-1 mr-auto">
<IconLoader size={14} /> Simulating transaction...
</p>
)}

{simulationCompleteStatus === SimulationCompleteStatus.SUCCESS && (
<p className="text-xs flex items-center gap-1 mr-auto text-success">
<IconCheck size={14} /> Simulation complete!
</p>
)}

{simulationCompleteStatus === SimulationCompleteStatus.ERROR && (
<p className="text-xs flex items-center gap-1 mr-auto text-error">
<IconX size={14} /> Simulation failed
</p>
)}
</div>
);
};

export { ActionSimulationStatus };
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./action-simuation-status";
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { TradeSide } from "~/components/common/trade-box-v2/utils";
import { ToggleGroup, ToggleGroupItem } from "~/components/ui/toggle-group";

interface ActionToggleProps {
tradeState: TradeSide;
setTradeState: (value: TradeSide) => void;
}

export const ActionToggle = ({ tradeState, setTradeState }: ActionToggleProps) => {
return (
<ToggleGroup
type="single"
className="w-full gap-4 p-0"
value={tradeState}
onValueChange={(value) => {
value && setTradeState(value as TradeSide);
}}
>
<ToggleGroupItem className="w-full border" value="long" aria-label="Toggle long">
Long
</ToggleGroupItem>
<ToggleGroupItem className="w-full border" value="short" aria-label="Toggle short">
Short
</ToggleGroupItem>
</ToggleGroup>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./action-toggle";
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from "react";
import Image from "next/image";
import { Input } from "~/components/ui/input";
import { MaxAction } from "./components";
import { ArenaBank } from "~/store/tradeStoreV2";

interface AmountInputProps {
maxAmount: number;
amount: string;
collateralBank: ArenaBank | null;

handleAmountChange: (value: string) => void;
}

export const AmountInput = ({
amount,
collateralBank,
maxAmount,

handleAmountChange,
}: AmountInputProps) => {
const amountInputRef = React.useRef<HTMLInputElement>(null);

return (
<div className="bg-accent p-2.5 border border-accent/150 rounded-lg">
<div className="flex justify-center gap-1 items-center font-medium ">
<span className="w-full flex items-center gap-1 max-w-[162px] text-muted-foreground text-base">
{collateralBank?.meta.tokenLogoUri && (
<Image
src={collateralBank?.meta.tokenLogoUri}
alt={collateralBank?.meta.tokenSymbol}
width={24}
height={24}
className="bg-background border rounded-full"
/>
)}
{collateralBank?.meta.tokenSymbol.toUpperCase()}
</span>
<div>
<Input
type="text"
ref={amountInputRef}
inputMode="decimal"
value={amount}
onChange={(e) => handleAmountChange(e.target.value)}
placeholder="0"
className="bg-transparent shadow-none min-w-[130px] h-auto py-0 pr-0 text-right outline-none focus-visible:outline-none focus-visible:ring-0 border-none text-base font-medium"
/>
</div>
</div>
<MaxAction maxAmount={maxAmount} collateralBank={collateralBank} setAmount={handleAmountChange} />
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./token-select";
export * from "./max-action";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./max-action";
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { dynamicNumeralFormatter } from "@mrgnlabs/mrgn-common";
import React from "react";
import { ArenaBank } from "~/store/tradeStoreV2";

interface TradeActionProps {
maxAmount: number;
collateralBank: ArenaBank | null;

setAmount: (amount: string) => void;
}

export const MaxAction = ({ maxAmount, collateralBank, setAmount }: TradeActionProps) => {
const maxLabel = React.useMemo((): {
amount: string;
showWalletIcon?: boolean;
label?: string;
} => {
if (!collateralBank) {
return {
amount: "-",
showWalletIcon: false,
};
}

const formatAmount = (maxAmount?: number, symbol?: string) =>
maxAmount !== undefined ? `${dynamicNumeralFormatter(maxAmount)} ${symbol?.toUpperCase()}` : "-";

return {
amount: formatAmount(maxAmount, collateralBank.meta.tokenSymbol),
label: "Wallet: ",
};
}, [collateralBank, maxAmount]);
return (
<>
{collateralBank && (
<ul className="flex flex-col gap-0.5 mt-2 text-xs w-full text-muted-foreground">
<li className="flex justify-between items-center gap-1.5">
<strong className="mr-auto">{maxLabel.label}</strong>
<div className="flex space-x-1">
<div>{maxLabel.amount}</div>

<button
className="cursor-pointer border-b border-transparent transition text-mfi-action-box-highlight hover:border-mfi-action-box-highlight"
disabled={maxAmount === 0}
onClick={() => {
setAmount(maxAmount.toString());
}}
>
MAX
</button>
</div>
</li>
</ul>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./token-select";
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const TokenSelect = () => {
return <div>TokenSelect</div>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./amount-input";
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from "react";

import { dynamicNumeralFormatter } from "@mrgnlabs/mrgn-common";
import { cn } from "@mrgnlabs/mrgn-utils";

import { IconLoader } from "~/components/ui/icons";
import { ArenaBank } from "~/store/tradeStoreV2";

interface AmountPreviewProps {
tradeSide: "long" | "short";
selectedBank: ArenaBank | null;
amount: number;
isLoading?: boolean;
}

export const AmountPreview = ({ tradeSide, amount, isLoading, selectedBank }: AmountPreviewProps) => {
return (
<div className="flex flex-col gap-6">
<dl className="grid grid-cols-2 gap-y-2 text-sm">
<Stat label={`Size of ${tradeSide}`}>
{isLoading ? <IconLoader size={16} /> : dynamicNumeralFormatter(amount)}{" "}
{selectedBank?.meta.tokenSymbol.toUpperCase()}
</Stat>
</dl>
</div>
);
};

interface StatProps {
label: string;
classNames?: string;
children: React.ReactNode;
style?: React.CSSProperties;
}
const Stat = ({ label, classNames, children, style }: StatProps) => {
return (
<>
<dt className="text-muted-foreground">{label}</dt>
<dd className={cn("flex justify-end text-right items-center gap-2", classNames)} style={style}>
{children}
</dd>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./amount-preview";
Loading
Loading