world-id-primitives 0.9.0

Contains the raw base primitives (without implementations) for the World ID Protocol.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
#![allow(clippy::option_if_let_else)]
//! Shared API request/response payloads for gateway and indexer services.
//!
//! Numeric fields that use the `hex_u*` serde helpers follow this contract:
//! - requests accept decimal strings (no prefix) or hex strings with `0x`/`0X` prefix;
//! - responses are serialized as canonical `0x`-prefixed hex strings.

pub use crate::merkle::AccountInclusionProof;
use crate::serde_utils::{
    hex_signature, hex_u32, hex_u32_opt, hex_u64, hex_u256, hex_u256_opt, hex_u256_opt_vec,
    hex_u256_vec,
};
use alloy_primitives::{Address, Signature};
use ruint::aliases::U256;
use serde::{Deserialize, Serialize};
use strum::EnumString;

#[cfg(feature = "openapi")]
use utoipa::{IntoParams, ToSchema};

/// The request to create a new World ID account.
///
/// Numeric string fields in this request accept decimal or `0x`/`0X`-prefixed hex.
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateAccountRequest {
    /// The recovery address.
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub recovery_address: Option<Address>,
    /// The addresses of the authenticators.
    #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>, format = "hex"))]
    pub authenticator_addresses: Vec<Address>,
    /// The compressed public keys of the authenticators.
    #[serde(with = "hex_u256_vec")]
    #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>, format = "hex"))]
    pub authenticator_pubkeys: Vec<U256>,
    /// The offchain signer commitment.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub offchain_signer_commitment: U256,
}

/// The request to update an authenticator.
///
/// Numeric string fields in this request accept decimal or `0x`/`0X`-prefixed hex.
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateAuthenticatorRequest {
    /// The account index.
    #[serde(with = "hex_u64")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub leaf_index: u64,
    /// The old authenticator address.
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub old_authenticator_address: Address,
    /// The new authenticator address.
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub new_authenticator_address: Address,
    /// The old offchain signer commitment.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub old_offchain_signer_commitment: U256,
    /// The new offchain signer commitment.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub new_offchain_signer_commitment: U256,
    /// The signature.
    #[serde(with = "hex_signature")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub signature: Signature,
    /// The nonce.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub nonce: U256,
    /// The pubkey id.
    #[serde(with = "hex_u32")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub pubkey_id: u32,
    /// The new authenticator pubkey.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub new_authenticator_pubkey: U256,
}

/// The request to insert an authenticator.
///
/// Numeric string fields in this request accept decimal or `0x`/`0X`-prefixed hex.
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[derive(Debug, Serialize, Deserialize)]
pub struct InsertAuthenticatorRequest {
    /// The account index.
    #[serde(with = "hex_u64")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub leaf_index: u64,
    /// The new authenticator address.
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub new_authenticator_address: Address,
    /// The old offchain signer commitment.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub old_offchain_signer_commitment: U256,
    /// The new offchain signer commitment.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub new_offchain_signer_commitment: U256,
    /// The signature.
    #[serde(with = "hex_signature")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub signature: Signature,
    /// The nonce.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub nonce: U256,
    /// The pubkey id.
    #[serde(with = "hex_u32")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub pubkey_id: u32,
    /// The new authenticator pubkey.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub new_authenticator_pubkey: U256,
}

/// The request to remove an authenticator.
///
/// Numeric string fields in this request accept decimal or `0x`/`0X`-prefixed hex.
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[derive(Debug, Serialize, Deserialize)]
pub struct RemoveAuthenticatorRequest {
    /// The account index.
    #[serde(with = "hex_u64")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub leaf_index: u64,
    /// The authenticator address.
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub authenticator_address: Address,
    /// The old offchain signer commitment.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub old_offchain_signer_commitment: U256,
    /// The new offchain signer commitment.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub new_offchain_signer_commitment: U256,
    /// The signature.
    #[serde(with = "hex_signature")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub signature: Signature,
    /// The nonce.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub nonce: U256,
    /// The pubkey id.
    #[serde(default, with = "hex_u32_opt")]
    #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "hex"))]
    pub pubkey_id: Option<u32>,
    /// The authenticator pubkey.
    #[serde(default, with = "hex_u256_opt")]
    #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "hex"))]
    pub authenticator_pubkey: Option<U256>,
}

