DeFi Protocol Templates
Production-ready templates for common DeFi protocols including staking, AMMs, governance, and flash loans.
When to Use This Skill
- Building staking platforms with reward distribution
- Implementing AMM (Automated Market Maker) protocols
- Creating governance token systems
- Integrating flash loan functionality
Staking Contract
solidity
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";import "@openzeppelin/contracts/security/ReentrancyGuard.sol";import "@openzeppelin/contracts/access/Ownable.sol";
contract StakingRewards is ReentrancyGuard, Ownable { IERC20 public stakingToken; IERC20 public rewardsToken;
uint256 public rewardRate = 100; // Rewards per second uint256 public lastUpdateTime; uint256 public rewardPerTokenStored;
mapping(address => uint256) public userRewardPerTokenPaid; mapping(address => uint256) public rewards; mapping(address => uint256) public balances;
uint256 private _totalSupply;
event Staked(address indexed user, uint256 amount); event Withdrawn(address indexed user, uint256 amount); event RewardPaid(address indexed user, uint256 reward);
constructor(address _stakingToken, address _rewardsToken) { stakingToken = IERC20(_stakingToken); rewardsToken = IERC20(_rewardsToken); }
modifier updateReward(address account) { rewardPerTokenStored = rewardPerToken(); lastUpdateTime = block.timestamp;
if (account != address(0)) { rewards[account] = earned(account); userRewardPerTokenPaid[account] = rewardPerTokenStored; } _; }
function rewardPerToken() public view returns (uint256) { if (_totalSupply == 0) { return rewardPerTokenStored; } return rewardPerTokenStored + ((block.timestamp - lastUpdateTime) * rewardRate * 1e18) / _totalSupply; }
function earned(address account) public view returns (uint256) { return (balances[account] * (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18 + rewards[account]; }
function stake(uint256 amount) external nonReentrant updateReward(msg.sender) { require(amount > 0, "Cannot stake 0"); _totalSupply += amount; balances[msg.sender] += amount; stakingToken.transferFrom(msg.sender, address(this), amount); emit Staked(msg.sender, amount); }
function withdraw(uint256 amount) public nonReentrant updateReward(msg.sender) { require(amount > 0, "Cannot withdraw 0"); _totalSupply -= amount; balances[msg.sender] -= amount; stakingToken.transfer(msg.sender, amount); emit Withdrawn(msg.sender, amount); }
function getReward() public nonReentrant updateReward(msg.sender) { uint256 reward = rewards[msg.sender]; if (reward > 0) { rewards[msg.sender] = 0; rewardsToken.transfer(msg.sender, reward); emit RewardPaid(msg.sender, reward); } }
function exit() external { withdraw(balances[msg.sender]); getReward(); }}AMM (Automated Market Maker)
solidity
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract SimpleAMM { IERC20 public token0; IERC20 public token1;
uint256 public reserve0; uint256 public reserve1;
uint256 public totalSupply; mapping(address => uint256) public balanceOf;
event Mint(address indexed to, uint256 amount); event Burn(address indexed from, uint256 amount); event Swap(address indexed trader, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out);
constructor(address _token0, address _token1) { token0 = IERC20(_token0); token1 = IERC20(_token1); }
function addLiquidity(uint256 amount0, uint256 amount1) external returns (uint256 shares) { token0.transferFrom(msg.sender, address(this), amount0); token1.transferFrom(msg.sender, address(this), amount1);
if (totalSupply == 0) { shares = sqrt(amount0 * amount1); } else { shares = min( (amount0 * totalSupply) / reserve0, (amount1 * totalSupply) / reserve1 ); }
require(shares > 0, "Shares = 0"); _mint(msg.sender, shares); _update( token0.balanceOf(address(this)), token1.balanceOf(address(this)) );
emit Mint(msg.sender, shares); }
function removeLiquidity(uint256 shares) external returns (uint256 amount0, uint256 amount1) { uint256 bal0 = token0.balanceOf(address(this)); uint256 bal1 = token1.balanceOf(address(this));
amount0 = (shares * bal0) / totalSupply; amount1 = (shares * bal1) / totalSupply;
require(amount0 > 0 && amount1 > 0, "Amount0 or amount1 = 0");
_burn(msg.sender, shares); _update(bal0 - amount0, bal1 - amount1);
token0.transfer(msg.sender, amount0); token1.transfer(msg.sender, amount1);
emit Burn(msg.sender, shares); }
function swap(address tokenIn, uint256 amountIn) external returns (uint256 amountOut) { require(tokenIn == address(token0) || tokenIn == address(token1), "Invalid token");
bool isToken0 = tokenIn == address(token0); (IERC20 tokenIn_, IERC20 tokenOut, uint256 resIn, uint256 resOut) = isToken0 ? (token0, token1, reserve0, reserve1) : (token1, token0, reserve1, reserve0);
tokenIn_.transferFrom(msg.sender, address(this), amountIn);
// 0.3% fee uint256 amountInWithFee = (amountIn * 997) / 1000; amountOut = (resOut * amountInWithFee) / (resIn + amountInWithFee);
tokenOut.transfer(msg.sender, amountOut);
_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); }
function _mint(address to, uint256 amount) private { balanceOf[to] += amount; totalSupply += amount; }
function _burn(address from, uint256 amount) private { balanceOf[from] -= amount; totalSupply -= amount; }
function _update(uint256 res0, uint256 res1) private { reserve0 = res0; reserve1 = res1; }
function sqrt(uint256 y) private pure returns (uint256 z) { if (y > 3) { z = y; uint256 x = y / 2 + 1; while (x < z) { z = x; x = (y / x + x) / 2; } } else if (y != 0) { z = 1; } }
function min(uint256 x, uint256 y) private pure returns (uint256) { return x <= y ? x : y; }}Additional patterns and templates
More detailed templates and worked examples live in references/details.md. Read that file for the full pattern library.


