-
Notifications
You must be signed in to change notification settings - Fork 70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
One-Click LP: Close Position #597
base: main
Are you sure you want to change the base?
Changes from all commits
2826aba
bd642dc
39b6610
47b639f
c00c897
9079235
f2a7ff6
281ab34
f514d6b
d08aaab
abbe0d7
c35573f
57179c3
7bb6e91
cf6df2f
42e9dff
7a33690
089b4a3
a0883ee
1740581
0d4ce8f
df90256
27b0146
8938fe7
8d439da
a47064b
7d9e72f
77b8d9c
4da583c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ import { ethers } from 'ethers' | |
import { useCallback } from 'react' | ||
import { useGetDebtAmount, useGetVault } from '../controller/hooks' | ||
import { indexAtom, normFactorAtom } from '../controller/atoms' | ||
import { getPoolState } from '../squeethPool/hooks' | ||
|
||
/*** CONSTANTS ***/ | ||
const COLLAT_RATIO_FLASHLOAN = 2 | ||
|
@@ -77,8 +78,8 @@ export const useOpenPositionDeposit = () => { | |
const amount0Min = amount0New.times(new BigNumber(1).minus(slippage)).toFixed(0) | ||
const amount1Min = amount1New.times(new BigNumber(1).minus(slippage)).toFixed(0) | ||
|
||
const collateralToWithdraw = fromTokenAmount(withdrawAmount, OSQUEETH_DECIMALS) | ||
const ethIndexPrice = toTokenAmount(index, 18).sqrt() | ||
const collateralToWithdraw = fromTokenAmount(withdrawAmount, WETH_DECIMALS) | ||
const ethIndexPrice = toTokenAmount(index, WETH_DECIMALS).sqrt() | ||
const vaultShortAmt = fromTokenAmount(vaultBefore.shortAmount, OSQUEETH_DECIMALS) | ||
const vaultCollateralAmt = fromTokenAmount(vaultBefore.collateralAmount, WETH_DECIMALS) | ||
|
||
|
@@ -135,6 +136,130 @@ export const useOpenPositionDeposit = () => { | |
return openPositionDeposit | ||
} | ||
|
||
// Close position with flashloan | ||
export const useFlashClosePosition = () => { | ||
const address = useAtomValue(addressAtom) | ||
const controllerHelperContract = useAtomValue(controllerHelperHelperContractAtom) | ||
const controllerContract = useAtomValue(controllerContractAtom) | ||
const handleTransaction = useHandleTransaction() | ||
const getDebtAmount = useGetDebtAmount() | ||
const getVault = useGetVault() | ||
const getPosition = useGetPosition() | ||
const getExactIn = useGetExactIn() | ||
const getExactOut = useGetExactOut() | ||
const getDecreaseLiquidity = useGetDecreaseLiquidity() | ||
const isWethToken0 = useAtomValue(isWethToken0Atom) | ||
const index = useAtomValue(indexAtom) | ||
const normFactor = useAtomValue(normFactorAtom) | ||
const flashClosePosition = useAppCallback( | ||
async ( | ||
vaultId: number, | ||
liquidityPercentage: number, | ||
burnPercentage: number, | ||
withdrawAmount: number, | ||
burnExactRemoved: boolean, | ||
slippage: number, | ||
onTxConfirmed?: () => void, | ||
) => { | ||
const vaultBefore = await getVault(vaultId) | ||
const uniTokenId = vaultBefore?.NFTCollateralId | ||
const position = await getPosition(uniTokenId) | ||
|
||
if ( | ||
!controllerContract || | ||
!controllerHelperContract || | ||
!address || | ||
!position || | ||
!vaultBefore || | ||
!vaultBefore.shortAmount | ||
) | ||
return | ||
|
||
const shortAmount = fromTokenAmount(vaultBefore.shortAmount, OSQUEETH_DECIMALS) | ||
|
||
// Get current LP positions | ||
const { amount0, amount1 } = await getDecreaseLiquidity( | ||
uniTokenId, | ||
position.liquidity, | ||
0, | ||
0, | ||
Math.floor(Date.now() / 1000 + 86400), | ||
) | ||
const wPowerPerpAmountInLP = isWethToken0 ? amount1 : amount0 | ||
|
||
const amountToLiquidate = new BigNumber(wPowerPerpAmountInLP).times(liquidityPercentage) | ||
const amountToBurn = shortAmount.times(burnPercentage) | ||
const limitEth = await (amountToLiquidate.gt(amountToBurn) | ||
? getExactIn(amountToLiquidate.minus(amountToBurn), true) | ||
: getExactOut(amountToBurn.minus(amountToLiquidate), true)) | ||
|
||
const collateralToWithdraw = fromTokenAmount(withdrawAmount, WETH_DECIMALS) | ||
const ethIndexPrice = toTokenAmount(index, WETH_DECIMALS).sqrt() | ||
const vaultShortAmt = fromTokenAmount(vaultBefore.shortAmount, OSQUEETH_DECIMALS) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a repeat of shortAmount, we can delete |
||
const vaultCollateralAmt = fromTokenAmount(vaultBefore.collateralAmount, WETH_DECIMALS) | ||
|
||
const flashLoanAmount = new BigNumber(COLLAT_RATIO_FLASHLOAN + FLASHLOAN_BUFFER) | ||
.times(vaultShortAmt) | ||
.times(normFactor) | ||
.times(ethIndexPrice) | ||
.div(INDEX_SCALE) | ||
.minus(vaultCollateralAmt) | ||
const flashLoanAmountPos = BigNumber.max(flashLoanAmount, 0) | ||
|
||
const limitPrice = new BigNumber(limitEth) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Think this should be (1+slippage) if we're buying oSQTH when amountToLiquidate > amountToBurn There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, agreed |
||
.div(amountToLiquidate.minus(amountToBurn).abs()) | ||
.times(new BigNumber(1).minus(slippage)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. slippage for remove liquidity probably different to slippage for oSQTH/eth swap There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah should probably be different variables, though for LP withdrawal slippage, it can probably not be surfaced for the user |
||
|
||
const amount0Min = new BigNumber(amount0) | ||
.times(liquidityPercentage) | ||
.times(new BigNumber(1).minus(slippage)) | ||
.toFixed(0) | ||
const amount1Min = new BigNumber(amount1) | ||
.times(liquidityPercentage) | ||
.times(new BigNumber(1).minus(slippage)) | ||
.toFixed(0) | ||
|
||
const flashloanCloseVaultLpNftParam = { | ||
vaultId: vaultId, | ||
tokenId: uniTokenId, | ||
liquidity: position.liquidity, | ||
liquidityPercentage: fromTokenAmount(liquidityPercentage, 18).toFixed(0), | ||
wPowerPerpAmountToBurn: amountToBurn.toFixed(0), | ||
collateralToFlashloan: flashLoanAmountPos.toFixed(0), | ||
collateralToWithdraw: collateralToWithdraw.toFixed(0), | ||
limitPriceEthPerPowerPerp: fromTokenAmount(limitPrice, 18).toFixed(0), | ||
amount0Min, | ||
amount1Min, | ||
poolFee: POOL_FEE, | ||
burnExactRemoved, | ||
} | ||
|
||
return handleTransaction( | ||
await controllerHelperContract.methods.flashloanCloseVaultLpNft(flashloanCloseVaultLpNftParam).send({ | ||
from: address, | ||
}), | ||
onTxConfirmed, | ||
) | ||
}, | ||
[ | ||
address, | ||
controllerHelperContract, | ||
controllerContract, | ||
handleTransaction, | ||
getDebtAmount, | ||
getVault, | ||
getPosition, | ||
getExactIn, | ||
getExactOut, | ||
getDecreaseLiquidity, | ||
isWethToken0, | ||
index, | ||
normFactor, | ||
], | ||
) | ||
return flashClosePosition | ||
} | ||
|
||
/*** GETTERS ***/ | ||
|
||
export const useGetPosition = () => { | ||
|
@@ -326,24 +451,27 @@ export const useGetExactOut = () => { | |
return getExactOut | ||
} | ||
|
||
async function getPoolState(poolContract: Contract) { | ||
const [slot, liquidity, tickSpacing] = await Promise.all([ | ||
poolContract?.methods.slot0().call(), | ||
poolContract?.methods.liquidity().call(), | ||
poolContract.methods.tickSpacing().call(), | ||
]) | ||
|
||
const PoolState = { | ||
liquidity, | ||
sqrtPriceX96: slot[0], | ||
tick: slot[1], | ||
observationIndex: slot[2], | ||
observationCardinality: slot[3], | ||
observationCardinalityNext: slot[4], | ||
feeProtocol: slot[5], | ||
unlocked: slot[6], | ||
tickSpacing, | ||
} | ||
|
||
return PoolState | ||
} | ||
export const useGetQuote = () => { | ||
const contract = useAtomValue(quoterContractAtom) | ||
const { weth, oSqueeth } = useAtomValue(addressesAtom) | ||
|
||
const getQuote = useCallback( | ||
async (amount: BigNumber, squeethIn: boolean) => { | ||
if (!contract) return null | ||
|
||
const QuoteExactInputSingleParams = { | ||
tokenIn: squeethIn ? oSqueeth : weth, | ||
tokenOut: squeethIn ? weth : oSqueeth, | ||
amountIn: amount.toFixed(0), | ||
fee: 3000, | ||
sqrtPriceLimitX96: 0, | ||
} | ||
|
||
const quote = await contract.methods.quoteExactInputSingle(QuoteExactInputSingleParams).call() | ||
return quote.amountOut | ||
}, | ||
[contract, weth, oSqueeth], | ||
) | ||
|
||
return getQuote | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right now, this doesn't take the pool fee in and do any autorouting, so we have to restrict this functionality to to be only the 0.3% pool