Version: 0.0.17
Date: 07/10/2025
SPDX-License-Identifier: BSL 1.1 - Peng Protocol 2025
Solidity Version: ^0.8.2
MailNames
is a decentralized domain name system with ERC721 compatibility, enabling free name minting with a 1-year allowance.
Names can only be renewed using "check-ins" during a 7-day grace period after the name's allowance ends.
-
Check-ins: A check-in is a 10 year lockup of $MAIL (handled by
MailLocker
) required to renew a name. Check-ins are queued and can take a minimum of 10 minutes or maximum of 2 weeks, the time and amount required depend on the number of active check-ins waiting in queue. -
Retention: Renewal is not strictly required to retain a name, however, after the primary grace-period; if any active ETH bids exist for the name, the system will settle the name to the highest ETH bidder after an additional 3-week secondary grace period. In this way only high value names require continuous renewal.
-
Wait Dynamics: Because the check-in wait time is sandwiched within a one month grace period; for valuable names, this forces the user to initiate renewal at any point during the first two weeks of their grace, absorbing any lock-up amount required to retain the name, or losing the name to the highest bidder.
Bids (handled by MailMarket
) are limited to a maximum of (100) bids per token, bidders are required to hold enough $MAIL to cover the cost of renewing the name. Each new bid in a full array pushes out the lowest bid and ensures that the highest bid has enough $MAIL to cover renewal.
Users can attach up to (5) "records" to their name, and create subnames in kind. Subnames are transferred with their parent name. Names are limited to (24) characters and records to (1024).
MailNames is the primary name system for Chainmail (link unavailable).
- NameRecord: Stores domain details.
name
: String (no spaces, <=24 chars).nameHash
: keccak256 hash of name.tokenId
: ERC721 token ID.allowanceEnd
: Timestamp when allowance expires.graceEnd
: Grace period end (reset on post-grace settle).customRecords
: Array of 5CustomRecord
structs.
- SubnameRecord: Stores subname details.
parentHash
: Parent name's hash.subname
: Subname string (<=24 chars).subnameHash
: keccak256 hash of subname.customRecords
: Array of 5CustomRecord
structs.
- PendingCheckin: Stores queued checkin details.
nameHash
: Name to check in.user
: Owner address.queuedTime
: Block timestamp at queue.waitDuration
: Wait (10min + 6h*queueLen, cap 2w).
- PendingSettlement: Stores queued settlement details.
nameHash
: Name to settle.bidIndex
: Bid index inMailMarket
.isETH
: True if ETH bid.token
: Token address (if ERC20).queueTime
: Block timestamp at queue.
- CustomRecord: Stores metadata (strings <=1024 chars).
text
: General text (e.g., description).resolver
: Resolver info.contentHash
: Content hash (e.g., IPFS).ttl
: Time-to-live.targetAddress
: Associated address.
- SettlementData: Stores settlement data.
nameHash
: Name hash.tokenId
: ERC721 token ID.oldOwner
: Previous owner.newOwner
: New owner.amount
: Bid amount.postGrace
: True if post-grace period.
nameRecords
: Mapsuint256
(nameHash) toNameRecord
.subnameRecords
: Mapsuint256
(parentHash) toSubnameRecord[]
.allNameHashes
: Array ofuint256
(minted nameHashes).totalNames
: uint256 (starts 0, ++ per mint).tokenIdToNameHash
: Mapsuint256
(tokenId) touint256
(nameHash).nameHashToTokenId
: Mapsuint256
(nameHash) touint256
(tokenId)._balances
: Mapsaddress
touint256
(owner => token count).ownerOf
: Mapsuint256
(tokenId) toaddress
(owner).getApproved
: Mapsuint256
(tokenId) toaddress
(spender).isApprovedForAll
: Mapsaddress
tomapping(address => bool)
(owner => operator => approved).pendingCheckins
: Array ofPendingCheckin
.pendingSettlements
: Array ofPendingSettlement
.nextProcessIndex
: uint256 (processed queue position).owner
: address (contract owner).mailToken
: address (ERC20 token).mailLocker
: address (MailLocker contract).mailMarket
: address (MailMarket contract).ALLOWANCE_PERIOD
: 365 days.GRACE_PERIOD
: 7 days.MAX_NAME_LENGTH
: 24.MAX_STRING_LENGTH
: 1024.
- Purpose: Mints ERC721 name (free, 1-year allowance).
- Checks:
_validateName
(spaces/length), name not minted. - Internal Calls:
_stringToHash
: Generates nameHash._validateName
: Validates format/length.
- Effects: Sets
NameRecord
, mapstokenIdToNameHash
/nameHashToTokenId
/ownerOf
/_balances
, pushes toallNameHashes
, emitsNameMinted
/Transfer
.
- Purpose: Queues checkin post-expiration (locks token in
MailLocker
for 10y). - Checks: Caller=owner, expired, transfer succeeds.
- Internal Calls:
_processQueueRequirements
: Locks tokens viaMailLocker.depositLock
._calculateMinRequired
: 1 * 2^queueLen wei._calculateWaitDuration
: 10min + 6h*queueLen, cap 2w.this.advance
: Processes if ready.
- Effects: Pushes
PendingCheckin
, emitsQueueCheckInQueued
.
- Purpose: Processes one ready checkin.
- Checks: Time/user eligibility.
- Internal Calls:
_processNextCheckin
: UpdatesallowanceEnd
, clearspendingSettlements
, emitsNameCheckedIn
/CheckInProcessed
.
- Effects: Increments
nextProcessIndex
.
- Purpose: Transfers name (inherits allowance).
- Checks: Name exists, auth via
_transfer
. - Internal Calls:
_transfer
: UpdatesownerOf
/_balances
, emitsTransfer
.
- Effects: ERC721 transfer.
- Purpose: ERC721 transfer.
- Checks:
_from==ownerOf
, valid tokenId. - Internal Calls:
_transfer
: UpdatesownerOf
/_balances
, emitsTransfer
.
- Effects: Transfers name.
- Purpose: Safe ERC721 transfer.
- Checks:
_from==ownerOf
, valid tokenId, receiver hook. - Internal Calls:
_transfer
: UpdatesownerOf
/_balances
._checkOnERC721Received
: Invokes receiver hook._isContract
: Checks if_to
is contract.
- Effects: Transfers with hook verification.
- Purpose: Approves spender.
- Checks: Caller=owner/operator, valid tokenId.
- Effects: Sets
getApproved
, emitsApproval
.
- Purpose: Batch operator approval.
- Effects: Updates
isApprovedForAll
, emitsApprovalForAll
.
- Purpose: Returns name count.
- Returns: uint256 (
_balances
).
- Purpose: Sets name record (0-4).
- Checks: Caller=owner, valid index/tokenId,
_validateRecord
. - Internal Calls:
_validateRecord
: String length checks.
- Effects: Updates
customRecords
, emitsRecordsUpdated
.
setSubnameRecord(uint256 _parentHash, uint256 _subnameIndex, uint256 _recordIndex, CustomRecord _record)
- Purpose: Sets subname record.
- Checks: Caller=parent owner, valid indices,
_validateRecord
. - Internal Calls:
_validateRecord
: String limits.
- Effects: Updates
subnameRecords
, emitsRecordsUpdated
.
- Purpose: Mints subname under parent.
- Checks: Valid subname, caller=parent owner.
- Internal Calls:
_stringToHash
: Hashes parent/subname._validateName
: Subname format.
- Effects: Pushes
SubnameRecord
, emitsSubnameMinted
.
- Purpose: Sets ERC20 token.
- Effects: Updates
mailToken
.
- Purpose: Sets
MailLocker
address. - Effects: Updates
mailLocker
.
- Purpose: Sets
MailMarket
address. - Effects: Updates
mailMarket
.
- Purpose: Transfers ownership.
- Checks:
_newOwner !=0
. - Effects: Sets
owner
, emitsOwnershipTransferred
.
- Purpose: Allows owner to accept bid via
MailMarket
. - Checks: Caller=owner, within allowance, valid tokenId.
- Internal Calls:
_settleBid
: Queues or settles viaMailMarket.settleBid
.
- Effects: Triggers settlement, emits
BidSettled
viaMailMarket
.
- Purpose: Processes queued settlement after 3 weeks.
- Checks: Valid index, time elapsed.
- Internal Calls:
MailMarket.settleBid
: Executes settlement.
- Effects: Resets
graceEnd
, swaps/pops queue, emitsSettlementProcessed
.
- Purpose: Resolves name to owner.
- Internal Calls:
_stringToHash
: Name hash.
- Returns: address (
ownerOf
).
- Purpose: Resolves name to NameRecord.
- Internal Calls:
_stringToHash
: Name hash.
- Returns:
NameRecord
(single record).
- Purpose: Returns PendingSettlement for given index.
- Checks: Valid index.
- Returns:
PendingSettlement
(single struct).
- Purpose: Paginated pending settlements for a name.
- Internal Calls:
_stringToHash
: Name hash.
- Returns:
PendingSettlement[]
.
- Purpose: Resolves tokenId to name string.
- Checks: Token minted.
- Returns: string (
nameRecords[nameHash].name
).
- Purpose: Finds subname index.
- Internal Calls:
_stringToHash
: Hashes.
- Returns: (index, found).
- Purpose: Retrieves subname records.
- Internal Calls:
_stringToHash
,this.getSubnameID
.
- Returns:
CustomRecord[5]
.
- Purpose: Paginated subname strings.
- Internal Calls:
_stringToHash
.
- Returns:
string[]
.
- _stringToHash(string _str): Generates keccak256 hash for storage/retrieval.
- _validateName(string _name): Ensures length <=24, no spaces, >0.
- _validateRecord(CustomRecord _record): Checks string lengths <=1024.
- _transfer(uint256 _tokenId, address _to): Updates
ownerOf
/_balances
, emitsTransfer
. - _isContract(address _account): Assembly check for contract detection.
- _checkOnERC721Received(address _to, address _from, uint256 _tokenId, bytes _data): Invokes receiver hook with try/catch.
- _calculateMinRequired(uint256 _queueLen): 1 * 2^_queueLen wei for checkin cost.
- _calculateWaitDuration(uint256 _queueLen): 10min + 6h*_queueLen, cap 2w.
- _processNextCheckin(): Updates
allowanceEnd
, clearspendingSettlements
, emitsNameCheckedIn
/CheckInProcessed
. - _processQueueRequirements(): Locks tokens via
MailLocker.depositLock
. - _settleBid(uint256 _nameHash, uint256 _bidIndex, bool _isETH, address _token): Queues settlement if post-grace (3w delay), immediate otherwise; handles checkin queueing.
- _clearPendingSettlements(uint256 _nameHash): Removes all pending settlements for a name on renewal.
- ERC721 Integration:
tokenId
enables standard transfers;_balances
O(1);nameHashToTokenId
speeds auth. - Queue Mechanics: Locks via
MailLocker
;advance
O(1); transfers don’t cancel queue; post-grace settlements queued for 3 weeks, cleared on renewal via_clearPendingSettlements
. - Bidding:
MailMarket
handles ETH/ERC20 bids;acceptMarketBid
enables owner-initiated settlements within allowance;processSettlement
for post-grace. - Settlement Management:
pendingSettlements
uses swap-and-pop for removals (processSettlement
,_clearPendingSettlements
), preventing gaps; only one settlement finalizes per name. - Fee Handling: Handled in
MailMarket
viaTokenTransferData
. - Gas/DoS: Paginated views (
getPendingSettlements
,getSubnames
), O(1) queue processing, swap-pop for settlements/checkins, assembly for_isContract
. - Ownership: Unified via
ownerOf
;acceptMarketBid
ensures owner auth. - Grace/Queue: 7-day primary grace; post-grace bids queue for 3 weeks; renewals clear settlements, preserving bids for later acceptance.
- Events: ERC721 (
Transfer
/Approval
/ApprovalForAll
) and custom (SettlementProcessed
); no emits in views. - Degradation: Reverts on critical failures; try/catch for hooks;
advance
/processSettlement
skip gracefully.
Version: 0.0.1
Date: 05/10/2025
Locks MailToken
deposits from MailNames
queues (10y unlock, multi-indexed). Owner-set post-deploy; handles normalized amounts.
- Deposit:
amount
: Normalized (no decimals).unlockTime
: Timestamp.
owner
: Contract owner.mailToken
: IERC20 token.mailNames
:MailNames
address.userDeposits
: Mapsaddress
toDeposit[]
.
- Purpose: Locks deposit from
MailNames
. - Checks: Caller=
mailNames
, transfer succeeds. - Effects: Pushes to
userDeposits
, emitsDepositLocked
.
- Purpose: Withdraws deposit (swap-pop).
- Checks: Valid index, time >= unlock.
- Effects: Transfers, emits
DepositWithdrawn
.
- Purpose: Sets token.
- Effects: Updates
mailToken
.
- Purpose: Sets
MailNames
. - Effects: Updates
mailNames
.
- Purpose: Transfers ownership.
- Checks:
_newOwner !=0
. - Effects: Sets
owner
, emitsOwnershipTransferred
.
- Purpose: Paginated deposits view.
- Returns:
Deposit[]
.
- Purpose: Sums locked amounts.
- Returns: uint256 total.
- Multi-Deposits: Separate 10y locks per user; withdraw by index.
- Normalization: Stores sans decimals; transfers use decimals.
- Gas/DoS: Swap-pop on withdraw, paginated views.
- Events:
DepositLocked
/Withdrawn
. - Degradation: Reverts on invalid transfer/time.
Version: 0.0.5
Date: 07/10/2025
Handles bidding for MailNames
(ETH/ERC20, auto-settle post-grace via queue). Supports token bids with tax token handling. Owner-set mailNames
/mailToken
. Bidders hold $MAIL scaled by their active bids, deterring griefing.
- Bid:
bidder
: Bidder address.amount
: Bid amount (post-fee for tokens).timestamp
: Bid time.
- BidValidation:
nameHash
: Name hash.tokenId
: ERC721 token ID.queueLen
: Queue length (0 inMailMarket
).minReq
: Minimum required wei.normMin
: Normalized minimum.
- TokenTransferData:
balanceBefore
: Pre-transfer balance.balanceAfter
: Post-transfer balance.receivedAmount
: Actual tokens received.transferAmount
: Requested transfer amount.
owner
: Contract owner.mailToken
: ERC20 token.mailNames
:MailNames
address.ethBids
: Mapsuint256
(nameHash) toBid[100]
.tokenBids
: Mapsuint256
(nameHash) tomapping(address => Bid[100])
.allowedTokens
: Array of allowed ERC20 tokens.tokenCounts
: Mapsaddress
to count inallowedTokens
.bidderActiveBids
: Mapsaddress
touint256
(bidder’s active bids across all names).MAX_BIDS
: 100.
- Purpose: Places ETH bid, increments
bidderActiveBids
. - Checks: Name exists,
msg.value>0
, sufficientmailToken
(scaled bybidderActiveBids
). - Internal Calls:
_validateBidRequirements
: Checks name, amount,mailToken
balance._insertAndSort
: Inserts bid with sorted insertion.
- Effects: Stores bid, emits
BidPlaced
.
- Purpose: Places ERC20 bid, increments
bidderActiveBids
. - Checks: Valid token, name, amount,
mailToken
balance (scaled bybidderActiveBids
). - Internal Calls:
_validateBidRequirements
: Validates inputs._handleTokenTransfer
: Computes received amount._insertAndSort
: Inserts bid with sorted insertion.
- Effects: Stores bid, emits
BidPlaced
.
- Purpose: Refunds/removes bid, decrements
bidderActiveBids
. - Checks: Valid index, caller=bidder.
- Internal Calls:
_removeBidFromArray
: Shifts array.
- Effects: Transfers refund, emits
BidClosed
.
- Purpose: Initiates owner-accepted bid.
- Checks: None (delegates to
MailNames
). - Internal Calls:
MailNames.acceptMarketBid
: Validates owner, callssettleBid
.
- Effects: Triggers settlement via
MailNames
, emitsBidSettled
.
- Purpose: Settles bid (called by
MailNames
), decrementsbidderActiveBids
. - Checks: Caller=
mailNames
, valid bid. - Internal Calls:
_transferBidFunds
: Transfers to old owner._removeBidFromArray
: Clears bid.
- Effects: Calls
MailNames.transfer
, emitsBidSettled
.
- Purpose: Validates top ETH bid’s
mailToken
balance; closes invalid bid, refunds, clears data. - Checks: Sufficient
mailToken
balance against_calculateMinRequired
. - Internal Calls:
_calculateMinRequired
: Minimum wei (1 * 2^0)._removeBidFromArray
: Clears invalid bid.
- Effects: Refunds ETH, decrements
bidderActiveBids
, emitsBidClosed
/TopBidInvalidated
. - Returns: (bidder, amount, valid).
- Purpose: Retrieves all bids for a name (ETH or ERC20).
- Internal Calls:
_stringToHash
: Computes name hash.
- Returns:
Bid[100]
.
getBidderBids(string _name, address _bidder, bool _isETH, address _token, uint256 _step, uint256 _maxIterations)
- Purpose: Retrieves paginated bid indices for a bidder on a name and token (
_isETH=true
for ETH,_isETH=false
ignores_token
address if ETH). - Internal Calls:
_stringToHash
: Computes name hash.
- Returns:
uint256[]
(bid indices).
- Purpose: Adds ERC20 token.
- Effects: Pushes to
allowedTokens
, emitsTokenAdded
.
- Purpose: Removes token.
- Effects: Removes from
allowedTokens
, emitsTokenRemoved
.
- Purpose: Sets token.
- Effects: Updates
mailToken
.
- Purpose: Sets
MailNames
. - Effects: Updates
mailNames
.
- Purpose: Transfers ownership.
- Checks:
_newOwner !=0
. - Effects: Sets
owner
, emitsOwnershipTransferred
.
- _stringToHash(string _str): Generates keccak256 hash.
- _validateBidRequirements(string _name, uint256 _bidAmount): Checks name, amount, $MAIL scaled by
bidderActiveBids
. - _handleTokenTransfer(address _token, uint256 _amount): Computes received amount for token bids.
- _insertAndSort(Bid[100] storage bidsArray, Bid newBid): Inserts bid with sorted insertion for gas efficiency.
- _removeBidFromArray(Bid[100] storage bidsArray, uint256 _bidIndex): Clears bid via shift.
- _transferBidFunds(address _oldOwner, uint256 _amount, bool _isETH, address _token): Transfers funds (ETH or token).
- _calculateMinRequired(uint256 _queueLen): 1 * 2^_queueLen wei.
- Bidding: ETH/ERC20 bids; auto-settle post-grace via
MailNames._settleBid
(queued for 3w); owner-initiated viaacceptBid
->MailNames.acceptMarketBid
. - Griefing Deterrence: $MAIL requirement scales with
bidderActiveBids
, increasing costs for spamming multiple bids. - Fee Handling:
TokenTransferData
ensures accurate token amounts. - Gas/DoS: Fixed 100 bids; optimized
_insertAndSort
with sorted insertion; paginated views (getNameBids
,getBidderBids
). - Events:
BidPlaced
/BidSettled
/BidClosed
/TopBidInvalidated
/OwnershipTransferred
. - Access Control:
settleBid
restricted toMailNames
;acceptMarketBid
ensures owner auth.
- No
ReentrancyGuard
needed; no recursive calls. - All on-chain; try/catch for ERC721 hooks.
- Automated renewal scripts can be outbid by users renewing during the script’s grace period, increasing lock-up costs and depleting script tokens before withdrawals.