Reflection Token Development
Reflection tokens are one of technically most problematic ERC-20 classes. Idea is simple: part of each transaction automatically distributed among all holders proportionally to their balance. In practice naive implementation requires iterating through all holders on each transfer, meaning gas proportional to holder count — at 10,000 holders transaction becomes so expensive contract stops working. Smart implementation through rFactor (reflection factor) solves this in O(1).
Reflection mechanism: mathematics
Standard approach — two balance types: rBalance (reflection balance) and tBalance (token balance). Holders store rBalance, which automatically grows on each transaction.
Key idea: instead of redistributing tokens among all holders, change conversion rate rBalance → tBalance. On transfer part of rAmount is excluded from rTotal (burned in reflection space), increasing rate = rTotal / tTotal for everyone else.
contract ReflectionToken is IERC20, Ownable {
uint256 private constant MAX = ~uint256(0);
uint256 private _tTotal; // total supply in token-space
uint256 private _rTotal; // total supply in reflection-space
uint256 private _tFeeTotal; // accumulated fees
uint256 public taxFee = 5; // 5% — distributed to holders
uint256 public liquidityFee = 3; // 3% — to liquidity pool
uint256 public burnFee = 2; // 2% — burned
mapping(address => uint256) private _rOwned; // reflection balance
mapping(address => uint256) private _tOwned; // only for excluded addresses
mapping(address => bool) private _isExcluded; // excluded from reflection
constructor(uint256 totalSupply) {
_tTotal = totalSupply * 10**18;
_rTotal = (MAX - (MAX % _tTotal)); // maximum possible rTotal divisible by tTotal
_rOwned[msg.sender] = _rTotal;
}
function _getRate() private view returns (uint256) {
(uint256 rSupply, uint256 tSupply) = _getCurrentSupply();
return rSupply / tSupply;
}
function _getCurrentSupply() private view returns (uint256, uint256) {
uint256 rSupply = _rTotal;
uint256 tSupply = _tTotal;
// excluded accounts (liquidity pools, contracts) don't participate in reflection
for (uint256 i = 0; i < _excluded.length; i++) {
if (_rOwned[_excluded[i]] > rSupply || _tOwned[_excluded[i]] > tSupply)
return (_rTotal, _tTotal);
rSupply -= _rOwned[_excluded[i]];
tSupply -= _tOwned[_excluded[i]];
}
if (rSupply < _rTotal / _tTotal) return (_rTotal, _tTotal);
return (rSupply, tSupply);
}
function balanceOf(address account) public view returns (uint256) {
if (_isExcluded[account]) return _tOwned[account];
return tokenFromReflection(_rOwned[account]);
}
function tokenFromReflection(uint256 rAmount) public view returns (uint256) {
require(rAmount <= _rTotal, "Amount too large");
return rAmount / _getRate();
}
function _transferStandard(address sender, address recipient, uint256 tAmount) private {
// calculate all values
(uint256 rAmount, uint256 rTransferAmount, uint256 rFee,
uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity, uint256 tBurn)
= _getValues(tAmount);
// update reflection balances
_rOwned[sender] -= rAmount;
_rOwned[recipient] += rTransferAmount;
// reflection fee: decrease rTotal — this automatically increases
// all other holders' balances without explicit iteration
_reflectFee(rFee, tFee);
// handle liquidity and burn
_takeLiquidity(tLiquidity);
_burn(sender, tBurn);
emit Transfer(sender, recipient, tTransferAmount);
}
function _reflectFee(uint256 rFee, uint256 tFee) private {
_rTotal -= rFee; // key operation: decrease rTotal
_tFeeTotal += tFee; // statistics
}
}
Problem of excluded addresses
Liquidity pool addresses (Uniswap pair, PancakeSwap pair) must be excluded from reflection. If pool participates in reflection, its token balance constantly grows, violating token/ETH ratio in pool and creating arb opportunities. This is classic mistake in early reflection tokens.
function excludeFromReward(address account) public onlyOwner {
require(!_isExcluded[account], "Already excluded");
if (_rOwned[account] > 0) {
_tOwned[account] = tokenFromReflection(_rOwned[account]);
}
_isExcluded[account] = true;
_excluded.push(account);
}
When adding to excluded, current tBalance is saved (converted from rBalance) — otherwise account loses funds.
Vulnerabilities and risks
Iteration over excluded: function _getCurrentSupply() iterates through excluded addresses array. If this array is large (attacker could add many addresses through some function), transactions start hitting gas limit. _excluded.length must be strictly limited, function excludeFromReward only for owner.
Precision loss at high transaction count: _rTotal decreases with each transaction. Theoretically after enormous number of transactions _rTotal could become so small _getRate() returns 0 and contract breaks. In practice with reasonable supply and fees this happens in thousands of years, but invariant worth checking in tests.
Anti-whale measures: without additional restrictions large holders can manipulate reflection. Standard solution — maxTransactionAmount and maxWalletSize:
uint256 public maxTxAmount = _tTotal / 100; // 1% of supply
uint256 public maxWalletToken = _tTotal / 50; // 2% of supply
function _transfer(address from, address to, uint256 amount) internal {
require(amount <= maxTxAmount, "Exceeds max tx");
if (!_isExcluded[to]) {
require(balanceOf(to) + amount <= maxWalletToken, "Exceeds max wallet");
}
// ...
}
Auto-liquidity mechanism
Many reflection tokens include auto-liquidity: accumulated liquidityFee periodically converted to LP tokens via Uniswap/PancakeSwap. This maintains liquidity without team involvement.
bool inSwapAndLiquify = false;
uint256 private numTokensSellToAddToLiquidity = _tTotal / 1000; // 0.1%
function _transfer(...) {
uint256 contractBalance = balanceOf(address(this));
bool overMinTokenBalance = contractBalance >= numTokensSellToAddToLiquidity;
if (overMinTokenBalance && !inSwapAndLiquify && from != uniswapV2Pair) {
inSwapAndLiquify = true;
swapAndLiquify(numTokensSellToAddToLiquidity);
inSwapAndLiquify = false;
}
// ...
}
Flag inSwapAndLiquify prevents recursive call on token swap through Uniswap.
Economic feasibility
High transfer fees (5–10% reflection + 3–5% liquidity + burn) create buy/sell spread making token poorly suited as actual payment method. Reflection tokens work as long-term holder reward mechanism and high-frequency trading deterrent, but at excessive fees repel liquid traders creating poor price discovery.
Recommended total fee range: 5–8%. Higher — economically dysfunctional construction regardless of technical correctness.







