// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** * @title AfterCoin (AFC) * @notice ERC-20 token for the Afterlife Project game preservation platform. * 1 AFC = 1 diamond. Zero decimals — integer-only balances. * @dev Self-contained implementation (no OpenZeppelin). Owner-gated mint, * burn-from, and bridge-transfer helpers for the off-chain bridge service. */ contract AfterCoin { // ──────────────────────────── ERC-20 metadata ──────────────────────────── string private constant _name = "AfterCoin"; string private constant _symbol = "AFC"; uint8 private constant _decimals = 0; // 1 token = 1 diamond // ──────────────────────────── State ────────────────────────────────────── uint256 private _totalSupply; mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; address public owner; // ──────────────────────────── Events (ERC-20) ──────────────────────────── event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); // ──────────────────────────── Errors ───────────────────────────────────── error NotOwner(); error ZeroAddress(); error InsufficientBalance(address account, uint256 required, uint256 available); error InsufficientAllowance(address spender, uint256 required, uint256 available); // ──────────────────────────── Modifier ─────────────────────────────────── modifier onlyOwner() { if (msg.sender != owner) revert NotOwner(); _; } // ──────────────────────────── Constructor ──────────────────────────────── constructor() { owner = msg.sender; // Initial supply is 0 — tokens are minted on demand by the bridge. } // ──────────────────────────── ERC-20 view functions ────────────────────── function name() external pure returns (string memory) { return _name; } function symbol() external pure returns (string memory) { return _symbol; } function decimals() external pure returns (uint8) { return _decimals; } function totalSupply() external view returns (uint256) { return _totalSupply; } function balanceOf(address account) external view returns (uint256) { return _balances[account]; } function allowance(address tokenOwner, address spender) external view returns (uint256) { return _allowances[tokenOwner][spender]; } // ──────────────────────────── ERC-20 mutative functions ────────────────── function transfer(address to, uint256 amount) external returns (bool) { _transfer(msg.sender, to, amount); return true; } function approve(address spender, uint256 amount) external returns (bool) { _approve(msg.sender, spender, amount); return true; } function transferFrom( address from, address to, uint256 amount ) external returns (bool) { uint256 currentAllowance = _allowances[from][msg.sender]; if (currentAllowance != type(uint256).max) { if (currentAllowance < amount) { revert InsufficientAllowance(msg.sender, amount, currentAllowance); } unchecked { _approve(from, msg.sender, currentAllowance - amount); } } _transfer(from, to, amount); return true; } // ──────────────────────────── Owner-only functions ─────────────────────── /** * @notice Mint new tokens to `to`. Only callable by the contract owner. * @param to Recipient address. * @param amount Number of tokens to create. */ function mint(address to, uint256 amount) external onlyOwner { if (to == address(0)) revert ZeroAddress(); _totalSupply += amount; _balances[to] += amount; emit Transfer(address(0), to, amount); } /** * @notice Burn tokens from `from`. Only callable by the contract owner. * Does NOT require an allowance — the owner is the bridge operator. * @param from Address whose tokens are burned. * @param amount Number of tokens to destroy. */ function burnFrom(address from, uint256 amount) external onlyOwner { if (from == address(0)) revert ZeroAddress(); uint256 bal = _balances[from]; if (bal < amount) { revert InsufficientBalance(from, amount, bal); } unchecked { _balances[from] = bal - amount; } _totalSupply -= amount; emit Transfer(from, address(0), amount); } /** * @notice Transfer tokens between two addresses on behalf of the bridge. * Only callable by the contract owner. * @param from Source address. * @param to Destination address. * @param amount Number of tokens to move. */ function bridgeTransfer( address from, address to, uint256 amount ) external onlyOwner { _transfer(from, to, amount); } // ──────────────────────────── Internal helpers ─────────────────────────── function _transfer(address from, address to, uint256 amount) internal { if (from == address(0)) revert ZeroAddress(); if (to == address(0)) revert ZeroAddress(); uint256 fromBal = _balances[from]; if (fromBal < amount) { revert InsufficientBalance(from, amount, fromBal); } unchecked { _balances[from] = fromBal - amount; } _balances[to] += amount; emit Transfer(from, to, amount); } function _approve(address tokenOwner, address spender, uint256 amount) internal { if (tokenOwner == address(0)) revert ZeroAddress(); if (spender == address(0)) revert ZeroAddress(); _allowances[tokenOwner][spender] = amount; emit Approval(tokenOwner, spender, amount); } }