Skip to content

Commit e55c49c

Browse files
committed
cancel adapter orders on chain via tx
1 parent e6b7150 commit e55c49c

File tree

18 files changed

+225
-18
lines changed

18 files changed

+225
-18
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { OrderStatus, SupportedChainId } from '@cowprotocol/cow-sdk';
2+
import { Trans } from '@lingui/macro';
3+
import { useQueryClient } from '@tanstack/react-query';
4+
import { Interface } from 'ethers/lib/utils';
5+
import { useIsWrongNetwork } from 'src/hooks/useIsWrongNetwork';
6+
import { useModalContext } from 'src/hooks/useModal';
7+
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
8+
import {
9+
ActionName,
10+
CowSwapSubset,
11+
isCowSwapSubset,
12+
SwapActionFields,
13+
TransactionHistoryItem,
14+
} from 'src/modules/history/types';
15+
import { useRootStore } from 'src/store/root';
16+
import { getErrorTextFromError, TxAction } from 'src/ui-config/errorMapping';
17+
import { updateCowOrderStatus } from 'src/utils/swapAdapterHistory';
18+
19+
import { ADAPTER_FACTORY } from '../Swap/constants/cow.constants';
20+
import { TxActionsWrapper } from '../TxActionsWrapper';
21+
22+
interface CancelAdapterOrderActionsProps {
23+
cowOrder: TransactionHistoryItem<
24+
| SwapActionFields[ActionName.DebtSwap]
25+
| SwapActionFields[ActionName.RepayWithCollateral]
26+
| SwapActionFields[ActionName.CollateralSwap]
27+
>;
28+
blocked: boolean;
29+
}
30+
31+
// ABI for cancelInstance function
32+
const ADAPTER_ABI = ['function cancelInstance(address instance) external'];
33+
34+
export const CancelAdapterOrderActions = ({
35+
cowOrder,
36+
blocked,
37+
}: CancelAdapterOrderActionsProps) => {
38+
const { isWrongNetwork } = useIsWrongNetwork(cowOrder.chainId);
39+
const { mainTxState, loadingTxns, setMainTxState, setTxError } = useModalContext();
40+
const { sendTx } = useWeb3Context();
41+
const queryClient = useQueryClient();
42+
const account = useRootStore((state) => state.account);
43+
44+
const action = async () => {
45+
try {
46+
setMainTxState({ ...mainTxState, loading: true });
47+
48+
// Type guard to ensure we have a CowSwapSubset with adapter fields
49+
if (!isCowSwapSubset(cowOrder)) {
50+
throw new Error('Order is not a CoW swap order');
51+
}
52+
53+
// At this point TypeScript knows cowOrder is CowSwapSubset, but we need to assert it has adapter fields
54+
const cowSwapOrder = cowOrder as CowSwapSubset;
55+
56+
if (!cowSwapOrder.adapterInstanceAddress) {
57+
throw new Error('Adapter instance address not found');
58+
}
59+
60+
const adapterInterface = new Interface(ADAPTER_ABI);
61+
62+
const factoryAddress = ADAPTER_FACTORY[cowOrder.chainId as SupportedChainId];
63+
64+
if (!factoryAddress) {
65+
throw new Error('Factory address not found for this chain');
66+
}
67+
68+
const data = adapterInterface.encodeFunctionData('cancelInstance', [
69+
cowSwapOrder.adapterInstanceAddress,
70+
]);
71+
72+
const txResponse = await sendTx({
73+
to: factoryAddress,
74+
data,
75+
chainId: cowOrder.chainId,
76+
});
77+
78+
await txResponse.wait(1);
79+
80+
// Update order status to cancelled in local storage
81+
if (account && cowSwapOrder.orderId) {
82+
updateCowOrderStatus(
83+
cowOrder.chainId,
84+
account,
85+
cowSwapOrder.orderId,
86+
OrderStatus.CANCELLED
87+
);
88+
}
89+
90+
queryClient.invalidateQueries({ queryKey: 'transactionHistory' });
91+
setMainTxState({
92+
...mainTxState,
93+
loading: false,
94+
success: true,
95+
txHash: txResponse.hash,
96+
});
97+
} catch (error) {
98+
const parsedError = getErrorTextFromError(error, TxAction.MAIN_ACTION, false);
99+
setTxError(parsedError);
100+
setMainTxState({
101+
txHash: undefined,
102+
loading: false,
103+
});
104+
}
105+
};
106+
107+
return (
108+
<TxActionsWrapper
109+
isWrongNetwork={isWrongNetwork}
110+
handleAction={action}
111+
actionText={<Trans>Cancel order</Trans>}
112+
actionInProgressText={<Trans>Cancelling order...</Trans>}
113+
blocked={blocked}
114+
mainTxState={mainTxState}
115+
requiresApproval={false}
116+
preparingTransactions={loadingTxns}
117+
/>
118+
);
119+
};

