Skip to content

For Validators

Relevant Source Files

This document provides a comprehensive guide for setting up and operating a validator in the SN106 Bittensor subnet system. Validators are responsible for evaluating miner performance by analyzing their concentrated liquidity positions across multiple blockchain networks and submitting calculated weights to the Subtensor chain every 20 minutes.

For information about how miners participate in the system, see For Miners. For general system architecture details, see System Architecture.

ComponentMinimumRecommended
CPU4 cores8 cores
RAM8GB16GB
Storage50GB+ SSD100GB+ SSD
Network50+ Mbps stable connection100+ Mbps high-speed
OSLinux/macOS/WindowsUbuntu 22.04 LTS
DependencyVersionPurpose
Node.jsv18.0.0+Runtime environment
npmv8.0.0+Package management
GitLatestRepository access

The validator requires RPC access to multiple blockchain networks:

  • Solana RPC: Mainnet/devnet access for position data
  • Subtensor: Bittensor network RPC for weight submission
  • Ethereum RPC: Coming soon for Uniswap V3 integration
  • Base RPC: Coming soon for Base network integration

Sources: README.md:53-81

Terminal window
# Clone the repository
git clone <repository-url>
cd sn106
# Install dependencies
npm install

Copy the environment template and configure required variables:

Terminal window
cp .env.example .env
nano .env

The configuration system uses a typed environment loader that validates and provides default values for missing variables.

The SN106 validator uses a centralized configuration system that loads environment variables and provides typed access throughout the application.

graph TD
    subgraph "Configuration Loading"
        ENV_FILE[".env File"]
        DOTENV["dotenv.config()"]
        ENV_OBJ["ENV Object<br/>Raw Environment Variables"]
    end
    
    subgraph "Configuration Processing"
        CONFIG_OBJ["CONFIG Object<br/>Typed & Validated"]
        VALIDATION["validateEnvironment()"]
        DEFAULTS["Default Value Assignment"]
    end
    
    subgraph "Chain Configuration"
        ENABLED_CHAINS["getEnabledChains()"]
        CHAIN_CHECK["isChainEnabled()"]
        SOLANA_CFG["CONFIG.SOLANA"]
        ETH_CFG["CONFIG.ETHEREUM"]
        BASE_CFG["CONFIG.BASE"]
    end
    
    subgraph "Runtime Configuration"
        SUBTENSOR_CFG["CONFIG.SUBTENSOR"]
        VALIDATOR_CFG["CONFIG.VALIDATOR"]
        PERFORMANCE_CFG["CONFIG.PERFORMANCE"]
        BITTENSOR_CFG["CONFIG.BITTENSOR"]
    end
    
    ENV_FILE --> DOTENV
    DOTENV --> ENV_OBJ
    ENV_OBJ --> CONFIG_OBJ
    ENV_OBJ --> VALIDATION
    ENV_OBJ --> DEFAULTS
    
    CONFIG_OBJ --> ENABLED_CHAINS
    CONFIG_OBJ --> CHAIN_CHECK
    CONFIG_OBJ --> SOLANA_CFG
    CONFIG_OBJ --> ETH_CFG
    CONFIG_OBJ --> BASE_CFG
    CONFIG_OBJ --> SUBTENSOR_CFG
    CONFIG_OBJ --> VALIDATOR_CFG
    CONFIG_OBJ --> PERFORMANCE_CFG
    CONFIG_OBJ --> BITTENSOR_CFG

Sources: config/environment.ts:1-169

