Skip to content

Commit c51908f

Browse files
committed
docs
1 parent 7ea5ea9 commit c51908f

33 files changed

+441
-68
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
## Swap module
2+
3+
![Swap module architecture](./docs/swap-modal-architecture.png)
4+
5+
### Goals
6+
- **Consistent UI/UX** across all swap-related modals
7+
- **Shared logic, no duplication** via a common base content and shared helpers
8+
- **Multi‑provider** architecture (CoW Protocol, ParaSwap; easy to extend)
9+
- **Composable and customizable** components
10+
11+
### High‑level flow
12+
1. A top‑level modal (`modals/*Modal.tsx`) renders a corresponding content component in `modals/request/*ModalContent.tsx`.
13+
2. Each `*ModalContent` composes `BaseSwapModalContent`, which wires data, inputs, warnings, details, and actions.
14+
3. Provider selection is automatic via `helpers/shared/provider.helpers.ts` (`getSwitchProvider`).
15+
4. Quotes are fetched with `hooks/useSwapQuote` and refreshed periodically; flash‑loan and health‑factor logic is handled by `hooks/useFlowSelector`.
16+
5. The UI is composed from shared inputs, pre/post warnings, per‑flow details, and provider‑specific actions.
17+
18+
### Directory overview
19+
- `modals/`
20+
- Entry points displayed to users: `SwapModal.tsx`, `CollateralSwapModal.tsx`, `DebtSwapModal.tsx`, etc.
21+
- `modals/request/` contains `*ModalContent.tsx` for each flow and a `BaseSwapModalContent` used by all.
22+
- `modals/result/` shows success/receipt views.
23+
24+
- `inputs/`
25+
- `SwapInputs.tsx` orchestrates order inputs; `MarketOrderInputs.tsx` and `LimitOrderInputs.tsx` are the two modes.
26+
- `inputs/shared/` and `inputs/primitives/` host reusable input UI.
27+
28+
- `warnings/`
29+
- Pre‑ and post‑input warnings: `SwapPreInputWarnings.tsx`, `SwapPostInputWarnings.tsx`.
30+
- Flow‑ and rule‑specific implementations live under `warnings/preInputs/` and `warnings/postInputs/`.
31+
32+
- `details/`
33+
- Transaction overview blocks: `SwapDetails.tsx`, plus flow variants like `CollateralSwapDetails.tsx`, `DebtSwapDetails.tsx`, `RepayWithCollateralDetails.tsx`, `WithdrawAndSwapDetails.tsx`.
34+
- Provider‑specific cost breakdowns, e.g. `CowCostsDetails.tsx`.
35+
36+
- `actions/`
37+
- The submit/CTA area and transaction execution.
38+
- Flow containers: `SwapActions`, `CollateralSwapActions`, `DebtSwapActions`, `RepayWithCollateralActions`, `WithdrawAndSwapActions`.
39+
- Provider adapters implement the same surface per flow, e.g. `SwapActionsViaCoW.tsx`, `SwapActionsViaParaswap.tsx`, and their flow equivalents under each subfolder.
40+
- `approval/useSwapTokenApproval.ts` handles allowance flows when needed.
41+
42+
- `hooks/`
43+
- `useSwapQuote` retrieves and normalizes quotes (CoW/ParaSwap) and writes into `SwapState`.
44+
- `useFlowSelector` computes health‑factor effects and decides when to use flash‑loans.
45+
- Other utilities: `useSwapOrderAmounts`, `useSwapGasEstimation`, `useSlippageSelector`, `useMaxNativeAmount`, `useProtocolReserves`, `useUserContext`.
46+
47+
- `helpers/`
48+
- `shared/` contains provider‑agnostic logic (provider selection, formatting, misc) and `gasEstimation.helpers.ts`.
49+
- `cow/` and `paraswap/` contain provider integrations (rates, order helpers, misc).
50+
51+
- `errors/`
52+
- UI components and mapping/helpers for error presentation; organized by provider and shared concerns.
53+
54+
- `constants/`
55+
- Provider and feature flags: `cow.constants.ts`, `paraswap.constants.ts`, `limitOrders.constants.ts`, `shared.constants.ts`.
56+
57+
- `types/`
58+
- Shared domain types: params, state, tokens, quotes, and re‑exports.
59+
60+
- `shared/`
61+
- Small UI pieces reused across modals (e.g., `OrderTypeSelector`, `SwapModalTitle`).
62+
63+
- `analytics/`
64+
- Analytics constants and hooks to track swap interactions.
65+
66+
- `backup/`
67+
- Legacy/previous implementation kept for reference during the migration to the new modular structure.
68+
69+
### Extending to a new provider
70+
1. Add provider constants in `constants/` and integration helpers under `helpers/<provider>/`.
71+
2. Plug the provider into `helpers/shared/provider.helpers.ts` so it can be selected.
72+
3. Implement `*ActionsVia<Provider>.tsx` for each supported flow under `actions/`.
73+
4. Optionally add provider‑specific details/warnings and wire them in the `*Details.tsx`/warnings where appropriate.
74+
75+
### Data model
76+
- `types/state.types.ts` defines the authoritative `SwapState` used across the module.
77+
- `types/params.types.ts` carries immutable parameters from the modal entry.
78+
- Quotes unify to a `quote.types.ts` shape so the UI remains provider‑agnostic.
79+
80+
### Notes
81+
- `useSwapQuote` refreshes quotes every 30s by default, paused during action execution.
82+
- Some flows invert the quote route (e.g., `DebtSwap`, `RepayWithCollateral`); this is encapsulated in `useSwapQuote` and consumers stay agnostic.
83+
84+
### Core types (documented)
85+
- State: see `types/state.types.ts` (`SwapState`, `TokensSwapState`, `ProtocolSwapState`).
86+
- Params: see `types/params.types.ts` (`SwapParams`).
87+
- Tokens and quotes: see `types/tokens.types.ts`, `types/quote.types.ts`, and shared enums in `types/shared.types.ts`.
88+
89+

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import { ExpiryToSecondsMap, SwapParams, SwapState } from '../../types';
2020
import { useSwapTokenApproval } from '../approval/useSwapTokenApproval';
2121

