1use crate::{
13 config::ConfigFileError,
14 models::signer::{
15 AwsKmsSignerConfig, CdpSignerConfig, GoogleCloudKmsSignerConfig,
16 GoogleCloudKmsSignerKeyConfig, GoogleCloudKmsSignerServiceAccountConfig, LocalSignerConfig,
17 Signer, SignerConfig, TurnkeySignerConfig, VaultSignerConfig, VaultTransitSignerConfig,
18 },
19 models::PlainOrEnvValue,
20};
21use secrets::SecretVec;
22use serde::{Deserialize, Serialize};
23use std::{collections::HashSet, path::Path};
24
25#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
26#[serde(deny_unknown_fields)]
27pub struct LocalSignerFileConfig {
28 pub path: String,
29 pub passphrase: PlainOrEnvValue,
30}
31
32#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
33#[serde(deny_unknown_fields)]
34pub struct AwsKmsSignerFileConfig {
35 pub region: String,
36 pub key_id: String,
37}
38
39#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
40#[serde(deny_unknown_fields)]
41pub struct TurnkeySignerFileConfig {
42 pub api_public_key: String,
43 pub api_private_key: PlainOrEnvValue,
44 pub organization_id: String,
45 pub private_key_id: String,
46 pub public_key: String,
47}
48
49#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
50#[serde(deny_unknown_fields)]
51pub struct CdpSignerFileConfig {
52 pub api_key_id: String,
53 pub api_key_secret: PlainOrEnvValue,
54 pub wallet_secret: PlainOrEnvValue,
55 pub account_address: String,
56}
57
58#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
59#[serde(deny_unknown_fields)]
60pub struct VaultSignerFileConfig {
61 pub address: String,
62 pub namespace: Option<String>,
63 pub role_id: PlainOrEnvValue,
64 pub secret_id: PlainOrEnvValue,
65 pub key_name: String,
66 pub mount_point: Option<String>,
67}
68
69#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
70#[serde(deny_unknown_fields)]
71pub struct VaultTransitSignerFileConfig {
72 pub key_name: String,
73 pub address: String,
74 pub role_id: PlainOrEnvValue,
75 pub secret_id: PlainOrEnvValue,
76 pub pubkey: String,
77 pub mount_point: Option<String>,
78 pub namespace: Option<String>,
79}
80
81fn google_cloud_default_auth_uri() -> String {
82 "https://accounts.google.com/o/oauth2/auth".to_string()
83}
84
85fn google_cloud_default_token_uri() -> String {
86 "https://oauth2.googleapis.com/token".to_string()
87}
88
89fn google_cloud_default_auth_provider_x509_cert_url() -> String {
90 "https://www.googleapis.com/oauth2/v1/certs".to_string()
91}
92
93fn google_cloud_default_client_x509_cert_url() -> String {
94 "https://www.googleapis.com/robot/v1/metadata/x509/solana-signer%40forward-emitter-459820-r7.iam.gserviceaccount.com".to_string()
95}
96
97fn google_cloud_default_universe_domain() -> String {
98 "googleapis.com".to_string()
99}
100
101fn google_cloud_default_key_version() -> u32 {
102 1
103}
104
105fn google_cloud_default_location() -> String {
106 "global".to_string()
107}
108
109#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
110#[serde(deny_unknown_fields)]
111pub struct GoogleCloudKmsServiceAccountFileConfig {
112 pub project_id: String,
113 pub private_key_id: PlainOrEnvValue,
114 pub private_key: PlainOrEnvValue,
115 pub client_email: PlainOrEnvValue,
116 pub client_id: String,
117 #[serde(default = "google_cloud_default_auth_uri")]
118 pub auth_uri: String,
119 #[serde(default = "google_cloud_default_token_uri")]
120 pub token_uri: String,
121 #[serde(default = "google_cloud_default_auth_provider_x509_cert_url")]
122 pub auth_provider_x509_cert_url: String,
123 #[serde(default = "google_cloud_default_client_x509_cert_url")]
124 pub client_x509_cert_url: String,
125 #[serde(default = "google_cloud_default_universe_domain")]
126 pub universe_domain: String,
127}
128
129#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
130#[serde(deny_unknown_fields)]
131pub struct GoogleCloudKmsKeyFileConfig {
132 #[serde(default = "google_cloud_default_location")]
133 pub location: String,
134 pub key_ring_id: String,
135 pub key_id: String,
136 #[serde(default = "google_cloud_default_key_version")]
137 pub key_version: u32,
138}
139
140#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
141#[serde(deny_unknown_fields)]
142pub struct GoogleCloudKmsSignerFileConfig {
143 pub service_account: GoogleCloudKmsServiceAccountFileConfig,
144 pub key: GoogleCloudKmsKeyFileConfig,
145}
146
147#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
149#[serde(tag = "type", rename_all = "lowercase", content = "config")]
150pub enum SignerFileConfigEnum {
151 Local(LocalSignerFileConfig),
152 #[serde(rename = "aws_kms")]
153 AwsKms(AwsKmsSignerFileConfig),
154 Turnkey(TurnkeySignerFileConfig),
155 Cdp(CdpSignerFileConfig),
156 Vault(VaultSignerFileConfig),
157 #[serde(rename = "vault_transit")]
158 VaultTransit(VaultTransitSignerFileConfig),
159 #[serde(rename = "google_cloud_kms")]
160 GoogleCloudKms(GoogleCloudKmsSignerFileConfig),
161}
162
163#[derive(Debug, Serialize, Deserialize, Clone)]
165#[serde(deny_unknown_fields)]
166pub struct SignerFileConfig {
167 pub id: String,
168 #[serde(flatten)]
169 pub config: SignerFileConfigEnum,
170}
171
172#[derive(Debug, Serialize, Deserialize, Clone)]
174#[serde(deny_unknown_fields)]
175pub struct SignersFileConfig {
176 pub signers: Vec<SignerFileConfig>,
177}
178
179impl SignerFileConfig {
180 pub fn validate_basic(&self) -> Result<(), ConfigFileError> {
181 if self.id.is_empty() {
182 return Err(ConfigFileError::InvalidIdLength(
183 "Signer ID cannot be empty".into(),
184 ));
185 }
186 Ok(())
187 }
188}
189
190impl SignersFileConfig {
191 pub fn new(signers: Vec<SignerFileConfig>) -> Self {
192 Self { signers }
193 }
194
195 pub fn validate(&self) -> Result<(), ConfigFileError> {
196 if self.signers.is_empty() {
197 return Ok(());
198 }
199
200 let mut ids = HashSet::new();
201 for signer in &self.signers {
202 signer.validate_basic()?;
203 if !ids.insert(signer.id.clone()) {
204 return Err(ConfigFileError::DuplicateId(signer.id.clone()));
205 }
206 }
207 Ok(())
208 }
209}
210
211impl TryFrom<LocalSignerFileConfig> for LocalSignerConfig {
212 type Error = ConfigFileError;
213
214 fn try_from(config: LocalSignerFileConfig) -> Result<Self, Self::Error> {
215 if config.path.is_empty() {
216 return Err(ConfigFileError::InvalidIdLength(
217 "Signer path cannot be empty".into(),
218 ));
219 }
220
221 let path = Path::new(&config.path);
222 if !path.exists() {
223 return Err(ConfigFileError::FileNotFound(format!(
224 "Signer file not found at path: {}",
225 path.display()
226 )));
227 }
228
229 if !path.is_file() {
230 return Err(ConfigFileError::InvalidFormat(format!(
231 "Path exists but is not a file: {}",
232 path.display()
233 )));
234 }
235
236 let passphrase = config.passphrase.get_value().map_err(|e| {
237 ConfigFileError::InvalidFormat(format!("Failed to get passphrase value: {e}"))
238 })?;
239
240 if passphrase.is_empty() {
241 return Err(ConfigFileError::InvalidFormat(
242 "Local signer passphrase cannot be empty".into(),
243 ));
244 }
245
246 let raw_key = SecretVec::new(32, |buffer| {
247 let loaded = oz_keystore::LocalClient::load(
248 Path::new(&config.path).to_path_buf(),
249 passphrase.to_str().as_str().to_string(),
250 );
251 buffer.copy_from_slice(&loaded);
252 });
253
254 Ok(LocalSignerConfig { raw_key })
255 }
256}
257
258impl TryFrom<AwsKmsSignerFileConfig> for AwsKmsSignerConfig {
259 type Error = ConfigFileError;
260
261 fn try_from(config: AwsKmsSignerFileConfig) -> Result<Self, Self::Error> {
262 Ok(AwsKmsSignerConfig {
263 region: Some(config.region),
264 key_id: config.key_id,
265 })
266 }
267}
268
269impl TryFrom<TurnkeySignerFileConfig> for TurnkeySignerConfig {
270 type Error = ConfigFileError;
271
272 fn try_from(config: TurnkeySignerFileConfig) -> Result<Self, Self::Error> {
273 let api_private_key = config.api_private_key.get_value().map_err(|e| {
274 ConfigFileError::InvalidFormat(format!("Failed to get API private key: {e}"))
275 })?;
276
277 Ok(TurnkeySignerConfig {
278 api_public_key: config.api_public_key,
279 api_private_key,
280 organization_id: config.organization_id,
281 private_key_id: config.private_key_id,
282 public_key: config.public_key,
283 })
284 }
285}
286
287impl TryFrom<CdpSignerFileConfig> for CdpSignerConfig {
288 type Error = ConfigFileError;
289
290 fn try_from(config: CdpSignerFileConfig) -> Result<Self, Self::Error> {
291 let api_key_secret = config.api_key_secret.get_value().map_err(|e| {
292 ConfigFileError::InvalidFormat(format!("Failed to get API key secret: {e}"))
293 })?;
294
295 let wallet_secret = config.wallet_secret.get_value().map_err(|e| {
296 ConfigFileError::InvalidFormat(format!("Failed to get wallet secret: {e}"))
297 })?;
298
299 Ok(CdpSignerConfig {
300 api_key_id: config.api_key_id,
301 api_key_secret,
302 wallet_secret,
303 account_address: config.account_address,
304 })
305 }
306}
307
308impl TryFrom<VaultSignerFileConfig> for VaultSignerConfig {
309 type Error = ConfigFileError;
310
311 fn try_from(config: VaultSignerFileConfig) -> Result<Self, Self::Error> {
312 let role_id = config
313 .role_id
314 .get_value()
315 .map_err(|e| ConfigFileError::InvalidFormat(format!("Failed to get role ID: {e}")))?;
316
317 let secret_id = config
318 .secret_id
319 .get_value()
320 .map_err(|e| ConfigFileError::InvalidFormat(format!("Failed to get secret ID: {e}")))?;
321
322 Ok(VaultSignerConfig {
323 address: config.address,
324 namespace: config.namespace,
325 role_id,
326 secret_id,
327 key_name: config.key_name,
328 mount_point: config.mount_point,
329 })
330 }
331}
332
333impl TryFrom<VaultTransitSignerFileConfig> for VaultTransitSignerConfig {
334 type Error = ConfigFileError;
335
336 fn try_from(config: VaultTransitSignerFileConfig) -> Result<Self, Self::Error> {
337 let role_id = config
338 .role_id
339 .get_value()
340 .map_err(|e| ConfigFileError::InvalidFormat(format!("Failed to get role ID: {e}")))?;
341
342 let secret_id = config
343 .secret_id
344 .get_value()
345 .map_err(|e| ConfigFileError::InvalidFormat(format!("Failed to get secret ID: {e}")))?;
346
347 Ok(VaultTransitSignerConfig {
348 key_name: config.key_name,
349 address: config.address,
350 namespace: config.namespace,
351 role_id,
352 secret_id,
353 pubkey: config.pubkey,
354 mount_point: config.mount_point,
355 })
356 }
357}
358
359impl TryFrom<GoogleCloudKmsSignerFileConfig> for GoogleCloudKmsSignerConfig {
360 type Error = ConfigFileError;
361
362 fn try_from(config: GoogleCloudKmsSignerFileConfig) -> Result<Self, Self::Error> {
363 use crate::models::SecretString;
364
365 let private_key = config
366 .service_account
367 .private_key
368 .get_value()
369 .map_err(|e| {
370 ConfigFileError::InvalidFormat(format!("Failed to get private key: {e}"))
371 })?;
372
373 let private_key_id = config
374 .service_account
375 .private_key_id
376 .get_value()
377 .map_err(|e| {
378 ConfigFileError::InvalidFormat(format!("Failed to get private key ID: {e}"))
379 })?;
380
381 let client_email = config
382 .service_account
383 .client_email
384 .get_value()
385 .map_err(|e| {
386 ConfigFileError::InvalidFormat(format!("Failed to get client email: {e}"))
387 })?;
388
389 let service_account = GoogleCloudKmsSignerServiceAccountConfig {
390 private_key,
391 private_key_id,
392 project_id: SecretString::new(&config.service_account.project_id),
393 client_email,
394 client_id: SecretString::new(&config.service_account.client_id),
395 auth_uri: SecretString::new(&config.service_account.auth_uri),
396 token_uri: SecretString::new(&config.service_account.token_uri),
397 auth_provider_x509_cert_url: SecretString::new(
398 &config.service_account.auth_provider_x509_cert_url,
399 ),
400 client_x509_cert_url: SecretString::new(&config.service_account.client_x509_cert_url),
401 universe_domain: SecretString::new(&config.service_account.universe_domain),
402 };
403
404 let key = GoogleCloudKmsSignerKeyConfig {
405 location: SecretString::new(&config.key.location),
406 key_ring_id: SecretString::new(&config.key.key_ring_id),
407 key_id: SecretString::new(&config.key.key_id),
408 key_version: config.key.key_version,
409 };
410
411 Ok(GoogleCloudKmsSignerConfig {
412 service_account,
413 key,
414 })
415 }
416}
417
418impl TryFrom<SignerFileConfigEnum> for SignerConfig {
419 type Error = ConfigFileError;
420
421 fn try_from(config: SignerFileConfigEnum) -> Result<Self, Self::Error> {
422 match config {
423 SignerFileConfigEnum::Local(local) => {
424 Ok(SignerConfig::Local(LocalSignerConfig::try_from(local)?))
425 }
426 SignerFileConfigEnum::AwsKms(aws_kms) => {
427 Ok(SignerConfig::AwsKms(AwsKmsSignerConfig::try_from(aws_kms)?))
428 }
429 SignerFileConfigEnum::Turnkey(turnkey) => Ok(SignerConfig::Turnkey(
430 TurnkeySignerConfig::try_from(turnkey)?,
431 )),
432 SignerFileConfigEnum::Cdp(cdp) => {
433 Ok(SignerConfig::Cdp(CdpSignerConfig::try_from(cdp)?))
434 }
435 SignerFileConfigEnum::Vault(vault) => {
436 Ok(SignerConfig::Vault(VaultSignerConfig::try_from(vault)?))
437 }
438 SignerFileConfigEnum::VaultTransit(vault_transit) => Ok(SignerConfig::VaultTransit(
439 VaultTransitSignerConfig::try_from(vault_transit)?,
440 )),
441 SignerFileConfigEnum::GoogleCloudKms(gcp_kms) => Ok(SignerConfig::GoogleCloudKms(
442 Box::new(GoogleCloudKmsSignerConfig::try_from(gcp_kms)?),
443 )),
444 }
445 }
446}
447
448impl TryFrom<SignerFileConfig> for Signer {
449 type Error = ConfigFileError;
450
451 fn try_from(config: SignerFileConfig) -> Result<Self, Self::Error> {
452 config.validate_basic()?;
453
454 let signer_config = SignerConfig::try_from(config.config)?;
455
456 let signer = Signer::new(config.id, signer_config);
458
459 signer.validate().map_err(|e| match e {
461 crate::models::signer::SignerValidationError::EmptyId => {
462 ConfigFileError::MissingField("signer id".into())
463 }
464 crate::models::signer::SignerValidationError::InvalidIdFormat => {
465 ConfigFileError::InvalidFormat("Invalid signer ID format".into())
466 }
467 crate::models::signer::SignerValidationError::InvalidConfig(msg) => {
468 ConfigFileError::InvalidFormat(format!("Invalid signer configuration: {msg}"))
469 }
470 })?;
471
472 Ok(signer)
473 }
474}
475
476#[cfg(test)]
477mod tests {
478 use super::*;
479 use crate::models::SecretString;
480
481 #[test]
482 fn test_aws_kms_conversion() {
483 let config = AwsKmsSignerFileConfig {
484 region: "us-east-1".to_string(),
485 key_id: "test-key-id".to_string(),
486 };
487
488 let result = AwsKmsSignerConfig::try_from(config);
489 assert!(result.is_ok());
490
491 let aws_config = result.unwrap();
492 assert_eq!(aws_config.region, Some("us-east-1".to_string()));
493 assert_eq!(aws_config.key_id, "test-key-id");
494 }
495
496 #[test]
497 fn test_turnkey_conversion() {
498 let config = TurnkeySignerFileConfig {
499 api_public_key: "test-public-key".to_string(),
500 api_private_key: PlainOrEnvValue::Plain {
501 value: SecretString::new("test-private-key"),
502 },
503 organization_id: "test-org".to_string(),
504 private_key_id: "test-private-key-id".to_string(),
505 public_key: "test-public-key".to_string(),
506 };
507
508 let result = TurnkeySignerConfig::try_from(config);
509 assert!(result.is_ok());
510
511 let turnkey_config = result.unwrap();
512 assert_eq!(turnkey_config.api_public_key, "test-public-key");
513 assert_eq!(turnkey_config.organization_id, "test-org");
514 }
515
516 #[test]
517 fn test_signer_file_config_validation() {
518 let signer_config = SignerFileConfig {
519 id: "test-signer".to_string(),
520 config: SignerFileConfigEnum::Local(LocalSignerFileConfig {
521 path: "test-path".to_string(),
522 passphrase: PlainOrEnvValue::Plain {
523 value: SecretString::new("test-passphrase"),
524 },
525 }),
526 };
527
528 assert!(signer_config.validate_basic().is_ok());
529 }
530
531 #[test]
532 fn test_empty_signer_id() {
533 let signer_config = SignerFileConfig {
534 id: "".to_string(),
535 config: SignerFileConfigEnum::Local(LocalSignerFileConfig {
536 path: "test-path".to_string(),
537 passphrase: PlainOrEnvValue::Plain {
538 value: SecretString::new("test-passphrase"),
539 },
540 }),
541 };
542
543 assert!(signer_config.validate_basic().is_err());
544 }
545
546 #[test]
547 fn test_signers_config_validation() {
548 let configs = SignersFileConfig::new(vec![
549 SignerFileConfig {
550 id: "signer1".to_string(),
551 config: SignerFileConfigEnum::Local(LocalSignerFileConfig {
552 path: "test-path".to_string(),
553 passphrase: PlainOrEnvValue::Plain {
554 value: SecretString::new("test-passphrase"),
555 },
556 }),
557 },
558 SignerFileConfig {
559 id: "signer2".to_string(),
560 config: SignerFileConfigEnum::Local(LocalSignerFileConfig {
561 path: "test-path".to_string(),
562 passphrase: PlainOrEnvValue::Plain {
563 value: SecretString::new("test-passphrase"),
564 },
565 }),
566 },
567 ]);
568
569 assert!(configs.validate().is_ok());
570 }
571
572 #[test]
573 fn test_duplicate_signer_ids() {
574 let configs = SignersFileConfig::new(vec![
575 SignerFileConfig {
576 id: "signer1".to_string(),
577 config: SignerFileConfigEnum::Local(LocalSignerFileConfig {
578 path: "test-path".to_string(),
579 passphrase: PlainOrEnvValue::Plain {
580 value: SecretString::new("test-passphrase"),
581 },
582 }),
583 },
584 SignerFileConfig {
585 id: "signer1".to_string(), config: SignerFileConfigEnum::Local(LocalSignerFileConfig {
587 path: "test-path".to_string(),
588 passphrase: PlainOrEnvValue::Plain {
589 value: SecretString::new("test-passphrase"),
590 },
591 }),
592 },
593 ]);
594
595 assert!(matches!(
596 configs.validate(),
597 Err(ConfigFileError::DuplicateId(_))
598 ));
599 }
600
601 #[test]
602 fn test_local_conversion_invalid_path() {
603 let config = LocalSignerFileConfig {
604 path: "non-existent-path".to_string(),
605 passphrase: PlainOrEnvValue::Plain {
606 value: SecretString::new("test-passphrase"),
607 },
608 };
609
610 let result = LocalSignerConfig::try_from(config);
611 assert!(result.is_err());
612 if let Err(ConfigFileError::FileNotFound(msg)) = result {
613 assert!(msg.contains("Signer file not found"));
614 } else {
615 panic!("Expected FileNotFound error");
616 }
617 }
618
619 #[test]
620 fn test_vault_conversion() {
621 let config = VaultSignerFileConfig {
622 address: "https://vault.example.com".to_string(),
623 namespace: Some("test-namespace".to_string()),
624 role_id: PlainOrEnvValue::Plain {
625 value: SecretString::new("test-role"),
626 },
627 secret_id: PlainOrEnvValue::Plain {
628 value: SecretString::new("test-secret"),
629 },
630 key_name: "test-key".to_string(),
631 mount_point: Some("test-mount".to_string()),
632 };
633
634 let result = VaultSignerConfig::try_from(config);
635 assert!(result.is_ok());
636
637 let vault_config = result.unwrap();
638 assert_eq!(vault_config.address, "https://vault.example.com");
639 assert_eq!(vault_config.namespace, Some("test-namespace".to_string()));
640 }
641
642 #[test]
643 fn test_google_cloud_kms_conversion() {
644 let config = GoogleCloudKmsSignerFileConfig {
645 service_account: GoogleCloudKmsServiceAccountFileConfig {
646 project_id: "test-project".to_string(),
647 private_key_id: PlainOrEnvValue::Plain {
648 value: SecretString::new("test-key-id"),
649 },
650 private_key: PlainOrEnvValue::Plain {
651 value: SecretString::new("test-private-key"),
652 },
653 client_email: PlainOrEnvValue::Plain {
654 value: SecretString::new("test@email.com"),
655 },
656 client_id: "test-client-id".to_string(),
657 auth_uri: google_cloud_default_auth_uri(),
658 token_uri: google_cloud_default_token_uri(),
659 auth_provider_x509_cert_url: google_cloud_default_auth_provider_x509_cert_url(),
660 client_x509_cert_url: google_cloud_default_client_x509_cert_url(),
661 universe_domain: google_cloud_default_universe_domain(),
662 },
663 key: GoogleCloudKmsKeyFileConfig {
664 location: google_cloud_default_location(),
665 key_ring_id: "test-ring".to_string(),
666 key_id: "test-key".to_string(),
667 key_version: google_cloud_default_key_version(),
668 },
669 };
670
671 let result = GoogleCloudKmsSignerConfig::try_from(config);
672 assert!(result.is_ok());
673
674 let gcp_config = result.unwrap();
675 assert_eq!(gcp_config.key.key_id.to_str().as_str(), "test-key");
676 assert_eq!(
677 gcp_config.service_account.project_id.to_str().as_str(),
678 "test-project"
679 );
680 }
681
682 #[test]
683 fn test_cdp_file_config_conversion() {
684 use crate::models::SecretString;
685 let cfg = CdpSignerFileConfig {
686 api_key_id: "id".into(),
687 api_key_secret: PlainOrEnvValue::Plain {
688 value: SecretString::new("asecret"),
689 },
690 wallet_secret: PlainOrEnvValue::Plain {
691 value: SecretString::new("wsecret"),
692 },
693 account_address: "0x0000000000000000000000000000000000000000".into(),
694 };
695 let res = CdpSignerConfig::try_from(cfg);
696 assert!(res.is_ok());
697 let c = res.unwrap();
698 assert_eq!(c.api_key_id, "id");
699 assert_eq!(
700 c.account_address,
701 "0x0000000000000000000000000000000000000000"
702 );
703 }
704
705 #[test]
706 fn test_cdp_file_config_conversion_api_key_secret_error() {
707 let cfg = CdpSignerFileConfig {
708 api_key_id: "id".into(),
709 api_key_secret: PlainOrEnvValue::Env {
710 value: "NONEXISTENT_ENV_VAR".into(),
711 },
712 wallet_secret: PlainOrEnvValue::Plain {
713 value: SecretString::new("wsecret"),
714 },
715 account_address: "0x0000000000000000000000000000000000000000".into(),
716 };
717 let res = CdpSignerConfig::try_from(cfg);
718 assert!(res.is_err());
719 let err = res.unwrap_err();
720 assert!(matches!(err, ConfigFileError::InvalidFormat(_)));
721 if let ConfigFileError::InvalidFormat(msg) = err {
722 assert!(msg.contains("Failed to get API key secret"));
723 }
724 }
725
726 #[test]
727 fn test_cdp_file_config_conversion_wallet_secret_error() {
728 let cfg = CdpSignerFileConfig {
729 api_key_id: "id".into(),
730 api_key_secret: PlainOrEnvValue::Plain {
731 value: SecretString::new("asecret"),
732 },
733 wallet_secret: PlainOrEnvValue::Env {
734 value: "NONEXISTENT_ENV_VAR".into(),
735 },
736 account_address: "0x0000000000000000000000000000000000000000".into(),
737 };
738 let res = CdpSignerConfig::try_from(cfg);
739 assert!(res.is_err());
740 let err = res.unwrap_err();
741 assert!(matches!(err, ConfigFileError::InvalidFormat(_)));
742 if let ConfigFileError::InvalidFormat(msg) = err {
743 assert!(msg.contains("Failed to get wallet secret"));
744 }
745 }
746}