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