The Problem of Heterogeneous Supply Signals

Decentralized Physical Infrastructure Networks (DePINs) expose radically different supply-side telemetry depending on their resource category. A wireless sensor network like WeatherXM emits station count, geographic coverage, and data-quality metrics. A compute marketplace like Akash emits active leases, CPU/GPU core allocations, and provider uptime. Naively comparing "3,000 nodes" across these networks is meaningless—you're conflating a $120 weather station with a 64-core GPU server.

The solution requires a canonical telemetry schema that normalizes heterogeneous supply signals into commensurable units, paired with self-certifying verification URLs that allow any observer to independently audit the claimed state. This post derives that schema and applies it end-to-end.

---

Supply Telemetry: Raw Signal Decomposition

WeatherXM: Wireless Sensor Network Telemetry

WeatherXM operates a network of LoRaWAN-connected weather stations that publish atmospheric readings on-chain. The core supply telemetry is exposed via their REST API and on-chain state.

Station Registry State:

The on-chain station registry maps a station ID to a metadata struct:

struct Station {
    station_id: Bytes32,       // keccak256(mac_address, model_hash)
    owner: Address,            // staking wallet
    lat: i32,                  // latitude * 1e6 (fixed-point)
    lon: i32,                  // longitude * 1e6
    elevation: u16,             // meters
    model: StationModel,       // WS1 | WS2 | WS_PRO
    last_heartbeat: Timestamp,  // unix epoch seconds
    total_observations: u64,    // cumulative on-chain data submissions
}

