polyoxide_relay/
config.rs1use alloy::primitives::{address, Address};
2use polyoxide_core::{current_timestamp, Base64Format, Signer};
3use reqwest::header::{HeaderMap, HeaderValue};
4
5#[derive(Clone, Debug)]
7pub struct ContractConfig {
8 pub safe_factory: Address,
9 pub safe_multisend: Address,
10 pub proxy_factory: Option<Address>,
11 pub relay_hub: Option<Address>,
12 pub rpc_url: &'static str,
13}
14
15pub fn get_contract_config(chain_id: u64) -> Option<ContractConfig> {
19 match chain_id {
20 137 => Some(ContractConfig {
21 safe_factory: address!("aacFeEa03eb1561C4e67d661e40682Bd20E3541b"),
22 safe_multisend: address!("A238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761"),
23 proxy_factory: Some(address!("aB45c5A4B0c941a2F231C04C3f49182e1A254052")),
24 relay_hub: Some(address!("D216153c06E857cD7f72665E0aF1d7D82172F494")),
25 rpc_url: "https://polygon.drpc.org",
26 }),
27 80002 => Some(ContractConfig {
28 safe_factory: address!("aacFeEa03eb1561C4e67d661e40682Bd20E3541b"),
29 safe_multisend: address!("A238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761"),
30 proxy_factory: None, relay_hub: None,
32 rpc_url: "https://rpc-amoy.polygon.technology",
33 }),
34 _ => None,
35 }
36}
37
38#[derive(Clone)]
43pub struct BuilderConfig {
44 pub key: String,
45 pub secret: String,
46 pub passphrase: Option<String>,
47}
48
49impl std::fmt::Debug for BuilderConfig {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 f.debug_struct("BuilderConfig")
52 .field("key", &"[REDACTED]")
53 .field("secret", &"[REDACTED]")
54 .field(
55 "passphrase",
56 &self.passphrase.as_ref().map(|_| "[REDACTED]"),
57 )
58 .finish()
59 }
60}
61
62impl BuilderConfig {
63 pub fn new(key: String, secret: String, passphrase: Option<String>) -> Self {
65 Self {
66 key,
67 secret,
68 passphrase,
69 }
70 }
71
72 pub fn generate_headers(
76 &self,
77 method: &str,
78 path: &str,
79 body: Option<&str>,
80 ) -> Result<HeaderMap, String> {
81 let mut headers = HeaderMap::new();
82 let timestamp = current_timestamp();
83
84 let signer = Signer::from_raw(&self.secret);
86 let message = Signer::create_message(timestamp, method, path, body);
87 let signature = signer.sign(&message, Base64Format::Standard)?;
88
89 headers.insert(
90 "POLY-API-KEY",
91 HeaderValue::from_str(&self.key).map_err(|e| e.to_string())?,
92 );
93 headers.insert(
94 "POLY-TIMESTAMP",
95 HeaderValue::from_str(×tamp.to_string()).map_err(|e| e.to_string())?,
96 );
97 headers.insert(
98 "POLY-SIGNATURE",
99 HeaderValue::from_str(&signature).map_err(|e| e.to_string())?,
100 );
101
102 if let Some(passphrase) = &self.passphrase {
103 headers.insert(
104 "POLY-PASSPHRASE",
105 HeaderValue::from_str(passphrase).map_err(|e| e.to_string())?,
106 );
107 }
108
109 Ok(headers)
110 }
111
112 pub fn generate_relayer_v2_headers(
116 &self,
117 method: &str,
118 path: &str,
119 body: Option<&str>,
120 ) -> Result<HeaderMap, String> {
121 let mut headers = HeaderMap::new();
122 let timestamp = current_timestamp();
123
124 let signer = Signer::new(&self.secret);
126 let message = Signer::create_message(timestamp, method, path, body);
127 let signature = signer.sign(&message, Base64Format::UrlSafe)?;
128
129 headers.insert(
130 "POLY_BUILDER_API_KEY",
131 HeaderValue::from_str(&self.key).map_err(|e| e.to_string())?,
132 );
133 headers.insert(
134 "POLY_BUILDER_TIMESTAMP",
135 HeaderValue::from_str(×tamp.to_string()).map_err(|e| e.to_string())?,
136 );
137 headers.insert(
138 "POLY_BUILDER_SIGNATURE",
139 HeaderValue::from_str(&signature).map_err(|e| e.to_string())?,
140 );
141
142 if let Some(passphrase) = &self.passphrase {
143 headers.insert(
144 "POLY_BUILDER_PASSPHRASE",
145 HeaderValue::from_str(passphrase).map_err(|e| e.to_string())?,
146 );
147 }
148
149 Ok(headers)
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn test_builder_config_debug_redacts_secrets() {
159 let config = BuilderConfig::new(
160 "my-api-key".to_string(),
161 "my-secret".to_string(),
162 Some("my-passphrase".to_string()),
163 );
164 let debug_output = format!("{:?}", config);
165
166 assert!(debug_output.contains("[REDACTED]"));
167 assert!(
168 !debug_output.contains("my-api-key"),
169 "Debug leaked API key: {}",
170 debug_output
171 );
172 assert!(
173 !debug_output.contains("my-secret"),
174 "Debug leaked secret: {}",
175 debug_output
176 );
177 assert!(
178 !debug_output.contains("my-passphrase"),
179 "Debug leaked passphrase: {}",
180 debug_output
181 );
182 }
183
184 #[test]
185 fn test_builder_config_debug_without_passphrase() {
186 let config = BuilderConfig::new("key".to_string(), "secret".to_string(), None);
187 let debug_output = format!("{:?}", config);
188
189 assert!(debug_output.contains("[REDACTED]"));
190 assert!(debug_output.contains("passphrase: None"));
191 }
192}