Deflationary Token Development (with Burn)
Many projects choose the deflationary mechanic but encounter non-obvious problems: fee-on-transfer conflicts with AMMs, and buyback requires a separate monitoring contract. We develop such tokens turnkey — since launching the DeFi sector we've delivered over 10 projects, passed audits by two top-tier teams. Using timelocks reduces gas costs by up to 30% compared to manual monitoring — with an average transaction volume of 10,000 per month, the savings amount to about $500.
Recently, a project approached us with a token traded on Uniswap V3. After each transfer, 2% was burned, but the pool constantly threw INSUFFICIENT_INPUT_AMOUNT errors. The problem turned out to be the standard swapExactTokensForTokens call — it doesn't account for the tax. We rewrote the integration to use SupportingFeeOnTransferTokens and configured an exempt list for the pool. Traders stopped losing funds. Contact us for a consultation on your tokenomics — we'll break down similar cases.
Why does fee-on-transfer conflict with AMM?
Standard ERC-20 isn't designed for transfer taxes. Uniswap V2 and PancakeSwap (especially wrappers) don't account for the fee — causing INSUFFICIENT_INPUT_AMOUNT errors. The solution is to use SupportingFeeOnTransferTokens on the frontend and configure isBurnExempt for token pairs. If the owner can uncontrollably add addresses to the exemption list, an attacker with a compromised key can disable burning. We implement a timelock (48 hours) on any burn parameter changes.
Which approach to choose: fee-on-transfer or buyback-and-burn?
We offer two approaches and select the optimal one for your tokenomics.
Fee-on-transfer (automatic burn on transfer)
Each transfer automatically burns X% of the amount. Contract code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
contract DeflationaryToken is ERC20, Ownable2Step {
uint256 public burnBps; // basis points, 100 = 1%
uint256 public constant MAX_BURN_BPS = 1000; // 10% max
// Addresses exempt from the tax (LP pairs, routers)
mapping(address => bool) public isBurnExempt;
event BurnBpsUpdated(uint256 oldBps, uint256 newBps);
constructor(
string memory name,
string memory symbol,
uint256 initialSupply,
uint256 _burnBps
) ERC20(name, symbol) Ownable2Step() {
require(_burnBps <= MAX_BURN_BPS, "Burn too high");
burnBps = _burnBps;
_mint(msg.sender, initialSupply);
}
function _transfer(
address from,
address to,
uint256 amount
) internal override {
if (burnBps > 0 && !isBurnExempt[from] && !isBurnExempt[to]) {
uint256 burnAmount = (amount * burnBps) / 10000;
uint256 sendAmount = amount - burnAmount;
super._transfer(from, address(0), burnAmount); // burn
super._transfer(from, to, sendAmount); // transfer
} else {
super._transfer(from, to, amount);
}
}
function setBurnBps(uint256 _burnBps) external onlyOwner {
require(_burnBps <= MAX_BURN_BPS, "Burn too high");
emit BurnBpsUpdated(burnBps, _burnBps);
burnBps = _burnBps;
}
function setBurnExempt(address account, bool exempt) external onlyOwner {
isBurnExempt[account] = exempt;
}
}
Critical issue: The Uniswap V2 router sends amountIn, but the pool receives amountIn - burnAmount. The solution is to use swapExactTokensForTokensSupportingFeeOnTransferTokens.
IUniswapV2Router02(router).swapExactTokensForTokensSupportingFeeOnTransferTokens(
amountIn,
amountOutMin,
path,
to,
deadline
);
This is the responsibility of the frontend and integrators — we document the mechanism and provide instructions.
Manual burn via buyback-and-burn
A more controlled approach: the protocol accumulates fees and periodically buys back tokens on the market to burn. Code:
contract BuybackBurnVault is Ownable2Step {
IERC20 public immutable token;
IUniswapV2Router02 public immutable router;
uint256 public totalBurned;
event BuybackExecuted(uint256 bnbSpent, uint256 tokensBurned);
constructor(address _token, address _router) Ownable2Step() {
token = IERC20(_token);
router = IUniswapV2Router02(_router);
}
receive() external payable {}
function executeBuyback(
uint256 bnbAmount,
uint256 minTokensOut,
uint256 deadline
) external onlyOwner {
require(address(this).balance >= bnbAmount, "Insufficient BNB");
address[] memory path = new address[](2);
path[0] = router.WETH(); // WBNB on BSC
path[1] = address(token);
uint256[] memory amounts = router.swapExactETHForTokens{value: bnbAmount}(
minTokensOut,
path,
address(this),
deadline
);
uint256 tokensBought = amounts[amounts.length - 1];
token.transfer(address(0), tokensBought);
totalBurned += tokensBought;
emit BuybackExecuted(bnbAmount, tokensBought);
}
}
Buyback-and-burn requires more code but offers 3x more flexibility in configuring tokenomics — you can change the frequency and volume of buybacks without deploying a new contract.
Comparison of Approaches
| Parameter | Fee-on-transfer | Buyback-and-burn |
|---|---|---|
| DeFi compatibility | Complex (requires SupportFeeOnTransfer) | Full |
| Implementation complexity | Medium | High |
| Control over tokenomics | Low (fixed %) | High (parameters adjustable) |
| Transparency | High (automatic events) | Medium (depends on execution publication) |
What burn percentage to choose?
1–2% is aggressive for high-frequency trading. Each swap on Uniswap = buy + sell = 2 transfers + AMM fee. With 1% burn, the token loses 2% per trade + 0.3% LP fee. This deters traders. For utility tokens with rare transfers, it's acceptable.
Fixed vs dynamic burn?
Dynamic (e.g., higher on large volume) complicates tokenomics but allows adapting selling pressure to market conditions.
Why is a burn cap needed?
With aggressive burning, supply could drop to illiquid levels. We set a minimum threshold: if totalSupply < MIN_SUPPLY, burning stops.
uint256 public constant MIN_SUPPLY = 1_000_000 * 10**18; // 1M tokens minimum
function _transfer(address from, address to, uint256 amount) internal override {
if (burnBps > 0 && !isBurnExempt[from] && !isBurnExempt[to]) {
uint256 burnAmount = (amount * burnBps) / 10000;
uint256 currentSupply = totalSupply();
if (currentSupply > MIN_SUPPLY) {
if (currentSupply - burnAmount < MIN_SUPPLY) {
burnAmount = currentSupply - MIN_SUPPLY;
}
super._transfer(from, address(0), burnAmount);
super._transfer(from, to, amount - burnAmount);
return;
}
}
super._transfer(from, to, amount);
}
How to protect a deflationary token from attacks?
Two specific risks for deflationary tokens:
-
Re-entrancy via approve. If
_transfermakes external calls (e.g., swapping part of the fee automatically), it's a classic attack. Solution:ReentrancyGuard+ CEI pattern. - Exempt list manipulation. Use a timelock on changes for projects with significant TVL.
uint256 public constant BURN_CHANGE_TIMELOCK = 48 hours;
mapping(bytes32 => uint256) public pendingChanges;
function scheduleBurnBpsChange(uint256 newBps) external onlyOwner {
bytes32 changeId = keccak256(abi.encodePacked("burnBps", newBps));
pendingChanges[changeId] = block.timestamp + BURN_CHANGE_TIMELOCK;
}
function executeBurnBpsChange(uint256 newBps) external onlyOwner {
bytes32 changeId = keccak256(abi.encodePacked("burnBps", newBps));
require(pendingChanges[changeId] != 0, "Not scheduled");
require(block.timestamp >= pendingChanges[changeId], "Timelock active");
burnBps = newBps;
delete pendingChanges[changeId];
}
We use OpenZeppelin Ownable2Step for access control.
What are the development stages?
- Consultation and tokenomics analysis (1 day).
- Mechanic design (fee-on-transfer or buyback-and-burn).
- Contract development + writing tests (Foundry/Hardhat) — 5–8 days.
- Compatibility testing with Uniswap/PancakeSwap — 1–2 days.
- Deployment, verification, LP pair setup — 1 day.
- Optional: subgraph setup for monitoring (2–3 days).
- Documentation and team training.
| Stage | Duration |
|---|---|
| Tokenomics analysis | 1 day |
| Development and testing | 5–8 days |
| AMM integration | 1–2 days |
| Deployment and verification | 1 day |
| Subgraph (optional) | 2–3 days |
Timelines are approximate: 5 to 12 days depending on mechanic complexity and need for subgraph integration. For all projects, we guarantee 30-day post-deployment support. Contact us to analyze your tokenomics. Get a consultation on choosing the burn mechanism.
What's included in the work
- Tokenomics audit and optimization.
- Smart contract development (Solidity 0.8.x).
- Unit and fuzz tests (Foundry).
- Deployment to the chosen network (Ethereum, BSC, Polygon).
- Verification on Etherscan/BscScan.
- AMM exempt list configuration.
- Documentation for integrators.
- 30-day contract warranty.







