builder_relayer_client_rust/builder/
create.rs

1use crate::builder::derive::derive_safe;
2use crate::errors::Result;
3use crate::types::{
4    SafeCreateTransactionArgs, SignatureParams, TransactionRequest, TransactionType,
5};
6use crate::utils::split_and_pack_sig;
7use ethers::abi::{encode, Token};
8use ethers::types::{Address, U256};
9use sha3::{Digest, Keccak256};
10
11// Gnosis Safe CREATE typed data (standard for createProxyWithNonce)
12// Based on GnosisSafeProxyFactory.sol signature
13const SAFE_CREATE_TYPE_STR: &str = "SafeCreate(address owner,address paymentToken,uint256 payment,address paymentReceiver,uint256 nonce)";
14const DOMAIN_TYPE_STR: &str = "EIP712Domain(uint256 chainId,address verifyingContract)";
15
16fn keccak_bytes(data: &[u8]) -> [u8; 32] {
17    let mut h = Keccak256::new();
18    h.update(data);
19    let out = h.finalize();
20    let mut arr = [0u8; 32];
21    arr.copy_from_slice(&out);
22    arr
23}
24
25fn safe_create_type_hash() -> [u8; 32] {
26    keccak_bytes(SAFE_CREATE_TYPE_STR.as_bytes())
27}
28fn domain_type_hash() -> [u8; 32] {
29    keccak_bytes(DOMAIN_TYPE_STR.as_bytes())
30}
31
32fn eip712_domain_separator(chain_id: U256, verifying_contract: Address) -> [u8; 32] {
33    let encoded = encode(&[
34        Token::FixedBytes(domain_type_hash().to_vec()),
35        Token::Uint(chain_id),
36        Token::Address(verifying_contract),
37    ]);
38    keccak_bytes(&encoded)
39}
40
41fn safe_create_struct_hash(
42    owner: Address,
43    payment_token: Address,
44    payment: U256,
45    payment_receiver: Address,
46    nonce: U256,
47) -> [u8; 32] {
48    let encoded = encode(&[
49        Token::FixedBytes(safe_create_type_hash().to_vec()),
50        Token::Address(owner),
51        Token::Address(payment_token),
52        Token::Uint(payment),
53        Token::Address(payment_receiver),
54        Token::Uint(nonce),
55    ]);
56    keccak_bytes(&encoded)
57}
58
59pub trait TypedDataSigner: Send + Sync {
60    fn sign_typed_create_proxy(
61        &self,
62        safe_factory: &str,
63        chain_id: u64,
64        payment_token: &str,
65        payment: &str,
66        payment_receiver: &str,
67    ) -> Result<String>;
68}
69
70pub trait AbstractSignerForCreate: Send + Sync {
71    fn get_address(&self) -> Result<String>;
72    fn sign_eip712_digest(&self, digest_hex: &str) -> Result<String>;
73}
74
75pub async fn build_safe_create_transaction_request(
76    signer: &dyn AbstractSignerForCreate,
77    safe_factory: &str,
78    args: SafeCreateTransactionArgs,
79) -> Result<TransactionRequest> {
80    let owner: Address = args
81        .from
82        .parse()
83        .map_err(|_| crate::errors::RelayClientError::SignerUnavailable)?;
84    let factory: Address = safe_factory
85        .parse()
86        .map_err(|_| crate::errors::RelayClientError::SignerUnavailable)?;
87    let payment_token: Address = args
88        .payment_token
89        .parse()
90        .map_err(|_| crate::errors::RelayClientError::SignerUnavailable)?;
91    let payment_receiver: Address = args
92        .payment_receiver
93        .parse()
94        .map_err(|_| crate::errors::RelayClientError::SignerUnavailable)?;
95    let payment = U256::from_dec_str(&args.payment).unwrap_or_default();
96
97    // For SAFE CREATE, nonce is typically 0 or derived from keccak(owner)
98    // Using 0 for simplicity (matches common patterns)
99    let nonce = U256::zero();
100
101    let struct_hash =
102        safe_create_struct_hash(owner, payment_token, payment, payment_receiver, nonce);
103    let domain_separator = eip712_domain_separator(U256::from(args.chain_id), factory);
104
105    let mut prefix = vec![0x19, 0x01];
106    prefix.extend_from_slice(&domain_separator);
107    prefix.extend_from_slice(&struct_hash);
108    let digest = keccak_bytes(&prefix);
109
110    let sig = signer.sign_eip712_digest(&format!("0x{}", hex::encode(digest)))?;
111    let packed_sig = split_and_pack_sig(&sig);
112
113    let sig_params = SignatureParams {
114        payment_token: Some(args.payment_token.clone()),
115        payment: Some(args.payment.clone()),
116        payment_receiver: Some(args.payment_receiver.clone()),
117        ..Default::default()
118    };
119
120    let safe_address = derive_safe(&args.from, safe_factory);
121
122    let req = TransactionRequest {
123        from: args.from.clone(),
124        to: safe_factory.to_string(),
125        proxy_wallet: Some(safe_address),
126        data: "0x".to_string(),
127        nonce: None,
128        signature: packed_sig,
129        signature_params: sig_params,
130        r#type: TransactionType::SafeCreate,
131        metadata: None,
132    };
133    Ok(req)
134}