/// The request to update a recovery agent.
///
/// Numeric string fields in this request accept decimal or `0x`/`0X`-prefixed hex.
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateRecoveryAgentRequest {
    /// The account index.
    #[serde(with = "hex_u64")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub leaf_index: u64,
    /// The new recovery agent address.
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub new_recovery_agent: Address,
    /// The signature.
    #[serde(with = "hex_signature")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub signature: Signature,
    /// The nonce.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub nonce: U256,
}

/// The request to execute a pending recovery agent update.
///
/// No signature is required — `executeRecoveryAgentUpdate` is permissionless.
/// The contract enforces the 14-day cooldown and will revert with
/// `RecoveryAgentUpdateStillInCooldown` if called too early.
///
/// Numeric string fields in this request accept decimal or `0x`/`0X`-prefixed hex.
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[derive(Debug, Serialize, Deserialize)]
pub struct ExecuteRecoveryAgentUpdateRequest {
    /// The account index.
    #[serde(with = "hex_u64")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub leaf_index: u64,
}

/// The request to cancel a pending recovery agent update.
///
/// Numeric string fields in this request accept decimal or `0x`/`0X`-prefixed hex.
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[derive(Debug, Serialize, Deserialize)]
pub struct CancelRecoveryAgentUpdateRequest {
    /// The account index.
    #[serde(with = "hex_u64")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub leaf_index: u64,
    /// The signature.
    #[serde(with = "hex_signature")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub signature: Signature,
    /// The nonce.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub nonce: U256,
}

/// The request to recover an account.
///
/// Numeric string fields in this request accept decimal or `0x`/`0X`-prefixed hex.
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[derive(Debug, Serialize, Deserialize)]
pub struct RecoverAccountRequest {
    /// The account index.
    #[serde(with = "hex_u64")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub leaf_index: u64,
    /// The new authenticator address.
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub new_authenticator_address: Address,
    /// The old offchain signer commitment.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub old_offchain_signer_commitment: U256,
    /// The new offchain signer commitment.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub new_offchain_signer_commitment: U256,
    /// The signature.
    #[serde(with = "hex_signature")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub signature: Signature,
    /// The nonce.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub nonce: U256,
    /// The new authenticator pubkey.
    #[serde(default, with = "hex_u256_opt")]
    #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, format = "hex"))]
    pub new_authenticator_pubkey: Option<U256>,
}

/// Strongly-typed identifier for a gateway request.
///
/// Returned by gateway mutation endpoints (create-account, insert/update/remove
/// authenticator, etc.) and used to poll the status of the request.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(transparent)]
pub struct GatewayRequestId(String);

impl GatewayRequestId {
    /// Creates a new `GatewayRequestId` from a raw string by prepending the
    /// `gw_` prefix.
    pub fn new(id: impl Into<String>) -> Self {
        Self(format!("gw_{}", id.into()))
    }

    /// Returns the underlying string representation.
    #[must_use]
    pub fn as_str(&self) -> &str {
        &self.0
    }

    /// Returns the underlying string representation without the `gw_` prefix,
    /// if present.
    #[must_use]
    pub fn as_str_without_prefix(&self) -> &str {
        self.0.strip_prefix("gw_").unwrap_or(&self.0)
    }
}

impl std::fmt::Display for GatewayRequestId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.0)
    }
}

