Token
0x00f0b7b5111ee8fa10f803d5f489842066d46ed6
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
3,418 bytes
ABI entries
24
15 read · 5 write
License
none
Contract information
- Address
- 0x00f0b7b5111ee8fa10f803d5f489842066d46ed6
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x5bB5C36721…2c38244FcA
- Creation tx
- 0xfa68dfe9ea…836f699a23
Token
- Name
- T-BILL
- Symbol
- TBILL
- Decimals
- 9
- Holders
- 287
- Market
- View token page →
Read contract (15)
airdropComplete() → bool
airdropSource() → address
allowance(address, address) → uint256
balanceOf(address) → uint256
decimals() → uint8
distributorAddress() → address
getCirculatingSupply() → uint256
getOwner() → address
isCapExempt(address) → bool
isDividendExempt(address) → bool
maxWalletAmount() → uint256
maxWalletBps() → uint256
name() → string
symbol() → string
totalSupply() → uint256
Events (2)
ApprovalTransfer
ABI
[
{
"inputs": [
{
"internalType": "string",
"name": "name_",
"type": "string"
},
{
"internalType": "string",
"name": "symbol_",
"type": "string"
},
{
"internalType": "address",
"name": "_dexRouter",
"type": "address"
},
{
"internalType": "address",
"name": "_rewardToken",
"type": "address"
},
{
"internalType": "address[]",
"name": "_dividendExempt",
"type": "address[]"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [],
"name": "airdropComplete",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "airdropSource",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "holder",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "approveMax",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "completeAirdrop",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "distributorAddress",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCirculatingSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getOwner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "isCapExempt",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "isDividendExempt",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxWalletAmount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "maxWalletBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
/**
SPDX-License-Identifier: Unlicensed
*/
/**
* Reward-bearing ERC-20.
*
* Transfers carry NO fee of any kind: `amount` sent is `amount` received,
* always. Holders accrue dividends in a reward token, funded externally
* via the distributor's public `deposit()` (e.g. by a liquidity-fee
* harvester). There are no owner or admin functions, no fee setters, no
* transfer limits, no blacklist, and no way to withdraw contract funds.
* Nothing here can be changed after deployment.
*/
pragma solidity ^0.8.24;
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) { return a + b; }
function sub(uint256 a, uint256 b) internal pure returns (uint256) { return a - b; }
function mul(uint256 a, uint256 b) internal pure returns (uint256) { return a * b; }
function div(uint256 a, uint256 b) internal pure returns (uint256) { return a / b; }
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b <= a, errorMessage);
return a - b;
}
}
}
interface IERC20Full {
function totalSupply() external view returns (uint256);
function decimals() external view returns (uint8);
function symbol() external view returns (string memory);
function name() external view returns (string memory);
function getOwner() external view returns (address);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address _owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
interface IDEXRouter {
function WETH() external pure returns (address);
function factory() external view returns (address);
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable;
}
interface IDEXFactoryView {
function getPair(address tokenA, address tokenB) external view returns (address);
}
interface IDEXPair {
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
function token0() external view returns (address);
}
interface IDividendDistributor {
function setShare(address shareholder, uint256 amount) external;
function deposit() external payable;
function process(uint256 gas) external;
}
contract Distributor is IDividendDistributor {
using SafeMath for uint256;
address _token;
struct Share {
uint256 amount;
uint256 totalExcluded; // excluded dividend
uint256 totalRealised;
}
IERC20Full public rewardToken;
address public WETH;
IDEXRouter router;
address[] shareholders;
mapping (address => uint256) shareholderIndexes;
mapping (address => uint256) public shareholderClaims;
mapping (address => Share) public shares;
// Dividends accrued to a holder and settled into a claimable balance when
// their token balance changed. Banking here (instead of transferring
// during a balance change) keeps setShare free of external calls, so a
// reward-token failure can never leave ERC20 balances updated while
// dividend shares go stale. Paid out only in the claim/process path.
mapping (address => uint256) public unclaimed;
uint256 public totalShares;
uint256 public totalDividends;
uint256 public totalDistributed; // to be shown in UI
uint256 public dividendsPerShare;
uint256 public dividendsPerShareAccuracyFactor = 10 ** 36;
uint256 public minPeriod = 1 hours;
// Denominated in reward-token units; set in the constructor to 1 whole
// reward token (10 ** rewardToken.decimals()).
uint256 public minDistribution;
uint256 currentIndex;
modifier onlyToken() {
require(msg.sender == _token); _;
}
constructor (address _router, address _rewardToken) {
require(_router != address(0), "router is zero address");
require(_rewardToken != address(0), "reward token is zero address");
router = IDEXRouter(_router);
rewardToken = IERC20Full(_rewardToken);
WETH = router.WETH();
minDistribution = 10 ** IERC20Full(_rewardToken).decimals();
_token = msg.sender;
}
function setShare(address shareholder, uint256 amount) external override onlyToken {
// Settle accrued dividends into the claimable balance WITHOUT any
// external call, so this function is deterministic and cannot revert
// on a reward-token failure (which would otherwise desync shares from
// the ERC20 balance in the token's try/catch caller).
if(shares[shareholder].amount > 0){
uint256 accrued = _accrued(shareholder);
if(accrued > 0){ unclaimed[shareholder] = unclaimed[shareholder].add(accrued); }
}
if(amount > 0 && shares[shareholder].amount == 0){
addShareholder(shareholder);
}else if(amount == 0 && shares[shareholder].amount > 0){
removeShareholder(shareholder);
}
totalShares = totalShares.sub(shares[shareholder].amount).add(amount);
shares[shareholder].amount = amount;
shares[shareholder].totalExcluded = getCumulativeDividends(shares[shareholder].amount);
}
// Max slippage tolerated on the ETH->reward swap, in basis points of the
// reserve-implied fair output. Bounds sandwich MEV: a swap that returns
// less than this reverts (and the caller rolls the deposit to a later
// attempt) rather than filling at an attacker-moved price.
uint256 public constant maxSlippageBps = 1000; // 10%
// Anyone may fund dividends: sent ETH is swapped to the reward token and
// credited pro-rata to all current shareholders. Reverts when there are
// no shareholders so funds are never stranded.
function deposit() external payable override {
require(totalShares > 0, "no shareholders");
uint256 balanceBefore = rewardToken.balanceOf(address(this));
address[] memory path = new address[](2);
path[0] = WETH;
path[1] = address(rewardToken);
router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: msg.value}(
_minRewardOut(msg.value),
path,
address(this),
block.timestamp
);
uint256 amount = rewardToken.balanceOf(address(this)).sub(balanceBefore);
totalDividends = totalDividends.add(amount);
dividendsPerShare = dividendsPerShare.add(dividendsPerShareAccuracyFactor.mul(amount).div(totalShares));
}
// Fair-output floor for `ethIn` worth of WETH->reward, derived from the
// canonical V2 pair's current reserves (constant-product with the 0.3%
// fee), discounted by maxSlippageBps. Returns 0 if the pair is missing or
// empty so the swap can still proceed (the harvest caller wraps deposit in
// try/catch, so a bad fill reverts and rolls forward rather than locking).
function _minRewardOut(uint256 ethIn) internal view returns (uint256) {
address pair = IDEXFactoryView(router.factory()).getPair(WETH, address(rewardToken));
if (pair == address(0)) return 0;
(uint112 r0, uint112 r1, ) = IDEXPair(pair).getReserves();
(uint256 reserveIn, uint256 reserveOut) =
IDEXPair(pair).token0() == WETH ? (uint256(r0), uint256(r1)) : (uint256(r1), uint256(r0));
if (reserveIn == 0 || reserveOut == 0) return 0;
uint256 amountInWithFee = ethIn.mul(997);
uint256 expectedOut = amountInWithFee.mul(reserveOut) / (reserveIn.mul(1000).add(amountInWithFee));
return expectedOut.mul(10000 - maxSlippageBps) / 10000;
}
function process(uint256 gas) external override onlyToken {
uint256 shareholderCount = shareholders.length;
if(shareholderCount == 0) { return; }
uint256 gasUsed = 0;
uint256 gasLeft = gasleft();
uint256 iterations = 0;
while(gasUsed < gas && iterations < shareholderCount) {
if(currentIndex >= shareholderCount){
currentIndex = 0;
}
if(shouldDistribute(shareholders[currentIndex])){
_distribute(shareholders[currentIndex]);
}
gasUsed = gasUsed.add(gasLeft.sub(gasleft()));
gasLeft = gasleft();
currentIndex++;
iterations++;
}
}
function shouldDistribute(address shareholder) internal view returns (bool) {
return shareholderClaims[shareholder] + minPeriod < block.timestamp
&& getUnpaidEarnings(shareholder) > minDistribution;
}
function _distribute(address shareholder) internal {
uint256 amount = getUnpaidEarnings(shareholder); // banked + accrued
if(amount == 0){ return; }
// Effects before interaction: zero the claim before paying, so a
// reentrant call during the transfer sees nothing owed.
unclaimed[shareholder] = 0;
shares[shareholder].totalExcluded = getCumulativeDividends(shares[shareholder].amount);
if(_pay(shareholder, amount)){
shareholderClaims[shareholder] = block.timestamp;
shares[shareholder].totalRealised = shares[shareholder].totalRealised.add(amount);
totalDistributed = totalDistributed.add(amount);
} else {
// Failed or frozen recipient: re-bank the full amount so it stays
// claimable later. One bad holder is skipped, never bricking the
// payout loop for everyone else.
unclaimed[shareholder] = amount;
}
}
// Non-reverting reward payout: a revert-style freeze (how real stablecoin
// blacklists behave) is treated the same as a false return.
function _pay(address shareholder, uint256 amount) internal returns (bool) {
try rewardToken.transfer(shareholder, amount) returns (bool ok) {
return ok;
} catch {
return false;
}
}
function claimDividend() external {
_distribute(msg.sender);
}
// Dividends still accruing on the current share balance (excludes banked).
function _accrued(address shareholder) internal view returns (uint256) {
if(shares[shareholder].amount == 0){ return 0; }
uint256 total = getCumulativeDividends(shares[shareholder].amount);
uint256 excluded = shares[shareholder].totalExcluded;
if(total <= excluded){ return 0; }
return total.sub(excluded);
}
function getUnpaidEarnings(address shareholder) public view returns (uint256) {
return unclaimed[shareholder].add(_accrued(shareholder));
}
function getCumulativeDividends(uint256 share) internal view returns (uint256) {
return share.mul(dividendsPerShare).div(dividendsPerShareAccuracyFactor);
}
function addShareholder(address shareholder) internal {
shareholderIndexes[shareholder] = shareholders.length;
shareholders.push(shareholder);
}
function removeShareholder(address shareholder) internal {
shareholders[shareholderIndexes[shareholder]] = shareholders[shareholders.length-1];
shareholderIndexes[shareholders[shareholders.length-1]] = shareholderIndexes[shareholder];
shareholders.pop();
}
}
contract Token is IERC20Full {
using SafeMath for uint256;
address constant DEAD = 0x000000000000000000000000000000000000dEaD;
address constant ZERO = 0x0000000000000000000000000000000000000000;
string _name;
string _symbol;
uint8 constant _decimals = 9;
uint256 constant _totalSupply = 1_000_000_000_000_000 * (10 ** _decimals);
mapping (address => uint256) _balances;
mapping (address => mapping (address => uint256)) _allowances;
// Infrastructure addresses (pool singleton, position manager, routers,
// fee harvester) hold tokens in custody, not as investors; they are
// excluded from dividends and the wallet cap at construction and this
// can never change.
mapping (address => bool) public isDividendExempt;
mapping (address => bool) public isCapExempt;
// Anti-whale wallet cap: no wallet may accumulate more than 2.5% of
// supply. Wallets already above the cap (migration airdrops) can only
// reduce their balance until they are under it.
//
// The deployer (airdropSource) may deliver over-cap balances so the
// migration airdrop can seed holders above 2.5%. This bypass is NOT
// permanent: once completeAirdrop() is called it is disabled forever and
// the deployer becomes an ordinary, cap-bound wallet. The launch pipeline
// calls completeAirdrop() immediately after the airdrop finishes.
uint256 public constant maxWalletBps = 250;
address public immutable airdropSource;
bool public airdropComplete;
Distributor distributor;
address public distributorAddress;
uint256 constant distributorGas = 500000;
constructor (
string memory name_,
string memory symbol_,
address _dexRouter,
address _rewardToken,
address[] memory _dividendExempt
) {
_name = name_;
_symbol = symbol_;
distributor = new Distributor(_dexRouter, _rewardToken);
distributorAddress = address(distributor);
isDividendExempt[address(this)] = true;
isDividendExempt[DEAD] = true;
isCapExempt[address(this)] = true;
isCapExempt[DEAD] = true;
for (uint256 i = 0; i < _dividendExempt.length; i++) {
isDividendExempt[_dividendExempt[i]] = true;
isCapExempt[_dividendExempt[i]] = true;
}
airdropSource = msg.sender;
_balances[msg.sender] = _totalSupply;
emit Transfer(address(0), msg.sender, _totalSupply);
}
function maxWalletAmount() public pure returns (uint256) {
return _totalSupply * maxWalletBps / 10000;
}
// One-way switch: permanently ends the deployer's cap bypass. Idempotent,
// callable only by the deployer. After this the deployer is cap-bound like
// any other wallet and no address can ever deliver over-cap balances.
function completeAirdrop() external {
require(msg.sender == airdropSource, "not airdrop source");
airdropComplete = true;
}
receive() external payable { }
function totalSupply() external pure override returns (uint256) { return _totalSupply; }
function decimals() external pure override returns (uint8) { return _decimals; }
function symbol() external view override returns (string memory) { return _symbol; }
function name() external view override returns (string memory) { return _name; }
// No owner or admin functions exist anywhere in this contract.
function getOwner() external pure override returns (address) { return address(0); }
function balanceOf(address account) public view override returns (uint256) { return _balances[account]; }
function allowance(address holder, address spender) external view override returns (uint256) { return _allowances[holder][spender]; }
function approve(address spender, uint256 amount) public override returns (bool) {
_allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function approveMax(address spender) external returns (bool) {
return approve(spender, type(uint256).max);
}
function transfer(address recipient, uint256 amount) external override returns (bool) {
return _transferFrom(msg.sender, recipient, amount);
}
function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool) {
uint256 allowed = _allowances[sender][msg.sender];
// standard infinite-approval sentinel; every finite allowance is consumed
if (allowed != type(uint256).max) {
_allowances[sender][msg.sender] = allowed.sub(amount, "Insufficient Allowance");
}
return _transferFrom(sender, recipient, amount);
}
function _transferFrom(address sender, address recipient, uint256 amount) internal returns (bool) {
_balances[sender] = _balances[sender].sub(amount, "Insufficient Balance");
bool airdropBypass = (sender == airdropSource && !airdropComplete);
if (!isCapExempt[recipient] && !airdropBypass) {
require(_balances[recipient].add(amount) <= maxWalletAmount(), "wallet cap exceeded");
}
_balances[recipient] = _balances[recipient].add(amount);
if(!isDividendExempt[sender]){ try distributor.setShare(sender, _balances[sender]) {} catch {} }
if(!isDividendExempt[recipient]){ try distributor.setShare(recipient, _balances[recipient]) {} catch {} }
try distributor.process(distributorGas) {} catch {}
emit Transfer(sender, recipient, amount);
return true;
}
function getCirculatingSupply() public view returns (uint256) {
return _totalSupply.sub(balanceOf(DEAD)).sub(balanceOf(ZERO));
}
}
Chain explorer5846msChain node87ms