builder_relayer_client_rust/builder/
safe.rs1use crate::builder::derive::derive_safe;
2use crate::encode::safe::create_safe_multisend_transaction;
3use crate::errors::Result;
4pub use crate::signer::AbstractSigner;
5use crate::types::{
6 SafeTransaction, SafeTransactionArgs, SignatureParams, TransactionRequest, TransactionType,
7};
8use crate::utils::split_and_pack_sig;
9use alloy::primitives::{Address, Bytes, U256, keccak256};
10use alloy::sol;
11use alloy::sol_types::SolStruct;
12use std::str::FromStr;
13
14#[derive(Clone, Debug)]
15pub struct SafeContractConfig {
16 pub safe_factory: String,
17 pub safe_multisend: String,
18}
19
20sol! {
22 #[derive(Debug)]
23 struct SafeTx {
24 address to;
25 uint256 value;
26 bytes data;
27 uint8 operation;
28 uint256 safeTxGas;
29 uint256 baseGas;
30 uint256 gasPrice;
31 address gasToken;
32 address refundReceiver;
33 uint256 nonce;
34 }
35
36 #[derive(Debug)]
37 struct EIP712Domain {
38 uint256 chainId;
39 address verifyingContract;
40 }
41}
42
43pub fn eip712_domain_separator(chain_id: U256, verifying_contract: Address) -> [u8; 32] {
44 let domain = EIP712Domain {
45 chainId: chain_id,
46 verifyingContract: verifying_contract,
47 };
48 domain.eip712_hash_struct().into()
49}
50
51pub fn safe_tx_struct_hash(
52 to: Address,
53 value: U256,
54 data: &[u8],
55 operation: u8,
56 safe_tx_gas: U256,
57 base_gas: U256,
58 gas_price: U256,
59 gas_token: Address,
60 refund_receiver: Address,
61 nonce: U256,
62) -> [u8; 32] {
63 let tx = SafeTx {
64 to,
65 value,
66 data: Bytes::from(data.to_vec()),
67 operation,
68 safeTxGas: safe_tx_gas,
69 baseGas: base_gas,
70 gasPrice: gas_price,
71 gasToken: gas_token,
72 refundReceiver: refund_receiver,
73 nonce,
74 };
75 tx.eip712_hash_struct().into()
76}
77
78fn aggregate_transaction(txns: &[SafeTransaction], safe_multisend: &str) -> SafeTransaction {
79 if txns.len() == 1 {
80 txns[0].clone()
81 } else {
82 create_safe_multisend_transaction(txns, safe_multisend)
83 }
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87pub enum SignatureMode {
88 Eip191StructHash,
90 Eip712Digest,
92 Eip191Digest,
93}
94
95pub async fn build_safe_transaction_request(
96 signer: &dyn AbstractSigner,
97 args: &SafeTransactionArgs,
98 config: &SafeContractConfig,
99 metadata: Option<String>,
100 mode: SignatureMode,
101) -> Result<TransactionRequest> {
102 let safe_address = if let Some(addr) = &args.safe_address {
103 Address::from_str(addr).map_err(|_| crate::errors::RelayClientError::InvalidAddress)?
104 } else {
105 let owner = Address::from_str(&args.from)
106 .map_err(|_| crate::errors::RelayClientError::InvalidAddress)?;
107 let factory = Address::from_str(&config.safe_factory)
108 .map_err(|_| crate::errors::RelayClientError::InvalidAddress)?;
109 derive_safe(factory, owner)?
110 };
111
112 let tx = aggregate_transaction(&args.transactions, &config.safe_multisend);
113
114 let to =
115 Address::from_str(&tx.to).map_err(|_| crate::errors::RelayClientError::InvalidAddress)?;
116 let value = U256::from_str(&tx.value).unwrap_or(U256::ZERO);
117 let data = hex::decode(tx.data.trim_start_matches("0x")).unwrap_or_default();
118 let operation = tx.operation as u8;
119
120 let safe_tx_gas = U256::ZERO;
122 let base_gas = U256::ZERO;
123 let gas_price = U256::ZERO;
124 let gas_token = Address::ZERO;
125 let refund_receiver = Address::ZERO;
126
127 let nonce = U256::from_str(&args.nonce).unwrap_or(U256::ZERO);
128
129 let struct_hash = safe_tx_struct_hash(
130 to,
131 value,
132 &data,
133 operation,
134 safe_tx_gas,
135 base_gas,
136 gas_price,
137 gas_token,
138 refund_receiver,
139 nonce,
140 );
141
142 let chain_id = U256::from(args.chain_id);
143 let domain_separator = eip712_domain_separator(chain_id, safe_address);
144
145 let mut digest_input = Vec::with_capacity(2 + 32 + 32);
147 digest_input.extend_from_slice(&[0x19, 0x01]);
148 digest_input.extend_from_slice(&domain_separator);
149 digest_input.extend_from_slice(&struct_hash);
150 let digest = keccak256(&digest_input);
151
152 let signature = match mode {
153 SignatureMode::Eip191StructHash => {
154 let mut msg = Vec::with_capacity(60);
155 msg.extend_from_slice(b"\x19Ethereum Signed Message:\n32");
156 msg.extend_from_slice(&struct_hash);
157 let hash = keccak256(&msg);
158
159 signer.sign_hash(hash).await?
160 }
161 SignatureMode::Eip712Digest => signer.sign_eip712_digest(digest).await?,
162 SignatureMode::Eip191Digest => {
163 let mut msg = Vec::with_capacity(60);
165 msg.extend_from_slice(b"\x19Ethereum Signed Message:\n32");
166 msg.extend_from_slice(digest.as_slice());
167 let hash = keccak256(&msg);
168 signer.sign_hash(hash).await?
169 }
170 };
171
172 let sig_hex = format!("0x{}", hex::encode(signature.as_bytes()));
174 let packed_sig = split_and_pack_sig(&sig_hex);
175
176 let signature_params = SignatureParams {
177 gas_price: Some(gas_price.to_string()),
178 relayer_fee: None,
179 gas_limit: None,
180 relay_hub: None,
181 relay: None,
182 operation: Some(operation.to_string()),
183 safe_txn_gas: Some(safe_tx_gas.to_string()),
184 base_gas: Some(base_gas.to_string()),
185 gas_token: Some(format!("{:#x}", gas_token)),
186 refund_receiver: Some(format!("{:#x}", refund_receiver)),
187 payment_token: None,
188 payment: None,
189 payment_receiver: None,
190 };
191
192 Ok(TransactionRequest {
193 r#type: TransactionType::SAFE,
194 from: args.from.clone(),
195 to: tx.to.clone(),
196 proxy_wallet: Some(format!("{:#x}", safe_address)),
197 data: tx.data.clone(),
198 nonce: Some(args.nonce.clone()),
199 signature: packed_sig,
200 signature_params,
201 metadata,
202 })
203}