Wild DNS Documentation


1. Domain Ownership Precautions

Smart contract transfers: Never transfer domains/subdomains to smart contracts unless they can safely handle native currency. Force buys may result in permanent loss if domains are in incompatible contracts.
Marketplace listings: Avoid listing domains/subdomains at prices higher than their force buy price. Buyers risk immediate loss as anyone can force buy at the lower price.

2. Core Functionality

Domain Registration

Mint top-level domains by paying a price based on name length:

  • Standard names (7+ characters): 0.01 ETH (this base price can depend from chain to chain)
  • Short names (1-6 characters): (8 - length) × 3 × base price
Example: 3-character name = (8-3) × 3 × 0.01 = 15 × base price

Observation: domains (and subdomains where applicable) don't expire when refresh interval ends (default is 54 weeks), you stil own them after that time but if you didn't refresh them (by paying base price, even for 1 letter domains) until refresh interval expires, then anyone can get the domain after that time with force buy by paying double the price of the domain registration.

Subdomain Management

Domain owners control subdomain policies:

  • Enable/disable subdomain creation
  • Set public minting permissions
  • Configure force buy rules
  • Define subscription requirements
Subdomain revenue split
  • For subdomain register: 80% to parent owner, 20% to platform
  • For subdomain force buy: 70% to previous owner, 24% to domain owner (same as parent owner), 6% to platform
Note: because force buy factor is at least 2x the previous price, that 70% will be 140% from the orifinal price paid.

Subdomain revenue splitting

Force Buy Mechanics

Top-level domains:

  • Initial force buy price: Mint price × 2
  • Owner receives 70% of force buy value
  • New force buy price = Payment × 2
Subdomains:
  • Customizable via parent domain policy
  • Revenue split: 70% to subdomain owner, 24% to parent owner, 6% to Wild DNS platform

Subscription Renewals

Subscription-based subdomains can be renewed anytime. Renewal price matches initial subscription/mint price and follows the revenue split of 80% to domain owner and 20% to platform.

3. Domain Protection Periods (default values for domains, subdomains can have custom values)

Period Type Duration Functionality
Force Buy Interval 14 days Allows force buys after initial registration
Refresh Interval 54 weeks Allows domain refresh after force buy interval ends

4. Account Management

Main Main Domain: Set a primary domain for your address, used to get domain for an address.

Admin Roles: Designate separate admin and payment addresses for domain management.

5. Obtaining Testnet Tokens

Use these faucets for test networks:

6. Getting IP for a domain

Example of python script that uses web3 library

from web3 import Web3
import argparse

# example on how to execute it
# example_script_to_get_ip.py --rpc https://ethereum-sepolia-rpc.publicnode.com --contract 0xf78db67621CDd201C47a09987DEF172529371cfc --domain apple.onchain.wild
# example_script_to_get_ip.py --rpc https://ethereum-sepolia-rpc.publicnode.com --contract 0xf78db67621CDd201C47a09987DEF172529371cfc --domain cryptokid.wild


# Minimal ABI required for the functions we'll use
ABI = [
    {
        "inputs": [{"name": "name", "type": "string"}],
        "name": "getTokenIdByName",
        "outputs": [{"name": "", "type": "uint256"}],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [{"name": "tokenId", "type": "uint256"}],
        "name": "_tokenData",
        "outputs": [
            {"name": "name", "type": "string"},
            {"name": "forceBuyPrice", "type": "uint256"},
            {"name": "lastForceBuyTime", "type": "uint256"},
            {"name": "subscriptionExpirationTime", "type": "uint256"},
            {"name": "mintTimestamp", "type": "uint256"},
            {"name": "adminAddress", "type": "address"},
            {"name": "paymentAddress", "type": "address"},
            {"name": "customData", "type": "bytes"}
        ],
        "stateMutability": "view",
        "type": "function"
    }
]

