Skip to content

Commit ee54f88

Browse files
authored
All Swaps Modals Refactor + CoW Protocol Adapters support (#2739)
1 parent f7e9ebb commit ee54f88

File tree

217 files changed

+18306
-9915
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

217 files changed

+18306
-9915
lines changed

.eslintignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ src/locales/
2020
*.md
2121
*.log
2222
*.lock
23+
24+
src/components/transactions/Swap/backup/**/*.*

.npmrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Uncomment for CoW Preview Releases
2+
3+
# @cowprotocol:registry=https://npm.pkg.github.com
4+
# always-auth=true
5+
# # registry=https://registry.npmjs.org/
6+
# //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@
3535
"@aave/math-utils": "1.36.1",
3636
"@aave/react": "0.6.1",
3737
"@amplitude/analytics-browser": "^2.13.0",
38+
"@cowprotocol/sdk-app-data": "4.1.6",
39+
"@cowprotocol/cow-sdk": "7.1.1",
40+
"@cowprotocol/sdk-flash-loans": "1.5.2",
41+
"@cowprotocol/sdk-viem-adapter": "0.2.0",
3842
"@bgd-labs/aave-address-book": "^4.36.0",
39-
"@cowprotocol/app-data": "^3.1.0",
40-
"@cowprotocol/cow-sdk": "6.3.3",
4143
"@emotion/cache": "11.10.3",
4244
"@emotion/react": "11.10.4",
4345
"@emotion/server": "latest",
@@ -157,4 +159,4 @@
157159
"budgetPercentIncreaseRed": 20,
158160
"showDetails": true
159161
}
160-
}
162+
}

pages/_app.page.tsx

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ import { AddressBlocked } from 'src/components/AddressBlocked';
1616
import { Meta } from 'src/components/Meta';
1717
import { TransactionEventHandler } from 'src/components/TransactionEventHandler';
1818
import { GasStationProvider } from 'src/components/transactions/GasStation/GasStationProvider';
19-
import { CowOrderToast } from 'src/components/transactions/Switch/cowprotocol/CowOrderToast';
19+
import { CowOrderToast } from 'src/components/transactions/Swap/modals/result/CowOrderToast';
2020
import { AppDataProvider } from 'src/hooks/app-data-provider/useAppDataProvider';
21-
import { CowOrderToastProvider } from 'src/hooks/useCowOrderToast';
2221
import { ModalContextProvider } from 'src/hooks/useModal';
22+
import { SwapOrdersTrackingProvider } from 'src/hooks/useSwapOrdersTracking';
2323
import { Web3ContextProvider } from 'src/libs/web3-data-provider/Web3Provider';
2424
import { useRootStore } from 'src/store/root';
2525
import { SharedDependenciesProvider } from 'src/ui-config/SharedDependenciesProvider';
@@ -31,16 +31,22 @@ import createEmotionCache from '../src/createEmotionCache';
3131
import { AppGlobalStyles } from '../src/layouts/AppGlobalStyles';
3232
import { LanguageProvider } from '../src/libs/LanguageProvider';
3333