impl AsRef<str> for GatewayRequestId {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

/// Response returned by the registry gateway for state-changing requests.
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct GatewayStatusResponse {
    /// Identifier assigned by the gateway to the submitted request.
    pub request_id: GatewayRequestId,
    /// The kind of operation that was submitted.
    pub kind: GatewayRequestKind,
    /// The current state of the request.
    pub status: GatewayRequestState,
}

/// Kind of request tracked by the registry gateway.
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "snake_case")]
pub enum GatewayRequestKind {
    /// Account creation request.
    CreateAccount,
    /// Authenticator update request.
    UpdateAuthenticator,
    /// Authenticator insertion request.
    InsertAuthenticator,
    /// Authenticator removal request.
    RemoveAuthenticator,
    /// Recovery agent update initiation request.
    UpdateRecoveryAgent,
    /// Recovery agent update cancellation request.
    CancelRecoveryAgentUpdate,
    /// Recovery agent update execution request.
    ExecuteRecoveryAgentUpdate,
    /// Account recovery request.
    RecoverAccount,
}

/// Tracking state for a registry gateway request.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(tag = "state", rename_all = "snake_case")]
pub enum GatewayRequestState {
    /// Request queued but not yet batched.
    Queued,
    /// Request currently being batched.
    Batching,
    /// Request submitted on-chain, hash available.
    Submitted {
        /// Transaction hash emitted when the request was submitted.
        tx_hash: String,
    },
    /// Request finalized on-chain.
    Finalized {
        /// Transaction hash emitted when the request was finalized.
        tx_hash: String,
    },
    /// Request failed during processing.
    Failed {
        /// Error message returned by the gateway.
        error: String,
        /// Specific error code, if available.
        #[serde(skip_serializing_if = "Option::is_none", default)]
        error_code: Option<GatewayErrorCode>,
    },
}

impl GatewayRequestState {
    /// Creates a failed state with an error message and optional error code.
    pub fn failed(error: impl Into<String>, error_code: Option<GatewayErrorCode>) -> Self {
        Self::Failed {
            error: error.into(),
            error_code,
        }
    }

    /// Creates a failed state from an error code (uses the code's display as the message).
    #[must_use]
    pub fn failed_from_code(code: GatewayErrorCode) -> Self {
        Self::Failed {
            error: code.to_string(),
            error_code: Some(code),
        }
    }
}

/// Request to fetch a packed account index from the indexer.
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct IndexerPackedAccountRequest {
    /// The authenticator address to look up
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex", example = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"))]
    pub authenticator_address: Address,
}

/// Response containing the packed account index from the indexer.
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct IndexerPackedAccountResponse {
    /// The packed account data [32 bits recoveryCounter][32 bits pubkeyId][192 bits leafIndex].
    ///
    /// Serialized as a canonical `0x`-prefixed hex string.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex", example = "0x1"))]
    pub packed_account_data: U256,
}

/// Query for the indexer based on a leaf index.
///
/// Used for getting inclusion proofs and signature nonces.
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct IndexerQueryRequest {
    /// The leaf index to query (from the `WorldIDRegistry`).
    ///
    /// Accepts decimal or `0x`/`0X`-prefixed hex input.
    #[serde(with = "hex_u64")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex", example = "0x1"))]
    pub leaf_index: u64,
}

/// Response containing the signature nonce from the indexer.
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct IndexerSignatureNonceResponse {
    /// The signature nonce for the account.
    ///
    /// Serialized as a canonical `0x`-prefixed hex string.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex", example = "0x0"))]
    pub signature_nonce: U256,
}

/// Response containing the recovery agent from the indexer.
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct IndexerRecoveryAgentResponse {
    /// The recovery agent for the World ID.
    ///
    /// Please note this may be the zero address if no recovery agent is set for the account.
    ///
    /// Serialized as a canonical `0x`-prefixed hex string.
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex", example = "0x0"))]
    pub recovery_agent: Address,
}

