From 9da028d24b20443b5777c218a70ec5087af9eff5 Mon Sep 17 00:00:00 2001 From: eli-d <64763513+eli-d@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:30:53 +0930 Subject: [PATCH] fix vesting flow when staking new/existing and unstaking vested positions --- .../app/stake/pool/confirm-withdraw/page.tsx | 11 +- web/src/components/ConfirmStake.tsx | 135 ++++++++++++++---- 2 files changed, 118 insertions(+), 28 deletions(-) diff --git a/web/src/app/stake/pool/confirm-withdraw/page.tsx b/web/src/app/stake/pool/confirm-withdraw/page.tsx index f4652576..cd6e6e85 100644 --- a/web/src/app/stake/pool/confirm-withdraw/page.tsx +++ b/web/src/app/stake/pool/confirm-withdraw/page.tsx @@ -173,15 +173,20 @@ export default function ConfirmWithdrawLiquidity() { } }; - // once yield is collected, update position + // once yield is collected or position is divested, update position useEffect(() => { - if (!collectResult.data || !isWithdrawingEntirePosition) return; - updatePosition(BigInt(positionId)); + if ( + (isWithdrawingEntirePosition && collectResult.data) || + (isDivesting && divestPositionResult.data) + ) + updatePosition(BigInt(positionId)); }, [ updatePosition, positionId, collectResult.data, isWithdrawingEntirePosition, + isDivesting, + divestPositionResult.data, ]); const getAmountsAndSetPosition = useCallback( diff --git a/web/src/components/ConfirmStake.tsx b/web/src/components/ConfirmStake.tsx index 75b2c185..79cb8fd2 100644 --- a/web/src/components/ConfirmStake.tsx +++ b/web/src/components/ConfirmStake.tsx @@ -33,6 +33,7 @@ import { useContracts } from "@/config/contracts"; import { TokenIcon } from "./TokenIcon"; import { useFeatureFlag } from "@/hooks/useFeatureFlag"; import { usePositions } from "@/hooks/usePostions"; +import { superpositionTestnet } from "@/config/chains"; type ConfirmStakeProps = | { @@ -67,9 +68,14 @@ export const ConfirmStake = ({ const fUSDC = useTokens(expectedChainId, "fusdc"); const ammContract = useContracts(expectedChainId, "amm"); const leoContract = useContracts(expectedChainId, "leo"); + const ownershipNFTContract = useContracts(expectedChainId, "ownershipNFTs"); const showBoostIncentives = useFeatureFlag("ui show boost incentives"); const showStakeApy = useFeatureFlag("ui show stake apy"); + const showLeo = + useFeatureFlag("ui show leo") && chainId === superpositionTestnet.id; + const isDivesting = showLeo && isVested; + useEffect(() => { if (!address || chainId !== expectedChainId) router.back(); }, [address, expectedChainId, chainId, router]); @@ -187,8 +193,16 @@ export const ConfirmStake = ({ writeContractAsync: writeContractDivestPosition, data: divestPositionData, error: divestPositionError, + isPending: isDivestPositionPending, reset: resetDivestPosition, } = useWriteContract(); + const { + writeContractAsync: writeContractApproveOwnershipNFT, + data: approveOwnershipNFTData, + error: approveOwnershipNFTError, + isPending: isApproveOwnershipNFTPending, + reset: resetApproveOwnershipNFT, + } = useWriteContract(); const divestPositionResult = useWaitForTransactionReceipt({ hash: divestPositionData, @@ -388,6 +402,11 @@ export const ConfirmStake = ({ hash: updatePositionData, }); + // wait for the approveOwnershipNFT transaction to complete + const approveOwnershipNFTResult = useWaitForTransactionReceipt({ + hash: approveOwnershipNFTData, + }); + const getAmountsAndSetPosition = useCallback( function (id: number, tickLower: number, tickUpper: number) { const position = { @@ -398,7 +417,8 @@ export const ConfirmStake = ({ }, lower: tickLower, upper: tickUpper, - isVested, + // if isVested already, we have to unvest to update the position + isVested: isVesting, }; getUsdTokenAmountsForPosition( expectedChainId, @@ -423,7 +443,25 @@ export const ConfirmStake = ({ }), ); }, - [expectedChainId, isVested, token0, tokenPrice, updatePositionLocal], + [expectedChainId, isVesting, token0, tokenPrice, updatePositionLocal], + ); + + const approveOwnershipNFT = useCallback( + () => + writeContractApproveOwnershipNFT({ + address: ownershipNFTContract.address, + abi: ownershipNFTContract.abi, + functionName: "approve", + args: [leoContract.address, BigInt(positionId ?? mintPositionId ?? 0)], + }), + [ + writeContractApproveOwnershipNFT, + ownershipNFTContract.address, + ownershipNFTContract.abi, + positionId, + mintPositionId, + leoContract.address, + ], ); const vestPositionResultIdle = useCallback( @@ -461,29 +499,24 @@ export const ConfirmStake = ({ ]); useEffect(() => { - if (updatePositionResult.isSuccess) { - // if we're vesting in Leo, do so now - if (isVesting) { - switch (true) { - // haven't yet called vest position, so call it and wait - case vestPositionResult.fetchStatus === "idle" && - !vestPositionResult.data: - vestPositionResultIdle(); - return; - // vest position call is pending, so wait - case isVestPositionPending: - return; - // call has completed, so proceed - default: - break; - } - } + if (updatePositionResult.isSuccess && isVesting) { + approveOwnershipNFT(); } + }, [updatePositionResult.isSuccess, isVesting, approveOwnershipNFT]); + + useEffect(() => { + // if we're vesting in Leo, have approved the ownership transfer, but haven't vested the position, do so now + if ( + approveOwnershipNFTResult.isSuccess && + isVesting && + vestPositionResult.fetchStatus === "idle" && + !vestPositionResult.data + ) + vestPositionResultIdle(); }, [ vestPositionResultIdle, - isVestPositionPending, isVesting, - updatePositionResult.isSuccess, + approveOwnershipNFTResult.isSuccess, vestPositionResult.data, vestPositionResult.fetchStatus, ]); @@ -494,6 +527,7 @@ export const ConfirmStake = ({ resetApproveToken1(); resetVestPosition(); resetDivestPosition(); + resetApproveOwnershipNFT(); updatePositionResult.refetch(); router.push("/stake"); }, [ @@ -502,6 +536,7 @@ export const ConfirmStake = ({ resetApproveToken1, resetVestPosition, resetDivestPosition, + resetApproveOwnershipNFT, updatePositionResult, router, ]); @@ -544,7 +579,23 @@ export const ConfirmStake = ({ ); } - // step 3 pending + // step 4 - divest from Leo if position is vested + if ( + isDivesting && + (isDivestPositionPending || + (divestPositionData && divestPositionResult?.isPending)) + ) { + return ( + + ); + } + + // step 5 pending if ( isUpdatePositionPending || (updatePositionData && updatePositionResult?.isPending) @@ -559,6 +610,38 @@ export const ConfirmStake = ({ ); } + // step 6 approving NFT ownership transfer for vesting + if ( + isVesting && + (isApproveOwnershipNFTPending || + (approveOwnershipNFTData && approveOwnershipNFTResult?.isPending)) + ) { + return ( + + ); + } + + // step 7 vesting position + if ( + isVesting && + (isVestPositionPending || + (vestPositionData && vestPositionResult?.isPending)) + ) { + return ( + + ); + } + // success if (updatePositionResult.data) { return ( @@ -576,7 +659,8 @@ export const ConfirmStake = ({ approvalErrorToken1 || updatePositionError || divestPositionError || - vestPositionError + vestPositionError || + approveOwnershipNFTError ) { const error = mintError || @@ -584,7 +668,8 @@ export const ConfirmStake = ({ approvalErrorToken1 || updatePositionError || divestPositionError || - vestPositionError; + vestPositionError || + approveOwnershipNFTError; return ; } @@ -838,7 +923,7 @@ export const ConfirmStake = ({ onClick={() => { mode === "new" ? createPosition() - : isVested + : isDivesting ? divestPosition(BigInt(positionId)) : updatePosition(BigInt(positionId)); }}