diff --git a/screens/Trading/components/ClosePositionMobile.tsx b/screens/Trading/components/ClosePositionMobile.tsx index 6903ab5c..0ac9ecf7 100644 --- a/screens/Trading/components/ClosePositionMobile.tsx +++ b/screens/Trading/components/ClosePositionMobile.tsx @@ -6,7 +6,7 @@ import { useAppSelector } from "../../../redux/hooks"; import { Wrapper } from "../../../components/Modal/style"; import { DEFAULT_POSITION } from "../../../utils/config"; import { CloseIcon } from "../../../components/Modal/svg"; -import { RefLogoIcon, RightArrow, MaxPositionIcon } from "./TradingIcon"; +import { RightArrow, MaxPositionIcon } from "./TradingIcon"; import { toInternationalCurrencySystem_number, toDecimal } from "../../../utils/uiNumber"; import { closePosition } from "../../../store/marginActions/closePosition"; import { useEstimateSwap } from "../../../hooks/useEstimateSwap"; @@ -22,7 +22,6 @@ import { YellowSolidSubmitButton as YellowSolidButton, RedSolidSubmitButton as RedSolidButton, } from "../../../components/Modal/button"; -import { showPositionClose } from "../../../components/HashResultModal"; import { beautifyPrice } from "../../../utils/beautyNumber"; import { findPathReserve } from "../../../api/get-swap-path"; import { getAssets, getAssetsMEME } from "../../../redux/assetsSelectors"; @@ -30,7 +29,7 @@ import { useMarginAccount } from "../../../hooks/useMarginAccount"; import { IClosePositionMobileProps } from "../comInterface"; import { getMarginConfig, getMarginConfigMEME } from "../../../redux/marginConfigSelectors"; import { handleTransactionHash } from "../../../services/transaction"; -import DataSource from "../../../data/datasource"; +import { showPositionFailure } from "../../../components/HashResultModal"; import { useRegisterTokenType } from "../../../hooks/useRegisterTokenType"; export const ModalContext = createContext(null) as any; @@ -204,7 +203,10 @@ const ClosePositionMobile: React.FC = ({ await handleTransactionHash(transactionHashes); } } catch (error) { - console.error("Failed to close position:", error); + showPositionFailure({ + title: "Transactions error", + errorMessage: error instanceof Error ? error.message : JSON.stringify(error), + }); } finally { setIsDisabled(false); } diff --git a/screens/Trading/components/ConfirmMobile.tsx b/screens/Trading/components/ConfirmMobile.tsx index 9e1fe5c7..d5a9e5ad 100644 --- a/screens/Trading/components/ConfirmMobile.tsx +++ b/screens/Trading/components/ConfirmMobile.tsx @@ -16,10 +16,8 @@ import { } from "../../../components/Modal/button"; import { shrinkToken, expandToken } from "../../../store"; import { beautifyPrice } from "../../../utils/beautyNumber"; -import { getAccountId } from "../../../redux/accountSelectors"; import { handleTransactionHash } from "../../../services/transaction"; import { showPositionFailure } from "../../../components/HashResultModal"; -import { getBurrow } from "../../../utils"; import { getSymbolById } from "../../../transformers/nearSymbolTrans"; import { IConfirmMobileProps } from "../comInterface"; import { useRegisterTokenType } from "../../../hooks/useRegisterTokenType"; @@ -31,20 +29,6 @@ const ConfirmMobile: React.FC = ({ action, confirmInfo, }) => { - const [burrowData, setBurrowData] = useState<{ - selector?: { - wallet: () => Promise<{ id: string }>; - }; - } | null>(null); - - useEffect(() => { - const initBurrow = async () => { - const data: any = await getBurrow(); - setBurrowData(data); - }; - initBurrow(); - }, []); - const accountId = useAppSelector(getAccountId); const theme = useTheme(); const [selectedCollateralType, setSelectedCollateralType] = useState(DEFAULT_POSITION); const { ReduxcategoryAssets1 } = useAppSelector((state) => state.category); @@ -193,26 +177,17 @@ const ConfirmMobile: React.FC = ({ : minAmountOutForPopUp / confirmInfo.tokenInAmount, }), ); - - const wallet = await burrowData?.selector?.wallet(); - if (wallet?.id && ["my-near-wallet", "mintbase-wallet", "bitte-wallet"].includes(wallet.id)) { - await openPosition(openPositionParams); - return; - } - const res: any = await openPosition(openPositionParams); - if (!res || !Array.isArray(res)) { - throw new Error("Invalid response from openPosition"); + if (res) { + const transactionHashes = res.map((item) => { + if (!item?.transaction?.hash) { + throw new Error("Invalid transaction hash"); + } + return item.transaction.hash; + }); + await handleTransactionHash(transactionHashes); } - const transactionHashes = res.map((item) => { - if (!item?.transaction?.hash) { - throw new Error("Invalid transaction hash"); - } - return item.transaction.hash; - }); - await handleTransactionHash(transactionHashes); } catch (error) { - console.error("Open position error:", error); showPositionFailure({ title: "Transactions error", errorMessage: error instanceof Error ? error.message : JSON.stringify(error), diff --git a/services/transaction.ts b/services/transaction.ts index ad22d8b6..1203b38d 100644 --- a/services/transaction.ts +++ b/services/transaction.ts @@ -3,18 +3,20 @@ import { showPositionResult, showPositionClose, showChangeCollateralPosition, + showPositionFailure, } from "../components/HashResultModal"; import { store } from "../redux/store"; import DataSource from "../data/datasource"; +import { getErrorMessage } from "../utils/transactionUtils"; interface ITransactionResult { txHash: string; - result: any; isOpenPosition?: boolean; isCloseMTPosition?: boolean; isDecreaseCollateral?: boolean; isIncreaseCollateral?: boolean; action?: Record; + error?: string | null | undefined; } export const handleTransactionHash = async ( transactionHashes: string | string[] | undefined, @@ -48,22 +50,22 @@ export const handleTransactionHash = async ( : JSON.parse(parsed_Args.msg)?.MarginExecute?.actions?.[0]; return { txHash, - result, isOpenPosition: Reflect.has(action, "OpenPosition"), isCloseMTPosition: Reflect.has(action, "CloseMTPosition"), isDecreaseCollateral: Reflect.has(action, "DecreaseCollateral"), isIncreaseCollateral: Reflect.has(action, "IncreaseCollateral"), action, + error: getErrorMessage(result?.receipts_outcome), }; } return { txHash, - result, isOpenPosition: false, isCloseMTPosition: false, isDecreaseCollateral: false, isIncreaseCollateral: false, action: undefined, + error: undefined, }; }), ); @@ -73,8 +75,9 @@ export const handleTransactionHash = async ( const targetCollateralTx = results.find( (item) => item.isDecreaseCollateral || item.isIncreaseCollateral, ); + const hasErrorTx = results.find((item) => item.error); // Reporting transactions - if (targetPositionTx) { + if (targetPositionTx && !hasErrorTx) { const currentState = store.getState(); await DataSource.shared.postMarginTradingPosition({ addr: currentState?.account?.accountId, @@ -83,7 +86,12 @@ export const handleTransactionHash = async ( }); } // Show transactions pop - if (targetPositionTx?.isCloseMTPosition) { + if (hasErrorTx) { + showPositionFailure({ + title: "Transactions error", + errorMessage: hasErrorTx.error || "Unknown error", + }); + } else if (targetPositionTx?.isCloseMTPosition) { // close position const marginPopType = localStorage.getItem("marginPopType") as "Long" | "Short" | undefined; showPositionClose({ type: marginPopType || "Long" }); diff --git a/utils/transactionUtils.tsx b/utils/transactionUtils.tsx new file mode 100644 index 00000000..548865e1 --- /dev/null +++ b/utils/transactionUtils.tsx @@ -0,0 +1,90 @@ +export const ERROR_PATTERN = { + slippageErrorPattern: /ERR_MIN_AMOUNT|slippage error/i, + invaliParamsErrorPattern: /invalid params/i, + ratesExpiredErrorPattern: /Rates expired/i, + integerOverflowErrorPattern: /Integer overflow/i, + ShareSupplyOverflowErrorPattern: /shares_total_supply overflow/i, + tokenFrozenErrorPattern: /token frozen/i, + poolBalanceLessPattern: /pool reserved token balance less than MIN_RESERVE/i, + nethErrorPattern: /Smart contract panicked: explicit guest panic/i, +}; +export enum TRANSACTION_ERROR_TYPE { + SLIPPAGE_VIOLATION = "Slippage Violation", + INVALID_PARAMS = "Invalid Params", + RATES_EXPIRED = "Rates Expired", + INTEGEROVERFLOW = "Integer Overflow", + SHARESUPPLYOVERFLOW = "Share Supply Overflow", + TOKEN_FROZEN = "Token Frozen", + POOL_BALANCE_LESS = "Pool Balance Less Than MIN_RESERVE", + NETH_ERROR = "Smart contract panicked", +} + +export const getErrorMessage = (receipts_outcome: any) => { + const isSlippageError = receipts_outcome.some((outcome: any) => { + return ERROR_PATTERN.slippageErrorPattern.test( + outcome?.outcome?.status?.Failure?.ActionError?.kind?.FunctionCallError?.ExecutionError, + ); + }); + + const isInvalidAmountError = receipts_outcome.some((outcome: any) => { + return ERROR_PATTERN.invaliParamsErrorPattern.test( + outcome?.outcome?.status?.Failure?.ActionError?.kind?.FunctionCallError?.ExecutionError, + ); + }); + + const isRatesExpiredError = receipts_outcome.some((outcome: any) => { + return ERROR_PATTERN.ratesExpiredErrorPattern.test( + outcome?.outcome?.status?.Failure?.ActionError?.kind?.FunctionCallError?.ExecutionError, + ); + }); + + const isIntegerOverFlowError = receipts_outcome.some((outcome: any) => { + return ERROR_PATTERN.integerOverflowErrorPattern.test( + outcome?.outcome?.status?.Failure?.ActionError?.kind?.FunctionCallError?.ExecutionError, + ); + }); + + const isShareSupplyOerflowError = receipts_outcome.some((outcome: any) => { + return ERROR_PATTERN.ShareSupplyOverflowErrorPattern.test( + outcome?.outcome?.status?.Failure?.ActionError?.kind?.FunctionCallError?.ExecutionError, + ); + }); + + const isTokenFrozen = receipts_outcome.some((outcome: any) => { + return ERROR_PATTERN.tokenFrozenErrorPattern.test( + outcome?.outcome?.status?.Failure?.ActionError?.kind?.FunctionCallError?.ExecutionError, + ); + }); + + const isPoolBalanceLess = receipts_outcome.some((outcome: any) => { + return ERROR_PATTERN.poolBalanceLessPattern.test( + outcome?.outcome?.status?.Failure?.ActionError?.kind?.FunctionCallError?.ExecutionError, + ); + }); + + const isNETHErrpr = receipts_outcome.some((outcome: any) => { + return ERROR_PATTERN.nethErrorPattern.test( + outcome?.outcome?.status?.Failure?.ActionError?.kind?.FunctionCallError?.ExecutionError, + ); + }); + + if (isSlippageError) { + return TRANSACTION_ERROR_TYPE.SLIPPAGE_VIOLATION; + } else if (isInvalidAmountError) { + return TRANSACTION_ERROR_TYPE.INVALID_PARAMS; + } else if (isRatesExpiredError) { + return TRANSACTION_ERROR_TYPE.RATES_EXPIRED; + } else if (isIntegerOverFlowError) { + return TRANSACTION_ERROR_TYPE.INTEGEROVERFLOW; + } else if (isShareSupplyOerflowError) { + return TRANSACTION_ERROR_TYPE.SHARESUPPLYOVERFLOW; + } else if (isTokenFrozen) { + return TRANSACTION_ERROR_TYPE.TOKEN_FROZEN; + } else if (isPoolBalanceLess) { + return TRANSACTION_ERROR_TYPE.POOL_BALANCE_LESS; + } else if (isNETHErrpr) { + return TRANSACTION_ERROR_TYPE.NETH_ERROR; + } else { + return null; + } +};