Skip to main content

polyoxide_relay/
config.rs

1use alloy::primitives::Address;
2use base64::{
3    engine::general_purpose::{STANDARD, URL_SAFE},
4    Engine as _,
5};
6use hmac::{Hmac, Mac};
7use reqwest::header::{HeaderMap, HeaderValue};
8use sha2::Sha256;
9use std::str::FromStr;
10use std::time::{SystemTime, UNIX_EPOCH};
11
12#[derive(Clone, Debug)]
13pub struct ContractConfig {
14    pub safe_factory: Address,
15    pub safe_multisend: Address,
16    pub proxy_factory: Option<Address>,
17    pub relay_hub: Option<Address>,
18    pub rpc_url: &'static str,
19}
20
21pub fn get_contract_config(chain_id: u64) -> Option<ContractConfig> {
22    match chain_id {
23        137 => Some(ContractConfig {
24            safe_factory: Address::from_str("0xaacFeEa03eb1561C4e67d661e40682Bd20E3541b").unwrap(),
25            safe_multisend: Address::from_str("0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761")
26                .unwrap(),
27            proxy_factory: Some(
28                Address::from_str("0xaB45c5A4B0c941a2F231C04C3f49182e1A254052").unwrap(),
29            ),
30            relay_hub: Some(
31                Address::from_str("0xD216153c06E857cD7f72665E0aF1d7D82172F494").unwrap(),
32            ),
33            rpc_url: "https://polygon.drpc.org",
34        }),
35        80002 => Some(ContractConfig {
36            safe_factory: Address::from_str("0xaacFeEa03eb1561C4e67d661e40682Bd20E3541b").unwrap(),
37            safe_multisend: Address::from_str("0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761")
38                .unwrap(),
39            proxy_factory: None, // Proxy not supported on Amoy testnet
40            relay_hub: None,
41            rpc_url: "https://rpc-amoy.polygon.technology",
42        }),
43        _ => None,
44    }
45}
46
47#[derive(Clone, Debug)]
48pub struct BuilderConfig {
49    pub key: String,
50    pub secret: String,
51    pub passphrase: Option<String>,
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 = SystemTime::now()
71            .duration_since(UNIX_EPOCH)
72            .unwrap()
73            .as_secs()
74            .to_string();
75
76        let body_str = body.unwrap_or("");
77        let message = format!("{}{}{}{}", timestamp, method, path, body_str);
78
79        let mut mac = Hmac::<Sha256>::new_from_slice(self.secret.as_bytes())
80            .map_err(|e| format!("Invalid secret: {}", e))?;
81        mac.update(message.as_bytes());
82        let result = mac.finalize();
83        let signature = STANDARD.encode(result.into_bytes());
84
85        headers.insert("POLY-API-KEY", HeaderValue::from_str(&self.key).unwrap());
86        headers.insert("POLY-TIMESTAMP", HeaderValue::from_str(&timestamp).unwrap());
87        headers.insert("POLY-SIGNATURE", HeaderValue::from_str(&signature).unwrap());
88
89        if let Some(passphrase) = &self.passphrase {
90            headers.insert(
91                "POLY-PASSPHRASE",
92                HeaderValue::from_str(passphrase).unwrap(),
93            );
94        }
95
96        Ok(headers)
97    }
98
99    pub fn generate_relayer_v2_headers(
100        &self,
101        method: &str,
102        path: &str,
103        body: Option<&str>,
104    ) -> Result<HeaderMap, String> {
105        let mut headers = HeaderMap::new();
106        let timestamp = SystemTime::now()
107            .duration_since(UNIX_EPOCH)
108            .unwrap()
109            .as_secs()
110            .to_string();
111
112        let body_str = body.unwrap_or("");
113        // Signature logic: timestamp + method + path + body
114        let message = format!("{}{}{}{}", timestamp, method, path, body_str);
115
116        // Try URL-safe decode first, fallback to standard
117        let secret_bytes = URL_SAFE
118            .decode(&self.secret)
119            .or_else(|_| STANDARD.decode(&self.secret))
120            .map_err(|e| format!("Invalid base64 secret: {}", e))?;
121
122        let mut mac = Hmac::<Sha256>::new_from_slice(&secret_bytes)
123            .map_err(|e| format!("Invalid secret: {}", e))?;
124        mac.update(message.as_bytes());
125        let result = mac.finalize();
126        // Use URL-safe encoding for signature (matching Python's urlsafe_b64encode)
127        let signature = URL_SAFE.encode(result.into_bytes());
128
129        headers.insert(
130            "POLY_BUILDER_API_KEY",
131            HeaderValue::from_str(&self.key).unwrap(),
132        );
133        headers.insert(
134            "POLY_BUILDER_TIMESTAMP",
135            HeaderValue::from_str(&timestamp).unwrap(),
136        );
137        headers.insert(
138            "POLY_BUILDER_SIGNATURE",
139            HeaderValue::from_str(&signature).unwrap(),
140        );
141
142        if let Some(passphrase) = &self.passphrase {
143            headers.insert(
144                "POLY_BUILDER_PASSPHRASE",
145                HeaderValue::from_str(passphrase).unwrap(),
146            );
147        }
148
149        Ok(headers)
150    }
151}