From d5c0a3f2a235855e6e80e07543571ce9d3ac1d37 Mon Sep 17 00:00:00 2001 From: borcherd Date: Wed, 19 Feb 2025 15:53:27 -0300 Subject: [PATCH] feat: github comments --- .../common/trade-box-v2/trade-box.tsx | 51 +-- .../move-position/move-position-dialog.tsx | 59 ++-- .../common/Portfolio/lending-portfolio.tsx | 37 ++- .../src/models/account/wrapper.ts | 10 +- .../modules/transactions/transaction.types.ts | 33 +- .../multi-step-toast/multi-step-toast.tsx | 12 +- .../mrgn-toasts/src/utils/toast-manager.tsx | 80 ++--- .../deposit-swap-box/deposit-swap-box.tsx | 55 ++-- .../hooks/use-lend-simulation.hooks.ts | 197 ++++++------ .../actions/lend-box/lend-box.tsx | 34 +- .../actions/loop-box/loop-box.tsx | 39 ++- .../actions/repay-box/repay-box.tsx | 72 +++-- .../actions/stake-box/stake-box.tsx | 50 ++- packages/mrgn-utils/src/actions/actions.ts | 298 +++++++++++++----- 14 files changed, 649 insertions(+), 378 deletions(-) diff --git a/apps/marginfi-v2-trading/src/components/common/trade-box-v2/trade-box.tsx b/apps/marginfi-v2-trading/src/components/common/trade-box-v2/trade-box.tsx index 4bc657dd7..f79ff387f 100644 --- a/apps/marginfi-v2-trading/src/components/common/trade-box-v2/trade-box.tsx +++ b/apps/marginfi-v2-trading/src/components/common/trade-box-v2/trade-box.tsx @@ -13,7 +13,7 @@ import { IconSettings } from "@tabler/icons-react"; import { dynamicNumeralFormatter } from "@mrgnlabs/mrgn-common"; import { ArenaPoolV2 } from "~/types/trade-store.types"; -import { SimulationStatus, TradeSide } from "~/components/common/trade-box-v2/utils"; +import { SimulationStatus, TradeSide } from "~/components/common/trade-box-v2/utils"; import { Card, CardContent, CardHeader } from "~/components/ui/card"; import { useTradeStoreV2, useUiStore } from "~/store"; import { useWallet, useWalletStore } from "~/components/wallet-v2"; @@ -85,13 +85,7 @@ export const TradeBoxV2 = ({ activePool, side = "long" }: TradeBoxV2Props) => { state.setBorrowBankPk, state.setMaxLeverage, ]); - const [ - jupiterOptions, - platformFeeBps, - broadcastType, - priorityFees, - setDisplaySettings, - ] = useUiStore((state) => [ + const [jupiterOptions, platformFeeBps, broadcastType, priorityFees, setDisplaySettings] = useUiStore((state) => [ state.jupiterOptions, state.platformFeeBps, state.broadcastType, @@ -141,7 +135,7 @@ export const TradeBoxV2 = ({ activePool, side = "long" }: TradeBoxV2Props) => { // Loading states const [isTransactionExecuting, setIsTransactionExecuting] = React.useState(false); - const [isSimulating, setIsSimulating] = React.useState<{ + const [simulationStatus, setSimulationStatus] = React.useState<{ isLoading: boolean; status: SimulationStatus; }>({ @@ -149,8 +143,8 @@ export const TradeBoxV2 = ({ activePool, side = "long" }: TradeBoxV2Props) => { status: SimulationStatus.IDLE, }); const isLoading = React.useMemo( - () => isTransactionExecuting || isSimulating.isLoading, - [isTransactionExecuting, isSimulating.isLoading] + () => isTransactionExecuting || simulationStatus.isLoading, + [isTransactionExecuting, simulationStatus.isLoading] ); // Memos @@ -243,7 +237,7 @@ export const TradeBoxV2 = ({ activePool, side = "long" }: TradeBoxV2Props) => { isEnabled: !actionMethods.filter((value) => value.isEnabled === false).length, setActionTxns: setActionTxns, setErrorMessage: setErrorMessage, - setIsLoading: setIsSimulating, + setIsLoading: setSimulationStatus, setSimulationResult, setMaxLeverage, tradeState, @@ -261,10 +255,9 @@ export const TradeBoxV2 = ({ activePool, side = "long" }: TradeBoxV2Props) => { // Trading Actions // ///////////////////// const handleTradeAction = useCallback(() => { - if (!depositBank || !actionTxns || !borrowBank || !client) return - + if (!depositBank || !actionTxns || !borrowBank || !client) return; - const props : ExecuteTradeActionProps = { + const props: ExecuteTradeActionProps = { actionTxns, attemptUuid: uuidv4(), marginfiClient: client, @@ -278,7 +271,7 @@ export const TradeBoxV2 = ({ activePool, side = "long" }: TradeBoxV2Props) => { wallet, groupPk: activePoolExtended.groupPk, banks: [activePoolExtended.tokenBank.address, activePoolExtended.quoteBank.address], - }) + }); }, }, infoProps: { @@ -287,13 +280,27 @@ export const TradeBoxV2 = ({ activePool, side = "long" }: TradeBoxV2Props) => { borrowAmount: dynamicNumeralFormatter(actionTxns.borrowAmount.toNumber()), borrowToken: borrowBank.meta.tokenSymbol, tradeSide: tradeState, - } - } + }, + }; ExecuteTradeAction(props); setAmountRaw(""); - }, [depositBank, actionTxns, borrowBank, client, priorityFees, broadcastType, amountRaw, tradeState, setAmountRaw, refreshGroup, connection, wallet, activePoolExtended]) + }, [ + depositBank, + actionTxns, + borrowBank, + client, + priorityFees, + broadcastType, + amountRaw, + tradeState, + setAmountRaw, + refreshGroup, + connection, + wallet, + activePoolExtended, + ]); return ( @@ -327,7 +334,7 @@ export const TradeBoxV2 = ({ activePool, side = "long" }: TradeBoxV2Props) => { )} @@ -345,7 +352,7 @@ export const TradeBoxV2 = ({ activePool, side = "long" }: TradeBoxV2Props) => { }) } refreshSimulation={refreshSimulation} - isRetrying={isSimulating.isLoading} + isRetrying={simulationStatus.isLoading} quoteBalance={maxAmount} quoteBank={activePoolExtended.quoteBank} /> @@ -363,7 +370,7 @@ export const TradeBoxV2 = ({ activePool, side = "long" }: TradeBoxV2Props) => { />
0} isActive={depositBank && amount > 0 ? true : false} /> 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 8710d6811..4425d280c 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 @@ -1,6 +1,7 @@ import React from "react"; import { Transaction, VersionedTransaction } from "@solana/web3.js"; +import { v4 as uuidv4 } from "uuid"; import { usdFormatter, @@ -15,6 +16,8 @@ import { captureSentryException, checkLendActionAvailable, composeExplorerUrl, + ExecuteMovePositionAction, + ExecuteMovePositionActionProps, extractErrorString, } from "@mrgnlabs/mrgn-utils"; import { MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; @@ -134,46 +137,36 @@ export const MovePositionDialog = ({ const handleMovePosition = React.useCallback(async () => { if (!marginfiClient || !accountToMoveTo || !actionTxns || !broadcastType || !priorityFees) { + console.error("Missing required props for ExecuteMovePositionAction"); return; } - const processOpts = { ...priorityFees, broadcastType }; + const props: ExecuteMovePositionActionProps = { + actionTxns: { transactions: actionTxns }, + attemptUuid: uuidv4(), + marginfiClient, + processOpts: { ...priorityFees, broadcastType }, + txOpts: {}, + infoProps: { + originAccountAddress: shortenAddress(selectedAccount?.address.toBase58() ?? ""), + destinationAccountAddress: shortenAddress(accountToMoveTo?.address.toBase58() ?? ""), + }, + callbacks: { + onComplete: () => { + fetchMrgnlendState(); + }, + }, + }; - const multiStepToast = toastManager.createMultiStepToast("Moving position", [ - { label: "Signing transaction" }, - { label: `Withdrawing from account ${shortenAddress(selectedAccount?.address.toBase58() ?? "", 8)}` }, - { label: `Depositing to account ${shortenAddress(accountToMoveTo?.address.toBase58(), 8)}` }, - { label: "Updating accounts" }, - ]); - multiStepToast.start(); - setIsExecutionLoading(true); - try { - const sigs = await marginfiClient.processTransactions(actionTxns, { - ...processOpts, - callback: (index, success, sig, stepsToAdvance) => - success && multiStepToast.successAndNext(stepsToAdvance, composeExplorerUrl(sig), sig), - }); - await fetchMrgnlendState(); - multiStepToast.success(); - setIsOpen(false); - } catch (error) { - const msg = extractErrorString(error); - console.error("Error moving position between accounts", msg); - multiStepToast.setFailed(msg); - captureSentryException(error, msg, { - action: "movePosition", - wallet: marginfiClient?.wallet.publicKey.toBase58(), - }); - } finally { - setIsExecutionLoading(false); - } + ExecuteMovePositionAction(props); + setIsOpen(false); }, [ marginfiClient, accountToMoveTo, actionTxns, broadcastType, priorityFees, - selectedAccount?.address, + selectedAccount, fetchMrgnlendState, setIsOpen, ]); @@ -181,7 +174,7 @@ export const MovePositionDialog = ({ React.useEffect(() => { if (!accountToMoveTo) return; handleSimulateTxns(); - }, [accountToMoveTo]); + }, [accountToMoveTo, handleSimulateTxns]); return ( = 0.5 ? "text-success" : actionSummary.health >= 0.25 - ? "text-alert-foreground" - : "text-destructive-foreground" + ? "text-alert-foreground" + : "text-destructive-foreground" }`} > <> 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 f7fcbb606..35c6b9c76 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,5 +1,6 @@ import React from "react"; import Link from "next/link"; +import { v4 as uuidv4 } from "uuid"; import { IconInfoCircle, IconX } from "@tabler/icons-react"; import { Id, toast } from "react-toastify"; @@ -7,7 +8,13 @@ import { Id, toast } from "react-toastify"; import { numeralFormatter, SolanaTransaction } from "@mrgnlabs/mrgn-common"; import { usdFormatter, usdFormatterDyn } from "@mrgnlabs/mrgn-common"; import { ActionType, ActiveBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; -import { cn, usePrevious } from "@mrgnlabs/mrgn-utils"; +import { + cn, + ExecuteActionProps, + ExecuteCollectRewardsAction, + ExecuteLoopActionProps, + usePrevious, +} from "@mrgnlabs/mrgn-utils"; import { useMrgnlendStore, useUiStore, useUserProfileStore } from "~/store"; @@ -102,14 +109,26 @@ export const LendingPortfolio = () => { } }, [handleSimulation, marginfiClient, selectedAccount, shouldFetchRewards]); - const handleCollectExecution = React.useCallback(async () => { + const handleCollectRewardsAction = React.useCallback(async () => { if (!marginfiClient || !actionTxn) return; - await executeCollectTxn(marginfiClient, actionTxn, { ...priorityFees, broadcastType }, setRewardsLoading, () => { - setRewardsDialogOpen(false); - }); - setRewardsState(initialRewardsState); - handleSimulation(); - }, [marginfiClient, actionTxn, priorityFees, broadcastType, handleSimulation]); + const props: ExecuteActionProps = { + marginfiClient, + actionTxns: { transactions: [actionTxn] }, + attemptUuid: uuidv4(), + processOpts: { ...priorityFees, broadcastType }, + txOpts: {}, + callbacks: { + onComplete: () => { + setRewardsState(initialRewardsState); + handleSimulation(); + }, + }, + }; + + ExecuteCollectRewardsAction(props); + + setRewardsDialogOpen(false); + }, [marginfiClient, actionTxn, priorityFees, broadcastType, handleSimulation, setRewardsDialogOpen]); const lendingBanks = React.useMemo( () => @@ -466,7 +485,7 @@ export const LendingPortfolio = () => { onOpenChange={(open) => { setRewardsDialogOpen(open); }} - onCollect={handleCollectExecution} + onCollect={handleCollectRewardsAction} isLoading={rewardsLoading} />
diff --git a/packages/marginfi-client-v2/src/models/account/wrapper.ts b/packages/marginfi-client-v2/src/models/account/wrapper.ts index e0d785c20..f4e93d09e 100644 --- a/packages/marginfi-client-v2/src/models/account/wrapper.ts +++ b/packages/marginfi-client-v2/src/models/account/wrapper.ts @@ -111,7 +111,11 @@ class MarginfiAccountWrapper { /** * @internal */ - constructor(marginfiAccountPk: PublicKey, private readonly client: MarginfiClient, marginfiAccount: MarginfiAccount) { + constructor( + marginfiAccountPk: PublicKey, + private readonly client: MarginfiClient, + marginfiAccount: MarginfiAccount + ) { this.address = marginfiAccountPk; this._marginfiAccount = marginfiAccount; } @@ -477,7 +481,7 @@ class MarginfiAccountWrapper { { signers: withdrawIxs.keys, addressLookupTables: lookupTables, - type: TransactionType.WITHDRAW, + type: TransactionType.MOVE_POSITION_WITHDRAW, } ); @@ -486,7 +490,7 @@ class MarginfiAccountWrapper { const depositTx = addTransactionMetadata(tx, { signers: depositIx.keys, addressLookupTables: lookupTables, - type: TransactionType.MOVE_POSITION, + type: TransactionType.MOVE_POSITION_DEPOSIT, }); const transactions = [...feedCrankTxs, withdrawTx, depositTx]; diff --git a/packages/mrgn-common/src/modules/transactions/transaction.types.ts b/packages/mrgn-common/src/modules/transactions/transaction.types.ts index 1942131ca..0d7a592b4 100644 --- a/packages/mrgn-common/src/modules/transactions/transaction.types.ts +++ b/packages/mrgn-common/src/modules/transactions/transaction.types.ts @@ -21,7 +21,8 @@ export enum TransactionType { // ACCOUNT MANAGEMENT CLOSE_ACCOUNT = "CLOSE_ACCOUNT", CLOSE_POSITION = "CLOSE_POSITION", - MOVE_POSITION = "MOVE_POSITION", + MOVE_POSITION_WITHDRAW = "MOVE_POSITION_WITHDRAW", + MOVE_POSITION_DEPOSIT = "MOVE_POSITION_DEPOSIT", WITHDRAW_ALL = "WITHDRAW_ALL", TRANSFER_AUTH = "TRANSFER_AUTH", @@ -87,11 +88,15 @@ export const TransactionConfigMap: Record = }, [TransactionType.LONG]: { label: ({ depositToken, depositAmount, borrowToken } = {}) => - depositToken && depositAmount && borrowToken ? `Longing ${depositToken} with ${depositAmount} ${borrowToken}` : "Opening Long position", + depositToken && depositAmount && borrowToken + ? `Longing ${depositToken} with ${depositAmount} ${borrowToken}` + : "Opening Long position", }, [TransactionType.SHORT]: { label: ({ borrowToken, depositAmount, depositToken } = {}) => - borrowToken && depositAmount && depositToken ? `Shorting ${borrowToken} with ${depositAmount} ${depositToken}` : "Opening Short position", + borrowToken && depositAmount && depositToken + ? `Shorting ${borrowToken} with ${depositAmount} ${depositToken}` + : "Opening Short position", }, // SWB @@ -110,15 +115,22 @@ export const TransactionConfigMap: Record = // ACCOUNT MANAGEMENT [TransactionType.CLOSE_ACCOUNT]: { label: () => "Closing Account" }, [TransactionType.CLOSE_POSITION]: { label: () => "Closing Position" }, - [TransactionType.MOVE_POSITION]: { label: () => "Moving Position" }, + [TransactionType.MOVE_POSITION_WITHDRAW]: { + label: ({ originAccountAddress } = {}) => `Withdrawing from ${originAccountAddress}`, + }, + [TransactionType.MOVE_POSITION_DEPOSIT]: { + label: ({ destinationAccountAddress } = {}) => `Depositing to ${destinationAccountAddress}`, + }, [TransactionType.TRANSFER_AUTH]: { label: () => "Transferring Account Authorization" }, // NATIVE STAKE ACTIONS [TransactionType.DEPOSIT_STAKE]: { - label: ({ amount, token } = {}) => (amount && token ? `Staking and depositing ${amount} ${token}` : "Staking and depositing"), + label: ({ amount, token } = {}) => + amount && token ? `Staking and depositing ${amount} ${token}` : "Staking and depositing", }, [TransactionType.WITHDRAW_STAKE]: { - label: ({ amount, token } = {}) => (amount && token ? `Unstaking and withdrawing ${amount} ${token}` : "Unstaking and withdrawing"), + label: ({ amount, token } = {}) => + amount && token ? `Unstaking and withdrawing ${amount} ${token}` : "Unstaking and withdrawing", }, [TransactionType.INITIALIZE_STAKED_POOL]: { label: () => "Initializing Staked Pool" }, [TransactionType.ADD_STAKED_BANK]: { label: () => "Adding Staked Bank" }, @@ -127,10 +139,11 @@ export const TransactionConfigMap: Record = [TransactionType.STAKE_TO_STAKE]: { label: () => "Converting Staked Token" }, [TransactionType.MINT_LST_NATIVE]: { label: () => "Minting Liquid Staked Native Token" }, [TransactionType.SWAP_TO_SOL]: { - label: ({ swapAmount, token } = {}) => (swapAmount && token ? `Swapping ${swapAmount} ${token} to SOL` : "Swapping to SOL"), + label: ({ swapAmount, token } = {}) => + swapAmount && token ? `Swapping ${swapAmount} ${token} to SOL` : "Swapping to SOL", }, [TransactionType.SOL_TO_LST]: { - label: ({ amount } = {}) => (amount ? `Minting LST with ${amount} SOL` : "Minting LST with SOL"), + label: ({ amount } = {}) => (amount ? `Minting LST with ${amount} SOL` : "Minting LST with SOL"), }, // EMISSIONS @@ -166,8 +179,8 @@ export type ExtendedTransactionProperties = { unitsConsumed?: number; }; -export type ExtendedTransaction = Transaction & ExtendedTransactionProperties +export type ExtendedTransaction = Transaction & ExtendedTransactionProperties; -export type ExtendedV0Transaction = VersionedTransaction & ExtendedTransactionProperties +export type ExtendedV0Transaction = VersionedTransaction & ExtendedTransactionProperties; export type SolanaTransaction = ExtendedTransaction | ExtendedV0Transaction; diff --git a/packages/mrgn-toasts/src/components/toasts/multi-step-toast/multi-step-toast.tsx b/packages/mrgn-toasts/src/components/toasts/multi-step-toast/multi-step-toast.tsx index b7f962903..86587e8f6 100644 --- a/packages/mrgn-toasts/src/components/toasts/multi-step-toast/multi-step-toast.tsx +++ b/packages/mrgn-toasts/src/components/toasts/multi-step-toast/multi-step-toast.tsx @@ -1,6 +1,15 @@ import { IconLoader2, IconCheck, IconExternalLink, IconX } from "@tabler/icons-react"; import { shortenAddress } from "@mrgnlabs/mrgn-common"; -import { MultiStepToastStep, ToastStatus } from "../../../utils"; +import { MultiStepToastStep } from "~/utils"; + +enum ToastStatus { + TODO = "todo", + PENDING = "pending", + SUCCESS = "success", + ERROR = "error", + CANCELED = "canceled", + PAUSED = "paused", +} // TODO: remove this and fix import interface MultiStepToastProps { toastId: string; @@ -9,6 +18,7 @@ interface MultiStepToastProps { } export function MultiStepToast({ title, steps }: MultiStepToastProps) { + console.log(ToastStatus); const lastFailedIndex = steps.map((s) => s.status).lastIndexOf(ToastStatus.ERROR); return ( diff --git a/packages/mrgn-toasts/src/utils/toast-manager.tsx b/packages/mrgn-toasts/src/utils/toast-manager.tsx index 5f0412f0b..cc39e858b 100644 --- a/packages/mrgn-toasts/src/utils/toast-manager.tsx +++ b/packages/mrgn-toasts/src/utils/toast-manager.tsx @@ -21,7 +21,6 @@ export interface MultiStepToastStep { onRetry?: () => void; } - export interface MultiStepToastController { start: () => void; successAndNext: (stepsToAdvance?: number | undefined, explorerUrl?: string, signature?: string) => void; @@ -38,18 +37,19 @@ class ToastManager { toast(, { duration: Infinity }); } - showErrorToast(props: string | any) { // TODO: this should be actionMessageType + showErrorToast(props: string | any) { + // TODO: this should be actionMessageType let description: string; - let code: number | undefined; - if (typeof props === "string") { - description = props; - code = undefined; - } else { - description = props.description || ""; - code = props.code; - } - - toast(, { + let code: number | undefined; + if (typeof props === "string") { + description = props; + code = undefined; + } else { + description = props.description || ""; + code = props.code; + } + + toast(, { duration: Infinity, }); } @@ -76,43 +76,43 @@ class ToastManager { const ToastController: MultiStepToastController = { start: () => { - updateToast(); + updateToast(); }, - successAndNext: (stepsToAdvance: number | undefined , explorerUrl?: string, signature?: string) => { - if (!toastId) return; + successAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => { + if (!toastId) return; - const currentIndex = stepsWithStatus.findIndex((s) => s.status === ToastStatus.PENDING); - if (currentIndex === -1) return; + const currentIndex = stepsWithStatus.findIndex((s) => s.status === ToastStatus.PENDING); + if (currentIndex === -1) return; - stepsWithStatus[currentIndex] = { - ...stepsWithStatus[currentIndex], - status: ToastStatus.SUCCESS, - explorerUrl, - signature, - }; + stepsWithStatus[currentIndex] = { + ...stepsWithStatus[currentIndex], + status: ToastStatus.SUCCESS, + explorerUrl, + signature, + }; - const advanceSteps = stepsToAdvance ?? 1; - const nextStepIndex = currentIndex + advanceSteps; + const advanceSteps = stepsToAdvance ?? 1; + const nextStepIndex = currentIndex + advanceSteps; - if (nextStepIndex >= stepsWithStatus.length) { - for (let i = currentIndex + 1; i < stepsWithStatus.length; i++) { - stepsWithStatus[i].status = ToastStatus.SUCCESS; - } + if (nextStepIndex >= stepsWithStatus.length) { + for (let i = currentIndex + 1; i < stepsWithStatus.length; i++) { + stepsWithStatus[i].status = ToastStatus.SUCCESS; + } - updateToast(); + updateToast(); - setTimeout(() => toast.dismiss(toastId), 5000); - } else { - for (let i = currentIndex + 1; i <= nextStepIndex; i++) { - if (stepsWithStatus[i]) { - stepsWithStatus[i].status = ToastStatus.PENDING; - } - } + setTimeout(() => toast.dismiss(toastId), 5000); + } else { + for (let i = currentIndex + 1; i <= nextStepIndex; i++) { + if (stepsWithStatus[i]) { + stepsWithStatus[i].status = ToastStatus.PENDING; + } + } - updateToast(); - } -}, + updateToast(); + } + }, success: (explorerUrl?: string, signature?: string) => { stepsWithStatus.forEach((s, index) => { diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/deposit-swap-box/deposit-swap-box.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/deposit-swap-box/deposit-swap-box.tsx index ecfb96192..7afc29080 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/deposit-swap-box/deposit-swap-box.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/deposit-swap-box/deposit-swap-box.tsx @@ -57,9 +57,7 @@ export type DepositSwapBoxProps = { walletTokens: WalletToken[] | null; allBanks?: ExtendedBankInfo[]; - onComplete?: (infoProps: { - walletToken?: WalletToken, - }) => void; + onComplete?: (infoProps: { walletToken?: WalletToken }) => void; captureEvent?: (event: string, properties?: Record) => void; setDisplaySettings?: (displaySettings: boolean) => void; }; @@ -133,7 +131,7 @@ export const DepositSwapBox = ({ return getBankOrWalletTokenByPk(banks, walletTokens, selectedSwapBankPk); }, [banks, walletTokens, selectedSwapBankPk]); - const [isSimulating, setIsSimulating] = React.useState<{ + const [simulationStatus, setSimulationStatus] = React.useState<{ isLoading: boolean; status: SimulationStatus; }>({ @@ -209,7 +207,7 @@ export const DepositSwapBox = ({ setSimulationResult, setActionTxns, setErrorMessage, - setIsLoading: setIsSimulating, + setIsLoading: setSimulationStatus, marginfiClient, isDisabled: actionMessages.some((message) => !message.isEnabled), }); @@ -286,32 +284,49 @@ export const DepositSwapBox = ({ ]); const handleDepositSwapAction = React.useCallback(() => { - if (!marginfiClient || actionTxns.transactions.length === 0) return; // TODO: see if this is ok enough, might need more checks + if (!actionTxns || !marginfiClient || !debouncedAmount || debouncedAmount === 0 || !transactionSettings) { + console.error("Missing required props for ExecuteDepositSwapAction"); + return; + } + + const depositAmount = !!actionTxns.actionQuote + ? dynamicNumeralFormatter( + Number( + nativeToUi(Number(actionTxns.actionQuote?.outAmount), selectedDepositBank?.info.rawBank.mintDecimals ?? 9) + ) + ) + : dynamicNumeralFormatter(debouncedAmount ?? 0); - const props: ExecuteDepositSwapActionProps = { + const swapTokenSymbol = selectedSwapBank + ? "info" in selectedSwapBank + ? selectedSwapBank.meta.tokenSymbol + : selectedSwapBank.symbol + : ""; + + const props = { actionTxns, attemptUuid: uuidv4(), marginfiClient, processOpts: { ...priorityFees, - broadcastType: transactionSettings?.broadcastType, + broadcastType: transactionSettings.broadcastType, }, txOpts: {}, callbacks: { - captureEvent: captureEvent , + captureEvent: captureEvent, onComplete: () => { onComplete?.({ - walletToken: selectedSwapBank && "info" in selectedSwapBank ? undefined : selectedSwapBank ?? undefined , - }) - } , + walletToken: selectedSwapBank && "info" in selectedSwapBank ? undefined : (selectedSwapBank ?? undefined), + }); + }, }, infoProps: { - depositToken: selectedDepositBank?.meta.tokenSymbol ??'', - swapToken: selectedSwapBank ? "info" in selectedSwapBank ? selectedSwapBank.meta.tokenSymbol : selectedSwapBank.symbol : "", - depositAmount: dynamicNumeralFormatter(Number(actionTxns.actionQuote?.outAmount) ? Number(nativeToUi(Number(actionTxns.actionQuote?.outAmount), selectedDepositBank?.info.rawBank.mintDecimals ?? 9)) :debouncedAmount ?? 0), + depositToken: selectedDepositBank?.meta.tokenSymbol ?? "", + swapToken: swapTokenSymbol, + depositAmount, swapAmount: dynamicNumeralFormatter(debouncedAmount ?? 0), }, - } + }; ExecuteDepositSwapAction(props); setAmountRaw(""); @@ -437,7 +452,7 @@ export const DepositSwapBox = ({ ) @@ -451,7 +466,7 @@ export const DepositSwapBox = ({
value.isEnabled === false).length } @@ -465,7 +480,7 @@ export const DepositSwapBox = ({
0} isActive={selectedDepositBank && amount > 0 ? true : false} /> @@ -475,7 +490,7 @@ export const DepositSwapBox = ({ 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 ae51396bd..a7936d593 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,7 +4,13 @@ import { Connection, PublicKey } from "@solana/web3.js"; import { AccountSummary, ActionType, ExtendedBankInfo } from "@mrgnlabs/marginfi-v2-ui-state"; import { MarginfiAccountWrapper, MarginfiClient, SimulationResult } from "@mrgnlabs/marginfi-client-v2"; -import { ActionMessageType, usePrevious, STATIC_SIMULATION_ERRORS, ActionTxns, extractErrorString } from "@mrgnlabs/mrgn-utils"; +import { + ActionMessageType, + usePrevious, + STATIC_SIMULATION_ERRORS, + ActionTxns, + extractErrorString, +} from "@mrgnlabs/mrgn-utils"; import { calculateSummary, generateActionTxns, getSimulationResult } from "../utils"; import { SimulationStatus } from "~/components/action-box-v2/utils"; @@ -12,7 +18,7 @@ import { SimulationStatus } from "~/components/action-box-v2/utils"; type LendSimulationProps = { debouncedAmount: number; selectedAccount: MarginfiAccountWrapper | null; - marginfiClient?: MarginfiClient | null; + marginfiClient: MarginfiClient | null; accountSummary?: AccountSummary; selectedBank: ExtendedBankInfo | null; lendMode: ActionType; @@ -70,20 +76,19 @@ export function useLendSimulation({ }; const simulationAction = async (props: { - txns: ActionTxns, - lendMode: ActionType - account: MarginfiAccountWrapper - bank: ExtendedBankInfo - amount: number - } ) => { - - if (props.txns.transactions.length > 0) { - const simulationResult = await getSimulationResult({ - actionMode: props.lendMode, - account: props.account, - bank: props.bank, - amount: props.amount, - txns: props.txns.transactions + txns: ActionTxns; + lendMode: ActionType; + account: MarginfiAccountWrapper; + bank: ExtendedBankInfo; + amount: number; + }) => { + if (props.txns.transactions.length > 0) { + const simulationResult = await getSimulationResult({ + actionMode: props.lendMode, + account: props.account, + bank: props.bank, + amount: props.amount, + txns: props.txns.transactions, }); if (simulationResult.actionMethod) { @@ -91,24 +96,25 @@ export function useLendSimulation({ } else if (simulationResult.simulationResult) { return { simulationResult: simulationResult.simulationResult, actionMessage: null }; } else { - const errorMessage = STATIC_SIMULATION_ERRORS.DEPOSIT_FAILED; + const errorMessage = STATIC_SIMULATION_ERRORS.DEPOSIT_FAILED; return { simulationResult: null, actionMessage: errorMessage }; } + } else { + throw new Error("account, bank or transactions are null"); // TODO: return error message? } - else { - throw new Error("account, bank or transactions are null"); // TODO: return error message? - } - } - + }; const fetchActionTxns = async (props: { - marginfiAccount: MarginfiAccountWrapper | null, - marginfiClient: MarginfiClient , - bank: ExtendedBankInfo, - lendMode: ActionType, - stakeAccount?: PublicKey, - amount: number, - } ): Promise<{ txns:{ actionTxns: ActionTxns , finalAccount: MarginfiAccountWrapper } | null; actionMessage: ActionMessageType | null }> => { + marginfiAccount: MarginfiAccountWrapper | null; + marginfiClient: MarginfiClient; + bank: ExtendedBankInfo; + lendMode: ActionType; + stakeAccount?: PublicKey; + amount: number; + }): Promise<{ + txns: { actionTxns: ActionTxns; finalAccount: MarginfiAccountWrapper } | null; + actionMessage: ActionMessageType | null; + }> => { try { const _actionTxns = await generateActionTxns(props); @@ -120,10 +126,10 @@ export function useLendSimulation({ }, actionMessage: null, }; - } else { + } else { const errorMessage = _actionTxns ?? STATIC_SIMULATION_ERRORS.DEPOSIT_FAILED; return { - txns:null, + txns: null, actionMessage: errorMessage, }; } @@ -134,75 +140,83 @@ export function useLendSimulation({ actionMessage: STATIC_SIMULATION_ERRORS.DEPOSIT_FAILED, }; } + }; - } + const handleSimulation = React.useCallback( + async (amount: number) => { + try { + if (amount === 0 || !selectedBank || !marginfiClient) { + // Selected account can be undefined, we'll make a tx for this if so + console.error("Missing params"); + setActionTxns({ transactions: [] }); + return; + } - const handleSimulation = React.useCallback(async (amount: number) => { - try { - if (amount === 0 || !selectedBank || !marginfiClient) { // Selected account can be undefined, we'll make a tx for this if so - console.error('Missing params') - setActionTxns({ transactions: [] }); - return; - } - - setIsLoading({ isLoading: true, status: SimulationStatus.SIMULATING }); + setIsLoading({ isLoading: true, status: SimulationStatus.SIMULATING }); - const props = { - marginfiAccount: selectedAccount, - marginfiClient: marginfiClient, - stakeAccount: selectedStakeAccount, - bank: selectedBank, - lendMode: lendMode, - amount: amount, - } + const props = { + marginfiAccount: selectedAccount, + marginfiClient: marginfiClient, + stakeAccount: selectedStakeAccount, + bank: selectedBank, + lendMode: lendMode, + amount: amount, + }; - const _actionTxns = await fetchActionTxns(props); + const _actionTxns = await fetchActionTxns(props); - if (_actionTxns.actionMessage || _actionTxns.txns?.actionTxns === undefined) { - handleError(_actionTxns.actionMessage ?? STATIC_SIMULATION_ERRORS.DEPOSIT_FAILED, { - setErrorMessage, - setSimulationResult, - setActionTxns, - setIsLoading, - }); - return; - } + if (_actionTxns.actionMessage || _actionTxns.txns?.actionTxns === undefined) { + handleError(_actionTxns.actionMessage ?? STATIC_SIMULATION_ERRORS.DEPOSIT_FAILED, { + setErrorMessage, + setSimulationResult, + setActionTxns, + setIsLoading, + }); + return; + } - const simulationResult = await simulationAction({ - txns: _actionTxns.txns?.actionTxns, - lendMode: lendMode, - account: _actionTxns.txns?.finalAccount, - bank: selectedBank, - amount: amount, - }) - - if (simulationResult.actionMessage || simulationResult.simulationResult === null) { - handleError(simulationResult.actionMessage ?? STATIC_SIMULATION_ERRORS.DEPOSIT_FAILED, { - setErrorMessage, - setSimulationResult, - setActionTxns, - setIsLoading, + const simulationResult = await simulationAction({ + txns: _actionTxns.txns?.actionTxns, + lendMode: lendMode, + account: _actionTxns.txns?.finalAccount, + bank: selectedBank, + amount: amount, }); - return; - } else if (simulationResult.simulationResult) { - setSimulationResult(simulationResult.simulationResult); - setActionTxns(_actionTxns.txns?.actionTxns); - setErrorMessage(null); - } else { - throw new Error("Unknown error"); // TODO: return error message? - } - } - - catch (error) { + if (simulationResult.actionMessage || simulationResult.simulationResult === null) { + handleError(simulationResult.actionMessage ?? STATIC_SIMULATION_ERRORS.DEPOSIT_FAILED, { + setErrorMessage, + setSimulationResult, + setActionTxns, + setIsLoading, + }); + return; + } else if (simulationResult.simulationResult) { + setSimulationResult(simulationResult.simulationResult); + setActionTxns(_actionTxns.txns?.actionTxns); + setErrorMessage(null); + } else { + throw new Error("Unknown error"); // TODO: return error message? + } + } catch (error) { console.error("Error simulating transaction", error); setSimulationResult(null); } finally { setIsLoading({ isLoading: false, status: SimulationStatus.COMPLETE }); } - - - }, [lendMode, marginfiClient, selectedAccount, selectedBank, selectedStakeAccount, setActionTxns, setErrorMessage, setIsLoading, setSimulationResult]); + }, + [ + lendMode, + marginfiClient, + selectedAccount, + selectedBank, + selectedStakeAccount, + setActionTxns, + setErrorMessage, + setIsLoading, + setSimulationResult, + ] + ); const refreshSimulation = React.useCallback(async () => { if (debouncedAmount > 0) { @@ -225,15 +239,12 @@ export function useLendSimulation({ ); React.useEffect(() => { - if ( - prevDebouncedAmount !== debouncedAmount - ) { + if (prevDebouncedAmount !== debouncedAmount) { if (debouncedAmount > 0) { handleSimulation(debouncedAmount); } } - }, [debouncedAmount, handleSimulation, prevDebouncedAmount, ]); - + }, [debouncedAmount, handleSimulation, prevDebouncedAmount]); const actionSummary = React.useMemo(() => { return handleActionSummary(accountSummary, simulationResult ?? undefined); @@ -241,6 +252,6 @@ export function useLendSimulation({ return { actionSummary, - refreshSimulation + refreshSimulation, }; } 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 2d664c411..8d3393a4a 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 @@ -171,7 +171,7 @@ export const LendBox = ({ }, [searchMode, _prevSearchMode]); const [isTransactionExecuting, setIsTransactionExecuting] = React.useState(false); - const [isSimulating, setIsSimulating] = React.useState<{ + const [simulationStatus, setSimulationStatus] = React.useState<{ isLoading: boolean; status: SimulationStatus; }>({ @@ -180,8 +180,8 @@ export const LendBox = ({ }); const isLoading = React.useMemo( - () => isTransactionExecuting || isSimulating.isLoading, - [isTransactionExecuting, isSimulating.isLoading] + () => isTransactionExecuting || simulationStatus.isLoading, + [isTransactionExecuting, simulationStatus.isLoading] ); const { transactionSettings, priorityFees } = useActionContext() || { transactionSettings: null, priorityFees: null }; @@ -211,7 +211,7 @@ export const LendBox = ({ setSimulationResult, setActionTxns, setErrorMessage, - setIsLoading: setIsSimulating, + setIsLoading: setSimulationStatus, marginfiClient: marginfiClient, }); @@ -290,8 +290,7 @@ export const LendBox = ({ }, [prevSelectedBank, prevAmount, selectedBank, amount, setErrorMessage]); const handleLendingAction = React.useCallback(() => { - - if (!selectedBank || !amount || !transactionSettings || !marginfiClient) return + if (!selectedBank || !amount || !transactionSettings || !marginfiClient) return; const props: ExecuteLendingActionProps = { actionTxns, @@ -309,12 +308,24 @@ export const LendBox = ({ }, nativeSolBalance: nativeSolBalance, actionType: lendMode, - } ; + }; ExecuteLendingAction(props); - setAmountRaw("") - }, [actionTxns, amount, captureEvent, lendMode, marginfiClient, nativeSolBalance, priorityFees, selectedBank, setAmountRaw, transactionSettings, onComplete]) + setAmountRaw(""); + }, [ + actionTxns, + amount, + captureEvent, + lendMode, + marginfiClient, + nativeSolBalance, + priorityFees, + selectedBank, + setAmountRaw, + transactionSettings, + onComplete, + ]); const hasErrorsWarnings = React.useMemo(() => { return ( @@ -400,7 +411,7 @@ export const LendBox = ({
) @@ -420,13 +431,12 @@ export const LendBox = ({ handleAction={() => { handleLendingAction(); }} - buttonLabel={buttonLabel} />
0 ? true : false} /> 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 d306ec397..2837ad609 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 @@ -127,7 +127,7 @@ export const LoopBox = ({ jupiterOptions: null, }; - const [isSimulating, setIsSimulating] = React.useState<{ + const [simulationStatus, setSimulationStatus] = React.useState<{ isLoading: boolean; status: SimulationStatus; }>({ @@ -190,7 +190,7 @@ export const LoopBox = ({ setSimulationResult, setActionTxns, setErrorMessage, - setIsLoading: setIsSimulating, + setIsLoading: setSimulationStatus, actionMessages: actionMessages, }); @@ -258,9 +258,8 @@ export const LoopBox = ({ // Looping Actions // ///////////////////// const handleLoopAction = React.useCallback(async () => { - if (!selectedBank || !amount || !marginfiClient || !selectedSecondaryBank || !transactionSettings) { - return + return; } const params: ExecuteLoopActionProps = { @@ -278,14 +277,26 @@ export const LoopBox = ({ depositToken: selectedBank.meta.tokenSymbol, borrowAmount: dynamicNumeralFormatter(actionTxns.borrowAmount.toNumber()), borrowToken: selectedSecondaryBank.meta.tokenSymbol, - }, + }, nativeSolBalance: nativeSolBalance, - } + }; - ExecuteLoopAction(params) + ExecuteLoopAction(params); - setAmountRaw("") - }, [actionTxns, amount, captureEvent, marginfiClient, priorityFees, selectedBank, selectedSecondaryBank, setAmountRaw, transactionSettings, nativeSolBalance, onComplete]) + setAmountRaw(""); + }, [ + actionTxns, + amount, + captureEvent, + marginfiClient, + priorityFees, + selectedBank, + selectedSecondaryBank, + setAmountRaw, + transactionSettings, + nativeSolBalance, + onComplete, + ]); React.useEffect(() => { if (marginfiClient) { @@ -332,7 +343,7 @@ export const LoopBox = ({ setSelectedSecondaryBank={(bank) => { setSelectedSecondaryBank(bank); }} - isLoading={isSimulating.isLoading} + isLoading={simulationStatus.isLoading} walletAmount={walletAmount} actionTxns={actionTxns} /> @@ -366,7 +377,7 @@ export const LoopBox = ({
) @@ -374,7 +385,7 @@ export const LoopBox = ({
0} isActive={selectedBank && amount > 0 ? true : false} actionType={ActionType.Loop} @@ -400,7 +411,7 @@ export const LoopBox = ({ {setDisplaySettings && setDisplaySettings(true)} />}
- + ); }; diff --git a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-box/repay-box.tsx b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-box/repay-box.tsx index 24e417aab..b7e23ac5d 100644 --- a/packages/mrgn-ui/src/components/action-box-v2/actions/repay-box/repay-box.tsx +++ b/packages/mrgn-ui/src/components/action-box-v2/actions/repay-box/repay-box.tsx @@ -123,7 +123,7 @@ export const RepayBox = ({ ]); const [isTransactionExecuting, setIsTransactionExecuting] = React.useState(false); - const [isSimulating, setIsSimulating] = React.useState<{ + const [simulationStatus, setSimulationStatus] = React.useState<{ isLoading: boolean; status: SimulationStatus; }>({ @@ -132,8 +132,8 @@ export const RepayBox = ({ }); const isLoading = React.useMemo( - () => isTransactionExecuting || isSimulating.isLoading, - [isTransactionExecuting, isSimulating.isLoading] + () => isTransactionExecuting || simulationStatus.isLoading, + [isTransactionExecuting, simulationStatus.isLoading] ); const { transactionSettings, priorityFees, jupiterOptions } = useActionContext() || { @@ -146,9 +146,7 @@ export const RepayBox = ({ actionTxns?.lastValidBlockHeight ); - const [ platformFeeBps] = useActionBoxStore((state) => [ - state.platformFeeBps, - ]); + const [platformFeeBps] = useActionBoxStore((state) => [state.platformFeeBps]); const accountSummary = React.useMemo(() => { return ( @@ -193,7 +191,7 @@ export const RepayBox = ({ setActionTxns, setErrorMessage, setRepayAmount, - setIsLoading: setIsSimulating, + setIsLoading: setSimulationStatus, setMaxAmountCollateral, setMaxOverflowHit, }); @@ -255,15 +253,15 @@ export const RepayBox = ({ const handleRepayAction = React.useCallback(async () => { if ( - !marginfiClient || - !selectedAccount || - !marginfiClient.provider.connection || - !transactionSettings || - !selectedBank || - !selectedSecondaryBank - ) { - return; - } + !marginfiClient || + !selectedAccount || + !marginfiClient.provider.connection || + !transactionSettings || + !selectedBank || + !selectedSecondaryBank + ) { + return; + } const params: ExecuteRepayActionProps = { actionTxns, @@ -272,22 +270,36 @@ export const RepayBox = ({ processOpts: { ...priorityFees, broadcastType: transactionSettings.broadcastType }, txOpts: {}, callbacks: { - captureEvent: captureEvent, - onComplete: onComplete, - }, - actionType: actionMode, - infoProps: { - repayAmount: dynamicNumeralFormatter(repayAmount), - repayToken: selectedSecondaryBank.meta.tokenSymbol, - amount: dynamicNumeralFormatter(amount), - token: selectedBank.meta.tokenSymbol, - } - } + captureEvent: captureEvent, + onComplete: onComplete, + }, + actionType: actionMode, + infoProps: { + repayAmount: dynamicNumeralFormatter(repayAmount), + repayToken: selectedSecondaryBank.meta.tokenSymbol, + amount: dynamicNumeralFormatter(amount), + token: selectedBank.meta.tokenSymbol, + }, + }; ExecuteRepayAction(params); setAmountRaw(""); - }, [actionMode, actionTxns, amount, captureEvent, marginfiClient, priorityFees, repayAmount, selectedAccount, selectedBank, selectedSecondaryBank, setAmountRaw, transactionSettings, onComplete]); + }, [ + actionMode, + actionTxns, + amount, + captureEvent, + marginfiClient, + priorityFees, + repayAmount, + selectedAccount, + selectedBank, + selectedSecondaryBank, + setAmountRaw, + transactionSettings, + onComplete, + ]); return ( @@ -341,7 +353,7 @@ export const RepayBox = ({ ) @@ -369,7 +381,7 @@ export const RepayBox = ({
0} isActive={selectedBank && amount > 0 ? true : false} /> 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 5465c6ed4..41a483045 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 @@ -19,7 +19,12 @@ import { import { useActionAmounts } from "~/components/action-box-v2/hooks"; import { WalletContextStateOverride } from "~/components/wallet-v2/hooks/use-wallet.hook"; import { ActionMessage } from "~/components"; -import { ActionBoxContentWrapper, ActionButton, ActionSettingsButton , ActionSimulationStatus} from "~/components/action-box-v2/components"; +import { + ActionBoxContentWrapper, + ActionButton, + ActionSettingsButton, + ActionSimulationStatus, +} from "~/components/action-box-v2/components"; import { useStakeBoxStore } from "./store"; import { AmountPreview } from "./components/amount-preview"; @@ -98,7 +103,7 @@ export const StakeBox = ({ ]); const [isTransactionExecuting, setIsTransactionExecuting] = React.useState(false); - const [isSimulating, setIsSimulating] = React.useState<{ + const [simulationStatus, setSimulationStatus] = React.useState<{ isLoading: boolean; status: SimulationStatus; }>({ @@ -107,8 +112,8 @@ export const StakeBox = ({ }); const isLoading = React.useMemo( - () => isTransactionExecuting || isSimulating.isLoading, - [isTransactionExecuting, isSimulating.isLoading] + () => isTransactionExecuting || simulationStatus.isLoading, + [isTransactionExecuting, simulationStatus.isLoading] ); const { amount, debouncedAmount, walletAmount, maxAmount } = useActionAmounts({ @@ -155,7 +160,7 @@ export const StakeBox = ({ }; }, [refreshState]); - const { refreshSimulation } = useStakeSimulation({ + const { refreshSimulation } = useStakeSimulation({ debouncedAmount: debouncedAmount ?? 0, selectedBank, actionMode, @@ -165,7 +170,7 @@ export const StakeBox = ({ setSimulationResult, setActionTxns, setErrorMessage, - setIsLoading: setIsSimulating, + setIsLoading: setSimulationStatus, marginfiClient, lstData, }); @@ -227,16 +232,29 @@ export const StakeBox = ({ }, infoProps: { swapAmount: dynamicNumeralFormatter(amount), - amount: dynamicNumeralFormatter(actionTxns.actionQuote ? nativeToUi(Number(actionTxns?.actionQuote?.outAmount), 9 ) : amount), // Always sol as output so 9 decimals + amount: dynamicNumeralFormatter( + actionTxns.actionQuote ? nativeToUi(Number(actionTxns?.actionQuote?.outAmount), 9) : amount + ), // Always sol as output so 9 decimals token: selectedBank.meta.tokenSymbol, actionType: requestedActionType, - }, - } + }, + }; - ExecuteStakeAction(params) + ExecuteStakeAction(params); - setAmountRaw("") - }, [actionTxns, amount, captureEvent, marginfiClient, priorityFees, requestedActionType, selectedBank, setAmountRaw, transactionSettings, onComplete]) + setAmountRaw(""); + }, [ + actionTxns, + amount, + captureEvent, + marginfiClient, + priorityFees, + requestedActionType, + selectedBank, + setAmountRaw, + transactionSettings, + onComplete, + ]); React.useEffect(() => { fetchActionBoxState({ requestedLendType: requestedActionType, requestedBank }); @@ -273,7 +291,7 @@ export const StakeBox = ({ />
- +
{additionalActionMessages.concat(actionMessages).map( (actionMessage, idx) => @@ -282,7 +300,7 @@ export const StakeBox = ({ ) @@ -301,7 +319,7 @@ export const StakeBox = ({
0} isActive={selectedBank && amount > 0 ? true : false} /> @@ -328,4 +346,4 @@ export const StakeBox = ({
); -}; \ No newline at end of file +}; diff --git a/packages/mrgn-utils/src/actions/actions.ts b/packages/mrgn-utils/src/actions/actions.ts index 85590e00c..85dd8819d 100644 --- a/packages/mrgn-utils/src/actions/actions.ts +++ b/packages/mrgn-utils/src/actions/actions.ts @@ -3,9 +3,9 @@ import { SolanaJSONRPCError } from "@solana/web3.js"; import { TransactionConfigMap, TransactionOptions } from "@mrgnlabs/mrgn-common"; import { toastManager, MultiStepToastController } from "@mrgnlabs/mrgn-toasts"; import { MarginfiClient, ProcessTransactionsClientOpts, ProcessTransactionError } from "@mrgnlabs/marginfi-client-v2"; -import { ActionType, FEE_MARGIN } from "@mrgnlabs/marginfi-v2-ui-state"; +import { ActionType, FEE_MARGIN } from "@mrgnlabs/marginfi-v2-ui-state"; -import { ActionTxns, } from "./types"; +import { ActionTxns } from "./types"; import { composeExplorerUrl } from "./helpers"; import { STATIC_SIMULATION_ERRORS } from "../errors"; import { captureSentryException } from "../sentry.utils"; @@ -20,7 +20,7 @@ import { extractErrorString } from "../mrgnUtils"; */ // ------------------------------------------------------------------// -interface ExecuteActionProps { +export interface ExecuteActionProps { actionTxns: ActionTxns; attemptUuid: string; marginfiClient: MarginfiClient; @@ -31,14 +31,17 @@ interface ExecuteActionProps { onComplete?: () => void | null; }; nativeSolBalance?: number; -} +} function getSteps(actionTxns: ActionTxns, infoProps: Record) { return [ { label: "Signing Transaction" }, ...actionTxns.transactions.map((tx) => { + console.log("tx", tx.type); const config = TransactionConfigMap[tx.type]; + console.log("config", config); + const message = config.label(infoProps); if (config.fallback && message === config.fallback) { @@ -50,23 +53,22 @@ function getSteps(actionTxns: ActionTxns, infoProps: Record) { ]; } - async function executeActionWrapper(props: { action: ( txns: ActionTxns, onSuccessAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => void - ) => Promise, - steps: { label: string }[], - actionName: string, - txns: ActionTxns, - existingToast?: MultiStepToastController, - nativeSolBalance?: number, - onComplete?: () => void + ) => Promise; + steps: { label: string }[]; + actionName: string; + txns: ActionTxns; + existingToast?: MultiStepToastController; + nativeSolBalance?: number; + onComplete?: () => void; }) { const { action, steps, actionName, txns, existingToast, nativeSolBalance, onComplete } = props; if (nativeSolBalance && nativeSolBalance < FEE_MARGIN) { - toastManager.showErrorToast(STATIC_SIMULATION_ERRORS.INSUFICIENT_LAMPORTS); + toastManager.showErrorToast(STATIC_SIMULATION_ERRORS.INSUFICIENT_LAMPORTS); return; } @@ -86,7 +88,7 @@ async function executeActionWrapper(props: { return txnSig; } catch (error) { if (!(error instanceof ProcessTransactionError || error instanceof SolanaJSONRPCError)) { - captureSentryException(error, JSON.stringify(error), { action: actionName }) + captureSentryException(error, JSON.stringify(error), { action: actionName }); } if (error instanceof ProcessTransactionError) { @@ -98,7 +100,7 @@ async function executeActionWrapper(props: { transactions: error.failedTxs, }; toast.setFailed(message, async () => { - await executeActionWrapper({...props, txns: updatedFailedTxns, existingToast: toast}); + await executeActionWrapper({ ...props, txns: updatedFailedTxns, existingToast: toast }); }); } else { toast.setFailed(message); @@ -112,9 +114,6 @@ async function executeActionWrapper(props: { } } - - - export interface ExecuteDepositSwapActionProps extends ExecuteActionProps { infoProps: { depositToken: string; @@ -131,12 +130,16 @@ export async function ExecuteDepositSwapAction(props: ExecuteDepositSwapActionPr originToken: props.infoProps.swapToken, destinationToken: props.infoProps.depositToken, originAmount: props.infoProps.swapAmount, - destinationAmount: props.infoProps.depositAmount, + destinationAmount: props.infoProps.depositAmount, }); - props.callbacks.captureEvent && props.callbacks.captureEvent("user_deposit_swap_initiate", { uuid: props.attemptUuid, ...props.infoProps }); + props.callbacks.captureEvent && + props.callbacks.captureEvent("user_deposit_swap_initiate", { uuid: props.attemptUuid, ...props.infoProps }); - const action = async (txns: ActionTxns, onSuccessAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => void) => { + const action = async ( + txns: ActionTxns, + onSuccessAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => void + ) => { const actionResponse = await props.marginfiClient.processTransactions( txns.transactions, { @@ -151,10 +154,18 @@ export async function ExecuteDepositSwapAction(props: ExecuteDepositSwapActionPr return actionResponse[actionResponse.length - 1]; }; - await executeActionWrapper({action, steps, actionName: "Deposit", txns: props.actionTxns, nativeSolBalance: props.nativeSolBalance, onComplete: props.callbacks.onComplete}); + await executeActionWrapper({ + action, + steps, + actionName: "Deposit", + txns: props.actionTxns, + nativeSolBalance: props.nativeSolBalance, + onComplete: props.callbacks.onComplete, + }); - props.callbacks.captureEvent && props.callbacks.captureEvent("user_deposit_swap", { uuid: props.attemptUuid, ...props.infoProps }); -} // TODO: this does not handle create account. We should handle this. + props.callbacks.captureEvent && + props.callbacks.captureEvent("user_deposit_swap", { uuid: props.attemptUuid, ...props.infoProps }); +} // TODO: this does not handle create account. We should handle this. export interface ExecuteLendingActionProps extends ExecuteActionProps { actionType: ActionType; @@ -164,37 +175,45 @@ export interface ExecuteLendingActionProps extends ExecuteActionProps { }; } - export async function ExecuteLendingAction(props: ExecuteLendingActionProps) { - const steps = getSteps(props.actionTxns, { amount: props.infoProps.amount, token: props.infoProps.token, }); + console.log("steps", steps); + props.callbacks.captureEvent && + props.callbacks.captureEvent("user_lending_initiate", { uuid: props.attemptUuid, ...props.infoProps }); - props.callbacks.captureEvent && props.callbacks.captureEvent("user_lending_initiate", { uuid: props.attemptUuid, ...props.infoProps }); - - const action = async (txns: ActionTxns, onSuccessAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => void) => { - const actionResponse = await props.marginfiClient.processTransactions( - txns.transactions, - { - ...props.processOpts, - callback: (index, success, sig, stepsToAdvance) => { - success && onSuccessAndNext(stepsToAdvance, composeExplorerUrl(sig), sig); + const action = async ( + txns: ActionTxns, + onSuccessAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => void + ) => { + const actionResponse = await props.marginfiClient.processTransactions( + txns.transactions, + { + ...props.processOpts, + callback: (index, success, sig, stepsToAdvance) => { + success && onSuccessAndNext(stepsToAdvance, composeExplorerUrl(sig), sig); + }, }, - }, - props.txOpts - ); + props.txOpts + ); - return actionResponse[actionResponse.length - 1]; -}; + return actionResponse[actionResponse.length - 1]; + }; -await executeActionWrapper({action, steps, actionName: props.actionType, txns: props.actionTxns, nativeSolBalance: props.nativeSolBalance, onComplete: props.callbacks.onComplete}); + await executeActionWrapper({ + action, + steps, + actionName: props.actionType, + txns: props.actionTxns, + nativeSolBalance: props.nativeSolBalance, + onComplete: props.callbacks.onComplete, + }); - props.callbacks.captureEvent && props.callbacks.captureEvent("user_lending", { uuid: props.attemptUuid, ...props.infoProps }); - - + props.callbacks.captureEvent && + props.callbacks.captureEvent("user_lending", { uuid: props.attemptUuid, ...props.infoProps }); } export interface ExecuteLoopActionProps extends ExecuteActionProps { @@ -206,9 +225,7 @@ export interface ExecuteLoopActionProps extends ExecuteActionProps { }; } - export async function ExecuteLoopAction(props: ExecuteLoopActionProps) { - const steps = getSteps(props.actionTxns, { depositAmount: props.infoProps.depositAmount, depositToken: props.infoProps.depositToken, @@ -216,9 +233,13 @@ export async function ExecuteLoopAction(props: ExecuteLoopActionProps) { borrowToken: props.infoProps.borrowToken, }); - props.callbacks.captureEvent && props.callbacks.captureEvent("user_looping_initiate", { uuid: props.attemptUuid, ...props.infoProps }); + props.callbacks.captureEvent && + props.callbacks.captureEvent("user_looping_initiate", { uuid: props.attemptUuid, ...props.infoProps }); - const action = async (txns: ActionTxns, onSuccessAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => void) => { + const action = async ( + txns: ActionTxns, + onSuccessAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => void + ) => { const actionResponse = await props.marginfiClient.processTransactions( txns.transactions, { @@ -233,9 +254,17 @@ export async function ExecuteLoopAction(props: ExecuteLoopActionProps) { return actionResponse[actionResponse.length - 1]; }; - await executeActionWrapper({action, steps, actionName: "Looping", txns: props.actionTxns, nativeSolBalance: props.nativeSolBalance, onComplete: props.callbacks.onComplete}); + await executeActionWrapper({ + action, + steps, + actionName: "Looping", + txns: props.actionTxns, + nativeSolBalance: props.nativeSolBalance, + onComplete: props.callbacks.onComplete, + }); - props.callbacks.captureEvent && props.callbacks.captureEvent("user_looping", { uuid: props.attemptUuid, ...props.infoProps }); + props.callbacks.captureEvent && + props.callbacks.captureEvent("user_looping", { uuid: props.attemptUuid, ...props.infoProps }); } export interface ExecuteStakeActionProps extends ExecuteActionProps { @@ -247,18 +276,23 @@ export interface ExecuteStakeActionProps extends ExecuteActionProps { }; } - export async function ExecuteStakeAction(props: ExecuteStakeActionProps) { - const steps = getSteps(props.actionTxns, { amount: props.infoProps.amount, swapAmount: props.infoProps.swapAmount, token: props.infoProps.token, }); - props.callbacks.captureEvent && props.callbacks.captureEvent(`user_${props.infoProps.actionType}_initiate`, { uuid: props.attemptUuid, ...props.infoProps }); + props.callbacks.captureEvent && + props.callbacks.captureEvent(`user_${props.infoProps.actionType}_initiate`, { + uuid: props.attemptUuid, + ...props.infoProps, + }); - const action = async (txns: ActionTxns, onSuccessAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => void) => { + const action = async ( + txns: ActionTxns, + onSuccessAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => void + ) => { const actionResponse = await props.marginfiClient.processTransactions( txns.transactions, { @@ -273,10 +307,18 @@ export async function ExecuteStakeAction(props: ExecuteStakeActionProps) { return actionResponse[actionResponse.length - 1]; }; - await executeActionWrapper({action, steps, actionName: props.infoProps.actionType === ActionType.MintLST ? "Staking" : "Unstaking", txns: props.actionTxns, nativeSolBalance: props.nativeSolBalance, onComplete: props.callbacks.onComplete}); + await executeActionWrapper({ + action, + steps, + actionName: props.infoProps.actionType === ActionType.MintLST ? "Staking" : "Unstaking", + txns: props.actionTxns, + nativeSolBalance: props.nativeSolBalance, + onComplete: props.callbacks.onComplete, + }); - props.callbacks.captureEvent && props.callbacks.captureEvent(`user_${props.infoProps.actionType}`, { uuid: props.attemptUuid, ...props.infoProps }); -} + props.callbacks.captureEvent && + props.callbacks.captureEvent(`user_${props.infoProps.actionType}`, { uuid: props.attemptUuid, ...props.infoProps }); +} export interface ExecuteRepayActionProps extends ExecuteActionProps { actionType: ActionType; @@ -285,12 +327,10 @@ export interface ExecuteRepayActionProps extends ExecuteActionProps { repayToken: string; amount: string; token: string; - } + }; } - export async function ExecuteRepayAction(props: ExecuteRepayActionProps) { - const steps = getSteps(props.actionTxns, { repayAmount: props.infoProps.repayAmount, repayToken: props.infoProps.repayToken, @@ -298,9 +338,13 @@ export async function ExecuteRepayAction(props: ExecuteRepayActionProps) { token: props.infoProps.token, }); - props.callbacks.captureEvent && props.callbacks.captureEvent(`user_${props.actionType}_initiate`, { uuid: props.attemptUuid, ...props.infoProps }); + props.callbacks.captureEvent && + props.callbacks.captureEvent(`user_${props.actionType}_initiate`, { uuid: props.attemptUuid, ...props.infoProps }); - const action = async (txns: ActionTxns, onSuccessAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => void) => { + const action = async ( + txns: ActionTxns, + onSuccessAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => void + ) => { const actionResponse = await props.marginfiClient.processTransactions( txns.transactions, { @@ -315,12 +359,19 @@ export async function ExecuteRepayAction(props: ExecuteRepayActionProps) { return actionResponse[actionResponse.length - 1]; }; - await executeActionWrapper({action, steps, actionName: props.actionType, txns: props.actionTxns, nativeSolBalance: props.nativeSolBalance, onComplete: props.callbacks.onComplete}); + await executeActionWrapper({ + action, + steps, + actionName: props.actionType, + txns: props.actionTxns, + nativeSolBalance: props.nativeSolBalance, + onComplete: props.callbacks.onComplete, + }); - props.callbacks.captureEvent && props.callbacks.captureEvent(`user_${props.actionType}`, { uuid: props.attemptUuid, ...props.infoProps }); + props.callbacks.captureEvent && + props.callbacks.captureEvent(`user_${props.actionType}`, { uuid: props.attemptUuid, ...props.infoProps }); } - export interface ExecuteTradeActionProps extends ExecuteActionProps { infoProps: { depositAmount: string; @@ -328,11 +379,10 @@ export interface ExecuteTradeActionProps extends ExecuteActionProps { borrowAmount: string; borrowToken: string; tradeSide: "long" | "short"; - } + }; } export async function ExecuteTradeAction(props: ExecuteTradeActionProps) { - const steps = getSteps(props.actionTxns, { depositAmount: props.infoProps.depositAmount, depositToken: props.infoProps.depositToken, @@ -340,9 +390,13 @@ export async function ExecuteTradeAction(props: ExecuteTradeActionProps) { borrowToken: props.infoProps.borrowToken, }); - props.callbacks.captureEvent && props.callbacks.captureEvent("user_trade_initiate", { uuid: props.attemptUuid, ...props.infoProps }); + props.callbacks.captureEvent && + props.callbacks.captureEvent("user_trade_initiate", { uuid: props.attemptUuid, ...props.infoProps }); - const action = async (txns: ActionTxns, onSuccessAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => void) => { + const action = async ( + txns: ActionTxns, + onSuccessAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => void + ) => { const actionResponse = await props.marginfiClient.processTransactions( txns.transactions, { @@ -357,12 +411,20 @@ export async function ExecuteTradeAction(props: ExecuteTradeActionProps) { return actionResponse[actionResponse.length - 1]; }; - await executeActionWrapper({action, steps, actionName: props.infoProps.tradeSide === "long" ? "Longing" : "Shorting", txns: props.actionTxns, nativeSolBalance: props.nativeSolBalance, onComplete: props.callbacks.onComplete}); + await executeActionWrapper({ + action, + steps, + actionName: props.infoProps.tradeSide === "long" ? "Longing" : "Shorting", + txns: props.actionTxns, + nativeSolBalance: props.nativeSolBalance, + onComplete: props.callbacks.onComplete, + }); - props.callbacks.captureEvent && props.callbacks.captureEvent("user_trade", { uuid: props.attemptUuid, ...props.infoProps }); + props.callbacks.captureEvent && + props.callbacks.captureEvent("user_trade", { uuid: props.attemptUuid, ...props.infoProps }); } -// This function is tailor made for closing positions in the arena. +// This function is tailor made for closing positions in the arena. // This functionality slightly deviates from the general functionality, but is still similar to the point where it should live here export interface ExecuteClosePositionActionProps extends ExecuteActionProps { infoProps: { @@ -418,12 +480,98 @@ export async function ExecuteClosePositionAction(props: ExecuteClosePositionActi console.error("ExecuteClosePositionAction error:", error); const message = extractErrorString(error); - + multiStepToast.setFailed(message, async () => { // When retry is clicked, execute the function again. await ExecuteClosePositionAction(props); }); captureSentryException(error, JSON.stringify(error), { action: "Close Position" }); - } -} \ No newline at end of file + } +} + +export interface ExecuteMovePositionActionProps extends ExecuteActionProps { + infoProps: { + originAccountAddress: string; + destinationAccountAddress: string; + }; +} + +export async function ExecuteMovePositionAction(props: ExecuteMovePositionActionProps) { + const steps = getSteps(props.actionTxns, { + originAccountAddress: props.infoProps.originAccountAddress, + destinationAccountAddress: props.infoProps.destinationAccountAddress, + }); + + console.log("steps", steps); + + console.log("ExecuteMovePositionAction props", props); + + props.callbacks.captureEvent && + props.callbacks.captureEvent("user_move_position_initiate", { uuid: props.attemptUuid, ...props.infoProps }); + + const action = async ( + txns: ActionTxns, + onSuccessAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => void + ) => { + const actionResponse = await props.marginfiClient.processTransactions( + txns.transactions, + { + ...props.processOpts, + callback: (index, success, sig, stepsToAdvance) => { + if (success) { + onSuccessAndNext(stepsToAdvance, composeExplorerUrl(sig), sig); + } + }, + }, + props.txOpts + ); + + return actionResponse[actionResponse.length - 1]; + }; + + await executeActionWrapper({ + action, + steps, + actionName: "Moving Position", + txns: props.actionTxns, + nativeSolBalance: props.nativeSolBalance, + onComplete: props.callbacks.onComplete, + }); + + props.callbacks.captureEvent && + props.callbacks.captureEvent("user_move_position", { uuid: props.attemptUuid, ...props.infoProps }); +} + +export async function ExecuteCollectRewardsAction(props: ExecuteActionProps) { + const steps = getSteps(props.actionTxns, {}); + + const action = async ( + txns: ActionTxns, + onSuccessAndNext: (stepsToAdvance: number | undefined, explorerUrl?: string, signature?: string) => void + ) => { + const actionResponse = await props.marginfiClient.processTransactions( + txns.transactions, + { + ...props.processOpts, + callback: (index, success, sig, stepsToAdvance) => { + success && onSuccessAndNext(stepsToAdvance, composeExplorerUrl(sig), sig); + }, + }, + props.txOpts + ); + + return actionResponse[actionResponse.length - 1]; + }; + + await executeActionWrapper({ + action, + steps, + actionName: "Collecting Rewards", + txns: props.actionTxns, + nativeSolBalance: props.nativeSolBalance, + onComplete: props.callbacks.onComplete, + }); + + props.callbacks.captureEvent && props.callbacks.captureEvent("user_collect_rewards", { uuid: props.attemptUuid }); +}