Skip to content

Commit

Permalink
chore: stake-unstake work
Browse files Browse the repository at this point in the history
  • Loading branch information
borcherd committed Oct 11, 2024
1 parent be8e6f0 commit c1ea5c5
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 72 deletions.
98 changes: 60 additions & 38 deletions apps/marginfi-v2-ui/src/components/common/Mint/MintCardWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,24 @@ import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { ActionBoxDialog } from "~/components/common/ActionBox";
import { LST_MINT } from "~/store/lstStore";

/// XXX
import { useWallet } from "~/components/wallet-v2/hooks/use-wallet.hook";

/// YYY

interface MintCardWrapperProps {
mintCard: MintCardProps;
}

export const MintCardWrapper: React.FC<MintCardWrapperProps> = ({ mintCard, ...props }) => {
const [extendedBankInfos] = useMrgnlendStore((state) => [state.extendedBankInfos]);

// XXX

const { connected } = useWallet();

/// YYY

const [requestedAction, setRequestedAction] = React.useState<ActionType>(ActionType.MintLST);

const transformedActionGate = React.useMemo(() => getBlockedActions(), []);
Expand All @@ -31,6 +42,7 @@ export const MintCardWrapper: React.FC<MintCardWrapperProps> = ({ mintCard, ...p
null,
[extendedBankInfos]
);

return (
<Card variant="default" className="relative">
<CardHeader className="pt-8">
Expand Down Expand Up @@ -73,33 +85,42 @@ export const MintCardWrapper: React.FC<MintCardWrapperProps> = ({ mintCard, ...p
)}

{mintCard.title === "LST" ? (
<ActionBoxDialog
requestedAction={requestedAction}
requestedBank={requestedAction === ActionType.UnstakeLST ? requestedBank : null}
>
<div className="flex items-center gap-2">
<Button
variant="secondary"
size="lg"
className="mt-4"
onClick={() => {
setRequestedAction(ActionType.MintLST);
}}
>
Stake {mintCard.title}
</Button>
<Button
variant="outline-dark"
size="lg"
className="mt-4 hover:text-primary"
onClick={() => {
setRequestedAction(ActionType.UnstakeLST);
}}
>
Unstake {mintCard.title}
</Button>
</div>
</ActionBoxDialog>
<div className="flex items-center gap-2">
<ActionBox.Stake
isDialog={true}
useProvider={true}
stakeProps={{
connected: connected,
requestedActionType: ActionType.MintLST,
}}
dialogProps={{
trigger: (
<Button variant="secondary" size="lg" className="mt-4">
Stake {mintCard.title}
</Button>
),
title: "Stake LST",
}}
/>

<ActionBox.Stake
isDialog={true}
useProvider={true}
stakeProps={{
connected: connected,
requestedActionType: ActionType.UnstakeLST,
requestedBank: extendedBankInfos.find((bank) => bank?.info?.state?.mint.equals(LST_MINT)),
}}
dialogProps={{
trigger: (
<Button variant="outline-dark" size="lg" className="mt-4 hover:text-primary">
Unstake {mintCard.title}
</Button>
),
title: "Unstake LST",
}}
/>
</div>
) : transformedActionGate?.find((value) => value === ActionType.MintYBX) ? (
<div className="flex items-center gap-2">
<Button
Expand All @@ -116,17 +137,18 @@ export const MintCardWrapper: React.FC<MintCardWrapperProps> = ({ mintCard, ...p
</Button>
</div>
) : (
<ActionBoxDialog
requestedAction={ActionType.MintYBX}
requestedBank={null}
//requestedToken={new PublicKey("2s37akK2eyBbp8DZgCm7RtsaEz8eJP3Nxd4urLHQv7yB")}
>
<div className="flex items-center gap-2">
<Button variant="secondary" size="lg" className="mt-4">
Stake
</Button>
</div>
</ActionBoxDialog>
// <ActionBoxDialog
// requestedAction={ActionType.MintYBX}
// requestedBank={null}
// //requestedToken={new PublicKey("2s37akK2eyBbp8DZgCm7RtsaEz8eJP3Nxd4urLHQv7yB")}
// >
// <div className="flex items-center gap-2">
// <Button variant="secondary" size="lg" className="mt-4">
// Stake
// </Button>
// </div>
// </ActionBoxDialog>
<></>
)}
</CardContent>
</Card>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ import { PortfolioUserStats, PortfolioAssetCard, PortfolioAssetCardSkeleton } fr
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "~/components/ui/tooltip";
import { ActionBox } from "@mrgnlabs/mrgn-ui";

/// XXX
import { useWallet } from "~/components/wallet-v2/hooks/use-wallet.hook";

/// YYY

export const LendingPortfolio = () => {
const router = useRouter();

Expand Down Expand Up @@ -99,12 +94,6 @@ export const LendingPortfolio = () => {
}
}, [accountSummary.healthFactor]);

// XXX

const { connected } = useWallet();

/// YYY

if (!lendingBanks.length && !borrowingBanks.length && isStoreInitialized) {
return (
<p className="text-center mt-4 text-muted-foreground">
Expand Down Expand Up @@ -170,18 +159,6 @@ export const LendingPortfolio = () => {
netValue={accountNetValue}
points={numeralFormatter(userPointsData.totalPoints)}
/>
<ActionBox.Stake
isDialog={true}
useProvider={true}
stakeProps={{
connected: connected,
requestedActionType: ActionType.MintLST,
}}
dialogProps={{
trigger: <div className="flex items-center gap-2 w-full">Dit is een trigger</div>,
title: "test",
}}
/>
</div>
<div className="flex flex-col md:flex-row justify-between flex-wrap gap-8 md:gap-20">
<div className="flex flex-col flex-1 gap-4 md:min-w-[340px]">
Expand Down
3 changes: 2 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 @@ -180,10 +180,11 @@ const Stake = (
} else {
combinedProps = stakeProps as StakeBoxProps;
}

return (
<ActionBox {...actionBoxProps}>
<ActionBoxWrapper showSettings={true} isDialog={props.isDialog} actionMode={ActionType.MintLST}>
<ActionBoxNavigator selectedAction={ActionType.MintLST}>
<ActionBoxNavigator selectedAction={ActionType.MintLST} bank={combinedProps.requestedBank}>
<StakeBox {...combinedProps} isDialog={props.isDialog} />
</ActionBoxNavigator>
</ActionBoxWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type ActionInputProps = {
showCloseBalance?: boolean;
isDialog?: boolean;
isMini?: boolean;

isSelectable?: boolean;
setAmountRaw: (amount: string) => void;
setSelectedBank: (bank: ExtendedBankInfo | null) => void;
};
Expand All @@ -33,7 +33,7 @@ export const ActionInput = ({
showCloseBalance,
connected,
isDialog,

isSelectable,
amountRaw,
selectedBank,
lendMode,
Expand Down Expand Up @@ -73,6 +73,7 @@ export const ActionInput = ({
nativeSolBalance={nativeSolBalance}
actionMode={lendMode}
connected={connected}
isSelectable={isSelectable}
/>
</div>
<div className="flex-auto">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface BankSelectProps {
nativeSolBalance: number;
actionMode: ActionType;
connected: boolean;
isSelectable?: boolean;

setSelectedBank: (selectedTokenBank: ExtendedBankInfo | null) => void;
}
Expand All @@ -23,10 +24,10 @@ export const BankSelect = ({
nativeSolBalance,
actionMode,
connected,
isSelectable,

setSelectedBank,
}: BankSelectProps) => {
const isSelectable = React.useMemo(() => true, []);
const [isOpen, setIsOpen] = React.useState(false);

return (
Expand All @@ -50,6 +51,7 @@ export const BankSelect = ({
banks={banks}
nativeSolBalance={nativeSolBalance}
connected={connected}
isSelectable={isSelectable}
/>
}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from "react";
import { ActionInput } from "./components/action-input";
import { MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2";
import { AccountSummary, ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state";
import { PreviousTxn } from "@mrgnlabs/mrgn-utils";
import { LstData, PreviousTxn } from "@mrgnlabs/mrgn-utils";
import { useStakeBoxStore } from "./store";
import { useActionAmounts } from "~/components/action-box-v2/hooks";
import { AmountPreview } from "./components/amount-preview";
Expand All @@ -12,6 +12,10 @@ import { StatsPreview } from "./components/stats-preview";
import { WalletContextStateOverride } from "~/components/wallet-v2/hooks/use-wallet.hook";
import { WalletContextState } from "@solana/wallet-adapter-react";
import { useStakeSimulation } from "./hooks";
import { fetchLstData } from "./utils";

import { useConnection } from "~/hooks/use-connection";
import { Connection } from "@solana/web3.js";

export type StakeBoxProps = {
nativeSolBalance: number;
Expand Down Expand Up @@ -79,6 +83,8 @@ export const StakeBox = ({
state.setErrorMessage,
]);

const isSelectable = React.useMemo(() => (requestedBank?.meta.tokenSymbol === "LST" ? false : true), [requestedBank]);

const { amount, debouncedAmount, walletAmount, maxAmount } = useActionAmounts({
amountRaw,
selectedBank,
Expand All @@ -88,6 +94,8 @@ export const StakeBox = ({

const {} = useStakeSimulation({});

const [lstData, setLstData] = React.useState<LstData | null>(null);

return (
<>
<div className="mb-6">
Expand All @@ -98,11 +106,12 @@ export const StakeBox = ({
amountRaw={amountRaw}
maxAmount={maxAmount}
connected={connected}
selectedBank={selectedBank}
lendMode={ActionType.MintLST}
isDialog={true}
selectedBank={requestedBank ?? selectedBank}
lendMode={actionMode}
isDialog={isDialog}
setAmountRaw={setAmountRaw}
setSelectedBank={setSelectedBank}
isSelectable={isSelectable}
/>
</div>
<div className="mb-6">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Connection, PublicKey } from "@solana/web3.js";
import * as solanaStakePool from "@solana/spl-stake-pool";
import { PERIOD, calcYield, fetchAndParsePricesCsv, getPriceRangeFromPeriod } from "@mrgnlabs/mrgn-utils";

const STAKE_POOL_ID = new PublicKey("DqhH94PjkZsjAqEze2BEkWhFQJ6EyU6MdtMphMgnXqeK");
const SOLANA_COMPASS_PRICES_URL =
"https://raw.githubusercontent.com/glitchful-dev/sol-stake-pool-apy/master/db/lst.csv";

export async function fetchLstData(connection: Connection): Promise<LstData> {
const [stakePoolInfo, stakePoolAccount, solanaCompassPrices] = await Promise.all([
solanaStakePool.stakePoolInfo(connection, STAKE_POOL_ID),
solanaStakePool.getStakePoolAccount(connection, STAKE_POOL_ID),
// fetch(STAKEVIEW_APP_URL).then((res) => res.json()),
fetchAndParsePricesCsv(SOLANA_COMPASS_PRICES_URL),
]);
const stakePool = stakePoolAccount.account.data;

const poolTokenSupply = Number(stakePoolInfo.poolTokenSupply);
const totalLamports = Number(stakePoolInfo.totalLamports);
const lastPoolTokenSupply = Number(stakePoolInfo.lastEpochPoolTokenSupply);
const lastTotalLamports = Number(stakePoolInfo.lastEpochTotalLamports);

const solDepositFee = stakePoolInfo.solDepositFee.denominator.eqn(0)
? 0
: stakePoolInfo.solDepositFee.numerator.toNumber() / stakePoolInfo.solDepositFee.denominator.toNumber();

const lstSolValue = poolTokenSupply > 0 ? totalLamports / poolTokenSupply : 1;

let projectedApy: number;
if (lastTotalLamports === 0 || lastPoolTokenSupply === 0) {
projectedApy = 0.08;
} else {
const priceRange = getPriceRangeFromPeriod(solanaCompassPrices, PERIOD.DAYS_7);
if (!priceRange) {
throw new Error("No price data found for the specified period!");
}
projectedApy = calcYield(priceRange).apy;
}

// commenting out until authorization for stakeview url is approved
// if (projectedApy < 0.08) {
// // temporarily use baseline validator APY waiting for a few epochs to pass
// const baselineValidatorData = apyData.validators.find((validator: any) => validator.id === BASELINE_VALIDATOR_ID);
// if (baselineValidatorData) projectedApy = baselineValidatorData.apy;
// }

return {
poolAddress: new PublicKey(stakePoolInfo.address),
tvl: totalLamports / 1e9,
projectedApy,
lstSolValue,
solDepositFee,
accountData: stakePool,
validatorList: stakePoolInfo.validatorList.map((v) => new PublicKey(v.voteAccountAddress)),
};
}

export interface LstData {
poolAddress: PublicKey;
tvl: number;
projectedApy: number;
lstSolValue: number;
solDepositFee: number;
accountData: solanaStakePool.StakePool;
validatorList: PublicKey[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./fetch-lst-data";
Loading

0 comments on commit c1ea5c5

Please sign in to comment.