use alloy_core::primitives::{address, Address};
use world_id_core::primitives::{Config, ServiceEndpoint};
use crate::{error::WalletKitError, Environment, Region};
pub static WORLD_ID_REGISTRY: Address =
address!("0x0000000000aE079eB8a274cD51c0f44a9E4d67d4");
pub static STAGING_WORLD_ID_REGISTRY: Address =
address!("0x8556d07D75025f286fe757C7EeEceC40D54FA16D");
pub static POH_RECOVERY_AGENT_ADDRESS_STAGING: Address =
address!("0x8df366ed8ef894f0d1d25dc21b7e36e2d97a7140");
pub static POH_RECOVERY_AGENT_ADDRESS_PRODUCTION: Address =
address!("0x00000000CBBA8Cb46C8CD414B62213F1B334fC59");
pub(crate) fn poh_recovery_agent_address(environment: &Environment) -> Address {
match environment {
Environment::Staging => POH_RECOVERY_AGENT_ADDRESS_STAGING,
Environment::Production => POH_RECOVERY_AGENT_ADDRESS_PRODUCTION,
}
}
const OPRF_NODE_COUNT: usize = 5;
fn oprf_node_urls(region: Region, environment: &Environment) -> Vec<String> {
let env_segment = match environment {
Environment::Staging => ".staging",
Environment::Production => "",
};
(0..OPRF_NODE_COUNT)
.map(|i| {
format!("https://node{i}.{region}{env_segment}.world.oprf.taceo.network")
})
.collect()
}
fn indexer_url(region: Region, environment: &Environment) -> String {
let domain = match environment {
Environment::Staging => "worldcoin.dev",
Environment::Production => "world.org",
};
format!("https://indexer.{region}.id-infra.{domain}")
}
fn gateway_url(environment: &Environment) -> String {
match environment {
Environment::Staging => "https://gateway.id-infra.worldcoin.dev".to_string(),
Environment::Production => "https://gateway.id-infra.world.org".to_string(),
}
}
fn ohttp_relay_url(region: Region, environment: &Environment) -> String {
let path = match environment {
Environment::Staging => format!("{region}-world-id-stage"),
Environment::Production => format!("{region}-world-id"),
};
let host = match environment {
Environment::Staging => "staging.privacy-relay.cloudflare.com",
Environment::Production => "privacy-relay.cloudflare.com",
};
format!("https://{host}/{path}")
}
const OHTTP_KEY_CONFIG_STAGING_US: &str = include_str!("ohttp_keys/staging_us.b64");
const OHTTP_KEY_CONFIG_STAGING_EU: &str = include_str!("ohttp_keys/staging_eu.b64");
const OHTTP_KEY_CONFIG_STAGING_AP: &str = include_str!("ohttp_keys/staging_ap.b64");
const OHTTP_KEY_CONFIG_PRODUCTION_US: &str =
include_str!("ohttp_keys/production_us.b64");
const OHTTP_KEY_CONFIG_PRODUCTION_EU: &str =
include_str!("ohttp_keys/production_eu.b64");
const OHTTP_KEY_CONFIG_PRODUCTION_AP: &str =
include_str!("ohttp_keys/production_ap.b64");
const fn ohttp_key_config(region: Region, environment: &Environment) -> &'static str {
match (environment, region) {
(Environment::Staging, Region::Us) => OHTTP_KEY_CONFIG_STAGING_US,
(Environment::Staging, Region::Eu) => OHTTP_KEY_CONFIG_STAGING_EU,
(Environment::Staging, Region::Ap) => OHTTP_KEY_CONFIG_STAGING_AP,
(Environment::Production, Region::Us) => OHTTP_KEY_CONFIG_PRODUCTION_US,
(Environment::Production, Region::Eu) => OHTTP_KEY_CONFIG_PRODUCTION_EU,
(Environment::Production, Region::Ap) => OHTTP_KEY_CONFIG_PRODUCTION_AP,
}
}
fn ohttp_endpoint(
url: String,
region: Region,
environment: &Environment,
) -> ServiceEndpoint {
ServiceEndpoint::ohttp(
url,
ohttp_relay_url(region, environment),
ohttp_key_config(region, environment).to_string(),
)
}
fn build_config(
environment: &Environment,
rpc_url: Option<String>,
region: Region,
indexer: ServiceEndpoint,
gateway: ServiceEndpoint,
) -> Result<Config, WalletKitError> {
let (chain_id, registry_address) = match environment {
Environment::Staging => (480, STAGING_WORLD_ID_REGISTRY),
Environment::Production => (480, WORLD_ID_REGISTRY),
};
Config::new(
rpc_url,
chain_id,
registry_address,
indexer,
gateway,
oprf_node_urls(region, environment),
3,
)
.map_err(WalletKitError::from)
}
pub fn default_config(
environment: &Environment,
rpc_url: Option<String>,
region: Option<Region>,
) -> Result<Config, WalletKitError> {
let region = region.unwrap_or_default();
let indexer = ServiceEndpoint::direct(indexer_url(region, environment));
let gateway = ServiceEndpoint::direct(gateway_url(environment));
build_config(environment, rpc_url, region, indexer, gateway)
}
pub fn default_config_with_ohttp(
environment: &Environment,
rpc_url: Option<String>,
region: Option<Region>,
) -> Result<Config, WalletKitError> {
let region = region.unwrap_or_default();
let indexer = ohttp_endpoint(indexer_url(region, environment), region, environment);
let gateway = ohttp_endpoint(gateway_url(environment), Region::Us, environment);
build_config(environment, rpc_url, region, indexer, gateway)
}
#[cfg(test)]
mod tests {
use super::*;
const ALL_ENVS: &[Environment] = &[Environment::Staging, Environment::Production];
const ALL_REGIONS: &[Region] = &[Region::Us, Region::Eu, Region::Ap];
#[test]
fn default_config_builds_direct_endpoints_for_every_env_and_region() {
for env in ALL_ENVS {
for region in ALL_REGIONS {
let config = default_config(env, None, Some(*region))
.expect("default_config should succeed");
assert!(
matches!(config.indexer(), ServiceEndpoint::Direct { .. }),
"indexer should be Direct for env={env:?} region={region:?}"
);
assert!(
matches!(config.gateway(), ServiceEndpoint::Direct { .. }),
"gateway should be Direct for env={env:?} region={region:?}"
);
assert_eq!(config.indexer_url(), indexer_url(*region, env));
assert_eq!(config.gateway_url(), gateway_url(env));
}
}
}
#[test]
fn default_config_with_ohttp_builds_ohttp_endpoints_with_region_specific_relays() {
for env in ALL_ENVS {
for region in ALL_REGIONS {
let config = default_config_with_ohttp(env, None, Some(*region))
.expect("default_config_with_ohttp should succeed");
match config.indexer() {
ServiceEndpoint::Ohttp {
url,
relay_url,
key_config_base64,
} => {
assert_eq!(url, &indexer_url(*region, env));
assert_eq!(relay_url, &ohttp_relay_url(*region, env));
assert_eq!(key_config_base64, ohttp_key_config(*region, env));
}
direct @ ServiceEndpoint::Direct { .. } => panic!(
"indexer should be Ohttp for env={env:?} region={region:?}, got {direct:?}"
),
}
}
}
}
#[test]
fn default_config_with_ohttp_pins_gateway_to_us_relay_regardless_of_region() {
for env in ALL_ENVS {
for region in ALL_REGIONS {
let config = default_config_with_ohttp(env, None, Some(*region))
.expect("default_config_with_ohttp should succeed");
match config.gateway() {
ServiceEndpoint::Ohttp {
url,
relay_url,
key_config_base64,
} => {
assert_eq!(url, &gateway_url(env));
assert_eq!(relay_url, &ohttp_relay_url(Region::Us, env));
assert_eq!(
key_config_base64,
ohttp_key_config(Region::Us, env)
);
}
direct @ ServiceEndpoint::Direct { .. } => panic!(
"gateway should be Ohttp for env={env:?} region={region:?}, got {direct:?}"
),
}
}
}
}
#[test]
fn default_config_defaults_region_when_not_specified() {
let with_default = default_config(&Environment::Staging, None, None).unwrap();
let with_explicit_default =
default_config(&Environment::Staging, None, Some(Region::default()))
.unwrap();
assert_eq!(
with_default.indexer_url(),
with_explicit_default.indexer_url(),
);
}
#[test]
fn default_config_with_ohttp_defaults_region_when_not_specified() {
let with_default =
default_config_with_ohttp(&Environment::Staging, None, None).unwrap();
let with_explicit_default = default_config_with_ohttp(
&Environment::Staging,
None,
Some(Region::default()),
)
.unwrap();
assert_eq!(
with_default.indexer_url(),
with_explicit_default.indexer_url(),
);
}
#[test]
fn both_builders_round_trip_through_config_json() {
for env in ALL_ENVS {
for region in ALL_REGIONS {
let direct = default_config(env, None, Some(*region)).unwrap();
let direct_json = serde_json::to_string(&direct).unwrap();
let direct_parsed = Config::from_json(&direct_json).unwrap();
assert!(matches!(
direct_parsed.indexer(),
ServiceEndpoint::Direct { .. }
));
assert!(matches!(
direct_parsed.gateway(),
ServiceEndpoint::Direct { .. }
));
let ohttp =
default_config_with_ohttp(env, None, Some(*region)).unwrap();
let ohttp_json = serde_json::to_string(&ohttp).unwrap();
let ohttp_parsed = Config::from_json(&ohttp_json).unwrap();
assert!(matches!(
ohttp_parsed.indexer(),
ServiceEndpoint::Ohttp { .. }
));
assert!(matches!(
ohttp_parsed.gateway(),
ServiceEndpoint::Ohttp { .. }
));
}
}
}
}