The key supply metrics WeatherXM publishes are:

  • stations_registered — Total stations ever registered on-chain.
  • stations_active — Stations with last_heartbeat within $\tau_{\text{active}}$ (typically 24 hours).
  • coverage_area_km2 — Geographic area where at least one active station exists within a $r$-km radius of any point.
  • observations_total — Cumulative count of on-chain data submissions.
  • The active station ratio is:

    $$\rho_{\text{active}} = \frac{|\{s \in \mathcal{S} : t_{\text{now}} - s.\text{last\_heartbeat} < \tau_{\text{active}}\}|}{|\mathcal{S}|}$$

    Where $\mathcal{S}$ is the full station set. Empirically, $\rho_{\text{active}} \approx 0.72$ across WeatherXM's deployed base.

    Verification Endpoint:
    GET https://api.weatherxm.com/v2/network/stats
    

    Returns:

    {
      "stations_registered": 8247,
      "stations_active": 5938,
      "coverage_area_km2": 1843200,
      "observations_total": 2948103,
      "last_block": 19284756
    }
    

    The last_block field enables verification: query the chain at that block height and recompute the aggregate from the registry contract state.

    Akash: Compute Marketplace Telemetry

    Akash is a Kubernetes-based compute marketplace where providers lease containerized workloads. Supply telemetry reflects compute capacity, not sensor observations.

    Provider On-Chain State:
    type Provider struct {
        Owner       sdk.AccAddress
        HostURI     string           // e.g., "https://provider.akash.network"
        Attributes  []Attribute      // key-value capability tags
        ActiveLeaseCount   uint32
        TotalLeaseCount    uint64
    }
    
    Bid Order and Lease State:
    type Lease struct {
        LeaseID    LeaseID
        Price      sdk.Coin        // uakt per block
        State      LeaseState      // active | closed
        CreatedAt  int64
        ClosedAt   int64
    }
    

    type ResourceGroup struct { CPU uint32 // millicores (1000 = 1 core) Memory uint64 // MiB GPU uint32 // count of GPU units Storage uint64 // MiB }

    Akash publishes:

  • providers_registered — Total providers with an active certificate on-chain.
  • providers_active — Providers with ≥1 active lease.
  • compute_cores_total — Sum of CPU millicores across all active providers' declared capacity.
  • compute_cores_leased — Sum of CPU millicores currently under active lease.
  • gpu_units_total / gpu_units_leased — Same for GPU.
  • active_leases — Count of leases in state == active.
  • The utilization ratio is:

    $$\eta_{\text{compute}} = \frac{\sum_{p \in \mathcal{P}_{\text{active}}} \text{LeasedCores}(p)}{\sum_{p \in \mathcal{P}_{\text{active}}} \text{DeclaredCores}(p)}$$

    Empirically, $\eta_{\text{compute}} \approx 0.31$ — compute networks run at significantly lower utilization than sensor networks' heartbeat ratios.

    Verification Endpoint:
    GET https://api.akash.network/v1/market/stats
    

    Returns:

    {
      "providers_registered": 217,
      "providers_active": 84,
      "compute_cores_total": 168320,
      "compute_cores_leased": 52179,
      "gpu_units_total": 492,
      "gpu_units_leased": 147,
      "active_leases": 1432,
      "last_block_height": 14782934
    }
    

    ---

    Canonical Normalization Schema

    Deriving Commensurable Units

    The fundamental insight is that all DePIN supply telemetry can be decomposed into three orthogonal canonical dimensions:

    | Canonical Metric | Semantics | Unit | |---|---|---| | devices_total | Physical or virtual endpoints producing supply | count | | compute_cores_total | Processing capacity available for lease | millicores | | nodes_online | Endpoints meeting an activity threshold within $\tau$ | count |

    For WeatherXM:

    devices_total      = stations_registered
    compute_cores_total = 0  (sensor networks don't lease compute)
    nodes_online        = stations_active
    

    For Akash:

    devices_total      = providers_registered
    compute_cores_total = compute_cores_total  (as-is, in millicores)
    nodes_online        = providers_active
    

    This yields a supply vector for each network:

    $$\vec{S}_{\text{WeatherXM}} = \begin{pmatrix} 8247 \\ 0 \\ 5938 \end{pmatrix}, \quad \vec{S}_{\text{Akash}} = \begin{pmatrix} 217 \\ 168320 \\ 84 \end{pmatrix}$$

    Cross-Category Normalization: The Unit Problem

    Direct comparison of $\vec{S}$ vectors is incoherent because the units differ by orders of magnitude. We need a normalized supply index that accounts for:

  • Capital intensity — The cost to provision one unit of supply.
  • Revenue generation — The economic output per unit per unit time.
  • Liveness probability — The probability a unit remains active over interval $\Delta t$.
  • Define the capital-weighted normalized supply for dimension $d$ as:

    $$\hat{S}_d^{(n)} = \frac{S_d^{(n)}}{C_d^{(n)}} \cdot \frac{1}{\max_j \left( S_d^{(j)} / C_d^{(j)} \right)}$$

    Where $C_d^{(n)}$ is the capital cost per unit of dimension $d$ for network $n$, and the denominator normalizes across all networks in the comparison set.

    Using approximate capital costs:

    | Network | $C_{\text{devices}}$ (USD) | $C_{\text{cores}}$ (USD/millicore) | $C_{\text{online}}$ (USD) | |---|---|---|---| | WeatherXM | $120$ | $\infty$ | $120$ | | Akash | $15{,}000$ | $0.015$ | $15{,}000$ |

    For devices_total:

    $$\hat{S}_{\text{devices}}^{\text{WeatherXM}} = \frac{8247 / 120}{8247/120 + 217/15000} = \frac{68.7}{68.7 + 0.0145} = 0.9998$$

    $$\hat{S}_{\text{devices}}^{\text{Akash}} = \frac{217 / 15000}{68.7 + 0.0145} = 0.0002$$

    For compute_cores_total, WeatherXM is undefined (zero), so the index collapses to Akash alone. This is expected—cross-category comparison is only meaningful within shared dimensions.

    For nodes_online:

    $$\hat{S}_{\text{online}}^{\text{WeatherXM}} = \frac{5938 / 120}{5938/120 + 84/15000} = \frac{49.5}{49.5 + 0.0056} = 0.9999$$

    The interpretation: WeatherXM dominates the device-density dimension; Akash dominates the compute-capacity dimension. The canonical schema makes this structural difference legible rather than hiding it behind a single "node count" metric.

    Liveness-Adjusted Supply

    Raw nodes_online is a snapshot. A more robust metric integrates over time:

    $$\mathcal{L}^{(n)}(t) = \frac{1}{\Delta t} \int_{t - \Delta t}^{t} \frac{\text{nodes\_online}^{(n)}(\tau)}{\text{devices\_total}^{(n)}} \, d\tau$$

    This is the time-averaged liveness ratio. For WeatherXM, this can be approximated from heartbeat timestamps:

    ```python def compute_liveness(stations: list[Station], delta_t: int, bucket_sec: int = 3600) -> float: """Compute time-averaged liveness ratio over delta_t seconds.""" t_now = int(time.time()) buckets = (delta_t // bucket