Terminal window
# WebSocket endpoint for Subtensor chain
SUBTENSOR_WS_URL=wss://your-subtensor-endpoint
# Validator hotkey (supports multiple formats)
VALIDATOR_HOTKEY_URI=your-hotkey-uri-or-mnemonic
# Network UID for SN106
NETUID=106
Terminal window
# Solana RPC endpoint
SOLANA_RPC_ENDPOINT=https://your-solana-rpc
# SN106 smart contract program ID
SN106_SVM_PROGRAM_ID=DtZgqA3dvVK1m1CUEnkipEHyDtPzAmxb98SU9sqYDpDE
# Raydium CLMM program ID
RAYDIUM_CLMM_PROGRAM_ID=DRayAUgENGQBKVaX8owNhgzkEDyoHTGVEGHVJT1E9pfH
Terminal window
# Execution interval in minutes (default: 20)
VALIDATOR_INTERVAL_MINUTES=20
# EMA smoothing parameters
USE_EMA=true
EMA_ALPHA=0.3
EMA_EPSILON=1e-6
# Enabled blockchain networks
ENABLED_CHAINS=SOLANA,ETHEREUM,BASE

The VALIDATOR_HOTKEY_URI supports multiple input formats for backward compatibility:

  • Mnemonic: word1 word2 word3 ... word12
  • Private Key: 0x1234... (hex format)
  • URI: //Alice or other Polkadot-style URIs

Sources: config/environment.ts:44-51 , README.md:104-132

The validator operates on a scheduled 20-minute cycle, executing a comprehensive data collection and weight calculation pipeline.

graph TD
    subgraph "Initialization Phase"
        START["runValidator()"]
        LOG_CHAINS["Log Enabled Chains"]
        INIT_CLIENT["subtensorClient.initialize()"]
        EMA_INIT["Initialize EMA State<br/>global.__sn106EmaWeights"]
    end
    
    subgraph "Data Collection Phase"
        FETCH_HOTKEYS["getHotkeyToUidMap()"]
        FETCH_POSITIONS["getAllNFTPositions()"]
        FETCH_TICKS["getCurrentTickPerPool()"]
        FETCH_PRICES["getSubnetAlphaPrices()"]
    end
    
    subgraph "Weight Calculation Phase"
        POOL_WEIGHTS["calculatePoolWeightsWithReservedPools()"]
        NFT_EMISSIONS["calculatePoolwiseNFTEmissions()"]
        MINER_AGGREGATION["Aggregate Per-Miner Emissions"]
        POLICY_APPLICATION["Apply Distribution Policy"]
    end
    
    subgraph "EMA Processing"
        EMA_CHECK["CONFIG.VALIDATOR.USE_EMA?"]
        EMA_UPDATE["updateEma()"]
        EMA_APPLY["Apply EMA Weights"]
        RAW_WEIGHTS["Use Raw Weights"]
    end
    
    subgraph "Weight Submission"
        PREPARE_WEIGHTS["Prepare Final Weights"]
        SUBMIT_WEIGHTS["setWeightsOnSubtensor()"]
        LOG_COMPLETE["Log Completion"]
    end
    
    subgraph "Scheduling"
        TIMER["setInterval()<br/>20-minute cycles"]
        SHUTDOWN["Graceful Shutdown<br/>SIGINT/SIGTERM"]
    end
    
    START --> LOG_CHAINS
    LOG_CHAINS --> INIT_CLIENT
    INIT_CLIENT --> EMA_INIT
    EMA_INIT --> FETCH_HOTKEYS
    FETCH_HOTKEYS --> FETCH_POSITIONS
    FETCH_POSITIONS --> FETCH_TICKS
    FETCH_TICKS --> FETCH_PRICES
    FETCH_PRICES --> POOL_WEIGHTS
    POOL_WEIGHTS --> NFT_EMISSIONS
    NFT_EMISSIONS --> MINER_AGGREGATION
    MINER_AGGREGATION --> POLICY_APPLICATION
    POLICY_APPLICATION --> EMA_CHECK
    EMA_CHECK -->|true| EMA_UPDATE
    EMA_CHECK -->|false| RAW_WEIGHTS
    EMA_UPDATE --> EMA_APPLY
    EMA_APPLY --> PREPARE_WEIGHTS
    RAW_WEIGHTS --> PREPARE_WEIGHTS
    PREPARE_WEIGHTS --> SUBMIT_WEIGHTS
    SUBMIT_WEIGHTS --> LOG_COMPLETE
    
    TIMER --> START
    SHUTDOWN --> INIT_CLIENT

