Token Swap System
Introduction
Token swap systems enable decentralized token exchanges through automated market makers (AMM). This tutorial explains how to implement a basic token swap system with core AMM functionality.
Features
- Constant product AMM
- Liquidity provision
- Token swapping
- Fee collection
- Price discovery
Contract Implementation
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
contract TokenSwap is ReentrancyGuard {
using SafeMath for uint256;
// Tokens in the pool
IERC20 public immutable token0;
IERC20 public immutable token1;
// Pool reserves
uint256 public reserve0;
uint256 public reserve1;
// Fee parameters
uint256 private constant FEE_DENOMINATOR = 1000;
uint256 private constant FEE = 3; // 0.3% fee
// Events
event Swap(
address indexed sender,
uint256 amount0In,
uint256 amount1In,
uint256 amount0Out,
uint256 amount1Out,
address indexed to
);
event AddLiquidity(
address indexed provider,
uint256 amount0,
uint256 amount1
);
event RemoveLiquidity(
address indexed provider,
uint256 amount0,
uint256 amount1
);
constructor(address _token0, address _token1) {
require(_token0 != address(0), "Invalid token0");
require(_token1 != address(0), "Invalid token1");
require(_token0 != _token1, "Same tokens");
token0 = IERC20(_token0);
token1 = IERC20(_token1);
}
// Add liquidity
function addLiquidity(uint256 amount0, uint256 amount1)
external
nonReentrant
returns (uint256 liquidity)
{
require(amount0 > 0 && amount1 > 0, "Insufficient amounts");
// Transfer tokens to contract
token0.transferFrom(msg.sender, address(this), amount0);
token1.transferFrom(msg.sender, address(this), amount1);
// Update reserves
reserve0 = reserve0.add(amount0);
reserve1 = reserve1.add(amount1);
emit AddLiquidity(msg.sender, amount0, amount1);
}
// Remove liquidity
function removeLiquidity(uint256 amount0, uint256 amount1)
external
nonReentrant
returns (uint256 amount0Out, uint256 amount1Out)
{
require(amount0 > 0 || amount1 > 0, "Insufficient amounts");
require(amount0 <= reserve0 && amount1 <= reserve1, "Exceeds reserves");
// Transfer tokens to user
if (amount0 > 0) {
token0.transfer(msg.sender, amount0);
}
if (amount1 > 0) {
token1.transfer(msg.sender, amount1);
}
// Update reserves
reserve0 = reserve0.sub(amount0);
reserve1 = reserve1.sub(amount1);
emit RemoveLiquidity(msg.sender, amount0, amount1);
return (amount0, amount1);
}
// Swap tokens
function swap(
address tokenIn,
uint256 amountIn,
uint256 amountOutMin,
address to
) external nonReentrant returns (uint256 amountOut) {
require(amountIn > 0, "Insufficient input amount");
require(to != address(0), "Invalid recipient");
require(
tokenIn == address(token0) || tokenIn == address(token1),
"Invalid token"
);
bool isToken0 = tokenIn == address(token0);
(IERC20 tokenInContract, IERC20 tokenOutContract) = isToken0
? (token0, token1)
: (token1, token0);
(uint256 reserveIn, uint256 reserveOut) = isToken0
? (reserve0, reserve1)
: (reserve1, reserve0);
// Transfer tokens to contract
tokenInContract.transferFrom(msg.sender, address(this), amountIn);
// Calculate amount out
uint256 amountInWithFee = amountIn.mul(FEE_DENOMINATOR.sub(FEE)).div(FEE_DENOMINATOR);
amountOut = getAmountOut(amountInWithFee, reserveIn, reserveOut);
require(amountOut >= amountOutMin, "Insufficient output amount");
// Transfer tokens to recipient
tokenOutContract.transfer(to, amountOut);
// Update reserves
_update(
token0.balanceOf(address(this)),
token1.balanceOf(address(this))
);
emit Swap(
msg.sender,
isToken0 ? amountIn : 0,
isToken0 ? 0 : amountIn,
isToken0 ? 0 : amountOut,
isToken0 ? amountOut : 0,
to
);
}
// Calculate output amount
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) public pure returns (uint256) {
require(amountIn > 0, "Insufficient input amount");
require(reserveIn > 0 && reserveOut > 0, "Insufficient liquidity");
uint256 numerator = amountIn.mul(reserveOut);
uint256 denominator = reserveIn.add(amountIn);
return numerator.div(denominator);
}
// Update reserves
function _update(uint256 balance0, uint256 balance1) private {
reserve0 = balance0;
reserve1 = balance1;
}
// Get reserves
function getReserves() public view returns (uint256, uint256) {
return (reserve0, reserve1);
}
}
Core Concepts
1. Automated Market Maker (AMM)
AMM is a decentralized trading mechanism that:
- Uses mathematical formulas
- Maintains liquidity pools
- Enables permissionless trading
- Provides continuous pricing
2. Constant Product Formula
The core formula x * y = k ensures:
- Price stability
- Liquidity availability
- Slippage protection
- Market efficiency
3. Liquidity Provision
Liquidity providers can:
- Add token pairs
- Earn trading fees
- Remove liquidity
- Track positions
4. Price Discovery
Price is determined by:
- Pool reserves
- Trade volumes
- Market dynamics
- Arbitrage activity
Security Considerations
Access Control
- Function permissions
- Transfer validation
- Balance checks
- Reentrancy protection
Price Protection
- Slippage limits
- Front-running prevention
- Price manipulation guards
- Reserve validation
Token Safety
- Address validation
- Transfer success checks
- Balance tracking
- Fee calculation
Pool Security
- Reserve updates
- Liquidity validation
- Emergency controls
- State consistency
Best Practices
Implementation
- Use SafeMath
- Implement nonReentrant
- Validate inputs
- Handle edge cases
Testing
- Unit tests
- Integration tests
- Scenario testing
- Security audits
Deployment
- Parameter verification
- Initial liquidity
- Fee settings
- Emergency controls
Monitoring
- Price tracking
- Volume monitoring
- Reserve tracking
- Event logging
Extended Features
Advanced Trading
- Multi-token swaps
- Flash swaps
- Price oracles
- Route optimization
Pool Management
- Dynamic fees
- Liquidity mining
- Pool parameters
- Emergency pause
Integration
- DEX aggregation
- Yield farming
- Lending protocols
- Governance systems
Analytics
- Price feeds
- Volume tracking
- Pool metrics
- User statistics
FAQ
General Questions
Q: What is a token swap?
A: A token swap system:
- Enables decentralized trading
- Uses AMM mechanism
- Provides liquidity pools
- Automates pricing
Q: Why use AMM?
A: Benefits include:
- Continuous liquidity
- Permissionless trading
- Automated pricing
- Low slippage
Technical Questions
Q: How to handle slippage?
A: Slippage control:
- Set minimum output
- Calculate price impact
- Monitor reserves
- Update frequently
Q: What about front-running?
A: Prevention measures:
- Minimum output
- Maximum slippage
- Transaction ordering
- MEV protection
Implementation Questions
Q: How to set parameters?
A: Consider:
- Pool size
- Token types
- Fee structure
- Market conditions
Q: What about upgrades?
A: Plan for:
- Contract migration
- State preservation
- Parameter updates
- Emergency procedures