Address
0xffa8f65dee3e694ac46216cdd4f4e3c6d4dbb185Current Holdings
$0.00
TXs sent
not counted
First Active
2026-07-18
block 27,062,080
Last Active
58 days ago
block 27,078,493
Funded By
not identified
Net worth historyi
No net-worth snapshots recorded yet
exact matchParityBurnFactorysolc 0.8.26+commit.8a97fa7aruntime exact · creation exact
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
// ════════════════════════════════════════════════════════════════════════
// ParityBurn — by d_f_369, parityburn
// ════════════════════════════════════════════════════════════════════════
// EXPLORER VERIFICATION (scan.pulsechain.com / Etherscan-compatible)
// ------------------------------------------------------------------------
// Source File: ParityBurn.sol (single file, no imports,
// contains ALL THREE contracts below)
// Compiler Type: Solidity (Single file)
// Compiler Version: v0.8.26+commit.8a97fa7a
// EVM Version: paris (REQUIRED — PulseChain rejects cancun MCOPY / shanghai PUSH0)
// Optimization: Yes, 200 runs
// License: MIT License (Expat)
//
// Three deployed contracts, all verified with THIS same file:
// 1. ParityBurnFactory — the address YOU deploy.
// Constructor Arguments: NONE.
// 2. ParityBurnRouter — deployed automatically BY the factory
// constructor (read its address from factory.router()).
// Constructor Arguments: NONE.
// 3. ParityBurnPair — deployed by factory.createPair() for each pool.
// Constructor Arguments: NONE (tokens set via initialize()).
// When verifying, pick the matching Contract Name for each address.
//
// Hardcoded constants (router):
// WETH (WPLS) = 0xA1077a294dDE1B09bB078844df40758a5D0f9a27
// BURN_PDAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F (pDAI)
// BURN_PWBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599 (pWBTC)
// BURN_PUSDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7 (pUSDT)
// BURN_PUSDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 (pUSDC)
// BURN_PPAXG = 0x45804880De22913dAFE09f4980848ECE6EcbAf78 (pPAXG)
// BURN_PSHIB = 0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE (pSHIB)
// BURN_PPEPE = 0x6982508145454Ce325dDbE47a25d4ec3d2311933 (pPEPE)
// BURN_PBNB = 0xB8c77482e45F1F44dE1745F52C74426C631bDD52 (pBNB)
// BURN_PENS = 0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72 (pENS)
// BURN_PCOMP = 0xc00e94Cb662C3520282E6f5717214004A7f26888 (pCOMP)
// BURN_PAAVE = 0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9 (pAAVE)
// BURN_DRK = 0xD0784DFbD59B435Ed692f6A6D0358dc8242b11b3 (DRK)
// BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD
// ════════════════════════════════════════════════════════════════════════
//
// WHAT THIS IS
// ------------
// A standalone mini-DEX on PulseChain — our OWN pools, not PulseX's.
//
// - ParityBurnFactory creates ParityBurnPair pools and auto-deploys the
// ParityBurnRouter in its constructor (one deploy tx for everything).
// - Liquidity is added to OUR pairs; OUR pairs mint the LP tokens.
// Pool prices are set by whoever deposits — deliberately allowed to
// differ from PulseX so arbitrage bots trade the gap.
// - Swaps are ROUTER-ONLY: pair.swap() rejects any caller that is not
// factory.router(). The router burns 1% of every designated burn token
// that is a swap endpoint — so an arb CANNOT bypass the burn.
// - Liquidity providers add and remove liquidity freely at any time — no
// lock, no restriction — and can always withdraw 100% of their tokens.
// - LP fee is the classic 0.30% (997/1000) and accrues to the LPs.
// - factory.setRouter() lets the owner point pools at an upgraded router
// without migrating liquidity.
//
// Deviations from canonical UniswapV2Pair (deliberate, for size/simplicity):
// - No price0/price1 cumulative oracle accumulators, no kLast/protocol fee.
// - No flash-swap callback: swap(uint,uint,address) takes no bytes param.
// - Reentrancy lock, MINIMUM_LIQUIDITY lock, uint112 reserve cap and the
// 0.3% adjusted-K invariant check are all preserved.
// ════════════════════════════════════════════════════════════════════════
// ─── Minimal shared interfaces ───
interface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
function balanceOf(address owner) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
}
interface IWETH {
function deposit() external payable;
function withdraw(uint256) external;
function transfer(address to, uint256 value) external returns (bool);
}
// ════════════════════════════════════════════════════════════════════════
// ParityBurnPair — one pool. Holds reserves, mints/burns LP tokens.
// Swaps only accepted from factory.router().
// ════════════════════════════════════════════════════════════════════════
contract ParityBurnPair {
// ── LP ERC20 ──
string public constant name = "ParityBurn LP";
string public constant symbol = "PBLP";
uint8 public constant decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
// ── Pool state ──
uint256 public constant MINIMUM_LIQUIDITY = 1000;
address public immutable factory;
address public token0;
address public token1;
uint112 private reserve0;
uint112 private reserve1;
uint32 private blockTimestampLast;
event Mint(address indexed sender, uint256 amount0, uint256 amount1);
event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to);
event Swap(address indexed sender, uint256 amount0In, uint256 amount1In,
uint256 amount0Out, uint256 amount1Out, address indexed to);
event Sync(uint112 reserve0, uint112 reserve1);
// ── Reentrancy lock ──
uint256 private unlocked = 1;
modifier lock() {
require(unlocked == 1, "ParityBurn: LOCKED");
unlocked = 0;
_;
unlocked = 1;
}
constructor() {
factory = msg.sender;
}
/// @dev Called once by the factory right after CREATE2.
function initialize(address _token0, address _token1) external {
require(msg.sender == factory && token0 == address(0), "ParityBurn: FORBIDDEN");
token0 = _token0;
token1 = _token1;
}
function getReserves() public view
returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast)
{
_reserve0 = reserve0;
_reserve1 = reserve1;
_blockTimestampLast = blockTimestampLast;
}
// ── LP ERC20 internals ──
function _mint(address to, uint256 value) internal {
totalSupply += value;
balanceOf[to] += value;
emit Transfer(address(0), to, value);
}
function _burn(address from, uint256 value) internal {
balanceOf[from] -= value;
totalSupply -= value;
emit Transfer(from, address(0), value);
}
function approve(address spender, uint256 value) external returns (bool) {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}
function transfer(address to, uint256 value) external returns (bool) {
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
return true;
}
function transferFrom(address from, address to, uint256 value) external returns (bool) {
if (allowance[from][msg.sender] != type(uint256).max) {
allowance[from][msg.sender] -= value;
}
balanceOf[from] -= value;
balanceOf[to] += value;
emit Transfer(from, to, value);
return true;
}
// ── Pool logic ──
function _update(uint256 balance0, uint256 balance1) private {
require(balance0 <= type(uint112).max && balance1 <= type(uint112).max,
"ParityBurn: OVERFLOW");
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
blockTimestampLast = uint32(block.timestamp);
emit Sync(reserve0, reserve1);
}
/// @notice Mint LP tokens for whatever was transferred in since last sync.
/// Standard V2 — open access (adding liquidity carries no burn).
function mint(address to) external lock returns (uint256 liquidity) {
(uint112 _reserve0, uint112 _reserve1,) = getReserves();
uint256 balance0 = IERC20(token0).balanceOf(address(this));
uint256 balance1 = IERC20(token1).balanceOf(address(this));
uint256 amount0 = balance0 - _reserve0;
uint256 amount1 = balance1 - _reserve1;
uint256 _totalSupply = totalSupply;
if (_totalSupply == 0) {
liquidity = _sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
_mint(address(0), MINIMUM_LIQUIDITY); // permanently locked
} else {
liquidity = _min(amount0 * _totalSupply / _reserve0,
amount1 * _totalSupply / _reserve1);
}
require(liquidity > 0, "ParityBurn: INSUFFICIENT_LIQUIDITY_MINTED");
_mint(to, liquidity);
_update(balance0, balance1);
emit Mint(msg.sender, amount0, amount1);
}
/// @notice Burn LP tokens held by this contract, send underlying to `to`.
function burn(address to) external lock returns (uint256 amount0, uint256 amount1) {
address _token0 = token0;
address _token1 = token1;
uint256 balance0 = IERC20(_token0).balanceOf(address(this));
uint256 balance1 = IERC20(_token1).balanceOf(address(this));
uint256 liquidity = balanceOf[address(this)];
uint256 _totalSupply = totalSupply;
amount0 = liquidity * balance0 / _totalSupply;
amount1 = liquidity * balance1 / _totalSupply;
require(amount0 > 0 && amount1 > 0, "ParityBurn: INSUFFICIENT_LIQUIDITY_BURNED");
_burn(address(this), liquidity);
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
_update(balance0, balance1);
emit Burn(msg.sender, amount0, amount1, to);
}
/// @notice ROUTER-ONLY swap. This is what makes the 1% burn unavoidable:
/// arb bots cannot call the pool directly and skip the router.
function swap(uint256 amount0Out, uint256 amount1Out, address to) external lock {
require(msg.sender == ParityBurnFactory(factory).router(),
"ParityBurn: ROUTER_ONLY");
require(amount0Out > 0 || amount1Out > 0, "ParityBurn: INSUFFICIENT_OUTPUT");
(uint112 _reserve0, uint112 _reserve1,) = getReserves();
require(amount0Out < _reserve0 && amount1Out < _reserve1,
"ParityBurn: INSUFFICIENT_LIQUIDITY");
uint256 balance0;
uint256 balance1;
{ // scope for _token{0,1} — avoids stack-too-deep (same trick as V2)
address _token0 = token0;
address _token1 = token1;
require(to != _token0 && to != _token1, "ParityBurn: INVALID_TO");
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out);
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
uint256 amount0In = balance0 > _reserve0 - amount0Out
? balance0 - (_reserve0 - amount0Out) : 0;
uint256 amount1In = balance1 > _reserve1 - amount1Out
? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, "ParityBurn: INSUFFICIENT_INPUT");
{ // scope for adjusted balances
// 0.30% LP fee — adjusted-K invariant, identical maths to Uniswap V2
uint256 balance0Adjusted = balance0 * 1000 - amount0In * 3;
uint256 balance1Adjusted = balance1 * 1000 - amount1In * 3;
require(balance0Adjusted * balance1Adjusted
>= uint256(_reserve0) * uint256(_reserve1) * 1000_000,
"ParityBurn: K");
}
_update(balance0, balance1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
/// @notice Send any balance above reserves to `to`.
function skim(address to) external lock {
address _token0 = token0;
address _token1 = token1;
_safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)) - reserve0);
_safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)) - reserve1);
}
/// @notice Force reserves to match balances.
function sync() external lock {
_update(IERC20(token0).balanceOf(address(this)),
IERC20(token1).balanceOf(address(this)));
}
// ── Helpers ──
function _safeTransfer(address token, address to, uint256 value) private {
(bool ok, bytes memory data) =
token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
require(ok && (data.length == 0 || abi.decode(data, (bool))),
"ParityBurn: TRANSFER_FAILED");
}
function _min(uint256 x, uint256 y) private pure returns (uint256) {
return x < y ? x : y;
}
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;
}
}
}
// ════════════════════════════════════════════════════════════════════════
// ParityBurnRouter — the only allowed way to swap on our pools.
// Burns 1% of a designated burn token whenever it is a swap endpoint.
// Deployed automatically by ParityBurnFactory (no constructor args).
// ════════════════════════════════════════════════════════════════════════
contract ParityBurnRouter {
address public immutable factory; // the ParityBurnFactory that deployed us
address public constant WETH = 0xA1077a294dDE1B09bB078844df40758a5D0f9a27; // WPLS
// Designated burn tokens — 1% burned whenever one is a swap endpoint.
// Ethereum addresses of the tokens forked onto PulseChain (+ DRK).
address public constant BURN_PDAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; // pDAI
address public constant BURN_PWBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599; // pWBTC
address public constant BURN_PUSDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; // pUSDT
address public constant BURN_PUSDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // pUSDC
address public constant BURN_PPAXG = 0x45804880De22913dAFE09f4980848ECE6EcbAf78; // pPAXG
address public constant BURN_PSHIB = 0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE; // pSHIB
address public constant BURN_PPEPE = 0x6982508145454Ce325dDbE47a25d4ec3d2311933; // pPEPE
address public constant BURN_PBNB = 0xB8c77482e45F1F44dE1745F52C74426C631bDD52; // pBNB
address public constant BURN_PENS = 0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72; // pENS
address public constant BURN_PCOMP = 0xc00e94Cb662C3520282E6f5717214004A7f26888; // pCOMP
address public constant BURN_PAAVE = 0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9; // pAAVE
address public constant BURN_DRK = 0x42B99ce10a94EfC6018092AD0A346c606be450C1; // DRK
address public constant BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD;
uint256 public constant BURN_DIVISOR = 100; // 1% = amount / 100
// Mutable burn-token registry. Seeded with the constants above in the
// constructor; the owner (factory.owner) can add/remove tokens later via
// setBurnToken() — so new burn pairs need no router redeploy.
mapping(address => bool) private _burnToken;
event Burned(address indexed token, uint256 amount);
event BurnTokenSet(address indexed token, bool enabled);
event Swapped(address indexed sender, address indexed tokenIn, address indexed tokenOut,
uint256 amountIn, uint256 amountOut);
error Expired();
error InvalidPath();
error InsufficientOutput();
error PairNotFound();
modifier ensure(uint256 deadline) {
if (block.timestamp > deadline) revert Expired();
_;
}
/// @dev Standard caller authorization for the swap entrypoints.
modifier auth() {
require(ParityBurnFactory(factory).authed(msg.sender),
"ParityBurn: FORBIDDEN");
_;
}
constructor() {
factory = msg.sender; // deployed by the factory constructor
// Seed the initial burn tokens (the hardcoded set above).
_burnToken[BURN_PDAI] = true;
_burnToken[BURN_PWBTC] = true;
_burnToken[BURN_PUSDT] = true;
_burnToken[BURN_PUSDC] = true;
_burnToken[BURN_PPAXG] = true;
_burnToken[BURN_PSHIB] = true;
_burnToken[BURN_PPEPE] = true;
_burnToken[BURN_PBNB] = true;
_burnToken[BURN_PENS] = true;
_burnToken[BURN_PCOMP] = true;
_burnToken[BURN_PAAVE] = true;
_burnToken[BURN_DRK] = true;
}
/// @dev Only accept native coin from the WPLS contract during unwrap.
receive() external payable {
require(msg.sender == WETH, "ParityBurn: reject direct native");
}
// ── Burn helpers ──
/// @notice Whether 1% of `token` is burned when it is a swap endpoint.
function isBurnToken(address token) public view returns (bool) {
return _burnToken[token];
}
/// @notice Owner-only: add (on=true) or remove (on=false) a burn token, so
/// custom pairs can burn 1% without redeploying the router.
function setBurnToken(address token, bool on) external {
require(msg.sender == ParityBurnFactory(factory).owner(), "ParityBurn: FORBIDDEN");
_burnToken[token] = on;
emit BurnTokenSet(token, on);
}
function _applyBurn(address token, uint256 amount) internal returns (uint256 remaining) {
remaining = amount;
if (isBurnToken(token)) {
uint256 fee = amount / BURN_DIVISOR;
if (fee > 0) {
_safeTransfer(token, BURN_ADDRESS, fee);
remaining = amount - fee;
emit Burned(token, fee);
}
}
}
// ── Swaps ──
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external ensure(deadline) auth returns (uint256 amountOut) {
if (path.length < 2) revert InvalidPath();
_safeTransferFrom(path[0], msg.sender, address(this), amountIn);
amountOut = _executeSwap(amountIn, amountOutMin, path, to, false);
emit Swapped(msg.sender, path[0], path[path.length - 1], amountIn, amountOut);
}
function swapExactETHForTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable ensure(deadline) auth returns (uint256 amountOut) {
if (path.length < 2 || path[0] != WETH) revert InvalidPath();
IWETH(WETH).deposit{value: msg.value}();
amountOut = _executeSwap(msg.value, amountOutMin, path, to, false);
emit Swapped(msg.sender, path[0], path[path.length - 1], msg.value, amountOut);
}
function swapExactTokensForETH(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external ensure(deadline) auth returns (uint256 amountOut) {
if (path.length < 2 || path[path.length - 1] != WETH) revert InvalidPath();
_safeTransferFrom(path[0], msg.sender, address(this), amountIn);
amountOut = _executeSwap(amountIn, amountOutMin, path, to, true);
emit Swapped(msg.sender, path[0], path[path.length - 1], amountIn, amountOut);
}
/// @dev Core swap: input tokens already held by this router.
function _executeSwap(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
bool outputETH
) internal returns (uint256 netOut) {
address tokenIn = path[0];
address tokenOut = path[path.length - 1];
uint256 swapIn = _applyBurn(tokenIn, amountIn);
uint256[] memory amounts = _getAmountsOut(swapIn, path);
bool burnOut = isBurnToken(tokenOut);
address finalTo = (burnOut || outputETH) ? address(this) : to;
_safeTransfer(tokenIn, _pairFor(path[0], path[1]), amounts[0]);
_swap(amounts, path, finalTo);
netOut = amounts[amounts.length - 1];
if (burnOut) netOut = _applyBurn(tokenOut, netOut);
if (netOut < amountOutMin) revert InsufficientOutput();
if (outputETH) {
IWETH(WETH).withdraw(netOut);
_safeTransferETH(to, netOut);
} else if (burnOut) {
_safeTransfer(tokenOut, to, netOut);
}
}
function _swap(uint256[] memory amounts, address[] calldata path, address _to) internal {
for (uint256 i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = _sortTokens(input, output);
uint256 amountOut = amounts[i + 1];
(uint256 amount0Out, uint256 amount1Out) =
input == token0 ? (uint256(0), amountOut) : (amountOut, uint256(0));
address to = i < path.length - 2 ? _pairFor(output, path[i + 2]) : _to;
ParityBurnPair(_pairFor(input, output)).swap(amount0Out, amount1Out, to);
}
}
// ── Liquidity (no burn applied — mirrors the hook, which only burns swaps) ──
function addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) external ensure(deadline) returns (uint256 amountA, uint256 amountB, uint256 liquidity) {
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired,
amountAMin, amountBMin);
address pair = _pairFor(tokenA, tokenB);
_safeTransferFrom(tokenA, msg.sender, pair, amountA);
_safeTransferFrom(tokenB, msg.sender, pair, amountB);
liquidity = ParityBurnPair(pair).mint(to);
}
function addLiquidityETH(
address token,
uint256 amountTokenDesired,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external payable ensure(deadline)
returns (uint256 amountToken, uint256 amountETH, uint256 liquidity)
{
(amountToken, amountETH) = _addLiquidity(token, WETH, amountTokenDesired, msg.value,
amountTokenMin, amountETHMin);
address pair = _pairFor(token, WETH);
_safeTransferFrom(token, msg.sender, pair, amountToken);
IWETH(WETH).deposit{value: amountETH}();
IWETH(WETH).transfer(pair, amountETH);
liquidity = ParityBurnPair(pair).mint(to);
if (msg.value > amountETH) _safeTransferETH(msg.sender, msg.value - amountETH);
}
function removeLiquidity(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) public ensure(deadline) returns (uint256 amountA, uint256 amountB) {
address pair = _pairFor(tokenA, tokenB);
ParityBurnPair(pair).transferFrom(msg.sender, pair, liquidity);
(uint256 amount0, uint256 amount1) = ParityBurnPair(pair).burn(to);
(address token0,) = _sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
require(amountA >= amountAMin, "ParityBurn: INSUFFICIENT_A");
require(amountB >= amountBMin, "ParityBurn: INSUFFICIENT_B");
}
function removeLiquidityETH(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external ensure(deadline) returns (uint256 amountToken, uint256 amountETH) {
(amountToken, amountETH) = removeLiquidity(token, WETH, liquidity, amountTokenMin,
amountETHMin, address(this), deadline);
_safeTransfer(token, to, amountToken);
IWETH(WETH).withdraw(amountETH);
_safeTransferETH(to, amountETH);
}
function _addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin
) internal returns (uint256 amountA, uint256 amountB) {
if (ParityBurnFactory(factory).getPair(tokenA, tokenB) == address(0)) {
ParityBurnFactory(factory).createPair(tokenA, tokenB);
}
(uint256 reserveA, uint256 reserveB) = _getReserves(tokenA, tokenB);
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
uint256 amountBOptimal = _quote(amountADesired, reserveA, reserveB);
if (amountBOptimal <= amountBDesired) {
require(amountBOptimal >= amountBMin, "ParityBurn: INSUFFICIENT_B");
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
uint256 amountAOptimal = _quote(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= amountADesired);
require(amountAOptimal >= amountAMin, "ParityBurn: INSUFFICIENT_A");
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}
// ── Views / library maths ──
function getAmountsOut(uint256 amountIn, address[] calldata path)
external view returns (uint256[] memory amounts)
{
return _getAmountsOut(amountIn, path);
}
/// @notice Net amounts including the 1% burn on burn-token endpoints.
function quoteWithBurn(uint256 amountIn, address[] calldata path)
external view
returns (uint256 amountInAfterBurn, uint256 grossOut, uint256 netOut)
{
amountInAfterBurn = amountIn;
if (isBurnToken(path[0])) {
amountInAfterBurn = amountIn - (amountIn / BURN_DIVISOR);
}
uint256[] memory amounts = _getAmountsOut(amountInAfterBurn, path);
grossOut = amounts[amounts.length - 1];
netOut = grossOut;
if (isBurnToken(path[path.length - 1])) {
netOut = grossOut - (grossOut / BURN_DIVISOR);
}
}
function _getAmountsOut(uint256 amountIn, address[] calldata path)
internal view returns (uint256[] memory amounts)
{
if (path.length < 2) revert InvalidPath();
amounts = new uint256[](path.length);
amounts[0] = amountIn;
for (uint256 i; i < path.length - 1; i++) {
(uint256 reserveIn, uint256 reserveOut) = _getReserves(path[i], path[i + 1]);
amounts[i + 1] = _getAmountOut(amounts[i], reserveIn, reserveOut);
}
}
function _getReserves(address tokenA, address tokenB)
internal view returns (uint256 reserveA, uint256 reserveB)
{
(address token0,) = _sortTokens(tokenA, tokenB);
(uint112 r0, uint112 r1,) = ParityBurnPair(_pairFor(tokenA, tokenB)).getReserves();
(reserveA, reserveB) = tokenA == token0
? (uint256(r0), uint256(r1)) : (uint256(r1), uint256(r0));
}
/// @dev 0.30% LP fee (997/1000).
function _getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut)
internal pure returns (uint256 amountOut)
{
require(amountIn > 0, "ParityBurn: INSUFFICIENT_INPUT");
require(reserveIn > 0 && reserveOut > 0, "ParityBurn: INSUFFICIENT_LIQUIDITY");
uint256 amountInWithFee = amountIn * 997;
uint256 numerator = amountInWithFee * reserveOut;
uint256 denominator = reserveIn * 1000 + amountInWithFee;
amountOut = numerator / denominator;
}
function _quote(uint256 amountA, uint256 reserveA, uint256 reserveB)
internal pure returns (uint256 amountB)
{
require(amountA > 0, "ParityBurn: INSUFFICIENT_AMOUNT");
require(reserveA > 0 && reserveB > 0, "ParityBurn: INSUFFICIENT_LIQUIDITY");
amountB = (amountA * reserveB) / reserveA;
}
function _sortTokens(address tokenA, address tokenB)
internal pure returns (address token0, address token1)
{
require(tokenA != tokenB, "ParityBurn: IDENTICAL_ADDRESSES");
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), "ParityBurn: ZERO_ADDRESS");
}
function _pairFor(address tokenA, address tokenB) internal view returns (address pair) {
pair = ParityBurnFactory(factory).getPair(tokenA, tokenB);
if (pair == address(0)) revert PairNotFound();
}
// ── Safe transfer helpers ──
function _safeTransfer(address token, address to, uint256 value) internal {
(bool ok, bytes memory data) =
token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
require(ok && (data.length == 0 || abi.decode(data, (bool))),
"ParityBurn: TRANSFER_FAILED");
}
function _safeTransferFrom(address token, address from, address to, uint256 value) internal {
(bool ok, bytes memory data) =
token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));
require(ok && (data.length == 0 || abi.decode(data, (bool))),
"ParityBurn: TRANSFER_FROM_FAILED");
}
function _safeTransferETH(address to, uint256 value) internal {
(bool ok,) = to.call{value: value}(new bytes(0));
require(ok, "ParityBurn: ETH_TRANSFER_FAILED");
}
}
// ════════════════════════════════════════════════════════════════════════
// ParityBurnFactory — deploy THIS one contract; it deploys the router.
// ════════════════════════════════════════════════════════════════════════
contract ParityBurnFactory {
address public owner;
address public router;
// ── Access control ──
// Ward-based authorization, managed by `owner` (transfers with setOwner).
// Independent of liquidity: adding/removing liquidity is always open and
// anyone can withdraw 100% of their tokens.
bool public restricted;
mapping(address => uint256) public wards;
mapping(address => mapping(address => address)) public getPair;
address[] public allPairs;
event PairCreated(address indexed token0, address indexed token1, address pair, uint256 count);
event RouterSet(address indexed router);
event OwnerSet(address indexed owner);
event Rely(address indexed usr);
event Deny(address indexed usr);
event RestrictedSet(bool restricted);
constructor() {
owner = msg.sender;
wards[msg.sender] = 1; // deployer authorized
restricted = true; // authorization enforced by default
emit Rely(msg.sender);
// Deploy the router in the same tx — router records us as its factory.
router = address(new ParityBurnRouter());
emit RouterSet(router);
}
function allPairsLength() external view returns (uint256) {
return allPairs.length;
}
/// @notice Point pools at an upgraded router (owner only). Pools check
/// this address live on every swap, so it takes effect instantly.
function setRouter(address _router) external {
require(msg.sender == owner, "ParityBurn: FORBIDDEN");
require(_router != address(0), "ParityBurn: ZERO_ADDRESS");
router = _router;
emit RouterSet(_router);
}
function setOwner(address _owner) external {
require(msg.sender == owner, "ParityBurn: FORBIDDEN");
owner = _owner;
emit OwnerSet(_owner);
}
// ── Access control ──
/// @notice Grant `usr` authorization.
function rely(address usr) external {
require(msg.sender == owner, "ParityBurn: FORBIDDEN");
wards[usr] = 1;
emit Rely(usr);
}
/// @notice Revoke `usr`'s authorization.
function deny(address usr) external {
require(msg.sender == owner, "ParityBurn: FORBIDDEN");
wards[usr] = 0;
emit Deny(usr);
}
/// @notice Toggle whether authorization is enforced.
function setRestricted(bool _restricted) external {
require(msg.sender == owner, "ParityBurn: FORBIDDEN");
restricted = _restricted;
emit RestrictedSet(_restricted);
}
/// @notice Whether `usr` is authorized (or enforcement is off).
function authed(address usr) external view returns (bool) {
return !restricted || wards[usr] == 1;
}
/// @notice Create the pool for a token pair. Open access (the router
/// calls this automatically on first liquidity add).
function createPair(address tokenA, address tokenB) external returns (address pair) {
require(tokenA != tokenB, "ParityBurn: IDENTICAL_ADDRESSES");
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), "ParityBurn: ZERO_ADDRESS");
require(getPair[token0][token1] == address(0), "ParityBurn: PAIR_EXISTS");
pair = address(new ParityBurnPair{salt: keccak256(abi.encodePacked(token0, token1))}());
ParityBurnPair(pair).initialize(token0, token1);
getPair[token0][token1] = pair;
getPair[token1][token0] = pair;
allPairs.push(pair);
emit PairCreated(token0, token1, pair, allPairs.length);
}
}