Integrate DeFi protocols — Uniswap, Aave, Compound, Curve. Swaps, lending, liquidity, flash loans, and yield strategies.
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract SwapHelper {
ISwapRouter public constant router =
ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); // Mainnet
/// @notice Swap exact amount of tokenIn for tokenOut
function swapExactInput(
address tokenIn,
address tokenOut,
uint24 fee, // 500 (0.05%), 3000 (0.3%), 10000 (1%)
uint256 amountIn,
uint256 amountOutMin
) external returns (uint256 amountOut) {
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
IERC20(tokenIn).approve(address(router), amountIn);
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
.ExactInputSingleParams({
tokenIn: tokenIn,
tokenOut: tokenOut,
fee: fee,
recipient: msg.sender,
deadline: block.timestamp + 300,
amountIn: amountIn,
amountOutMinimum: amountOutMin, // slippage protection
sqrtPriceLimitX96: 0
});
amountOut = router.exactInputSingle(params);
}
}
function swapMultiHop(
address tokenIn, // first token in the path
bytes memory path, // abi.encodePacked(tokenA, fee1, tokenB, fee2, tokenC)
uint256 amountIn,
uint256 amountOutMin
) external returns (uint256) {
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
IERC20(tokenIn).approve(address(router), amountIn);
ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({
path: path,
recipient: msg.sender,
deadline: block.timestamp + 300,
amountIn: amountIn,
amountOutMinimum: amountOutMin
});
return router.exactInput(params);
}
import "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";
INonfungiblePositionManager public constant positionManager =
INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);
function addLiquidity(
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint256 amount0Desired,
uint256 amount1Desired
) external returns (uint256 tokenId) {
IERC20(token0).approve(address(positionManager), amount0Desired);
IERC20(token1).approve(address(positionManager), amount1Desired);
INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager
.MintParams({
token0: token0,
token1: token1,
fee: fee,
tickLower: tickLower,
tickUpper: tickUpper,
amount0Desired: amount0Desired,
amount1Desired: amount1Desired,
amount0Min: 0,
amount1Min: 0,
recipient: msg.sender,
deadline: block.timestamp + 300
});
(tokenId, , , ) = positionManager.mint(params);
}
V4 introduces hooks — custom logic at swap/liquidity lifecycle points:
import {BaseHook} from "v4-periphery/BaseHook.sol";
import {Hooks} from "v4-core/src/libraries/Hooks.sol";
contract MyHook is BaseHook {
function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: false,
afterInitialize: false,
beforeAddLiquidity: false,
afterAddLiquidity: false,
beforeRemoveLiquidity: false,
afterRemoveLiquidity: false,
beforeSwap: true, // Custom pre-swap logic
afterSwap: true, // Custom post-swap logic
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: false,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}
function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata)
external override returns (bytes4, BeforeSwapDelta, uint24)
{
// Custom logic: dynamic fees, TWAP oracle, limit orders, etc.
return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);
}
}
Uniswap V3 Router: 0xE592427A0AEce92De3Edee1F18E0157C05861564
Uniswap V3 Factory: 0x1F98431c8aD98523631AE4a59f267346ea31F984
Uniswap V3 Position Manager: 0xC36442b4a4522E871399CD717aBDD847Ab11FE88
Uniswap V3 Quoter V2: 0x61fFE014bA17989E743c5F6cB21bF9697530B21e
Universal Router: 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD
WETH: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
USDC: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
USDT: 0xdAC17F958D2ee523a2206206994597C13D831ec7
DAI: 0x6B175474E89094C44Da98b954EedeAC495271d0F
import {IPool} from "@aave/v3-core/contracts/interfaces/IPool.sol";
IPool constant POOL = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2); // Mainnet
function supply(address asset, uint256 amount) external {
IERC20(asset).transferFrom(msg.sender, address(this), amount);
IERC20(asset).approve(address(POOL), amount);
POOL.supply(asset, amount, msg.sender, 0);
// msg.sender receives aTokens (interest-bearing)
}
function borrow(address asset, uint256 amount, uint256 interestRateMode) external {
// interestRateMode: 1 = stable, 2 = variable
// Must have sufficient collateral supplied first
POOL.borrow(asset, amount, interestRateMode, 0, msg.sender);
}
import {IFlashLoanSimpleReceiver} from "@aave/v3-core/contracts/flashloan/base/FlashLoanSimpleReceiver.sol";
import {IPoolAddressesProvider} from "@aave/v3-core/contracts/interfaces/IPoolAddressesProvider.sol";
contract AaveFlashLoanReceiver is IFlashLoanSimpleReceiver {
IPoolAddressesProvider public constant _ADDRESSES_PROVIDER =
IPoolAddressesProvider(0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e);
function executeFlashLoan(address asset, uint256 amount) external {
IPool(_ADDRESSES_PROVIDER.getPool()).flashLoanSimple(
address(this),
asset,
amount,
"", // params
0 // referralCode
);
}
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external override returns (bool) {
// --- YOUR ARBITRAGE / LIQUIDATION LOGIC HERE ---
// You have `amount` of `asset` available
// Repay flash loan + premium (0.05% fee on Aave V3)
uint256 totalDebt = amount + premium;
IERC20(asset).approve(msg.sender, totalDebt); // msg.sender = Pool
return true;
}
function POOL() public view override returns (IPool) {
return IPool(_ADDRESSES_PROVIDER.getPool());
}
function ADDRESSES_PROVIDER() public view override returns (IPoolAddressesProvider) {
return _ADDRESSES_PROVIDER;
}
}
// Note: The constant is named `_ADDRESSES_PROVIDER` to avoid collision with
// the `ADDRESSES_PROVIDER()` function required by IFlashLoanSimpleReceiver.
Pool: 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2
PoolAddressesProvider: 0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e
Oracle: 0x54586bE62E3c3580375aE3723C145253060Ca0C2
Flash loan fee: 0.05% (5 bps)
import {IComet} from "./interfaces/IComet.sol";
IComet constant COMET_USDC = IComet(0xc3d688B66703497DAA19211EEdff47f25384cdc3); // cUSDCv3
// Supply collateral
function supplyCollateral(address asset, uint256 amount) external {
IERC20(asset).approve(address(COMET_USDC), amount);
COMET_USDC.supply(asset, amount);
}
// Borrow base asset (USDC)
function borrow(uint256 amount) external {
COMET_USDC.withdraw(COMET_USDC.baseToken(), amount);
}
// Check account health
function isLiquidatable(address account) external view returns (bool) {
return COMET_USDC.isLiquidatable(account);
}
interface ICurvePool {
function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256);
function get_dy(int128 i, int128 j, uint256 dx) external view returns (uint256);
}
ICurvePool constant THREE_POOL = ICurvePool(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7);
// 3pool indices: 0=DAI, 1=USDC, 2=USDT
function swapStables(uint256 amountIn, uint256 minOut) external {
IERC20(DAI).approve(address(THREE_POOL), amountIn);
uint256 amountOut = THREE_POOL.exchange(0, 1, amountIn, minOut); // DAI → USDC
}
// Frontend: fetch quote from 1inch API
const quote = await fetch(
`https://api.1inch.dev/swap/v6.0/1/swap?` +
`src=${tokenIn}&dst=${tokenOut}&amount=${amountIn}` +
`&from=${userAddress}&slippage=0.5`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
const { tx } = await quote.json();
// Execute swap via returned tx data
await signer.sendTransaction({
to: tx.to,
data: tx.data,
value: tx.value,
gasLimit: tx.gas,
});
const priceRoute = await fetch(
`https://apiv5.paraswap.io/prices?srcToken=${tokenIn}&destToken=${tokenOut}` +
`&amount=${amountIn}&network=1&srcDecimals=18&destDecimals=6`
);
const route = await priceRoute.json();
const txData = await fetch('https://apiv5.paraswap.io/transactions/1', {
method: 'POST',
body: JSON.stringify({
srcToken: tokenIn, destToken: tokenOut,
srcAmount: amountIn, slippage: 50, // 0.5%
priceRoute: route.priceRoute,
userAddress: userAddress,
}),
});
contract FlashArbitrage is IFlashLoanSimpleReceiver {
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address,
bytes calldata
) external override returns (bool) {
// Step 1: Buy cheap on DEX A
IERC20(asset).approve(address(routerA), amount);
uint256 tokenBAmount = routerA.swapExactTokensForTokens(
amount, 0, pathAtoB, address(this), block.timestamp
)[1];
// Step 2: Sell expensive on DEX B
IERC20(tokenB).approve(address(routerB), tokenBAmount);
uint256 profit = routerB.swapExactTokensForTokens(
tokenBAmount, 0, pathBtoA, address(this), block.timestamp
)[1];
// Step 3: Repay flash loan
uint256 totalDebt = amount + premium;
require(profit >= totalDebt, "No profit");
IERC20(asset).approve(msg.sender, totalDebt);
return true;
}
}
// Calculate minimum output with slippage tolerance
uint256 expectedOut = quoter.quoteExactInputSingle(tokenIn, tokenOut, fee, amountIn, 0);
uint256 minOut = expectedOut * (10000 - slippageBps) / 10000; // e.g., 50 bps = 0.5%
https://rpc.flashbots.net — private mempooldeadline = block.timestamp + 300 (5 min)amountOutMin = 0 — sandwich attack guaranteedhttps://rpc.mevblocker.io), FlashbotsmaxPriorityFeePerGas to avoid overpayingimport {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
contract YieldVault is ERC4626 {
constructor(IERC20 asset_) ERC4626(asset_) ERC20("Yield Vault", "yVault") {}
function totalAssets() public view override returns (uint256) {
// Return total value managed: deposited + yield earned
return IERC20(asset()).balanceOf(address(this)) + _calculateYield();
}
function _afterDeposit(uint256 assets, uint256) internal override {
// Deploy assets to yield source (Aave, Compound, etc.)
_deployToAave(assets);
}
function _beforeWithdraw(uint256 assets, uint256) internal override {
// Withdraw from yield source
_withdrawFromAave(assets);
}
}
User deposits → Vault → Strategy A (60% Aave)
→ Strategy B (40% Curve)
Harvest → Compound rewards → Rebalance
| Protocol | Fee | Paid by |
|---|---|---|
| Uniswap V3 | 0.01% / 0.05% / 0.3% / 1% (pool-specific) | Swapper |
| Aave V3 flash loan | 0.05% (5 bps) | Borrower |
| Aave V3 borrow | Variable APR (market-driven) | Borrower |
| Compound V3 | Variable APR | Borrower |
| Curve | 0.04% swap fee (most pools) | Swapper |
| 1inch | No protocol fee (aggregator) | — |
| Balancer V2 | Pool-specific (0.01-10%) | Swapper |
// test/DeFiFork.t.sol
contract DeFiForkTest is Test {
uint256 mainnetFork;
function setUp() public {
mainnetFork = vm.createFork(vm.envString("ETH_RPC_URL"), 19500000);
vm.selectFork(mainnetFork);
}
function test_aaveSupplyAndBorrow() public {
address user = makeAddr("user");
deal(USDC, user, 10_000e6);
vm.startPrank(user);
IERC20(USDC).approve(address(POOL), 10_000e6);
POOL.supply(USDC, 10_000e6, user, 0);
// Borrow ETH against USDC collateral
POOL.borrow(WETH, 1e18, 2, 0, user);
assertGt(IERC20(WETH).balanceOf(user), 0);
vm.stopPrank();
}
function test_uniswapSwap() public {
address user = makeAddr("user");
deal(WETH, user, 10e18);
vm.startPrank(user);
IERC20(WETH).approve(address(router), 10e18);
uint256 usdcOut = router.exactInputSingle(
ISwapRouter.ExactInputSingleParams({
tokenIn: WETH, tokenOut: USDC, fee: 3000,
recipient: user, deadline: block.timestamp,
amountIn: 10e18, amountOutMinimum: 1, sqrtPriceLimitX96: 0
})
);
assertGt(usdcOut, 0);
vm.stopPrank();
}
}
# Run fork tests
forge test --fork-url $ETH_RPC_URL --match-contract DeFiForkTest -vvv