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