def get_ip_for_domain(rpc_url, contract_address, domain_name):
    """
    Retrieves the IP address associated with a domain name from the blockchain.
    
    Args:
        rpc_url: URL of the Ethereum RPC endpoint
        contract_address: Address of the WildDNS contract
        domain_name: Full domain name (e.g., "subdomain.example.wild")
    
    Returns:
        IP address string if found, or None if not set or invalid
    """
    # Connect to blockchain
    w3 = Web3(Web3.HTTPProvider(rpc_url))
    
    if not w3.is_connected():
        raise ConnectionError("Failed to connect to RPC server")
    
    # Create contract instance
    contract = w3.eth.contract(address=contract_address, abi=ABI)
    
    # Remove .wild TLD if present
    if domain_name.endswith('.wild'):
        domain_name = domain_name[:-5]
    
    try:
        # Get token ID
        token_id = contract.functions.getTokenIdByName(domain_name).call()
        
        if token_id == 0:
            print(f"Domain not found: {domain_name}")
            return None
        
        # Get token data
        token_data = contract.functions._tokenData(token_id).call()
        custom_data = token_data[7]  # customData is at index 7
        
        # Check if custom data is an IPv4 address (4 bytes)
        if len(custom_data) == 4:
            # Convert bytes to IPv4 string
            return '.'.join(str(byte) for byte in custom_data)
        elif custom_data:
            print(f"Custom data is not an IPv4 address: {custom_data.hex()}")
        else:
            print("No IP address set for this domain")
            
    except Exception as e:
        print(f"Error: {str(e)}")
    
    return None

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Get IP address for a WildDNS domain')
    parser.add_argument('--rpc', required=True, help='Ethereum RPC URL')
    parser.add_argument('--contract', required=True, help='Contract address')
    parser.add_argument('--domain', required=True, help='Domain name (e.g., "example.wild" or "sub.example.wild")')
    
    args = parser.parse_args()
    
    ip_address = get_ip_for_domain(args.rpc, args.contract, args.domain)
    
    if ip_address:
        print(f"IP address for {args.domain}: {ip_address}")

Example of a python script that makes directly http requests withoutusing web3 library

import requests
import json
import argparse

