From c70c83bbb7ca5fc675ec9cba61c474d56d6301ce Mon Sep 17 00:00:00 2001 From: borcherd Date: Tue, 22 Oct 2024 15:51:27 +0800 Subject: [PATCH 01/14] feat: txn composing --- .../LendingPortfolio/LendingPortfolio.tsx | 215 +++++++++++++++++- .../common/Portfolio/rewards/index.ts | 1 + .../Portfolio/rewards/rewards-dialog.tsx | 33 +++ 3 files changed, 246 insertions(+), 3 deletions(-) create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/rewards/index.ts create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/rewards/rewards-dialog.tsx diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/LendingPortfolio/LendingPortfolio.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/LendingPortfolio/LendingPortfolio.tsx index 5de479f94e..a8410e9d27 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/LendingPortfolio/LendingPortfolio.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/LendingPortfolio/LendingPortfolio.tsx @@ -4,7 +4,7 @@ import { useRouter } from "next/router"; import { IconInfoCircle } from "@tabler/icons-react"; -import { numeralFormatter } from "@mrgnlabs/mrgn-common"; +import { nativeToUi, numeralFormatter, Wallet } from "@mrgnlabs/mrgn-common"; import { usdFormatter, usdFormatterDyn } from "@mrgnlabs/mrgn-common"; import { ActiveBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { LendingModes } from "@mrgnlabs/mrgn-utils"; @@ -16,23 +16,185 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "~/comp import { WalletButton } from "~/components/wallet-v2"; import { useWallet } from "~/components/wallet-v2/hooks/use-wallet.hook"; import { Loader } from "~/components/ui/loader"; +import { RewardsDialog } from "../rewards"; +import { ActionComplete } from "~/components/common/ActionComplete"; +import { + PublicKey, + Transaction, + TransactionInstruction, + TransactionMessage, + VersionedTransaction, +} from "@solana/web3.js"; +import { EMISSION_MINT_INFO_MAP } from "~/components/desktop/AssetList/components"; +import { Balance, Bank, makeBundleTipIx, MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; +import BigNumber from "bignumber.js"; +import { margin } from "@mui/system"; + +export type rewardsType = { + totalReward: number; + rewards: { bank: string; amount: number }[]; +}; export const LendingPortfolio = () => { const router = useRouter(); const { connected } = useWallet(); const [walletConnectionDelay, setWalletConnectionDelay] = React.useState(false); - const [isStoreInitialized, sortedBanks, accountSummary, isRefreshingStore] = useMrgnlendStore((state) => [ + const [ + isStoreInitialized, + sortedBanks, + accountSummary, + isRefreshingStore, + marginfiClient, + selectedAccount, + extendedBankInfos, + ] = useMrgnlendStore((state) => [ state.initialized, state.extendedBankInfos, state.accountSummary, state.isRefreshingStore, + state.marginfiClient, + state.selectedAccount, + state.extendedBankInfos, ]); const [setLendingMode] = useUiStore((state) => [state.setLendingMode]); const [userPointsData] = useUserProfileStore((state) => [state.userPointsData]); + const bankAddressesWithEmissions: PublicKey[] = React.useMemo(() => { + if (!selectedAccount) return []; + return [...EMISSION_MINT_INFO_MAP.keys()] + .map((bankMintSymbol) => { + const bankInfo = extendedBankInfos?.find((b) => b.address && b.meta.tokenSymbol === bankMintSymbol); + if (bankInfo?.info.state.emissions.toString() === "1") return bankInfo?.address; + }) + .filter((address) => address !== undefined) as PublicKey[]; + }, [selectedAccount, extendedBankInfos]); // TODO: confirm this is correct. I'm not sure, but some info.state.emissions are 0 and some are 1. For now i'm assuming that the banks with emissions are the ones with state.emissions = 1 + + const [collectTxn, setCollectTxn] = React.useState(null); + + const generateCollectTransaction = React.useCallback(async () => { + if (!marginfiClient || !selectedAccount) return; + + const ixs: TransactionInstruction[] = []; + const bundleTipIx = makeBundleTipIx(marginfiClient?.wallet.publicKey); + const priorityFeeIx = selectedAccount?.makePriorityFeeIx(0); // TODO: set priorityfee + const connection = marginfiClient?.provider.connection; // TODO: fix + if (!connection) return; // TODO: handle + const blockhash = (await connection.getLatestBlockhash()).blockhash; + + for (let bankAddress of bankAddressesWithEmissions) { + const ix = await selectedAccount?.makeWithdrawEmissionsIx(bankAddress); + if (!ix) continue; + ixs.push(...ix.instructions); + } // TODO: promise.all + + const tx = new VersionedTransaction( + new TransactionMessage({ + instructions: [bundleTipIx, ...priorityFeeIx, ...ixs], + payerKey: selectedAccount?.authority, + recentBlockhash: blockhash, + }).compileToV0Message() + ); + + setCollectTxn(tx); + }, [marginfiClient, selectedAccount, setCollectTxn, bankAddressesWithEmissions]); + + const simulateCollectTransaction = React.useCallback(async () => { + if (!collectTxn || !marginfiClient || !selectedAccount) return; // TODO: handle + + const balancesBeforeSimulation = selectedAccount.balances.filter((balance) => + bankAddressesWithEmissions.map((b) => b.toBase58()).includes(balance.bankPk.toBase58()) + ); + + console.log(balancesBeforeSimulation[0].emissionsOutstanding.toNumber()); + + const simulationResults = await marginfiClient.simulateTransactions( + [collectTxn], + [selectedAccount.address, ...bankAddressesWithEmissions] + ); + + if (!simulationResults || simulationResults.length === 0) return; // TODO: handle + + const [mfiAccountData, ...banksData] = simulationResults; // The first element is the account data, rest are the banks + + if (banksData.length !== bankAddressesWithEmissions.length) return; // TODO: handle + + const previewBanks = marginfiClient.banks; + for (let i = 0; i < bankAddressesWithEmissions.length; i++) { + const bankAddress = bankAddressesWithEmissions[i]; + const bankData = banksData[i]; + if (!bankData) continue; + previewBanks.set( + bankAddress.toBase58(), + Bank.fromBuffer(bankAddress, bankData, marginfiClient.program.idl, marginfiClient.feedIdMap) + ); + } + + const previewClient = new MarginfiClient( + marginfiClient.config, + marginfiClient.program, + {} as Wallet, + true, + marginfiClient.group, + previewBanks, + marginfiClient.oraclePrices, + marginfiClient.mintDatas, + marginfiClient.feedIdMap + ); + + if (!mfiAccountData) return; + + const previewMarginfiAccount = MarginfiAccountWrapper.fromAccountDataRaw( + selectedAccount.address, + previewClient, + mfiAccountData, + marginfiClient.program.idl + ); + + const balancesAfterSimulation = previewMarginfiAccount.balances.filter((balance) => + bankAddressesWithEmissions.map((b) => b.toBase58()).includes(balance.bankPk.toBase58()) + ); + console.log(balancesAfterSimulation[0].emissionsOutstanding.toNumber()); + + compareBankBalancesForEmissions(previewMarginfiAccount); + }, [marginfiClient, selectedAccount, collectTxn, bankAddressesWithEmissions]); + + const compareBankBalancesForEmissions = (originalAccount: MarginfiAccountWrapper) => { + const banksWithOustandingEmissions = originalAccount.balances.filter((b) => + b.emissionsOutstanding.isGreaterThan(0) + ); + + const outstandingRewards = banksWithOustandingEmissions.map((b) => { + const bank = marginfiClient?.getBankByPk(b.bankPk); + + return { + bank: bank, + amount: b.emissionsOutstanding.toNumber(), + }; + }); + + // console.log(outstandingRewards); + }; + + const handleCollectAction = async () => { + if (!collectTxn || !marginfiClient) return; + + const sig = await marginfiClient.processTransaction(collectTxn); + + // console.log(sig); + }; + + useEffect(() => { + simulateCollectTransaction(); + }, [collectTxn, marginfiClient, bankAddressesWithEmissions, selectedAccount]); + + useEffect(() => { + generateCollectTransaction(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [bankAddressesWithEmissions, marginfiClient, selectedAccount]); + const lendingBanks = React.useMemo( () => sortedBanks && isStoreInitialized @@ -110,6 +272,31 @@ export const LendingPortfolio = () => { [isStoreInitialized, walletConnectionDelay, isRefreshingStore, accountSummary.balance, lendingBanks, borrowingBanks] ); + const [rewards, setRewards] = React.useState({ + totalReward: 100, + rewards: [ + { + bank: "JitoSOL", + amount: 10, + }, + { + bank: "SOL", + amount: 10, + }, + { + bank: "SOL", + amount: 10, + }, + { + bank: "SOL", + amount: 10, + }, + ], + }); + + const [rewardsDialogOpen, setRewardsDialogOpen] = React.useState(false); + const [previousTxn] = useUiStore((state) => [state.previousTxn]); + // Introduced this useEffect to show the loader for 2 seconds after wallet connection. This is to avoid the flickering of the loader, since the isRefreshingStore isnt set immediately after the wallet connection. useEffect(() => { if (connected) { @@ -147,7 +334,17 @@ export const LendingPortfolio = () => { return (
-

Lend/borrow

+
+

Lend/borrow

+ { + setRewardsDialogOpen(true); + }} + > + Collect Rewards + +
@@ -262,6 +459,18 @@ export const LendingPortfolio = () => { )}
+ { + setRewardsDialogOpen(false); + }} + open={rewardsDialogOpen} + onOpenChange={(open) => { + setRewardsDialogOpen(open); + }} + onCollect={handleCollectAction} + /> + {previousTxn && } ); }; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/rewards/index.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/rewards/index.ts new file mode 100644 index 0000000000..2bae00a5fc --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/rewards/index.ts @@ -0,0 +1 @@ +export * from "./rewards-dialog"; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/rewards/rewards-dialog.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/rewards/rewards-dialog.tsx new file mode 100644 index 0000000000..93426da065 --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/rewards/rewards-dialog.tsx @@ -0,0 +1,33 @@ +import React from "react"; + +import { DialogDescription, DialogProps } from "@radix-ui/react-dialog"; + +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "~/components/ui/dialog"; +import { rewardsType } from ".."; +import { Button } from "~/components/ui/button"; +interface RewardsDialogProps extends DialogProps { + availableRewards: rewardsType; + onClose: () => void; + onCollect: () => void; +} + +export const RewardsDialog: React.FC = ({ availableRewards, onClose, onCollect, ...props }) => { + return ( + + + + Reward overview + + Here you find an overview of which assets have earned rewards. Click the button below to collect them. + + +
    + {availableRewards.rewards.map((reward) => ( +
  • {`${reward.amount} -> ${reward.bank}`}
  • + ))} +
+ +
+
+ ); +}; From 0a781cb31938ef7f1f64f16d8bd06ff0b1c8560a Mon Sep 17 00:00:00 2001 From: borcherd Date: Wed, 23 Oct 2024 13:42:57 +0800 Subject: [PATCH 02/14] feat: refactor, ui changes & cleaning --- .../Portfolio/LendingPortfolio/index.ts | 1 - .../common/Portfolio/components/index.ts | 4 + .../portfolio-asset-card.tsx} | 0 .../portfolio-header.tsx} | 0 .../portfolio-user-stats.tsx} | 0 .../{ => components}/rewards/index.ts | 0 .../rewards/rewards-dialog.tsx | 18 +- .../common/Portfolio/hooks/index.ts | 1 + .../Portfolio/hooks/use-reward-simulation.tsx | 164 ++++++++++++ .../src/components/common/Portfolio/index.ts | 1 + .../src/components/common/Portfolio/index.tsx | 4 +- ...ingPortfolio.tsx => lending-portfolio.tsx} | 253 ++++++------------ .../common/Portfolio/types/index.ts | 1 + .../common/Portfolio/types/reward.types.ts | 4 + .../common/Portfolio/utils/index.ts | 1 + .../common/Portfolio/utils/rewards.utils.ts | 9 + apps/marginfi-v2-ui/src/pages/portfolio.tsx | 2 +- .../hooks/use-stake-simulation.hooks.tsx | 6 +- 18 files changed, 283 insertions(+), 186 deletions(-) delete mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/LendingPortfolio/index.ts create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/components/index.ts rename apps/marginfi-v2-ui/src/components/common/Portfolio/{PortfolioAssetCard.tsx => components/portfolio-asset-card.tsx} (100%) rename apps/marginfi-v2-ui/src/components/common/Portfolio/{PortfolioHeader.tsx => components/portfolio-header.tsx} (100%) rename apps/marginfi-v2-ui/src/components/common/Portfolio/{PortfolioUserStats.tsx => components/portfolio-user-stats.tsx} (100%) rename apps/marginfi-v2-ui/src/components/common/Portfolio/{ => components}/rewards/index.ts (100%) rename apps/marginfi-v2-ui/src/components/common/Portfolio/{ => components}/rewards/rewards-dialog.tsx (57%) create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/index.ts create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/index.ts rename apps/marginfi-v2-ui/src/components/common/Portfolio/{LendingPortfolio/LendingPortfolio.tsx => lending-portfolio.tsx} (63%) create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/types/index.ts create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/types/reward.types.ts create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/utils/index.ts create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/utils/rewards.utils.ts diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/LendingPortfolio/index.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/LendingPortfolio/index.ts deleted file mode 100644 index b0948e4c47..0000000000 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/LendingPortfolio/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './LendingPortfolio' \ No newline at end of file diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/index.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/index.ts new file mode 100644 index 0000000000..f9bbbc0fa3 --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/index.ts @@ -0,0 +1,4 @@ +export * from "./portfolio-asset-card"; +export * from "./portfolio-user-stats"; +export * from "./portfolio-header"; +export * from "./rewards"; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/PortfolioAssetCard.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/portfolio-asset-card.tsx similarity index 100% rename from apps/marginfi-v2-ui/src/components/common/Portfolio/PortfolioAssetCard.tsx rename to apps/marginfi-v2-ui/src/components/common/Portfolio/components/portfolio-asset-card.tsx diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/PortfolioHeader.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/portfolio-header.tsx similarity index 100% rename from apps/marginfi-v2-ui/src/components/common/Portfolio/PortfolioHeader.tsx rename to apps/marginfi-v2-ui/src/components/common/Portfolio/components/portfolio-header.tsx diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/PortfolioUserStats.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/portfolio-user-stats.tsx similarity index 100% rename from apps/marginfi-v2-ui/src/components/common/Portfolio/PortfolioUserStats.tsx rename to apps/marginfi-v2-ui/src/components/common/Portfolio/components/portfolio-user-stats.tsx diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/rewards/index.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/rewards/index.ts similarity index 100% rename from apps/marginfi-v2-ui/src/components/common/Portfolio/rewards/index.ts rename to apps/marginfi-v2-ui/src/components/common/Portfolio/components/rewards/index.ts diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/rewards/rewards-dialog.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/rewards/rewards-dialog.tsx similarity index 57% rename from apps/marginfi-v2-ui/src/components/common/Portfolio/rewards/rewards-dialog.tsx rename to apps/marginfi-v2-ui/src/components/common/Portfolio/components/rewards/rewards-dialog.tsx index 93426da065..083cf341f4 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/rewards/rewards-dialog.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/rewards/rewards-dialog.tsx @@ -3,10 +3,11 @@ import React from "react"; import { DialogDescription, DialogProps } from "@radix-ui/react-dialog"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "~/components/ui/dialog"; -import { rewardsType } from ".."; import { Button } from "~/components/ui/button"; + +import { rewardsType } from "../../types"; interface RewardsDialogProps extends DialogProps { - availableRewards: rewardsType; + availableRewards: rewardsType | null; onClose: () => void; onCollect: () => void; } @@ -14,16 +15,19 @@ interface RewardsDialogProps extends DialogProps { export const RewardsDialog: React.FC = ({ availableRewards, onClose, onCollect, ...props }) => { return ( - + - Reward overview + Reward overview Here you find an overview of which assets have earned rewards. Click the button below to collect them. -
    - {availableRewards.rewards.map((reward) => ( -
  • {`${reward.amount} -> ${reward.bank}`}
  • +
      + {availableRewards?.rewards.map((reward, idx) => ( +
    • + + {`${reward.amount} ${reward.bank}`} +
    • ))}
    diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/index.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/index.ts new file mode 100644 index 0000000000..98840dbd94 --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/index.ts @@ -0,0 +1 @@ +export * from "./use-reward-simulation"; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx new file mode 100644 index 0000000000..1cc17fe9fd --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx @@ -0,0 +1,164 @@ +import React from "react"; + +import { PublicKey, TransactionInstruction, TransactionMessage, VersionedTransaction } from "@solana/web3.js"; +import { rewardsType } from "../types"; +import { makeBundleTipIx, MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; +import { ActionMethod, TOKEN_2022_MINTS } from "@mrgnlabs/mrgn-utils"; +import { EMISSION_MINT_INFO_MAP } from "~/components/desktop/AssetList/components"; +import { ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; +import { + AccountLayout, + getAssociatedTokenAddressSync, + nativeToUi, + numeralFormatter, + TOKEN_2022_PROGRAM_ID, + TOKEN_PROGRAM_ID, +} from "@mrgnlabs/mrgn-common"; + +type RewardSimulationProps = { + simulationResult: rewardsType | null; + actionTxn: VersionedTransaction | null; + marginfiClient: MarginfiClient | null; + selectedAccount: MarginfiAccountWrapper | null; + extendedBankInfos: ExtendedBankInfo[]; + + setSimulationResult: (result: rewardsType | null) => void; + setActionTxn: (actionTxn: VersionedTransaction | null) => void; + setErrorMessage: (error: ActionMethod | null) => void; + setIsLoading: ({ state, type }: { state: boolean; type: string | null }) => void; +}; + +export const useRewardSimulation = ({ + simulationResult, + actionTxn, + marginfiClient, + selectedAccount, + extendedBankInfos, + setSimulationResult, + setActionTxn, + setErrorMessage, + setIsLoading, +}: RewardSimulationProps) => { + const bankAddressesWithEmissions: PublicKey[] = React.useMemo(() => { + if (!selectedAccount) return []; + return [...EMISSION_MINT_INFO_MAP.keys()] + .map((bankMintSymbol) => { + const bankInfo = extendedBankInfos?.find((b) => b.address && b.meta.tokenSymbol === bankMintSymbol); + if (bankInfo?.info.state.emissions.toString() === "1") return bankInfo?.address; + }) + .filter((address) => address !== undefined) as PublicKey[]; + }, [selectedAccount, extendedBankInfos]); // TODO: confirm this is correct. I'm not sure, but some info.state.emissions are 0 and some are 1. For now i'm assuming that the banks with emissions are the ones with state.emissions = 1 + + const handleSimulation = React.useCallback(async () => { + if (!actionTxn || !marginfiClient || !selectedAccount) return; // TODO: handle + + // Structure to hold before and after amounts + const beforeAmounts = new Map(); + const afterAmounts = new Map(); + + const atas: PublicKey[] = []; + + for (let bankAddress of bankAddressesWithEmissions) { + const bank = marginfiClient.getBankByPk(bankAddress); + if (!bank) return; // TODO: handle + + const tokenMint = bank.emissionsMint; + const tokenSymbol = bank.tokenSymbol ?? ""; + const mintDecimals = bank.mintDecimals; + if (!tokenMint) return; // TODO: handle + + const programId = TOKEN_2022_MINTS.includes(tokenMint.toString()) ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID; + const ata = getAssociatedTokenAddressSync(tokenMint, marginfiClient.wallet.publicKey, true, programId); + if (!ata) return; // TODO: handle + atas.push(ata); + + const originData = await marginfiClient.provider.connection.getAccountInfo(ata); + if (!originData) return; // TODO: handle + + const beforeAmount = AccountLayout.decode(originData.data).amount.toString(); + beforeAmounts.set(bankAddress, { amount: beforeAmount, tokenSymbol, mintDecimals }); + } + + // Simulate transactions + const previewAtas = await marginfiClient.simulateTransactions([actionTxn], atas); + if (!previewAtas[0]) return; // TODO: handle + + // Map after amounts + previewAtas.forEach((ata, index) => { + if (!ata) return; // TODO: handle + + const afterAmount = AccountLayout.decode(ata).amount.toString(); + const bankAddress = bankAddressesWithEmissions[index]; + const beforeData = beforeAmounts.get(bankAddress); + + if (beforeData) { + afterAmounts.set(bankAddress, { + amount: afterAmount, + tokenSymbol: beforeData.tokenSymbol, + mintDecimals: beforeData.mintDecimals, + }); + } + }); + + let rewards: rewardsType = { + totalReward: 0, + rewards: [], + }; + + beforeAmounts.forEach((beforeData, bankAddress) => { + const afterData = afterAmounts.get(bankAddress); + + if (afterData) { + const beforeAmount = nativeToUi(beforeData.amount, beforeData.mintDecimals); + const afterAmount = nativeToUi(afterData.amount, afterData.mintDecimals); + const rewardAmount = afterAmount - beforeAmount; + + if (rewardAmount > 0) { + rewards.rewards.push({ + bank: beforeData.tokenSymbol, + amount: rewardAmount < 0.01 ? "<0.01" : numeralFormatter(rewardAmount), + }); + rewards.totalReward += rewardAmount; + } + } + }); + + // Set simulation result + setSimulationResult(rewards); + }, [actionTxn, bankAddressesWithEmissions, marginfiClient, selectedAccount, setSimulationResult]); + + const generateTxn = React.useCallback(async () => { + const connection = marginfiClient?.provider.connection; + if (!marginfiClient || !selectedAccount || !connection || !bankAddressesWithEmissions) return; + + const ixs: TransactionInstruction[] = []; + const bundleTipIx = makeBundleTipIx(marginfiClient?.wallet.publicKey); + const priorityFeeIx = selectedAccount?.makePriorityFeeIx(0); // TODO: set priorityfee + const blockhash = (await connection.getLatestBlockhash()).blockhash; + + for (let bankAddress of bankAddressesWithEmissions) { + const ix = await selectedAccount?.makeWithdrawEmissionsIx(bankAddress); + if (!ix) continue; + ixs.push(...ix.instructions); + } + + const tx = new VersionedTransaction( + new TransactionMessage({ + instructions: [bundleTipIx, ...priorityFeeIx, ...ixs], + payerKey: selectedAccount?.authority, + recentBlockhash: blockhash, + }).compileToV0Message() + ); + + setActionTxn(tx); + }, [bankAddressesWithEmissions, marginfiClient, selectedAccount, setActionTxn]); + + React.useEffect(() => { + generateTxn(); + }, [marginfiClient, bankAddressesWithEmissions, selectedAccount]); + + return { + handleSimulation, + generateTxn, + }; +}; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/index.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/index.ts new file mode 100644 index 0000000000..b660a7352d --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/index.ts @@ -0,0 +1 @@ +export * from "./lending-portfolio"; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/index.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/index.tsx index 37f9442f67..b660a7352d 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/index.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/index.tsx @@ -1,3 +1 @@ -export * from "./LendingPortfolio"; -export * from "./PortfolioAssetCard"; -export * from "./PortfolioUserStats"; +export * from "./lending-portfolio"; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/LendingPortfolio/LendingPortfolio.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx similarity index 63% rename from apps/marginfi-v2-ui/src/components/common/Portfolio/LendingPortfolio/LendingPortfolio.tsx rename to apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx index a8410e9d27..71856de048 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/LendingPortfolio/LendingPortfolio.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx @@ -2,21 +2,28 @@ import React, { useEffect } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import { IconInfoCircle } from "@tabler/icons-react"; +import { IconInfoCircle, IconMoneybag } from "@tabler/icons-react"; -import { nativeToUi, numeralFormatter, Wallet } from "@mrgnlabs/mrgn-common"; +import { + AccountLayout, + getAssociatedTokenAddressSync, + nativeToUi, + numeralFormatter, + TOKEN_2022_PROGRAM_ID, + TOKEN_PROGRAM_ID, + Wallet, +} from "@mrgnlabs/mrgn-common"; import { usdFormatter, usdFormatterDyn } from "@mrgnlabs/mrgn-common"; import { ActiveBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; -import { LendingModes } from "@mrgnlabs/mrgn-utils"; +import { LendingModes, TOKEN_2022_MINTS } from "@mrgnlabs/mrgn-utils"; import { useMrgnlendStore, useUiStore, useUserProfileStore } from "~/store"; -import { PortfolioUserStats, PortfolioAssetCard, PortfolioAssetCardSkeleton } from "~/components/common/Portfolio"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "~/components/ui/tooltip"; import { WalletButton } from "~/components/wallet-v2"; import { useWallet } from "~/components/wallet-v2/hooks/use-wallet.hook"; import { Loader } from "~/components/ui/loader"; -import { RewardsDialog } from "../rewards"; +import { RewardsDialog } from "./components/rewards"; import { ActionComplete } from "~/components/common/ActionComplete"; import { PublicKey, @@ -26,14 +33,12 @@ import { VersionedTransaction, } from "@solana/web3.js"; import { EMISSION_MINT_INFO_MAP } from "~/components/desktop/AssetList/components"; -import { Balance, Bank, makeBundleTipIx, MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; -import BigNumber from "bignumber.js"; -import { margin } from "@mui/system"; - -export type rewardsType = { - totalReward: number; - rewards: { bank: string; amount: number }[]; -}; +import { Balance, Bank, makeBundleTipIx } from "@mrgnlabs/marginfi-client-v2"; +import { IconLoader } from "~/components/ui/icons"; +import { PortfolioAssetCard, PortfolioAssetCardSkeleton, PortfolioUserStats } from "./components"; +import { rewardsType } from "./types"; +import { useRewardSimulation } from "./hooks"; +import { executeCollectTxn } from "./utils"; export const LendingPortfolio = () => { const router = useRouter(); @@ -62,138 +67,30 @@ export const LendingPortfolio = () => { const [userPointsData] = useUserProfileStore((state) => [state.userPointsData]); - const bankAddressesWithEmissions: PublicKey[] = React.useMemo(() => { - if (!selectedAccount) return []; - return [...EMISSION_MINT_INFO_MAP.keys()] - .map((bankMintSymbol) => { - const bankInfo = extendedBankInfos?.find((b) => b.address && b.meta.tokenSymbol === bankMintSymbol); - if (bankInfo?.info.state.emissions.toString() === "1") return bankInfo?.address; - }) - .filter((address) => address !== undefined) as PublicKey[]; - }, [selectedAccount, extendedBankInfos]); // TODO: confirm this is correct. I'm not sure, but some info.state.emissions are 0 and some are 1. For now i'm assuming that the banks with emissions are the ones with state.emissions = 1 - - const [collectTxn, setCollectTxn] = React.useState(null); - - const generateCollectTransaction = React.useCallback(async () => { - if (!marginfiClient || !selectedAccount) return; - - const ixs: TransactionInstruction[] = []; - const bundleTipIx = makeBundleTipIx(marginfiClient?.wallet.publicKey); - const priorityFeeIx = selectedAccount?.makePriorityFeeIx(0); // TODO: set priorityfee - const connection = marginfiClient?.provider.connection; // TODO: fix - if (!connection) return; // TODO: handle - const blockhash = (await connection.getLatestBlockhash()).blockhash; - - for (let bankAddress of bankAddressesWithEmissions) { - const ix = await selectedAccount?.makeWithdrawEmissionsIx(bankAddress); - if (!ix) continue; - ixs.push(...ix.instructions); - } // TODO: promise.all - - const tx = new VersionedTransaction( - new TransactionMessage({ - instructions: [bundleTipIx, ...priorityFeeIx, ...ixs], - payerKey: selectedAccount?.authority, - recentBlockhash: blockhash, - }).compileToV0Message() - ); - - setCollectTxn(tx); - }, [marginfiClient, selectedAccount, setCollectTxn, bankAddressesWithEmissions]); - - const simulateCollectTransaction = React.useCallback(async () => { - if (!collectTxn || !marginfiClient || !selectedAccount) return; // TODO: handle - - const balancesBeforeSimulation = selectedAccount.balances.filter((balance) => - bankAddressesWithEmissions.map((b) => b.toBase58()).includes(balance.bankPk.toBase58()) - ); - - console.log(balancesBeforeSimulation[0].emissionsOutstanding.toNumber()); - - const simulationResults = await marginfiClient.simulateTransactions( - [collectTxn], - [selectedAccount.address, ...bankAddressesWithEmissions] - ); - - if (!simulationResults || simulationResults.length === 0) return; // TODO: handle - - const [mfiAccountData, ...banksData] = simulationResults; // The first element is the account data, rest are the banks - - if (banksData.length !== bankAddressesWithEmissions.length) return; // TODO: handle - - const previewBanks = marginfiClient.banks; - for (let i = 0; i < bankAddressesWithEmissions.length; i++) { - const bankAddress = bankAddressesWithEmissions[i]; - const bankData = banksData[i]; - if (!bankData) continue; - previewBanks.set( - bankAddress.toBase58(), - Bank.fromBuffer(bankAddress, bankData, marginfiClient.program.idl, marginfiClient.feedIdMap) - ); - } - - const previewClient = new MarginfiClient( - marginfiClient.config, - marginfiClient.program, - {} as Wallet, - true, - marginfiClient.group, - previewBanks, - marginfiClient.oraclePrices, - marginfiClient.mintDatas, - marginfiClient.feedIdMap - ); - - if (!mfiAccountData) return; - - const previewMarginfiAccount = MarginfiAccountWrapper.fromAccountDataRaw( - selectedAccount.address, - previewClient, - mfiAccountData, - marginfiClient.program.idl - ); - - const balancesAfterSimulation = previewMarginfiAccount.balances.filter((balance) => - bankAddressesWithEmissions.map((b) => b.toBase58()).includes(balance.bankPk.toBase58()) - ); - console.log(balancesAfterSimulation[0].emissionsOutstanding.toNumber()); - - compareBankBalancesForEmissions(previewMarginfiAccount); - }, [marginfiClient, selectedAccount, collectTxn, bankAddressesWithEmissions]); - - const compareBankBalancesForEmissions = (originalAccount: MarginfiAccountWrapper) => { - const banksWithOustandingEmissions = originalAccount.balances.filter((b) => - b.emissionsOutstanding.isGreaterThan(0) - ); - - const outstandingRewards = banksWithOustandingEmissions.map((b) => { - const bank = marginfiClient?.getBankByPk(b.bankPk); - - return { - bank: bank, - amount: b.emissionsOutstanding.toNumber(), - }; - }); - - // console.log(outstandingRewards); - }; - - const handleCollectAction = async () => { - if (!collectTxn || !marginfiClient) return; - - const sig = await marginfiClient.processTransaction(collectTxn); + const [rewards, setRewards] = React.useState(null); + const [rewardsDialogOpen, setRewardsDialogOpen] = React.useState(false); + const [actionTxn, setActionTxn] = React.useState(null); - // console.log(sig); - }; + const { handleSimulation } = useRewardSimulation({ + simulationResult: rewards, + actionTxn, + marginfiClient, + selectedAccount, + extendedBankInfos, + setSimulationResult: setRewards, + setActionTxn, + setErrorMessage: () => {}, + setIsLoading: () => {}, + }); useEffect(() => { - simulateCollectTransaction(); - }, [collectTxn, marginfiClient, bankAddressesWithEmissions, selectedAccount]); + if (actionTxn) handleSimulation(); + }, [actionTxn]); - useEffect(() => { - generateCollectTransaction(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [bankAddressesWithEmissions, marginfiClient, selectedAccount]); + const handleCollectExectuion = React.useCallback(async () => { + if (!marginfiClient || !actionTxn) return; + const signature = await executeCollectTxn(marginfiClient, actionTxn); + }, [marginfiClient, actionTxn]); const lendingBanks = React.useMemo( () => @@ -272,29 +169,6 @@ export const LendingPortfolio = () => { [isStoreInitialized, walletConnectionDelay, isRefreshingStore, accountSummary.balance, lendingBanks, borrowingBanks] ); - const [rewards, setRewards] = React.useState({ - totalReward: 100, - rewards: [ - { - bank: "JitoSOL", - amount: 10, - }, - { - bank: "SOL", - amount: 10, - }, - { - bank: "SOL", - amount: 10, - }, - { - bank: "SOL", - amount: 10, - }, - ], - }); - - const [rewardsDialogOpen, setRewardsDialogOpen] = React.useState(false); const [previousTxn] = useUiStore((state) => [state.previousTxn]); // Introduced this useEffect to show the loader for 2 seconds after wallet connection. This is to avoid the flickering of the loader, since the isRefreshingStore isnt set immediately after the wallet connection. @@ -336,14 +210,47 @@ export const LendingPortfolio = () => {

    Lend/borrow

    - { - setRewardsDialogOpen(true); - }} - > - Collect Rewards - + +
    + {rewards ? ( + rewards.totalReward > 0 ? ( + + ) : ( + + ) + ) : ( + + Rewards loading + + )}{" "} + {rewards && ( + + + + + + + + {rewards && rewards.totalReward > 0 + ? `You are earning rewards on the following banks: ${rewards.rewards + .map((r) => r.bank) + .join(", ")}` + : "No outstanding rewards. you can earn rewards on the following banks: PYUSD, BSOL and UXD"} + {" "} + + + + )} +
    @@ -468,7 +375,7 @@ export const LendingPortfolio = () => { onOpenChange={(open) => { setRewardsDialogOpen(open); }} - onCollect={handleCollectAction} + onCollect={handleCollectExectuion} /> {previousTxn && }
    diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/types/index.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/types/index.ts new file mode 100644 index 0000000000..f13a1c0af0 --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/types/index.ts @@ -0,0 +1 @@ +export * from "./reward.types"; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/types/reward.types.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/types/reward.types.ts new file mode 100644 index 0000000000..c1afb01d0b --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/types/reward.types.ts @@ -0,0 +1,4 @@ +export type rewardsType = { + totalReward: number; + rewards: { bank: string; amount: string }[]; +}; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/index.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/index.ts new file mode 100644 index 0000000000..b721c3b97b --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/index.ts @@ -0,0 +1 @@ +export * from "./rewards.utils"; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/rewards.utils.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/rewards.utils.ts new file mode 100644 index 0000000000..aea9c49eba --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/rewards.utils.ts @@ -0,0 +1,9 @@ +import { MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; +import { VersionedTransaction } from "@solana/web3.js"; + +export const executeCollectTxn = async (marginfiClient: MarginfiClient, actionTxn: VersionedTransaction) => { + const sig = await marginfiClient.processTransaction(actionTxn); + + return sig; +}; +// TODO: error handling here diff --git a/apps/marginfi-v2-ui/src/pages/portfolio.tsx b/apps/marginfi-v2-ui/src/pages/portfolio.tsx index 09833b649b..100dd4e3e1 100644 --- a/apps/marginfi-v2-ui/src/pages/portfolio.tsx +++ b/apps/marginfi-v2-ui/src/pages/portfolio.tsx @@ -1,7 +1,7 @@ import React from "react"; import { LendingPortfolio } from "~/components/common/Portfolio"; -import { PortfolioHeader } from "~/components/common/Portfolio/PortfolioHeader"; +import { PortfolioHeader } from "~/components/common/Portfolio/components"; export default function PortfolioPage() { return ( diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/hooks/use-stake-simulation.hooks.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/hooks/use-stake-simulation.hooks.tsx index 5ac5928624..ba0eac58a4 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/hooks/use-stake-simulation.hooks.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/hooks/use-stake-simulation.hooks.tsx @@ -59,7 +59,11 @@ export function useStakeSimulation({ setErrorMessage, setIsLoading, }: StakeSimulationProps) { - const [slippageBps, platformFeeBps] = useActionBoxStore((state) => [state.slippageBps, state.platformFeeBps]); + const [slippageBps, platformFeeBps] = useActionBoxStore((state) => [ + state.slippageBps, + state.platformFeeBps, + state.priorityFee, + ]); const prevDebouncedAmount = usePrevious(debouncedAmount); From 12f0cbbc75cd2e60250ea8607751fbb8c0e5b138 Mon Sep 17 00:00:00 2001 From: borcherd Date: Wed, 23 Oct 2024 23:21:58 +0800 Subject: [PATCH 03/14] chore: refactoring, error handling & cleaning --- .../src/components/common/Navbar/Navbar.tsx | 19 -- .../components/rewards/rewards-dialog.tsx | 14 +- .../Portfolio/hooks/use-reward-simulation.tsx | 199 +++++++++--------- .../common/Portfolio/lending-portfolio.tsx | 43 ++-- .../common/Portfolio/utils/rewards.utils.ts | 51 ++++- packages/mrgn-ui/src/components/ui/icons.tsx | 5 +- 6 files changed, 179 insertions(+), 152 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/Navbar/Navbar.tsx b/apps/marginfi-v2-ui/src/components/common/Navbar/Navbar.tsx index e2eb24c698..557d52887e 100644 --- a/apps/marginfi-v2-ui/src/components/common/Navbar/Navbar.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Navbar/Navbar.tsx @@ -156,25 +156,6 @@ export const Navbar: FC = () => {
    {initialized && (
    -
    0 ? "cursor-pointer hover:text-[#AAA]" : "cursor-not-allowed" - }`} - onClick={async () => { - if (!wallet || !selectedAccount || bankAddressesWithEmissions.length === 0) return; - const priorityFee = 0; // code has been removed on new collect rewards so temporary placeholder - await collectRewardsBatch(selectedAccount, bankAddressesWithEmissions, priorityFee); - }} - > - {!isMobile && "collect"} rewards - {bankAddressesWithEmissions.length > 0 && ( - - - - - )} -
    -
- +
); diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx index 1cc17fe9fd..0a23a4c3ca 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx @@ -1,10 +1,9 @@ import React from "react"; import { PublicKey, TransactionInstruction, TransactionMessage, VersionedTransaction } from "@solana/web3.js"; -import { rewardsType } from "../types"; + import { makeBundleTipIx, MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; import { ActionMethod, TOKEN_2022_MINTS } from "@mrgnlabs/mrgn-utils"; -import { EMISSION_MINT_INFO_MAP } from "~/components/desktop/AssetList/components"; import { ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { AccountLayout, @@ -15,6 +14,8 @@ import { TOKEN_PROGRAM_ID, } from "@mrgnlabs/mrgn-common"; +import { rewardsType } from "../types"; + type RewardSimulationProps = { simulationResult: rewardsType | null; actionTxn: VersionedTransaction | null; @@ -25,132 +26,140 @@ type RewardSimulationProps = { setSimulationResult: (result: rewardsType | null) => void; setActionTxn: (actionTxn: VersionedTransaction | null) => void; setErrorMessage: (error: ActionMethod | null) => void; - setIsLoading: ({ state, type }: { state: boolean; type: string | null }) => void; }; export const useRewardSimulation = ({ - simulationResult, actionTxn, marginfiClient, selectedAccount, extendedBankInfos, setSimulationResult, setActionTxn, - setErrorMessage, - setIsLoading, }: RewardSimulationProps) => { const bankAddressesWithEmissions: PublicKey[] = React.useMemo(() => { - if (!selectedAccount) return []; - return [...EMISSION_MINT_INFO_MAP.keys()] - .map((bankMintSymbol) => { - const bankInfo = extendedBankInfos?.find((b) => b.address && b.meta.tokenSymbol === bankMintSymbol); - if (bankInfo?.info.state.emissions.toString() === "1") return bankInfo?.address; - }) - .filter((address) => address !== undefined) as PublicKey[]; - }, [selectedAccount, extendedBankInfos]); // TODO: confirm this is correct. I'm not sure, but some info.state.emissions are 0 and some are 1. For now i'm assuming that the banks with emissions are the ones with state.emissions = 1 + if (!extendedBankInfos) return []; + return extendedBankInfos.filter((bank) => bank.info.state.emissionsRate > 0).map((bank) => bank.meta.address); + }, [extendedBankInfos, selectedAccount]); const handleSimulation = React.useCallback(async () => { - if (!actionTxn || !marginfiClient || !selectedAccount) return; // TODO: handle + try { + if (!actionTxn || !marginfiClient || !selectedAccount) return; - // Structure to hold before and after amounts - const beforeAmounts = new Map(); - const afterAmounts = new Map(); + const beforeAmounts = new Map(); + const afterAmounts = new Map(); - const atas: PublicKey[] = []; + const atas: PublicKey[] = []; - for (let bankAddress of bankAddressesWithEmissions) { - const bank = marginfiClient.getBankByPk(bankAddress); - if (!bank) return; // TODO: handle + for (let bankAddress of bankAddressesWithEmissions) { + const bank = marginfiClient.getBankByPk(bankAddress); + if (!bank) continue; - const tokenMint = bank.emissionsMint; - const tokenSymbol = bank.tokenSymbol ?? ""; - const mintDecimals = bank.mintDecimals; - if (!tokenMint) return; // TODO: handle - - const programId = TOKEN_2022_MINTS.includes(tokenMint.toString()) ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID; - const ata = getAssociatedTokenAddressSync(tokenMint, marginfiClient.wallet.publicKey, true, programId); - if (!ata) return; // TODO: handle - atas.push(ata); - - const originData = await marginfiClient.provider.connection.getAccountInfo(ata); - if (!originData) return; // TODO: handle - - const beforeAmount = AccountLayout.decode(originData.data).amount.toString(); - beforeAmounts.set(bankAddress, { amount: beforeAmount, tokenSymbol, mintDecimals }); - } + const tokenMint = bank.emissionsMint; + const tokenSymbol = bank.tokenSymbol ?? ""; + const mintDecimals = bank.mintDecimals; + if (!tokenMint) continue; - // Simulate transactions - const previewAtas = await marginfiClient.simulateTransactions([actionTxn], atas); - if (!previewAtas[0]) return; // TODO: handle + const programId = TOKEN_2022_MINTS.includes(tokenMint.toString()) ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID; + const ata = getAssociatedTokenAddressSync(tokenMint, marginfiClient.wallet.publicKey, true, programId); + if (!ata) continue; + atas.push(ata); - // Map after amounts - previewAtas.forEach((ata, index) => { - if (!ata) return; // TODO: handle + const originData = await marginfiClient.provider.connection.getAccountInfo(ata); + if (!originData) continue; - const afterAmount = AccountLayout.decode(ata).amount.toString(); - const bankAddress = bankAddressesWithEmissions[index]; - const beforeData = beforeAmounts.get(bankAddress); + const beforeAmount = AccountLayout.decode(originData.data).amount.toString(); + beforeAmounts.set(bankAddress, { amount: beforeAmount, tokenSymbol, mintDecimals }); + } - if (beforeData) { - afterAmounts.set(bankAddress, { - amount: afterAmount, - tokenSymbol: beforeData.tokenSymbol, - mintDecimals: beforeData.mintDecimals, + if (beforeAmounts.size === 0) { + setSimulationResult({ + totalReward: 0, + rewards: [], }); + return; } - }); - let rewards: rewardsType = { - totalReward: 0, - rewards: [], - }; + const previewAtas = await marginfiClient.simulateTransactions([actionTxn], atas); + if (!previewAtas[0]) return; - beforeAmounts.forEach((beforeData, bankAddress) => { - const afterData = afterAmounts.get(bankAddress); + previewAtas.forEach((ata, index) => { + if (!ata) return; - if (afterData) { - const beforeAmount = nativeToUi(beforeData.amount, beforeData.mintDecimals); - const afterAmount = nativeToUi(afterData.amount, afterData.mintDecimals); - const rewardAmount = afterAmount - beforeAmount; + const afterAmount = AccountLayout.decode(ata).amount.toString(); + const bankAddress = bankAddressesWithEmissions[index]; + const beforeData = beforeAmounts.get(bankAddress); - if (rewardAmount > 0) { - rewards.rewards.push({ - bank: beforeData.tokenSymbol, - amount: rewardAmount < 0.01 ? "<0.01" : numeralFormatter(rewardAmount), + if (beforeData) { + afterAmounts.set(bankAddress, { + amount: afterAmount, + tokenSymbol: beforeData.tokenSymbol, + mintDecimals: beforeData.mintDecimals, }); - rewards.totalReward += rewardAmount; } - } - }); - - // Set simulation result - setSimulationResult(rewards); + }); + + let rewards: rewardsType = { + totalReward: 0, + rewards: [], + }; + + beforeAmounts.forEach((beforeData, bankAddress) => { + const afterData = afterAmounts.get(bankAddress); + + if (afterData) { + const beforeAmount = nativeToUi(beforeData.amount, beforeData.mintDecimals); + const afterAmount = nativeToUi(afterData.amount, afterData.mintDecimals); + const rewardAmount = afterAmount - beforeAmount; + + if (rewardAmount > 0) { + rewards.rewards.push({ + bank: beforeData.tokenSymbol, + amount: rewardAmount < 0.01 ? "<0.01" : numeralFormatter(rewardAmount), + }); + rewards.totalReward += rewardAmount; + } + } + }); + + setSimulationResult(rewards); + } catch (error) { + setSimulationResult({ + totalReward: 0, + rewards: [], + }); + } }, [actionTxn, bankAddressesWithEmissions, marginfiClient, selectedAccount, setSimulationResult]); const generateTxn = React.useCallback(async () => { - const connection = marginfiClient?.provider.connection; - if (!marginfiClient || !selectedAccount || !connection || !bankAddressesWithEmissions) return; - - const ixs: TransactionInstruction[] = []; - const bundleTipIx = makeBundleTipIx(marginfiClient?.wallet.publicKey); - const priorityFeeIx = selectedAccount?.makePriorityFeeIx(0); // TODO: set priorityfee - const blockhash = (await connection.getLatestBlockhash()).blockhash; - - for (let bankAddress of bankAddressesWithEmissions) { - const ix = await selectedAccount?.makeWithdrawEmissionsIx(bankAddress); - if (!ix) continue; - ixs.push(...ix.instructions); + try { + const connection = marginfiClient?.provider.connection; + if (!marginfiClient || !selectedAccount || !connection || !bankAddressesWithEmissions) return; + + const ixs: TransactionInstruction[] = []; + const bundleTipIx = makeBundleTipIx(marginfiClient?.wallet.publicKey); + const priorityFeeIx = selectedAccount?.makePriorityFeeIx(0); // TODO: set priorityfee + const blockhash = (await connection.getLatestBlockhash()).blockhash; + + await Promise.all( + bankAddressesWithEmissions.map(async (bankAddress) => { + const ix = await selectedAccount?.makeWithdrawEmissionsIx(bankAddress); + if (!ix) return; + ixs.push(...ix.instructions); + }) + ); + + const tx = new VersionedTransaction( + new TransactionMessage({ + instructions: [bundleTipIx, ...priorityFeeIx, ...ixs], + payerKey: selectedAccount?.authority, + recentBlockhash: blockhash, + }).compileToV0Message() + ); + + setActionTxn(tx); + } catch (error) { + setActionTxn(null); } - - const tx = new VersionedTransaction( - new TransactionMessage({ - instructions: [bundleTipIx, ...priorityFeeIx, ...ixs], - payerKey: selectedAccount?.authority, - recentBlockhash: blockhash, - }).compileToV0Message() - ); - - setActionTxn(tx); }, [bankAddressesWithEmissions, marginfiClient, selectedAccount, setActionTxn]); React.useEffect(() => { diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx index 71856de048..d369809510 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx @@ -1,21 +1,13 @@ -import React, { useEffect } from "react"; +import React from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import { IconInfoCircle, IconMoneybag } from "@tabler/icons-react"; +import { VersionedTransaction } from "@solana/web3.js"; -import { - AccountLayout, - getAssociatedTokenAddressSync, - nativeToUi, - numeralFormatter, - TOKEN_2022_PROGRAM_ID, - TOKEN_PROGRAM_ID, - Wallet, -} from "@mrgnlabs/mrgn-common"; +import { numeralFormatter } from "@mrgnlabs/mrgn-common"; import { usdFormatter, usdFormatterDyn } from "@mrgnlabs/mrgn-common"; import { ActiveBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; -import { LendingModes, TOKEN_2022_MINTS } from "@mrgnlabs/mrgn-utils"; +import { LendingModes } from "@mrgnlabs/mrgn-utils"; import { useMrgnlendStore, useUiStore, useUserProfileStore } from "~/store"; @@ -25,16 +17,8 @@ import { useWallet } from "~/components/wallet-v2/hooks/use-wallet.hook"; import { Loader } from "~/components/ui/loader"; import { RewardsDialog } from "./components/rewards"; import { ActionComplete } from "~/components/common/ActionComplete"; -import { - PublicKey, - Transaction, - TransactionInstruction, - TransactionMessage, - VersionedTransaction, -} from "@solana/web3.js"; -import { EMISSION_MINT_INFO_MAP } from "~/components/desktop/AssetList/components"; -import { Balance, Bank, makeBundleTipIx } from "@mrgnlabs/marginfi-client-v2"; -import { IconLoader } from "~/components/ui/icons"; +import { IconInfoCircle, IconLoader } from "~/components/ui/icons"; + import { PortfolioAssetCard, PortfolioAssetCardSkeleton, PortfolioUserStats } from "./components"; import { rewardsType } from "./types"; import { useRewardSimulation } from "./hooks"; @@ -62,15 +46,14 @@ export const LendingPortfolio = () => { state.selectedAccount, state.extendedBankInfos, ]); - const [setLendingMode] = useUiStore((state) => [state.setLendingMode]); - const [userPointsData] = useUserProfileStore((state) => [state.userPointsData]); + // Rewards const [rewards, setRewards] = React.useState(null); const [rewardsDialogOpen, setRewardsDialogOpen] = React.useState(false); const [actionTxn, setActionTxn] = React.useState(null); - + const [rewardsLoading, setRewardsLoading] = React.useState(false); const { handleSimulation } = useRewardSimulation({ simulationResult: rewards, actionTxn, @@ -80,16 +63,15 @@ export const LendingPortfolio = () => { setSimulationResult: setRewards, setActionTxn, setErrorMessage: () => {}, - setIsLoading: () => {}, }); - useEffect(() => { + React.useEffect(() => { if (actionTxn) handleSimulation(); }, [actionTxn]); const handleCollectExectuion = React.useCallback(async () => { if (!marginfiClient || !actionTxn) return; - const signature = await executeCollectTxn(marginfiClient, actionTxn); + const signature = await executeCollectTxn(marginfiClient, actionTxn, setRewardsLoading); }, [marginfiClient, actionTxn]); const lendingBanks = React.useMemo( @@ -172,7 +154,7 @@ export const LendingPortfolio = () => { const [previousTxn] = useUiStore((state) => [state.previousTxn]); // Introduced this useEffect to show the loader for 2 seconds after wallet connection. This is to avoid the flickering of the loader, since the isRefreshingStore isnt set immediately after the wallet connection. - useEffect(() => { + React.useEffect(() => { if (connected) { setWalletConnectionDelay(true); const timer = setTimeout(() => { @@ -229,7 +211,7 @@ export const LendingPortfolio = () => { ) ) : ( - Rewards loading + Calculating rewards )}{" "} {rewards && ( @@ -376,6 +358,7 @@ export const LendingPortfolio = () => { setRewardsDialogOpen(open); }} onCollect={handleCollectExectuion} + isLoading={rewardsLoading} /> {previousTxn && } diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/rewards.utils.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/rewards.utils.ts index aea9c49eba..5acb10fffe 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/rewards.utils.ts +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/rewards.utils.ts @@ -1,9 +1,50 @@ -import { MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; +import * as Sentry from "@sentry/nextjs"; + import { VersionedTransaction } from "@solana/web3.js"; -export const executeCollectTxn = async (marginfiClient: MarginfiClient, actionTxn: VersionedTransaction) => { - const sig = await marginfiClient.processTransaction(actionTxn); +import { MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; +import { extractErrorString, MultiStepToastHandle } from "@mrgnlabs/mrgn-utils"; + +const captureException = (error: any, msg: string, tags: Record) => { + if (msg.includes("User rejected")) return; + Sentry.setTags({ + ...tags, + customMessage: msg, + }); + Sentry.captureException(error); +}; + +export const executeCollectTxn = async ( + marginfiClient: MarginfiClient, + actionTxn: VersionedTransaction, + setIsLoading: (isLoading: boolean) => void +) => { + setIsLoading(true); + const multiStepToast = new MultiStepToastHandle("Collecting rewards", [ + { + label: "Executing transaction", + }, + ]); + multiStepToast.start(); + + try { + const sig = await marginfiClient.processTransaction(actionTxn); + multiStepToast.setSuccessAndNext(); + + return sig; + } catch (error) { + const msg = extractErrorString(error); + multiStepToast.setFailed(msg); + console.log(`Error while actiontype: ${msg}`); + console.log(error); + + const walletAddress = marginfiClient.wallet.publicKey.toBase58(); - return sig; + captureException(error, msg, { + action: "Collect rewards", + wallet: walletAddress, + }); + } finally { + setIsLoading(false); + } }; -// TODO: error handling here diff --git a/packages/mrgn-ui/src/components/ui/icons.tsx b/packages/mrgn-ui/src/components/ui/icons.tsx index bf63d4c111..a9cc6a5662 100644 --- a/packages/mrgn-ui/src/components/ui/icons.tsx +++ b/packages/mrgn-ui/src/components/ui/icons.tsx @@ -1,5 +1,5 @@ import { cn } from "@mrgnlabs/mrgn-utils"; -import { IconLoader2 } from "@tabler/icons-react"; +import { IconLoader2, IconInfoCircle as _IconInfoCircle } from "@tabler/icons-react"; export type IconProps = { size?: number; @@ -419,6 +419,8 @@ const IconLoader = ({ size = 24, className }: IconProps) => ( ); +const IconInfoCircle = ({ size = 24, className }: IconProps) => <_IconInfoCircle size={size} className={className} />; + const IconArena = ({ size = 48, className }: IconProps) => ( Date: Wed, 23 Oct 2024 23:26:29 +0800 Subject: [PATCH 04/14] chore: following code conventions --- .../common/Portfolio/components/asset-card/index.ts | 1 + .../components/{ => asset-card}/portfolio-asset-card.tsx | 0 .../components/common/Portfolio/components/header/index.ts | 1 + .../Portfolio/components/{ => header}/portfolio-header.tsx | 0 .../src/components/common/Portfolio/components/index.ts | 6 +++--- .../common/Portfolio/components/user-stats/index.ts | 1 + .../components/{ => user-stats}/portfolio-user-stats.tsx | 0 apps/marginfi-v2-ui/src/pages/portfolio.tsx | 3 ++- 8 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/index.ts rename apps/marginfi-v2-ui/src/components/common/Portfolio/components/{ => asset-card}/portfolio-asset-card.tsx (100%) create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/components/header/index.ts rename apps/marginfi-v2-ui/src/components/common/Portfolio/components/{ => header}/portfolio-header.tsx (100%) create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/components/user-stats/index.ts rename apps/marginfi-v2-ui/src/components/common/Portfolio/components/{ => user-stats}/portfolio-user-stats.tsx (100%) diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/index.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/index.ts new file mode 100644 index 0000000000..2e24148283 --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/index.ts @@ -0,0 +1 @@ +export * from "./portfolio-asset-card"; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/portfolio-asset-card.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx similarity index 100% rename from apps/marginfi-v2-ui/src/components/common/Portfolio/components/portfolio-asset-card.tsx rename to apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/header/index.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/header/index.ts new file mode 100644 index 0000000000..89e4686122 --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/header/index.ts @@ -0,0 +1 @@ +export * from "./portfolio-header"; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/portfolio-header.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/header/portfolio-header.tsx similarity index 100% rename from apps/marginfi-v2-ui/src/components/common/Portfolio/components/portfolio-header.tsx rename to apps/marginfi-v2-ui/src/components/common/Portfolio/components/header/portfolio-header.tsx diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/index.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/index.ts index f9bbbc0fa3..0c3f611bbe 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/index.ts +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/index.ts @@ -1,4 +1,4 @@ -export * from "./portfolio-asset-card"; -export * from "./portfolio-user-stats"; -export * from "./portfolio-header"; +export * from "./asset-card"; +export * from "./user-stats"; +export * from "./header"; export * from "./rewards"; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/user-stats/index.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/user-stats/index.ts new file mode 100644 index 0000000000..1f1e118f81 --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/user-stats/index.ts @@ -0,0 +1 @@ +export * from "./portfolio-user-stats"; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/portfolio-user-stats.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/user-stats/portfolio-user-stats.tsx similarity index 100% rename from apps/marginfi-v2-ui/src/components/common/Portfolio/components/portfolio-user-stats.tsx rename to apps/marginfi-v2-ui/src/components/common/Portfolio/components/user-stats/portfolio-user-stats.tsx diff --git a/apps/marginfi-v2-ui/src/pages/portfolio.tsx b/apps/marginfi-v2-ui/src/pages/portfolio.tsx index 100dd4e3e1..d15bd99331 100644 --- a/apps/marginfi-v2-ui/src/pages/portfolio.tsx +++ b/apps/marginfi-v2-ui/src/pages/portfolio.tsx @@ -1,7 +1,8 @@ import React from "react"; import { LendingPortfolio } from "~/components/common/Portfolio"; -import { PortfolioHeader } from "~/components/common/Portfolio/components"; + +import { PortfolioHeader } from "~/components/common/Portfolio/components/header/portfolio-header"; export default function PortfolioPage() { return ( From e0cfb918f494dbd5f979bd9b8d4cbd7884431b9e Mon Sep 17 00:00:00 2001 From: borcherd Date: Fri, 25 Oct 2024 10:44:23 +0800 Subject: [PATCH 05/14] feat: account switching & rewards QA notes impl --- .../asset-card/portfolio-asset-card.tsx | 136 +++++++++++++++++- .../Portfolio/hooks/use-reward-simulation.tsx | 1 + .../common/Portfolio/lending-portfolio.tsx | 84 +++++++++-- .../common/Portfolio/utils/rewards.utils.ts | 7 +- 4 files changed, 215 insertions(+), 13 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx index f29148575a..a6c9b1c878 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx @@ -7,7 +7,7 @@ import { usdFormatter, numeralFormatter, dynamicNumeralFormatter } from "@mrgnla import { ActiveBankInfo, ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { capture } from "@mrgnlabs/mrgn-utils"; import { ActionBox } from "@mrgnlabs/mrgn-ui"; - +import { MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; import { cn } from "@mrgnlabs/mrgn-utils"; import { useAssetItemData } from "~/hooks/useAssetItemData"; @@ -15,7 +15,16 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "~/ import { Button } from "~/components/ui/button"; import { Skeleton } from "~/components/ui/skeleton"; import { useWallet } from "~/components/wallet-v2/hooks/use-wallet.hook"; -import { useMrgnlendStore } from "~/store"; +import { useMrgnlendStore, useUiStore } from "~/store"; +import { + Dialog, + DialogContent, + DialogTrigger, + DialogHeader, + DialogTitle, + DialogDescription, +} from "~/components/ui/dialog"; +import { Select, SelectContent, SelectItem, SelectTrigger } from "~/components/ui/select"; interface PortfolioAssetCardProps { bank: ActiveBankInfo; @@ -25,7 +34,11 @@ interface PortfolioAssetCardProps { export const PortfolioAssetCard = ({ bank, isInLendingMode, isBorrower = true }: PortfolioAssetCardProps) => { const { rateAP } = useAssetItemData({ bank, isInLendingMode }); - + const [selectedAccount, marginfiAccounts, marginfiClient] = useMrgnlendStore((state) => [ + state.selectedAccount, + state.marginfiAccounts, + state.marginfiClient, + ]); const isIsolated = React.useMemo(() => bank.info.state.isIsolated, [bank]); const isUserPositionPoorHealth = React.useMemo(() => { @@ -42,6 +55,8 @@ export const PortfolioAssetCard = ({ bank, isInLendingMode, isBorrower = true }: } }, [bank]); + const [isMovePositionDialogOpen, setIsMovePositionDialogOpen] = React.useState(false); + const postionMovingPossible = React.useMemo(() => marginfiAccounts.length > 1, marginfiAccounts); return ( + + {postionMovingPossible && ( + + )} + + {isMovePositionDialogOpen && ( + + )} @@ -262,3 +300,95 @@ export const PortfolioAssetCardSkeleton = () => { ); }; + +export const MovePositionDialog = ({ + selectedAccount, + marginfiAccounts, + isOpen, + setIsOpen, + bank, + marginfiClient, +}: { + selectedAccount: MarginfiAccountWrapper | null; + marginfiAccounts: MarginfiAccountWrapper[]; + isOpen: boolean; + setIsOpen: (isOpen: boolean) => void; + bank: ActiveBankInfo; + marginfiClient: MarginfiClient | null; +}) => { + const [accountToMoveTo, setAccountToMoveTo] = React.useState( + selectedAccount + ); + + const handleMovePosition = React.useCallback(async () => { + if (!marginfiClient || !accountToMoveTo) { + return; + } + + const connection = marginfiClient.provider.connection; + // selectedAccount.simulate + }, [marginfiClient, accountToMoveTo, marginfiAccounts, selectedAccount, bank]); + + return ( + { + setIsOpen(value); + }} + > + + + Move position to another account + Move your position to another account + + +
+
+
Token value:
+
+ {bank.position.amount < 0.01 ? "< $0.01" : numeralFormatter(bank.position.amount)} + {" " + bank.meta.tokenSymbol} +
+
USD value
+
+ {bank.position.usdValue < 0.01 ? "< $0.01" : usdFormatter.format(bank.position.usdValue)} +
+
+
+ Select account to move position to: + +
+
+ Account address: +
+ + {`${accountToMoveTo?.address.toBase58().slice(0, 8)} + ...${accountToMoveTo?.address.toBase58().slice(-8)}`} + +
+
{" "} +
+ + + + + ); +}; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx index 0a23a4c3ca..1dad0f64ae 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx @@ -169,5 +169,6 @@ export const useRewardSimulation = ({ return { handleSimulation, generateTxn, + bankAddressesWithEmissions, }; }; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx index d369809510..b358344644 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx @@ -2,12 +2,12 @@ import React from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import { VersionedTransaction } from "@solana/web3.js"; +import { PublicKey, VersionedTransaction } from "@solana/web3.js"; import { numeralFormatter } from "@mrgnlabs/mrgn-common"; import { usdFormatter, usdFormatterDyn } from "@mrgnlabs/mrgn-common"; import { ActiveBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; -import { LendingModes } from "@mrgnlabs/mrgn-utils"; +import { LendingModes, MultiStepToastHandle } from "@mrgnlabs/mrgn-utils"; import { useMrgnlendStore, useUiStore, useUserProfileStore } from "~/store"; @@ -17,12 +17,14 @@ import { useWallet } from "~/components/wallet-v2/hooks/use-wallet.hook"; import { Loader } from "~/components/ui/loader"; import { RewardsDialog } from "./components/rewards"; import { ActionComplete } from "~/components/common/ActionComplete"; -import { IconInfoCircle, IconLoader } from "~/components/ui/icons"; +import { IconLoader } from "~/components/ui/icons"; +import { IconInfoCircle } from "@tabler/icons-react"; import { PortfolioAssetCard, PortfolioAssetCardSkeleton, PortfolioUserStats } from "./components"; import { rewardsType } from "./types"; import { useRewardSimulation } from "./hooks"; import { executeCollectTxn } from "./utils"; +import { Select, SelectContent, SelectItem, SelectTrigger } from "~/components/ui/select"; export const LendingPortfolio = () => { const router = useRouter(); @@ -37,6 +39,8 @@ export const LendingPortfolio = () => { marginfiClient, selectedAccount, extendedBankInfos, + marginfiAccounts, + fetchMrgnlendState, ] = useMrgnlendStore((state) => [ state.initialized, state.extendedBankInfos, @@ -45,6 +49,8 @@ export const LendingPortfolio = () => { state.marginfiClient, state.selectedAccount, state.extendedBankInfos, + state.marginfiAccounts, + state.fetchMrgnlendState, ]); const [setLendingMode] = useUiStore((state) => [state.setLendingMode]); const [userPointsData] = useUserProfileStore((state) => [state.userPointsData]); @@ -54,7 +60,7 @@ export const LendingPortfolio = () => { const [rewardsDialogOpen, setRewardsDialogOpen] = React.useState(false); const [actionTxn, setActionTxn] = React.useState(null); const [rewardsLoading, setRewardsLoading] = React.useState(false); - const { handleSimulation } = useRewardSimulation({ + const { handleSimulation, bankAddressesWithEmissions } = useRewardSimulation({ simulationResult: rewards, actionTxn, marginfiClient, @@ -71,7 +77,9 @@ export const LendingPortfolio = () => { const handleCollectExectuion = React.useCallback(async () => { if (!marginfiClient || !actionTxn) return; - const signature = await executeCollectTxn(marginfiClient, actionTxn, setRewardsLoading); + await executeCollectTxn(marginfiClient, actionTxn, setRewardsLoading, setActionTxn, () => { + setRewardsDialogOpen(false); + }); }, [marginfiClient, actionTxn]); const lendingBanks = React.useMemo( @@ -165,6 +173,33 @@ export const LendingPortfolio = () => { } }, [connected]); + // Switching account logic + const [isSwitchingAccount, setIsSwitchingAccount] = React.useState(false); + + const hasMultipleAccount = React.useMemo(() => marginfiAccounts.length > 1, [marginfiAccounts]); + + const handleSwitchAccount = React.useCallback( + async (value: PublicKey) => { + if (selectedAccount?.address.toBase58() === value.toBase58()) return; + setIsSwitchingAccount(true); + const multiStepToast = new MultiStepToastHandle("Switching account", [{ label: "Fetching account data" }]); + try { + multiStepToast.start(); + const account = marginfiAccounts.find((account) => account.address.toBase58() === value.toBase58()); + if (!account) return; + localStorage.setItem("mfiAccount", account.address.toBase58()); + await fetchMrgnlendState(); + multiStepToast.setSuccessAndNext(); + } catch (error) { + console.error(error); + multiStepToast.setFailed("Failed to switch account"); + } finally { + setIsSwitchingAccount(false); + } + }, + [marginfiAccounts, selectedAccount] + ); + if (isStoreInitialized && !connected) { return ; } @@ -190,6 +225,33 @@ export const LendingPortfolio = () => { return (
+
+ {hasMultipleAccount && ( + <> +

Current account:

+ + + )} +

Lend/borrow

@@ -205,7 +267,7 @@ export const LendingPortfolio = () => { Collect rewards ) : ( - ) @@ -218,7 +280,7 @@ export const LendingPortfolio = () => { - + @@ -226,7 +288,13 @@ export const LendingPortfolio = () => { ? `You are earning rewards on the following banks: ${rewards.rewards .map((r) => r.bank) .join(", ")}` - : "No outstanding rewards. you can earn rewards on the following banks: PYUSD, BSOL and UXD"} + : `You do not have any outstanding rewards. Deposit into a bank with emissions to earn additional rewards on top of yield. Banks with emissions: ${bankAddressesWithEmissions + ?.map( + (bank) => + extendedBankInfos.find((b) => b.meta.address.toBase58() === bank.toBase58())?.meta + .tokenSymbol + ) + .join(", ")}`} {" "} diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/rewards.utils.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/rewards.utils.ts index 5acb10fffe..5b342abddf 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/rewards.utils.ts +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/rewards.utils.ts @@ -17,7 +17,9 @@ const captureException = (error: any, msg: string, tags: Record void + setIsLoading: (isLoading: boolean) => void, + setActionTxn: (actionTxn: VersionedTransaction | null) => void, + closeDialog: () => void ) => { setIsLoading(true); const multiStepToast = new MultiStepToastHandle("Collecting rewards", [ @@ -30,7 +32,8 @@ export const executeCollectTxn = async ( try { const sig = await marginfiClient.processTransaction(actionTxn); multiStepToast.setSuccessAndNext(); - + setActionTxn(null); + closeDialog(); return sig; } catch (error) { const msg = extractErrorString(error); From 4a356c1898d50d6cc63c82706b77c1d5ce3aad86 Mon Sep 17 00:00:00 2001 From: borcherd Date: Fri, 25 Oct 2024 16:33:32 +0800 Subject: [PATCH 06/14] feat: moving positions between accounts --- .../asset-card/portfolio-asset-card.tsx | 88 +++++++++++++++++-- 1 file changed, 79 insertions(+), 9 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx index a6c9b1c878..459e7e124c 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx @@ -2,12 +2,12 @@ import React from "react"; import Image from "next/image"; -import { IconAlertTriangle } from "@tabler/icons-react"; -import { usdFormatter, numeralFormatter, dynamicNumeralFormatter } from "@mrgnlabs/mrgn-common"; +import { IconAlertTriangle, IconInfoCircle } from "@tabler/icons-react"; +import { usdFormatter, numeralFormatter } from "@mrgnlabs/mrgn-common"; import { ActiveBankInfo, ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; -import { capture } from "@mrgnlabs/mrgn-utils"; +import { capture, MultiStepToastHandle } from "@mrgnlabs/mrgn-utils"; import { ActionBox } from "@mrgnlabs/mrgn-ui"; -import { MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; +import { makeBundleTipIx, MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; import { cn } from "@mrgnlabs/mrgn-utils"; import { useAssetItemData } from "~/hooks/useAssetItemData"; @@ -25,6 +25,9 @@ import { DialogDescription, } from "~/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger } from "~/components/ui/select"; +import { TransactionMessage, VersionedTransaction } from "@solana/web3.js"; +import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from "~/components/ui/tooltip"; +import { IconLoader } from "~/components/ui/icons"; interface PortfolioAssetCardProps { bank: ActiveBankInfo; @@ -34,10 +37,11 @@ interface PortfolioAssetCardProps { export const PortfolioAssetCard = ({ bank, isInLendingMode, isBorrower = true }: PortfolioAssetCardProps) => { const { rateAP } = useAssetItemData({ bank, isInLendingMode }); - const [selectedAccount, marginfiAccounts, marginfiClient] = useMrgnlendStore((state) => [ + const [selectedAccount, marginfiAccounts, marginfiClient, fetchMrgnlendState] = useMrgnlendStore((state) => [ state.selectedAccount, state.marginfiAccounts, state.marginfiClient, + state.fetchMrgnlendState, ]); const isIsolated = React.useMemo(() => bank.info.state.isIsolated, [bank]); @@ -194,6 +198,7 @@ export const PortfolioAssetCard = ({ bank, isInLendingMode, isBorrower = true }: marginfiAccounts={marginfiAccounts} bank={bank} marginfiClient={marginfiClient} + fetchMrgnlendState={fetchMrgnlendState} /> )} @@ -308,6 +313,7 @@ export const MovePositionDialog = ({ setIsOpen, bank, marginfiClient, + fetchMrgnlendState, }: { selectedAccount: MarginfiAccountWrapper | null; marginfiAccounts: MarginfiAccountWrapper[]; @@ -315,18 +321,65 @@ export const MovePositionDialog = ({ setIsOpen: (isOpen: boolean) => void; bank: ActiveBankInfo; marginfiClient: MarginfiClient | null; + fetchMrgnlendState: () => void; }) => { const [accountToMoveTo, setAccountToMoveTo] = React.useState( selectedAccount ); + const buttonDisabledState = React.useMemo(() => { + if (accountToMoveTo?.address.toBase58() === selectedAccount?.address.toBase58()) + return "Please select a different account"; + return "active"; + }, [accountToMoveTo, selectedAccount, marginfiAccounts, bank]); + + const [isLoading, setIsLoading] = React.useState(false); const handleMovePosition = React.useCallback(async () => { if (!marginfiClient || !accountToMoveTo) { return; } - const connection = marginfiClient.provider.connection; - // selectedAccount.simulate + const multiStepToast = new MultiStepToastHandle("Moving position", [ + { + label: `Moving to account ${`${accountToMoveTo?.address.toBase58().slice(0, 8)} + ...${accountToMoveTo?.address.toBase58().slice(-8)}`}`, + }, + ]); + multiStepToast.start(); + + try { + setIsLoading(true); + const connection = marginfiClient.provider.connection; + const blockHash = await connection.getLatestBlockhash(); + const withdrawIx = await selectedAccount?.makeWithdrawIx(bank.position.amount, bank.address); + if (!withdrawIx) return; + const withdrawMessage = new TransactionMessage({ + payerKey: marginfiClient.wallet.publicKey, + recentBlockhash: blockHash.blockhash, + instructions: [...withdrawIx.instructions], + }); + const withdrawTx = new VersionedTransaction(withdrawMessage.compileToV0Message()); + + const bundleTipIx = makeBundleTipIx(marginfiClient?.wallet.publicKey); + const depositIx = await accountToMoveTo.makeDepositIx(bank.position.amount, bank.address); + if (!depositIx) return; + const depositInstruction = new TransactionMessage({ + payerKey: marginfiClient.wallet.publicKey, + recentBlockhash: blockHash.blockhash, + instructions: [...depositIx.instructions, bundleTipIx], + }); + const depositTx = new VersionedTransaction(depositInstruction.compileToV0Message()); + + await marginfiClient.processTransactions([withdrawTx, depositTx]); + await fetchMrgnlendState(); + multiStepToast.setSuccessAndNext(); + setIsOpen(false); + } catch (error) { + console.error("Error moving position between accounts", error); + multiStepToast.setFailed("Error moving position between accounts"); + } finally { + setIsLoading(false); + } }, [marginfiClient, accountToMoveTo, marginfiAccounts, selectedAccount, bank]); return ( @@ -378,7 +431,7 @@ export const MovePositionDialog = ({
Account address: -
+
{`${accountToMoveTo?.address.toBase58().slice(0, 8)} ...${accountToMoveTo?.address.toBase58().slice(-8)}`} @@ -387,7 +440,24 @@ export const MovePositionDialog = ({
{" "}
- + + + + + + {buttonDisabledState !== "active" && ( + + {buttonDisabledState} + + )} + + +
+ The transaction will say that there are no balance changes found. The position/funds will be moved between + marginfi accounts, but will remain on the same wallet. +
); From 8b4224782d0707d6898262a2394b2671dcaf9c6d Mon Sep 17 00:00:00 2001 From: borcherd Date: Tue, 29 Oct 2024 09:56:49 +0800 Subject: [PATCH 07/14] feat: ActionMessage refactor v1 **wip** --- .../asset-card/portfolio-asset-card.tsx | 196 +--------- .../components/move-position/index.ts | 1 + .../move-position/move-position-dialog.tsx | 204 +++++++++++ .../common/Portfolio/hooks/index.ts | 1 + .../hooks/use-move-position-simulation.tsx | 81 +++++ .../common/Portfolio/lending-portfolio.tsx | 337 +++++++++--------- .../Portfolio/utils/move-position.utils.ts | 0 .../actions/lend-box/lend-box.tsx | 8 +- .../actions/lend-box/utils/index.ts | 1 - .../actions/lend-box/utils/lend-box.utils.ts | 103 ------ .../actions/loop-box/loop-box.tsx | 14 +- .../actions/loop-box/utils/index.ts | 1 - .../actions/loop-box/utils/loop-box.utils.ts | 62 ---- .../repay-collat-box/repay-collat-box.tsx | 23 +- .../actions/repay-collat-box/utils/index.ts | 1 - .../repay-collat-box/utils/repay-box.utils.ts | 62 ---- .../actions/stake-box/stake-box.tsx | 11 +- .../actions/stake-box/utils/index.ts | 1 - .../stake-box/utils/stake-box.utils.ts | 73 ---- .../action-box-v2/components/index.ts | 1 - .../action-message/action-message.tsx | 0 .../components => }/action-message/index.ts | 0 packages/mrgn-ui/src/components/index.ts | 1 + .../mrgn-utils/src/action-message.utils.ts | 229 ++++++++++++ packages/mrgn-utils/src/index.ts | 1 + 25 files changed, 745 insertions(+), 667 deletions(-) create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/components/move-position/index.ts create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/components/move-position/move-position-dialog.tsx create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-move-position-simulation.tsx create mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/utils/move-position.utils.ts delete mode 100644 packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/lend-box.utils.ts delete mode 100644 packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-box.utils.ts delete mode 100644 packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/repay-box.utils.ts delete mode 100644 packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/utils/stake-box.utils.ts rename packages/mrgn-ui/src/components/{action-box-v2/components => }/action-message/action-message.tsx (100%) rename packages/mrgn-ui/src/components/{action-box-v2/components => }/action-message/index.ts (100%) create mode 100644 packages/mrgn-utils/src/action-message.utils.ts diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx index 459e7e124c..c0e0ba5735 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx @@ -1,33 +1,22 @@ import React from "react"; import Image from "next/image"; +import { IconAlertTriangle } from "@tabler/icons-react"; -import { IconAlertTriangle, IconInfoCircle } from "@tabler/icons-react"; import { usdFormatter, numeralFormatter } from "@mrgnlabs/mrgn-common"; import { ActiveBankInfo, ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; -import { capture, MultiStepToastHandle } from "@mrgnlabs/mrgn-utils"; +import { capture } from "@mrgnlabs/mrgn-utils"; import { ActionBox } from "@mrgnlabs/mrgn-ui"; -import { makeBundleTipIx, MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; import { cn } from "@mrgnlabs/mrgn-utils"; -import { useAssetItemData } from "~/hooks/useAssetItemData"; +import { useAssetItemData } from "~/hooks/useAssetItemData"; +import { useMrgnlendStore, useUiStore } from "~/store"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "~/components/ui/accordion"; import { Button } from "~/components/ui/button"; import { Skeleton } from "~/components/ui/skeleton"; import { useWallet } from "~/components/wallet-v2/hooks/use-wallet.hook"; -import { useMrgnlendStore, useUiStore } from "~/store"; -import { - Dialog, - DialogContent, - DialogTrigger, - DialogHeader, - DialogTitle, - DialogDescription, -} from "~/components/ui/dialog"; -import { Select, SelectContent, SelectItem, SelectTrigger } from "~/components/ui/select"; -import { TransactionMessage, VersionedTransaction } from "@solana/web3.js"; -import { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from "~/components/ui/tooltip"; -import { IconLoader } from "~/components/ui/icons"; + +import { MovePositionDialog } from "../move-position"; interface PortfolioAssetCardProps { bank: ActiveBankInfo; @@ -37,12 +26,15 @@ interface PortfolioAssetCardProps { export const PortfolioAssetCard = ({ bank, isInLendingMode, isBorrower = true }: PortfolioAssetCardProps) => { const { rateAP } = useAssetItemData({ bank, isInLendingMode }); - const [selectedAccount, marginfiAccounts, marginfiClient, fetchMrgnlendState] = useMrgnlendStore((state) => [ - state.selectedAccount, - state.marginfiAccounts, - state.marginfiClient, - state.fetchMrgnlendState, - ]); + const [selectedAccount, marginfiAccounts, marginfiClient, fetchMrgnlendState, extendedBankInfos] = useMrgnlendStore( + (state) => [ + state.selectedAccount, + state.marginfiAccounts, + state.marginfiClient, + state.fetchMrgnlendState, + state.extendedBankInfos, + ] + ); const isIsolated = React.useMemo(() => bank.info.state.isIsolated, [bank]); const isUserPositionPoorHealth = React.useMemo(() => { @@ -199,6 +191,7 @@ export const PortfolioAssetCard = ({ bank, isInLendingMode, isBorrower = true }: bank={bank} marginfiClient={marginfiClient} fetchMrgnlendState={fetchMrgnlendState} + extendedBankInfos={extendedBankInfos} /> )} @@ -305,160 +298,3 @@ export const PortfolioAssetCardSkeleton = () => {
); }; - -export const MovePositionDialog = ({ - selectedAccount, - marginfiAccounts, - isOpen, - setIsOpen, - bank, - marginfiClient, - fetchMrgnlendState, -}: { - selectedAccount: MarginfiAccountWrapper | null; - marginfiAccounts: MarginfiAccountWrapper[]; - isOpen: boolean; - setIsOpen: (isOpen: boolean) => void; - bank: ActiveBankInfo; - marginfiClient: MarginfiClient | null; - fetchMrgnlendState: () => void; -}) => { - const [accountToMoveTo, setAccountToMoveTo] = React.useState( - selectedAccount - ); - const buttonDisabledState = React.useMemo(() => { - if (accountToMoveTo?.address.toBase58() === selectedAccount?.address.toBase58()) - return "Please select a different account"; - return "active"; - }, [accountToMoveTo, selectedAccount, marginfiAccounts, bank]); - - const [isLoading, setIsLoading] = React.useState(false); - - const handleMovePosition = React.useCallback(async () => { - if (!marginfiClient || !accountToMoveTo) { - return; - } - - const multiStepToast = new MultiStepToastHandle("Moving position", [ - { - label: `Moving to account ${`${accountToMoveTo?.address.toBase58().slice(0, 8)} - ...${accountToMoveTo?.address.toBase58().slice(-8)}`}`, - }, - ]); - multiStepToast.start(); - - try { - setIsLoading(true); - const connection = marginfiClient.provider.connection; - const blockHash = await connection.getLatestBlockhash(); - const withdrawIx = await selectedAccount?.makeWithdrawIx(bank.position.amount, bank.address); - if (!withdrawIx) return; - const withdrawMessage = new TransactionMessage({ - payerKey: marginfiClient.wallet.publicKey, - recentBlockhash: blockHash.blockhash, - instructions: [...withdrawIx.instructions], - }); - const withdrawTx = new VersionedTransaction(withdrawMessage.compileToV0Message()); - - const bundleTipIx = makeBundleTipIx(marginfiClient?.wallet.publicKey); - const depositIx = await accountToMoveTo.makeDepositIx(bank.position.amount, bank.address); - if (!depositIx) return; - const depositInstruction = new TransactionMessage({ - payerKey: marginfiClient.wallet.publicKey, - recentBlockhash: blockHash.blockhash, - instructions: [...depositIx.instructions, bundleTipIx], - }); - const depositTx = new VersionedTransaction(depositInstruction.compileToV0Message()); - - await marginfiClient.processTransactions([withdrawTx, depositTx]); - await fetchMrgnlendState(); - multiStepToast.setSuccessAndNext(); - setIsOpen(false); - } catch (error) { - console.error("Error moving position between accounts", error); - multiStepToast.setFailed("Error moving position between accounts"); - } finally { - setIsLoading(false); - } - }, [marginfiClient, accountToMoveTo, marginfiAccounts, selectedAccount, bank]); - - return ( - { - setIsOpen(value); - }} - > - - - Move position to another account - Move your position to another account - - -
-
-
Token value:
-
- {bank.position.amount < 0.01 ? "< $0.01" : numeralFormatter(bank.position.amount)} - {" " + bank.meta.tokenSymbol} -
-
USD value
-
- {bank.position.usdValue < 0.01 ? "< $0.01" : usdFormatter.format(bank.position.usdValue)} -
-
-
- Select account to move position to: - -
-
- Account address: -
- - {`${accountToMoveTo?.address.toBase58().slice(0, 8)} - ...${accountToMoveTo?.address.toBase58().slice(-8)}`} - -
-
{" "} -
- - - - - - - {buttonDisabledState !== "active" && ( - - {buttonDisabledState} - - )} - - -
- The transaction will say that there are no balance changes found. The position/funds will be moved between - marginfi accounts, but will remain on the same wallet. -
-
-
- ); -}; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/move-position/index.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/move-position/index.ts new file mode 100644 index 0000000000..bedf2dd47f --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/move-position/index.ts @@ -0,0 +1 @@ +export * from "./move-position-dialog"; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/move-position/move-position-dialog.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/move-position/move-position-dialog.tsx new file mode 100644 index 0000000000..9bfbfd8f93 --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/move-position/move-position-dialog.tsx @@ -0,0 +1,204 @@ +import React from "react"; + +import Image from "next/image"; +import { TransactionMessage, VersionedTransaction } from "@solana/web3.js"; + +import { usdFormatter, numeralFormatter } from "@mrgnlabs/mrgn-common"; +import { ActiveBankInfo, ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; +import { checkLendActionAvailable, MultiStepToastHandle } from "@mrgnlabs/mrgn-utils"; +import { makeBundleTipIx, MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; + +import { Button } from "~/components/ui/button"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "~/components/ui/dialog"; +import { Select, SelectContent, SelectItem, SelectTrigger } from "~/components/ui/select"; +import { IconLoader } from "~/components/ui/icons"; +import { useMoveSimulation } from "../../hooks"; +import { ActionMessage } from "~/components"; + +export const MovePositionDialog = ({ + selectedAccount, + marginfiAccounts, + isOpen, + setIsOpen, + bank, + marginfiClient, + fetchMrgnlendState, + extendedBankInfos, +}: { + selectedAccount: MarginfiAccountWrapper | null; + marginfiAccounts: MarginfiAccountWrapper[]; + isOpen: boolean; + setIsOpen: (isOpen: boolean) => void; + bank: ActiveBankInfo; + marginfiClient: MarginfiClient | null; + fetchMrgnlendState: () => void; + extendedBankInfos: ExtendedBankInfo[]; +}) => { + const [accountToMoveTo, setAccountToMoveTo] = React.useState(undefined); + const [actionTxns, setActionTxns] = React.useState([]); + const [isLoading, setIsLoading] = React.useState(false); + + const { handleSimulateTxns } = useMoveSimulation({ + actionTxns, + marginfiClient, + accountToMoveTo, + selectedAccount, + activeBank: bank, + extendedBankInfos, + setActionTxns, + setIsLoading, + }); + + const actionMethods = React.useMemo(() => { + const withdrawActionResult = checkLendActionAvailable({ + amount: bank.position.amount, + connected: true, + selectedBank: bank, + nativeSolBalance: 0, + banks: extendedBankInfos, + lendMode: ActionType.Withdraw, + marginfiAccount: selectedAccount, + }); + + const depositActionResult = checkLendActionAvailable({ + amount: bank.position.amount, + connected: true, + selectedBank: bank, + nativeSolBalance: 0, + banks: extendedBankInfos, + lendMode: ActionType.Deposit, + marginfiAccount: accountToMoveTo!, + }); + + return [...withdrawActionResult, ...depositActionResult]; + }, [bank, selectedAccount, extendedBankInfos, accountToMoveTo]); + + const isButtonDisabled = React.useMemo(() => { + if (!accountToMoveTo) return true; + if (actionMethods && actionMethods.filter((value) => value.isEnabled === false).length > 0) return true; + if (isLoading) return true; + return false; + }, [accountToMoveTo, actionMethods, isLoading]); + + const handleMovePosition = React.useCallback(async () => { + if (!marginfiClient || !accountToMoveTo || !actionTxns) { + return; + } + + const multiStepToast = new MultiStepToastHandle("Moving position", [ + { + label: `Moving to account ${`${accountToMoveTo?.address.toBase58().slice(0, 8)} + ...${accountToMoveTo?.address.toBase58().slice(-8)}`}`, + }, + ]); + multiStepToast.start(); + setIsLoading(true); + try { + await marginfiClient.processTransactions(actionTxns); + await fetchMrgnlendState(); + multiStepToast.setSuccessAndNext(); + setIsOpen(false); + } catch (error) { + console.error("Error moving position between accounts", error); + multiStepToast.setFailed("Error moving position between accounts"); // TODO: update + } finally { + setIsLoading(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [marginfiClient, accountToMoveTo, selectedAccount, bank]); + + React.useEffect(() => { + if (!accountToMoveTo) return; + handleSimulateTxns(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [accountToMoveTo]); + + return ( + { + setIsOpen(value); + }} + > + + + Move position to another account + Move your position to another account + + +
+
+
Token value:
+
+ {bank.position.amount < 0.01 ? "< $0.01" : numeralFormatter(bank.position.amount)} + {" " + bank.meta.tokenSymbol} +
+
USD value
+
+ {bank.position.usdValue < 0.01 ? "< $0.01" : usdFormatter.format(bank.position.usdValue)} +
+
+
+ Select account to move position to: + +
+ {accountToMoveTo && ( +
+ Account address: +
+ + {`${accountToMoveTo?.address.toBase58().slice(0, 8)} + ...${accountToMoveTo?.address.toBase58().slice(-8)}`} + +
+
+ )} +
+ + {actionMethods.map( + (actionMethod, idx) => + actionMethod.description && ( +
+ +
+ ) + )} + + + +
+ The transaction will look like there are no balance changes for this position. The position/funds will be + moved between marginfi accounts, but will remain on the same wallet. +
+
+
+ ); +}; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/index.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/index.ts index 98840dbd94..f4bf140fc1 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/index.ts +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/index.ts @@ -1 +1,2 @@ export * from "./use-reward-simulation"; +export * from "./use-move-position-simulation"; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-move-position-simulation.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-move-position-simulation.tsx new file mode 100644 index 0000000000..9a54b11514 --- /dev/null +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-move-position-simulation.tsx @@ -0,0 +1,81 @@ +import React from "react"; + +import { TransactionMessage, VersionedTransaction } from "@solana/web3.js"; + +import { makeBundleTipIx, MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; +import { ActionMethod } from "@mrgnlabs/mrgn-utils"; +import { ActiveBankInfo, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; + +type MovePositionSimulationProps = { + actionTxns: VersionedTransaction[] | null; + marginfiClient: MarginfiClient | null; + selectedAccount: MarginfiAccountWrapper | null; + accountToMoveTo: MarginfiAccountWrapper | undefined; + extendedBankInfos: ExtendedBankInfo[]; + activeBank: ActiveBankInfo; + + setActionTxns: (actionTxn: VersionedTransaction[]) => void; + setIsLoading: (isLoading: boolean) => void; +}; + +export const useMoveSimulation = ({ + marginfiClient, + accountToMoveTo, + selectedAccount, + activeBank, + setActionTxns, + setIsLoading, +}: MovePositionSimulationProps) => { + const generateTxns = React.useCallback(async () => { + if (!marginfiClient || !accountToMoveTo) { + return; + } + try { + setIsLoading(true); + const connection = marginfiClient.provider.connection; + const blockHash = await connection.getLatestBlockhash(); + const withdrawIx = await selectedAccount?.makeWithdrawIx(activeBank.position.amount, activeBank.address); + if (!withdrawIx) return; + const withdrawMessage = new TransactionMessage({ + payerKey: marginfiClient.wallet.publicKey, + recentBlockhash: blockHash.blockhash, + instructions: [...withdrawIx.instructions], + }); + const withdrawTx = new VersionedTransaction(withdrawMessage.compileToV0Message()); + + const bundleTipIx = makeBundleTipIx(marginfiClient?.wallet.publicKey); + const depositIx = await accountToMoveTo.makeDepositIx(activeBank.position.amount, activeBank.address); + if (!depositIx) return; + const depositInstruction = new TransactionMessage({ + payerKey: marginfiClient.wallet.publicKey, + recentBlockhash: blockHash.blockhash, + instructions: [...depositIx.instructions, bundleTipIx], + }); + const depositTx = new VersionedTransaction(depositInstruction.compileToV0Message()); + return [withdrawTx, depositTx]; + } catch (error) { + console.error("Error creating transactions", error); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [marginfiClient, accountToMoveTo, selectedAccount, activeBank]); + + const handleSimulateTxns = React.useCallback(async () => { + try { + const txns = await generateTxns(); + if (!txns) return; + + const simulationResult = await marginfiClient?.simulateTransactions(txns, []); + + if (!simulationResult) return; + setActionTxns(txns); + } catch (error) { + } finally { + setIsLoading(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [generateTxns]); + + return { + handleSimulateTxns, + }; +}; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx index b358344644..2b56f4c627 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx @@ -25,6 +25,7 @@ import { rewardsType } from "./types"; import { useRewardSimulation } from "./hooks"; import { executeCollectTxn } from "./utils"; import { Select, SelectContent, SelectItem, SelectTrigger } from "~/components/ui/select"; +import { EMISSION_MINT_INFO_MAP } from "~/components/desktop/AssetList/components"; export const LendingPortfolio = () => { const router = useRouter(); @@ -224,8 +225,8 @@ export const LendingPortfolio = () => { } return ( -
-
+
+
{hasMultipleAccount && ( <>

Current account:

@@ -252,183 +253,181 @@ export const LendingPortfolio = () => { )}
-
-

Lend/borrow

+
+
+

Lend/borrow

-
- {rewards ? ( - rewards.totalReward > 0 ? ( - +
+ {rewards ? ( + rewards.totalReward > 0 ? ( + + ) : ( + + ) ) : ( - - ) - ) : ( - - Calculating rewards - - )}{" "} - {rewards && ( - - - - - - - - {rewards && rewards.totalReward > 0 - ? `You are earning rewards on the following banks: ${rewards.rewards - .map((r) => r.bank) - .join(", ")}` - : `You do not have any outstanding rewards. Deposit into a bank with emissions to earn additional rewards on top of yield. Banks with emissions: ${bankAddressesWithEmissions - ?.map( - (bank) => - extendedBankInfos.find((b) => b.meta.address.toBase58() === bank.toBase58())?.meta - .tokenSymbol - ) - .join(", ")}`} - {" "} - - - - )} + + Calculating rewards + + )}{" "} + {rewards && ( + + + + + + + + {rewards && rewards.totalReward > 0 + ? `You are earning rewards on the following banks: ${rewards.rewards + .map((r) => r.bank) + .join(", ")}` + : `You do not have any outstanding rewards. Deposit into a bank with emissions to earn additional rewards on top of yield. Banks with emissions: ${[ + ...EMISSION_MINT_INFO_MAP.keys(), + ].join(", ")}`} + {" "} + + + + )} +
-
-
-
-
- Lend/borrow health factor - - - - - - -
-

- Health factor is based off price biased and weighted asset and liability values. -

-
- When your account health reaches 0% or below, you are exposed to liquidation. +
+
+
+ Lend/borrow health factor + + + + + + +
+

+ Health factor is based off price biased and weighted asset and liability values. +

+
+ When your account health reaches 0% or below, you are exposed to liquidation. +
+

The formula is:

+

{"(assets - liabilities) / (assets)"}

+

Your math is:

+

{`(${usdFormatter.format( + accountSummary.lendingAmountWithBiasAndWeighted + )} - ${usdFormatter.format( + accountSummary.borrowingAmountWithBiasAndWeighted + )}) / (${usdFormatter.format(accountSummary.lendingAmountWithBiasAndWeighted)})`}

-

The formula is:

-

{"(assets - liabilities) / (assets)"}

-

Your math is:

-

{`(${usdFormatter.format( - accountSummary.lendingAmountWithBiasAndWeighted - )} - ${usdFormatter.format( - accountSummary.borrowingAmountWithBiasAndWeighted - )}) / (${usdFormatter.format(accountSummary.lendingAmountWithBiasAndWeighted)})`}

-
- - - -
-
- {numeralFormatter(accountSummary.healthFactor * 100)}% -
-
-
-
+ + + +
+ {numeralFormatter(accountSummary.healthFactor * 100)}% +
+ +
+
+
+
- -
-
-
-
-
Supplied
-
{accountSupplied}
-
- {isStoreInitialized ? ( - lendingBanks.length > 0 ? ( -
- {lendingBanks.map((bank) => ( - 0} - /> - ))} -
+
+
+
+
Supplied
+
{accountSupplied}
+
+ {isStoreInitialized ? ( + lendingBanks.length > 0 ? ( +
+ {lendingBanks.map((bank) => ( + 0} + /> + ))} +
+ ) : ( +
+ No lending positions found. +
+ ) ) : ( -
- No lending positions found. -
- ) - ) : ( - - )} -
-
-
-
Borrowed
-
{accountBorrowed}
-
- {isStoreInitialized ? ( - borrowingBanks.length > 0 ? ( -
- {borrowingBanks.map((bank) => ( - 0} - /> - ))} -
+ + )} +
+
+
+
Borrowed
+
{accountBorrowed}
+
+ {isStoreInitialized ? ( + borrowingBanks.length > 0 ? ( +
+ {borrowingBanks.map((bank) => ( + 0} + /> + ))} +
+ ) : ( +
+ No borrow positions found.{" "} + {" "} + and open a new borrow. +
+ ) ) : ( -
- No borrow positions found.{" "} - {" "} - and open a new borrow. -
- ) - ) : ( - - )} + + )} +
+ { + setRewardsDialogOpen(false); + }} + open={rewardsDialogOpen} + onOpenChange={(open) => { + setRewardsDialogOpen(open); + }} + onCollect={handleCollectExectuion} + isLoading={rewardsLoading} + /> + {previousTxn && }
- { - setRewardsDialogOpen(false); - }} - open={rewardsDialogOpen} - onOpenChange={(open) => { - setRewardsDialogOpen(open); - }} - onCollect={handleCollectExectuion} - isLoading={rewardsLoading} - /> - {previousTxn && }
); }; diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/move-position.utils.ts b/apps/marginfi-v2-ui/src/components/common/Portfolio/utils/move-position.utils.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/lend-box.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/lend-box.tsx index 7c645b241c..8658275323 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/lend-box.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/lend-box.tsx @@ -12,16 +12,18 @@ import { DEFAULT_ACCOUNT_SUMMARY, } from "@mrgnlabs/marginfi-v2-ui-state"; import { ActionMethod, MarginfiActionParams, PreviousTxn, useConnection, usePriorityFee } from "@mrgnlabs/mrgn-utils"; +import { ActionMethod, checkLendActionAvailable, MarginfiActionParams, PreviousTxn } from "@mrgnlabs/mrgn-utils"; import { MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; -import { ActionButton, ActionMessage, ActionSettingsButton } from "~/components/action-box-v2/components"; +import { ActionButton } from "~/components/action-box-v2/components"; import { useActionAmounts } from "~/components/action-box-v2/hooks"; import { LSTDialog, LSTDialogVariants } from "~/components/LSTDialog"; import { WalletContextStateOverride } from "~/components/wallet-v2/hooks/use-wallet.hook"; +import { ActionMessage } from "~/components"; import { useLendBoxStore } from "./store"; -import { checkActionAvailable, handleExecuteCloseBalance, handleExecuteLendingAction } from "./utils"; +import { handleExecuteCloseBalance, handleExecuteLendingAction } from "./utils"; import { Collateral, ActionInput, Preview } from "./components"; import { useLendSimulation } from "./hooks"; import { useActionBoxStore } from "../../store"; @@ -187,7 +189,7 @@ export const LendBox = ({ const actionMethods = React.useMemo( () => - checkActionAvailable({ + checkLendActionAvailable({ amount, connected, showCloseBalance, diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/index.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/index.ts index 4a239fae07..0c96afda25 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/index.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/index.ts @@ -1,3 +1,2 @@ -export * from "./lend-box.utils"; export * from "./lend-simulation.utils"; export * from "./lend-actions.utils"; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/lend-box.utils.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/lend-box.utils.ts deleted file mode 100644 index 49ffeea074..0000000000 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/lend-box.utils.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { - ActionMethod, - ActionMethodType, - canBeBorrowed, - canBeLent, - canBeRepaid, - canBeWithdrawn, -} from "@mrgnlabs/mrgn-utils"; -import { ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; -import { MarginfiAccountWrapper } from "@mrgnlabs/marginfi-client-v2"; - -export function getColorForActionMethodType(type?: ActionMethodType) { - if (type === "INFO") { - return "info"; - } else if (type === "WARNING") { - return "alert"; - } else { - return "alert"; - } -} - -interface CheckActionAvailableProps { - amount: number | null; - connected: boolean; - nativeSolBalance: number; - showCloseBalance?: boolean; - selectedBank: ExtendedBankInfo | null; - banks: ExtendedBankInfo[]; - marginfiAccount: MarginfiAccountWrapper | null; - lendMode: ActionType; -} - -export function checkActionAvailable({ - amount, - nativeSolBalance, - connected, - showCloseBalance, - selectedBank, - banks, - marginfiAccount, - lendMode, -}: CheckActionAvailableProps): ActionMethod[] { - let checks: ActionMethod[] = []; - - const requiredCheck = getRequiredCheck(connected, selectedBank); - if (requiredCheck) return [requiredCheck]; - - const generalChecks = getGeneralChecks(amount ?? 0, showCloseBalance); - if (generalChecks) checks.push(...generalChecks); - - // allert checks - if (selectedBank) { - switch (lendMode) { - case ActionType.Deposit: - const lentChecks = canBeLent(selectedBank, nativeSolBalance); - if (lentChecks.length) checks.push(...lentChecks); - break; - case ActionType.Withdraw: - const withdrawChecks = canBeWithdrawn(selectedBank, marginfiAccount); - if (withdrawChecks.length) checks.push(...withdrawChecks); - break; - case ActionType.Borrow: - const borrowChecks = canBeBorrowed(selectedBank, banks, marginfiAccount); - if (borrowChecks.length) checks.push(...borrowChecks); - break; - case ActionType.Repay: - const repayChecks = canBeRepaid(selectedBank); - if (repayChecks) checks.push(...repayChecks); - break; - } - } - - if (checks.length === 0) - checks.push({ - isEnabled: true, - }); - - return checks; -} - -function getRequiredCheck(connected: boolean, selectedBank: ExtendedBankInfo | null): ActionMethod | null { - if (!connected) { - return { isEnabled: false }; - } - if (!selectedBank) { - return { isEnabled: false }; - } - - return null; -} - -function getGeneralChecks(amount: number = 0, showCloseBalance?: boolean): ActionMethod[] { - let checks: ActionMethod[] = []; - if (showCloseBalance) { - checks.push({ actionMethod: "INFO", description: "Close lending balance.", isEnabled: true }); - } - - if (amount === 0) { - checks.push({ isEnabled: false }); - } - - return checks; -} diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/loop-box.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/loop-box.tsx index c71a7a00ff..4fc3df3450 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/loop-box.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/loop-box.tsx @@ -17,18 +17,26 @@ import { useConnection, usePriorityFee, } from "@mrgnlabs/mrgn-utils"; +import { + ActionMethod, + checkLoopActionAvailable, + MarginfiActionParams, + PreviousTxn, + showErrorToast, +} from "@mrgnlabs/mrgn-utils"; import { MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; import { useAmountDebounce } from "~/hooks/useAmountDebounce"; import { WalletContextStateOverride } from "~/components/wallet-v2"; import { CircularProgress } from "~/components/ui/circular-progress"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "~/components/ui/tooltip"; -import { ActionButton, ActionMessage, ActionSettingsButton } from "~/components/action-box-v2/components"; +import { ActionButton, ActionSettingsButton } from "~/components/action-box-v2/components"; import { useActionAmounts, usePollBlockHeight } from "~/components/action-box-v2/hooks"; +import { ActionMessage } from "~/components"; import { useActionBoxStore } from "../../store"; -import { checkActionAvailable, handleExecuteLoopAction } from "./utils"; +import { handleExecuteLoopAction } from "./utils"; import { ActionInput, Preview } from "./components"; import { useLoopBoxStore } from "./store"; import { useLoopSimulation } from "./hooks"; @@ -203,7 +211,7 @@ export const LoopBox = ({ const actionMethods = React.useMemo( () => - checkActionAvailable({ + checkLoopActionAvailable({ amount, connected, selectedBank, diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/index.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/index.ts index 4fbf0831d9..a9424155a8 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/index.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/index.ts @@ -1,3 +1,2 @@ export * from "./loop-simulation.utils"; export * from "./loop-action.utils"; -export * from "./loop-box.utils"; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-box.utils.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-box.utils.ts deleted file mode 100644 index e33ded5a63..0000000000 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-box.utils.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { QuoteResponse } from "@jup-ag/api"; - -import { ActionMethod, canBeLooped } from "@mrgnlabs/mrgn-utils"; -import { ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; - -interface CheckActionAvailableProps { - amount: number | null; - connected: boolean; - selectedBank: ExtendedBankInfo | null; - selectedSecondaryBank: ExtendedBankInfo | null; - actionQuote: QuoteResponse | null; -} - -export function checkActionAvailable({ - amount, - connected, - selectedBank, - selectedSecondaryBank, - actionQuote, -}: CheckActionAvailableProps): ActionMethod[] { - let checks: ActionMethod[] = []; - - const requiredCheck = getRequiredCheck(connected, selectedBank); - if (requiredCheck) return [requiredCheck]; - - const generalChecks = getGeneralChecks(amount ?? 0); - if (generalChecks) checks.push(...generalChecks); - - // allert checks - if (selectedBank) { - const loopChecks = canBeLooped(selectedBank, selectedSecondaryBank, actionQuote); - if (loopChecks.length) checks.push(...loopChecks); - } - - if (checks.length === 0) - checks.push({ - isEnabled: true, - }); - - return checks; -} - -function getRequiredCheck(connected: boolean, selectedBank: ExtendedBankInfo | null): ActionMethod | null { - if (!connected) { - return { isEnabled: false }; - } - if (!selectedBank) { - return { isEnabled: false }; - } - - return null; -} - -function getGeneralChecks(amount: number = 0): ActionMethod[] { - let checks: ActionMethod[] = []; - - if (amount === 0) { - checks.push({ isEnabled: false }); - } - - return checks; -} diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/repay-collat-box.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/repay-collat-box.tsx index b41dbcf338..9bba04b85c 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/repay-collat-box.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/repay-collat-box.tsx @@ -16,14 +16,22 @@ import { useConnection, usePriorityFee, } from "@mrgnlabs/mrgn-utils"; +import { + ActionMethod, + checkRepayCollatActionAvailable, + MarginfiActionParams, + PreviousTxn, + showErrorToast, +} from "@mrgnlabs/mrgn-utils"; import { MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; import { CircularProgress } from "~/components/ui/circular-progress"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "~/components/ui/tooltip"; -import { ActionButton, ActionMessage, ActionSettingsButton } from "~/components/action-box-v2/components"; +import { ActionButton, ActionSettingsButton } from "~/components/action-box-v2/components"; import { useActionAmounts, usePollBlockHeight } from "~/components/action-box-v2/hooks"; +import { ActionMessage } from "~/components"; -import { checkActionAvailable, handleExecuteRepayCollatAction } from "./utils"; +import { handleExecuteRepayCollatAction } from "./utils"; import { Collateral, ActionInput, Preview } from "./components"; import { useRepayCollatBoxStore } from "./store"; import { useRepayCollatSimulation } from "./hooks"; @@ -189,7 +197,7 @@ export const RepayCollatBox = ({ const actionMethods = React.useMemo( () => - checkActionAvailable({ + checkRepayCollatActionAvailable({ amount, connected, selectedBank, @@ -344,3 +352,12 @@ export const RepayCollatBox = ({ ); }; +function checkRepayColatActionAvailable(arg0: { + amount: number; + connected: boolean; + selectedBank: ExtendedBankInfo | null; + selectedSecondaryBank: ExtendedBankInfo | null; + actionQuote: import("@jup-ag/api").QuoteResponse | null; +}): any { + throw new Error("Function not implemented."); +} diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/index.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/index.ts index d35aa86f4f..3a7e6b1354 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/index.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/index.ts @@ -1,3 +1,2 @@ export * from "./repay-simulation.utils"; export * from "./repay-action.utils"; -export * from "./repay-box.utils"; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/repay-box.utils.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/repay-box.utils.ts deleted file mode 100644 index ef84137e50..0000000000 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/repay-box.utils.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { QuoteResponse } from "@jup-ag/api"; - -import { ActionMethod, canBeRepaidCollat } from "@mrgnlabs/mrgn-utils"; -import { ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; - -interface CheckActionAvailableProps { - amount: number | null; - connected: boolean; - selectedBank: ExtendedBankInfo | null; - selectedSecondaryBank: ExtendedBankInfo | null; - actionQuote: QuoteResponse | null; -} - -export function checkActionAvailable({ - amount, - connected, - selectedBank, - selectedSecondaryBank, - actionQuote, -}: CheckActionAvailableProps): ActionMethod[] { - let checks: ActionMethod[] = []; - - const requiredCheck = getRequiredCheck(connected, selectedBank); - if (requiredCheck) return [requiredCheck]; - - const generalChecks = getGeneralChecks(amount ?? 0); - if (generalChecks) checks.push(...generalChecks); - - // allert checks - if (selectedBank) { - const repayChecks = canBeRepaidCollat(selectedBank, selectedSecondaryBank, [], actionQuote); - if (repayChecks) checks.push(...repayChecks); - } - - if (checks.length === 0) - checks.push({ - isEnabled: true, - }); - - return checks; -} - -function getRequiredCheck(connected: boolean, selectedBank: ExtendedBankInfo | null): ActionMethod | null { - if (!connected) { - return { isEnabled: false }; - } - if (!selectedBank) { - return { isEnabled: false }; - } - - return null; -} - -function getGeneralChecks(amount: number = 0): ActionMethod[] { - let checks: ActionMethod[] = []; - - if (amount === 0) { - checks.push({ isEnabled: false }); - } - - return checks; -} diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/stake-box.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/stake-box.tsx index 339e126642..b5bb4ad81c 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/stake-box.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/stake-box.tsx @@ -2,8 +2,9 @@ import React, { useEffect } from "react"; import { WalletContextState } from "@solana/wallet-adapter-react"; -import { getPriceWithConfidence, MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; +import { getPriceWithConfidence, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; import { AccountSummary, ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; +import { ActionMethod, checkStakeActionAvailable, LstData, PreviousTxn } from "@mrgnlabs/mrgn-utils"; import { ActionMethod, LstData, @@ -14,17 +15,19 @@ import { } from "@mrgnlabs/mrgn-utils"; import { nativeToUi, NATIVE_MINT as SOL_MINT, uiToNative } from "@mrgnlabs/mrgn-common"; -import { useActionAmounts, usePollBlockHeight } from "~/components/action-box-v2/hooks"; +import { useActionAmounts } from "~/components/action-box-v2/hooks"; import { WalletContextStateOverride } from "~/components/wallet-v2/hooks/use-wallet.hook"; +import { ActionMessage } from "~/components"; import { useStakeBoxStore } from "./store"; import { AmountPreview } from "./components/amount-preview"; -import { ActionButton, ActionMessage, ActionSettingsButton } from "../../components"; +import { ActionButton, ActionSettingsButton } from "../../components"; import { StatsPreview } from "./components/stats-preview"; import { useStakeSimulation } from "./hooks"; import { useActionBoxStore } from "../../store"; import { handleExecuteLstAction } from "./utils/stake-action.utils"; import { ActionInput } from "./components/action-input"; +import { useStakeBoxContext } from "../../contexts"; import { checkActionAvailable } from "./utils"; import { useActionContext, useStakeBoxContext } from "../../contexts"; @@ -257,7 +260,7 @@ export const StakeBox = ({ const actionMethods = React.useMemo(() => { setAdditionalActionMethods([]); - return checkActionAvailable({ + return checkStakeActionAvailable({ amount, connected, selectedBank, diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/utils/index.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/utils/index.ts index 45dfedc61b..c43ce95fdb 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/utils/index.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/utils/index.ts @@ -1,2 +1 @@ export * from "./stake-simulation.utils"; -export * from "./stake-box.utils"; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/utils/stake-box.utils.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/utils/stake-box.utils.ts deleted file mode 100644 index 79a7c73478..0000000000 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/utils/stake-box.utils.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { QuoteResponse } from "@jup-ag/api"; - -import { ActionMethod, DYNAMIC_SIMULATION_ERRORS, LstData } from "@mrgnlabs/mrgn-utils"; -import { ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; -import { IconAdjustmentsQuestion } from "@tabler/icons-react"; - -interface CheckActionAvailableProps { - amount: number | null; - connected: boolean; - selectedBank: ExtendedBankInfo | null; - actionQuote: QuoteResponse | null; - lstData: LstData | null; -} - -export function checkActionAvailable({ - amount, - connected, - selectedBank, - actionQuote, - lstData, -}: CheckActionAvailableProps): ActionMethod[] { - let checks: ActionMethod[] = []; - - const requiredCheck = getRequiredCheck(connected, selectedBank); - if (requiredCheck) return [requiredCheck]; - - const generalChecks = getGeneralChecks(amount ?? 0); - if (generalChecks) checks.push(...generalChecks); - - if (selectedBank?.meta.tokenSymbol !== "SOL" && !actionQuote) checks.push({ isEnabled: false }); - - if (actionQuote?.priceImpactPct && Number(actionQuote.priceImpactPct) > 0.01) { - if (actionQuote?.priceImpactPct && Number(actionQuote.priceImpactPct) > 0.05) { - checks.push(DYNAMIC_SIMULATION_ERRORS.PRICE_IMPACT_ERROR_CHECK(Number(actionQuote.priceImpactPct))); - } else { - checks.push(DYNAMIC_SIMULATION_ERRORS.PRICE_IMPACT_WARNING_CHECK(Number(actionQuote.priceImpactPct))); - } - } - - if (lstData && lstData?.updateRequired) - checks.push({ - isEnabled: false, - description: "Epoch change detected - staking available again shortly", - }); - - if (checks.length === 0) - checks.push({ - isEnabled: true, - }); - - return checks; -} - -function getRequiredCheck(connected: boolean, selectedBank: ExtendedBankInfo | null): ActionMethod | null { - if (!connected) { - return { isEnabled: false }; - } - if (!selectedBank) { - return { isEnabled: false }; - } - - return null; -} - -function getGeneralChecks(amount: number = 0): ActionMethod[] { - let checks: ActionMethod[] = []; - - if (amount === 0) { - checks.push({ isEnabled: false }); - } - - return checks; -} diff --git a/packages/mrgn-ui/src/components/action-box-v2/components/index.ts b/packages/mrgn-ui/src/components/action-box-v2/components/index.ts index 59cb12efb7..4df5c7cb1c 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/components/index.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/components/index.ts @@ -1,6 +1,5 @@ export * from "./action-input"; export * from "./action-stats"; -export * from "./action-message"; export * from "./action-settings"; export * from "./action-button"; export * from "./action-wrappers"; diff --git a/packages/mrgn-ui/src/components/action-box-v2/components/action-message/action-message.tsx b/packages/mrgn-ui/src/components/action-message/action-message.tsx similarity index 100% rename from packages/mrgn-ui/src/components/action-box-v2/components/action-message/action-message.tsx rename to packages/mrgn-ui/src/components/action-message/action-message.tsx diff --git a/packages/mrgn-ui/src/components/action-box-v2/components/action-message/index.ts b/packages/mrgn-ui/src/components/action-message/index.ts similarity index 100% rename from packages/mrgn-ui/src/components/action-box-v2/components/action-message/index.ts rename to packages/mrgn-ui/src/components/action-message/index.ts diff --git a/packages/mrgn-ui/src/components/index.ts b/packages/mrgn-ui/src/components/index.ts index a06371a27f..e483833368 100644 --- a/packages/mrgn-ui/src/components/index.ts +++ b/packages/mrgn-ui/src/components/index.ts @@ -2,4 +2,5 @@ export * from "./action-box-v2"; export * from "./wallet-v2"; export * from "./LSTDialog"; export * from "./action-complete"; +export * from "./action-message"; export * from "./settings"; diff --git a/packages/mrgn-utils/src/action-message.utils.ts b/packages/mrgn-utils/src/action-message.utils.ts new file mode 100644 index 0000000000..e10f88392d --- /dev/null +++ b/packages/mrgn-utils/src/action-message.utils.ts @@ -0,0 +1,229 @@ +import { + ActionMethod, + ActionMethodType, + canBeBorrowed, + canBeLent, + canBeRepaidCollat, + canBeRepaid, + canBeWithdrawn, + canBeLooped, + DYNAMIC_SIMULATION_ERRORS, + LstData, +} from "."; +import { ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; +import { MarginfiAccountWrapper } from "@mrgnlabs/marginfi-client-v2"; +import { QuoteResponse } from "@jup-ag/api"; + +export function getColorForActionMethodType(type?: ActionMethodType) { + if (type === "INFO") { + return "info"; + } else if (type === "WARNING") { + return "alert"; + } else { + return "alert"; + } +} + +interface CheckActionAvailableProps { + amount: number | null; + connected: boolean; + selectedBank: ExtendedBankInfo | null; + selectedSecondaryBank: ExtendedBankInfo | null; + actionQuote: QuoteResponse | null; +} + +interface CheckLendActionAvailableProps { + amount: number | null; + connected: boolean; + nativeSolBalance: number; + showCloseBalance?: boolean; + selectedBank: ExtendedBankInfo | null; + banks: ExtendedBankInfo[]; + marginfiAccount: MarginfiAccountWrapper | null; + lendMode: ActionType; +} + +export function checkLendActionAvailable({ + amount, + nativeSolBalance, + connected, + showCloseBalance, + selectedBank, + banks, + marginfiAccount, + lendMode, +}: CheckLendActionAvailableProps): ActionMethod[] { + let checks: ActionMethod[] = []; + + const requiredCheck = getRequiredCheck(connected, selectedBank); + if (requiredCheck) return [requiredCheck]; + + const generalChecks = getGeneralChecks(amount ?? 0, showCloseBalance); + if (generalChecks) checks.push(...generalChecks); + + // allert checks + if (selectedBank) { + switch (lendMode) { + case ActionType.Deposit: + const lentChecks = canBeLent(selectedBank, nativeSolBalance); + if (lentChecks.length) checks.push(...lentChecks); + break; + case ActionType.Withdraw: + const withdrawChecks = canBeWithdrawn(selectedBank, marginfiAccount); + if (withdrawChecks.length) checks.push(...withdrawChecks); + break; + case ActionType.Borrow: + const borrowChecks = canBeBorrowed(selectedBank, banks, marginfiAccount); + if (borrowChecks.length) checks.push(...borrowChecks); + break; + case ActionType.Repay: + const repayChecks = canBeRepaid(selectedBank); + if (repayChecks) checks.push(...repayChecks); + break; + } + } + + if (checks.length === 0) + checks.push({ + isEnabled: true, + }); + + return checks; +} + +interface CheckLoopActionAvailableProps { + amount: number | null; + connected: boolean; + selectedBank: ExtendedBankInfo | null; + selectedSecondaryBank: ExtendedBankInfo | null; + actionQuote: QuoteResponse | null; +} + +export function checkLoopActionAvailable({ + amount, + connected, + selectedBank, + selectedSecondaryBank, + actionQuote, +}: CheckLoopActionAvailableProps): ActionMethod[] { + let checks: ActionMethod[] = []; + + const requiredCheck = getRequiredCheck(connected, selectedBank); + if (requiredCheck) return [requiredCheck]; + + const generalChecks = getGeneralChecks(amount ?? 0); + if (generalChecks) checks.push(...generalChecks); + + // allert checks + if (selectedBank) { + const loopChecks = canBeLooped(selectedBank, selectedSecondaryBank, actionQuote); + if (loopChecks.length) checks.push(...loopChecks); + } + + if (checks.length === 0) + checks.push({ + isEnabled: true, + }); + + return checks; +} + +export function checkRepayCollatActionAvailable({ + amount, + connected, + selectedBank, + selectedSecondaryBank, + actionQuote, +}: CheckActionAvailableProps): ActionMethod[] { + let checks: ActionMethod[] = []; + + const requiredCheck = getRequiredCheck(connected, selectedBank); + if (requiredCheck) return [requiredCheck]; + + const generalChecks = getGeneralChecks(amount ?? 0); + if (generalChecks) checks.push(...generalChecks); + + // allert checks + if (selectedBank) { + const repayChecks = canBeRepaidCollat(selectedBank, selectedSecondaryBank, [], actionQuote); + if (repayChecks) checks.push(...repayChecks); + } + + if (checks.length === 0) + checks.push({ + isEnabled: true, + }); + + return checks; +} + +function getRequiredCheck(connected: boolean, selectedBank: ExtendedBankInfo | null): ActionMethod | null { + if (!connected) { + return { isEnabled: false }; + } + if (!selectedBank) { + return { isEnabled: false }; + } + + return null; +} + +function getGeneralChecks(amount: number = 0, showCloseBalance?: boolean): ActionMethod[] { + let checks: ActionMethod[] = []; + if (showCloseBalance) { + checks.push({ actionMethod: "INFO", description: "Close lending balance.", isEnabled: true }); + } // TODO: only for lend and withdraw + + if (amount === 0) { + checks.push({ isEnabled: false }); + } + + return checks; +} + +interface CheckStakeActionAvailableProps { + amount: number | null; + connected: boolean; + selectedBank: ExtendedBankInfo | null; + actionQuote: QuoteResponse | null; + lstData: LstData | null; +} + +export function checkStakeActionAvailable({ + amount, + connected, + selectedBank, + actionQuote, + lstData, +}: CheckStakeActionAvailableProps): ActionMethod[] { + let checks: ActionMethod[] = []; + + const requiredCheck = getRequiredCheck(connected, selectedBank); + if (requiredCheck) return [requiredCheck]; + + const generalChecks = getGeneralChecks(amount ?? 0); + if (generalChecks) checks.push(...generalChecks); + + if (selectedBank?.meta.tokenSymbol !== "SOL" && !actionQuote) checks.push({ isEnabled: false }); + + if (actionQuote?.priceImpactPct && Number(actionQuote.priceImpactPct) > 0.01) { + if (actionQuote?.priceImpactPct && Number(actionQuote.priceImpactPct) > 0.05) { + checks.push(DYNAMIC_SIMULATION_ERRORS.PRICE_IMPACT_ERROR_CHECK(Number(actionQuote.priceImpactPct))); + } else { + checks.push(DYNAMIC_SIMULATION_ERRORS.PRICE_IMPACT_WARNING_CHECK(Number(actionQuote.priceImpactPct))); + } + } + + if (lstData && lstData?.updateRequired) + checks.push({ + isEnabled: false, + description: "Epoch change detected - staking available again shortly", + }); + + if (checks.length === 0) + checks.push({ + isEnabled: true, + }); + + return checks; +} diff --git a/packages/mrgn-utils/src/index.ts b/packages/mrgn-utils/src/index.ts index ea6dec9bc7..d5f94809e0 100644 --- a/packages/mrgn-utils/src/index.ts +++ b/packages/mrgn-utils/src/index.ts @@ -14,3 +14,4 @@ export * from "./token.utils"; export * from "./jup-referral.utils"; export * from "./bank-data.utils"; export * from "./priority.utils"; +export * from "./action-message.utils"; From c14d4271ece16b6751e287609712b0a102b4cd23 Mon Sep 17 00:00:00 2001 From: borcherd Date: Tue, 29 Oct 2024 10:38:03 +0800 Subject: [PATCH 08/14] feat: actionMessage refactor v2 --- .../src/utils/actionBoxUtils.ts | 4 +-- .../move-position/move-position-dialog.tsx | 14 +++++----- .../src/utils/actionBoxUtils.ts | 4 +-- .../actions/lend-box/lend-box.tsx | 19 +++++++------ .../actions/loop-box/loop-box.tsx | 21 ++++++++------ .../repay-collat-box/repay-collat-box.tsx | 19 +++++++------ .../actions/stake-box/stake-box.tsx | 21 ++++++++------ .../action-message/action-message.tsx | 20 ++++++------- .../mrgn-utils/src/action-message.utils.ts | 28 +++++++++---------- packages/mrgn-utils/src/actions/types.ts | 6 ++-- 10 files changed, 84 insertions(+), 72 deletions(-) diff --git a/apps/marginfi-v2-trading/src/utils/actionBoxUtils.ts b/apps/marginfi-v2-trading/src/utils/actionBoxUtils.ts index d600c9b2ef..ec1788f2fc 100644 --- a/apps/marginfi-v2-trading/src/utils/actionBoxUtils.ts +++ b/apps/marginfi-v2-trading/src/utils/actionBoxUtils.ts @@ -1,6 +1,6 @@ import { ActionMethod, - ActionMethodType, + ActionMessageUIType, canBeBorrowed, canBeLent, canBeRepaid, @@ -13,7 +13,7 @@ import { MarginfiAccountWrapper } from "@mrgnlabs/marginfi-client-v2"; import { createJupiterApiClient, QuoteGetRequest, QuoteResponse } from "@jup-ag/api"; import { PublicKey } from "@solana/web3.js"; -export function getColorForActionMethodType(type?: ActionMethodType) { +export function getColorForActionMessageUIType(type?: ActionMessageUIType) { if (type === "INFO") { return "info"; } else if (type === "WARNING") { diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/move-position/move-position-dialog.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/move-position/move-position-dialog.tsx index 9bfbfd8f93..0c0cb9ad19 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/move-position/move-position-dialog.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/move-position/move-position-dialog.tsx @@ -49,7 +49,7 @@ export const MovePositionDialog = ({ setIsLoading, }); - const actionMethods = React.useMemo(() => { + const actionMessages = React.useMemo(() => { const withdrawActionResult = checkLendActionAvailable({ amount: bank.position.amount, connected: true, @@ -75,10 +75,10 @@ export const MovePositionDialog = ({ const isButtonDisabled = React.useMemo(() => { if (!accountToMoveTo) return true; - if (actionMethods && actionMethods.filter((value) => value.isEnabled === false).length > 0) return true; + if (actionMessages && actionMessages.filter((value) => value.isEnabled === false).length > 0) return true; if (isLoading) return true; return false; - }, [accountToMoveTo, actionMethods, isLoading]); + }, [accountToMoveTo, actionMessages, isLoading]); const handleMovePosition = React.useCallback(async () => { if (!marginfiClient || !accountToMoveTo || !actionTxns) { @@ -181,11 +181,11 @@ export const MovePositionDialog = ({ )}
- {actionMethods.map( - (actionMethod, idx) => - actionMethod.description && ( + {actionMessages.map( + (actionMessage, idx) => + actionMessage.description && (
- +
) )} diff --git a/apps/marginfi-v2-ui/src/utils/actionBoxUtils.ts b/apps/marginfi-v2-ui/src/utils/actionBoxUtils.ts index da7bff1d23..36a0bb4231 100644 --- a/apps/marginfi-v2-ui/src/utils/actionBoxUtils.ts +++ b/apps/marginfi-v2-ui/src/utils/actionBoxUtils.ts @@ -4,7 +4,7 @@ import { PublicKey } from "@solana/web3.js"; import { ActionMethod, - ActionMethodType, + ActionMessageUIType, canBeBorrowed, canBeLent, canBeLooped, @@ -18,7 +18,7 @@ import { import { ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { MarginfiAccountWrapper } from "@mrgnlabs/marginfi-client-v2"; -export function getColorForActionMethodType(type?: ActionMethodType) { +export function getColorForActionMessageUIType(type?: ActionMessageUIType) { if (type === "INFO") { return "info"; } else if (type === "WARNING") { diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/lend-box.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/lend-box.tsx index 8658275323..902f040b2e 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/lend-box.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/lend-box.tsx @@ -11,6 +11,7 @@ import { computeAccountSummary, DEFAULT_ACCOUNT_SUMMARY, } from "@mrgnlabs/marginfi-v2-ui-state"; +import { ActionMessageType, checkLendActionAvailable, MarginfiActionParams, PreviousTxn } from "@mrgnlabs/mrgn-utils"; import { ActionMethod, MarginfiActionParams, PreviousTxn, useConnection, usePriorityFee } from "@mrgnlabs/mrgn-utils"; import { ActionMethod, checkLendActionAvailable, MarginfiActionParams, PreviousTxn } from "@mrgnlabs/mrgn-utils"; import { MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; @@ -156,7 +157,7 @@ export const LendBox = ({ }); const [lstDialogCallback, setLSTDialogCallback] = React.useState<(() => void) | null>(null); - const [additionalActionMethods, setAdditionalActionMethods] = React.useState([]); + const [additionalActionMessages, setAdditionalActionMessages] = React.useState([]); // Cleanup the store when the wallet disconnects React.useEffect(() => { @@ -177,7 +178,7 @@ export const LendBox = ({ React.useEffect(() => { if (errorMessage && errorMessage.description) { - setAdditionalActionMethods([errorMessage]); + setAdditionalActionMessages([errorMessage]); } }, [errorMessage]); @@ -187,7 +188,7 @@ export const LendBox = ({ [lendMode, isDust] ); - const actionMethods = React.useMemo( + const actionMessages = React.useMemo( () => checkLendActionAvailable({ amount, @@ -363,11 +364,11 @@ export const LendBox = ({ />
- {additionalActionMethods.concat(actionMethods).map( - (actionMethod, idx) => - actionMethod.description && ( + {additionalActionMessages.concat(actionMessages).map( + (actionMessage, idx) => + actionMessage.description && (
- +
) )} @@ -381,7 +382,9 @@ export const LendBox = ({
value.isEnabled === false).length} + isEnabled={ + !additionalActionMessages.concat(actionMessages).filter((value) => value.isEnabled === false).length + } connected={connected} // showCloseBalance={showCloseBalance} handleAction={() => { diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/loop-box.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/loop-box.tsx index 4fc3df3450..f6778d16a1 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/loop-box.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/loop-box.tsx @@ -19,6 +19,7 @@ import { } from "@mrgnlabs/mrgn-utils"; import { ActionMethod, + ActionMessageType, checkLoopActionAvailable, MarginfiActionParams, PreviousTxn, @@ -187,7 +188,7 @@ export const LoopBox = ({ setIsLoading, }); - const [additionalActionMethods, setAdditionalActionMethods] = React.useState([]); + const [additionalActionMessages, setAdditionalActionMessages] = React.useState([]); // Cleanup the store when the wallet disconnects React.useEffect(() => { @@ -203,13 +204,13 @@ export const LoopBox = ({ React.useEffect(() => { if (errorMessage && errorMessage.description) { showErrorToast(errorMessage?.description); - setAdditionalActionMethods([errorMessage]); + setAdditionalActionMessages([errorMessage]); } else { - setAdditionalActionMethods([]); + setAdditionalActionMessages([]); } }, [errorMessage]); - const actionMethods = React.useMemo( + const actionMessages = React.useMemo( () => checkLoopActionAvailable({ amount, @@ -361,11 +362,11 @@ export const LoopBox = ({ borrowLstApy={borrowLstApy} />
- {additionalActionMethods.concat(actionMethods).map( - (actionMethod, idx) => - actionMethod.description && ( + {additionalActionMessages.concat(actionMessages).map( + (actionMessage, idx) => + actionMessage.description && (
- +
) )} @@ -373,7 +374,9 @@ export const LoopBox = ({
value.isEnabled === false).length} + isEnabled={ + !additionalActionMessages.concat(actionMessages).filter((value) => value.isEnabled === false).length + } connected={connected} handleAction={() => { handleLoopAction(); diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/repay-collat-box.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/repay-collat-box.tsx index 9bba04b85c..8e0d314ced 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/repay-collat-box.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/repay-collat-box.tsx @@ -18,6 +18,7 @@ import { } from "@mrgnlabs/mrgn-utils"; import { ActionMethod, + ActionMessageType, checkRepayCollatActionAvailable, MarginfiActionParams, PreviousTxn, @@ -175,7 +176,7 @@ export const RepayCollatBox = ({ setMaxAmountCollateral, }); - const [additionalActionMethods, setAdditionalActionMethods] = React.useState([]); + const [additionalActionMessages, setAdditionalActionMessages] = React.useState([]); // Cleanup the store when the wallet disconnects React.useEffect(() => { @@ -191,11 +192,11 @@ export const RepayCollatBox = ({ React.useEffect(() => { if (errorMessage && errorMessage.description) { showErrorToast(errorMessage?.description); - setAdditionalActionMethods([errorMessage]); + setAdditionalActionMessages([errorMessage]); } }, [errorMessage]); - const actionMethods = React.useMemo( + const actionMessages = React.useMemo( () => checkRepayCollatActionAvailable({ amount, @@ -319,11 +320,11 @@ export const RepayCollatBox = ({ />
- {additionalActionMethods.concat(actionMethods).map( - (actionMethod, idx) => - actionMethod.description && ( + {additionalActionMessages.concat(actionMessages).map( + (actionMessage, idx) => + actionMessage.description && (
- +
) )} @@ -337,7 +338,9 @@ export const RepayCollatBox = ({
value.isEnabled === false).length} + isEnabled={ + !additionalActionMessages.concat(actionMessages).filter((value) => value.isEnabled === false).length + } connected={connected} handleAction={() => { handleRepayCollatAction(); diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/stake-box.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/stake-box.tsx index b5bb4ad81c..7bd8e4e0fd 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/stake-box.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/stake-box.tsx @@ -13,6 +13,7 @@ import { STATIC_SIMULATION_ERRORS, usePriorityFee, } from "@mrgnlabs/mrgn-utils"; +import { ActionMessageType, checkStakeActionAvailable, LstData, PreviousTxn } from "@mrgnlabs/mrgn-utils"; import { nativeToUi, NATIVE_MINT as SOL_MINT, uiToNative } from "@mrgnlabs/mrgn-common"; import { useActionAmounts } from "~/components/action-box-v2/hooks"; @@ -125,7 +126,7 @@ export const StakeBox = ({ const { lstData } = useStakeBoxContext()!; - const [additionalActionMethods, setAdditionalActionMethods] = React.useState([]); + const [additionalActionMessages, setAdditionalActionMessages] = React.useState([]); const solPriceUsd = React.useMemo(() => { const bank = banks.find((bank) => bank.info.state.mint.equals(SOL_MINT)); @@ -258,8 +259,8 @@ export const StakeBox = ({ onComplete, ]); - const actionMethods = React.useMemo(() => { - setAdditionalActionMethods([]); + const actionMessages = React.useMemo(() => { + setAdditionalActionMessages([]); return checkStakeActionAvailable({ amount, connected, @@ -275,7 +276,7 @@ export const StakeBox = ({ React.useEffect(() => { if (errorMessage && errorMessage.description) { - setAdditionalActionMethods([{ ...errorMessage, isEnabled: false }]); + setAdditionalActionMessages([{ ...errorMessage, isEnabled: false }]); } }, [errorMessage]); @@ -311,18 +312,20 @@ export const StakeBox = ({ isLoading={isLoading.type === "SIMULATION" ? isLoading.state : false} />
- {additionalActionMethods.concat(actionMethods).map( - (actionMethod, idx) => - actionMethod.description && ( + {additionalActionMessages.concat(actionMessages).map( + (actionMessage, idx) => + actionMessage.description && (
- +
) )}
value.isEnabled === false).length} + isEnabled={ + !additionalActionMessages.concat(actionMessages).filter((value) => value.isEnabled === false).length + } connected={connected} handleAction={handleLstAction} buttonLabel={requestedActionType === ActionType.MintLST ? "Mint LST" : "Unstake LST"} diff --git a/packages/mrgn-ui/src/components/action-message/action-message.tsx b/packages/mrgn-ui/src/components/action-message/action-message.tsx index f4c6c246ef..146b88fcc0 100644 --- a/packages/mrgn-ui/src/components/action-message/action-message.tsx +++ b/packages/mrgn-ui/src/components/action-message/action-message.tsx @@ -1,35 +1,35 @@ import Link from "next/link"; import { IconAlertTriangle, IconExternalLink } from "@tabler/icons-react"; -import { ActionMethod, cn } from "@mrgnlabs/mrgn-utils"; +import { ActionMessageType, cn } from "@mrgnlabs/mrgn-utils"; type ActionMessageProps = { - actionMethod: ActionMethod; + _actionMessage: ActionMessageType; }; -export const ActionMessage = ({ actionMethod }: ActionMessageProps) => { +export const ActionMessage = ({ _actionMessage }: ActionMessageProps) => { return (
-

{actionMethod.description}

- {actionMethod.link && ( +

{_actionMessage.description}

+ {_actionMessage.link && (

{" "} - {actionMethod.linkText || "Read more"} + {_actionMessage.linkText || "Read more"}

)} diff --git a/packages/mrgn-utils/src/action-message.utils.ts b/packages/mrgn-utils/src/action-message.utils.ts index e10f88392d..55718c740f 100644 --- a/packages/mrgn-utils/src/action-message.utils.ts +++ b/packages/mrgn-utils/src/action-message.utils.ts @@ -1,6 +1,6 @@ import { - ActionMethod, - ActionMethodType, + ActionMessageUIType, + ActionMessageType, canBeBorrowed, canBeLent, canBeRepaidCollat, @@ -14,7 +14,7 @@ import { ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { MarginfiAccountWrapper } from "@mrgnlabs/marginfi-client-v2"; import { QuoteResponse } from "@jup-ag/api"; -export function getColorForActionMethodType(type?: ActionMethodType) { +export function getColorForActionMessageUIType(type?: ActionMessageUIType) { if (type === "INFO") { return "info"; } else if (type === "WARNING") { @@ -52,8 +52,8 @@ export function checkLendActionAvailable({ banks, marginfiAccount, lendMode, -}: CheckLendActionAvailableProps): ActionMethod[] { - let checks: ActionMethod[] = []; +}: CheckLendActionAvailableProps): ActionMessageType[] { + let checks: ActionMessageType[] = []; const requiredCheck = getRequiredCheck(connected, selectedBank); if (requiredCheck) return [requiredCheck]; @@ -105,8 +105,8 @@ export function checkLoopActionAvailable({ selectedBank, selectedSecondaryBank, actionQuote, -}: CheckLoopActionAvailableProps): ActionMethod[] { - let checks: ActionMethod[] = []; +}: CheckLoopActionAvailableProps): ActionMessageType[] { + let checks: ActionMessageType[] = []; const requiredCheck = getRequiredCheck(connected, selectedBank); if (requiredCheck) return [requiredCheck]; @@ -134,8 +134,8 @@ export function checkRepayCollatActionAvailable({ selectedBank, selectedSecondaryBank, actionQuote, -}: CheckActionAvailableProps): ActionMethod[] { - let checks: ActionMethod[] = []; +}: CheckActionAvailableProps): ActionMessageType[] { + let checks: ActionMessageType[] = []; const requiredCheck = getRequiredCheck(connected, selectedBank); if (requiredCheck) return [requiredCheck]; @@ -157,7 +157,7 @@ export function checkRepayCollatActionAvailable({ return checks; } -function getRequiredCheck(connected: boolean, selectedBank: ExtendedBankInfo | null): ActionMethod | null { +function getRequiredCheck(connected: boolean, selectedBank: ExtendedBankInfo | null): ActionMessageType | null { if (!connected) { return { isEnabled: false }; } @@ -168,8 +168,8 @@ function getRequiredCheck(connected: boolean, selectedBank: ExtendedBankInfo | n return null; } -function getGeneralChecks(amount: number = 0, showCloseBalance?: boolean): ActionMethod[] { - let checks: ActionMethod[] = []; +function getGeneralChecks(amount: number = 0, showCloseBalance?: boolean): ActionMessageType[] { + let checks: ActionMessageType[] = []; if (showCloseBalance) { checks.push({ actionMethod: "INFO", description: "Close lending balance.", isEnabled: true }); } // TODO: only for lend and withdraw @@ -195,8 +195,8 @@ export function checkStakeActionAvailable({ selectedBank, actionQuote, lstData, -}: CheckStakeActionAvailableProps): ActionMethod[] { - let checks: ActionMethod[] = []; +}: CheckStakeActionAvailableProps): ActionMessageType[] { + let checks: ActionMessageType[] = []; const requiredCheck = getRequiredCheck(connected, selectedBank); if (requiredCheck) return [requiredCheck]; diff --git a/packages/mrgn-utils/src/actions/types.ts b/packages/mrgn-utils/src/actions/types.ts index ce487921bc..88b93fd65f 100644 --- a/packages/mrgn-utils/src/actions/types.ts +++ b/packages/mrgn-utils/src/actions/types.ts @@ -29,10 +29,10 @@ export enum YbxType { RepayYbx = "Repay", } -export type ActionMethodType = "WARNING" | "ERROR" | "INFO"; -export interface ActionMethod { +export type ActionMessageUIType = "WARNING" | "ERROR" | "INFO"; +export interface ActionMessageType { isEnabled: boolean; - actionMethod?: ActionMethodType; + actionMethod?: ActionMessageUIType; description?: string; link?: string; linkText?: string; From cc4da5d30cb78bf66bd5ae9a24f5b45c2f39a38e Mon Sep 17 00:00:00 2001 From: borcherd Date: Tue, 29 Oct 2024 14:05:21 +0800 Subject: [PATCH 09/14] feat: replace ActionMethod with ActionMessage --- .../common/TradingBox/TradingBox.tsx | 4 +- .../common/TradingBox/tradingBox.utils.tsx | 16 +++---- .../src/store/actionBoxStore.ts | 4 +- .../src/utils/actionBoxUtils.ts | 16 ++++--- .../src/utils/tradingActions.ts | 4 +- .../hooks/use-move-position-simulation.tsx | 1 - .../Portfolio/hooks/use-reward-simulation.tsx | 4 +- .../common/Portfolio/lending-portfolio.tsx | 5 +- .../src/utils/actionBoxUtils.ts | 16 ++++--- .../hooks/use-lend-simulation.hooks.ts | 3 +- .../actions/lend-box/store/lend-box-store.ts | 6 +-- .../lend-box/utils/lend-actions.utils.ts | 4 +- .../lend-box/utils/lend-simulation.utils.ts | 4 +- .../hooks/use-loop-simulation.hooks.ts | 4 +- .../actions/loop-box/store/loop-store.ts | 6 +-- .../loop-box/utils/loop-action.utils.ts | 4 +- .../loop-box/utils/loop-simulation.utils.ts | 4 +- .../hooks/use-repay-simulation.hooks.ts | 4 +- .../store/repay-collat-store.ts | 6 +-- .../utils/repay-action.utils.ts | 4 +- .../utils/repay-simulation.utils.ts | 4 +- .../hooks/use-stake-simulation.hooks.tsx | 4 +- .../stake-box/store/stake-box-store.ts | 6 +-- .../stake-box/utils/stake-simulation.utils.ts | 4 +- packages/mrgn-utils/src/actions/checks.ts | 30 ++++++------ .../src/actions/flashloans/builders.ts | 10 ++-- .../src/actions/flashloans/helpers.ts | 10 ++-- packages/mrgn-utils/src/errors.ts | 46 +++++++++---------- 28 files changed, 121 insertions(+), 112 deletions(-) diff --git a/apps/marginfi-v2-trading/src/components/common/TradingBox/TradingBox.tsx b/apps/marginfi-v2-trading/src/components/common/TradingBox/TradingBox.tsx index 8d08c2015b..c4f1b6e38e 100644 --- a/apps/marginfi-v2-trading/src/components/common/TradingBox/TradingBox.tsx +++ b/apps/marginfi-v2-trading/src/components/common/TradingBox/TradingBox.tsx @@ -7,7 +7,7 @@ import { useRouter } from "next/router"; import { ActionType, ActiveBankInfo, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { numeralFormatter } from "@mrgnlabs/mrgn-common"; import { - ActionMethod, + ActionMessageType, calculateLoopingParams, handleSimulationError, LoopingObject, @@ -56,7 +56,7 @@ export const TradingBox = ({ activeGroup, side = "long" }: TradingBoxProps) => { const [leverage, setLeverage] = React.useState(0); const [isLoading, setIsLoading] = React.useState(false); const [Stats, setStats] = React.useState(<>); - const [additionalChecks, setAdditionalChecks] = React.useState(); + const [additionalChecks, setAdditionalChecks] = React.useState(); const debouncedLeverage = useDebounce(leverage, 1000); const debouncedAmount = useDebounce(amount, 1000); diff --git a/apps/marginfi-v2-trading/src/components/common/TradingBox/tradingBox.utils.tsx b/apps/marginfi-v2-trading/src/components/common/TradingBox/tradingBox.utils.tsx index 38694707c9..501ca04923 100644 --- a/apps/marginfi-v2-trading/src/components/common/TradingBox/tradingBox.utils.tsx +++ b/apps/marginfi-v2-trading/src/components/common/TradingBox/tradingBox.utils.tsx @@ -18,7 +18,7 @@ import { usdFormatter, } from "@mrgnlabs/mrgn-common"; import { - ActionMethod, + ActionMessageType, DYNAMIC_SIMULATION_ERRORS, loopingBuilder, LoopingObject, @@ -375,8 +375,8 @@ export function checkLoopingActionAvailable({ activeGroup, loopingObject, tradeSide, -}: CheckActionAvailableProps): ActionMethod[] { - let checks: ActionMethod[] = []; +}: CheckActionAvailableProps): ActionMessageType[] { + let checks: ActionMessageType[] = []; const requiredCheck = getRequiredCheck(connected, activeGroup, loopingObject); if (requiredCheck) return [requiredCheck]; @@ -402,7 +402,7 @@ function getRequiredCheck( connected: boolean, activeGroup: GroupData | null, loopingObject: LoopingObject | null -): ActionMethod | null { +): ActionMessageType | null { if (!connected) { return { isEnabled: false }; } @@ -416,8 +416,8 @@ function getRequiredCheck( return null; } -function getGeneralChecks(amount: string): ActionMethod[] { - let checks: ActionMethod[] = []; +function getGeneralChecks(amount: string): ActionMessageType[] { + let checks: ActionMessageType[] = []; try { if (Number(amount) === 0) { @@ -430,8 +430,8 @@ function getGeneralChecks(amount: string): ActionMethod[] { } } -function canBeLooped(activeGroup: GroupData, loopingObject: LoopingObject, tradeSide: TradeSide): ActionMethod[] { - let checks: ActionMethod[] = []; +function canBeLooped(activeGroup: GroupData, loopingObject: LoopingObject, tradeSide: TradeSide): ActionMessageType[] { + let checks: ActionMessageType[] = []; const isUsdcBankPaused = activeGroup.pool.quoteTokens[0].info.rawBank.config.operationalState === OperationalState.Paused; const isTokenBankPaused = activeGroup.pool.token.info.rawBank.config.operationalState === OperationalState.Paused; diff --git a/apps/marginfi-v2-trading/src/store/actionBoxStore.ts b/apps/marginfi-v2-trading/src/store/actionBoxStore.ts index 17686b426e..58d858e8c2 100644 --- a/apps/marginfi-v2-trading/src/store/actionBoxStore.ts +++ b/apps/marginfi-v2-trading/src/store/actionBoxStore.ts @@ -11,7 +11,7 @@ import { LstType, RepayType, YbxType, - ActionMethod, + ActionMessageType, calculateMaxRepayableCollateral, calculateRepayCollateralParams, DYNAMIC_SIMULATION_ERRORS, @@ -40,7 +40,7 @@ interface ActionBoxState { feedCrankTxs: VersionedTransaction[]; }; - errorMessage: ActionMethod | null; + errorMessage: ActionMessageType | null; isLoading: boolean; // Actions diff --git a/apps/marginfi-v2-trading/src/utils/actionBoxUtils.ts b/apps/marginfi-v2-trading/src/utils/actionBoxUtils.ts index ec1788f2fc..0f7aad65ef 100644 --- a/apps/marginfi-v2-trading/src/utils/actionBoxUtils.ts +++ b/apps/marginfi-v2-trading/src/utils/actionBoxUtils.ts @@ -1,5 +1,5 @@ import { - ActionMethod, + ActionMessageType, ActionMessageUIType, canBeBorrowed, canBeLent, @@ -53,8 +53,8 @@ export function checkActionAvailable({ blacklistRoutes, repayMode, repayCollatQuote, -}: CheckActionAvailableProps): ActionMethod[] { - let checks: ActionMethod[] = []; +}: CheckActionAvailableProps): ActionMessageType[] { + let checks: ActionMessageType[] = []; const requiredCheck = getRequiredCheck(connected, selectedBank); if (requiredCheck) return [requiredCheck]; @@ -97,7 +97,7 @@ export function checkActionAvailable({ return checks; } -function getRequiredCheck(connected: boolean, selectedBank: ExtendedBankInfo | null): ActionMethod | null { +function getRequiredCheck(connected: boolean, selectedBank: ExtendedBankInfo | null): ActionMessageType | null { if (!connected) { return { isEnabled: false }; } @@ -108,8 +108,12 @@ function getRequiredCheck(connected: boolean, selectedBank: ExtendedBankInfo | n return null; } -function getGeneralChecks(amount: number = 0, repayAmount: number = 0, showCloseBalance?: boolean): ActionMethod[] { - let checks: ActionMethod[] = []; +function getGeneralChecks( + amount: number = 0, + repayAmount: number = 0, + showCloseBalance?: boolean +): ActionMessageType[] { + let checks: ActionMessageType[] = []; if (showCloseBalance) { checks.push({ actionMethod: "INFO", description: "Close lending balance.", isEnabled: true }); } diff --git a/apps/marginfi-v2-trading/src/utils/tradingActions.ts b/apps/marginfi-v2-trading/src/utils/tradingActions.ts index ec1e8047d9..0197d4540c 100644 --- a/apps/marginfi-v2-trading/src/utils/tradingActions.ts +++ b/apps/marginfi-v2-trading/src/utils/tradingActions.ts @@ -17,7 +17,7 @@ import { BankConfigOpt, MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs import { calculateLoopingTransaction, LoopingObject, - ActionMethod, + ActionMessageType, calculateBorrowLendPositionParams, getMaybeSquadsOptions, ToastStep, @@ -313,7 +313,7 @@ export async function calculateClosePositions({ feedCrankTxs: VersionedTransaction[]; quote?: QuoteResponse; } - | ActionMethod + | ActionMessageType > { // user is borrowing and depositing if (borrowBank && depositBanks.length === 1) { diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-move-position-simulation.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-move-position-simulation.tsx index 9a54b11514..5ef52b36f1 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-move-position-simulation.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-move-position-simulation.tsx @@ -3,7 +3,6 @@ import React from "react"; import { TransactionMessage, VersionedTransaction } from "@solana/web3.js"; import { makeBundleTipIx, MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; -import { ActionMethod } from "@mrgnlabs/mrgn-utils"; import { ActiveBankInfo, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; type MovePositionSimulationProps = { diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx index 1dad0f64ae..520cb2e5a9 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/hooks/use-reward-simulation.tsx @@ -3,7 +3,7 @@ import React from "react"; import { PublicKey, TransactionInstruction, TransactionMessage, VersionedTransaction } from "@solana/web3.js"; import { makeBundleTipIx, MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; -import { ActionMethod, TOKEN_2022_MINTS } from "@mrgnlabs/mrgn-utils"; +import { ActionMessageType, TOKEN_2022_MINTS } from "@mrgnlabs/mrgn-utils"; import { ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { AccountLayout, @@ -25,7 +25,7 @@ type RewardSimulationProps = { setSimulationResult: (result: rewardsType | null) => void; setActionTxn: (actionTxn: VersionedTransaction | null) => void; - setErrorMessage: (error: ActionMethod | null) => void; + setErrorMessage: (error: ActionMessageType | null) => void; }; export const useRewardSimulation = ({ diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx index 2b56f4c627..0781376881 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/lending-portfolio.tsx @@ -16,7 +16,6 @@ import { WalletButton } from "~/components/wallet-v2"; import { useWallet } from "~/components/wallet-v2/hooks/use-wallet.hook"; import { Loader } from "~/components/ui/loader"; import { RewardsDialog } from "./components/rewards"; -import { ActionComplete } from "~/components/common/ActionComplete"; import { IconLoader } from "~/components/ui/icons"; import { IconInfoCircle } from "@tabler/icons-react"; @@ -26,6 +25,7 @@ import { useRewardSimulation } from "./hooks"; import { executeCollectTxn } from "./utils"; import { Select, SelectContent, SelectItem, SelectTrigger } from "~/components/ui/select"; import { EMISSION_MINT_INFO_MAP } from "~/components/desktop/AssetList/components"; +import { ActionComplete } from "~/components"; export const LendingPortfolio = () => { const router = useRouter(); @@ -160,8 +160,6 @@ export const LendingPortfolio = () => { [isStoreInitialized, walletConnectionDelay, isRefreshingStore, accountSummary.balance, lendingBanks, borrowingBanks] ); - const [previousTxn] = useUiStore((state) => [state.previousTxn]); - // Introduced this useEffect to show the loader for 2 seconds after wallet connection. This is to avoid the flickering of the loader, since the isRefreshingStore isnt set immediately after the wallet connection. React.useEffect(() => { if (connected) { @@ -426,7 +424,6 @@ export const LendingPortfolio = () => { onCollect={handleCollectExectuion} isLoading={rewardsLoading} /> - {previousTxn && }
); diff --git a/apps/marginfi-v2-ui/src/utils/actionBoxUtils.ts b/apps/marginfi-v2-ui/src/utils/actionBoxUtils.ts index 36a0bb4231..907511414d 100644 --- a/apps/marginfi-v2-ui/src/utils/actionBoxUtils.ts +++ b/apps/marginfi-v2-ui/src/utils/actionBoxUtils.ts @@ -3,7 +3,7 @@ import { QuoteResponse } from "@jup-ag/api"; import { PublicKey } from "@solana/web3.js"; import { - ActionMethod, + ActionMessageType, ActionMessageUIType, canBeBorrowed, canBeLent, @@ -62,8 +62,8 @@ export function checkActionAvailable({ repayMode, repayCollatQuote, lstQuoteMeta, -}: CheckActionAvailableProps): ActionMethod[] { - let checks: ActionMethod[] = []; +}: CheckActionAvailableProps): ActionMessageType[] { + let checks: ActionMessageType[] = []; const requiredCheck = getRequiredCheck(connected, selectedBank, selectedStakingAccount); if (requiredCheck) return [requiredCheck]; @@ -126,7 +126,7 @@ function getRequiredCheck( connected: boolean, selectedBank: ExtendedBankInfo | null, selectedStakingAccount: StakeData | null -): ActionMethod | null { +): ActionMessageType | null { if (!connected) { return { isEnabled: false }; } @@ -137,8 +137,12 @@ function getRequiredCheck( return null; } -function getGeneralChecks(amount: number = 0, repayAmount: number = 0, showCloseBalance?: boolean): ActionMethod[] { - let checks: ActionMethod[] = []; +function getGeneralChecks( + amount: number = 0, + repayAmount: number = 0, + showCloseBalance?: boolean +): ActionMessageType[] { + let checks: ActionMessageType[] = []; if (showCloseBalance) { checks.push({ actionMethod: "INFO", description: "Close lending balance.", isEnabled: true }); } diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/hooks/use-lend-simulation.hooks.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/hooks/use-lend-simulation.hooks.ts index b97ad53980..5702a82ccc 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/hooks/use-lend-simulation.hooks.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/hooks/use-lend-simulation.hooks.ts @@ -6,6 +6,7 @@ import { AccountSummary, ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi import { MarginfiAccountWrapper, SimulationResult } from "@mrgnlabs/marginfi-client-v2"; import { ActionMethod, STATIC_SIMULATION_ERRORS, usePrevious } from "@mrgnlabs/mrgn-utils"; import { TransactionBroadcastType } from "@mrgnlabs/mrgn-common"; +import { ActionMessageType, STATIC_SIMULATION_ERRORS, usePrevious } from "@mrgnlabs/mrgn-utils"; import { calculateLendingTransaction, calculateSummary, getSimulationResult } from "../utils"; @@ -34,7 +35,7 @@ type LendSimulationProps = { actionTxn: VersionedTransaction | Transaction | null; additionalTxns: (VersionedTransaction | Transaction)[]; }) => void; - setErrorMessage: (error: ActionMethod | null) => void; + setErrorMessage: (error: ActionMessageType | null) => void; setIsLoading: (isLoading: boolean) => void; }; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/store/lend-box-store.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/store/lend-box-store.ts index f8998eb43d..18a4ee4057 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/store/lend-box-store.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/store/lend-box-store.ts @@ -2,7 +2,7 @@ import { create, StateCreator } from "zustand"; import { Transaction, VersionedTransaction } from "@solana/web3.js"; import { ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; -import { ActionMethod, ActionTxns } from "@mrgnlabs/mrgn-utils"; +import { ActionMessageType, ActionTxns } from "@mrgnlabs/mrgn-utils"; import { SimulationResult } from "@mrgnlabs/marginfi-client-v2"; interface LendBoxState { @@ -15,7 +15,7 @@ interface LendBoxState { simulationResult: SimulationResult | null; actionTxns: ActionTxns; - errorMessage: ActionMethod | null; + errorMessage: ActionMessageType | null; isLoading: boolean; // Actions @@ -28,7 +28,7 @@ interface LendBoxState { setActionTxns: (actionTxns: ActionTxns) => void; setSelectedBank: (bank: ExtendedBankInfo | null) => void; setIsLoading: (isLoading: boolean) => void; - setErrorMessage: (errorMessage: ActionMethod | null) => void; + setErrorMessage: (errorMessage: ActionMessageType | null) => void; } function createLendBoxStore() { diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/lend-actions.utils.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/lend-actions.utils.ts index eb1631446b..af115abfaf 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/lend-actions.utils.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/lend-actions.utils.ts @@ -5,7 +5,7 @@ import { MarginfiAccountWrapper } from "@mrgnlabs/marginfi-client-v2"; import { ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { TransactionBroadcastType } from "@mrgnlabs/mrgn-common"; import { - ActionMethod, + ActionMessageType, closeBalance, executeLendingAction, isWholePosition, @@ -120,7 +120,7 @@ export async function calculateLendingTransaction( actionTxn: VersionedTransaction | Transaction; additionalTxns: VersionedTransaction[]; } - | ActionMethod + | ActionMessageType > { switch (actionMode) { case ActionType.Deposit: diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/lend-simulation.utils.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/lend-simulation.utils.ts index 1f6d9808df..14c63273b5 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/lend-simulation.utils.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/utils/lend-simulation.utils.ts @@ -2,7 +2,7 @@ import { Transaction, VersionedTransaction } from "@solana/web3.js"; import { ExtendedBankInfo, ActionType, AccountSummary } from "@mrgnlabs/marginfi-v2-ui-state"; import { nativeToUi } from "@mrgnlabs/mrgn-common"; -import { ActionMethod, handleSimulationError, isWholePosition } from "@mrgnlabs/mrgn-utils"; +import { ActionMessageType, handleSimulationError, isWholePosition } from "@mrgnlabs/mrgn-utils"; import { MarginfiAccountWrapper, SimulationResult } from "@mrgnlabs/marginfi-client-v2"; import { simulatedHealthFactor, simulatedPositionSize, simulatedCollateral } from "~/components/action-box-v2/utils"; @@ -70,7 +70,7 @@ export function calculateSummary({ } export const getSimulationResult = async (props: SimulateActionProps) => { - let actionMethod: ActionMethod | undefined = undefined; + let actionMethod: ActionMessageType | undefined = undefined; let simulationResult: SimulationResult | null = null; try { diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/hooks/use-loop-simulation.hooks.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/hooks/use-loop-simulation.hooks.ts index 8bef2266f8..fa93af2a7a 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/hooks/use-loop-simulation.hooks.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/hooks/use-loop-simulation.hooks.ts @@ -9,7 +9,7 @@ import { SimulationResult, } from "@mrgnlabs/marginfi-client-v2"; import { - ActionMethod, + ActionMessageType, DYNAMIC_SIMULATION_ERRORS, LoopActionTxns, STATIC_SIMULATION_ERRORS, @@ -37,7 +37,7 @@ type LoopSimulationProps = { setSimulationResult: (simulationResult: SimulationResult | null) => void; setActionTxns: (actionTxns: LoopActionTxns) => void; - setErrorMessage: (error: ActionMethod) => void; + setErrorMessage: (error: ActionMessageType) => void; setIsLoading: (isLoading: boolean) => void; setMaxLeverage: (maxLeverage: number) => void; }; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/store/loop-store.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/store/loop-store.ts index 36a4e893ce..dd6636e008 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/store/loop-store.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/store/loop-store.ts @@ -3,7 +3,7 @@ import BigNumber from "bignumber.js"; import { ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { SimulationResult } from "@mrgnlabs/marginfi-client-v2"; -import { ActionMethod, calculateLstYield, LoopActionTxns, LSTS_SOLANA_COMPASS_MAP } from "@mrgnlabs/mrgn-utils"; +import { ActionMessageType, calculateLstYield, LoopActionTxns, LSTS_SOLANA_COMPASS_MAP } from "@mrgnlabs/mrgn-utils"; interface LoopBoxState { // State @@ -21,7 +21,7 @@ interface LoopBoxState { actionTxns: LoopActionTxns; - errorMessage: ActionMethod | null; + errorMessage: ActionMessageType | null; isLoading: boolean; // Actions @@ -34,7 +34,7 @@ interface LoopBoxState { setSimulationResult: (simulationResult: SimulationResult | null) => void; setActionTxns: (actionTxns: LoopActionTxns) => void; - setErrorMessage: (errorMessage: ActionMethod | null) => void; + setErrorMessage: (errorMessage: ActionMessageType | null) => void; setSelectedBank: (bank: ExtendedBankInfo | null) => void; setSelectedSecondaryBank: (bank: ExtendedBankInfo | null) => void; setDepositLstApy: (bank: ExtendedBankInfo) => void; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-action.utils.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-action.utils.ts index c6e7007785..dc0abda3f9 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-action.utils.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-action.utils.ts @@ -6,7 +6,7 @@ import { MarginfiAccountWrapper } from "@mrgnlabs/marginfi-client-v2"; import { ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { TransactionBroadcastType } from "@mrgnlabs/mrgn-common"; import { - ActionMethod, + ActionMessageType, calculateLoopingParams, executeLoopingAction, LoopingObject, @@ -69,6 +69,8 @@ export async function calculateLooping( platformFeeBps: number, broadcastType: TransactionBroadcastType ): Promise { + platformFeeBps: number +): Promise { // TODO setup logging again // capture("looper", { // amountIn: uiToNative(amount, loopBank.info.state.mintDecimals).toNumber(), diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-simulation.utils.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-simulation.utils.ts index d0fa738d64..364b99077a 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-simulation.utils.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-simulation.utils.ts @@ -2,7 +2,7 @@ import { Transaction, VersionedTransaction } from "@solana/web3.js"; import { ExtendedBankInfo, AccountSummary } from "@mrgnlabs/marginfi-v2-ui-state"; import { nativeToUi } from "@mrgnlabs/mrgn-common"; -import { ActionMethod, handleSimulationError, LoopActionTxns } from "@mrgnlabs/mrgn-utils"; +import { ActionMessageType, handleSimulationError, LoopActionTxns } from "@mrgnlabs/mrgn-utils"; import { MarginfiAccountWrapper, SimulationResult } from "@mrgnlabs/marginfi-client-v2"; import { @@ -46,7 +46,7 @@ export function calculateSummary({ } export const getSimulationResult = async (props: SimulateActionProps) => { - let actionMethod: ActionMethod | undefined = undefined; + let actionMethod: ActionMessageType | undefined = undefined; let simulationResult: SimulationResult | null = null; try { diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/hooks/use-repay-simulation.hooks.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/hooks/use-repay-simulation.hooks.ts index a750c0a9ee..495b12088c 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/hooks/use-repay-simulation.hooks.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/hooks/use-repay-simulation.hooks.ts @@ -3,7 +3,7 @@ import { Transaction, VersionedTransaction } from "@solana/web3.js"; import { MarginfiAccountWrapper, MarginfiClient, SimulationResult } from "@mrgnlabs/marginfi-client-v2"; import { - ActionMethod, + ActionMessageType, calculateMaxRepayableCollateral, DYNAMIC_SIMULATION_ERRORS, RepayCollatActionTxns, @@ -31,7 +31,7 @@ type RepayCollatSimulationProps = { setSimulationResult: (simulationResult: SimulationResult | null) => void; setActionTxns: (actionTxns: RepayCollatActionTxns) => void; - setErrorMessage: (error: ActionMethod) => void; + setErrorMessage: (error: ActionMessageType) => void; setRepayAmount: (repayAmount: number) => void; setIsLoading: (isLoading: boolean) => void; setMaxAmountCollateral: (maxAmountCollateral: number) => void; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/store/repay-collat-store.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/store/repay-collat-store.ts index d568b8f8e8..76700baf46 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/store/repay-collat-store.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/store/repay-collat-store.ts @@ -5,7 +5,7 @@ import { Transaction, VersionedTransaction } from "@solana/web3.js"; import { ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { SimulationResult } from "@mrgnlabs/marginfi-client-v2"; -import { ActionMethod, RepayCollatActionTxns } from "@mrgnlabs/mrgn-utils"; +import { ActionMessageType, RepayCollatActionTxns } from "@mrgnlabs/mrgn-utils"; interface RepayCollatBoxState { // State @@ -20,7 +20,7 @@ interface RepayCollatBoxState { actionTxns: RepayCollatActionTxns; - errorMessage: ActionMethod | null; + errorMessage: ActionMessageType | null; isLoading: boolean; // Actions @@ -33,7 +33,7 @@ interface RepayCollatBoxState { setSimulationResult: (simulationResult: SimulationResult | null) => void; setActionTxns: (actionTxns: RepayCollatActionTxns) => void; - setErrorMessage: (errorMessage: ActionMethod | null) => void; + setErrorMessage: (errorMessage: ActionMessageType | null) => void; setSelectedBank: (bank: ExtendedBankInfo | null) => void; setSelectedSecondaryBank: (bank: ExtendedBankInfo | null) => void; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/repay-action.utils.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/repay-action.utils.ts index a16a32a669..31c304a5ca 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/repay-action.utils.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/repay-action.utils.ts @@ -6,7 +6,7 @@ import { MarginfiAccountWrapper } from "@mrgnlabs/marginfi-client-v2"; import { ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { TransactionBroadcastType } from "@mrgnlabs/mrgn-common"; import { - ActionMethod, + ActionMessageType, calculateRepayCollateralParams, executeLendingAction, MarginfiActionParams, @@ -74,7 +74,7 @@ export async function calculateRepayCollateral( amount: number; lastValidBlockHeight?: number; } - | ActionMethod + | ActionMessageType > { // TODO setup logging again // capture("repay_with_collat", { diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/repay-simulation.utils.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/repay-simulation.utils.ts index 2b31a80ada..95071f3638 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/repay-simulation.utils.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/utils/repay-simulation.utils.ts @@ -2,7 +2,7 @@ import { Transaction, VersionedTransaction } from "@solana/web3.js"; import { ExtendedBankInfo, AccountSummary } from "@mrgnlabs/marginfi-v2-ui-state"; import { nativeToUi } from "@mrgnlabs/mrgn-common"; -import { ActionMethod, handleSimulationError } from "@mrgnlabs/mrgn-utils"; +import { ActionMessageType, handleSimulationError } from "@mrgnlabs/mrgn-utils"; import { MarginfiAccountWrapper, SimulationResult } from "@mrgnlabs/marginfi-client-v2"; import { @@ -48,7 +48,7 @@ export function calculateSummary({ } export const getSimulationResult = async (props: SimulateActionProps) => { - let actionMethod: ActionMethod | undefined = undefined; + let actionMethod: ActionMessageType | undefined = undefined; let simulationResult: SimulationResult | null = null; try { diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/hooks/use-stake-simulation.hooks.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/hooks/use-stake-simulation.hooks.tsx index ba0eac58a4..68d5e0cf49 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/hooks/use-stake-simulation.hooks.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/hooks/use-stake-simulation.hooks.tsx @@ -6,7 +6,7 @@ import { createJupiterApiClient } from "@jup-ag/api"; import { makeBundleTipIx, makeUnwrapSolIx, MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; import { ExtendedBankInfo, ActionType } from "@mrgnlabs/marginfi-v2-ui-state"; import { - ActionMethod, + ActionMessageType, deserializeInstruction, extractErrorString, getSwapQuoteWithRetry, @@ -39,7 +39,7 @@ type StakeSimulationProps = { broadcastType: TransactionBroadcastType; setSimulationResult: (result: any | null) => void; setActionTxns: (actionTxns: StakeActionTxns) => void; - setErrorMessage: (error: ActionMethod | null) => void; + setErrorMessage: (error: ActionMessageType | null) => void; setIsLoading: ({ state, type }: { state: boolean; type: string | null }) => void; }; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/store/stake-box-store.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/store/stake-box-store.ts index 06a6e0166b..eac200dde2 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/store/stake-box-store.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/store/stake-box-store.ts @@ -1,7 +1,7 @@ import { create, StateCreator } from "zustand"; import { ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; -import { ActionMethod, LstData, StakeActionTxns } from "@mrgnlabs/mrgn-utils"; +import { ActionMessageType, LstData, StakeActionTxns } from "@mrgnlabs/mrgn-utils"; import { SimulationResult } from "@mrgnlabs/marginfi-client-v2"; interface StateBoxState { @@ -14,7 +14,7 @@ interface StateBoxState { simulationResult: SimulationResult | null; actionTxns: StakeActionTxns; - errorMessage: ActionMethod | null; + errorMessage: ActionMessageType | null; isLoading: { state: boolean; type: string | null }; // Actions @@ -27,7 +27,7 @@ interface StateBoxState { setActionTxns: (actionTxns: StakeActionTxns) => void; setSelectedBank: (bank: ExtendedBankInfo | null) => void; setIsLoading: ({ state, type }: { state: boolean; type: string | null }) => void; - setErrorMessage: (errorMessage: ActionMethod | null) => void; + setErrorMessage: (errorMessage: ActionMessageType | null) => void; } function createStakeBoxStore() { diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/utils/stake-simulation.utils.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/utils/stake-simulation.utils.ts index 0715e60ad3..694982852c 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/utils/stake-simulation.utils.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/utils/stake-simulation.utils.ts @@ -14,7 +14,7 @@ import { getAssociatedTokenAddressSync, uiToNative, } from "@mrgnlabs/mrgn-common"; -import { ActionMethod, handleSimulationError, LstData } from "@mrgnlabs/mrgn-utils"; +import { ActionMessageType, handleSimulationError, LstData } from "@mrgnlabs/mrgn-utils"; import { AddressLookupTableAccount, Connection, @@ -91,7 +91,7 @@ function calculateActionPreview( export const getSimulationResult = async ({ marginfiClient, txns, selectedBank }: SimulateActionProps) => { const ataLst = getAssociatedTokenAddressSync(LST_MINT, marginfiClient.wallet.publicKey); - let actionMethod: ActionMethod | undefined = undefined; + let actionMethod: ActionMessageType | undefined = undefined; let simulationSucceeded = false; try { diff --git a/packages/mrgn-utils/src/actions/checks.ts b/packages/mrgn-utils/src/actions/checks.ts index 0c914143b6..8b0c125c26 100644 --- a/packages/mrgn-utils/src/actions/checks.ts +++ b/packages/mrgn-utils/src/actions/checks.ts @@ -1,5 +1,5 @@ import { floor, percentFormatter, WSOL_MINT } from "@mrgnlabs/mrgn-common"; -import { ActionMethod } from "./types"; +import { ActionMessageType } from "./types"; import { QuoteResponse } from "@jup-ag/api"; import { MarginfiAccountWrapper, @@ -27,8 +27,8 @@ export { canBeWithdrawn, canBeRepaid, canBeRepaidCollat, canBeLooped, canBeBorro function canBeWithdrawn( targetBankInfo: ExtendedBankInfo, marginfiAccount: MarginfiAccountWrapper | null -): ActionMethod[] { - let checks: ActionMethod[] = []; +): ActionMessageType[] { + let checks: ActionMessageType[] = []; const isPaused = targetBankInfo.info.rawBank.config.operationalState === OperationalState.Paused; if (isPaused) { checks.push(DYNAMIC_SIMULATION_ERRORS.BANK_PAUSED_CHECK(targetBankInfo.info.rawBank.tokenSymbol)); @@ -54,8 +54,8 @@ function canBeWithdrawn( return checks; } -function canBeRepaid(targetBankInfo: ExtendedBankInfo): ActionMethod[] { - let checks: ActionMethod[] = []; +function canBeRepaid(targetBankInfo: ExtendedBankInfo): ActionMessageType[] { + let checks: ActionMessageType[] = []; const isPaused = targetBankInfo.info.rawBank.config.operationalState === OperationalState.Paused; if (isPaused) { checks.push(DYNAMIC_SIMULATION_ERRORS.BANK_PAUSED_CHECK(targetBankInfo.info.rawBank.tokenSymbol)); @@ -81,8 +81,8 @@ function canBeRepaidCollat( repayBankInfo: ExtendedBankInfo | null, blacklistRoutes: PublicKey[] | null, swapQuote: QuoteResponse | null -): ActionMethod[] { - let checks: ActionMethod[] = []; +): ActionMessageType[] { + let checks: ActionMessageType[] = []; const isPaused = targetBankInfo.info.rawBank.config.operationalState === OperationalState.Paused; if (isPaused) { @@ -125,8 +125,8 @@ function canBeLooped( targetBankInfo: ExtendedBankInfo, repayBankInfo: ExtendedBankInfo | null, swapQuote: QuoteResponse | null -): ActionMethod[] { - let checks: ActionMethod[] = []; +): ActionMessageType[] { + let checks: ActionMessageType[] = []; const isTargetBankPaused = targetBankInfo.info.rawBank.config.operationalState === OperationalState.Paused; const isRepayBankPaused = repayBankInfo?.info.rawBank.config.operationalState === OperationalState.Paused; @@ -164,8 +164,8 @@ function canBeBorrowed( targetBankInfo: ExtendedBankInfo, extendedBankInfos: ExtendedBankInfo[], marginfiAccount: MarginfiAccountWrapper | null -): ActionMethod[] { - let checks: ActionMethod[] = []; +): ActionMessageType[] { + let checks: ActionMessageType[] = []; const isPaused = targetBankInfo.info.rawBank.config.operationalState === OperationalState.Paused; if (isPaused) { checks.push(DYNAMIC_SIMULATION_ERRORS.BANK_PAUSED_CHECK(targetBankInfo.info.rawBank.tokenSymbol)); @@ -228,8 +228,8 @@ function canBeBorrowed( return checks; } -function canBeLent(targetBankInfo: ExtendedBankInfo, nativeSolBalance: number): ActionMethod[] { - let checks: ActionMethod[] = []; +function canBeLent(targetBankInfo: ExtendedBankInfo, nativeSolBalance: number): ActionMessageType[] { + let checks: ActionMessageType[] = []; const isPaused = targetBankInfo.info.rawBank.config.operationalState === OperationalState.Paused; if (isPaused) { @@ -280,8 +280,8 @@ function canBeLent(targetBankInfo: ExtendedBankInfo, nativeSolBalance: number): return checks; } -function canBeLstStaked(lstQuoteMeta: QuoteResponseMeta | null): ActionMethod[] { - let checks: ActionMethod[] = []; +function canBeLstStaked(lstQuoteMeta: QuoteResponseMeta | null): ActionMessageType[] { + let checks: ActionMessageType[] = []; if (lstQuoteMeta?.quoteResponse?.priceImpactPct && Number(lstQuoteMeta?.quoteResponse.priceImpactPct) > 0.01) { if (lstQuoteMeta?.quoteResponse?.priceImpactPct && Number(lstQuoteMeta?.quoteResponse.priceImpactPct) > 0.05) { diff --git a/packages/mrgn-utils/src/actions/flashloans/builders.ts b/packages/mrgn-utils/src/actions/flashloans/builders.ts index 0df5703859..f6bec5935f 100644 --- a/packages/mrgn-utils/src/actions/flashloans/builders.ts +++ b/packages/mrgn-utils/src/actions/flashloans/builders.ts @@ -8,7 +8,7 @@ import { LUT_PROGRAM_AUTHORITY_INDEX, nativeToUi, TransactionBroadcastType, uiTo import { deserializeInstruction, getAdressLookupTableAccounts, getSwapQuoteWithRetry } from "../helpers"; import { isWholePosition } from "../../mrgnUtils"; -import { ActionMethod, LoopingObject, LoopingOptions, RepayWithCollatOptions } from "../types"; +import { ActionMessageType, LoopingObject, LoopingOptions, RepayWithCollatOptions } from "../types"; import { STATIC_SIMULATION_ERRORS } from "../../errors"; import { TOKEN_2022_MINTS, getFeeAccount } from "../../jup-referral.utils"; @@ -53,7 +53,7 @@ export async function calculateRepayCollateralParams( amount: number; lastValidBlockHeight?: number; } - | ActionMethod + | ActionMessageType > { const maxRepayAmount = bank.isActive ? bank?.position.amount : 0; @@ -144,7 +144,7 @@ export async function calculateBorrowLendPositionParams({ feedCrankTxs: VersionedTransaction[]; quote: QuoteResponse; } - | ActionMethod + | ActionMessageType > { let firstQuote; const maxAccountsArr = [undefined, 50, 40, 30]; @@ -229,6 +229,7 @@ export async function calculateLoopingParams({ isTrading?: boolean; broadcastType: TransactionBroadcastType; }): Promise { +}): Promise { if (!marginfiAccount && !marginfiClient) { return STATIC_SIMULATION_ERRORS.NOT_INITIALIZED; } @@ -296,7 +297,7 @@ export async function calculateLoopingParams({ flashloanTx: VersionedTransaction | null; feedCrankTxs: VersionedTransaction[]; addressLookupTableAccounts: AddressLookupTableAccount[]; - error?: ActionMethod; + error?: ActionMessageType; } = { flashloanTx: null, feedCrankTxs: [], @@ -360,6 +361,7 @@ export async function calculateLoopingTransaction({ isTrading?: boolean; broadcastType: TransactionBroadcastType; }): Promise { +}): Promise { if (loopObject && marginfiAccount) { const txn = await verifyTxSizeLooping( marginfiAccount, diff --git a/packages/mrgn-utils/src/actions/flashloans/helpers.ts b/packages/mrgn-utils/src/actions/flashloans/helpers.ts index 77d8285047..7f3c6075b8 100644 --- a/packages/mrgn-utils/src/actions/flashloans/helpers.ts +++ b/packages/mrgn-utils/src/actions/flashloans/helpers.ts @@ -7,7 +7,7 @@ import { ActiveBankInfo, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state import { nativeToUi, TransactionBroadcastType, uiToNative } from "@mrgnlabs/mrgn-common"; import { STATIC_SIMULATION_ERRORS } from "../../errors"; -import { ActionMethod } from "../types"; +import { ActionMessageType } from "../types"; import { closePositionBuilder, loopingBuilder, repayWithCollatBuilder } from "./builders"; import { getSwapQuoteWithRetry } from "../helpers"; @@ -37,7 +37,7 @@ export async function verifyTxSizeLooping( flashloanTx: VersionedTransaction | null; feedCrankTxs: VersionedTransaction[]; addressLookupTableAccounts: AddressLookupTableAccount[]; - error?: ActionMethod; + error?: ActionMessageType; }> { try { const builder = await loopingBuilder({ @@ -86,7 +86,7 @@ export async function verifyTxSizeCloseBorrowLendPosition( flashloanTx: VersionedTransaction | null; feedCrankTxs: VersionedTransaction[]; addressLookupTableAccounts: AddressLookupTableAccount[]; - error?: ActionMethod; + error?: ActionMessageType; }> { try { if (quoteResponse.slippageBps > 150) { @@ -139,7 +139,7 @@ export async function verifyTxSizeCollat( flashloanTx: VersionedTransaction | null; feedCrankTxs: VersionedTransaction[]; addressLookupTableAccounts: AddressLookupTableAccount[]; - error?: ActionMethod; + error?: ActionMessageType; lastValidBlockHeight?: number; }> { try { @@ -184,7 +184,7 @@ export const verifyFlashloanTxSize = (builder: { feedCrankTxs: VersionedTransaction[]; addressLookupTableAccounts: AddressLookupTableAccount[]; lastValidBlockHeight?: number; - error?: ActionMethod; + error?: ActionMessageType; } => { try { const totalSize = builder.flashloanTx.message.serialize().length; diff --git a/packages/mrgn-utils/src/errors.ts b/packages/mrgn-utils/src/errors.ts index e30e4cebdf..087c6c8c9b 100644 --- a/packages/mrgn-utils/src/errors.ts +++ b/packages/mrgn-utils/src/errors.ts @@ -3,10 +3,10 @@ import { JUPITER_PROGRAM_V6_ID } from "@jup-ag/react-hook"; import { ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { percentFormatter } from "@mrgnlabs/mrgn-common"; -import { ActionMethod } from "./actions"; +import { ActionMessageType } from "./actions"; // Static errors that are not expected to change -export const STATIC_SIMULATION_ERRORS: { [key: string]: ActionMethod } = { +export const STATIC_SIMULATION_ERRORS: { [key: string]: ActionMessageType } = { NOT_INITIALIZED: { isEnabled: false, actionMethod: "WARNING", @@ -137,63 +137,63 @@ export const STATIC_SIMULATION_ERRORS: { [key: string]: ActionMethod } = { }, }; -const createRepayCollatFailedCheck = (tokenSymbol?: string): ActionMethod => ({ +const createRepayCollatFailedCheck = (tokenSymbol?: string): ActionMessageType => ({ description: `Unable to repay using ${tokenSymbol}, please select another collateral.`, isEnabled: false, }); -const createInsufficientBalanceCheck = (tokenSymbol?: string): ActionMethod => ({ +const createInsufficientBalanceCheck = (tokenSymbol?: string): ActionMessageType => ({ description: `Insufficient ${tokenSymbol} in wallet.`, isEnabled: false, }); -const createExistingIsolatedBorrowCheck = (tokenSymbol?: string): ActionMethod => ({ +const createExistingIsolatedBorrowCheck = (tokenSymbol?: string): ActionMessageType => ({ description: `You have an active isolated borrow (${tokenSymbol}). You cannot borrow another asset while you do.`, isEnabled: false, }); -const createBorrowCapacityCheck = (tokenSymbol?: string): ActionMethod => ({ +const createBorrowCapacityCheck = (tokenSymbol?: string): ActionMessageType => ({ description: `The ${tokenSymbol} bank is at borrow capacity.`, isEnabled: false, }); -const createBankRetiredCheck = (tokenSymbol?: string): ActionMethod => ({ +const createBankRetiredCheck = (tokenSymbol?: string): ActionMessageType => ({ description: `The ${tokenSymbol} bank is being retired. You may only withdraw a deposit or repay a loan.`, isEnabled: false, }); -const createReduceOnlyCheck = (tokenSymbol?: string): ActionMethod => ({ +const createReduceOnlyCheck = (tokenSymbol?: string): ActionMessageType => ({ description: `The ${tokenSymbol} bank is in reduce-only mode. You may only withdraw a deposit or repay a loan.`, isEnabled: false, }); -const createWalletRapayCheck = (tokenSymbol?: string): ActionMethod => ({ +const createWalletRapayCheck = (tokenSymbol?: string): ActionMessageType => ({ description: `You have ${tokenSymbol} in your wallet and can repay without using collateral.`, isEnabled: true, actionMethod: "INFO", }); -const createSufficientLiqCheck = (tokenSymbol?: string): ActionMethod => ({ +const createSufficientLiqCheck = (tokenSymbol?: string): ActionMessageType => ({ description: `Insufficient ${tokenSymbol} in wallet for loan repayment.`, isEnabled: false, }); -const createIfBorrowingCheck = (tokenSymbol?: string): ActionMethod => ({ +const createIfBorrowingCheck = (tokenSymbol?: string): ActionMessageType => ({ description: `You're not borrowing ${tokenSymbol}.`, isEnabled: false, }); -const createIfLendingCheck = (tokenSymbol?: string): ActionMethod => ({ +const createIfLendingCheck = (tokenSymbol?: string): ActionMessageType => ({ description: `You're not lending ${tokenSymbol}.`, isEnabled: false, }); -const createBankPausedCheck = (tokenSymbol?: string): ActionMethod => ({ +const createBankPausedCheck = (tokenSymbol?: string): ActionMessageType => ({ description: `The ${tokenSymbol} bank is paused at this time.`, isEnabled: false, }); -const createStaleCheck = (action: string): ActionMethod => ({ +const createStaleCheck = (action: string): ActionMessageType => ({ isEnabled: true, actionMethod: "WARNING", description: `${action} from this bank may fail due to network congestion preventing oracles from updating price data.`, @@ -205,7 +205,7 @@ const createWithdrawCheck = ( tradeSide: string, stableBank: ExtendedBankInfo, tokenBank: ExtendedBankInfo -): ActionMethod => ({ +): ActionMessageType => ({ isEnabled: false, description: `Before you can ${tradeSide} this asset, you'll need to withdraw your supplied ${ tradeSide === "long" ? stableBank.meta.tokenSymbol : tokenBank.meta.tokenSymbol @@ -220,7 +220,7 @@ const createRepayCheck = ( tradeSide: string, stableBank: ExtendedBankInfo, tokenBank: ExtendedBankInfo -): ActionMethod => ({ +): ActionMessageType => ({ isEnabled: false, description: `Before you can ${tradeSide} this asset, you'll need to repay your borrowed ${ tradeSide === "long" ? tokenBank : stableBank @@ -235,7 +235,7 @@ const createLoopCheck = ( tradeSide: string, stableBank: ExtendedBankInfo, tokenBank: ExtendedBankInfo -): ActionMethod => ({ +): ActionMessageType => ({ isEnabled: false, description: `You are already ${tradeSide} this asset, you need to close that position before you can go ${ tradeSide === "long" ? "short" : "long" @@ -246,7 +246,7 @@ const createLoopCheck = ( }, }); -const createPriceImpactErrorCheck = (priceImpactPct: number): ActionMethod => { +const createPriceImpactErrorCheck = (priceImpactPct: number): ActionMessageType => { return { description: `Price impact is ${percentFormatter.format(priceImpactPct)}.`, actionMethod: "ERROR", @@ -254,7 +254,7 @@ const createPriceImpactErrorCheck = (priceImpactPct: number): ActionMethod => { }; }; -const createPriceImpactWarningCheck = (priceImpactPct: number): ActionMethod => { +const createPriceImpactWarningCheck = (priceImpactPct: number): ActionMessageType => { return { description: `Price impact is ${percentFormatter.format(Number(priceImpactPct))}.`, isEnabled: true, @@ -286,7 +286,7 @@ export const DYNAMIC_SIMULATION_ERRORS = { REPAY_COLLAT_FAILED_CHECK: createRepayCollatFailedCheck, }; -const createCustomError = (description: string): ActionMethod => ({ +const createCustomError = (description: string): ActionMessageType => ({ isEnabled: true, actionMethod: "WARNING", description, @@ -297,7 +297,7 @@ export const handleError = ( bank: ExtendedBankInfo | null, isArena: boolean = false, action?: string -): ActionMethod | null => { +): ActionMessageType | null => { try { // JUPITER ERRORS if (error?.programId === JUPITER_PROGRAM_V6_ID.toBase58()) { @@ -399,7 +399,7 @@ export const handleTransactionError = ( error: any, bank: ExtendedBankInfo | null, isArena: boolean = false -): ActionMethod | undefined => { +): ActionMessageType | undefined => { try { const action = handleError(error, bank, isArena); if (action) { @@ -417,7 +417,7 @@ export const handleSimulationError = ( bank: ExtendedBankInfo | null, isArena: boolean = false, actionString?: string -): ActionMethod | undefined => { +): ActionMessageType | undefined => { try { const action = handleError(error, bank, isArena, actionString); if (action) { From b7772c0f9114568988638041f5a5365b2234f00d Mon Sep 17 00:00:00 2001 From: borcherd Date: Wed, 30 Oct 2024 18:17:48 +0800 Subject: [PATCH 10/14] chore: fix QA comments **WIP** --- .../src/components/common/Navbar/Navbar.tsx | 32 +++---------- .../asset-card/portfolio-asset-card.tsx | 31 +++++++------ .../move-position/move-position-dialog.tsx | 45 ++++++++++--------- .../components/rewards/rewards-dialog.tsx | 4 +- .../hooks/use-move-position-simulation.tsx | 8 ++-- .../Portfolio/hooks/use-reward-simulation.tsx | 4 +- .../src/components/common/Portfolio/index.tsx | 1 - apps/marginfi-v2-ui/src/pages/portfolio.tsx | 2 +- .../hooks/use-stake-simulation.hooks.tsx | 6 +-- 9 files changed, 53 insertions(+), 80 deletions(-) delete mode 100644 apps/marginfi-v2-ui/src/components/common/Portfolio/index.tsx diff --git a/apps/marginfi-v2-ui/src/components/common/Navbar/Navbar.tsx b/apps/marginfi-v2-ui/src/components/common/Navbar/Navbar.tsx index 557d52887e..fea3b7f121 100644 --- a/apps/marginfi-v2-ui/src/components/common/Navbar/Navbar.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Navbar/Navbar.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useMemo, useState } from "react"; +import { FC } from "react"; import Link from "next/link"; import Image from "next/image"; @@ -6,17 +6,18 @@ import { useRouter } from "next/router"; import { PublicKey } from "@solana/web3.js"; import { IconBell, IconBrandTelegram, IconSettings } from "@tabler/icons-react"; +import { IconBell, IconBrandTelegram } from "@tabler/icons-react"; import { collectRewardsBatch, capture, cn } from "@mrgnlabs/mrgn-utils"; import { Settings, Wallet } from "@mrgnlabs/mrgn-ui"; +import { capture, cn } from "@mrgnlabs/mrgn-utils"; +import { Wallet } from "@mrgnlabs/mrgn-ui"; import { useMrgnlendStore, useUiStore, useUserProfileStore } from "~/store"; import { useFirebaseAccount } from "~/hooks/useFirebaseAccount"; -import { useWallet } from "~/components/wallet-v2/hooks/use-wallet.hook"; + import { useConnection } from "~/hooks/use-connection"; -import { useIsMobile } from "~/hooks/use-is-mobile"; -import { EMISSION_MINT_INFO_MAP } from "~/components/desktop/AssetList/components"; import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover"; import { Button } from "~/components/ui/button"; import { IconMrgn } from "~/components/ui/icons"; @@ -26,8 +27,6 @@ export const Navbar: FC = () => { useFirebaseAccount(); const { connection } = useConnection(); - const isMobile = useIsMobile(); - const { wallet } = useWallet(); const router = useRouter(); const [ initialized, @@ -61,29 +60,10 @@ export const Navbar: FC = () => { setTransactionSettings: state.setTransactionSettings, }) ); + const [isOraclesStale] = useUiStore((state) => [state.isOraclesStale, state.priorityFee]); const [userPointsData] = useUserProfileStore((state) => [state.userPointsData]); - // const [lipAccount, setLipAccount] = useState(null); - - const bankAddressesWithEmissions: PublicKey[] = useMemo(() => { - if (!selectedAccount) return []; - return [...EMISSION_MINT_INFO_MAP.keys()] - .map((bankMintSymbol) => { - const uxdBankInfo = extendedBankInfos?.find((b) => b.isActive && b.meta.tokenSymbol === bankMintSymbol); - return uxdBankInfo?.address; - }) - .filter((address) => address !== undefined) as PublicKey[]; - }, [selectedAccount, extendedBankInfos]); - - // useEffect(() => { - // (async function () { - // if (!mfiClient || !lipClient || !walletAddress) return; - // const lipAccount = await LipAccount.fetch(walletAddress, lipClient, mfiClient); - // setLipAccount(lipAccount); - // })(); - // }, [lipClient, mfiClient, walletAddress]); - return (
-
+

Lend/borrow

- {rewards ? ( - rewards.totalReward > 0 ? ( - - ) : ( - - ) - ) : ( - - Calculating rewards - - )}{" "} {rewards && ( - - + +
+ {rewards ? ( + rewards.totalReward > 0 ? ( + + ) : ( + + ) + ) : ( + + Calculating rewards + + )}{" "} + +
- {rewards && rewards.totalReward > 0 + {EMISSION_MINT_INFO_MAP.size === 0 + ? "There are currently no banks that are outputting rewards." + : rewards && rewards.totalReward > 0 ? `You are earning rewards on the following banks: ${rewards.rewards .map((r) => r.bank) .join(", ")}` From 77f9f438e739c3d97030f52367717039ade1c698 Mon Sep 17 00:00:00 2001 From: Kobe Date: Thu, 31 Oct 2024 15:18:46 +0100 Subject: [PATCH 14/14] fix: resolve merge conflicts --- .../src/components/common/Navbar/Navbar.tsx | 11 ++-------- .../asset-card/portfolio-asset-card.tsx | 2 +- .../src/models/account/wrapper.ts | 20 ++++++++++++------- .../hooks/use-lend-simulation.hooks.ts | 3 +-- .../actions/lend-box/lend-box.tsx | 11 +++++++--- .../actions/lend-box/store/lend-box-store.ts | 1 - .../actions/loop-box/loop-box.tsx | 13 +++--------- .../loop-box/utils/loop-action.utils.ts | 2 -- .../repay-collat-box/repay-collat-box.tsx | 13 +++--------- .../actions/stake-box/stake-box.tsx | 13 +++++------- .../src/actions/flashloans/builders.ts | 2 -- 11 files changed, 36 insertions(+), 55 deletions(-) diff --git a/apps/marginfi-v2-ui/src/components/common/Navbar/Navbar.tsx b/apps/marginfi-v2-ui/src/components/common/Navbar/Navbar.tsx index fea3b7f121..bfaffada9b 100644 --- a/apps/marginfi-v2-ui/src/components/common/Navbar/Navbar.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Navbar/Navbar.tsx @@ -3,15 +3,7 @@ import { FC } from "react"; import Link from "next/link"; import Image from "next/image"; import { useRouter } from "next/router"; - -import { PublicKey } from "@solana/web3.js"; import { IconBell, IconBrandTelegram, IconSettings } from "@tabler/icons-react"; -import { IconBell, IconBrandTelegram } from "@tabler/icons-react"; - -import { collectRewardsBatch, capture, cn } from "@mrgnlabs/mrgn-utils"; -import { Settings, Wallet } from "@mrgnlabs/mrgn-ui"; -import { capture, cn } from "@mrgnlabs/mrgn-utils"; -import { Wallet } from "@mrgnlabs/mrgn-ui"; import { useMrgnlendStore, useUiStore, useUserProfileStore } from "~/store"; import { useFirebaseAccount } from "~/hooks/useFirebaseAccount"; @@ -21,6 +13,8 @@ import { useConnection } from "~/hooks/use-connection"; import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover"; import { Button } from "~/components/ui/button"; import { IconMrgn } from "~/components/ui/icons"; +import { cn, capture } from "@mrgnlabs/mrgn-utils"; +import { Settings, Wallet } from "~/components"; // @todo implement second pretty navbar row export const Navbar: FC = () => { @@ -60,7 +54,6 @@ export const Navbar: FC = () => { setTransactionSettings: state.setTransactionSettings, }) ); - const [isOraclesStale] = useUiStore((state) => [state.isOraclesStale, state.priorityFee]); const [userPointsData] = useUserProfileStore((state) => [state.userPointsData]); diff --git a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx index b699039b12..eb69490cdd 100644 --- a/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx +++ b/apps/marginfi-v2-ui/src/components/common/Portfolio/components/asset-card/portfolio-asset-card.tsx @@ -3,7 +3,7 @@ import React from "react"; import Image from "next/image"; import { IconAlertTriangle } from "@tabler/icons-react"; -import { usdFormatter, numeralFormatter } from "@mrgnlabs/mrgn-common"; +import { usdFormatter, dynamicNumeralFormatter } from "@mrgnlabs/mrgn-common"; import { ActiveBankInfo, ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { capture } from "@mrgnlabs/mrgn-utils"; import { ActionBox } from "@mrgnlabs/mrgn-ui"; diff --git a/packages/marginfi-client-v2/src/models/account/wrapper.ts b/packages/marginfi-client-v2/src/models/account/wrapper.ts index 0d08f5c114..d6af6a1dde 100644 --- a/packages/marginfi-client-v2/src/models/account/wrapper.ts +++ b/packages/marginfi-client-v2/src/models/account/wrapper.ts @@ -1071,14 +1071,16 @@ class MarginfiAccountWrapper { ); } - async withdrawEmissions( + async makeWithdrawEmissionsTx( bankAddresses: PublicKey[], priorityFeeUi?: number, broadcastType: TransactionBroadcastType = "BUNDLE" - ): Promise { - async makeWithdrawEmissionsTx(bankAddresses: PublicKey[]): Promise { - const bundleTipIx = makeBundleTipIx(this.client.provider.publicKey); - const priorityFeeIx = this.makePriorityFeeIx(0); // TODO: set priorityfee + ): Promise { + const { bundleTipIx, priorityFeeIx } = makeTxPriorityIx( + this.client.provider.publicKey, + priorityFeeUi, + broadcastType + ); const blockhash = (await this._program.provider.connection.getLatestBlockhash()).blockhash; const ixs: TransactionInstruction[] = []; @@ -1092,14 +1094,18 @@ class MarginfiAccountWrapper { return new VersionedTransaction( new TransactionMessage({ - instructions: [bundleTipIx, ...priorityFeeIx, ...ixs], + instructions: [priorityFeeIx, ...(bundleTipIx ? [bundleTipIx] : []), ...ixs], payerKey: this.authority, recentBlockhash: blockhash, }).compileToV0Message() ); } - async withdrawEmissions(bankAddresses: PublicKey[], priorityFeeUi?: number): Promise { + async withdrawEmissions( + bankAddresses: PublicKey[], + priorityFeeUi?: number, + broadcastType: TransactionBroadcastType = "BUNDLE" + ): Promise { const debug = require("debug")(`mfi:margin-account:${this.address.toString()}:withdraw-emissions`); debug("Withdrawing emission from marginfi account (bank: %s)", bankAddresses.map((b) => b.toBase58()).join(", ")); const { bundleTipIx, priorityFeeIx } = makeTxPriorityIx( diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/hooks/use-lend-simulation.hooks.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/hooks/use-lend-simulation.hooks.ts index 5702a82ccc..079489b3df 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/hooks/use-lend-simulation.hooks.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/hooks/use-lend-simulation.hooks.ts @@ -4,9 +4,8 @@ import { Transaction, VersionedTransaction } from "@solana/web3.js"; import { AccountSummary, ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { MarginfiAccountWrapper, SimulationResult } from "@mrgnlabs/marginfi-client-v2"; -import { ActionMethod, STATIC_SIMULATION_ERRORS, usePrevious } from "@mrgnlabs/mrgn-utils"; import { TransactionBroadcastType } from "@mrgnlabs/mrgn-common"; -import { ActionMessageType, STATIC_SIMULATION_ERRORS, usePrevious } from "@mrgnlabs/mrgn-utils"; +import { ActionMessageType, usePrevious, STATIC_SIMULATION_ERRORS } from "@mrgnlabs/mrgn-utils"; import { calculateLendingTransaction, calculateSummary, getSimulationResult } from "../utils"; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/lend-box.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/lend-box.tsx index 902f040b2e..d2112a944b 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/lend-box.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/lend-box.tsx @@ -11,10 +11,15 @@ import { computeAccountSummary, DEFAULT_ACCOUNT_SUMMARY, } from "@mrgnlabs/marginfi-v2-ui-state"; -import { ActionMessageType, checkLendActionAvailable, MarginfiActionParams, PreviousTxn } from "@mrgnlabs/mrgn-utils"; -import { ActionMethod, MarginfiActionParams, PreviousTxn, useConnection, usePriorityFee } from "@mrgnlabs/mrgn-utils"; -import { ActionMethod, checkLendActionAvailable, MarginfiActionParams, PreviousTxn } from "@mrgnlabs/mrgn-utils"; + import { MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; +import { + ActionMessageType, + checkLendActionAvailable, + MarginfiActionParams, + PreviousTxn, + usePriorityFee, +} from "@mrgnlabs/mrgn-utils"; import { ActionButton } from "~/components/action-box-v2/components"; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/store/lend-box-store.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/store/lend-box-store.ts index 18a4ee4057..1a93915805 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/store/lend-box-store.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/lend-box/store/lend-box-store.ts @@ -1,5 +1,4 @@ import { create, StateCreator } from "zustand"; -import { Transaction, VersionedTransaction } from "@solana/web3.js"; import { ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { ActionMessageType, ActionTxns } from "@mrgnlabs/mrgn-utils"; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/loop-box.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/loop-box.tsx index f6778d16a1..55580d9013 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/loop-box.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/loop-box.tsx @@ -9,23 +9,16 @@ import { ActiveBankInfo, } from "@mrgnlabs/marginfi-v2-ui-state"; import { WalletContextState } from "@solana/wallet-adapter-react"; + +import { MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; import { - ActionMethod, - MarginfiActionParams, - PreviousTxn, - showErrorToast, - useConnection, - usePriorityFee, -} from "@mrgnlabs/mrgn-utils"; -import { - ActionMethod, ActionMessageType, checkLoopActionAvailable, MarginfiActionParams, PreviousTxn, showErrorToast, + usePriorityFee, } from "@mrgnlabs/mrgn-utils"; -import { MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; import { useAmountDebounce } from "~/hooks/useAmountDebounce"; import { WalletContextStateOverride } from "~/components/wallet-v2"; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-action.utils.ts b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-action.utils.ts index dc0abda3f9..514864f7c7 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-action.utils.ts +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/loop-box/utils/loop-action.utils.ts @@ -68,8 +68,6 @@ export async function calculateLooping( priorityFee: number, platformFeeBps: number, broadcastType: TransactionBroadcastType -): Promise { - platformFeeBps: number ): Promise { // TODO setup logging again // capture("looper", { diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/repay-collat-box.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/repay-collat-box.tsx index 8e0d314ced..d34f5e4475 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/repay-collat-box.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-collat-box/repay-collat-box.tsx @@ -8,23 +8,16 @@ import { DEFAULT_ACCOUNT_SUMMARY, ActiveBankInfo, } from "@mrgnlabs/marginfi-v2-ui-state"; + +import { MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; import { - ActionMethod, - MarginfiActionParams, PreviousTxn, - showErrorToast, - useConnection, usePriorityFee, -} from "@mrgnlabs/mrgn-utils"; -import { - ActionMethod, ActionMessageType, + showErrorToast, checkRepayCollatActionAvailable, MarginfiActionParams, - PreviousTxn, - showErrorToast, } from "@mrgnlabs/mrgn-utils"; -import { MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; import { CircularProgress } from "~/components/ui/circular-progress"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "~/components/ui/tooltip"; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/stake-box.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/stake-box.tsx index 7bd8e4e0fd..395f78a2b6 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/stake-box.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/stake-box/stake-box.tsx @@ -4,17 +4,15 @@ import { WalletContextState } from "@solana/wallet-adapter-react"; import { getPriceWithConfidence, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; import { AccountSummary, ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; -import { ActionMethod, checkStakeActionAvailable, LstData, PreviousTxn } from "@mrgnlabs/mrgn-utils"; + +import { nativeToUi, NATIVE_MINT as SOL_MINT, uiToNative } from "@mrgnlabs/mrgn-common"; import { - ActionMethod, LstData, PreviousTxn, - showErrorToast, - STATIC_SIMULATION_ERRORS, usePriorityFee, + ActionMessageType, + checkStakeActionAvailable, } from "@mrgnlabs/mrgn-utils"; -import { ActionMessageType, checkStakeActionAvailable, LstData, PreviousTxn } from "@mrgnlabs/mrgn-utils"; -import { nativeToUi, NATIVE_MINT as SOL_MINT, uiToNative } from "@mrgnlabs/mrgn-common"; import { useActionAmounts } from "~/components/action-box-v2/hooks"; import { WalletContextStateOverride } from "~/components/wallet-v2/hooks/use-wallet.hook"; @@ -28,8 +26,7 @@ import { useStakeSimulation } from "./hooks"; import { useActionBoxStore } from "../../store"; import { handleExecuteLstAction } from "./utils/stake-action.utils"; import { ActionInput } from "./components/action-input"; -import { useStakeBoxContext } from "../../contexts"; -import { checkActionAvailable } from "./utils"; + import { useActionContext, useStakeBoxContext } from "../../contexts"; export type StakeBoxProps = { diff --git a/packages/mrgn-utils/src/actions/flashloans/builders.ts b/packages/mrgn-utils/src/actions/flashloans/builders.ts index f6bec5935f..a2db22e064 100644 --- a/packages/mrgn-utils/src/actions/flashloans/builders.ts +++ b/packages/mrgn-utils/src/actions/flashloans/builders.ts @@ -228,7 +228,6 @@ export async function calculateLoopingParams({ platformFeeBps?: number; isTrading?: boolean; broadcastType: TransactionBroadcastType; -}): Promise { }): Promise { if (!marginfiAccount && !marginfiClient) { return STATIC_SIMULATION_ERRORS.NOT_INITIALIZED; @@ -360,7 +359,6 @@ export async function calculateLoopingTransaction({ loopObject?: LoopingObject; isTrading?: boolean; broadcastType: TransactionBroadcastType; -}): Promise { }): Promise { if (loopObject && marginfiAccount) { const txn = await verifyTxSizeLooping(