Etheris

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];
    }
    

}

Tags:
ERC20, Token, Burnable, Factory|addr:0xd7932ca3c54ebb65e18a5b1049ffba656a528679|verified:true|block:23428107|tx:0xafbd7e05dbc3e6736ec57d8ade67733e4449007c19f35c4a2bbbde527059455a|first_check:1758728093

Submitted on: 2025-09-24 17:34:58

Comments

Log in to comment.

No comments yet.