// SPDX-License-Identifier: MIT pragma solidity 0.8.26; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; /// @title Ambrosia ($AMBR) /// @notice Lifetime cap of 1,000,000,000 counted on every token ever minted, so burns never reopen room. /// Three minters, each with its own fixed budget, wired once by the deployer. No owner after that. contract AmbrosiaToken is ERC20, ERC20Burnable { uint256 public constant CAP = 1_000_000_000 ether; uint256 public constant MINTER_BUDGET = 850_000_000 ether; // 664.388M paid + 130M launch pool + 50M free uint256 public constant ROUNDS_BUDGET = 100_000_000 ether; uint256 public constant STAKING_BUDGET = 50_000_000 ether; address private immutable deployer; address public minter; address public rounds; address public staking; uint256 public totalMinted; mapping(address => uint256) public mintedBy; error AlreadyWired(); error NotMinter(); error OverBudget(); constructor() ERC20("Ambrosia", "AMBR") { deployer = msg.sender; } function wire(address minter_, address rounds_, address staking_) external { if (msg.sender != deployer || minter != address(0)) revert AlreadyWired(); minter = minter_; rounds = rounds_; staking = staking_; } function budgetOf(address who) public view returns (uint256) { if (who == address(0)) return 0; if (who == minter) return MINTER_BUDGET; if (who == rounds) return ROUNDS_BUDGET; if (who == staking) return STAKING_BUDGET; return 0; } function mint(address to, uint256 amount) external { uint256 budget = budgetOf(msg.sender); if (budget == 0) revert NotMinter(); uint256 used = mintedBy[msg.sender] + amount; if (used > budget || totalMinted + amount > CAP) revert OverBudget(); mintedBy[msg.sender] = used; totalMinted += amount; _mint(to, amount); } }