Sources: validator/index.ts:17-181

The validator implements a sophisticated weight distribution policy based on miner position quality:

  1. In-Range Miners: If any miner has positions within the current trading range, only those miners receive non-zero weights
  2. All Out-of-Range: If all positions are out-of-range, uniform weights are distributed across all UIDs
  3. EMA Smoothing: When enabled, weights are smoothed using exponential moving averages with configurable EMA_ALPHA
graph TD
    subgraph "Policy Decision Tree"
        CHECK_POSITIVE["hasPositiveEmission<br/>Any miner with emission > 0?"]
        INIT_ZERO["Initialize all UIDs to 0"]
        EMA_DECISION["CONFIG.VALIDATOR.USE_EMA?"]
        APPLY_EMA["Apply EMA to eligible miners<br/>updateEma(emaWeights, eligible)"]
        ASSIGN_RAW["Assign raw weights<br/>to positive miners only"]
        UNIFORM_FALLBACK["setWeightsOnSubtensor<br/>handles uniform distribution"]
    end
    
    subgraph "EMA Weight Management"
        EMA_STATE["global.__sn106EmaWeights<br/>Persistent EMA state"]
        EMA_UPDATE["nextEma = updateEma(prev, curr)"]
        EMA_THRESHOLD["Filter by EMA_EPSILON<br/>Remove near-zero weights"]
        EMA_SAVE["Save updated EMA state"]
    end
    
    CHECK_POSITIVE -->|true| INIT_ZERO
    CHECK_POSITIVE -->|false| UNIFORM_FALLBACK
    INIT_ZERO --> EMA_DECISION
    EMA_DECISION -->|true| APPLY_EMA
    EMA_DECISION -->|false| ASSIGN_RAW
    APPLY_EMA --> EMA_STATE
    EMA_STATE --> EMA_UPDATE
    EMA_UPDATE --> EMA_THRESHOLD
    EMA_THRESHOLD --> EMA_SAVE
    EMA_SAVE --> UNIFORM_FALLBACK
    ASSIGN_RAW --> UNIFORM_FALLBACK

Sources: validator/index.ts:110-158

Terminal window
# Start the validator
npm run validator

The validator will:

  1. Initialize connection to Subtensor chain using subtensorClient.initialize()
  2. Begin 20-minute execution cycles using setInterval()
  3. Log operation status and weight submissions to console
  4. Save weight history to weights/weights_history.json

The validator handles shutdown signals gracefully:

Terminal window
# Graceful shutdown (CTRL+C)
SIGINT -> subtensorClient.shutdown() -> process.exit(0)
# Termination signal
SIGTERM -> subtensorClient.shutdown() -> process.exit(0)

Sources: validator/index.ts:166-176

The validator implements a multi-stage weight calculation system that evaluates NFT position quality and subnet performance.

