Skip to content

Commit

Permalink
Merge branch 'feat/swap-lend-box-v2' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
borcherd committed Jan 17, 2025
2 parents 9d4f877 + a1c2692 commit 864ff06
Show file tree
Hide file tree
Showing 47 changed files with 2,324 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ActionBox, ActionBoxProvider } from "~/components/action-box-v2";
import { Button } from "~/components/ui/button";

import { MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2";
import { ArenaPoolV2Extended } from "~/types/trade-store.types";
import { ArenaPoolV2Extended, GroupStatus } from "~/types/trade-store.types";
import { ClosePosition } from "./components";

type PositionActionButtonsProps = {
Expand All @@ -37,10 +37,11 @@ export const PositionActionButtons = ({
const { connection } = useConnection();
const { wallet, connected } = useWallet();

const [refreshGroup, nativeSolBalance, positionsByGroupPk] = useTradeStoreV2((state) => [
const [refreshGroup, nativeSolBalance, positionsByGroupPk, banksByBankPk] = useTradeStoreV2((state) => [
state.refreshGroup,
state.nativeSolBalance,
state.positionsByGroupPk,
state.banksByBankPk,
]);

const depositBanks = React.useMemo(() => {
Expand All @@ -64,9 +65,16 @@ export const PositionActionButtons = ({
return borrowBank;
}, [arenaPool]);

const extendedBankInfos = React.useMemo(() => {
const banks = Object.values(banksByBankPk);
const uniqueBanksMap = new Map(banks.map((bank) => [bank.info.state.mint.toBase58(), bank]));
const uniqueBanks = Array.from(uniqueBanksMap.values());
return uniqueBanks;
}, [banksByBankPk]);

return (
<ActionBoxProvider
banks={[arenaPool.tokenBank, arenaPool.quoteBank]}
banks={extendedBankInfos}
nativeSolBalance={nativeSolBalance}
marginfiClient={client}
selectedAccount={selectedAccount}
Expand All @@ -76,13 +84,13 @@ export const PositionActionButtons = ({
hidePoolStats={["type"]}
>
<div className={cn("flex gap-3 w-full", className)}>
<ActionBox.Lend
<ActionBox.DepositSwap
isDialog={true}
useProvider={true}
lendProps={{
depositSwapProps={{
connected: connected,
requestedLendType: ActionType.Deposit,
requestedBank: depositBanks[0] ?? undefined,
requestedDepositBank: depositBanks[0],
requestedSwapBank: arenaPool.status === GroupStatus.LONG ? borrowBank ?? undefined : undefined,
showAvailableCollateral: false,
captureEvent: () => {
capture("position_add_btn_click", {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,20 @@ export const PositionListItem = ({ arenaPool }: props) => {
});

return (
<TableRow
className="cursor-pointer transition-colors hover:bg-accent/75"
onClick={(e) => {
if (
e.target instanceof HTMLButtonElement ||
e.target instanceof HTMLAnchorElement ||
e.target instanceof SVGElement ||
(e.target instanceof Element &&
(e.target.hasAttribute("data-state") || e.target.closest("[data-command-item]")))
)
return;
router.push(`/trade/${arenaPool.groupPk.toBase58()}`);
}}
>
<TableRow className="transition-colors hover:bg-accent/75">
<TableCell>
{arenaPool.status === GroupStatus.LONG ? (
<Badge className="w-14 bg-success uppercase font-medium justify-center">long</Badge>
) : (
<Badge className="w-14 bg-error uppercase font-medium justify-center">short</Badge>
)}
</TableCell>
<TableCell>
<div className="flex items-center gap-2 justify-start">
<TableCell
onClick={(e) => {
router.push(`/trade/${arenaPool.groupPk.toBase58()}`);
}}
>
<div className="flex items-center gap-2 justify-start cursor-pointer">
<div className="relative w-max flex items-center justify-center">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
Expand Down
37 changes: 37 additions & 0 deletions apps/marginfi-v2-ui/src/pages/deposit-swap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from "react";
import { ActionBox as ActionBoxV2 } from "@mrgnlabs/mrgn-ui";
import { capture } from "@mrgnlabs/mrgn-utils";
import { useMrgnlendStore } from "~/store";

import { PageHeading } from "~/components/common/PageHeading";

import { Loader } from "~/components/ui/loader";
import { useWallet } from "~/components/wallet-v2";

export default function DepositSwapPage() {
const [initialized] = useMrgnlendStore((state) => [state.initialized, state.extendedBankInfos]);
const { connected } = useWallet();

return (
<>
{!initialized && <Loader label="Loading marginfi..." className="mt-16" />}

{initialized && (
<div className="w-full max-w-7xl mx-auto mb-20 px-5">
<PageHeading heading="✨ Deposit Swap" body={<p>Swap any token and deposit in your chosen collateral.</p>} />
<ActionBoxV2.DepositSwap
useProvider={true}
depositSwapProps={{
connected: connected,
requestedDepositBank: undefined,
requestedSwapBank: undefined,
captureEvent: (event, properties) => {
capture(event, properties);
},
}}
/>
</div>
)}
</>
);
}
42 changes: 41 additions & 1 deletion packages/mrgn-ui/src/components/action-box-v2/action-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@ import React from "react";

import { ActionType } from "@mrgnlabs/marginfi-v2-ui-state";

import { LendBox, LendBoxProps, LoopBox, LoopBoxProps, RepayCollatBox, StakeBox, StakeBoxProps } from "./actions";
import {
LendBox,
LendBoxProps,
LoopBox,
LoopBoxProps,
RepayCollatBox,
StakeBox,
StakeBoxProps,
DepositSwapBoxProps,
DepositSwapBox,
} from "./actions";
import { ActionDialogWrapper, ActionBoxWrapper, ActionBoxNavigator } from "./components";
import { useActionBoxContext, useStakeBoxContext } from "./contexts";
import {
Expand All @@ -14,6 +24,7 @@ import {
RequiredStakeBoxProps,
RequiredRepayBoxProps,
RequiredLoopBoxProps,
RequiredDepositSwapBoxProps,
} from "./types";

const ActionBox: ActionBoxComponent = (props) => {
Expand Down Expand Up @@ -63,6 +74,35 @@ const Lend = (props: ActionBoxProps & { lendProps: RequiredLendBoxProps | LendBo
};
ActionBox.Lend = Lend;

const DepositSwap = (
props: ActionBoxProps & { depositSwapProps: RequiredDepositSwapBoxProps | DepositSwapBoxProps; useProvider?: boolean }
) => {
const contextProps = useActionBoxContext();
const { depositSwapProps, useProvider, ...actionBoxProps } = props;

let combinedProps: DepositSwapBoxProps;

if (useProvider && contextProps) {
combinedProps = {
...contextProps,
...(depositSwapProps as RequiredDepositSwapBoxProps),
};
} else {
combinedProps = depositSwapProps as DepositSwapBoxProps;
}

return (
<ActionBox {...actionBoxProps}>
<ActionBoxWrapper showSettings={false} isDialog={props.isDialog} actionMode={ActionType.Deposit}>
<ActionBoxNavigator selectedAction={ActionType.Deposit}>
<DepositSwapBox {...combinedProps} isDialog={props.isDialog} />
</ActionBoxNavigator>
</ActionBoxWrapper>
</ActionBox>
);
};
ActionBox.DepositSwap = DepositSwap;

const BorrowLend = (
props: ActionBoxProps & { lendProps: RequiredLendBoxProps | LendBoxProps; useProvider?: boolean }
) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React from "react";

import { ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state";
import { formatAmount } from "@mrgnlabs/mrgn-utils";
import { usdFormatter, tokenPriceFormatter } from "@mrgnlabs/mrgn-common";

import { Input } from "~/components/ui/input";

import { BankSelect, DepositSwapAction } from "./components";

type ActionInputProps = {
amountRaw: string;
amount: number | null;
nativeSolBalance: number;
walletAmount: number | undefined;
maxAmount: number;
banks: ExtendedBankInfo[];
selectedBank: ExtendedBankInfo | null;
lendMode: ActionType;

connected: boolean;
showCloseBalance?: boolean;
isDialog?: boolean;
showTokenSelection?: boolean;
showTokenSelectionGroups?: boolean;
isMini?: boolean;

isInputDisabled?: boolean;

setAmountRaw: (amount: string) => void;
setSelectedBank: (bank: ExtendedBankInfo | null) => void;
};

export const ActionInput = ({
banks,
nativeSolBalance,
walletAmount,
maxAmount,
showCloseBalance,
connected,
isDialog,
showTokenSelection,
showTokenSelectionGroups,
amountRaw,
amount,
selectedBank,
lendMode,
isInputDisabled: _isInputDisabled,
setAmountRaw,
setSelectedBank,
}: ActionInputProps) => {
const amountInputRef = React.useRef<HTMLInputElement>(null);

const numberFormater = React.useMemo(() => new Intl.NumberFormat("en-US", { maximumFractionDigits: 10 }), []);

const isInputDisabled = React.useMemo(
() => (maxAmount === 0 && !showCloseBalance) || _isInputDisabled,
[maxAmount, showCloseBalance, _isInputDisabled]
);

const formatAmountCb = React.useCallback(
(newAmount: string, bank: ExtendedBankInfo | null) => {
return formatAmount(newAmount, maxAmount, bank, numberFormater);
},
[maxAmount, numberFormater]
);

const handleInputChange = React.useCallback(
(newAmount: string) => {
setAmountRaw(formatAmountCb(newAmount, selectedBank));
},
[formatAmountCb, setAmountRaw, selectedBank]
);

const isTokenSelectionAvailable = React.useMemo(() => {
if (showTokenSelection === undefined) return !isDialog;
else return showTokenSelection;
}, [showTokenSelection, isDialog]);

return (
<div className="rounded-lg p-2.5 bg-mfi-action-box-background-dark">
<div className="flex justify-center gap-1 items-center font-medium text-3xl">
<div className="w-full flex-auto max-w-[162px]">
<BankSelect
selectedBank={selectedBank}
setSelectedBank={(bank) => {
setSelectedBank(bank);
}}
isSelectable={isTokenSelectionAvailable}
showTokenSelectionGroups={showTokenSelectionGroups}
banks={banks}
nativeSolBalance={nativeSolBalance}
lendMode={lendMode}
connected={connected}
/>
</div>
<div className="flex-auto flex flex-col gap-0 items-end">
<Input
type="text"
ref={amountInputRef}
inputMode="decimal"
value={amountRaw}
disabled={isInputDisabled}
onChange={(e) => handleInputChange(e.target.value)}
placeholder="0"
className="bg-transparent shadow-none min-w-[130px] text-right h-auto py-0 pr-0 outline-none focus-visible:outline-none focus-visible:ring-0 border-none text-base font-medium"
/>
{amount !== null && amount > 0 && selectedBank && (
<span className="text-xs text-muted-foreground font-light">
{tokenPriceFormatter(amount * selectedBank.info.oraclePrice.priceRealtime.price.toNumber())}
</span>
)}
</div>
</div>
{!isInputDisabled && (
<DepositSwapAction
walletAmount={walletAmount}
maxAmount={maxAmount}
onSetAmountRaw={(amount) => handleInputChange(amount)}
selectedBank={selectedBank}
lendMode={lendMode}
/>
)}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from "react";

import { ExtendedBankInfo, ActionType } from "@mrgnlabs/marginfi-v2-ui-state";
import { computeBankRate, LendingModes } from "@mrgnlabs/mrgn-utils";

import { SelectedBankItem, BankListWrapper } from "~/components/action-box-v2/components";

import { BankTrigger, BankList } from "./components";

type BankSelectProps = {
selectedBank: ExtendedBankInfo | null;
banks: ExtendedBankInfo[];
nativeSolBalance: number;
lendMode: ActionType;
connected: boolean;
isSelectable?: boolean;
showTokenSelectionGroups?: boolean;
setSelectedBank: (selectedBank: ExtendedBankInfo | null) => void;
};

export const BankSelect = ({
selectedBank,
banks,
nativeSolBalance,
lendMode,
connected,
isSelectable = true,
showTokenSelectionGroups,
setSelectedBank,
}: BankSelectProps) => {
// idea check list if banks[] == 1 make it unselectable
const [isOpen, setIsOpen] = React.useState(false);

const lendingMode = React.useMemo(
() =>
lendMode === ActionType.Deposit || lendMode === ActionType.Withdraw ? LendingModes.LEND : LendingModes.BORROW,
[lendMode]
);
return (
<>
<BankListWrapper
isOpen={isOpen}
setIsOpen={setIsOpen}
Trigger={<BankTrigger selectedBank={selectedBank} lendingMode={lendingMode} isOpen={isOpen} />}
Content={
<BankList
isOpen={isOpen}
onClose={() => setIsOpen(false)}
selectedBank={selectedBank}
onSetSelectedBank={setSelectedBank}
lendMode={lendMode}
banks={banks}
nativeSolBalance={nativeSolBalance}
connected={connected}
showTokenSelectionGroups={showTokenSelectionGroups}
/>
}
/>
</>
);
};
Loading

0 comments on commit 864ff06

Please sign in to comment.