defi-protocol-templates
wshobson/agents
Production-ready Solidity templates for staking, AMMs, governance, and flash loans.
What is defi-protocol-templates?
Provides battle-tested smart contract templates for common DeFi protocols including staking rewards, automated market makers (AMMs), governance systems, and flash loans. Use this when building decentralized finance applications or implementing protocol-level smart contracts.
- Staking contract with reward distribution and reentrancy protection
- Automated Market Maker (AMM) with liquidity provision and token swaps
- Reward per token calculations and earned amount tracking
- Liquidity pool management with share-based accounting
- 0.3% fee mechanism for swaps
- Exit function for simultaneous withdrawal and reward claiming
How to install defi-protocol-templates
npx skills add https://github.com/wshobson/agents --skill defi-protocol-templates- Solidity ^0.8.0 compiler
- OpenZeppelin contracts library (@openzeppelin/contracts)
- Understanding of ERC20 token standards
- Familiarity with smart contract security patterns
How to use defi-protocol-templates
- 1.Review the staking contract template and customize reward rate and token addresses for your use case
- 2.Adapt the AMM template by replacing token0 and token1 addresses with your desired trading pairs
- 3.Deploy the contract to your target blockchain network
- 4.Initialize with appropriate token addresses and configuration parameters
- 5.Test thoroughly on testnet before mainnet deployment
Use cases
- Building a staking platform where users deposit tokens to earn rewards over time
- Implementing a decentralized exchange with liquidity pools and automated pricing
- Creating a governance token system with reward incentives
- Launching a DeFi protocol that requires flash loan functionality
- Smart contract developers building DeFi protocols
- Blockchain engineers implementing token economics
- DeFi protocol architects designing staking or AMM systems
- Teams launching decentralized finance applications
defi-protocol-templates FAQ
The fee is deducted from the input amount to incentivize liquidity provision and prevent arbitrage exploitation. It's calculated as (amountIn * 997) / 1000, keeping 0.3% of swaps.
The modifier ensures reward calculations are current before any state changes, preventing stale reward data and maintaining accurate per-token reward tracking across all operations.
The provided template shows rewardRate as a state variable, but you would need to add owner-controlled setter functions to make it adjustable post-deployment.
It prevents reentrancy attacks by ensuring external calls cannot recursively call back into the contract during execution, protecting against fund theft in stake/withdraw/reward operations.
Shares are calculated as the minimum of (amount0 * totalSupply) / reserve0 and (amount1 * totalSupply) / reserve1 to maintain balanced pool ratios.
Full instructions (SKILL.md)
Source of truth, from wshobson/agents.
name: defi-protocol-templates description: Implement DeFi protocols with production-ready templates for staking, AMMs, governance, and flash loans. Use when building decentralized finance applications or smart contract protocols.
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
// 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/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)
// SPDX-License-Identifier: MIT
pragma 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.
Related skills
More from wshobson/agents and the wider catalog.

dependency-upgrade
Manage major dependency version upgrades with compatibility analysis, staged rollout, and comprehensive testing.

deployment-pipeline-design
Design multi-stage CI/CD pipelines with approval gates, security checks, and progressive delivery strategies.

design-system-patterns
Build scalable design systems with tokens, theming, and component architecture patterns.

distributed-tracing
Track requests across microservices with Jaeger and Tempo to identify latency and bottlenecks.

dotnet-backend-patterns
Master C#/.NET backend patterns for robust APIs, MCP servers, and enterprise applications.

e2e-testing-patterns
Master end-to-end testing with Playwright and Cypress to build reliable, maintainable test suites.