Geographic-aware route provider for the Internet Computer. Ensures IC requests only go through API boundary nodes located in specific regions.
The Internet Computer's ic-agent routes requests to API boundary nodes
without geographic awareness. For data sovereignty (EU/Swiss compliance,
GDPR, FINMA), you may need to guarantee that all IC traffic stays within
a specific jurisdiction. This crate solves that by filtering boundary
nodes based on their registered data center location in the NNS registry.
NNS Registry (periodic sync)
|
v
GeoRouteProvider
1. Fetch API boundary node IDs
2. Resolve geo chain: NodeRecord -> NodeOperatorRecord -> DataCenterRecord
3. Filter by region prefix (e.g. "Europe,CH")
4. Health-check surviving nodes (GET /health)
5. Expose via RouteProvider trait
|
v
ic-agent / ic-http-gateway
- route() -> random healthy node in region
- n_ordered_routes -> top n healthy nodes in region
The NNS registry is the source of truth. Every API boundary node has a
NodeRecord linking to a NodeOperatorRecord linking to a
DataCenterRecord with a region string (e.g. "Europe,CH,Zurich")
and GPS coordinates. No GeoIP heuristics are needed.
A background task re-syncs from the registry and health-checks nodes on configurable intervals.
Important: This controls which API boundary node handles your request, not which subnet replicas execute it. If the target canister lives on a subnet whose replica nodes are outside your filtered region, the boundary node will still forward the request to those replicas. To achieve full data sovereignty, the canister itself must be deployed on a subnet with nodes in the desired region.
Add to Cargo.toml:
[dependencies]
ic-geo-route-provider = { path = "." } # or git/crates.io once published
ic-agent = "0.46"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
url = "2"use ic_agent::Agent;
use ic_geo_route_provider::GeoRouteProviderBuilder;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider = GeoRouteProviderBuilder::new()
.with_seed_urls(vec!["https://icp-api.io".parse()?])
.with_region_filter(vec!["Europe,CH".into()]) // Swiss-only
.build()
.await?;
let agent = Agent::builder()
.with_arc_route_provider(std::sync::Arc::new(provider))
.build()?;
// All requests now go through Swiss boundary nodes
// agent.query(...).await?;
Ok(())
}use ic_agent::Agent;
use ic_http_gateway::HttpGatewayClient;
use ic_geo_route_provider::GeoRouteProviderBuilder;
let provider = GeoRouteProviderBuilder::new()
.with_seed_urls(vec!["https://icp-api.io".parse()?])
.with_region_filter(vec!["Europe,CH".into()]) // Swiss-only
.build()
.await?;
let agent = Agent::builder()
.with_arc_route_provider(std::sync::Arc::new(provider))
.build()?;
let gateway_client = HttpGatewayClient::builder()
.with_agent(agent)
.build()?;
// All gateway requests now route through Swiss boundary nodes| Filter | Matches |
|---|---|
["Europe,CH"] |
Swiss nodes only (Geneva, Zurich) |
["Europe"] |
All European nodes (CH, DE, RO, BE, ...) |
["Europe,CH", "Europe,DE"] |
Swiss + German nodes |
["North America,US"] |
US nodes only |
Region strings use comma-separated hierarchies matching the NNS registry
DataCenterRecord.region field. Filtering is case-insensitive prefix
matching.
GeoRouteProviderBuilder::new()
.with_seed_urls(urls) // Required: bootstrap URLs for registry queries
.with_region_filter(filters) // Required: region prefix list
.with_sync_interval(dur) // Registry re-sync interval (default: 60s)
.with_health_check_interval(dur) // Health check interval (default: 10s)
.build()
.await?;| Region | Nodes |
|---|---|
| Europe,CH (Swiss) | 3 (Geneva, Zurich x2) |
| Europe (all) | 6 (CH, DE, RO, BE) |
| North America | 8 (US, CA) |
| Asia | 5 (IN, KR, SG) |
| Africa | 1 (ZA) |
# Unit tests
cargo test
# Integration tests (hits IC mainnet)
cargo test --test mainnet -- --nocapture --ignoredMPL-2.0