Skip to main content

builder_relayer_client_rust/builder/
proxy.rs

1use crate::builder::derive::derive_proxy;
2use crate::encode::proxy::encode_proxy_transaction_data;
3use crate::errors::{RelayClientError, Result};
4use crate::signer::AbstractSigner;
5use crate::types::{
6    ProxyTransaction, ProxyTransactionArgs, SignatureParams, TransactionRequest, TransactionType,
7};
8use alloy::primitives::{Address, B256, U256, keccak256};
9use std::str::FromStr;
10
11const DEFAULT_GAS_LIMIT: &str = "10000000";
12const RELAY_HUB_PREFIX: &[u8] = b"rlx:";
13
14#[derive(Clone, Debug)]
15pub struct ProxyContractConfig {
16    pub relay_hub: String,
17    pub proxy_factory: String,
18}
19
20pub fn is_proxy_contract_config_valid(config: &ProxyContractConfig) -> bool {
21    !config.proxy_factory.is_empty() && !config.relay_hub.is_empty()
22}
23
24fn as_be_bytes_32(value: &str) -> U256 {
25    U256::from_str_radix(value, 10).unwrap_or(U256::ZERO)
26}
27
28fn create_struct_hash(
29    from: &str,
30    to: &str,
31    data_hex: &str,
32    tx_fee: &str,
33    gas_price: &str,
34    gas_limit: &str,
35    nonce: &str,
36    relay_hub: &str,
37    relay: &str,
38) -> Result<B256> {
39    let from_addr = Address::from_str(from).map_err(|_| RelayClientError::InvalidAddress)?;
40    let to_addr = Address::from_str(to).map_err(|_| RelayClientError::InvalidAddress)?;
41    let relay_hub_addr =
42        Address::from_str(relay_hub).map_err(|_| RelayClientError::InvalidAddress)?;
43    let relay_addr = Address::from_str(relay).map_err(|_| RelayClientError::InvalidAddress)?;
44
45    let mut data_bytes = data_hex.trim_start_matches("0x").as_bytes().to_vec();
46    if let Ok(decoded) = hex::decode(data_hex.trim_start_matches("0x")) {
47        data_bytes = decoded;
48    }
49
50    let tx_fee_bytes = as_be_bytes_32(tx_fee).to_be_bytes::<32>();
51    let gas_price_bytes = as_be_bytes_32(gas_price).to_be_bytes::<32>();
52    let gas_limit_bytes = as_be_bytes_32(gas_limit).to_be_bytes::<32>();
53    let nonce_bytes = as_be_bytes_32(nonce).to_be_bytes::<32>();
54
55    let mut payload: Vec<u8> = Vec::new();
56    payload.extend_from_slice(RELAY_HUB_PREFIX);
57    payload.extend_from_slice(from_addr.as_slice());
58    payload.extend_from_slice(to_addr.as_slice());
59    payload.extend_from_slice(&data_bytes);
60    payload.extend_from_slice(&tx_fee_bytes);
61    payload.extend_from_slice(&gas_price_bytes);
62    payload.extend_from_slice(&gas_limit_bytes);
63    payload.extend_from_slice(&nonce_bytes);
64    payload.extend_from_slice(relay_hub_addr.as_slice());
65    payload.extend_from_slice(relay_addr.as_slice());
66
67    Ok(keccak256(payload))
68}
69
70async fn create_proxy_signature(signer: &dyn AbstractSigner, struct_hash: B256) -> Result<String> {
71    // Mirror ethers.js signMessage on a 32-byte struct hash
72    let mut msg = Vec::with_capacity(2 + 32 + 32);
73    msg.extend_from_slice(b"\x19Ethereum Signed Message:\n32");
74    msg.extend_from_slice(struct_hash.as_slice());
75    let digest = keccak256(&msg);
76
77    let sig = signer.sign_hash(digest).await?;
78    Ok(format!("0x{}", hex::encode(sig.as_bytes())))
79}
80
81pub async fn build_proxy_transaction_request(
82    signer: &dyn AbstractSigner,
83    args: &ProxyTransactionArgs,
84    proxy_contract_config: &ProxyContractConfig,
85    metadata: Option<String>,
86    txns: &[ProxyTransaction],
87) -> Result<TransactionRequest> {
88    if !is_proxy_contract_config_valid(proxy_contract_config) {
89        return Err(RelayClientError::InvalidNetwork);
90    }
91
92    let proxy_factory = &proxy_contract_config.proxy_factory;
93    let from_addr = Address::from_str(&args.from).map_err(|_| RelayClientError::InvalidAddress)?;
94    let factory_addr =
95        Address::from_str(proxy_factory).map_err(|_| RelayClientError::InvalidAddress)?;
96    let proxy_wallet = derive_proxy(factory_addr, from_addr)?.to_string();
97
98    let relayer_fee = "0".to_string();
99    let gas_limit = args
100        .gas_limit
101        .clone()
102        .filter(|g| !g.is_empty() && g != "0")
103        .unwrap_or_else(|| DEFAULT_GAS_LIMIT.to_string());
104
105    let struct_hash = create_struct_hash(
106        &args.from,
107        proxy_factory,
108        &args.data,
109        &relayer_fee,
110        &args.gas_price,
111        &gas_limit,
112        &args.nonce,
113        &proxy_contract_config.relay_hub,
114        &args.relay,
115    )?;
116
117    let signature = create_proxy_signature(signer, struct_hash).await?;
118
119    let sig_params = SignatureParams {
120        gas_price: Some(args.gas_price.clone()),
121        relayer_fee: Some(relayer_fee.clone()),
122        gas_limit: Some(gas_limit.clone()),
123        relay_hub: Some(proxy_contract_config.relay_hub.clone()),
124        relay: Some(args.relay.clone()),
125        ..SignatureParams::default()
126    };
127
128    let packed_metadata = metadata.unwrap_or_default();
129    let data = encode_proxy_transaction_data(txns);
130
131    Ok(TransactionRequest {
132        r#type: TransactionType::Proxy,
133        from: args.from.clone(),
134        to: proxy_factory.clone(),
135        proxy_wallet: Some(proxy_wallet),
136        data,
137        nonce: Some(args.nonce.clone()),
138        signature,
139        signature_params: sig_params,
140        metadata: Some(packed_metadata),
141    })
142}