diff --git a/liquidity/lib/isBaseAndromeda/isBaseAndromeda.ts b/liquidity/lib/isBaseAndromeda/isBaseAndromeda.ts index 230d96b10..5294983a2 100644 --- a/liquidity/lib/isBaseAndromeda/isBaseAndromeda.ts +++ b/liquidity/lib/isBaseAndromeda/isBaseAndromeda.ts @@ -1,6 +1,17 @@ export const isBaseAndromeda = (id?: number | string, preset?: string) => (id?.toString() === '8453' || '84532') && preset === 'andromeda'; +export function getRepayerContract(id?: number) { + switch (id) { + case 8453: + return '0xBD8004ea5c73E33d405d35d594221Efc733F7E37'; + case 84532: + return '0xD00a601eafC2C131F46105827AFB02b925Adf62A'; + default: + return ''; + } +} + export function getUSDCAddress(id?: number) { switch (id) { case 8453: @@ -28,6 +39,6 @@ export const sUSDC = '0xC74eA762cF06c9151cE074E6a569a5945b6302E7'; // Base Sepolia export const BASE_SEPOLIA_fUSDC = '0xc43708f8987Df3f3681801e5e640667D86Ce3C30'; -export const BASE_SEPOLIA_sUSDC = '0x434Aa3FDb11798EDaB506D4a5e48F70845a66219'; +export const BASE_SEPOLIA_sUSDC = '0x8069c44244e72443722cfb22DcE5492cba239d39'; export const USDC_BASE_MARKET = '1'; diff --git a/liquidity/lib/useClearDebt/index.ts b/liquidity/lib/useClearDebt/index.ts new file mode 100644 index 000000000..55586c26f --- /dev/null +++ b/liquidity/lib/useClearDebt/index.ts @@ -0,0 +1 @@ +export * from './useClearDebt'; diff --git a/liquidity/lib/useClearDebt/package.json b/liquidity/lib/useClearDebt/package.json new file mode 100644 index 000000000..360e52c90 --- /dev/null +++ b/liquidity/lib/useClearDebt/package.json @@ -0,0 +1,24 @@ +{ + "name": "@snx-v3/useClearDebt", + "private": true, + "main": "index.ts", + "version": "0.0.1", + "dependencies": { + "@snx-v3/fetchPythPrices": "workspace:*", + "@snx-v3/isBaseAndromeda": "workspace:*", + "@snx-v3/tsHelpers": "workspace:*", + "@snx-v3/txnReducer": "workspace:*", + "@snx-v3/useAllCollateralPriceIds": "workspace:*", + "@snx-v3/useBlockchain": "workspace:*", + "@snx-v3/useCoreProxy": "workspace:*", + "@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", + "ethers": "^5.7.2", + "react": "^18.2.0" + } +} diff --git a/liquidity/lib/useClearDebt/useClearDebt.tsx b/liquidity/lib/useClearDebt/useClearDebt.tsx new file mode 100644 index 000000000..73d9d5d0b --- /dev/null +++ b/liquidity/lib/useClearDebt/useClearDebt.tsx @@ -0,0 +1,146 @@ +import { useReducer } from 'react'; +import { useCoreProxy } from '@snx-v3/useCoreProxy'; +import { formatGasPriceForTransaction } from '@snx-v3/useGasOptions'; +import { useMutation } from '@tanstack/react-query'; +import { useNetwork, useProvider, useSigner } from '@snx-v3/useBlockchain'; +import { initialState, reducer } from '@snx-v3/txnReducer'; +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'; +import { fetchPriceUpdates, priceUpdatesToPopulatedTx } from '@snx-v3/fetchPythPrices'; +import { useSpotMarketProxy } from '../useSpotMarketProxy'; +import { USDC_BASE_MARKET, getRepayerContract } from '@snx-v3/isBaseAndromeda'; + +const DEBT_REPAYER_ABI = [ + { + inputs: [ + { internalType: 'contract ISynthetixCore', name: 'synthetixCore', type: 'address' }, + { internalType: 'contract ISpotMarket', name: 'spotMarket', type: 'address' }, + { internalType: 'uint128', name: 'accountId', type: 'uint128' }, + { internalType: 'uint128', name: 'poolId', type: 'uint128' }, + { internalType: 'address', name: 'collateralType', type: 'address' }, + { internalType: 'uint128', name: 'spotMarketId', type: 'uint128' }, + ], + name: 'depositDebtToRepay', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +]; + +export const useClearDebt = ({ + accountId, + poolId, + collateralTypeAddress, + availableUSDCollateral, + debt, +}: { + accountId?: string; + poolId?: string; + collateralTypeAddress?: string; + availableUSDCollateral?: Wei; + debt?: Wei; +}) => { + const [txnState, dispatch] = useReducer(reducer, initialState); + const { data: CoreProxy } = useCoreProxy(); + const { data: UsdProxy } = useUSDProxy(); + const { data: SpotMarketProxy } = useSpotMarketProxy(); + const { data: collateralPriceIds } = useAllCollateralPriceIds(); + + const signer = useSigner(); + const { network } = useNetwork(); + const { gasSpeed } = useGasSpeed(); + const provider = useProvider(); + + const mutation = useMutation({ + mutationFn: async () => { + if (!signer || !network || !provider) throw new Error('No signer or network'); + + if ( + !( + CoreProxy && + poolId && + accountId && + collateralTypeAddress && + UsdProxy && + SpotMarketProxy && + collateralPriceIds + ) + ) { + return; + } + + const repayer = new Contract(getRepayerContract(network.id), DEBT_REPAYER_ABI, signer); + + if (!availableUSDCollateral) return; + + try { + dispatch({ type: 'prompting' }); + + const depositDebtToRepay = repayer.populateTransaction.depositDebtToRepay( + CoreProxy.address, + SpotMarketProxy.address, + accountId, + poolId, + collateralTypeAddress, + USDC_BASE_MARKET + ); + + const burn = CoreProxy.populateTransaction.burnUsd( + BigNumber.from(accountId), + BigNumber.from(poolId), + collateralTypeAddress, + debt?.mul(110).div(100).toBN().toString() || '0' + ); + + const callsPromise = Promise.all([depositDebtToRepay, burn].filter(notNil)); + + const walletAddress = await signer.getAddress(); + + const collateralPriceCallsPromise = fetchPriceUpdates( + collateralPriceIds, + network.isTestnet + ).then((signedData) => + priceUpdatesToPopulatedTx(walletAddress, collateralPriceIds, signedData) + ); + + const [calls, gasPrices, collateralPriceCalls] = await Promise.all([ + callsPromise, + getGasPrice({ provider }), + collateralPriceCallsPromise, + ]); + + const allCalls = collateralPriceCalls.concat(calls); + + const erc7412Tx = await withERC7412(network, allCalls, 'useRepay', 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/RepayAllDebt.tsx b/liquidity/ui/src/pages/Manage/RepayAllDebt.tsx index 29b23c8ce..7adf35c62 100644 --- a/liquidity/ui/src/pages/Manage/RepayAllDebt.tsx +++ b/liquidity/ui/src/pages/Manage/RepayAllDebt.tsx @@ -2,16 +2,15 @@ import { Button, Flex, Text } from '@chakra-ui/react'; import { Amount } from '@snx-v3/Amount'; import { LiquidityPosition } from '@snx-v3/useLiquidityPosition'; import { wei } from '@synthetixio/wei'; -import { getUSDCAddress, isBaseAndromeda } from '@snx-v3/isBaseAndromeda'; +import { getRepayerContract, getUSDCAddress, isBaseAndromeda } from '@snx-v3/isBaseAndromeda'; import { useNetwork } from '@snx-v3/useBlockchain'; -import { useRepayBaseAndromeda } from '../../../../lib/useRepayBaseAndromeda'; import { useParams, useSearchParams } from 'react-router-dom'; import { useCallback, useMemo } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { useApprove } from '@snx-v3/useApprove'; import { parseUnits } from '@snx-v3/format'; -import { useSpotMarketProxy } from '../../../../lib/useSpotMarketProxy'; import { useTokenBalance } from '@snx-v3/useTokenBalance'; +import { useClearDebt } from '../../../../lib/useClearDebt'; export const RepayAllDebt = ({ liquidityPosition }: { liquidityPosition: LiquidityPosition }) => { const { network } = useNetwork(); @@ -24,8 +23,6 @@ export const RepayAllDebt = ({ liquidityPosition }: { liquidityPosition: Liquidi const debtExists = liquidityPosition.debt.gt(0.01); const currentDebt = debtExists ? liquidityPosition.debt : wei(0); - const { data: SpotMarketProxy } = useSpotMarketProxy(); - const { data: tokenBalance } = useTokenBalance( isBase ? getUSDCAddress(network?.id) : liquidityPosition.tokenAddress ); @@ -39,12 +36,12 @@ export const RepayAllDebt = ({ liquidityPosition }: { liquidityPosition: Liquidi exec: execRepay, settle: settleRepay, isLoading, - } = useRepayBaseAndromeda({ + } = useClearDebt({ accountId: searchParams.get('accountId') || '', poolId: params.poolId, collateralTypeAddress: liquidityPosition?.tokenAddress, - debtChange: currentDebt, availableUSDCollateral: liquidityPosition.accountCollateral.availableCollateral, + debt: currentDebt, }); const { @@ -53,8 +50,9 @@ export const RepayAllDebt = ({ liquidityPosition }: { liquidityPosition: Liquidi isLoading: approvalLoading, } = useApprove({ contractAddress: getUSDCAddress(network?.id), - amount: parseUnits(currentDebt.toString(), 6).add(1), - spender: SpotMarketProxy?.address, + //slippage for approval + amount: parseUnits(currentDebt.toString(), 6).mul(110).div(100), + spender: getRepayerContract(network?.id), }); const submit = useCallback(async () => { diff --git a/yarn.lock b/yarn.lock index 8db5f1fb1..a0353aea6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6770,6 +6770,29 @@ __metadata: languageName: unknown linkType: soft +"@snx-v3/useClearDebt@workspace:liquidity/lib/useClearDebt": + version: 0.0.0-use.local + resolution: "@snx-v3/useClearDebt@workspace:liquidity/lib/useClearDebt" + dependencies: + "@snx-v3/fetchPythPrices": "workspace:*" + "@snx-v3/isBaseAndromeda": "workspace:*" + "@snx-v3/tsHelpers": "workspace:*" + "@snx-v3/txnReducer": "workspace:*" + "@snx-v3/useAllCollateralPriceIds": "workspace:*" + "@snx-v3/useBlockchain": "workspace:*" + "@snx-v3/useCoreProxy": "workspace:*" + "@snx-v3/useGasOptions": "workspace:*" + "@snx-v3/useGasPrice": "workspace:*" + "@snx-v3/useGasSpeed": "workspace:*" + "@snx-v3/useUSDProxy": "workspace:*" + "@snx-v3/withERC7412": "workspace:*" + "@synthetixio/wei": "npm:^2.74.4" + "@tanstack/react-query": "npm:^5.8.3" + ethers: "npm:^5.7.2" + react: "npm:^18.2.0" + languageName: unknown + linkType: soft + "@snx-v3/useCollateralPrices@workspace:*, @snx-v3/useCollateralPrices@workspace:liquidity/lib/useCollateralPrices": version: 0.0.0-use.local resolution: "@snx-v3/useCollateralPrices@workspace:liquidity/lib/useCollateralPrices"