1use alloy_core::primitives::{address, Address};
2use world_id_core::primitives::{Config, ServiceEndpoint};
3
4use crate::{error::WalletKitError, Environment, Region};
5
6pub static WORLD_ID_REGISTRY: Address =
8 address!("0x0000000000aE079eB8a274cD51c0f44a9E4d67d4");
9
10pub static STAGING_WORLD_ID_REGISTRY: Address =
12 address!("0x8556d07D75025f286fe757C7EeEceC40D54FA16D");
13
14pub static WORLD_ID_VERIFIER_STAGING: Address =
19 address!("0x703a6316c975DEabF30b637c155edD53e24657DB");
20
21pub static WORLD_ID_VERIFIER_PRODUCTION: Address =
26 address!("0x00000000009E00F9FE82CfeeBB4556686da094d7");
27
28#[must_use]
30pub fn world_id_verifier_address(environment: &Environment) -> Address {
31 match environment {
32 Environment::Staging => WORLD_ID_VERIFIER_STAGING,
33 Environment::Production => WORLD_ID_VERIFIER_PRODUCTION,
34 }
35}
36
37pub static POH_RECOVERY_AGENT_ADDRESS_STAGING: Address =
39 address!("0x8df366ed8ef894f0d1d25dc21b7e36e2d97a7140");
40
41pub static POH_RECOVERY_AGENT_ADDRESS_PRODUCTION: Address =
43 address!("0x00000000CBBA8Cb46C8CD414B62213F1B334fC59");
44
45pub(crate) fn poh_recovery_agent_address(environment: &Environment) -> Address {
46 match environment {
47 Environment::Staging => POH_RECOVERY_AGENT_ADDRESS_STAGING,
48 Environment::Production => POH_RECOVERY_AGENT_ADDRESS_PRODUCTION,
49 }
50}
51
52const OPRF_NODE_COUNT: usize = 5;
53
54fn oprf_node_urls(region: Region, environment: &Environment) -> Vec<String> {
56 let env_segment = match environment {
57 Environment::Staging => ".staging",
58 Environment::Production => "",
59 };
60
61 (0..OPRF_NODE_COUNT)
62 .map(|i| {
63 format!("https://node{i}.{region}{env_segment}.world.oprf.taceo.network")
64 })
65 .collect()
66}
67
68fn indexer_url(region: Region, environment: &Environment) -> String {
69 let domain = match environment {
70 Environment::Staging => "worldcoin.dev",
71 Environment::Production => "world.org",
72 };
73 format!("https://indexer.{region}.id-infra.{domain}")
74}
75
76fn gateway_url(environment: &Environment) -> String {
77 match environment {
78 Environment::Staging => "https://gateway.id-infra.worldcoin.dev".to_string(),
79 Environment::Production => "https://gateway.id-infra.world.org".to_string(),
80 }
81}
82
83fn ohttp_relay_url(region: Region, environment: &Environment) -> String {
84 let path = match environment {
85 Environment::Staging => format!("{region}-world-id-stage"),
86 Environment::Production => format!("{region}-world-id"),
87 };
88 let host = match environment {
89 Environment::Staging => "staging.privacy-relay.cloudflare.com",
90 Environment::Production => "privacy-relay.cloudflare.com",
91 };
92 format!("https://{host}/{path}")
93}
94
95const OHTTP_KEY_CONFIG_STAGING_US: &str = include_str!("ohttp_keys/staging_us.b64");
99const OHTTP_KEY_CONFIG_STAGING_EU: &str = include_str!("ohttp_keys/staging_eu.b64");
100const OHTTP_KEY_CONFIG_STAGING_AP: &str = include_str!("ohttp_keys/staging_ap.b64");
101const OHTTP_KEY_CONFIG_PRODUCTION_US: &str =
102 include_str!("ohttp_keys/production_us.b64");
103const OHTTP_KEY_CONFIG_PRODUCTION_EU: &str =
104 include_str!("ohttp_keys/production_eu.b64");
105const OHTTP_KEY_CONFIG_PRODUCTION_AP: &str =
106 include_str!("ohttp_keys/production_ap.b64");
107
108const fn ohttp_key_config(region: Region, environment: &Environment) -> &'static str {
109 match (environment, region) {
110 (Environment::Staging, Region::Us) => OHTTP_KEY_CONFIG_STAGING_US,
111 (Environment::Staging, Region::Eu) => OHTTP_KEY_CONFIG_STAGING_EU,
112 (Environment::Staging, Region::Ap) => OHTTP_KEY_CONFIG_STAGING_AP,
113 (Environment::Production, Region::Us) => OHTTP_KEY_CONFIG_PRODUCTION_US,
114 (Environment::Production, Region::Eu) => OHTTP_KEY_CONFIG_PRODUCTION_EU,
115 (Environment::Production, Region::Ap) => OHTTP_KEY_CONFIG_PRODUCTION_AP,
116 }
117}
118
119fn ohttp_endpoint(
120 url: String,
121 region: Region,
122 environment: &Environment,
123) -> ServiceEndpoint {
124 ServiceEndpoint::ohttp(
125 url,
126 ohttp_relay_url(region, environment),
127 ohttp_key_config(region, environment).to_string(),
128 )
129}
130
131fn build_config(
132 environment: &Environment,
133 rpc_url: Option<String>,
134 region: Region,
135 indexer: ServiceEndpoint,
136 gateway: ServiceEndpoint,
137) -> Result<Config, WalletKitError> {
138 let (chain_id, registry_address) = match environment {
139 Environment::Staging => (480, STAGING_WORLD_ID_REGISTRY),
141 Environment::Production => (480, WORLD_ID_REGISTRY),
142 };
143
144 Config::new(
145 rpc_url,
146 chain_id,
147 registry_address,
148 indexer,
149 gateway,
150 oprf_node_urls(region, environment),
151 3,
152 )
153 .map_err(WalletKitError::from)
154}
155
156pub fn default_config(
164 environment: &Environment,
165 rpc_url: Option<String>,
166 region: Option<Region>,
167) -> Result<Config, WalletKitError> {
168 let region = region.unwrap_or_default();
169 let indexer = ServiceEndpoint::direct(indexer_url(region, environment));
170 let gateway = ServiceEndpoint::direct(gateway_url(environment));
171 build_config(environment, rpc_url, region, indexer, gateway)
172}
173
174pub fn default_config_with_ohttp(
187 environment: &Environment,
188 rpc_url: Option<String>,
189 region: Option<Region>,
190) -> Result<Config, WalletKitError> {
191 let region = region.unwrap_or_default();
192 let indexer = ohttp_endpoint(indexer_url(region, environment), region, environment);
193 let gateway = ohttp_endpoint(gateway_url(environment), Region::Us, environment);
194 build_config(environment, rpc_url, region, indexer, gateway)
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 const ALL_ENVS: &[Environment] = &[Environment::Staging, Environment::Production];
202 const ALL_REGIONS: &[Region] = &[Region::Us, Region::Eu, Region::Ap];
203
204 #[test]
205 fn default_config_builds_direct_endpoints_for_every_env_and_region() {
206 for env in ALL_ENVS {
207 for region in ALL_REGIONS {
208 let config = default_config(env, None, Some(*region))
209 .expect("default_config should succeed");
210
211 assert!(
212 matches!(config.indexer(), ServiceEndpoint::Direct { .. }),
213 "indexer should be Direct for env={env:?} region={region:?}"
214 );
215 assert!(
216 matches!(config.gateway(), ServiceEndpoint::Direct { .. }),
217 "gateway should be Direct for env={env:?} region={region:?}"
218 );
219 assert_eq!(config.indexer_url(), indexer_url(*region, env));
220 assert_eq!(config.gateway_url(), gateway_url(env));
221 }
222 }
223 }
224
225 #[test]
226 fn default_config_with_ohttp_builds_ohttp_endpoints_with_region_specific_relays() {
227 for env in ALL_ENVS {
228 for region in ALL_REGIONS {
229 let config = default_config_with_ohttp(env, None, Some(*region))
230 .expect("default_config_with_ohttp should succeed");
231
232 match config.indexer() {
233 ServiceEndpoint::Ohttp {
234 url,
235 relay_url,
236 key_config_base64,
237 } => {
238 assert_eq!(url, &indexer_url(*region, env));
239 assert_eq!(relay_url, &ohttp_relay_url(*region, env));
240 assert_eq!(key_config_base64, ohttp_key_config(*region, env));
241 }
242 direct @ ServiceEndpoint::Direct { .. } => panic!(
243 "indexer should be Ohttp for env={env:?} region={region:?}, got {direct:?}"
244 ),
245 }
246 }
247 }
248 }
249
250 #[test]
251 fn default_config_with_ohttp_pins_gateway_to_us_relay_regardless_of_region() {
252 for env in ALL_ENVS {
253 for region in ALL_REGIONS {
254 let config = default_config_with_ohttp(env, None, Some(*region))
255 .expect("default_config_with_ohttp should succeed");
256
257 match config.gateway() {
258 ServiceEndpoint::Ohttp {
259 url,
260 relay_url,
261 key_config_base64,
262 } => {
263 assert_eq!(url, &gateway_url(env));
264 assert_eq!(relay_url, &ohttp_relay_url(Region::Us, env));
265 assert_eq!(
266 key_config_base64,
267 ohttp_key_config(Region::Us, env)
268 );
269 }
270 direct @ ServiceEndpoint::Direct { .. } => panic!(
271 "gateway should be Ohttp for env={env:?} region={region:?}, got {direct:?}"
272 ),
273 }
274 }
275 }
276 }
277
278 #[test]
279 fn default_config_defaults_region_when_not_specified() {
280 let with_default = default_config(&Environment::Staging, None, None).unwrap();
281 let with_explicit_default =
282 default_config(&Environment::Staging, None, Some(Region::default()))
283 .unwrap();
284 assert_eq!(
285 with_default.indexer_url(),
286 with_explicit_default.indexer_url(),
287 );
288 }
289
290 #[test]
291 fn default_config_with_ohttp_defaults_region_when_not_specified() {
292 let with_default =
293 default_config_with_ohttp(&Environment::Staging, None, None).unwrap();
294 let with_explicit_default = default_config_with_ohttp(
295 &Environment::Staging,
296 None,
297 Some(Region::default()),
298 )
299 .unwrap();
300 assert_eq!(
301 with_default.indexer_url(),
302 with_explicit_default.indexer_url(),
303 );
304 }
305
306 #[test]
307 fn both_builders_round_trip_through_config_json() {
308 for env in ALL_ENVS {
309 for region in ALL_REGIONS {
310 let direct = default_config(env, None, Some(*region)).unwrap();
311 let direct_json = serde_json::to_string(&direct).unwrap();
312 let direct_parsed = Config::from_json(&direct_json).unwrap();
313 assert!(matches!(
314 direct_parsed.indexer(),
315 ServiceEndpoint::Direct { .. }
316 ));
317 assert!(matches!(
318 direct_parsed.gateway(),
319 ServiceEndpoint::Direct { .. }
320 ));
321
322 let ohttp =
323 default_config_with_ohttp(env, None, Some(*region)).unwrap();
324 let ohttp_json = serde_json::to_string(&ohttp).unwrap();
325 let ohttp_parsed = Config::from_json(&ohttp_json).unwrap();
326 assert!(matches!(
327 ohttp_parsed.indexer(),
328 ServiceEndpoint::Ohttp { .. }
329 ));
330 assert!(matches!(
331 ohttp_parsed.gateway(),
332 ServiceEndpoint::Ohttp { .. }
333 ));
334 }
335 }
336 }
337}