// SPDX-License-Identifier: MIT
pragma solidity 0.8.35;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {IPeepsLaunchToken} from "./interfaces/IPeepsLaunchToken.sol";
/// @title PeepsLaunchToken — PEEPS launch token (spec §6.1, §8.3; contracts.md §2.1)
/// @notice Plain OpenZeppelin v5 `ERC20` + `ERC20Permit`. 18 decimals. Ownerless, no
/// mint/burn/hooks/taxes/blacklist. The entire fixed supply is minted exactly once, in the
/// constructor, to the owning `PeepsBondingCurve`. The only surface beyond stock OZ is the
/// immutable `metadataHash` commitment (§8.3) and the `TOTAL_SUPPLY` getter.
/// @dev Implements the frozen {IPeepsLaunchToken}. Design decisions (recorded for the security gate):
///
/// 1. Ownerless by construction — the contract inherits NO access-control mixin (`Ownable`/
/// `AccessControl` are not imported). Chosen over "inherit Ownable and renounce" because
/// renouncement is a post-deploy runtime action that can be forgotten or front-run, whereas
/// "no owner code at all" is a compile-time guarantee with a smaller attack surface (spec
/// §6.1 "ownerless"; §6.6 "owner can never touch token economics"). Proven by the unit test
/// asserting the `owner()` selector is absent (reverts).
///
/// 2. No `burn()`/`_burn`, no `mint()` beyond the single constructor `_mint`. OZ ERC20 exposes
/// no public mint/burn; we add none. Graduation "dust burn" (spec §12.13) is a plain
/// `transfer` to `0x…dEaD` performed by the migrator — the token itself is burn-free, so
/// `totalSupply()` is constant `1e27` forever (contracts.md §2.1 invariant). Proven by unit
/// tests calling the `mint`/`burn` selectors and asserting they are not present.
///
/// 3. `metadataHash` is `immutable`, set once in the constructor. Chosen over a storage
/// variable to make tamper-impossibility a bytecode property (no SSTORE path exists),
/// matching the §8.3 integrity-commitment requirement.
///
/// 4. `ERC20Permit(name_)` fixes the EIP-712 domain `version` to `"1"` (OZ v5 default) and uses
/// the token name as the domain name — the standard EIP-2612 layout wagmi/viem expect, so
/// `sellWithPermit` (PeepsRouter, M1-9) and any wallet-side permit signing interoperate without a
/// bespoke domain. Verified by the permit unit test signing and spending an allowance.
///
/// Emits no events of its own; the constructor `_mint` emits the standard
/// `Transfer(address(0) → curve)`.
contract PeepsLaunchToken is ERC20, ERC20Permit, IPeepsLaunchToken {
/// @inheritdoc IPeepsLaunchToken
/// @dev `constant` (never an SSTORE target) — the fixed 1,000,000,000e18 supply (spec §6.4,
/// contracts.md §2.1). `CURVE_SUPPLY + LP_TOKEN_TRANCHE == TOTAL_SUPPLY` is asserted by the
/// factory/deploy script, not here (this contract owns only the supply total).
uint256 public constant override TOTAL_SUPPLY = 1_000_000_000e18;
/// @inheritdoc IPeepsLaunchToken
/// @dev `immutable`: no code path can change it after construction (§8.3 commitment).
bytes32 public immutable override metadataHash;
/// @param name_ Token name (length validated upstream by the factory, contracts.md §2.2).
/// @param symbol_ Ticker (length validated upstream by the factory).
/// @param metadataHash_ keccak256 of the canonicalized metadata JSON (§8.3). Non-zero enforced by
/// the factory (`ZeroMetadataHash`); stored verbatim, immutably.
/// @param curve_ The owning PeepsBondingCurve; receives the full `TOTAL_SUPPLY` at birth.
/// @dev Input validation (name/symbol length, non-zero hash, non-zero curve) lives in the factory
/// before deploy (contracts.md §2.1 "Errors: none of its own"), keeping this contract a
/// minimal, audit-trivial ERC20. The mint to `curve_` is the sole supply-creating action.
constructor(string memory name_, string memory symbol_, bytes32 metadataHash_, address curve_)
ERC20(name_, symbol_)
ERC20Permit(name_)
{
metadataHash = metadataHash_;
_mint(curve_, TOTAL_SUPPLY);
}
/// @inheritdoc IERC20Permit
/// @dev Pure disambiguation override: `nonces(address)` is inherited both from `ERC20Permit`
/// (concrete, backed by OZ `Nonces`) and from `IERC20Permit` via {IPeepsLaunchToken}. Solidity
/// requires an explicit override listing both bases; behaviour is unchanged (`super`
/// forwards to the OZ implementation). No new storage or logic is introduced.
function nonces(address owner) public view virtual override(ERC20Permit, IERC20Permit) returns (uint256) {
return super.nonces(owner);
}
}[
{
"type": "constructor",
"inputs": [
{
"name": "name_",
"type": "string",
"internalType": "string"
},
{
"name": "symbol_",
"type": "string",
"internalType": "string"
},
{
"name": "metadataHash_",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "curve_",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "nonpayable"
},
{
"name": "ECDSAInvalidSignature",
"type": "error",
"inputs": []
},
{
"name": "ECDSAInvalidSignatureLength",
"type": "error",
"inputs": [
{
"name": "length",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "ECDSAInvalidSignatureS",
"type": "error",
"inputs": [
{
"name": "s",
"type": "bytes32",
"internalType": "bytes32"
}
]
},
{
"name": "ERC20InsufficientAllowance",
"type": "error",
"inputs": [
{
"name": "spender",
"type": "address",
"internalType": "address"
},
{
"name": "allowance",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "needed",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "ERC20InsufficientBalance",
"type": "error",
"inputs": [
{
"name": "sender",
"type": "address",
"internalType": "address"
},
{
"name": "balance",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "needed",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "ERC20InvalidApprover",
"type": "error",
"inputs": [
{
"name": "approver",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "ERC20InvalidReceiver",
"type": "error",
"inputs": [
{
"name": "receiver",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "ERC20InvalidSender",
"type": "error",
"inputs": [
{
"name": "sender",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "ERC20InvalidSpender",
"type": "error",
"inputs": [
{
"name": "spender",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "ERC2612ExpiredSignature",
"type": "error",
"inputs": [
{
"name": "deadline",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "ERC2612InvalidSigner",
"type": "error",
"inputs": [
{
"name": "signer",
"type": "address",
"internalType": "address"
},
{
"name": "owner",
"type": "address",
"internalType": "address"
}
]
},
{
"name": "InvalidAccountNonce",
"type": "error",
"inputs": [
{
"name": "account",
"type": "address",
"internalType": "address"
},
{
"name": "currentNonce",
"type": "uint256",
"internalType": "uint256"
}
]
},
{
"name": "InvalidShortString",
"type": "error",
"inputs": []
},
{
"name": "StringTooLong",
"type": "error",
"inputs": [
{
"name": "str",
"type": "string",
"internalType": "string"
}
]
},
{
"name": "Approval",
"type": "event",
"inputs": [
{
"name": "owner",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "spender",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "value",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "EIP712DomainChanged",
"type": "event",
"inputs": [],
"anonymous": false
},
{
"name": "Transfer",
"type": "event",
"inputs": [
{
"name": "from",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "to",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "value",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "DOMAIN_SEPARATOR",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"name": "TOTAL_SUPPLY",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "allowance",
"type": "function",
"inputs": [
{
"name": "owner",
"type": "address",
"internalType": "address"
},
{
"name": "spender",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "approve",
"type": "function",
"inputs": [
{
"name": "spender",
"type": "address",
"internalType": "address"
},
{
"name": "value",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "balanceOf",
"type": "function",
"inputs": [
{
"name": "account",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "decimals",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint8",
"internalType": "uint8"
}
],
"stateMutability": "view"
},
{
"name": "eip712Domain",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "fields",
"type": "bytes1",
"internalType": "bytes1"
},
{
"name": "name",
"type": "string",
"internalType": "string"
},
{
"name": "version",
"type": "string",
"internalType": "string"
},
{
"name": "chainId",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "verifyingContract",
"type": "address",
"internalType": "address"
},
{
"name": "salt",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "extensions",
"type": "uint256[]",
"internalType": "uint256[]"
}
],
"stateMutability": "view"
},
{
"name": "metadataHash",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "bytes32",
"internalType": "bytes32"
}
],
"stateMutability": "view"
},
{
"name": "name",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "string",
"internalType": "string"
}
],
"stateMutability": "view"
},
{
"name": "nonces",
"type": "function",
"inputs": [
{
"name": "owner",
"type": "address",
"internalType": "address"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "permit",
"type": "function",
"inputs": [
{
"name": "owner",
"type": "address",
"internalType": "address"
},
{
"name": "spender",
"type": "address",
"internalType": "address"
},
{
"name": "value",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "deadline",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "v",
"type": "uint8",
"internalType": "uint8"
},
{
"name": "r",
"type": "bytes32",
"internalType": "bytes32"
},
{
"name": "s",
"type": "bytes32",
"internalType": "bytes32"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "symbol",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "string",
"internalType": "string"
}
],
"stateMutability": "view"
},
{
"name": "totalSupply",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"name": "transfer",
"type": "function",
"inputs": [
{
"name": "to",
"type": "address",
"internalType": "address"
},
{
"name": "value",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
},
{
"name": "transferFrom",
"type": "function",
"inputs": [
{
"name": "from",
"type": "address",
"internalType": "address"
},
{
"name": "to",
"type": "address",
"internalType": "address"
},
{
"name": "value",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "bool",
"internalType": "bool"
}
],
"stateMutability": "nonpayable"
}
]0x6080806040526004361015610012575f80fd5b5f3560e01c90816306fdde031461069357508063095ea7b31461066d57806318160ddd1461065057806323b872dd14610571578063313ce567146105565780633644e5151461053457806370a08231146104fd5780637ecebe00146104c557806384b0196e146103cd578063902d55a5146103a757806395d89b41146102c5578063a9059cbb14610294578063c5a1d7f01461025a578063d505accf146101155763dd62ed3e146100c1575f80fd5b34610111576040366003190112610111576100da610759565b6100e261076f565b6001600160a01b039182165f908152600160209081526040808320949093168252928352819020549051908152f35b5f80fd5b346101115760e03660031901126101115761012e610759565b61013661076f565b604435906064359260843560ff81168103610111578442116102475761020a6102139160018060a01b03841696875f52600760205260405f20908154916001830190556040519060208201927f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c984528a604084015260018060a01b038916606084015289608084015260a083015260c082015260c081526101d860e08261083e565b5190206101e3610931565b906040519161190160f01b83526002830152602282015260c43591604260a4359220610b4a565b90929192610bd7565b6001600160a01b0316848103610230575061022e9350610a4d565b005b84906325c0072360e11b5f5260045260245260445ffd5b8463313c898160e11b5f5260045260245ffd5b34610111575f3660031901126101115760206040517fad7cd302e0f2416e8b2716c95bb8ca108f4d7b24bbb96c825e9f0b3f7c4402778152f35b34610111576040366003190112610111576102ba6102b0610759565b6024359033610874565b602060405160018152f35b34610111575f366003190112610111576040515f6004546102e581610785565b80845290600181169081156103835750600114610325575b6103218361030d8185038261083e565b604051918291602083526020830190610735565b0390f35b60045f9081527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b939250905b8082106103695750909150810160200161030d6102fd565b919260018160209254838588010152019101909291610351565b60ff191660208086019190915291151560051b8401909101915061030d90506102fd565b34610111575f3660031901126101115760206040516b033b2e3c9fd0803ce80000008152f35b34610111575f366003190112610111576104696104097f66756e206b696c6c65720000000000000000000000000000000000000000000a610ab0565b6104327f3100000000000000000000000000000000000000000000000000000000000001610b13565b602061047760405192610445838561083e565b5f84525f368137604051958695600f60f81b875260e08588015260e0870190610735565b908582036040870152610735565b4660608501523060808501525f60a085015283810360c08501528180845192838152019301915f5b8281106104ae57505050500390f35b83518552869550938101939281019260010161049f565b34610111576020366003190112610111576001600160a01b036104e6610759565b165f526007602052602060405f2054604051908152f35b34610111576020366003190112610111576001600160a01b0361051e610759565b165f525f602052602060405f2054604051908152f35b34610111575f36600319011261011157602061054e610931565b604051908152f35b34610111575f36600319011261011157602060405160128152f35b346101115760603660031901126101115761058a610759565b61059261076f565b6001600160a01b0382165f818152600160209081526040808320338452909152902054909260443592915f1981106105d0575b506102ba9350610874565b83811061063557841561062257331561060f576102ba945f52600160205260405f2060018060a01b0333165f526020528360405f2091039055846105c5565b634a1406b160e11b5f525f60045260245ffd5b63e602df0560e01b5f525f60045260245ffd5b8390637dc7a0d960e11b5f523360045260245260445260645ffd5b34610111575f366003190112610111576020600254604051908152f35b34610111576040366003190112610111576102ba610689610759565b6024359033610a4d565b34610111575f366003190112610111575f6003546106b081610785565b808452906001811690811561038357506001146106d7576103218361030d8185038261083e565b60035f9081527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b939250905b80821061071b5750909150810160200161030d6102fd565b919260018160209254838588010152019101909291610703565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b038216820361011157565b602435906001600160a01b038216820361011157565b90600182811c921680156107b3575b602083101461079f57565b634e487b7160e01b5f52602260045260245ffd5b91607f1691610794565b5f92918154916107cc83610785565b808352926001811690811561082157506001146107e857505050565b5f9081526020812093945091925b838310610807575060209250010190565b6001816020929493945483858701015201910191906107f6565b915050602093945060ff929192191683830152151560051b010190565b90601f8019910116810190811067ffffffffffffffff82111761086057604052565b634e487b7160e01b5f52604160045260245ffd5b6001600160a01b031690811561091e576001600160a01b031691821561090b57815f525f60205260405f20548181106108f257817fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92602092855f525f84520360405f2055845f525f825260405f20818154019055604051908152a3565b8263391434e360e21b5f5260045260245260445260645ffd5b63ec442f0560e01b5f525f60045260245ffd5b634b637e8f60e11b5f525f60045260245ffd5b307f00000000000000000000000098a112e5a1fcbf6a6f70d376e0d50cd88f5b4d7f6001600160a01b03161480610a24575b1561098c577f76282d8f9c1366a905c73082f593a5f6ba01d87945ef8db3fd57ccedfca9a88e90565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527fbbc0ca2b472bfedca15008dacbb8f6a3a2039b581dcc4d88a0ce05c226c8ef6e60408201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260a08152610a1e60c08261083e565b51902090565b507f00000000000000000000000000000000000000000000000000000000000012374614610963565b6001600160a01b0316908115610622576001600160a01b031691821561060f5760207f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591835f526001825260405f20855f5282528060405f2055604051908152a3565b60ff8114610af65760ff811690601f8211610ae75760405191610ad460408461083e565b6020808452838101919036833783525290565b632cd44ac360e21b5f5260045ffd5b50604051610b1081610b098160056107bd565b038261083e565b90565b60ff8114610b375760ff811690601f8211610ae75760405191610ad460408461083e565b50604051610b1081610b098160066107bd565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411610bcc579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610bc1575f516001600160a01b03811615610bb757905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f9160039190565b6004811015610c375780610be9575050565b60018103610c005763f645eedf60e01b5f5260045ffd5b60028103610c1b575063fce698f760e01b5f5260045260245ffd5b600314610c255750565b6335e2f38360e21b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffdfea2646970667358221220024f271aa213e8ce80135b450150ac00d9a711b1b0c77dd35e158034e8a5ccfa64736f6c63430008230033
| Token | Symbol | Balance | Price | Value |
|---|---|---|---|---|
| no token holdings | ||||
| Transaction Hash | Method | Block | Age | From | To | Amount | Token | ||
|---|---|---|---|---|---|---|---|---|---|
| no token transfers for this address yet | |||||||||
| Type | Age | Block | Details |
|---|---|---|---|
| no cross-chain L1↔L2 transactions for this address | |||
| Transaction Hash | Method | Block | Age | From | To | Type | Item | ||
|---|---|---|---|---|---|---|---|---|---|
| no NFT transfers for this address yet | |||||||||
| Transaction Hash | Method ? | Block | Age | From | To | Amount | Txn Fee ? | ||
|---|---|---|---|---|---|---|---|---|---|
| 0xe71ef9…0cab73 | Approve | 21,960,957 | 20 days agoTue, 28 Jul 2026 22:44:57 UTC | 0x0229…510e | IN | fun killer | $0.000 ETH | 0.00000129 |
| Txn Hash | Age | Event | Topics / Data |
|---|---|---|---|
| 0x1d57ba…2ebf0d | 19 days agoWed, 29 Jul 2026 11:24:52 UTC | Transfer | [0] 0x000000000000…78bf510e [1] 0x000000000000…58884116 data: 0x000000000000000000…97b414b0 |
| 0x364bdf…cbc0b2 | 19 days agoWed, 29 Jul 2026 10:01:36 UTC | Transfer | [0] 0x000000000000…58884116 [1] 0x000000000000…5c037e71 data: 0x000000000000000000…d17f2895 |
| 0x550299…c48274 | 20 days agoTue, 28 Jul 2026 23:17:00 UTC | Transfer | [0] 0x000000000000…58884116 [1] 0x000000000000…78bf510e data: 0x000000000000000000…97b414b0 |
| 0x56b35b…cca12c | 20 days agoTue, 28 Jul 2026 22:45:26 UTC | Transfer | [0] 0x000000000000…78bf510e [1] 0x000000000000…58884116 data: 0x000000000000000000…f9b97055 |
| 0xe71ef9…0cab73 | 20 days agoTue, 28 Jul 2026 22:44:57 UTC | Approval | [0] 0x000000000000…78bf510e [1] 0x000000000000…e5d1afc6 data: 0xffffffffffffffffff…ffffffff |
| 0xb7517b…eaf5f8 | 20 days agoTue, 28 Jul 2026 20:29:23 UTC | Transfer | [0] 0x000000000000…58884116 [1] 0x000000000000…78bf510e data: 0x000000000000000000…f9b97055 |
| 0xb7517b…eaf5f8 | 20 days agoTue, 28 Jul 2026 20:29:23 UTC | Transfer | [0] 0x000000000000…00000000 [1] 0x000000000000…58884116 data: 0x000000000000000000…e8000000 |
| Block | Age | Parent Transaction Hash | Type | Method | From | To | Value | |
|---|---|---|---|---|---|---|---|---|
| 21,879,739 | 20 days agoTue, 28 Jul 2026 20:29:23 UTC | 0xb7517b…eaf5f8 | CREATE | createToken | 0x138c…b9dd | IN | 0x98a1…4d7f | 0 ETH |