2222
export const CollateralSwapActionsViaCowAdapters = ({
23-
params,
2423
state,
2524
setState,
2625
trackingHandlers,
@@ -190,7 +189,6 @@ export const CollateralSwapActionsViaCowAdapters = ({
190189
const result = await tradingSdk.postLimitOrder(limitOrder, orderPostParams.swapSettings);
191190

192191
trackingHandlers.trackSwap();
193-
params.invalidateAppState(); // todo remove, running when finished
194192
setMainTxState({
195193
loading: false,
196194
success: true,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,8 @@ export const CollateralSwapActionsViaParaswapAdapters = ({
191191
chainId: state.chainId,
192192
}
193193
);
194-
trackingHandlers.trackSwap(); // TODO: check this happening in all actions
195-
params.invalidateAppState(); // TODO: check this happening in all actions
194+
trackingHandlers.trackSwap();
195+
params.invalidateAppState();
196196
setMainTxState({
197197
txHash: response.hash,
198198
loading: false,

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,16 @@ import { useSwapGasEstimation } from '../../hooks/useSwapGasEstimation';
2020
import { ExpiryToSecondsMap, isProtocolSwapState, SwapParams, SwapState } from '../../types';
2121
import { useSwapTokenApproval } from '../approval/useSwapTokenApproval';
2222

23+
/**
24+
* Debt swap via CoW Protocol Flashloan Adapters.
25+
*
26+
* Flow summary:
27+
* 1) Approve delegation on the destination variable debt token (permit supported)
28+
* 2) Compute flashloan fee and sell amount; we temporarily borrow to close existing debt
29+
* 3) Create a LIMIT order INVERTED relative to the UI: new debt asset -> old debt asset
30+
* 4) Post order with adapter swap settings; adapter executes the repay + reborrow atomically
31+
*/
2332
export const DebtSwapActionsViaCoW = ({
24-
params,
2533
state,
2634
setState,
2735
trackingHandlers,
@@ -198,7 +206,6 @@ export const DebtSwapActionsViaCoW = ({
198206
const result = await tradingSdk.postLimitOrder(limitOrder, orderPostParams.swapSettings);
199207

200208
trackingHandlers.trackSwap();
201-
params.invalidateAppState(); // move to sucess state
202209
setMainTxState({
203210
loading: false,
204211
success: true,

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { valueToBigNumber } from '@aave/math-utils';
22
import { Trans } from '@lingui/macro';
3-
import { useQueryClient } from '@tanstack/react-query';
43
import { parseUnits } from 'ethers/lib/utils';
54
import { Dispatch } from 'react';
65
import { TxActionsWrapper } from 'src/components/transactions/TxActionsWrapper';
@@ -9,7 +8,6 @@ import { useModalContext } from 'src/hooks/useModal';
98
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
109
import { useRootStore } from 'src/store/root';
1110
import { getErrorTextFromError, TxAction } from 'src/ui-config/errorMapping';
12-
import { queryKeysFactory } from 'src/ui-config/queries';
1311
import { useShallow } from 'zustand/shallow';
1412

1513
import { TrackAnalyticsHandlers } from '../../analytics/useTrackAnalytics';
@@ -18,9 +16,20 @@ import { useSwapGasEstimation } from '../../hooks/useSwapGasEstimation';
1816
import { isParaswapRates, ProtocolSwapParams, ProtocolSwapState, SwapState } from '../../types';
1917
import { useSwapTokenApproval } from '../approval/useSwapTokenApproval';
2018

19+
/**
20+
* Debt swap via ParaSwap Adapter.
21+
*
22+
* Flow summary:
23+
* 1) Approve delegation on the destination variable debt token to the adapter
24+
* 2) Build a ParaSwap route INVERTED relative to the UI: new debt asset -> old debt asset
25+
* - Inversion is required because we're acquiring new debt to repay old debt
26+
* 3) Call the Debt Switch adapter with swap calldata and permit/delegation signature
27+
*/
2128
export const DebtSwapActionsViaParaswap = ({
2229
state,
30+
params,
2331
setState,
32+
trackingHandlers,
2433
}: {
2534
params: ProtocolSwapParams;
2635
state: ProtocolSwapState;
@@ -38,7 +47,6 @@ export const DebtSwapActionsViaParaswap = ({
3847
const { approvalTxState, mainTxState, loadingTxns, setMainTxState, setTxError } =
3948
useModalContext();
4049
const { sendTx } = useWeb3Context();
41-
const queryClient = useQueryClient();
4250

4351
// TODO: CHECK LIMIT ORDERS BUY ORDERS
4452

@@ -157,8 +165,8 @@ export const DebtSwapActionsViaParaswap = ({
157165
.toString(),
158166
});
159167

160-
queryClient.invalidateQueries({ queryKey: queryKeysFactory.pool });
161-
queryClient.invalidateQueries({ queryKey: queryKeysFactory.gho });
168+
params.invalidateAppState();
169+
trackingHandlers.trackSwap();
162170
} catch (error) {
163171
console.error('error', error);
164172
const parsedError = getErrorTextFromError(error, TxAction.GAS_ESTIMATION, false);

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,18 @@ import { useSwapGasEstimation } from '../../hooks/useSwapGasEstimation';
1919
import { ExpiryToSecondsMap, SwapParams, SwapState } from '../../types';
2020
import { useSwapTokenApproval } from '../approval/useSwapTokenApproval';
2121

22+
/**
23+
* Repay-with-collateral via CoW Protocol Flashloan Adapters.
24+
*
25+
* Flow summary:
26+
* 1) Approve collateral aToken (permit supported) to the CoW flashloan adapter
27+
* 2) Compute flashloan fee and sell amount to sign
28+
* 3) Create a LIMIT order INVERTED relative to the UI: collateral -> debt asset
29+
* - The order kind depends on processed side; inversion is required because
30+
* we swap the available collateral to acquire the debt asset to repay
31+
* 4) Post order with adapter-provided swap settings; adapter orchestrates repay
32+
*/
2233
export const RepayWithCollateralActionsViaCoW = ({
23-
params,
2434
state,
2535
setState,
2636
trackingHandlers,
@@ -191,7 +201,6 @@ export const RepayWithCollateralActionsViaCoW = ({
191201
const result = await tradingSdk.postLimitOrder(limitOrder, orderPostParams.swapSettings);
192202

193203
trackingHandlers.trackSwap();
194-
params.invalidateAppState();
195204
setMainTxState({
196205
loading: false,
197206
success: true,

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ import { useSwapGasEstimation } from '../../hooks/useSwapGasEstimation';
1616
import { isParaswapRates, ProtocolSwapParams, ProtocolSwapState, SwapState } from '../../types';
1717
import { useSwapTokenApproval } from '../approval/useSwapTokenApproval';
1818

19+
/**
20+
* Repay-with-collateral via ParaSwap Adapter.
21+
*
22+
* Flow summary:
23+
* 1) Approve aToken (or use permit) to the RepayWithCollateral adapter
24+
* 2) Build a ParaSwap route INVERTED relative to the UI: collateral aToken -> debt token
25+
* - We invert because the protocol action consumes collateral to acquire the debt asset
26+
* 3) Compute repay amounts with slippage; detect `repayAllDebt` when balance covers max with margin
27+
* 4) Call adapter with swap calldata + optional permit to execute repay and residual handling
28+
*/
1929
export const RepayWithCollateralActionsViaParaswap = ({
2030
params,
2131
state,
@@ -46,6 +56,7 @@ export const RepayWithCollateralActionsViaParaswap = ({
4656
-state.destinationToken.decimals
4757
).toString(),
4858
0
59+
// Adds margin to account future incremental so better ux
4960
),
5061
state.destinationToken.decimals
5162
);
@@ -57,8 +68,7 @@ export const RepayWithCollateralActionsViaParaswap = ({
5768
token: state.destinationToken.addressToSwap, // aToken
5869
symbol: state.destinationToken.symbol,
5970
decimals: state.destinationToken.decimals,
60-
// amount: normalizeBN(state.outputAmount, -state.destinationToken.decimals).toString(), // TODO: need to add a margin to account time for better ux?
61-
amount: collateralToRepayAmountToApprove.toString(), // TODO: need to add a margin to account time for better ux?
71+
amount: collateralToRepayAmountToApprove.toString(),
6272
spender: currentMarketData.addresses.REPAY_WITH_COLLATERAL_ADAPTER,
6373
setState,
6474
});

src/components/transactions/Swap/actions/SwapActions/SwapActionsViaCoW.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ import { useSwapGasEstimation } from '../../hooks/useSwapGasEstimation';
3333
import { isCowProtocolRates, OrderType, SwapParams, SwapState } from '../../types';
3434
import { useSwapTokenApproval } from '../approval/useSwapTokenApproval';
3535

36+
/**
37+
* Asset swap via CoW Protocol (Limit/Market orders).
38+
*
39+
* Process:
40+
* 1) Ensure token approval (with permit when possible) for the CoW Relayer. Handles smart contract wallets and native token flows.
41+
* 2) For tokens requiring approval, attempts onchain approval and reacts to possible failures or pending states.
42+
* 3) For ERC-20s supporting permit, attempts signature path unless already approved.
43+
* 4) Handles both normal EOA users and smart contract wallets (e.g. Gnosis Safe) with pre-sign or off-chain signatures as needed.
44+
* 5) Posts order to the CoW API (off-chain) or submits on-chain pre-sign transaction, depending on user/wallet.
45+
* 6) Tracks tx/analytics and updates transaction state accurately for UI.
46+
*
47+
* Automatically accounts for amount normalization, possible token decimal mismatches,
48+
* error states in approvals or post order flow, and UI feedback for each path.
49+
*/
3650
export const SwapActionsViaCoW = ({
3751
params,
3852
state,
@@ -78,9 +92,6 @@ export const SwapActionsViaCoW = ({
7892

7993
const slippageInPercent = state.slippage;
8094

81-
// TODO: decide amount to use
82-
// const sellAmountAccountingCosts = sellAmountWithCostsIncluded(state);
83-
// const buyAmountAccountingCosts = buyAmountWithCostsIncluded(state);
8495
const sellAmountAccountingCosts = state.sellAmountBigInt;
8596
const buyAmountAccountingCosts = state.buyAmountBigInt;
8697

@@ -329,7 +340,6 @@ export const SwapActionsViaCoW = ({
329340
}
330341

331342
trackingHandlers.trackSwap();
332-
params.invalidateAppState();
333343
};
334344

335345
return (

src/components/transactions/Swap/actions/SwapActions/SwapActionsViaParaswap.tsx

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
import { Trans } from '@lingui/macro';
2-
import { useQueryClient } from '@tanstack/react-query';
32
import { Dispatch, useEffect } from 'react';
43
import { TxActionsWrapper } from 'src/components/transactions/TxActionsWrapper';
54
import { useParaswapSellTxParams } from 'src/hooks/paraswap/useParaswapRates';
65
import { useModalContext } from 'src/hooks/useModal';
76
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
87
import { useRootStore } from 'src/store/root';
98
import { getErrorTextFromError, TxAction } from 'src/ui-config/errorMapping';
10-
import { queryKeysFactory } from 'src/ui-config/queries';
119
import { useShallow } from 'zustand/shallow';
1210

1311
import { TrackAnalyticsHandlers } from '../../analytics/useTrackAnalytics';
1412
import { useSwapGasEstimation } from '../../hooks/useSwapGasEstimation';
1513
import { isParaswapRates, SwapParams, SwapState } from '../../types';
1614
import { useSwapTokenApproval } from '../approval/useSwapTokenApproval';
1715

16+
/**
17+
* Simple asset swap via ParaSwap Adapter (non-position flow).
18+
* Prepares approval if needed and executes the route returned by useSwapQuote.
19+
*/
1820
export const SwapActionsViaParaswap = ({
1921
params,
2022
state,
@@ -26,20 +28,14 @@ export const SwapActionsViaParaswap = ({
2628
setState: Dispatch<Partial<SwapState>>;
2729
trackingHandlers: TrackAnalyticsHandlers;
2830
}) => {
29-
const [user, estimateGasLimit, addTransaction, currentMarketData] = useRootStore(
30-
useShallow((state) => [
31-
state.account,
32-
state.estimateGasLimit,
33-
state.addTransaction,
34-
state.currentMarketData,
35-
])
31+
const [user, estimateGasLimit, addTransaction] = useRootStore(
32+
useShallow((state) => [state.account, state.estimateGasLimit, state.addTransaction])
3633
);
3734

3835
const { mainTxState, loadingTxns, setMainTxState, setTxError, approvalTxState } =
3936
useModalContext();
4037

4138
const { sendTx } = useWeb3Context();
42-
const queryClient = useQueryClient();
4339
const { mutateAsync: fetchParaswapTxParams } = useParaswapSellTxParams(state.chainId);
4440

4541
const slippageInPercent = (Number(state.slippage) * 100).toString();
@@ -102,9 +98,9 @@ export const SwapActionsViaParaswap = ({
10298
loading: false,
10399
success: true,
104100
});
105-
queryClient.invalidateQueries({
106-
queryKey: queryKeysFactory.poolTokens(user, currentMarketData),
107-
});
101+
102+
params.invalidateAppState();
103+
trackingHandlers.trackSwap();
108104
} catch (error) {
109105
const parsedError = getErrorTextFromError(error, TxAction.MAIN_ACTION, false);
110106
setTxError(parsedError);
@@ -144,9 +140,6 @@ export const SwapActionsViaParaswap = ({
144140
actionsLoading: false,
145141
});
146142
}
147-
148-
params.invalidateAppState();
149-
trackingHandlers.trackSwap();
150143
};
151144

152145
// Track execution state to pause rate updates during actions

0 commit comments

Comments
 (0)