Smart Contracts
Complete reference for all 13 Inferno on-chain components — 9 repository contracts, 3 v2 upgrades (LiquidityReserve, BuybackVault, BurnReserve with transferOwnership), and the Uniswap V2 liquidity pair. Includes constructor parameters, public functions,
events, access control, and ethers.js usage examples.
contracts/token/InfernoToken.sol
The core ERC-20 token of the Inferno protocol. Inherits from OpenZeppelin's
ERC20, ERC20Burnable, and Ownable.
Uses 9 decimals (not the standard 18) and a total supply of 1 billion IFR.
Implements a fee-on-transfer mechanism: every non-exempt transfer deducts a sender burn fee,
a recipient burn fee, and a pool fee. The combined fee rate is capped at 5%. Default rates
are 2.0% sender burn, 0.5% recipient burn, and 1.0% pool fee (3.5% total).
Note: Mint and burn operations (transfers from/to address(0)) bypass fee logic.
Addresses marked as feeExempt also bypass fees entirely.
Constructor
| Parameter | Type | Description |
_poolFeeReceiver |
address |
Address that receives the pool fee portion of each transfer. Cannot be address(0). |
State Variables
| Variable | Type | Default | Description |
senderBurnBps |
uint256 |
200 (2.0%) |
Basis points burned from sender on each transfer |
recipientBurnBps |
uint256 |
50 (0.5%) |
Basis points burned from recipient portion on each transfer |
poolFeeBps |
uint256 |
100 (1.0%) |
Basis points sent to poolFeeReceiver on each transfer |
poolFeeReceiver |
address |
-- |
Address receiving pool fees |
feeExempt |
mapping(address => bool) |
-- |
Addresses exempt from transfer fees |
Functions
| Function | Parameters | Returns | Access |
decimals() |
-- |
uint8 (always 9) |
Public |
setFeeExempt(address, bool) |
account, exempt |
-- |
Owner |
setPoolFeeReceiver(address) |
receiver (non-zero) |
-- |
Owner |
setFeeRates(uint256, uint256, uint256) |
_senderBurnBps, _recipientBurnBps, _poolFeeBps |
-- |
Owner |
burn(uint256) |
amount |
-- |
Public |
Fee cap: setFeeRates enforces senderBurnBps + recipientBurnBps + poolFeeBps <= 500 (5% maximum total fee).
Internal Functions
| Function | Description |
_update(from, to, value) |
Overrides ERC20._update to implement fee-on-transfer. On non-exempt transfers:
calculates burnSender, burnRecipient, and poolFee;
sends netAmount to recipient, poolFee to poolFeeReceiver,
and burns totalBurn (sender + recipient burn) via transfer to address(0).
|
Events
| Event | Parameters |
FeeExemptUpdated |
address indexed account, bool exempt |
PoolFeeReceiverUpdated |
address indexed receiver |
FeesUpdated |
uint256 senderBurnBps, uint256 recipientBurnBps, uint256 poolFeeBps |
ethers.js Example
const { ethers } = require("ethers");
const TOKEN_ADDR = "0x3Bd71947F288d1dd8B21129B1bE4FF16EDd5d1F4";
const abi = [
"function decimals() view returns (uint8)",
"function balanceOf(address) view returns (uint256)",
"function senderBurnBps() view returns (uint256)",
"function recipientBurnBps() view returns (uint256)",
"function poolFeeBps() view returns (uint256)",
"function feeExempt(address) view returns (bool)",
"function transfer(address to, uint256 amount) returns (bool)",
"function setFeeExempt(address account, bool exempt)",
];
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const token = new ethers.Contract(TOKEN_ADDR, abi, provider);
const senderBurn = await token.senderBurnBps();
const recipBurn = await token.recipientBurnBps();
const poolFee = await token.poolFeeBps();
const amount = ethers.utils.parseUnits("1000", 9);
const totalFee = amount.mul(senderBurn.add(recipBurn).add(poolFee)).div(10000);
const netAmount = amount.sub(totalFee);
console.log("Net received:", ethers.utils.formatUnits(netAmount, 9), "IFR");
const signer = provider.getSigner();
const tokenWithSigner = token.connect(signer);
await tokenWithSigner.transfer(recipientAddr, amount);
contracts/liquidity/LiquidityReserve.sol
Holds 200M IFR as a strategic liquidity reserve with a time-lock and staged withdrawal
mechanism. Tokens are locked for an initial lock period (6 months in production). After the lock expires,
withdrawals are rate-limited to a maximum amount per period (e.g. 50M per quarter) to prevent supply shocks.
Includes a guardian role for emergency pause/unpause.
Constructor
| Parameter | Type | Description |
_token |
address |
Address of the IFR token contract |
_lockDuration |
uint256 |
Duration in seconds before first withdrawal is allowed |
_maxWithdrawPerPeriod |
uint256 |
Maximum tokens withdrawable per period (raw units, 9 decimals) |
_periodDuration |
uint256 |
Duration of each withdrawal period in seconds |
_guardian |
address |
Guardian address for emergency pause/unpause |
Functions
| Function | Parameters | Returns | Access |
withdraw(address, uint256) |
to, amount |
-- |
Owner |
availableToWithdraw() |
-- |
uint256 |
Public |
currentPeriod() |
-- |
uint256 |
Public |
pendingBalance() |
-- |
uint256 |
Public |
setMaxWithdrawPerPeriod(uint256) |
_max |
-- |
Owner |
setGuardian(address) |
_guardian |
-- |
Owner |
pause() |
-- |
-- |
Guardian |
unpause() |
-- |
-- |
Guardian |
Events
| Event | Parameters |
Withdrawn |
address indexed to, uint256 amount |
MaxWithdrawPerPeriodUpdated |
uint256 newMax |
GuardianUpdated |
address indexed newGuardian |
Paused |
address indexed account |
Unpaused |
address indexed account |
ethers.js Example
const RESERVE_ADDR = "0xF7E90D0d17f8232365186AA085D26eaEfAf011aF";
const abi = [
"function lockEnd() view returns (uint256)",
"function currentPeriod() view returns (uint256)",
"function availableToWithdraw() view returns (uint256)",
"function pendingBalance() view returns (uint256)",
"function withdraw(address to, uint256 amount)",
"function paused() view returns (bool)",
];
const reserve = new ethers.Contract(RESERVE_ADDR, abi, provider);
const lockEnd = await reserve.lockEnd();
const now = Math.floor(Date.now() / 1000);
console.log("Locked:", now < lockEnd.toNumber());
const available = await reserve.availableToWithdraw();
console.log("Available:", ethers.utils.formatUnits(available, 9), "IFR");
const amount = ethers.utils.parseUnits("10000000", 9);
await reserve.connect(owner).withdraw(treasuryAddr, amount);
contracts/vesting/Vesting.sol
Linear vesting contract for 150M IFR team allocation. Implements a
12-month cliff followed by 36-month linear vesting.
No tokens are releasable before the cliff. After the cliff, tokens vest linearly based on
elapsed time. The beneficiary calls release() to claim vested tokens.
A guardian can pause releases for emergency situations.
Vesting formula:
vestedAmount = totalAllocation * (elapsed - cliffDuration) / (duration - cliffDuration)
Returns 0 before cliff, and totalAllocation after full duration.
Constructor
| Parameter | Type | Description |
_token |
address |
Address of the IFR token contract |
_beneficiary |
address |
Address that can claim vested tokens |
_cliffDuration |
uint256 |
Cliff period in seconds (e.g. 365 days for 12 months) |
_duration |
uint256 |
Total vesting duration in seconds (must be >= cliffDuration) |
_totalAllocation |
uint256 |
Total tokens to vest (raw units, 9 decimals) |
_guardian |
address |
Guardian address for emergency pause/unpause |
Functions
| Function | Parameters | Returns | Access |
release() |
-- |
-- |
Beneficiary |
releasableAmount() |
-- |
uint256 |
Public |
vestedAmount() |
-- |
uint256 |
Public |
vestingSchedule() |
-- |
(uint256 _start, uint256 _cliff, uint256 _duration) |
Public |
pause() |
-- |
-- |
Guardian |
unpause() |
-- |
-- |
Guardian |
Events
| Event | Parameters |
Released |
address indexed beneficiary, uint256 amount |
Paused |
address indexed guardian |
Unpaused |
address indexed guardian |
Custom Errors
| Error | Description |
NotBeneficiary() | Caller is not the beneficiary |
NothingToRelease() | No tokens available to release |
OnlyGuardian() | Caller is not the guardian |
IsPaused() | Contract is paused |
ethers.js Example
const VESTING_ADDR = "0xa710f9FE7bf42981E60BE2Fbe7D87Fb3541a3F8B";
const abi = [
"function beneficiary() view returns (address)",
"function vestedAmount() view returns (uint256)",
"function releasableAmount() view returns (uint256)",
"function released() view returns (uint256)",
"function vestingSchedule() view returns (uint256, uint256, uint256)",
"function release()",
];
const vesting = new ethers.Contract(VESTING_ADDR, abi, provider);
const [start, cliff, duration] = await vesting.vestingSchedule();
const vested = await vesting.vestedAmount();
const releasable = await vesting.releasableAmount();
const released = await vesting.released();
console.log("Vested:", ethers.utils.formatUnits(vested, 9), "IFR");
console.log("Releasable:", ethers.utils.formatUnits(releasable, 9), "IFR");
console.log("Released:", ethers.utils.formatUnits(released, 9), "IFR");
await vesting.connect(beneficiarySigner).release();
contracts/buyback/BuybackVault.sol
Accepts ETH deposits and uses them to buy IFR tokens on Uniswap V2. Purchased tokens are
split 50/50 between the BurnReserve (permanent burn) and the treasury.
Features a configurable activation delay (60 days in production), a cooldown
between buybacks (default 1 hour), and slippage protection (default 5%) using
price quotes recorded at deposit time.
Constructor
| Parameter | Type | Description |
_token |
address |
Address of the IFR token contract |
_burnReserve |
address |
BurnReserve contract address (receives burn share) |
_treasury |
address |
Treasury address (receives treasury share) |
_router |
address |
Uniswap V2 Router address for swap execution |
_guardian |
address |
Guardian address for emergency pause/unpause |
_activationDelay |
uint256 |
Seconds before buybacks can execute (from deploy time) |
State Variables
| Variable | Type | Default | Description |
burnShareBps |
uint256 |
5000 (50%) |
Percentage of purchased IFR sent to BurnReserve |
cooldown |
uint256 |
3600 (1 hour) |
Minimum seconds between buyback executions |
slippageBps |
uint256 |
500 (5%) |
Maximum allowed slippage for swaps |
pendingExpectedOut |
uint256 |
0 |
Accumulated expected output from deposits (for slippage calc) |
Functions
| Function | Parameters | Returns | Access |
depositETH() |
-- (payable, sends ETH via msg.value) |
-- |
Public |
executeBuyback() |
-- |
-- |
Owner |
setParams(uint256, uint256, uint256, address, address) |
_burnShareBps, _cooldown, _slippageBps, _router, _treasury |
-- |
Owner |
pause() |
-- |
-- |
Guardian |
unpause() |
-- |
-- |
Guardian |
Events
| Event | Parameters |
Deposited |
address indexed sender, uint256 amount |
BuybackExecuted |
uint256 ethSpent, uint256 burnAmount, uint256 treasuryAmount |
Paused |
address indexed account |
Unpaused |
address indexed account |
ParamsUpdated |
uint256 burnShareBps, uint256 cooldown, uint256 slippageBps |
ethers.js Example
const VAULT_ADDR = "0xC8ABb9039BEd24f4dBf5Cff09699877D81f0D63C";
const abi = [
"function depositETH() payable",
"function executeBuyback()",
"function activationTime() view returns (uint256)",
"function lastBuybackAt() view returns (uint256)",
"function cooldown() view returns (uint256)",
"function pendingExpectedOut() view returns (uint256)",
"function paused() view returns (bool)",
];
const vault = new ethers.Contract(VAULT_ADDR, abi, provider);
await vault.connect(signer).depositETH({
value: ethers.utils.parseEther("0.5"),
});
const activation = await vault.activationTime();
const lastBuyback = await vault.lastBuybackAt();
const cd = await vault.cooldown();
const now = Math.floor(Date.now() / 1000);
const isActive = now >= activation.toNumber();
const cooldownPassed = now >= lastBuyback.add(cd).toNumber();
console.log("Ready:", isActive && cooldownPassed);
await vault.connect(owner).executeBuyback();
contracts/burnreserve/BurnReserve.sol
Permanent token burning contract with on-chain tracking. Tokens are deposited via
deposit() (requires prior approval) or sent directly from BuybackVault.
The owner or guardian can then call burn() or burnAll() to permanently
destroy the tokens, reducing the total supply. Tracks cumulative totalBurned for transparency.
Constructor
| Parameter | Type | Description |
_token |
address |
Address of the IFR token contract |
_guardian |
address |
Guardian address (can trigger burns alongside owner) |
Functions
| Function | Parameters | Returns | Access |
deposit(uint256) |
amount |
-- |
Public |
burn(uint256) |
amount |
-- |
Owner | Guardian |
burnAll() |
-- |
-- |
Owner | Guardian |
pendingBurn() |
-- |
uint256 |
Public |
setGuardian(address) |
_guardian |
-- |
Owner |
Events
| Event | Parameters |
Deposited |
address indexed from, uint256 amount |
Burned |
uint256 amount, uint256 newTotalBurned |
GuardianUpdated |
address indexed newGuardian |
ethers.js Example
const BURN_ADDR = "0x6D4582FCac792FD3880e252fC0a585A0c1823e80";
const abi = [
"function pendingBurn() view returns (uint256)",
"function totalBurned() view returns (uint256)",
"function deposit(uint256 amount)",
"function burn(uint256 amount)",
"function burnAll()",
];
const burnReserve = new ethers.Contract(BURN_ADDR, abi, provider);
const pending = await burnReserve.pendingBurn();
const totalBurned = await burnReserve.totalBurned();
console.log("Pending burn:", ethers.utils.formatUnits(pending, 9));
console.log("Total burned:", ethers.utils.formatUnits(totalBurned, 9));
const amount = ethers.utils.parseUnits("100000", 9);
await token.connect(signer).approve(BURN_ADDR, amount);
await burnReserve.connect(signer).deposit(amount);
await burnReserve.connect(owner).burnAll();
contracts/governance/Governance.sol
Timelock-based governor for the Inferno protocol with a 48-hour delay.
All protocol contracts have their ownership transferred to this contract, ensuring that
parameter changes (fee rates, treasury address, etc.) go through the timelock.
The owner proposes parameter changes; after the delay passes, the owner can execute them.
The guardian (or owner) can cancel pending proposals for emergency response.
The delay itself can only be changed via a governance proposal (self-call).
Security: The setDelay function uses the onlySelf modifier,
meaning it can only be called by the Governance contract itself -- i.e., through a proposal that targets
this contract. Delay must be between MIN_DELAY (1 hour) and MAX_DELAY (30 days).
Constructor
| Parameter | Type | Description |
_delay |
uint256 |
Timelock delay in seconds (must be between 1 hour and 30 days) |
_guardian |
address |
Guardian address for proposal cancellation |
Proposal Struct
| Field | Type | Description |
target |
address |
Contract address to call |
data |
bytes |
Encoded function call data |
eta |
uint256 |
Earliest execution time (block.timestamp + delay) |
executed |
bool |
Whether the proposal has been executed |
cancelled |
bool |
Whether the proposal has been cancelled |
Functions
| Function | Parameters | Returns | Access |
propose(address, bytes) |
target, data |
uint256 proposalId |
Owner |
execute(uint256) |
proposalId |
-- |
Owner |
cancel(uint256) |
proposalId |
-- |
Owner | Guardian |
setDelay(uint256) |
_delay |
-- |
Self Only |
setGuardian(address) |
_guardian |
-- |
Owner |
setOwner(address) |
_owner |
-- |
Owner |
getProposal(uint256) |
proposalId |
(address, bytes, uint256, bool, bool) |
Public |
Events
| Event | Parameters |
ProposalCreated |
uint256 indexed id, address target, bytes data, uint256 eta |
ProposalExecuted |
uint256 indexed id |
ProposalCancelled |
uint256 indexed id |
DelayUpdated |
uint256 oldDelay, uint256 newDelay |
GuardianUpdated |
address indexed oldGuardian, address indexed newGuardian |
OwnerUpdated |
address indexed oldOwner, address indexed newOwner |
ethers.js Example
const GOV_ADDR = "0x6050b22E4EAF3f414d1155fBaF30B868e0107017";
const TOKEN_ADDR = "0x3Bd71947F288d1dd8B21129B1bE4FF16EDd5d1F4";
const govAbi = [
"function propose(address target, bytes data) returns (uint256)",
"function execute(uint256 proposalId)",
"function cancel(uint256 proposalId)",
"function getProposal(uint256) view returns (address, bytes, uint256, bool, bool)",
"function delay() view returns (uint256)",
"function proposalCount() view returns (uint256)",
];
const gov = new ethers.Contract(GOV_ADDR, govAbi, provider);
const iface = new ethers.utils.Interface([
"function setFeeRates(uint256, uint256, uint256)",
]);
const calldata = iface.encodeFunctionData("setFeeRates", [150, 50, 100]);
const tx = await gov.connect(owner).propose(TOKEN_ADDR, calldata);
const receipt = await tx.wait();
const proposalId = receipt.events[0].args.id;
await gov.connect(owner).execute(proposalId);
const [target, data, eta, executed, cancelled] =
await gov.getProposal(proposalId);
contracts/lock/IFRLock.sol
Generic token lock contract designed for multi-application use. Users lock IFR tokens
(with an optional lockType tag) and external resolvers query isLocked()
to gate access to features (e.g. premium content, voting weight, staking tiers).
Inherits OpenZeppelin's ReentrancyGuard and Pausable.
No rewards or vesting -- purely a lock/unlock mechanism with on-chain queryability.
Important: This contract must be set as feeExempt
on InfernoToken to prevent fee deductions on lock/unlock transfers. Otherwise, users will lose
tokens to fees when locking and unlocking.
Constructor
| Parameter | Type | Description |
_token |
address |
Address of the IFR token contract |
_guardian |
address |
Guardian address for pause/unpause and guardian updates |
LockData Struct
| Field | Type | Description |
amount |
uint256 |
Total IFR locked by the user |
lockedAt |
uint256 |
Timestamp of last lock/increase |
Functions
| Function | Parameters | Returns | Access |
lock(uint256) |
amount |
-- |
Public |
lockWithType(uint256, bytes32) |
amount, lockType |
-- |
Public |
unlock() |
-- |
-- |
Public |
lockedBalance(address) |
user |
uint256 |
Public |
isLocked(address, uint256) |
user, minAmount |
bool |
Public |
lockInfo(address) |
user |
(uint256 amount, uint256 lockedAt) |
Public |
pause() |
-- |
-- |
Guardian |
unpause() |
-- |
-- |
Guardian |
setGuardian(address) |
_guardian |
-- |
Guardian |
Reentrancy protection: Both lock() / lockWithType() (via internal _lock)
and unlock() use OpenZeppelin's nonReentrant modifier. Locking is also gated by whenNotPaused,
but unlock() always remains available even when paused so users can recover their funds.
Events
| Event | Parameters |
Locked |
address indexed user, uint256 amount, bytes32 indexed lockType |
Unlocked |
address indexed user, uint256 amount |
GuardianUpdated |
address indexed oldGuardian, address indexed newGuardian |
ethers.js Example
const LOCK_ADDR = "0x...";
const TOKEN_ADDR = "0x3Bd71947F288d1dd8B21129B1bE4FF16EDd5d1F4";
const lockAbi = [
"function lock(uint256 amount)",
"function lockWithType(uint256 amount, bytes32 lockType)",
"function unlock()",
"function lockedBalance(address) view returns (uint256)",
"function isLocked(address, uint256) view returns (bool)",
"function lockInfo(address) view returns (uint256, uint256)",
"function totalLocked() view returns (uint256)",
];
const tokenAbi = [
"function approve(address spender, uint256 amount) returns (bool)",
];
const lockContract = new ethers.Contract(LOCK_ADDR, lockAbi, provider);
const token = new ethers.Contract(TOKEN_ADDR, tokenAbi, provider);
const amount = ethers.utils.parseUnits("10000", 9);
await token.connect(signer).approve(LOCK_ADDR, amount);
await lockContract.connect(signer).lock(amount);
const lockType = ethers.utils.id("premium");
await lockContract.connect(signer).lockWithType(amount, lockType);
const minRequired = ethers.utils.parseUnits("5000", 9);
const qualifies = await lockContract.isLocked(userAddr, minRequired);
console.log("Qualifies:", qualifies);
const [lockedAmt, lockedAt] = await lockContract.lockInfo(userAddr);
console.log("Locked:", ethers.utils.formatUnits(lockedAmt, 9), "IFR");
console.log("Since:", new Date(lockedAt.toNumber() * 1000));
await lockContract.connect(signer).unlock();
contracts/partner/PartnerVault.sol
Manages the 40M IFR Partner Ecosystem Pool (4% of total supply). Two mechanisms:
milestone-based unlocking (governance records milestones that unlock IFR allocations) and
lock-triggered creator rewards (when a user locks IFR for a partner's product, the partner accrues
a reward of lockAmount × rewardBps / 10000 from the pool).
Rewards vest linearly over 6–12 months with optional cliff.
Constructor
| Parameter | Type | Description |
_ifrToken | address | InfernoToken address |
_admin | address | Governance Timelock address |
_guardian | address | Emergency pause authority |
_rewardBps | uint256 | Creator reward rate in bps (500–2500 = 5–25%) |
_annualEmissionCap | uint256 | Max IFR distributable per year (1M–10M) |
Admin Functions (Governance only)
| Function | Description |
createPartner(partnerId, beneficiary, maxAllocation, vestingDuration, cliff, tier) | Register a new partner with allocation cap and vesting schedule |
activatePartner(partnerId) | Activate a partner to start earning rewards |
recordMilestone(partnerId, milestoneId, unlockAmount) | Record milestone completion, unlock IFR allocation |
recordLockReward(partnerId, lockAmount, wallet) | Record a user lock → creator reward (lockAmount × effectiveBps / 10000). Anti-double-count per wallet. |
setRewardBps(newBps) | Update reward rate (500–2500 bps) |
setAnnualEmissionCap(newCap) | Update annual emission cap (1M–10M IFR) |
setAuthorizedCaller(caller, status) | Whitelist an address (e.g. backend wallet) to call recordLockReward |
setIFRLock(ifrLockAddress) | Set IFRLock reference for algorithmic emission throttle |
setPartnerBeneficiary(partnerId, newBeneficiary) | Change partner payout address |
setPartnerAllocation(partnerId, newMax) | Adjust partner allocation cap |
finalizeMilestones(partnerId) | Lock milestones — no further milestone unlocks |
Permissionless Functions
| Function | Description |
claim(partnerId) | Transfer vested IFR to partner beneficiary (ReentrancyGuard) |
claimable(partnerId) | View: currently claimable amount |
vestedAmount(partnerId) | View: total vested so far |
partners(partnerId) | View: full partner data struct |
pendingBalance() | View: IFR balance held by vault |
walletRewardClaimed(wallet, partnerId) | View: whether a wallet has been rewarded for a partner |
authorizedCaller(address) | View: whether an address is whitelisted for recordLockReward |
Key Design
- MUST be feeExempt on InfernoToken for correct claim transfers
- Governance-controlled parameters with enforced min/max bounds
- Annual emission cap resets every 365 days
- Milestone replay protection via
milestoneDone mapping
- AuthorizedCaller whitelist:
recordLockReward callable by admin OR whitelisted backend wallet
- Anti-double-count: each
(wallet, partnerId) pair can only be rewarded once
- Algorithmic emission throttle: reward rate scales from
rewardBps down to MIN_REWARD_BPS (500) as global lock ratio rises from 1% to 50%
- SafeERC20 for all token transfers
contracts/FeeRouterV1.sol
Handles protocol-level fees on IFR swaps. Accepts optional EIP-712
discount vouchers issued by the Points Backend.
Deployment (Sepolia)
0x499289C8Ef49769F4FcFF3ca86D4BD7b55B49aa4
— Etherscan
Key Parameters
| Parameter | Value | Governance |
protocolFeeBps | 5 (0.05%) | setFeeBps() — max 25 |
FEE_CAP_BPS | 25 (0.25%) | Hard-coded, immutable |
voucherSigner | Points Backend Wallet | setVoucherSigner() |
paused | false | setPaused() |
Core Functions
| Function | Description |
swapWithFee(adapter, data, voucher, sig, useVoucher) | Swap with optional discount |
isVoucherValid(voucher, sig) | Off-chain voucher check |
setFeeBps(uint16) | Change protocol fee (Governance) |
setAdapter(address, bool) | Whitelist adapter (Governance) |
setVoucherSigner(address) | Rotate signer (Governance) |
setPaused(bool) | Emergency pause (Governance) |
EIP-712 Voucher Struct
DiscountVoucher {
address user
uint16 discountBps
uint32 maxUses
uint64 expiry
uint256 nonce
}
Key Design Points
- Replay protection:
usedNonces[user][nonce] = true after voucher use
- Fee cap:
FEE_CAP_BPS = 25 is immutable — governance cannot exceed 0.25%
- Adapter whitelist: only governance-approved adapters can receive swap calls
- Pause: governance can halt all swaps in emergency
- Owned by Governance timelock
Created by Uniswap V2 Factory
The IFR/WETH liquidity pair on Uniswap V2. This is not a custom contract but a standard
UniswapV2Pair created by the factory during the LP pairing step. It holds
400M IFR (40% of total supply) paired with ETH, providing the primary
trading market for the token.
Note: The LP Pair is a standard Uniswap V2 contract, not an Inferno-authored contract.
It is included here as the 10th on-chain component because it holds a significant portion of
the token supply and is critical infrastructure for the protocol.
Addresses
| Component | Address |
| LP Pair |
0x2252e8bBDE0E50CD372748aC233A99C08627d9c7 |
| Uniswap V2 Router |
0xC532a74256D3Db42D0Bf7a0400fEFDbad7694008 |
| Uniswap V2 Factory |
0x7E0987E5b3a30e3f2828572Bb659A548460a3003 |
| WETH |
0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9 |
Pool Details
| Property | Value |
| IFR in Pool |
400,000,000 IFR (40% of supply) |
| ETH in Pool |
0.01 ETH (Sepolia testnet) |
| LP Tokens |
Held by deployer (to be locked/burned on mainnet) |
ethers.js Example
const PAIR_ADDR = "0x2252e8bBDE0E50CD372748aC233A99C08627d9c7";
const pairAbi = [
"function getReserves() view returns (uint112, uint112, uint32)",
"function token0() view returns (address)",
"function token1() view returns (address)",
"function totalSupply() view returns (uint256)",
];
const pair = new ethers.Contract(PAIR_ADDR, pairAbi, provider);
const [reserve0, reserve1] = await pair.getReserves();
const token0 = await pair.token0();
console.log("Token0:", token0);
console.log("Reserve0:", reserve0.toString());
console.log("Reserve1:", reserve1.toString());
Inferno Protocol — Solidity 0.8.20 • OpenZeppelin v5 • Hardhat v2
All contracts verified on Sepolia Etherscan