This project implements core components inspired by the Uniswap V2 decentralized exchange model, including Pair, Factory, and Router contracts that enable liquidity management, token swaps, and pool creation.
- Manages liquidity for two ERC20 tokens.
- Tracks reserves and enforces the constant product invariant (x \times y = k).
- Handles adding/removing liquidity.
- Allows token swaps between the two tokens.
A user wants to swap 100 DAI for USDC:
1. User calls the Router: router.swapExactTokensForTokens(...)
2. Router transfers 100 DAI to the Pair contract.
3. Router calls pair.swap(0, amountOutUSDC, to) on the Pair contract.
4. Pair executes the swap.
- Creates pairs of any two ERC20 tokens (sorted by address to avoid duplicates).
- Keeps track of all pairs in a mapping keyed by the token addresses.
- Deploys new Pair contracts using the
create2
opcode. - Emits an event when a new pair is created.
-
Deploys contracts with deterministic addresses based on:
- Deployer’s address (Factory contract)
- Contract bytecode
- A salt (unique value)
-
Predictable pair addresses allow:
- Prevention of duplicate pairs
- Clients and frontends to compute pair addresses before creation
- Improved security and UX
- Facilitates token swaps between any two ERC20 tokens.
- Routes swaps via direct pairs if they exist.
- Handles liquidity adding/removing (focus on swaps currently).
- Uses Factory contract to find pairs.
- Computes output amounts using the constant product formula.
- Transfers tokens correctly between user, router, and pairs.
1. User calls Router with input token, output token, and amount.
2. Router finds the pair from Factory.
3. Router transfers amountIn of input token from user to the Pair contract.
4. Router calls Pair's swap() specifying amountOut and output token.
5. Pair executes the swap, sending output tokens to user.
6. Router returns the amount out.
- Selects amount of Token A to add.
- Sets slippage tolerance (e.g., 1%).
const [reserve0, reserve1] = await pairContract.getReserves();
const token0 = await pairContract.token0();
let reserveA, reserveB;
if (tokenA.toLowerCase() === token0.toLowerCase()) {
reserveA = reserve0;
reserveB = reserve1;
} else {
reserveA = reserve1;
reserveB = reserve0;
}
function quote(amountA, reserveA, reserveB) {
return (amountA * reserveB) / reserveA;
}
const amountBDesired = quote(amountADesired, reserveA, reserveB);
const amountAMin = amountADesired * (1 - slippagePercent);
const amountBMin = amountBDesired * (1 - slippagePercent);
await routerContract.addLiquidity(
tokenA,
tokenB,
amountADesired,
amountBDesired,
amountAMin,
amountBMin,
userAddress
);
const lpBalance = await pairContract.balanceOf(userAddress);
const liquidity = lpBalance.mul(50).div(100); // 50%
const [reserve0, reserve1] = await pairContract.getReserves();
const totalSupply = await pairContract.totalSupply();
const token0 = await pairContract.token0();
const [reserveA, reserveB] =
tokenA.toLowerCase() === token0.toLowerCase()
? [reserve0, reserve1]
: [reserve1, reserve0];
const amountA = liquidity.mul(reserveA).div(totalSupply);
const amountB = liquidity.mul(reserveB).div(totalSupply);
const amountAMin = amountA.mul(100 - slippageTolerance * 100).div(100);
const amountBMin = amountB.mul(100 - slippageTolerance * 100).div(100);
await pairContract.approve(routerAddress, liquidity);
await routerContract.removeLiquidity(
tokenA,
tokenB,
liquidity,
amountAMin,
amountBMin,
userAddress
);
- Input Token: DAI
- Output Token: WETH
- Input Amount: 100 DAI
- Slippage Tolerance: 0.5%
- Swap Route: DAI → USDC → WETH
const path = [DAI, USDC, WETH];
const amountOut = await router.getAmountsOut(amountIn, path);
const minAmountOut = amountOut * (1 - slippage / 100);
await DAI.approve(routerAddress, amountIn);
await router.multiHopSwap(amountIn, minAmountOut, path);
- Router iterates through the swap path.
- Calls
swap
on each Pair contract. - Sends final output token to the user.
- Emits
MultiSwap(...)
event.
- Frontend shows confirmation with the received amount.
- Provides transaction link (e.g., to Etherscan).



This contract manages creation and storage of all Pools. It includes functions to:
- Create a new pool
- Query all existing pools
- Basic ERC20 contracts (e.g., TokenA, TokenB, TokenC)
- Tokens simulate liquidity and enable swapping
- Include minting functions for testing purposes
We will build 4 main pages:
Features:
- Select two tokens from a list (Token A and Token B)
- Enter initial amounts to create the pool ✔️
- Button to create the pool by calling the Factory contract ✔️
- Interface to mint token balances to your wallet (connect wallet and mint TokenA, TokenB, etc) ✔️
Features:
- List all available pools from the Factory contract ✔️
Each pool includes buttons to: - Add Liquidity: Deposit tokens into the pool
- Remove Liquidity: Withdraw tokens from the pool
Features:
- View pool info: tokens, reserves, volume ✔️
- Show user balances for each token in the pool
- Swap interface:
- Enter input amount
- Calculate expected output amount (simulation)
- Execute the swap
Priority: Last
Features:
- Select a token pair
- Enter input amount
- Show user’s balance
- Calculate expected output amount using possible routes
- Automatically execute the best swap route
- Dropdowns to select Token A and Token B ✔️
- Prevent selecting the same token for both ✔️
- Display token icons and symbols in the dropdown (optional, user-friendly)
- Check if a pool already exists for the selected token pair ✔️
- Show a warning message if the pool exists
- Disable or prevent pool creation if pool exists ✔️
- Button to trigger pool creation ✔️
- Show spinner or status message while transaction is pending ✔️
- Handle transaction errors (user rejection, on-chain errors) ✔️
- Show transaction hash after submission
- Show toast/message when transaction is confirmed ✔️
- Redirect to
/pool/[poolAddress]
page after confirmation ✔️
- Show appropriate toast messages for:
- Wallet not connected ✔️
- Same token selected
- Pool already exists
- Transaction errors
- Success
- UI to mint test tokens (if working on testnet/local dev)
⚠️ - Token balance display (optional but helpful)
- Responsive design
- Clear and clean error messages
- Smooth transitions or status indicators
This project is licensed under the MIT License. See the LICENSE file for details.