src/components/transactions/CancelCowOrder/CancelCowOrderActions.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import { AdapterContext, OrderBookApi, OrderSigningUtils } from '@cowprotocol/cow-sdk';
1+
import { AdapterContext, OrderBookApi, OrderSigningUtils, OrderStatus } from '@cowprotocol/cow-sdk';
22
import { Trans } from '@lingui/macro';
33
import { useQueryClient } from '@tanstack/react-query';
44
import { useIsWrongNetwork } from 'src/hooks/useIsWrongNetwork';
55
import { useModalContext } from 'src/hooks/useModal';
66
import { ActionName, SwapActionFields, TransactionHistoryItem } from 'src/modules/history/types';
7+
import { useRootStore } from 'src/store/root';
78
import { getErrorTextFromError, TxAction } from 'src/ui-config/errorMapping';
89
import { wagmiConfig } from 'src/ui-config/wagmiConfig';
10+
import { updateCowOrderStatus } from 'src/utils/swapAdapterHistory';
911
import { getWalletClient } from 'wagmi/actions';
1012

1113
import { COW_ENV, getCowAdapter } from '../Swap/helpers/cow';
@@ -21,6 +23,7 @@ export const CancelCowOrderActions = ({ cowOrder, blocked }: CancelCowOrderActio
2123
const { isWrongNetwork } = useIsWrongNetwork(cowOrder.chainId);
2224
const { mainTxState, loadingTxns, setMainTxState, setTxError } = useModalContext();
2325
const queryClient = useQueryClient();
26+
const account = useRootStore((state) => state.account);
2427

2528
const action = async () => {
2629
try {
@@ -44,6 +47,12 @@ export const CancelCowOrderActions = ({ cowOrder, blocked }: CancelCowOrderActio
4447
signature,
4548
signingScheme,
4649
});
50+
51+
// Update order status to cancelled in local storage
52+
if (account && cowOrder.id) {
53+
updateCowOrderStatus(cowOrder.chainId, account, cowOrder.id, OrderStatus.CANCELLED);
54+
}
55+
4756
queryClient.invalidateQueries({ queryKey: 'transactionHistory' });
4857
setTimeout(() => {
4958
setMainTxState({

src/components/transactions/CancelCowOrder/CancelCowOrderModal.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ import { CancelCowOrderModalContent } from './CancelCowOrderModalContent';
77

88
export const CancelCowOrderModal = () => {
99
const { type, close, args } = useModalContext() as ModalContextType<{
10-
cowOrder: TransactionHistoryItem<SwapActionFields[ActionName.Swap]>;
10+
cowOrder: TransactionHistoryItem<
11+
| SwapActionFields[ActionName.Swap]
12+
| SwapActionFields[ActionName.CollateralSwap]
13+
| SwapActionFields[ActionName.DebtSwap]
14+
| SwapActionFields[ActionName.RepayWithCollateral]
15+
| SwapActionFields[ActionName.WithdrawAndSwap]
16+
>;
1117
}>;
1218
return (
1319
<BasicModal open={type === ModalType.CancelCowOrder} setOpen={close}>

src/components/transactions/CancelCowOrder/CancelCowOrderModalContent.tsx

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,30 @@ import { Typography } from '@mui/material';
33
import { useIsWrongNetwork } from 'src/hooks/useIsWrongNetwork';
44
import { useModalContext } from 'src/hooks/useModal';
55
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
6-
import { ActionName, SwapActionFields, TransactionHistoryItem } from 'src/modules/history/types';
6+
import {
7+
ActionName,
8+
isCowSwapSubset,
9+
SwapActionFields,
10+
TransactionHistoryItem,
11+
} from 'src/modules/history/types';
712
import { getNetworkConfig } from 'src/utils/marketsAndNetworksConfig';
813
import { formatUnits } from 'viem';
914

1015
import { BaseSuccessView } from '../FlowCommons/BaseSuccess';
1116
import { GasEstimationError } from '../FlowCommons/GasEstimationError';
1217
import { DetailsNumberLine, DetailsTextLine, TxModalDetails } from '../FlowCommons/TxModalDetails';
1318
import { ChangeNetworkWarning } from '../Warnings/ChangeNetworkWarning';
19+
import { CancelAdapterOrderActions } from './CancelAdapterOrderActions';
1420
import { CancelCowOrderActions } from './CancelCowOrderActions';
1521

1622
interface CancelCowOrderModalContentProps {
17-
cowOrder: TransactionHistoryItem<SwapActionFields[ActionName.Swap]>;
23+
cowOrder: TransactionHistoryItem<
24+
| SwapActionFields[ActionName.Swap]
25+
| SwapActionFields[ActionName.CollateralSwap]
26+
| SwapActionFields[ActionName.DebtSwap]
27+
| SwapActionFields[ActionName.RepayWithCollateral]
28+
| SwapActionFields[ActionName.WithdrawAndSwap]
29+
>;
1830
}
1931

2032
export const CancelCowOrderModalContent = ({ cowOrder }: CancelCowOrderModalContentProps) => {
@@ -26,8 +38,11 @@ export const CancelCowOrderModalContent = ({ cowOrder }: CancelCowOrderModalCont
2638
const showNetworkWarning = isWrongNetwork && !readOnlyMode;
2739

2840
if (mainTxState.success) {
41+
// Show explorer link if txHash exists (adapter cancellations have txHash)
42+
const hasTxHash = !!mainTxState.txHash;
43+
2944
return (
30-
<BaseSuccessView hideTx={true}>
45+
<BaseSuccessView hideTx={!hasTxHash} txHash={mainTxState.txHash}>
3146
<Typography sx={{ mt: 4 }} variant="h2">
3247
<Trans>Cancellation submited</Trans>
3348
</Typography>
@@ -43,10 +58,18 @@ export const CancelCowOrderModalContent = ({ cowOrder }: CancelCowOrderModalCont
4358
<Trans>Cancel order</Trans>
4459
</Typography>
4560
<Typography sx={{ mt: 2, mb: 4 }} variant="description" color="text.secondary">
46-
<Trans>
47-
This is an off-chain operation. Keep in mind that a solver may already have filled your
48-
order.
49-
</Trans>
61+
{isCowSwapSubset(cowOrder) && cowOrder.usedAdapter ? (
62+
<Trans>
63+
This will cancel the order via an on-chain transaction. Note that the order will not
64+
be marked as cancelled in the CoW Protocol system, but will remain open and expire
65+
naturally. Keep in mind that a solver may already have filled your order.
66+
</Trans>
67+
) : (
68+
<Trans>
69+
This is an off-chain operation. Keep in mind that a solver may already have filled
70+
your order.
71+
</Trans>
72+
)}
5073
</Typography>
5174
<DetailsTextLine
5275
description="Order ID"
@@ -65,7 +88,23 @@ export const CancelCowOrderModalContent = ({ cowOrder }: CancelCowOrderModalCont
6588
/>
6689
</TxModalDetails>
6790
{txError && <GasEstimationError txError={txError} />}
68-
<CancelCowOrderActions cowOrder={cowOrder} blocked={false} />
91+
{isCowSwapSubset(cowOrder) && cowOrder.usedAdapter && cowOrder.adapterInstanceAddress ? (
92+
<CancelAdapterOrderActions
93+
cowOrder={
94+
cowOrder as TransactionHistoryItem<
95+
| SwapActionFields[ActionName.DebtSwap]
96+
| SwapActionFields[ActionName.RepayWithCollateral]
97+
| SwapActionFields[ActionName.CollateralSwap]
98+
>
99+
}
100+
blocked={false}
101+
/>
102+
) : (
103+
<CancelCowOrderActions
104+
cowOrder={cowOrder as TransactionHistoryItem<SwapActionFields[ActionName.Swap]>}
105+
blocked={false}
106+
/>
107+
)}
69108
</>
70109
);
71110
};

src/components/transactions/Swap/actions/CollateralSwap/CollateralSwapActionsViaCoWAdapters.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,8 @@ export const CollateralSwapActionsViaCowAdapters = ({
277277
name: state.buyAmountToken.symbol,
278278
decimals: state.buyAmountToken.decimals,
279279
},
280+
adapterInstanceAddress: instanceAddress,
281+
usedAdapter: true, // CollateralSwap via adapters always uses adapter (flashloan)
280282
srcAmount: state.sellAmountBigInt.toString(),
281283
destAmount: state.buyAmountBigInt.toString(),
282284
});

src/components/transactions/Swap/actions/DebtSwap/DebtSwapActionsViaCoW.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,8 @@ export const DebtSwapActionsViaCoW = ({
283283
name: state.buyAmountToken.symbol,
284284
decimals: state.buyAmountToken.decimals,
285285
},
286+
adapterInstanceAddress: instanceAddress,
287+
usedAdapter: true, // DebtSwap always uses adapter
286288
srcAmount: state.sellAmountBigInt.toString(),
287289
destAmount: state.buyAmountBigInt.toString(),
288290
});

src/components/transactions/Swap/actions/RepayWithCollateral/RepayWithCollateralActionsViaCoW.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ export const RepayWithCollateralActionsViaCoW = ({
282282
name: state.buyAmountToken.symbol,
283283
decimals: state.buyAmountToken.decimals,
284284
},
285+
adapterInstanceAddress: instanceAddress,
286+
usedAdapter: true, // RepayWithCollateral always uses adapter
285287
srcAmount: state.sellAmountBigInt.toString(),
286288
destAmount: state.buyAmountBigInt.toString(),
287289
});

src/components/transactions/Swap/constants/cow.constants.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ export const HOOK_ADAPTER_PER_TYPE: Record<AaveFlashLoanType, Record<SupportedCh
4646
},
4747
};
4848

49+
export const ADAPTER_FACTORY: Record<SupportedChainId, string> = {
50+
[SupportedChainId.MAINNET]: '0x43c658Ea38bBfD897706fDb35e2468ef5D8F6927',
51+
[SupportedChainId.GNOSIS_CHAIN]: '0x43c658Ea38bBfD897706fDb35e2468ef5D8F6927',
52+
[SupportedChainId.ARBITRUM_ONE]: '0x43c658Ea38bBfD897706fDb35e2468ef5D8F6927',
53+
[SupportedChainId.AVALANCHE]: '0x43c658Ea38bBfD897706fDb35e2468ef5D8F6927',
54+
[SupportedChainId.BNB]: '0x43c658Ea38bBfD897706fDb35e2468ef5D8F6927',
55+
[SupportedChainId.POLYGON]: '0x43c658Ea38bBfD897706fDb35e2468ef5D8F6927',
56+
[SupportedChainId.SEPOLIA]: '0x43c658Ea38bBfD897706fDb35e2468ef5D8F6927',
57+
[SupportedChainId.BASE]: '0x43c658Ea38bBfD897706fDb35e2468ef5D8F6927',
58+
[SupportedChainId.LENS]: '0x43c658Ea38bBfD897706fDb35e2468ef5D8F6927',
59+
[SupportedChainId.LINEA]: '0x43c658Ea38bBfD897706fDb35e2468ef5D8F6927',
60+
[SupportedChainId.PLASMA]: '0x43c658Ea38bBfD897706fDb35e2468ef5D8F6927',
61+
};
62+
4963
export const COW_UNSUPPORTED_ASSETS: Partial<
5064
Record<SwapType | 'ALL', Partial<Record<SupportedChainId, string[] | 'ALL'>>>
5165
> = {

src/hooks/useTransactionHistory.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,8 @@ export const useTransactionHistory = ({ isFilterActive }: { isFilterActive: bool
440440
protocol: 'cow',
441441
orderId: e.orderId,
442442
chainId: e.chainId,
443+
adapterInstanceAddress: e.adapterInstanceAddress,
444+
usedAdapter: e.usedAdapter,
443445
}));
444446

445447
const localParaswapTxns: TransactionHistoryItemUnion[] = (

src/locales/el/messages.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)