polyoxide_relay/
account.rs1use crate::config::BuilderConfig;
2use crate::error::RelayError;
3use alloy::primitives::Address;
4use alloy::signers::local::PrivateKeySigner;
5
6#[derive(Clone)]
7pub struct BuilderAccount {
8 pub(crate) signer: PrivateKeySigner,
9 pub(crate) config: Option<BuilderConfig>,
10}
11
12impl std::fmt::Debug for BuilderAccount {
13 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14 f.debug_struct("BuilderAccount")
15 .field("address", &self.signer.address())
16 .field("config", &self.config)
17 .finish()
18 }
19}
20
21impl BuilderAccount {
22 pub fn new(
23 private_key: impl Into<String>,
24 config: Option<BuilderConfig>,
25 ) -> Result<Self, RelayError> {
26 let signer = private_key
27 .into()
28 .parse::<PrivateKeySigner>()
29 .map_err(|e| RelayError::Signer(format!("Failed to parse private key: {}", e)))?;
30
31 Ok(Self { signer, config })
32 }
33
34 pub fn address(&self) -> Address {
35 self.signer.address()
36 }
37
38 pub fn signer(&self) -> &PrivateKeySigner {
39 &self.signer
40 }
41
42 pub fn config(&self) -> Option<&BuilderConfig> {
43 self.config.as_ref()
44 }
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50
51 const TEST_PRIVATE_KEY: &str =
54 "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
55
56 #[test]
57 fn test_new_valid_private_key() {
58 let account = BuilderAccount::new(TEST_PRIVATE_KEY, None);
59 assert!(account.is_ok());
60 }
61
62 #[test]
63 fn test_new_with_0x_prefix() {
64 let key = format!("0x{}", TEST_PRIVATE_KEY);
65 let account = BuilderAccount::new(key, None);
66 assert!(account.is_ok());
68 }
69
70 #[test]
71 fn test_new_invalid_private_key() {
72 let result = BuilderAccount::new("not_a_valid_key", None);
73 assert!(result.is_err());
74 let err = result.unwrap_err();
75 match err {
76 RelayError::Signer(msg) => {
77 assert!(
78 msg.contains("Failed to parse private key"),
79 "unexpected: {msg}"
80 );
81 }
82 other => panic!("Expected Signer error, got: {other:?}"),
83 }
84 }
85
86 #[test]
87 fn test_new_empty_key() {
88 let result = BuilderAccount::new("", None);
89 assert!(result.is_err());
90 }
91
92 #[test]
93 fn test_address_derivation_deterministic() {
94 let a1 = BuilderAccount::new(TEST_PRIVATE_KEY, None).unwrap();
95 let a2 = BuilderAccount::new(TEST_PRIVATE_KEY, None).unwrap();
96 assert_eq!(a1.address(), a2.address());
97 }
98
99 #[test]
100 fn test_address_matches_known_value() {
101 let account = BuilderAccount::new(TEST_PRIVATE_KEY, None).unwrap();
103 let expected: Address = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
104 .parse()
105 .unwrap();
106 assert_eq!(account.address(), expected);
107 }
108
109 #[test]
110 fn test_debug_redacts_private_key() {
111 let account = BuilderAccount::new(TEST_PRIVATE_KEY, None).unwrap();
112 let debug_output = format!("{:?}", account);
113 assert!(
114 debug_output.contains("address"),
115 "Debug should show address, got: {debug_output}"
116 );
117 assert!(
118 !debug_output.contains(TEST_PRIVATE_KEY),
119 "Debug should not contain the private key, got: {debug_output}"
120 );
121 }
122
123 #[test]
124 fn test_config_none() {
125 let account = BuilderAccount::new(TEST_PRIVATE_KEY, None).unwrap();
126 assert!(account.config().is_none());
127 }
128
129 #[test]
130 fn test_config_some() {
131 let config = BuilderConfig::new("key".into(), "secret".into(), None);
132 let account = BuilderAccount::new(TEST_PRIVATE_KEY, Some(config)).unwrap();
133 assert!(account.config().is_some());
134 }
135}