1use alloy::{
2 consensus::{SignableTransaction, TxLegacy},
3 network::TxSigner,
4 primitives::{Address, Bytes, TxKind, U256},
5 rpc::client::{ClientBuilder, ReqwestClient},
6 signers::Signature,
7};
8use async_trait::async_trait;
9use serde::Serialize;
10use url::Url;
11
12#[derive(Debug)]
16pub struct Web3Signer {
17 pub client: ReqwestClient,
19 pub address: Address,
22}
23
24#[derive(Serialize, Clone, Debug)]
25#[serde(rename_all = "camelCase")]
26struct SignTransactionParams {
27 from: Address,
28 #[serde(default, skip_serializing_if = "TxKind::is_create")]
29 to: TxKind,
30 value: U256,
31 #[serde(with = "alloy_serde::quantity")]
32 gas: u128,
33 #[serde(
34 skip_serializing_if = "Option::is_none",
35 with = "alloy_serde::quantity::opt"
36 )]
37 gas_price: Option<u128>,
38 #[serde(with = "alloy_serde::quantity")]
39 nonce: u64,
40 data: Bytes,
41}
42
43impl Web3Signer {
44 pub fn new(address: Address, url: Url) -> Self {
45 Web3Signer {
46 client: ClientBuilder::default().http(url),
47 address,
48 }
49 }
50}
51
52#[async_trait]
53impl TxSigner<Signature> for Web3Signer {
54 fn address(&self) -> Address {
55 self.address
56 }
57
58 async fn sign_transaction(
59 &self,
60 tx: &mut dyn SignableTransaction<Signature>,
61 ) -> alloy::signers::Result<Signature> {
62 let params = SignTransactionParams {
63 from: self.address,
64 to: tx.to(),
65 value: tx.value(),
66 gas: tx.gas_limit(),
67 gas_price: tx.gas_price(),
68 nonce: tx.nonce(),
69 data: Bytes::copy_from_slice(tx.input()),
70 };
71
72 let response = self
73 .client
74 .request::<Vec<SignTransactionParams>, Bytes>("eth_signTransaction", vec![params])
75 .await
76 .map_err(alloy::signers::Error::other)?;
77
78 let signed_tx = TxLegacy::decode_signed_fields(&mut response.as_ref())
79 .map_err(alloy::signers::Error::other)?;
80
81 Ok(*signed_tx.signature())
82 }
83}