34-
const SwitchModal = dynamic(() =>
35-
import('src/components/transactions/Switch/SwitchModal').then((module) => module.SwitchModal)
34+
const SwapModal = dynamic(() =>
35+
import('src/components/transactions/Swap/modals/SwapModal').then((module) => module.SwapModal)
3636
);
3737

3838
const CollateralSwapModal = dynamic(() =>
39-
import('src/components/transactions/Switch/CollateralSwap/CollateralSwapModal').then(
39+
import('src/components/transactions/Swap/modals/CollateralSwapModal').then(
4040
(module) => module.CollateralSwapModal
4141
)
4242
);
4343

44+
const DebtSwapModal = dynamic(() =>
45+
import('src/components/transactions/Swap/modals/DebtSwapModal').then(
46+
(module) => module.DebtSwapModal
47+
)
48+
);
49+
4450
const BridgeModal = dynamic(() =>
4551
import('src/components/transactions/Bridge/BridgeModal').then((module) => module.BridgeModal)
4652
);
@@ -53,16 +59,6 @@ const ClaimRewardsModal = dynamic(() =>
5359
(module) => module.ClaimRewardsModal
5460
)
5561
);
56-
const CollateralChangeModal = dynamic(() =>
57-
import('src/components/transactions/CollateralChange/CollateralChangeModal').then(
58-
(module) => module.CollateralChangeModal
59-
)
60-
);
61-
const DebtSwitchModal = dynamic(() =>
62-
import('src/components/transactions/DebtSwitch/DebtSwitchModal').then(
63-
(module) => module.DebtSwitchModal
64-
)
65-
);
6662
const EmodeModal = dynamic(() =>
6763
import('src/components/transactions/Emode/EmodeModal').then((module) => module.EmodeModal)
6864
);
@@ -160,7 +156,7 @@ export default function MyApp(props: MyAppProps) {
160156
<Web3ContextProvider>
161157
<AppGlobalStyles>
162158
<AddressBlocked>
163-
<CowOrderToastProvider>
159+
<SwapOrdersTrackingProvider>
164160
<ModalContextProvider>
165161
<SharedDependenciesProvider>
166162
<AppDataProvider>
@@ -170,24 +166,25 @@ export default function MyApp(props: MyAppProps) {
170166
<WithdrawModal />
171167
<BorrowModal />
172168
<RepayModal />
173-
<CollateralChangeModal />
174-
<DebtSwitchModal />
175169
<ClaimRewardsModal />
176170
<EmodeModal />
177171
<FaucetModal />
178172
<TransactionEventHandler />
179-
<SwitchModal />
180-
<CollateralSwapModal />
181173
<StakingMigrateModal />
182174
<BridgeModal />
183175
<ReadOnlyModal />
184-
<CowOrderToast />
176+
177+
{/* Swap Modals */}
178+
<SwapModal />
179+
<CollateralSwapModal />
180+
<DebtSwapModal />
185181
<CancelCowOrderModal />
182+
<CowOrderToast />
186183
</GasStationProvider>
187184
</AppDataProvider>
188185
</SharedDependenciesProvider>
189186
</ModalContextProvider>
190-
</CowOrderToastProvider>
187+
</SwapOrdersTrackingProvider>
191188
</AddressBlocked>
192189
</AppGlobalStyles>
193190
</Web3ContextProvider>

src/components/MarketSwitcher.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,13 @@ type MarketLogoProps = {
7878
export const MarketLogo = ({ size, logo, testChainName, sx }: MarketLogoProps) => {
7979
return (
8080
<Box sx={{ mr: 2, width: size, height: size, position: 'relative', ...sx }}>
81-
<img src={logo} alt="" width="100%" height="100%" />
81+
<img
82+
src={logo}
83+
alt=""
84+
width="100%"
85+
height="100%"
86+
style={{ display: 'block', objectFit: 'contain', objectPosition: 'center center' }}
87+
/>
8288

8389
{testChainName && (
8490
<Tooltip title={testChainName} arrow>

src/components/StyledToggleButton.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,31 @@ const CustomTxModalToggleButton = styled(ToggleButton)<ToggleButtonProps>(({ the
3737
color: theme.palette.text.muted,
3838
borderRadius: '4px',
3939

40+
// Selected (active) state
4041
'&.Mui-selected, &.Mui-selected:hover': {
4142
border: `1px solid ${theme.palette.other.standardInputLine}`,
4243
backgroundColor: '#FFFFFF',
4344
borderRadius: '4px !important',
44-
},
45-
46-
'&.Mui-selected, &.Mui-disabled': {
45+
color: theme.palette.background.header,
4746
zIndex: 100,
4847
height: '100%',
4948
display: 'flex',
5049
justifyContent: 'center',
50+
},
51+
52+
// Disabled but NOT selected: keep readable text with slight fade
53+
'&.Mui-disabled:not(.Mui-selected)': {
54+
color: theme.palette.text.secondary,
55+
opacity: 0.55,
56+
},
57+
58+
// Disabled + selected: preserve the selected look
59+
'&.Mui-disabled.Mui-selected': {
60+
border: `1px solid ${theme.palette.other.standardInputLine}`,
61+
backgroundColor: '#FFFFFF',
62+
borderRadius: '4px !important',
5163
color: theme.palette.background.header,
64+
opacity: 1,
5265
},
5366
})) as typeof ToggleButton;
5467

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Trans } from '@lingui/macro';
2+
3+
import { TextWithTooltip } from '../TextWithTooltip';
4+
5+
export const EstimatedCostsForLimitSwapTooltip = () => {
6+
return (
7+
<TextWithTooltip variant="caption" text={<Trans>Estimated Costs & Fees</Trans>}>
8+
<Trans>
9+
These are the estimated costs associated with your limit swap, including costs and fees.
10+
Consider these costs when setting your order amounts to help optimize execution and maximize
11+
your chances of filling the order.
12+
</Trans>
13+
</TextWithTooltip>
14+
);
15+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Trans } from '@lingui/macro';
2+
3+
import { TextWithTooltip } from '../TextWithTooltip';
4+
5+
export const ExecutionFeeTooltip = () => {
6+
return (
7+
<TextWithTooltip variant="caption" text={<Trans>Execution fee</Trans>}>
8+
<Trans>This is the fee for executing position changes, set by governance.</Trans>
9+
</TextWithTooltip>
10+
);
11+
};
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: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,58 @@
1-
import { 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';
6-
import { getEthersProvider } from 'src/libs/web3-data-provider/adapters/EthersAdapter';
7-
import { ActionFields, TransactionHistoryItem } from 'src/modules/history/types';
6+
import { ActionName, SwapActionFields, TransactionHistoryItem } from 'src/modules/history/types';
7+
import { useRootStore } from 'src/store/root';
88
import { getErrorTextFromError, TxAction } from 'src/ui-config/errorMapping';
99
import { wagmiConfig } from 'src/ui-config/wagmiConfig';
10+
import { updateCowOrderStatus } from 'src/utils/swapAdapterHistory';
11+
import { getWalletClient } from 'wagmi/actions';
1012

13+
import { COW_ENV, getCowAdapter } from '../Swap/helpers/cow';
1114
import { TxActionsWrapper } from '../TxActionsWrapper';
1215

16+
// TODO: check with cow if we can cancel adapters orders
1317
interface CancelCowOrderActionsProps {
14-
cowOrder: TransactionHistoryItem<ActionFields['CowSwap']>;
18+
cowOrder: TransactionHistoryItem<SwapActionFields[ActionName.Swap]>;
1519
blocked: boolean;
1620
}
1721

1822
export const CancelCowOrderActions = ({ cowOrder, blocked }: CancelCowOrderActionsProps) => {
1923
const { isWrongNetwork } = useIsWrongNetwork(cowOrder.chainId);
2024
const { mainTxState, loadingTxns, setMainTxState, setTxError } = useModalContext();
2125
const queryClient = useQueryClient();
26+
const account = useRootStore((state) => state.account);
2227

2328
const action = async () => {
2429
try {
2530
setMainTxState({ ...mainTxState, loading: true });
26-
const provider = getEthersProvider(wagmiConfig, { chainId: cowOrder.chainId });
27-
const signer = (await provider).getSigner();
28-
const orderBookApi = new OrderBookApi({ chainId: cowOrder.chainId });
31+
32+
const adapter = await getCowAdapter(cowOrder.chainId);
33+
AdapterContext.getInstance().setAdapter(adapter);
34+
const orderBookApi = new OrderBookApi({ chainId: cowOrder.chainId, env: COW_ENV });
35+
const walletClient = await getWalletClient(wagmiConfig, { chainId: cowOrder.chainId });
36+
37+
if (!walletClient || !walletClient.account) {
38+
throw new Error('Wallet not connected for signing');
39+
}
2940
const { signature, signingScheme } = await OrderSigningUtils.signOrderCancellation(
3041
cowOrder.id,
3142
cowOrder.chainId,
32-
signer
43+
walletClient
3344
);
3445
await orderBookApi.sendSignedOrderCancellations({
3546
orderUids: [cowOrder.id],
3647
signature,
3748
signingScheme,
3849
});
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+
3956
queryClient.invalidateQueries({ queryKey: 'transactionHistory' });
4057
setTimeout(() => {
4158
setMainTxState({

0 commit comments

Comments
 (0)