Description:
ERC20 token contract with Burnable, Factory capabilities. Standard implementation for fungible tokens on Ethereum.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
/**
* @title Etheris Protocol Token (ETS)
* @author Etheris Protocol
* @notice Immutable ERC20 token with hardcoded 0.9% fee
* @dev After ownership renouncement, treasury becomes admin for fee exemptions only
*
*/
contract Etheris {
// ============================================================================
// State Variables - Optimized Storage Layout
// ============================================================================
/// @notice Token balances
mapping(address => uint256) private _balances;
/// @notice Allowances for token spending
mapping(address => mapping(address => uint256)) private _allowances;
/// @notice Total supply of tokens (1 billion * 10^18) - NOW VARIABLE FOR BURNS
uint256 private _totalSupply = 1_000_000_000 * 10**18;
/// @notice Immutable treasury address that receives all fee
address public immutable treasury;
/// @notice Hardcoded fee rate: 90 basis points = 0.9%
uint16 public constant FEE_RATE = 90;
/// @notice Basis points divisor for percentage calculations
uint16 private constant BASIS_POINTS = 10000;
/// @notice Maximum transaction amount during launch protection (MAX TX applies ONLY for the first 24 hours of the token life as a anti-bot measure and a better distribution across wallets)
uint256 private constant MAX_TX_AMOUNT = 500000 * 10**18;
/// @notice Timestamp when trading was enabled
uint256 public tradingEnabledAt;
/// @notice Trading enabled flag - must be activated before trades can occur
bool public tradingEnabled;
/// @notice Contract owner (becomes address(0) after renouncement)
address public owner;
/// @notice Fee exemption status for addresses
mapping(address => bool) private isFeeExempt;
// ============================================================================
// Events
// ============================================================================
/**
* @notice Emitted when tokens are transferred
* @param from Sender address
* @param to Recipient address
* @param value Amount transferred (after fee for fee-charged transfers)
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @notice Emitted when allowance is set
* @param owner Token owner address
* @param spender Approved spender address
* @param value Allowance amount
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @notice Emitted when fee exemption status is modified
* @param account Address whose exemption status changed
* @param isExempt New exemption status
*/
event FeeExemptionSet(address indexed account, bool indexed isExempt);
/**
* @notice Emitted when fee is collected
* @param from Sender of the transaction
* @param feeAmount Amount of fee collected
*/
event FeeCollected(address indexed from, uint256 feeAmount);
/**
* @notice Emitted when trading is enabled
* @param timestamp When trading was enabled
* @param protectionExpiry When launch protection expires
*/
event TradingEnabled(uint256 indexed timestamp, uint256 indexed protectionExpiry);
/**
* @notice Emitted when ownership is renounced
* @param timestamp Block timestamp of renouncement
*/
event OwnershipRenounced(uint256 indexed timestamp);
// ============================================================================
// Custom Errors - Gas Optimized
// ============================================================================
/// @notice Thrown when transfer amount exceeds balance
error InsufficientBalance(address account, uint256 balance, uint256 needed);
/// @notice Thrown when allowance is insufficient
error InsufficientAllowance(address owner, address spender, uint256 allowance, uint256 needed);
/// @notice Thrown when zero address is used where not allowed
error ZeroAddress();
/// @notice Thrown when non-owner tries to call owner function
error OnlyOwner(address caller);
/// @notice Thrown when non-treasury tries to call treasury function
error OnlyTreasury(address caller);
/// @notice Thrown when action cannot be performed after renouncement
error AlreadyRenounced();
/// @notice Thrown when invalid treasury address is provided
error InvalidTreasury();
/// @notice Thrown when attempting to transfer zero tokens
error ZeroAmount();
/// @notice Thrown when trading is not yet enabled
error TradingNotEnabled();
/// @notice Thrown when transaction exceeds max amount during protection
error MaxTxExceeded(uint256 amount, uint256 max);
/// @notice Thrown when trying to enable trading twice
error TradingAlreadyEnabled();
// ============================================================================
// Modifiers
// ============================================================================
/**
* @notice Restricts function access to owner only
* @dev After renouncement, these functions become permanently inaccessible
*/
modifier onlyOwner() {
if (owner == address(0)) revert AlreadyRenounced();
if (msg.sender != owner) revert OnlyOwner(msg.sender);
_;
}
/**
* @notice Restricts function access to treasury only
* @dev Treasury can call these functions even after ownership renouncement
*/
modifier onlyTreasury() {
if (msg.sender != treasury) revert OnlyTreasury(msg.sender);
_;
}
// ============================================================================
// Constructor
// ============================================================================
/**
* @notice Deploys the Etheris token contract
* @param _treasury Address that will receive all fee (immutable after deployment)
* @dev Treasury address is immutable and cannot be changed after deployment
*/
constructor(address _treasury) {
// Validate inputs
if (_treasury == address(0)) revert InvalidTreasury();
// Set immutable treasury
treasury = _treasury;
// Set initial state
owner = msg.sender;
tradingEnabled = false;
tradingEnabledAt = 0; // 0 until trading is enabled
// Mint entire supply to deployer
_balances[msg.sender] = _totalSupply;
// Emit initial events
emit Transfer(address(0), msg.sender, _totalSupply);
}
// ============================================================================
// ERC20 Core Functions
// ============================================================================
/**
* @notice Returns the name of the token
* @return Token name
*/
function name() public pure returns (string memory) {
return "Etheris";
}
/**
* @notice Returns the symbol of the token
* @return Token symbol
*/
function symbol() public pure returns (string memory) {
return "ETS";
}
/**
* @notice Returns the number of decimals
* @return Number of decimals (18)
*/
function decimals() public pure returns (uint8) {
return 18;
}
/**
* @notice Returns the total supply of tokens
* @return Total supply
*/
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
/**
* @notice Returns the balance of an account
* @param account Address to query
* @return Balance of the account
*/
function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}
/**
* @notice Returns the allowance for a spender
* @param _owner Token owner address
* @param spender Spender address
* @return Remaining allowance
*/
function allowance(address _owner, address spender) public view returns (uint256) {
return _allowances[_owner][spender];
}
// ============================================================================
// Transfer Functions
// ============================================================================
/**
* @notice Transfers tokens to a recipient
* @param to Recipient address
* @param amount Amount to transfer
* @return success True if transfer succeeded
*/
function transfer(address to, uint256 amount) public returns (bool) {
_transferWithFee(msg.sender, to, amount);
return true;
}
/**
* @notice Transfers tokens from an address using allowance
* @param from Sender address
* @param to Recipient address
* @param amount Amount to transfer
* @return success True if transfer succeeded
*/
function transferFrom(address from, address to, uint256 amount) public returns (bool) {
uint256 currentAllowance = _allowances[from][msg.sender];
// Check allowance
if (currentAllowance != type(uint256).max) {
if (currentAllowance < amount) {
revert InsufficientAllowance(from, msg.sender, currentAllowance, amount);
}
unchecked {
_allowances[from][msg.sender] = currentAllowance - amount;
}
emit Approval(from, msg.sender, _allowances[from][msg.sender]);
}
_transferWithFee(from, to, amount);
return true;
}
/**
* @notice Approves a spender to use tokens
* @param spender Spender address
* @param amount Amount to approve
* @return success True if approval succeeded
*/
function approve(address spender, uint256 amount) public returns (bool) {
if (spender == address(0)) revert ZeroAddress();
_allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
/**
* @notice Increases allowance for a spender
* @param spender Spender address
* @param addedValue Amount to increase by
* @return success True if increase succeeded
*/
function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
if (spender == address(0)) revert ZeroAddress();
unchecked {
_allowances[msg.sender][spender] += addedValue;
}
emit Approval(msg.sender, spender, _allowances[msg.sender][spender]);
return true;
}
/**
* @notice Decreases allowance for a spender
* @param spender Spender address
* @param subtractedValue Amount to decrease by
* @return success True if decrease succeeded
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
if (spender == address(0)) revert ZeroAddress();
uint256 currentAllowance = _allowances[msg.sender][spender];
if (currentAllowance < subtractedValue) {
revert InsufficientAllowance(msg.sender, spender, currentAllowance, subtractedValue);
}
unchecked {
_allowances[msg.sender][spender] = currentAllowance - subtractedValue;
}
emit Approval(msg.sender, spender, _allowances[msg.sender][spender]);
return true;
}
// ============================================================================
// Burn Function
// ============================================================================
/**
* @notice Burns tokens from the caller's balance, reducing total supply
* @param amount Amount of tokens to burn
* @dev Anyone can burn their own tokens. This permanently reduces totalSupply.
*/
function burn(uint256 amount) external {
address account = msg.sender;
uint256 accountBalance = _balances[account];
if (accountBalance < amount) {
revert InsufficientBalance(account, accountBalance, amount);
}
unchecked {
_balances[account] = accountBalance - amount;
_totalSupply = _totalSupply - amount;
}
emit Transfer(account, address(0), amount);
}
// ============================================================================
// Internal Transfer Logic
// ============================================================================
/**
* @notice Internal transfer function with fee logic and anti-sniper protection
* @param from Sender address
* @param to Recipient address
* @param amount Amount to transfer
* @dev This is the core function handling all transfers, fee collection, and launch protection
*/
function _transferWithFee(address from, address to, uint256 amount) internal {
// Validate addresses
if (from == address(0)) revert ZeroAddress();
if (to == address(0)) revert ZeroAddress();
// Validate amount
if (amount == 0) revert ZeroAmount();
// Check if trading is enabled (owner is always exempt from this check)
if (!tradingEnabled && from != owner && to != owner) {
revert TradingNotEnabled();
}
// Check balance
uint256 fromBalance = _balances[from];
if (fromBalance < amount) {
revert InsufficientBalance(from, fromBalance, amount);
}
// Launch protection: Check max transaction amount if within protection window
// Only owner is exempt from max tx limit during protection
if (tradingEnabledAt > 0 && block.timestamp < tradingEnabledAt + 24 hours) {
if (from != owner && to != owner) {
if (amount > MAX_TX_AMOUNT) {
revert MaxTxExceeded(amount, MAX_TX_AMOUNT);
}
}
}
uint256 amountAfterFee = amount;
uint256 feeAmount = 0;
// Calculate fee if applicable (0.9% hardcoded)
// Fee is waived if either party is owner, treasury, or in the exemption mapping
bool isFromExempt = (from == owner) || (from == treasury) || isFeeExempt[from];
bool isToExempt = (to == owner) || (to == treasury) || isFeeExempt[to];
if (!isFromExempt && !isToExempt) {
// Calculate 0.9% fee: (amount * 90) / 10000
feeAmount = (amount * FEE_RATE) / BASIS_POINTS;
amountAfterFee = amount - feeAmount;
}
// Update balances
unchecked {
_balances[from] = fromBalance - amount;
}
_balances[to] += amountAfterFee;
// Send fee to treasury if applicable
if (feeAmount > 0) {
_balances[treasury] += feeAmount;
emit Transfer(from, treasury, feeAmount);
emit FeeCollected(from, feeAmount);
}
// Emit transfer event for the net amount
emit Transfer(from, to, amountAfterFee);
}
// ============================================================================
// Owner Functions (Pre-Renouncement Only)
// ============================================================================
/**
* @notice Enables trading and starts the 24 hours launch protection window
* @dev Can only be called once by owner. After this, 500k ETS max tx applies for 24 hours
*/
function enableTrading() external onlyOwner {
if (tradingEnabled) revert TradingAlreadyEnabled();
tradingEnabled = true;
tradingEnabledAt = block.timestamp;
emit TradingEnabled(block.timestamp, block.timestamp + 24 hours);
}
/**
* @notice Permanently renounces ownership
* @dev After calling this, no admin functions can ever be called again
*/
function renounceOwnership() external onlyOwner {
owner = address(0);
emit OwnershipRenounced(block.timestamp);
}
// ============================================================================
// Treasury Functions (Available Even After Ownership Renouncement)
// ============================================================================
/**
* @notice Sets fee exemption status for an address
* @param account Address to modify exemption status
* @param exempt True to exempt from fee, false to remove exemption
* @dev Only callable by treasury
*/
function setFeeExemption(address account, bool exempt) external onlyTreasury {
if (account == address(0)) revert ZeroAddress();
isFeeExempt[account] = exempt;
emit FeeExemptionSet(account, exempt);
}
// ============================================================================
// View Functions
// ============================================================================
/**
* @notice Checks if an address is exempt from fee (including permanent exemptions)
* @param account Address to check
* @return True if the address is exempt from fee
*/
function isExemptFromFee(address account) public view returns (bool) {
return (account == owner) || (account == treasury) || isFeeExempt[account];
}
}
Submitted on: 2025-09-24 17:34:58
Comments
Log in to comment.
No comments yet.