Skip to main content
Price oracles determine the cost of registering and renewing .eth names. ENS provides several oracle implementations with different pricing models.

Overview

All price oracles implement the IPriceOracle interface and return a Price struct:
struct Price {
    uint256 base;      // Base price in wei
    uint256 premium;   // Premium price in wei (for expired names)
}
Total price = base + premium

StablePriceOracle

A price oracle that returns prices in USD, converted to ETH using a Chainlink oracle. Source: StablePriceOracle.sol:14

Features

  • Prices denominated in USD (stable pricing)
  • Length-based pricing (shorter names cost more)
  • Uses Chainlink oracle for ETH/USD conversion
  • No premium decay (base implementation)

Price Structure

Prices are set per name length:
uint256 public immutable price1Letter;  // Premium price for 1-char names
uint256 public immutable price2Letter;  // Price for 2-char names
uint256 public immutable price3Letter;  // Price for 3-char names
uint256 public immutable price4Letter;  // Price for 4-char names
uint256 public immutable price5Letter;  // Price for 5+ char names
Source: StablePriceOracle.sol:18-22

Constructor

constructor(
    AggregatorInterface _usdOracle,
    uint256[] memory _rentPrices
)
_usdOracle
AggregatorInterface
required
Chainlink oracle address for ETH/USD price feed
_rentPrices
uint256[]
required
Array of 5 prices in attoUSD (10^-18 USD) for 1-char through 5+ char names
Source: StablePriceOracle.sol:29

price

Returns the price for a name.
function price(
    string calldata name,
    uint256 expires,
    uint256 duration
) external view returns (IPriceOracle.Price memory)
name
string
required
The label to price
expires
uint256
required
Current expiration timestamp (0 for new registrations)
duration
uint256
required
Registration duration in seconds
Returns: Price struct with base and premium in wei Source: StablePriceOracle.sol:38

Example Usage

import { ethers } from 'ethers';

const oracle = new ethers.Contract(oracleAddress, oracleABI, provider);

// Price for registering "alice" for 1 year
const price = await oracle.price('alice', 0, 31536000);

console.log(`Base: ${ethers.utils.formatEther(price.base)} ETH`);
console.log(`Premium: ${ethers.utils.formatEther(price.premium)} ETH`);
console.log(`Total: ${ethers.utils.formatEther(price.base.add(price.premium))} ETH`);

LinearPremiumPriceOracle

Extends StablePriceOracle with a linearly decaying premium for recently expired names. Source: LinearPremiumPriceOracle.sol:7

Features

  • All features of StablePriceOracle
  • Linear premium decay for expired names
  • Premium starts at initialPremium and decreases by premiumDecreaseRate per second
  • 90-day grace period (no premium during grace period)

Premium Calculation

The premium decay works as follows:
time since expiry = block.timestamp - (expires + 90 days)
if time since expiry <= 0:
    premium = 0  // Still in grace period or not expired
else:
    discount = premiumDecreaseRate * time since expiry
    premium = initialPremium - discount
    if premium < 0:
        premium = 0
Source: LinearPremiumPriceOracle.sol:28

Constructor

constructor(
    AggregatorInterface _usdOracle,
    uint256[] memory _rentPrices,
    uint256 _initialPremium,
    uint256 _premiumDecreaseRate
)
_usdOracle
AggregatorInterface
required
Chainlink oracle for ETH/USD
_rentPrices
uint256[]
required
Array of 5 base prices in attoUSD
_initialPremium
uint256
required
Starting premium in attoUSD immediately after grace period ends
_premiumDecreaseRate
uint256
required
Amount in attoUSD the premium decreases per second
Source: LinearPremiumPriceOracle.sol:18

timeUntilPremium

Calculates when the premium will reach a specific amount.
function timeUntilPremium(
    uint256 expires,
    uint256 amount
) external view returns (uint256)
expires
uint256
required
The name’s expiration timestamp
amount
uint256
required
Desired premium amount in wei
Returns: Timestamp when premium will equal amount Source: LinearPremiumPriceOracle.sol:57

Example Usage

// Find when premium drops to 0.1 ETH
const expires = await baseRegistrar.nameExpires(tokenId);
const targetPremium = ethers.utils.parseEther('0.1');

const timestamp = await oracle.timeUntilPremium(expires, targetPremium);
const date = new Date(timestamp * 1000);