# Function to encode ABI data for getTokenIdByName
def encode_get_token_id_by_name(domain_name):
    # Remove .wild suffix if present
    if domain_name.endswith('.wild'):
        domain_name = domain_name[:-5]
    
    # Function signature (first 4 bytes of keccak256('getTokenIdByName(string)'))
    function_sig = "0xdb7a298e"
    
    # Encode domain name
    domain_bytes = domain_name.encode('utf-8')
    domain_hex = domain_bytes.hex()
    
    # ABI encoding for string
    # Offset to string data (always 0x20 for single string param)
    offset = "0000000000000000000000000000000000000000000000000000000000000020"
    
    # String length (padded to 32 bytes)
    length = hex(len(domain_bytes))[2:].zfill(64)
    
    # String content (padded to 32 bytes multiple)
    content = domain_hex.ljust((len(domain_hex) + 63) // 64 * 64, '0')
    
    return function_sig + offset + length + content

# Function to encode ABI data for _tokenData
def encode_token_data(token_id):
    # Function signature (first 4 bytes of keccak256('_tokenData(uint256)'))
    function_sig = "0x2c2cdd60"
    
    # Encode token ID (padded to 32 bytes)
    token_id_hex = hex(token_id)[2:].zfill(64)
    
    return function_sig + token_id_hex

# Function to parse _tokenData response and extract IP
def parse_token_data_response(response_hex):
    # Remove 0x prefix
    if response_hex.startswith("0x"):
        response_hex = response_hex[2:]
    
    # The customData field is the 8th element in the tuple
    # First we get the offset to customData (bytes at position 7*64 = 448 to 512)
    custom_data_offset = int(response_hex[448:512], 16) * 2  # Convert to hex position
    
    # At the offset, first 64 characters (32 bytes) is the length of customData
    custom_data_length_hex = response_hex[custom_data_offset:custom_data_offset+64]
    custom_data_length = int(custom_data_length_hex, 16)
    
    # Next comes the actual customData (padded to 32 bytes)
    custom_data_start = custom_data_offset + 64
    custom_data_end = custom_data_start + custom_data_length * 2
    custom_data_hex = response_hex[custom_data_start:custom_data_end]
    
    # Convert hex to bytes
    custom_data_bytes = bytes.fromhex(custom_data_hex)
    
    # If we have exactly 4 bytes, it's an IPv4 address
    if len(custom_data_bytes) == 4:
        return '.'.join(str(byte) for byte in custom_data_bytes)
    
    return None

def get_ip_for_domain(rpc_url, contract_address, domain_name):
    # Step 1: Get token ID for domain
    data = encode_get_token_id_by_name(domain_name)
    payload = {
        "jsonrpc": "2.0",
        "method": "eth_call",
        "params": [{
            "to": contract_address,
            "data": data
        }, "latest"],
        "id": 1
    }
    
    try:
        response = requests.post(rpc_url, json=payload)
        result = response.json()
        
        if 'error' in result:
            print(f"Error getting token ID: {result['error']['message']}")
            return None
            
        token_id_hex = result['result']
        if token_id_hex == "0x0000000000000000000000000000000000000000000000000000000000000000":
            print(f"Domain not found: {domain_name}")
            return None
            
        token_id = int(token_id_hex, 16)
    except Exception as e:
        print(f"Token ID request failed: {str(e)}")
        return None
    
    # Step 2: Get token data
    data = encode_token_data(token_id)
    payload = {
        "jsonrpc": "2.0",
        "method": "eth_call",
        "params": [{
            "to": contract_address,
            "data": data
        }, "latest"],
        "id": 2
    }
    
    try:
        response = requests.post(rpc_url, json=payload)
        result = response.json()
        
        if 'error' in result:
            print(f"Error getting token data: {result['error']['message']}")
            return None
            
        token_data_hex = result['result']
        ip_address = parse_token_data_response(token_data_hex)
        return ip_address
        
    except Exception as e:
        print(f"Token data request failed: {str(e)}")
        return None

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Get IP address for a WildDNS domain')
    parser.add_argument('--rpc', required=True, help='Ethereum RPC URL')
    parser.add_argument('--contract', required=True, help='Contract address')
    parser.add_argument('--domain', required=True, help='Domain name (e.g., "example.wild" or "sub.example.wild")')
    
    args = parser.parse_args()
    
    ip_address = get_ip_for_domain(args.rpc, args.contract, args.domain)
    
    if ip_address:
        print(f"IP address for {args.domain}: {ip_address}")
    else:
        print("No IP address found for this domain")

7. Key Contract Functions

Domain Registration

function mint(string memory _name) external payable {
    require(GLOBAL_MINT_ENABLED, "Minting disabled");
    _validateName(_name);
    uint256 mintPrice = _calculateMintPrice(_name);
    require(msg.value == mintPrice, "Incorrect payment");
    // ... (token creation logic)
}

Subdomain Creation

function createSubdomain(uint256 parentTokenId_, string memory subLabel) 
    external payable {
    require(policy.subdomainsEnabled, "Subdomains disabled");
    // ... (access control and payment checks)
    // Revenue split: 80% to parent owner, 20% to contract
}

Force Buy

function forceBuy(uint256 tokenId) external payable {
    require(GLOBAL_FORCE_BUY_ENABLED, "Force buy disabled");
    // ... (price and timing validation)
    // Domain-specific payment distribution
}

Subscription Renewal

function renewSubscription(uint256 tokenId) external payable {
    require(isSubdomain(tokenId), "Not subdomain");
    // ... (payment and expiration update)
    // Same revenue split as subdomain creation
}