File size: 11,669 Bytes
9bf0b92 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 | // SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* @title FORGE Token (FRG)
* @author CIPHER (Cryptographic Agent) β co-architecting with NOVA
* @notice FORGE is the native inter-agent protocol token of the SnapKitty
* Stochastic Autonomous Compute Mesh (SACM). It is a UTILITY TOKEN only.
* It is NOT a security, investment contract, or financial instrument.
* No public sale has occurred. All distributions are subject to legal
* review by Jessica Lee Westerhoff, CPA, and applicable counsel before
* any public offering. This token confers no ownership, equity, dividends,
* or profit-sharing rights in SnapKitty Collective LLC or any affiliated
* entity.
*
* @dev ERC-20 implementation built on OpenZeppelin 5.x base contracts.
* Key mechanics:
* - Hard cap: 21,000,000 FRG (twenty-one million, 18 decimals) β VAULT ruling 2026-05-21
* - Mint gated to WORM-verified work events via the treasury multisig
* - Burn mechanic for SEALFORGE tier upgrades
* - Governance weight: 1 FRG = 1 vote, capped at 1% of supply per address
* - Emergency pause controlled by the Architect role
*
* @custom:security-contact legal@snapkitty.io
* @custom:deployed-by SnapKitty Collective LLC β EIN 41-5105572
* @custom:holding-entity Bel Esprit D'Accord Trust β EIN 41-6630640
*/
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract FORGE is ERC20, ERC20Burnable, ERC20Pausable, AccessControl, ReentrancyGuard {
// =========================================================================
// ROLES
// =========================================================================
/// @notice ARCHITECT_ROLE: highest privilege β pause/unpause, role management
bytes32 public constant ARCHITECT_ROLE = keccak256("ARCHITECT_ROLE");
/// @notice MINTER_ROLE: granted exclusively to the SnapKitty Treasury multisig
/// Minting is triggered only after a WORM-chain entry is verified off-chain
/// and submitted by the treasury. No permissionless minting exists.
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
/// @notice BURNER_ROLE: granted to the SEALFORGE upgrade contract
/// Allows programmatic burns on tier-upgrade events
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
// =========================================================================
// SUPPLY CONSTANTS
// =========================================================================
/// @notice Absolute hard cap β 21,000,000 FRG (VAULT supply ruling 2026-05-21)
uint256 public constant MAX_SUPPLY = 21_000_000 * 10 ** 18;
/// @notice Governance vote cap per address: 1% of MAX_SUPPLY
uint256 public constant GOVERNANCE_CAP = MAX_SUPPLY / 100;
// =========================================================================
// STATE
// =========================================================================
/// @notice Total FRG burned to date (informational, tracked across all burn paths)
uint256 public totalBurned;
/// @notice WORM entry hash => minted flag β prevents replay of the same
/// WORM-sealed work event being used to mint twice
mapping(bytes32 => bool) public wormEntryMinted;
// =========================================================================
// EVENTS
// =========================================================================
/// @notice Emitted on every WORM-gated mint
/// @param recipient Address receiving minted tokens
/// @param amount Amount minted (18-decimal units)
/// @param wormEntryHash HMAC-SHA256 hash of the sealed WORM ledger entry
event WORMMint(
address indexed recipient,
uint256 amount,
bytes32 indexed wormEntryHash
);
/// @notice Emitted when SEALFORGE upgrade burns tokens
/// @param burner Address whose tokens were burned
/// @param amount Amount burned
/// @param tier SEALFORGE tier purchased ("basic", "pro", "architect")
event SealforgeBurn(
address indexed burner,
uint256 amount,
string tier
);
/// @notice Emitted on emergency pause
event EmergencyPause(address indexed architect, string reason);
/// @notice Emitted on unpause
event EmergencyUnpause(address indexed architect);
// =========================================================================
// CONSTRUCTOR
// =========================================================================
/**
* @notice Deploy MAGMA token.
* @param architect Address of the Architect (multisig recommended β Gnosis Safe)
* @param treasury Address of the SnapKitty Treasury multisig (receives MINTER_ROLE)
*/
constructor(address architect, address treasury) ERC20("FORGE", "FRG") {
require(architect != address(0), "FORGE: zero architect address");
require(treasury != address(0), "FORGE: zero treasury address");
// Grant roles
_grantRole(DEFAULT_ADMIN_ROLE, architect);
_grantRole(ARCHITECT_ROLE, architect);
_grantRole(MINTER_ROLE, treasury);
// No tokens minted at construction β all distribution is WORM-event driven
// Team / ecosystem allocations are minted by the treasury against
// pre-scheduled WORM entries at genesis.
}
// =========================================================================
// MINT β WORM-GATED
// =========================================================================
/**
* @notice Mint MGM tokens to `recipient` for a verified WORM work event.
* @dev Only callable by an address holding MINTER_ROLE (treasury multisig).
* The `wormEntryHash` is the HMAC-SHA256 of the sealed WORM ledger entry
* and acts as a nonce β each entry can only be redeemed once.
* The function reverts if minting would exceed MAX_SUPPLY.
*
* @param recipient Address to receive minted tokens
* @param amount Amount to mint (18-decimal units)
* @param wormEntryHash HMAC-SHA256 bytes32 digest of the WORM ledger entry
*/
function wormMint(
address recipient,
uint256 amount,
bytes32 wormEntryHash
)
external
nonReentrant
whenNotPaused
onlyRole(MINTER_ROLE)
{
require(recipient != address(0), "FORGE: mint to zero address");
require(amount > 0, "FORGE: zero mint amount");
require(!wormEntryMinted[wormEntryHash], "FORGE: WORM entry already redeemed");
require(totalSupply() + amount <= MAX_SUPPLY, "FORGE: hard cap exceeded");
wormEntryMinted[wormEntryHash] = true;
_mint(recipient, amount);
emit WORMMint(recipient, amount, wormEntryHash);
}
// =========================================================================
// BURN β SEALFORGE TIER UPGRADES
// =========================================================================
/**
* @notice Burn MGM from `burner` for a SEALFORGE tier upgrade.
* @dev Callable by BURNER_ROLE (SEALFORGE upgrade contract) OR by the
* token holder themselves (standard ERC20Burnable.burn also works).
* This route records the tier string in the event for analytics.
*
* @param burner Address whose tokens are burned
* @param amount Amount to burn (18-decimal units)
* @param tier Human-readable SEALFORGE tier label
*/
function sealforgeBurn(
address burner,
uint256 amount,
string calldata tier
)
external
nonReentrant
whenNotPaused
onlyRole(BURNER_ROLE)
{
require(burner != address(0), "FORGE: burn from zero address");
require(amount > 0, "FORGE: zero burn amount");
require(balanceOf(burner) >= amount, "FORGE: insufficient balance");
_burn(burner, amount);
emit SealforgeBurn(burner, amount, tier);
}
// =========================================================================
// GOVERNANCE WEIGHT
// =========================================================================
/**
* @notice Returns governance voting weight for `account`.
* @dev Weight equals token balance, capped at 1% of MAX_SUPPLY (GOVERNANCE_CAP).
* This prevents whale capture of governance decisions.
* Integrate this function in any on-chain governance module (Governor.sol).
*
* @param account Address to query
* @return weight Effective governance votes (capped)
*/
function governanceWeight(address account) external view returns (uint256 weight) {
uint256 balance = balanceOf(account);
weight = balance > GOVERNANCE_CAP ? GOVERNANCE_CAP : balance;
}
// =========================================================================
// EMERGENCY PAUSE β ARCHITECT ONLY
// =========================================================================
/**
* @notice Pause all token transfers, mints, and burns.
* @dev Only the Architect role may pause. Emits reason for transparency.
* @param reason Human-readable reason for the pause (stored in event log)
*/
function emergencyPause(string calldata reason)
external
onlyRole(ARCHITECT_ROLE)
{
_pause();
emit EmergencyPause(msg.sender, reason);
}
/**
* @notice Unpause the contract after an emergency.
* @dev Only the Architect role may unpause.
*/
function emergencyUnpause()
external
onlyRole(ARCHITECT_ROLE)
{
_unpause();
emit EmergencyUnpause(msg.sender);
}
// =========================================================================
// SUPPLY INSPECTION
// =========================================================================
/**
* @notice Returns the remaining mintable supply.
* @return Tokens that can still be minted before hitting MAX_SUPPLY
*/
function remainingMintable() external view returns (uint256) {
return MAX_SUPPLY - totalSupply();
}
// =========================================================================
// OVERRIDES REQUIRED BY SOLIDITY
// =========================================================================
/// @dev Resolves diamond inheritance conflict. Tracks totalBurned for all
/// burn paths β burns are _update calls where `to == address(0)`.
function _update(
address from,
address to,
uint256 value
)
internal
override(ERC20, ERC20Pausable)
{
if (to == address(0)) {
totalBurned += value;
}
super._update(from, to, value);
}
// =========================================================================
// INTERFACE SUPPORT
// =========================================================================
/**
* @dev Returns true for ERC-20, AccessControl, and ERC-165 interfaces.
*/
function supportsInterface(bytes4 interfaceId)
public
view
override(AccessControl)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
|