From 19f8bf418388b36aa7ba75cde69ce80b12278122 Mon Sep 17 00:00:00 2001 From: Peiman <25097709+Rickk137@users.noreply.github.com> Date: Sat, 13 Apr 2024 14:44:02 +0330 Subject: [PATCH] fix: undelegate with repay in single tx (#240) --- .../UndelegateModal/UndelegateModal.tsx | 16 +- .../components/UndelegateModal/package.json | 1 + liquidity/lib/useClearDebt/package.json | 1 - liquidity/lib/useClearDebt/useClearDebt.tsx | 5 +- .../lib/useUndelegateBaseAndromeda/index.ts | 1 + .../useUndelegateBaseAndromeda/package.json | 26 +++ .../useUndelegateBaseAndromeda.tsx | 153 ++++++++++++++++++ liquidity/ui/src/pages/Manage/Undelegate.tsx | 5 - yarn.lock | 27 +++- 9 files changed, 223 insertions(+), 12 deletions(-) create mode 100644 liquidity/lib/useUndelegateBaseAndromeda/index.ts create mode 100644 liquidity/lib/useUndelegateBaseAndromeda/package.json create mode 100644 liquidity/lib/useUndelegateBaseAndromeda/useUndelegateBaseAndromeda.tsx diff --git a/liquidity/components/UndelegateModal/UndelegateModal.tsx b/liquidity/components/UndelegateModal/UndelegateModal.tsx index 11f562ebf..3bd3dc31d 100644 --- a/liquidity/components/UndelegateModal/UndelegateModal.tsx +++ b/liquidity/components/UndelegateModal/UndelegateModal.tsx @@ -26,6 +26,8 @@ import { useContractErrorParser } from '@snx-v3/useContractErrorParser'; import { ContractError } from '@snx-v3/ContractError'; import { useQueryClient } from '@tanstack/react-query'; import { useNetwork } from '@snx-v3/useBlockchain'; +import { useUndelegateBaseAndromeda } from '../../lib/useUndelegateBaseAndromeda'; +import { isBaseAndromeda } from '@snx-v3/isBaseAndromeda'; export const UndelegateModalUi: FC<{ amount: Wei; @@ -112,6 +114,14 @@ export const UndelegateModal: UndelegateModalProps = ({ onClose, isOpen, liquidi collateralChange, currentCollateral: currentCollateral, }); + const { exec: undelegateBaseAndromeda } = useUndelegateBaseAndromeda({ + accountId: params.accountId, + poolId: params.poolId, + collateralTypeAddress: liquidityPosition?.tokenAddress, + collateralChange, + currentCollateral: currentCollateral, + liquidityPosition, + }); const { data: CoreProxy } = useCoreProxy(); const errorParserCoreProxy = useContractErrorParser(CoreProxy); @@ -123,7 +133,11 @@ export const UndelegateModal: UndelegateModalProps = ({ onClose, isOpen, liquidi services: { [ServiceNames.undelegate]: async () => { try { - await execUndelegate(); + if (isBaseAndromeda(network?.id, network?.preset)) { + await undelegateBaseAndromeda(); + } else { + await execUndelegate(); + } await queryClient.invalidateQueries({ queryKey: [`${network?.id}-${network?.preset}`, 'LiquidityPosition'], exact: false, diff --git a/liquidity/components/UndelegateModal/package.json b/liquidity/components/UndelegateModal/package.json index 344319177..9f69c6131 100644 --- a/liquidity/components/UndelegateModal/package.json +++ b/liquidity/components/UndelegateModal/package.json @@ -9,6 +9,7 @@ "@snx-v3/ContractError": "workspace:*", "@snx-v3/ManagePositionContext": "workspace:*", "@snx-v3/Multistep": "workspace:*", + "@snx-v3/isBaseAndromeda": "workspace:*", "@snx-v3/useBlockchain": "workspace:*", "@snx-v3/useCollateralTypes": "workspace:*", "@snx-v3/useContractErrorParser": "workspace:*", diff --git a/liquidity/lib/useClearDebt/package.json b/liquidity/lib/useClearDebt/package.json index 360e52c90..67b59cb6e 100644 --- a/liquidity/lib/useClearDebt/package.json +++ b/liquidity/lib/useClearDebt/package.json @@ -14,7 +14,6 @@ "@snx-v3/useGasOptions": "workspace:*", "@snx-v3/useGasPrice": "workspace:*", "@snx-v3/useGasSpeed": "workspace:*", - "@snx-v3/useUSDProxy": "workspace:*", "@snx-v3/withERC7412": "workspace:*", "@synthetixio/wei": "^2.74.4", "@tanstack/react-query": "^5.8.3", diff --git a/liquidity/lib/useClearDebt/useClearDebt.tsx b/liquidity/lib/useClearDebt/useClearDebt.tsx index 73d9d5d0b..fa7f1aaed 100644 --- a/liquidity/lib/useClearDebt/useClearDebt.tsx +++ b/liquidity/lib/useClearDebt/useClearDebt.tsx @@ -8,7 +8,6 @@ import Wei from '@synthetixio/wei'; import { BigNumber, Contract } from 'ethers'; import { getGasPrice } from '@snx-v3/useGasPrice'; import { useGasSpeed } from '@snx-v3/useGasSpeed'; -import { useUSDProxy } from '@snx-v3/useUSDProxy'; import { notNil } from '@snx-v3/tsHelpers'; import { withERC7412 } from '@snx-v3/withERC7412'; import { useAllCollateralPriceIds } from '@snx-v3/useAllCollateralPriceIds'; @@ -16,7 +15,7 @@ import { fetchPriceUpdates, priceUpdatesToPopulatedTx } from '@snx-v3/fetchPythP import { useSpotMarketProxy } from '../useSpotMarketProxy'; import { USDC_BASE_MARKET, getRepayerContract } from '@snx-v3/isBaseAndromeda'; -const DEBT_REPAYER_ABI = [ +export const DEBT_REPAYER_ABI = [ { inputs: [ { internalType: 'contract ISynthetixCore', name: 'synthetixCore', type: 'address' }, @@ -48,7 +47,6 @@ export const useClearDebt = ({ }) => { const [txnState, dispatch] = useReducer(reducer, initialState); const { data: CoreProxy } = useCoreProxy(); - const { data: UsdProxy } = useUSDProxy(); const { data: SpotMarketProxy } = useSpotMarketProxy(); const { data: collateralPriceIds } = useAllCollateralPriceIds(); @@ -67,7 +65,6 @@ export const useClearDebt = ({ poolId && accountId && collateralTypeAddress && - UsdProxy && SpotMarketProxy && collateralPriceIds ) diff --git a/liquidity/lib/useUndelegateBaseAndromeda/index.ts b/liquidity/lib/useUndelegateBaseAndromeda/index.ts new file mode 100644 index 000000000..2bd53f145 --- /dev/null +++ b/liquidity/lib/useUndelegateBaseAndromeda/index.ts @@ -0,0 +1 @@ +export * from './useUndelegateBaseAndromeda'; diff --git a/liquidity/lib/useUndelegateBaseAndromeda/package.json b/liquidity/lib/useUndelegateBaseAndromeda/package.json new file mode 100644 index 000000000..b37d559af --- /dev/null +++ b/liquidity/lib/useUndelegateBaseAndromeda/package.json @@ -0,0 +1,26 @@ +{ + "name": "@snx-v3/useUndelegateBaseAndromeda", + "private": true, + "main": "index.ts", + "version": "0.0.1", + "dependencies": { + "@snx-v3/fetchPythPrices": "workspace:*", + "@snx-v3/format": "workspace:*", + "@snx-v3/isBaseAndromeda": "workspace:*", + "@snx-v3/tsHelpers": "workspace:*", + "@snx-v3/txnReducer": "workspace:*", + "@snx-v3/useAllCollateralPriceIds": "workspace:*", + "@snx-v3/useApprove": "workspace:*", + "@snx-v3/useBlockchain": "workspace:*", + "@snx-v3/useCoreProxy": "workspace:*", + "@snx-v3/useGasOptions": "workspace:*", + "@snx-v3/useGasPrice": "workspace:*", + "@snx-v3/useGasSpeed": "workspace:*", + "@snx-v3/useLiquidityPosition": "workspace:*", + "@snx-v3/withERC7412": "workspace:*", + "@synthetixio/wei": "^2.74.4", + "@tanstack/react-query": "^5.8.3", + "ethers": "^5.7.2", + "react": "^18.2.0" + } +} diff --git a/liquidity/lib/useUndelegateBaseAndromeda/useUndelegateBaseAndromeda.tsx b/liquidity/lib/useUndelegateBaseAndromeda/useUndelegateBaseAndromeda.tsx new file mode 100644 index 000000000..558772aaf --- /dev/null +++ b/liquidity/lib/useUndelegateBaseAndromeda/useUndelegateBaseAndromeda.tsx @@ -0,0 +1,153 @@ +import { useReducer } from 'react'; +import { useCoreProxy } from '@snx-v3/useCoreProxy'; +import { useMutation } from '@tanstack/react-query'; +import { useNetwork, useProvider, useSigner } from '@snx-v3/useBlockchain'; +import { initialState, reducer } from '@snx-v3/txnReducer'; +import Wei, { wei } from '@synthetixio/wei'; +import { BigNumber, Contract, PopulatedTransaction } from 'ethers'; +import { formatGasPriceForTransaction } from '@snx-v3/useGasOptions'; +import { getGasPrice } from '@snx-v3/useGasPrice'; +import { useGasSpeed } from '@snx-v3/useGasSpeed'; +import { withERC7412 } from '@snx-v3/withERC7412'; +import { useAllCollateralPriceIds } from '@snx-v3/useAllCollateralPriceIds'; +import { fetchPriceUpdates, priceUpdatesToPopulatedTx } from '@snx-v3/fetchPythPrices'; +import { LiquidityPosition } from '@snx-v3/useLiquidityPosition'; +import { useApprove } from '@snx-v3/useApprove'; +import { USDC_BASE_MARKET, getRepayerContract, getUSDCAddress } from '@snx-v3/isBaseAndromeda'; +import { parseUnits } from '@snx-v3/format'; +import { DEBT_REPAYER_ABI } from '../useClearDebt'; +import { useSpotMarketProxy } from '../useSpotMarketProxy'; +import { notNil } from '@snx-v3/tsHelpers'; + +export const useUndelegateBaseAndromeda = ({ + accountId, + poolId, + collateralTypeAddress, + collateralChange, + currentCollateral, + liquidityPosition, +}: { + accountId?: string; + poolId?: string; + collateralTypeAddress?: string; + currentCollateral: Wei; + collateralChange: Wei; + liquidityPosition?: LiquidityPosition; +}) => { + const [txnState, dispatch] = useReducer(reducer, initialState); + const { data: CoreProxy } = useCoreProxy(); + const { data: SpotMarketProxy } = useSpotMarketProxy(); + + const signer = useSigner(); + const { gasSpeed } = useGasSpeed(); + const provider = useProvider(); + const { data: collateralPriceUpdates } = useAllCollateralPriceIds(); + const { network } = useNetwork(); + + const debtExists = liquidityPosition?.debt.gt(0.01); + const currentDebt = debtExists && liquidityPosition ? liquidityPosition.debt : wei(0); + + const { approve, requireApproval } = useApprove({ + contractAddress: getUSDCAddress(network?.id), + //slippage for approval + amount: parseUnits(currentDebt.toString(), 6).mul(110).div(100), + spender: getRepayerContract(network?.id), + }); + + const mutation = useMutation({ + mutationFn: async () => { + if (!signer || !network || !provider) throw new Error('No signer or network'); + if ( + !(CoreProxy && poolId && collateralTypeAddress && collateralPriceUpdates && SpotMarketProxy) + ) + return; + if (collateralChange.eq(0)) return; + if (currentCollateral.eq(0)) return; + try { + dispatch({ type: 'prompting' }); + + if (debtExists && requireApproval) { + await approve(false); + } + + const transactions: Promise[] = []; + + if (debtExists) { + const repayer = new Contract(getRepayerContract(network.id), DEBT_REPAYER_ABI, signer); + + const depositDebtToRepay = repayer.populateTransaction.depositDebtToRepay( + CoreProxy.address, + SpotMarketProxy.address, + accountId, + poolId, + collateralTypeAddress, + USDC_BASE_MARKET + ); + transactions.push(depositDebtToRepay); + + const burn = CoreProxy.populateTransaction.burnUsd( + BigNumber.from(accountId), + BigNumber.from(poolId), + collateralTypeAddress, + currentDebt?.mul(110).div(100).toBN().toString() || '0' + ); + transactions.push(burn); + } + + const populatedTxnPromised = CoreProxy.populateTransaction.delegateCollateral( + BigNumber.from(accountId), + BigNumber.from(poolId), + collateralTypeAddress, + currentCollateral.add(collateralChange).toBN(), + wei(1).toBN() + ); + + const walletAddress = await signer.getAddress(); + + const callsPromise = Promise.all([...transactions, populatedTxnPromised].filter(notNil)); + + const collateralPriceCallsPromise = fetchPriceUpdates( + collateralPriceUpdates, + network.isTestnet + ).then((signedData) => + priceUpdatesToPopulatedTx(walletAddress, collateralPriceUpdates, signedData) + ); + const [calls, gasPrices, collateralPriceCalls] = await Promise.all([ + callsPromise, + getGasPrice({ provider }), + collateralPriceCallsPromise, + ]); + const allCalls = collateralPriceCalls.concat(...calls); + + const erc7412Tx = await withERC7412( + network, + allCalls, + 'useUndelegate', + CoreProxy.interface + ); + + const gasOptionsForTransaction = formatGasPriceForTransaction({ + gasLimit: erc7412Tx.gasLimit, + gasPrices, + gasSpeed, + }); + + const txn = await signer.sendTransaction({ ...erc7412Tx, ...gasOptionsForTransaction }); + dispatch({ type: 'pending', payload: { txnHash: txn.hash } }); + + await txn.wait(); + dispatch({ type: 'success' }); + } catch (error: any) { + dispatch({ type: 'error', payload: { error } }); + throw error; + } + }, + }); + return { + mutation, + txnState, + settle: () => dispatch({ type: 'settled' }), + isLoading: mutation.isPending, + exec: mutation.mutateAsync, + }; +}; diff --git a/liquidity/ui/src/pages/Manage/Undelegate.tsx b/liquidity/ui/src/pages/Manage/Undelegate.tsx index 5b354acc3..c537bd12e 100644 --- a/liquidity/ui/src/pages/Manage/Undelegate.tsx +++ b/liquidity/ui/src/pages/Manage/Undelegate.tsx @@ -22,7 +22,6 @@ import React, { FC, useContext } from 'react'; import { useParams } from '@snx-v3/useParams'; import { isBaseAndromeda } from '@snx-v3/isBaseAndromeda'; import { useNetwork } from '@snx-v3/useBlockchain'; -import { RepayAllDebt } from './RepayAllDebt'; export const UndelegateUi: FC<{ collateralChange: Wei; @@ -190,10 +189,6 @@ export const Undelegate = ({ liquidityPosition }: { liquidityPosition?: Liquidit } const max = maxUndelegate(); - if (liquidityPosition?.debt.gt(0.01) && isBaseAndromeda(network?.id, network?.preset)) { - return ; - } - return (