Skip to main content

karak_kms/web3/
mod.rs

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/// A signer that sends an RPC request to sign a transaction remotely
13/// Implements `eth_signTransaction` method of Consensys Web3 Signer
14/// Reference: https://docs.web3signer.consensys.io/reference/api/json-rpc#eth_signtransaction
15#[derive(Debug)]
16pub struct Web3Signer {
17    /// Client used to send an RPC request
18    pub client: ReqwestClient,
19    /// Address of the account that intends to sign a transaction.
20    /// It must match the `from` field in the transaction.
21    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}