console.log(`Premium will be 0.1 ETH at: ${date}`);

ExponentialPremiumPriceOracle

Extends StablePriceOracle with an exponentially decaying premium (halves each day). Source: ExponentialPremiumPriceOracle.sol:6

Features

  • All features of StablePriceOracle
  • Exponential decay: premium halves every day
  • High initial premium to discourage immediate re-registration
  • Premium eventually reaches endValue (minimum floor)

Premium Calculation

The premium uses exponential decay:
premium(t) = startPremium * (0.5 ^ days_elapsed) - endValue
Where:
  • days_elapsed = time since grace period ended, in days
  • startPremium = Initial premium value
  • endValue = Minimum premium floor (never goes below this)
Source: ExponentialPremiumPriceOracle.sol:40

Constructor

constructor(
    AggregatorInterface _usdOracle,
    uint256[] memory _rentPrices,
    uint256 _startPremium,
    uint256 totalDays
)
_usdOracle
AggregatorInterface
required
Chainlink oracle for ETH/USD
_rentPrices
uint256[]
required
Array of 5 base prices in attoUSD
_startPremium
uint256
required
Initial premium immediately after grace period (in attoUSD)
totalDays
uint256
required
Number of days over which premium decays. Used to calculate endValue = _startPremium >> totalDays
Source: ExponentialPremiumPriceOracle.sol:11

decayedPremium

Calculates the premium at a specific elapsed time.
function decayedPremium(
    uint256 startPremium,
    uint256 elapsed
) public pure returns (uint256)
startPremium
uint256
required
The starting premium value
elapsed
uint256
required
Time elapsed in seconds
Returns: The decayed premium value Source: ExponentialPremiumPriceOracle.sol:61

Example Decay Timeline

With startPremium = 1000 USD and totalDays = 21:
Days After Grace PeriodPremium
01000 USD
1500 USD
2250 USD
3125 USD
77.8 USD
140.06 USD
21~0 USD (reaches endValue)

Example Usage

// Check premium decay for an expired name
const expires = await baseRegistrar.nameExpires(tokenId);
const gracePeriod = 90 * 24 * 60 * 60; // 90 days
const now = Math.floor(Date.now() / 1000);

if (expires + gracePeriod < now) {
  const elapsed = now - (expires + gracePeriod);
  const premium = await oracle.decayedPremium(
    ethers.utils.parseEther('1000'), // 1000 ETH equivalent in attoUSD
    elapsed
  );
  
  console.log(`Premium after ${elapsed / 86400} days: ${ethers.utils.formatEther(premium)} ETH`);
}

Comparison

OraclePremium DecayBest ForComplexity
StablePriceOracleNoneCustom implementationsLow
LinearPremiumPriceOracleLinearGradual, predictable decayMedium
ExponentialPremiumPriceOracleExponentialFast initial decay, then gradualHigh

Integration Notes

Price Units

Prices are stored in “attoUSD” (10^-18 USD):
// $5 USD per year = 5 * 10^18 attoUSD
const priceInAttoUSD = ethers.utils.parseEther('5');

ETH/USD Oracle

The Chainlink oracle must return price with 8 decimals:
// If ETH = $2000
// Oracle returns: 200000000000 (2000 * 10^8)

Conversion Formula

The conversion from attoUSD to wei:
function attoUSDToWei(uint256 amount) internal view returns (uint256) {
    uint256 ethPrice = uint256(usdOracle.latestAnswer());
    return (amount * 1e8) / ethPrice;
}
Source: StablePriceOracle.sol:83

Grace Period Handling

All oracles treat names in the grace period (90 days after expiry) as renewals with no premium:
expires = expires + GRACE_PERIOD;
if (expires > block.timestamp) {
    return 0; // No premium
}

Custom Implementations

To create a custom price oracle:
  1. Implement the IPriceOracle interface
  2. Return a Price struct with base and premium in wei
  3. Consider inheriting from StablePriceOracle for USD pricing support
  4. Override _premium() for custom premium logic
contract CustomOracle is StablePriceOracle {
    function _premium(
        string memory name,
        uint256 expires,
        uint256 duration
    ) internal view override returns (uint256) {
        // Your custom premium logic
        return 0;
    }
}

Build docs developers (and LLMs) love