builder_relayer_client_rust/builder/
safe.rs1use crate::builder::derive::derive_safe;
2use crate::encode::safe::create_safe_multisend_transaction;
3use crate::errors::Result;
4use crate::types::{
5 SafeTransaction, SafeTransactionArgs, SignatureParams, TransactionRequest, TransactionType,
6};
7use crate::utils::split_and_pack_sig;
8use ethers::abi::{encode, Token};
9use ethers::types::{Address, U256};
10use sha3::{Digest, Keccak256};
11
12#[derive(Clone, Debug)]
13pub struct SafeContractConfig {
14 pub safe_factory: String,
15 pub safe_multisend: String,
16}
17
18const SAFE_TX_TYPE_STR: &str = "SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)";
20const DOMAIN_TYPE_STR: &str = "EIP712Domain(uint256 chainId,address verifyingContract)";
21
22fn keccak_bytes(data: &[u8]) -> [u8; 32] {
23 let mut h = Keccak256::new();
24 h.update(data);
25 let out = h.finalize();
26 let mut arr = [0u8; 32];
27 arr.copy_from_slice(&out);
28 arr
29}
30
31fn safe_tx_type_hash() -> [u8; 32] {
32 keccak_bytes(SAFE_TX_TYPE_STR.as_bytes())
33}
34fn domain_type_hash() -> [u8; 32] {
35 keccak_bytes(DOMAIN_TYPE_STR.as_bytes())
36}
37
38fn keccak(data: &[u8]) -> [u8; 32] {
39 keccak_bytes(data)
40}
41
42pub fn eip712_domain_separator(chain_id: U256, verifying_contract: Address) -> [u8; 32] {
43 let encoded = encode(&[
45 Token::FixedBytes(domain_type_hash().to_vec()),
46 Token::Uint(chain_id),
47 Token::Address(verifying_contract),
48 ]);
49 keccak(&encoded)
50}
51
52pub fn safe_tx_struct_hash(
53 to: Address,
54 value: U256,
55 data: &[u8],
56 operation: u8,
57 safe_tx_gas: U256,
58 base_gas: U256,
59 gas_price: U256,
60 gas_token: Address,
61 refund_receiver: Address,
62 nonce: U256,
63) -> [u8; 32] {
64 let data_hash = keccak(data);
65 let encoded = encode(&[
66 Token::FixedBytes(safe_tx_type_hash().to_vec()),
67 Token::Address(to),
68 Token::Uint(value),
69 Token::FixedBytes(data_hash.to_vec()),
70 Token::Uint(U256::from(operation)),
71 Token::Uint(safe_tx_gas),
72 Token::Uint(base_gas),
73 Token::Uint(gas_price),
74 Token::Address(gas_token),
75 Token::Address(refund_receiver),
76 Token::Uint(nonce),
77 ]);
78 keccak(&encoded)
79}
80
81fn aggregate_transaction(txns: &[SafeTransaction], safe_multisend: &str) -> SafeTransaction {
82 if txns.len() == 1 {
83 txns[0].clone()
84 } else {
85 create_safe_multisend_transaction(txns, safe_multisend)
86 }
87}
88
89pub trait AbstractSigner: Send + Sync {
90 fn get_address(&self) -> Result<String>;
91 fn sign_message(&self, hash32_hex: &str) -> Result<String>; fn sign_eip712_digest(&self, digest_hex: &str) -> Result<String>; }
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq)]
96pub enum SignatureMode {
97 Eip191StructHash,
99 Eip712Digest,
101 Eip191Digest,
103}
104
105pub async fn build_safe_transaction_request(
106 signer: &dyn AbstractSigner,
107 args: SafeTransactionArgs,
108 safe_contract_config: SafeContractConfig,
109 metadata: Option<String>,
110 sig_mode: SignatureMode,
111) -> Result<TransactionRequest> {
112 let safe_factory = &safe_contract_config.safe_factory;
113 let safe_multisend = &safe_contract_config.safe_multisend;
114 let transaction = aggregate_transaction(&args.transactions, safe_multisend);
115 let safe_txn_gas = U256::zero();
116 let base_gas = U256::zero();
117 let gas_price = U256::zero();
118 let gas_token: Address = "0x0000000000000000000000000000000000000000"
119 .parse()
120 .unwrap();
121 let refund_receiver: Address = "0x0000000000000000000000000000000000000000"
122 .parse()
123 .unwrap();
124
125 let safe_address = args
126 .safe_address
127 .clone()
128 .unwrap_or_else(|| derive_safe(&args.from, safe_factory));
129
130 eprintln!("[DEBUG] args.safe_address input: {:?}", args.safe_address);
131 eprintln!("[DEBUG] Final safe_address used: {}", safe_address);
132
133 let to_addr: Address = transaction
134 .to
135 .parse()
136 .expect("invalid address in transaction.to");
137 let value = U256::from_dec_str(&transaction.value).unwrap_or_default();
138 let data_bytes = hex::decode(transaction.data.trim_start_matches("0x")).unwrap_or_default();
139 let nonce = U256::from_dec_str(&args.nonce).unwrap_or_default();
140 let struct_hash = safe_tx_struct_hash(
141 to_addr,
142 value,
143 &data_bytes,
144 transaction.operation as u8,
145 safe_txn_gas,
146 base_gas,
147 gas_price,
148 gas_token,
149 refund_receiver,
150 nonce,
151 );
152 let domain_separator =
153 eip712_domain_separator(U256::from(args.chain_id), safe_address.parse().unwrap());
154 let mut prefix = vec![0x19, 0x01];
155 prefix.extend_from_slice(&domain_separator);
156 prefix.extend_from_slice(&struct_hash);
157 let digest = keccak(&prefix);
158
159 eprintln!("[DEBUG] Safe address: {}", safe_address);
160 eprintln!(
161 "[DEBUG] Domain separator: 0x{}",
162 hex::encode(domain_separator)
163 );
164 eprintln!("[DEBUG] Struct hash: 0x{}", hex::encode(struct_hash));
165 eprintln!(
166 "[DEBUG] Digest (0x1901||domain||struct): 0x{}",
167 hex::encode(digest)
168 );
169
170 let sig = match sig_mode {
172 SignatureMode::Eip191StructHash => {
173 eprintln!("[DEBUG] SignatureMode=Eip191StructHash (signMessage(structHash))");
174 signer.sign_message(&format!("0x{}", hex::encode(struct_hash)))?
175 }
176 SignatureMode::Eip712Digest => {
177 eprintln!("[DEBUG] SignatureMode=Eip712Digest (sign_eip712_digest(digest))");
178 signer.sign_eip712_digest(&format!("0x{}", hex::encode(digest)))?
179 }
180 SignatureMode::Eip191Digest => {
181 eprintln!("[DEBUG] SignatureMode=Eip191Digest (signMessage(digest))");
182 signer.sign_message(&format!("0x{}", hex::encode(digest)))?
183 }
184 };
185 eprintln!("[DEBUG] Raw signature before pack: {}", sig);
186 let packed_sig = split_and_pack_sig(&sig);
187 eprintln!("[DEBUG] Packed signature: {}", packed_sig);
188
189 let signer_addr = signer.get_address()?;
192 eprintln!("[DEBUG] Expected signer address: {}", signer_addr);
193
194 use ethers::types::Signature as EthSig;
195 if let Ok(sig_parsed) = packed_sig.parse::<EthSig>() {
196 let verify_hash = match sig_mode {
197 SignatureMode::Eip191StructHash => {
198 let mut msg = b"\x19Ethereum Signed Message:\n32".to_vec();
199 msg.extend_from_slice(&struct_hash);
200 keccak(&msg)
201 }
202 SignatureMode::Eip712Digest => digest,
203 SignatureMode::Eip191Digest => {
204 let mut msg = b"\x19Ethereum Signed Message:\n32".to_vec();
205 msg.extend_from_slice(&digest);
206 keccak(&msg)
207 }
208 };
209 if let Ok(recovered) = sig_parsed.recover(verify_hash) {
210 eprintln!("[DEBUG] Recovered address: 0x{:x}", recovered);
211 if format!("0x{:x}", recovered).to_lowercase() != signer_addr.to_lowercase() {
212 eprintln!("[WARNING] Signature does not recover to signer address!");
213 } else {
214 eprintln!("[DEBUG] Signature recovery VERIFIED ✓");
215 }
216 }
217 }
218
219 let sig_params = SignatureParams {
220 gas_price: Some(gas_price.to_string()),
221 operation: Some((transaction.operation as u8).to_string()),
222 safe_txn_gas: Some(safe_txn_gas.to_string()),
223 base_gas: Some(base_gas.to_string()),
224 gas_token: Some(format!("0x{:x}", gas_token)),
225 refund_receiver: Some(format!("0x{:x}", refund_receiver)),
226 ..Default::default()
227 };
228
229 Ok(TransactionRequest {
230 from: args.from.clone(),
231 to: transaction.to.clone(),
232 proxy_wallet: Some(safe_address),
233 data: transaction.data.clone(),
234 nonce: Some(args.nonce.clone()),
235 signature: packed_sig,
236 signature_params: sig_params,
237 r#type: TransactionType::SAFE,
238 metadata,
239 })
240}