graph TB
    subgraph "Input Data Sources"
        POSITIONS_DATA["getAllNFTPositions()<br/>Multi-chain NFT data"]
        TICK_DATA["getCurrentTickPerPool()<br/>Current market state"]
        ALPHA_PRICES["getSubnetAlphaPrices()<br/>Subnet performance"]
        HOTKEY_MAP["getHotkeyToUidMap()<br/>Miner identities"]
    end
    
    subgraph "Pool Weight Calculation"
        RESERVED_LOGIC["Reserved Share Logic<br/>25% for subnet 0 pools"]
        SUBNET_WEIGHTS["Subnet Weight Calculation<br/>Based on alpha prices"]
        POOL_WEIGHTS_CALC["calculatePoolWeightsWithReservedPools()"]
    end
    
    subgraph "NFT Scoring Engine"
        WIDTH_SCORE["Position Width Analysis<br/>Concentration bonus"]
        DISTANCE_SCORE["Distance from Current Tick<br/>Market proximity penalty"]
        LIQUIDITY_SCORE["Liquidity Amount<br/>Capital contribution"]
        COMBINED_SCORE["Combined NFT Score"]
    end
    
    subgraph "Emission Distribution"
        POOL_ALLOCATION["Pool-wise Allocation<br/>Based on pool weights"]
        NFT_COMPETITION["Intra-pool Competition<br/>NFTs compete within pools"]
        EMISSION_CALC["calculatePoolwiseNFTEmissions()"]
    end
    
    subgraph "Miner Aggregation"
        PER_MINER["Aggregate emissions per miner<br/>Sum of all NFT emissions"]
        POLICY_CHECK["Distribution policy check<br/>In-range vs out-of-range"]
        FINAL_WEIGHTS["Final miner weights"]
    end
    
    POSITIONS_DATA --> POOL_WEIGHTS_CALC
    TICK_DATA --> POOL_WEIGHTS_CALC
    ALPHA_PRICES --> POOL_WEIGHTS_CALC
    POOL_WEIGHTS_CALC --> RESERVED_LOGIC
    RESERVED_LOGIC --> SUBNET_WEIGHTS
    
    POSITIONS_DATA --> WIDTH_SCORE
    TICK_DATA --> DISTANCE_SCORE
    POSITIONS_DATA --> LIQUIDITY_SCORE
    WIDTH_SCORE --> COMBINED_SCORE
    DISTANCE_SCORE --> COMBINED_SCORE
    LIQUIDITY_SCORE --> COMBINED_SCORE
    
    SUBNET_WEIGHTS --> POOL_ALLOCATION
    COMBINED_SCORE --> NFT_COMPETITION
    POOL_ALLOCATION --> EMISSION_CALC
    NFT_COMPETITION --> EMISSION_CALC
    
    EMISSION_CALC --> PER_MINER
    PER_MINER --> POLICY_CHECK
    POLICY_CHECK --> FINAL_WEIGHTS
    HOTKEY_MAP --> FINAL_WEIGHTS

Sources: validator/index.ts:71-108

The validator provides comprehensive logging through the logger utility:

  • Startup: Chain configuration and initialization status
  • Data Collection: Position counts, tick data, and price information
  • Weight Calculation: Pool weights, NFT emissions, and policy decisions
  • Submission: Weight submission results and completion status
  • Errors: Detailed error messages for troubleshooting

All weight submissions are automatically saved to weights/weights_history.json for audit and analysis purposes. This file contains:

  • Timestamp of each submission
  • Final weight values sent to Subtensor
  • Policy decisions (in-range vs uniform distribution)

Key indicators to monitor:

MetricExpected BehaviorTroubleshooting
Execution CycleEvery 20 minutesCheck VALIDATOR_INTERVAL_MINUTES
Position Count> 0 for active minersVerify chain connectivity
Weight SubmissionSuccessful every cycleCheck VALIDATOR_HOTKEY_URI
EMA StatePersistent across runsMonitor global.__sn106EmaWeights

The validator includes several performance optimization settings:

Terminal window
# Retry configuration
MAX_RETRIES=3
RETRY_BASE_DELAY_MS=1000
MAX_RETRY_DELAY_MS=5000
# Batch processing
POSITION_BATCH_SIZE=100
MAX_CONCURRENT_BATCHES=3
HOTKEY_BATCH_SIZE=8
# Timeouts
RPC_TIMEOUT_MS=30000

Sources: config/environment.ts:57-68 , README.md:149-153

IssueSymptomsSolution
Missing Environment VariablesWarning messages at startupConfigure required variables in .env
RPC Connection FailuresTimeout errors in logsVerify RPC endpoints and network connectivity
Weight Submission FailuresError in setWeightsOnSubtensor()Check VALIDATOR_HOTKEY_URI format and permissions
EMA State LossInconsistent weight smoothingEnsure validator process persistence

The configuration system automatically validates required environment variables using validateEnvironment() and provides warnings for missing values with fallback to defaults.

The validator uses subtensorClient for connection management with automatic initialization and graceful shutdown handling to prevent resource leaks.

Sources: config/environment.ts:154-169 , validator/index.ts:29-30