/// Response containing the pending recovery agent from the indexer.
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct IndexerPendingRecoveryAgentResponse {
    /// The pending recovery agent for the World ID.
    ///
    /// Please note this may be the zero address if no recovery agent update is pending.
    ///
    /// Serialized as a canonical `0x`-prefixed hex string.
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex", example = "0x0"))]
    pub pending_recovery_agent: Address,

    /// The earliest timestamp at which the pending recovery agent update may be executed.
    ///
    /// Please note this may be zero if no recovery agent update is pending.
    ///
    /// Serialized as a canonical `0x`-prefixed hex string.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex", example = "0x0"))]
    pub execute_after: U256,
}

/// Response containing authenticator public keys for an account from the indexer.
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct IndexerAuthenticatorPubkeysResponse {
    /// The compressed authenticator public keys for the account.
    ///
    /// Entries may be `null` for removed authenticator slots to preserve `pubkey_id` positions.
    #[serde(with = "hex_u256_opt_vec")]
    #[cfg_attr(feature = "openapi", schema(value_type = Vec<Option<String>>, format = "hex"))]
    pub authenticator_pubkeys: Vec<Option<U256>>,

    /// The commitment to all the authenticator pubkeys. This commitment is
    /// stored in the `WorldIDRegistry`.
    #[serde(with = "hex_u256")]
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub offchain_signer_commitment: U256,
}

/// Health response for an API service (gateway or indexer).
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct HealthResponse {
    /// Success value.
    pub success: bool,
}

/// Query params for the `/is-valid-root` endpoint.
#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "openapi", derive(IntoParams, ToSchema))]
pub struct IsValidRootQuery {
    /// Root to validate (hex string).
    #[cfg_attr(feature = "openapi", schema(value_type = String, format = "hex"))]
    pub root: String,
}

/// Response payload for root validity checks.
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[derive(Debug, Serialize, Deserialize)]
pub struct IsValidRootResponse {
    /// Whether the root is currently valid on-chain.
    pub valid: bool,
}

/// Indexer error codes.
#[derive(Debug, Clone, EnumString, Serialize, Deserialize, strum::Display)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum IndexerErrorCode {
    /// Internal server error occurred in the indexer.
    InternalServerError,
    /// Requested resource was not found.
    NotFound,
    /// The provided leaf index is invalid.
    InvalidLeafIndex,
    /// The resource is locked and cannot be accessed.
    Locked,
    /// The account does not exist.
    AccountDoesNotExist,
    /// The request timed out.
    RequestTimeout,
}

/// Gateway error codes.
#[derive(Debug, Clone, Deserialize, Serialize, strum::Display)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum GatewayErrorCode {
    /// Internal server error occurred in the gateway.
    InternalServerError,
    /// Requested resource was not found.
    NotFound,
    /// Bad request - invalid input.
    BadRequest,
    /// Batcher service unavailable.
    BatcherUnavailable,
    /// Authenticator address is already in use by another account.
    AuthenticatorAlreadyExists,
    /// Authenticator does not exist on the account.
    AuthenticatorDoesNotExist,
    /// The signature nonce does not match the expected value.
    MismatchedSignatureNonce,
    /// The pubkey ID slot is already in use.
    PubkeyIdInUse,
    /// The pubkey ID is out of bounds (max 7 authenticators).
    PubkeyIdOutOfBounds,
    /// The authenticator does not belong to the specified account.
    AuthenticatorDoesNotBelongToAccount,
    /// Transaction was submitted but reverted on-chain.
    TransactionReverted,
    /// Error while waiting for transaction confirmation.
    ConfirmationError,
    /// A request with the same authenticator address is already being processed.
    DuplicateRequestInFlight,
    /// Rate limit exceeded for this leaf_index.
    RateLimitExceeded,
    /// The request timed out.
    RequestTimeout,
}

