The full swap flow
A token swap through an AMM like those powering Doodledapp.com involves a specific sequence of calls. Understanding this flow is essential for building frontends and for auditing swap contracts.
solidity
// Step 1: User approves the router to spend their tokenIn
token.approve(address(router), amountIn);
// Step 2: User calls the router's swap function
router.swapExactTokensForTokens(
amountIn, // exact amount in
minAmountOut, // minimum acceptable output (slippage protection)
path, // [tokenA, tokenB] or [tokenA, WETH, tokenB] for multi-hop
msg.sender, // recipient
deadline // transaction must execute before this timestamp
);
// Step 3: Inside the router
// - Pulls tokenIn from user via transferFrom
// - Calls pair.swap() with computed output amount
// - Pair transfers tokenOut to recipientExact input vs. exact output swaps
solidity
// Exact input: specify how much you're spending, get at least minOut
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
// Exact output: specify how much you want, spend at most maxIn
function swapTokensForExactTokens(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);Multi-hop swaps
Not every token pair has a direct pool. Routers can chain through multiple pools to find a path.
solidity
// Swap TOKEN_A → WETH → TOKEN_B
address[] memory path = new address[](3);
path[0] = TOKEN_A;
path[1] = WETH;
path[2] = TOKEN_B;
router.swapExactTokensForTokens(amountIn, minOut, path, recipient, deadline);Calling a swap from another contract
Protocols routinely call swap functions programmatically — for example, auto-compounding yield, liquidating collateral, or rebalancing portfolios.
solidity
import '@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol';
contract AutoCompound {
IUniswapV2Router02 public immutable router;
constructor(address _router) {
router = IUniswapV2Router02(_router);
}
function swapRewards(
address rewardToken,
address targetToken,
uint256 amount
) internal {
IERC20(rewardToken).approve(address(router), amount);
address[] memory path = new address[](2);
path[0] = rewardToken;
path[1] = targetToken;
router.swapExactTokensForTokens(
amount,
0, // accept any output (use slippage protection in production!)
path,
address(this),
block.timestamp
);
}
}Slippage and deadlines in production
Always set a meaningful minAmountOut and deadline in production. Zero slippage protection invites sandwich attacks. An expired deadline prevents a transaction from being included in a block far later than expected (when market conditions may have changed dramatically).
solidity
// 0.5% slippage tolerance
uint256 amountOutMin = (expectedAmountOut * 995) / 1000;
// Must execute within 20 minutes
uint256 deadline = block.timestamp + 20 minutes;