Solidity contracts for Grails: batch ENS .eth name registration and tiered subscriptions. Built with Foundry.
- Batch registration — register multiple
.ethnames in one transaction - Mixed-length support — no same-price restriction; each name is priced individually
- Referral tracking — emits
NameRegisteredevents with an immutable referrer - Automatic refunds — excess ETH is returned in the same transaction
- Batch utilities — bulk availability checks, price quotes, commitment generation
- Multi-tier subscriptions — subscribe to Pro, Plus, or Gold for any number of days
- Oracle-based USD pricing — tier prices are set in attoUSD/second; ETH costs are derived at transaction time via a Chainlink ETH/USD oracle
- Tier upgrades — upgrade to a higher tier and convert remaining time proportionally based on tier price rates (no value lost)
- Automatic refunds — excess ETH is returned in the same transaction
- Owner withdrawal — owner can withdraw collected funds
- Ownable2Step — safe two-step ownership transfers
| Contract | Description |
|---|---|
src/BulkRegistration.sol |
Batch commit, register, and view functions for ENS names |
src/IETHRegistrarController.sol |
Interface for the wrapped ETHRegistrarController |
src/GrailsSubscription.sol |
Subscription management — subscribe, upgrade, and query |
src/GrailsPricing.sol |
USD-based tier pricing with Chainlink oracle integration |
src/IGrailsPricing.sol |
Interface for the pricing contract |
script/Deploy.s.sol |
Deployment script for BulkRegistration (mainnet + sepolia) |
script/DeploySubscription.s.sol |
Deployment script for GrailsPricing + GrailsSubscription |
git clone <repo-url>
cd contracts
forge install
cp .env.example .env
# Fill in MAINNET_RPC_URL, SEPOLIA_RPC_URL, ETHERSCAN_API_KEYforge buildUnit tests run without a fork. Fork tests (registration and subscription oracle integration) require a mainnet RPC:
# Unit tests only
forge test -vvv
# Including fork tests
forge test --fork-url $MAINNET_RPC_URL -vvvREFERRER=0x000000000000000000000000<your-address> \
forge script script/Deploy.s.sol --rpc-url $MAINNET_RPC_URL --broadcast --verifyDEPLOYER=0x<owner-address> \
forge script script/DeploySubscription.s.sol --rpc-url $MAINNET_RPC_URL --broadcast --verify- Check availability — call
available(names)to verify names are open - Get pricing — call
totalPrice(names, duration)for the required ETH - Commit — call
makeCommitments(...)thenmultiCommit(commitments) - Wait — wait at least 60 seconds for the commitment to mature
- Register — call
multiRegister{value: totalPrice}(...)with sufficient ETH; excess is refunded automatically
- Check price — call
getPrice(tierId, durationDays)to get the required ETH for a tier and duration - Subscribe — call
subscribe{value: cost}(tierId, durationDays)to start a subscription (replaces any existing subscription from the current timestamp) - Check status — call
getSubscription(address)to view expiry timestamp, or readsubscriptions(address)for both expiry and tier ID - Upgrade — call
upgrade(newTierId, extraDays)to move to a higher tier; remaining time is converted proportionally based on the tier price ratio, andextraDayscan be purchased in the same transaction - Preview upgrade — call
previewUpgrade(address, newTierId)to see the projected expiry before committing