/// Error object returned by the services APIs (indexer, gateway).
#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct ServiceApiError<T>
where
    T: Clone,
{
    /// The error code.
    pub code: T,
    /// Human-readable error message.
    pub message: String,
}

impl<T> ServiceApiError<T>
where
    T: Clone,
{
    /// Creates a new error object.
    pub const fn new(code: T, message: String) -> Self {
        Self { code, message }
    }
}

/// `OpenAPI` schema representation of the `AccountInclusionProof` response.
#[derive(serde::Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct AccountInclusionProofSchema {
    /// The root hash of the Merkle tree (hex string)
    #[cfg_attr(
        feature = "openapi",
        schema(value_type = String, format = "hex", example = "0x1a2b3c4d5e6f7890")
    )]
    pub root: String,
    /// The World ID's leaf position in the Merkle tree
    #[cfg_attr(
        feature = "openapi",
        schema(value_type = String, format = "hex", example = "0x2a")
    )]
    pub leaf_index: String,
    /// The sibling path up to the Merkle root (array of hex strings)
    #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>, format = "hex"))]
    pub siblings: Vec<String>,
    /// The compressed authenticator public keys for the account (array of hex strings)
    #[cfg_attr(feature = "openapi", schema(value_type = Vec<String>, format = "hex"))]
    pub authenticator_pubkeys: Vec<String>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn insert_authenticator_request_signature_serializes_as_hex_string() {
        let request = InsertAuthenticatorRequest {
            leaf_index: 42,
            new_authenticator_address: Address::from([0x11; 20]),
            old_offchain_signer_commitment: U256::from(0x1234_u64),
            new_offchain_signer_commitment: U256::from(0x5678_u64),
            signature: Signature::new(U256::from(0xdead_u64), U256::from(0xbeef_u64), false),
            nonce: U256::from(0x9abc_u64),
            pubkey_id: 7,
            new_authenticator_pubkey: U256::from(0xdef0_u64),
        };

        let json = serde_json::to_string(&request).unwrap();
        let value: serde_json::Value = serde_json::from_str(&json).unwrap();

        assert_eq!(
            value["new_authenticator_address"]
                .as_str()
                .expect("new_authenticator_address should be a string"),
            "0x1111111111111111111111111111111111111111"
        );
        assert_eq!(
            value["old_offchain_signer_commitment"]
                .as_str()
                .expect("old_offchain_signer_commitment should be a string"),
            "0x1234"
        );
        assert_eq!(
            value["new_offchain_signer_commitment"]
                .as_str()
                .expect("new_offchain_signer_commitment should be a string"),
            "0x5678"
        );
        assert_eq!(
            value["signature"]
                .as_str()
                .expect("signature should be a string"),
            "0x000000000000000000000000000000000000000000000000000000000000dead000000000000000000000000000000000000000000000000000000000000beef1b"
        );
        assert_eq!(
            value["nonce"].as_str().expect("nonce should be a string"),
            "0x9abc"
        );
        assert_eq!(
            value["new_authenticator_pubkey"]
                .as_str()
                .expect("new_authenticator_pubkey should be a string"),
            "0xdef0"
        );

        let roundtripped: InsertAuthenticatorRequest = serde_json::from_str(&json).unwrap();
        assert_eq!(roundtripped.leaf_index, request.leaf_index);
        assert_eq!(
            roundtripped.new_authenticator_address,
            request.new_authenticator_address
        );
        assert_eq!(
            roundtripped.old_offchain_signer_commitment,
            request.old_offchain_signer_commitment
        );
        assert_eq!(
            roundtripped.new_offchain_signer_commitment,
            request.new_offchain_signer_commitment
        );
        assert_eq!(roundtripped.signature, request.signature);
        assert_eq!(roundtripped.nonce, request.nonce);
        assert_eq!(roundtripped.pubkey_id, request.pubkey_id);
        assert_eq!(
            roundtripped.new_authenticator_pubkey,
            request.new_authenticator_pubkey
        );
    }
}