# Etherpage > Simple onchain pages on OP Mainnet (chain id 10). Each page is an ERC-721 token > with a short name such as `alice`, or `blog.alice` for a subpage, that holds > addresses for any chain, a profile (text records) and a website (contenthash), > and can be an address's display page. The contracts (PageNFT, SubpageRegistrar, > PageCard) have no owner, no admin key, no proxy and no upgrade path. Every yearly > fee goes into a deposit that stays with the page and goes back to the page's > payee when the page ends; nobody collects fees. Verify the contract address AND > chainid == 10 before signing; the same address on another chain is not these > contracts. Deployment (OP Mainnet, chain id 10): - PageNFT: to be published on deploy day - PageCard: to be published on deploy day - SubpageRegistrar: to be published on deploy day Check that PageNFT.ROOT() returns 0x62460c06d95b73747abf6ea4f28aefd78b4ad2abee112e7cf15fe6a924cca3c6, that PageNFT.card() returns the PageCard address and that SubpageRegistrar.registry() returns the PageNFT address. Explorer: https://optimistic.etherscan.io. Source: https://github.com/daamin/etherpage (contracts/, Apache-2.0). Critical rules for programmatic use (each prevents a real failure): - Names have NO suffix. A name is `alice` or, for a subpage, `blog.alice`; one level only. Never append or strip `.eth`, `.ether` or anything else: a dotted name is always a subpage, so `alice.eth` is the subpage `alice` of the page `eth`, which anyone may register. - Label rule (PageNFT.normalize): 1 to 63 bytes; A-Z are lowercased; only a-z, 0-9 and `-` are kept, every other byte (including all bytes >= 0x80) reverts InvalidLabel, a wrong length reverts InvalidLength. No hyphen first or last; with 4+ characters the 3rd and 4th may not both be `-`; `0x` followed by exactly 40 hex characters is refused. No Unicode, no ENSIP-15. - Ids: ROOT = keccak256("etherpage.root.v1") = 0x62460c06d95b73747abf6ea4f28aefd78b4ad2abee112e7cf15fe6a924cca3c6. Page id = uint256(keccak256(abi.encodePacked(ROOT, keccak256(label)))). Subpage id = uint256(keccak256(abi.encodePacked(bytes32(parentId), keccak256(label)))). computeId("alice") = 0xbd0888be4fc21a23d82616269103328d0cc8ab2697e8da7ad4c2d0d47a6cfa22. The contract's computeId only lowercases A-Z; validate labels with normalize. The resolver node is bytes32(tokenId). - Registration is commit-reveal: commit(makeCommitment(label, owner, referrerId, secret)), wait until a block timestamp >= commit block timestamp + 20, then reveal(label, owner, referrerId, secret) with value quote(label).total before commit + 86400 (both ends inclusive). makeCommitment = keccak256(abi.encode( bytes normalizedLabel, owner, referrerId, secret)). The page mints to `owner`; ANYONE may send and pay the reveal; excess value is refunded to msg.sender. Pass the SAME owner, referrerId and secret to makeCommitment and reveal. - The secret must be 32 cryptographically random bytes, unique per commitment, never derived from the label/time/address. Persist it (with owner and referrerId) to durable storage BEFORE broadcasting commit. - A reverted reveal leaves the commitment live: retry the SAME reveal. Sending the identical commitment again within a day reverts AlreadyCommitted. Before any retry, check ownerOf: someone may have sent your reveal for you. - Trials: revealTrial(label, owner, referrerId, secret) registers for 14 days at getFee(length)/10, with its own commitment keccak256(abi.encode(bytes normalizedLabel, owner, referrerId, secret, true)) (no view; hash locally after calling makeCommitment to validate). The trial money is held in trialPaymentOf(id) and always credited back to its payer (on renew, on release(id) after the end, or on the next registration). A trial has no grace period, cannot have subpages and can move only to the dead address. Refused (TrialNotAllowed) while a paid page is live, in grace or in its premium period, and until 14 days after the page's last registration ended. - Fees per year by label length: 0.01 ETH (1-2), 0.001 ETH (3-4), 0.0001 ETH (5+). Every yearly fee (registration and renewals) is added to depositOf(id). - Grace: 90 days after expiry, only for a page with a deposit. During grace the page answers nothing, cannot move or change, can be renewed by anyone, and cannot be registered by anyone else. - Premium: after grace, 10 ETH falling linearly to 0 over 21 days. A registration in that period also pays `carried` (the old deposit) to the last payee and takes the deposit over. Of the premium, 75% goes to the last payee and 25% to the payee of the referrer named at the last registration (the dead address when the referrer is 0, unknown or a subpage). A page in grace quotes only the fee, then jumps to fee + deposit + ~10 ETH at the first second after grace; read quote(label) as of the reveal block. - Deposit return: after expiry + 111 days anyone may call release(id), which credits the deposit to payee(id); the next registration does it by itself. payee = the main address the current holder saved, else the holder. Credits sit in owed(account); anyone may call withdraw(account), which pays that account. - Referrers: referrerId is another top-level page's token id or 0, part of the commitment, never the page itself (SelfReferral). It earns ONLY the 25% premium share described above; nothing at registration or renewal. - renew(tokenId) is open to anyone for a top-level page until its grace ends. It adds 365 days to the current expiry for getFee(length), refunds excess to msg.sender, and reverts RenewalTooFar past 3650 days ahead. PageNFT.multicall is NOT payable, so it cannot carry renew; send one transaction per year or a wallet batch with value. Renewing a trial makes it paid and refunds the trial money. - Pages freeze the moment they expire: resolve() returns address(0), addr/text/ contenthash return empty, transfers and record writes revert Expired. Check liveness with status(tokenId): 0 Active, 1 Grace, 2 Expired, 3 Invalid, 4 Trial (reverts for an unknown id). isExpired/inGracePeriod are always false for subpages. - Token ids are REUSED: a new registration after a lapse burns and re-mints the same tokenId, raises epoch and recordVersion and empties every address, text and website. Cache records(tokenId).epoch and treat a change as a different page. ERC-6551 token-bound accounts follow the token: a lapsed page's TBA (and its assets) belongs to the next registrant. - Receiver rule: a page or subpage minted to a contract must be accepted by onERC721Received unless the contract is the caller, or an account with no code or an EIP-7702 delegation. In a SubpageRegistrar sale the caller is the registrar, so a contract receiver must always accept. A contract that sends a reveal must accept ETH refunds or pay the exact price. - Records: setAddr(id, a) sets the main address = the address on OP Mainnet (coin type 0x8000000a) and the payee; zero points back at the holder. setAddrForCoin(id, coinType, bytes) sets other chains: 60 = Ethereum, 0x80000000 | chainId = another EVM chain, 0x80000000 = default for every EVM chain without its own entry. EVM values are 20 bytes or empty. addr(id, 60) does NOT fall back to the main address. addr(bytes32 node) returns the OP main address. Every saved address counts only while the account that saved it holds the page. clearRecords(id) empties everything. multicall(bytes[]) batches record writes as the same caller (all or nothing, not payable). - Profile rule: text and contenthash read empty unless the page is active AND profileWriter(id) holds it. After a transfer the profile is hidden until the new holder calls keepProfile(id); any write by the new holder starts an empty profile. - Display page: setPrimaryPage(id) (0 clears) by the holder or the main address. reverseResolve(address) returns the name only while the page is active, held by the same account as when it was chosen, and resolve(id) == address. A transfer clears the sender's choice. primaryPage(address) is the raw, possibly stale value. - Subpages: registerSubpage(label, parentId) / registerSubpageFor(label, parentId, to), free, one transaction, by the parent's holder or its subpage minter (setSubpageMinter). The parent must be a live, paid top-level page (a trial parent reverts Unpaid). A subpage follows its parent's expiry and grace; it is Invalid when its parentEpoch differs from the parent's epoch or when the dead address holds it. The parent's holder can always replace a subpage. Delete a subpage by sending it to 0x000000000000000000000000000000000000dEaD. - SubpageRegistrar sales: the holder calls configure(parentId, payout, feeToken, price, enabled, gateToken, minGateBalance, commitReveal) and PageNFT.setSubpageMinter(parentId, registrar). Buyers call registerSubpage(parentId, label, maxPrice, expectedFeeToken) or registerSubpageFor(parentId, label, to, maxPrice, expectedFeeToken), or, when commitReveal is on, commitSubpage(makeSubpageCommitment(parentId, label, to, secret)) then revealSubpage/revealSubpageFor 20 s to 1 day later. config(parentId) returns (controller, enabled, commitReveal, feeToken, price, gateToken, minGateBalance, payout, parentEpoch). For a token price approve EXACTLY the price. ETH proceeds: proceedsOf(payout), claimProceeds(to) or claimProceedsFor(account). Never send a page to the registrar. - Availability and quote lookups leak the label to whoever runs the RPC; use a node you trust for valuable names. The commit is a salted hash and leaks nothing. - Avatar NFTs (`avatar` = eip155:1/erc721:/) live on Ethereum: read the token's holder on Ethereum and the page's ownerOf on OP Mainnet, and require the same address. ## Docs - [Getting started](https://etherpage.com/docs/getting-started): what a page is and how to register one. - [Registration](https://etherpage.com/docs/registration): commit and reveal, the name rules, trials, renewal, resuming a pending registration. - [Pricing](https://etherpage.com/docs/pricing): fees, the refundable deposit, trials, grace, the premium period, referrers. - [Records & resolution](https://etherpage.com/docs/records): main address, other chains, text records, the profile rule, avatars, display page, website. - [Subpages](https://etherpage.com/docs/subpages): one level, holder or minter, deleting, epochs, status. - [Selling subpages](https://etherpage.com/docs/selling-subpages): the SubpageRegistrar, token gating, commit and reveal, proceeds. - [IPFS websites](https://etherpage.com/docs/ipfs-websites): contenthash and the gateway at https://.ipfs.cc. - [FAQ](https://etherpage.com/docs/faq): common questions. - [Integrate identity](https://etherpage.com/docs/integrate-identity): resolving, profiles, avatar checks, gating on pages and subpages. - [AI agents & integrators](https://etherpage.com/docs/agents): the page this file summarizes. - [Contracts reference](https://etherpage.com/docs/contracts): addresses, constants, every function, event and error.