Skip to content

Commit 19f8bf4

Browse files
authored
fix: undelegate with repay in single tx (#240)
1 parent 25b03c6 commit 19f8bf4

File tree

9 files changed

+223
-12
lines changed

9 files changed

+223
-12
lines changed

liquidity/components/UndelegateModal/UndelegateModal.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import { useContractErrorParser } from '@snx-v3/useContractErrorParser';
2626
import { ContractError } from '@snx-v3/ContractError';
2727
import { useQueryClient } from '@tanstack/react-query';
2828
import { useNetwork } from '@snx-v3/useBlockchain';
29+
import { useUndelegateBaseAndromeda } from '../../lib/useUndelegateBaseAndromeda';
30+
import { isBaseAndromeda } from '@snx-v3/isBaseAndromeda';
2931

3032
export const UndelegateModalUi: FC<{
3133
amount: Wei;
@@ -112,6 +114,14 @@ export const UndelegateModal: UndelegateModalProps = ({ onClose, isOpen, liquidi
112114
collateralChange,
113115
currentCollateral: currentCollateral,
114116
});
117+
const { exec: undelegateBaseAndromeda } = useUndelegateBaseAndromeda({
118+
accountId: params.accountId,
119+
poolId: params.poolId,
120+
collateralTypeAddress: liquidityPosition?.tokenAddress,
121+
collateralChange,
122+
currentCollateral: currentCollateral,
123+
liquidityPosition,
124+
});
115125

116126
const { data: CoreProxy } = useCoreProxy();
117127
const errorParserCoreProxy = useContractErrorParser(CoreProxy);
@@ -123,7 +133,11 @@ export const UndelegateModal: UndelegateModalProps = ({ onClose, isOpen, liquidi
123133
services: {
124134
[ServiceNames.undelegate]: async () => {
125135
try {
126-
await execUndelegate();
136+
if (isBaseAndromeda(network?.id, network?.preset)) {
137+
await undelegateBaseAndromeda();
138+
} else {
139+
await execUndelegate();
140+
}
127141
await queryClient.invalidateQueries({
128142
queryKey: [`${network?.id}-${network?.preset}`, 'LiquidityPosition'],
129143
exact: false,

liquidity/components/UndelegateModal/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"@snx-v3/ContractError": "workspace:*",
1010
"@snx-v3/ManagePositionContext": "workspace:*",
1111
"@snx-v3/Multistep": "workspace:*",
12+
"@snx-v3/isBaseAndromeda": "workspace:*",
1213
"@snx-v3/useBlockchain": "workspace:*",
1314
"@snx-v3/useCollateralTypes": "workspace:*",
1415
"@snx-v3/useContractErrorParser": "workspace:*",

liquidity/lib/useClearDebt/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
"@snx-v3/useGasOptions": "workspace:*",
1515
"@snx-v3/useGasPrice": "workspace:*",
1616
"@snx-v3/useGasSpeed": "workspace:*",
17-
"@snx-v3/useUSDProxy": "workspace:*",
1817
"@snx-v3/withERC7412": "workspace:*",
1918
"@synthetixio/wei": "^2.74.4",
2019
"@tanstack/react-query": "^5.8.3",

liquidity/lib/useClearDebt/useClearDebt.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@ import Wei from '@synthetixio/wei';
88
import { BigNumber, Contract } from 'ethers';
99
import { getGasPrice } from '@snx-v3/useGasPrice';
1010
import { useGasSpeed } from '@snx-v3/useGasSpeed';
11-
import { useUSDProxy } from '@snx-v3/useUSDProxy';
1211
import { notNil } from '@snx-v3/tsHelpers';
1312
import { withERC7412 } from '@snx-v3/withERC7412';
1413
import { useAllCollateralPriceIds } from '@snx-v3/useAllCollateralPriceIds';
1514
import { fetchPriceUpdates, priceUpdatesToPopulatedTx } from '@snx-v3/fetchPythPrices';
1615
import { useSpotMarketProxy } from '../useSpotMarketProxy';
1716
import { USDC_BASE_MARKET, getRepayerContract } from '@snx-v3/isBaseAndromeda';
1817

19-
const DEBT_REPAYER_ABI = [
18+
export const DEBT_REPAYER_ABI = [
2019
{
2120
inputs: [
2221
{ internalType: 'contract ISynthetixCore', name: 'synthetixCore', type: 'address' },
@@ -48,7 +47,6 @@ export const useClearDebt = ({
4847
}) => {
4948
const [txnState, dispatch] = useReducer(reducer, initialState);
5049
const { data: CoreProxy } = useCoreProxy();
51-
const { data: UsdProxy } = useUSDProxy();
5250
const { data: SpotMarketProxy } = useSpotMarketProxy();
5351
const { data: collateralPriceIds } = useAllCollateralPriceIds();
5452

@@ -67,7 +65,6 @@ export const useClearDebt = ({
6765
poolId &&
6866
accountId &&
6967
collateralTypeAddress &&
70-
UsdProxy &&
7168
SpotMarketProxy &&
7269
collateralPriceIds
7370
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './useUndelegateBaseAndromeda';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "@snx-v3/useUndelegateBaseAndromeda",
3+
"private": true,
4+
"main": "index.ts",
5+
"version": "0.0.1",
6+
"dependencies": {
7+
"@snx-v3/fetchPythPrices": "workspace:*",
8+
"@snx-v3/format": "workspace:*",
9+
"@snx-v3/isBaseAndromeda": "workspace:*",
10+
"@snx-v3/tsHelpers": "workspace:*",
11+
"@snx-v3/txnReducer": "workspace:*",
12+
"@snx-v3/useAllCollateralPriceIds": "workspace:*",
13+
"@snx-v3/useApprove": "workspace:*",
14+
"@snx-v3/useBlockchain": "workspace:*",
15+
"@snx-v3/useCoreProxy": "workspace:*",
16+
"@snx-v3/useGasOptions": "workspace:*",
17+
"@snx-v3/useGasPrice": "workspace:*",
18+
"@snx-v3/useGasSpeed": "workspace:*",
19+
"@snx-v3/useLiquidityPosition": "workspace:*",
20+
"@snx-v3/withERC7412": "workspace:*",
21+
"@synthetixio/wei": "^2.74.4",
22+
"@tanstack/react-query": "^5.8.3",
23+
"ethers": "^5.7.2",
24+
"react": "^18.2.0"
25+
}
26+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { useReducer } from 'react';
2+
import { useCoreProxy } from '@snx-v3/useCoreProxy';
3+
import { useMutation } from '@tanstack/react-query';
4+
import { useNetwork, useProvider, useSigner } from '@snx-v3/useBlockchain';
5+
import { initialState, reducer } from '@snx-v3/txnReducer';
6+
import Wei, { wei } from '@synthetixio/wei';
7+
import { BigNumber, Contract, PopulatedTransaction } from 'ethers';
8+
import { formatGasPriceForTransaction } from '@snx-v3/useGasOptions';
9+
import { getGasPrice } from '@snx-v3/useGasPrice';
10+
import { useGasSpeed } from '@snx-v3/useGasSpeed';
11+
import { withERC7412 } from '@snx-v3/withERC7412';
12+
import { useAllCollateralPriceIds } from '@snx-v3/useAllCollateralPriceIds';
13+
import { fetchPriceUpdates, priceUpdatesToPopulatedTx } from '@snx-v3/fetchPythPrices';
14+
import { LiquidityPosition } from '@snx-v3/useLiquidityPosition';
15+
import { useApprove } from '@snx-v3/useApprove';
16+
import { USDC_BASE_MARKET, getRepayerContract, getUSDCAddress } from '@snx-v3/isBaseAndromeda';
17+
import { parseUnits } from '@snx-v3/format';
18+
import { DEBT_REPAYER_ABI } from '../useClearDebt';
19+
import { useSpotMarketProxy } from '../useSpotMarketProxy';
20+
import { notNil } from '@snx-v3/tsHelpers';
21+
22+
export const useUndelegateBaseAndromeda = ({
23+
accountId,
24+
poolId,
25+
collateralTypeAddress,
26+
collateralChange,
27+
currentCollateral,
28+
liquidityPosition,
29+
}: {
30+
accountId?: string;
31+
poolId?: string;
32+
collateralTypeAddress?: string;
33+
currentCollateral: Wei;
34+
collateralChange: Wei;
35+
liquidityPosition?: LiquidityPosition;
36+
}) => {
37+
const [txnState, dispatch] = useReducer(reducer, initialState);
38+
const { data: CoreProxy } = useCoreProxy();
39+
const { data: SpotMarketProxy } = useSpotMarketProxy();
40+
41+
const signer = useSigner();
42+
const { gasSpeed } = useGasSpeed();
43+
const provider = useProvider();
44+
const { data: collateralPriceUpdates } = useAllCollateralPriceIds();
45+
const { network } = useNetwork();
46+
47+
const debtExists = liquidityPosition?.debt.gt(0.01);
48+
const currentDebt = debtExists && liquidityPosition ? liquidityPosition.debt : wei(0);
49+
50+
const { approve, requireApproval } = useApprove({
51+
contractAddress: getUSDCAddress(network?.id),
52+
//slippage for approval
53+
amount: parseUnits(currentDebt.toString(), 6).mul(110).div(100),
54+
spender: getRepayerContract(network?.id),
55+
});
56+
57+
const mutation = useMutation({
58+
mutationFn: async () => {
59+
if (!signer || !network || !provider) throw new Error('No signer or network');
60+
if (
61+
!(CoreProxy && poolId && collateralTypeAddress && collateralPriceUpdates && SpotMarketProxy)
62+
)
63+
return;
64+
if (collateralChange.eq(0)) return;
65+
if (currentCollateral.eq(0)) return;
66+
try {
67+
dispatch({ type: 'prompting' });
68+
69+
if (debtExists && requireApproval) {
70+
await approve(false);
71+
}
72+
73+
const transactions: Promise<PopulatedTransaction>[] = [];
74+
75+
if (debtExists) {
76+
const repayer = new Contract(getRepayerContract(network.id), DEBT_REPAYER_ABI, signer);
77+
78+
const depositDebtToRepay = repayer.populateTransaction.depositDebtToRepay(
79+
CoreProxy.address,
80+
SpotMarketProxy.address,
81+
accountId,
82+
poolId,
83+
collateralTypeAddress,
84+
USDC_BASE_MARKET
85+
);
86+
transactions.push(depositDebtToRepay);
87+
88+
const burn = CoreProxy.populateTransaction.burnUsd(
89+
BigNumber.from(accountId),
90+
BigNumber.from(poolId),
91+
collateralTypeAddress,
92+
currentDebt?.mul(110).div(100).toBN().toString() || '0'
93+
);
94+
transactions.push(burn);
95+
}
96+
97+
const populatedTxnPromised = CoreProxy.populateTransaction.delegateCollateral(
98+
BigNumber.from(accountId),
99+
BigNumber.from(poolId),
100+
collateralTypeAddress,
101+
currentCollateral.add(collateralChange).toBN(),
102+
wei(1).toBN()
103+
);
104+
105+
const walletAddress = await signer.getAddress();
106+
107+
const callsPromise = Promise.all([...transactions, populatedTxnPromised].filter(notNil));
108+
109+
const collateralPriceCallsPromise = fetchPriceUpdates(
110+
collateralPriceUpdates,
111+
network.isTestnet
112+
).then((signedData) =>
113+
priceUpdatesToPopulatedTx(walletAddress, collateralPriceUpdates, signedData)
114+
);
115+
const [calls, gasPrices, collateralPriceCalls] = await Promise.all([
116+
callsPromise,
117+
getGasPrice({ provider }),
118+
collateralPriceCallsPromise,
119+
]);
120+
const allCalls = collateralPriceCalls.concat(...calls);
121+
122+
const erc7412Tx = await withERC7412(
123+
network,
124+
allCalls,
125+
'useUndelegate',
126+
CoreProxy.interface
127+
);
128+
129+
const gasOptionsForTransaction = formatGasPriceForTransaction({
130+
gasLimit: erc7412Tx.gasLimit,
131+
gasPrices,
132+
gasSpeed,
133+
});
134+
135+
const txn = await signer.sendTransaction({ ...erc7412Tx, ...gasOptionsForTransaction });
136+
dispatch({ type: 'pending', payload: { txnHash: txn.hash } });
137+
138+
await txn.wait();
139+
dispatch({ type: 'success' });
140+
} catch (error: any) {
141+
dispatch({ type: 'error', payload: { error } });
142+
throw error;
143+
}
144+
},
145+
});
146+
return {
147+
mutation,
148+
txnState,
149+
settle: () => dispatch({ type: 'settled' }),
150+
isLoading: mutation.isPending,
151+
exec: mutation.mutateAsync,
152+
};
153+
};

liquidity/ui/src/pages/Manage/Undelegate.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import React, { FC, useContext } from 'react';
2222
import { useParams } from '@snx-v3/useParams';
2323
import { isBaseAndromeda } from '@snx-v3/isBaseAndromeda';
2424
import { useNetwork } from '@snx-v3/useBlockchain';
25-
import { RepayAllDebt } from './RepayAllDebt';
2625

2726
export const UndelegateUi: FC<{
2827
collateralChange: Wei;
@@ -190,10 +189,6 @@ export const Undelegate = ({ liquidityPosition }: { liquidityPosition?: Liquidit
190189
}
191190
const max = maxUndelegate();
192191

193-
if (liquidityPosition?.debt.gt(0.01) && isBaseAndromeda(network?.id, network?.preset)) {
194-
return <RepayAllDebt liquidityPosition={liquidityPosition} />;
195-
}
196-
197192
return (
198193
<UndelegateUi
199194
displaySymbol={collateralType.displaySymbol}

yarn.lock

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6189,6 +6189,7 @@ __metadata:
61896189
"@snx-v3/ContractError": "workspace:*"
61906190
"@snx-v3/ManagePositionContext": "workspace:*"
61916191
"@snx-v3/Multistep": "workspace:*"
6192+
"@snx-v3/isBaseAndromeda": "workspace:*"
61926193
"@snx-v3/useBlockchain": "workspace:*"
61936194
"@snx-v3/useCollateralTypes": "workspace:*"
61946195
"@snx-v3/useContractErrorParser": "workspace:*"
@@ -6784,7 +6785,6 @@ __metadata:
67846785
"@snx-v3/useGasOptions": "workspace:*"
67856786
"@snx-v3/useGasPrice": "workspace:*"
67866787
"@snx-v3/useGasSpeed": "workspace:*"
6787-
"@snx-v3/useUSDProxy": "workspace:*"
67886788
"@snx-v3/withERC7412": "workspace:*"
67896789
"@synthetixio/wei": "npm:^2.74.4"
67906790
"@tanstack/react-query": "npm:^5.8.3"
@@ -7214,6 +7214,31 @@ __metadata:
72147214
languageName: unknown
72157215
linkType: soft
72167216

7217+
"@snx-v3/useUndelegateBaseAndromeda@workspace:liquidity/lib/useUndelegateBaseAndromeda":
7218+
version: 0.0.0-use.local
7219+
resolution: "@snx-v3/useUndelegateBaseAndromeda@workspace:liquidity/lib/useUndelegateBaseAndromeda"
7220+
dependencies:
7221+
"@snx-v3/fetchPythPrices": "workspace:*"
7222+
"@snx-v3/format": "workspace:*"
7223+
"@snx-v3/isBaseAndromeda": "workspace:*"
7224+
"@snx-v3/tsHelpers": "workspace:*"
7225+
"@snx-v3/txnReducer": "workspace:*"
7226+
"@snx-v3/useAllCollateralPriceIds": "workspace:*"
7227+
"@snx-v3/useApprove": "workspace:*"
7228+
"@snx-v3/useBlockchain": "workspace:*"
7229+
"@snx-v3/useCoreProxy": "workspace:*"
7230+
"@snx-v3/useGasOptions": "workspace:*"
7231+
"@snx-v3/useGasPrice": "workspace:*"
7232+
"@snx-v3/useGasSpeed": "workspace:*"
7233+
"@snx-v3/useLiquidityPosition": "workspace:*"
7234+
"@snx-v3/withERC7412": "workspace:*"
7235+
"@synthetixio/wei": "npm:^2.74.4"
7236+
"@tanstack/react-query": "npm:^5.8.3"
7237+
ethers: "npm:^5.7.2"
7238+
react: "npm:^18.2.0"
7239+
languageName: unknown
7240+
linkType: soft
7241+
72177242
"@snx-v3/useVaultsData@workspace:*, @snx-v3/useVaultsData@workspace:liquidity/lib/useVaultsData":
72187243
version: 0.0.0-use.local
72197244
resolution: "@snx-v3/useVaultsData@workspace:liquidity/lib/useVaultsData"

0 commit comments

Comments
 (0)