DePIN Network Coverage: The 482 Networks With No Free Public Feed
At Kairos Signal, we index Decentralized Physical Infrastructure Networks (DePINs) for a living. Our ingestion pipeline currently catalogs 745 distinct DePIN networks across compute, storage, wireless, sensor, and energy verticals. That number sounds impressive on a pitch deck. The reality beneath it is far less so.
Of those 745 networks:
| Category | Count | Share | |---|---|---| | Networks with live, accessible data feeds | 263 | 35.3% | | Networks with first-party supply telemetry (node counts, geographic distribution, uptime) | 171 | 22.9% | | Networks with no free public feed of any kind | 482 | 64.7% |
This post is about that last row—the 482 networks that are, from a data perspective, dark matter. We cannot see them. We cannot verify them. We refuse to pretend otherwise.
---
The Coverage Function
Let us define the problem formally. A DePIN network $N_i$ is a tuple:
$$N_i = \langle \mathcal{S}_i, \mathcal{D}_i, \mathcal{A}_i, \mathcal{T}_i \rangle$$
Where:
- $\mathcal{S}_i$ is the set of supply-side nodes (physical infrastructure operators)
- $\mathcal{D}_i$ is the set of demand-side consumers
- $\mathcal{A}_i$ is the protocol-level accounting mechanism (token flows, staking, slashing)
- $\mathcal{T}_i$ is the telemetry interface—how external observers access state
For 171 of those, we can additionally access first-party supply telemetry—meaning the protocol itself exposes node-level metadata (geolocation, hardware type, version, uptime ratio) through official APIs or on-chain registries. These are the gold standard.
For the remaining 482:
$$\mathcal{T}_i = \emptyset$$
No public RPC that returns node registries. No subgraph indexing operator state. No REST endpoint. No WebSocket stream. No CSV dump on a governance forum. Nothing.
---
Why We Do Not Estimate
The industry temptation is to fill these gaps with models. Given partial data from the 263 observable networks, one could train a regressor—say, a gradient-boosted model over protocol type, token market cap, chain, and age—to predict node counts for the 482 dark networks.
We refuse. Here is the mathematical argument.
The Estimation Trap
Let $\hat{n}_i$ be the estimated node count for network $N_i$ where $\mathcal{T}_i = \emptyset$. The estimator is trained on observable networks:
$$\hat{n}_i = f(N_i \mid \theta), \quad \theta^* = \arg\min_\theta \sum_{j \in \mathcal{O}} \mathcal{L}(n_j, f(N_j \mid \theta))$$
Where $\mathcal{O}$ is the set of observable networks and $\mathcal{L}$ is some loss function. This is standard supervised learning. The problem is distribution shift: the observable set $\mathcal{O}$ is not drawn iid from the population $\mathcal{P}$ of all DePIN networks. Networks that expose telemetry are systematically different from those that do not.
Specifically, networks with $\mathcal{T}_i = \emptyset$ are more likely to be:
- Early-stage (no engineering resources for public APIs)
- Opaque by design (competitive moat through information asymmetry)
- Inactive or abandoned (no live infrastructure to expose)
- Running on private chains with no public RPC
$$\text{Bias}(\hat{n}_i) = \mathbb{E}[\hat{n}_i] - n_i, \quad i \in \mathcal{P} \setminus \mathcal{O}$$
This bias is unbounded because we have no ground truth for $n_i$ in the dark set. Cross-validation on $\mathcal{O}$ tells us nothing about generalization to $\mathcal{P} \setminus \mathcal{O}$.
Publishing $\hat{n}_i$ as if it were data creates a category error: it conflates observation with interpolation across a distributional chasm. Downstream consumers—researchers, investors, regulators—will treat these estimates as measurements, and decisions made on fictional precision are worse than decisions made on acknowledged ignorance.
---
The not_covered Map
Instead of estimation, we build a not_covered map: a formal, machine-readable specification of what our data does NOT contain.
Schema
# not_covered.toml — Kairos Signal Coverage Boundary Specification
Each entry represents a network where T_i = ∅
[[not_covered]]
network_id = "depin-00417"
name = "Helium IoT (legacy chain, pre-migration)"
chain = "solana"
category = "wireless_iot"
reason = "no_public_rpc"
last_attempted = "2025-01-08T14:32:00Z"
attempt_method = "rpc_node_registry"
notes = "Legacy Helium IoT entity registry inaccessible; migration to Solana incomplete for historical data"
[[not_covered]]
network_id = "depin-00582"
name = "Xnet WiFi"
chain = "ethereum"
category = "wireless_wifi"
reason = "no_telemetry_endpoint"
last_attempted = "2025-01-10T09:17:00Z"
attempt_method = "subgraph_query, rest_api_probe"
notes = "Token contract exists; no operator registry or supply API found"
[[not_covered]]
network_id = "depin-00691"
name = "WeatherXM"
chain = "polygon"
category = "sensor_weather"
reason = "gated_api"
last_attempted = "2025-01-12T16:45:00Z"
attempt_method = "api_docs_review, swagger_probe"
notes = "API requires API key; free tier does not expose node-level supply data"
Each entry is an assertion of ignorance with metadata about the attempt to resolve it. This is not a bug tracker; it is an epistemic boundary.
The Coverage Set Formalization
We define the coverage set $\mathcal{C}$ and its complement $\bar{\mathcal{C}}$:
$$\mathcal{C} = \{N_i \in \mathcal{P} : \mathcal{T}_i \neq \emptyset \land \text{staleness}(\mathcal{T}_i) \leq \delta_t\}$$
$$\bar{\mathcal{C}} = \mathcal{P} \setminus \mathcal{C} = \{N_i \in \mathcal{P} : \mathcal{T}_i = \emptyset \lor \text{staleness}(\mathcal{T}_i) > \delta_t\}$$
Our data products carry an explicit coverage_mask:
from dataclasses import dataclass
from enum import Enum
from typing import Optional
from datetime import datetime
class CoverageReason(Enum):
NO_PUBLIC_RPC = "no_public_rpc"
NO_TELEMETRY_ENDPOINT = "no_telemetry_endpoint"
GATED_API = "gated_api"
PRIVATE_CHAIN = "private_chain"
ABANDONED = "abandoned"
STALE_DATA = "stale_data"
RATE_LIMITED = "rate_limited"
@dataclass
class CoverageBoundary:
network_id: str
reason: CoverageReason
last_attempt: datetime
attempt_method: str
notes: Optional[str] = None
def to_null_marker(self) -> dict:
"""Convert to a null marker for downstream pipeline filtering."""
return {
"network_id": self.network_id,
"node_count": None, # Explicitly NULL, not 0
"node_count_ci_lower": None,
"node_count_ci_upper": None,
"coverage_status": "not_covered",
"reason": self.reason.value,
"epistemic_type": "null_observation" # NOT "missing_data"
}
Note the distinction: node_count is None, not 0. A zero count is a measurement ("we counted zero nodes"). A None is an assertion ("we cannot count"). These are fundamentally different propositions, and conflating them is the most common data sin in DePIN analytics.
---
Why not_covered Is More Useful Than Estimates
For Researchers
A researcher studying DePIN geographic distribution needs to know where data infrastructure is missing, not where we have fabricated numbers. Consider a study mapping wireless coverage density across Sub-Saharan Africa:
-- WRONG: Includes estimates that conflate observation with interpolation
SELECT
region,
SUM(node_count) AS total_nodes,
COUNT(*) AS network_count
FROM depin_supply_estimates
WHERE category = 'wireless'
GROUP BY region;
-- RIGHT: Separates observed from unobserved
SELECT
region,
SUM(CASE WHEN coverage_status = 'covered' THEN node_count ELSE 0 END) AS observed_nodes,
SUM(CASE WHEN coverage_status = 'covered' THEN 1 ELSE 0 END) AS observable_networks,
SUM(CASE WHEN coverage_status = 'not_covered' THEN 1 ELSE 0 END) AS dark_networks,
COUNT(*) AS total_networks,
ROUND(
SUM(CASE WHEN coverage_status = 'not_covered' THEN 1 ELSE 0 END)::FLOAT /
COUNT(*)::FLOAT,
3
) AS darkness_ratio
FROM depin_supply_with_coverage_mask
WHERE category = 'wireless'
GROUP BY region;
The second query tells you: "In West Africa, we observe 340 nodes across 12 networks, but there are