Skip to main content

reth_rpc_eth_api/helpers/
signer.rs

1//! An abstraction over ethereum signers.
2
3use alloy_dyn_abi::TypedData;
4use alloy_primitives::{Address, Signature};
5use alloy_rpc_types_eth::TransactionRequest;
6use dyn_clone::DynClone;
7use reth_rpc_eth_types::SignError;
8use std::{result, sync::Arc};
9use tokio::sync::Mutex;
10
11/// Result returned by [`EthSigner`] methods.
12pub type Result<T> = result::Result<T, SignError>;
13
14/// An Ethereum Signer used via RPC.
15#[async_trait::async_trait]
16pub trait EthSigner<T, TxReq = TransactionRequest>: Send + Sync + DynClone {
17    /// Returns the available accounts for this signer.
18    fn accounts(&self) -> Vec<Address>;
19
20    /// Returns `true` whether this signer can sign for this address
21    fn is_signer_for(&self, addr: &Address) -> bool {
22        self.accounts().contains(addr)
23    }
24
25    /// Returns the signature
26    async fn sign(&self, address: Address, message: &[u8]) -> Result<Signature>;
27
28    /// signs a transaction request using the given account in request
29    async fn sign_transaction(&self, request: TxReq, address: &Address) -> Result<T>;
30
31    /// Encodes and signs the typed data according EIP-712. Payload must implement Eip712 trait.
32    fn sign_typed_data(&self, address: Address, payload: &TypedData) -> Result<Signature>;
33
34    /// Returns the lock `eth_sendTransaction` holds for this signer's accounts from filling the
35    /// nonce until the signed transaction is in the pool, so concurrent requests from one account
36    /// get distinct nonces.
37    ///
38    /// `None` leaves those requests unserialized.
39    fn nonce_lock(&self) -> Option<Arc<Mutex<()>>> {
40        None
41    }
42}
43
44dyn_clone::clone_trait_object!(<T, TxReq> EthSigner<T, TxReq>);
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[derive(Clone)]
51    struct MockSigner;
52
53    struct MockSignedTx;
54    struct MockTxReq;
55
56    #[async_trait::async_trait]
57    impl EthSigner<MockSignedTx, MockTxReq> for MockSigner {
58        fn accounts(&self) -> Vec<Address> {
59            Vec::new()
60        }
61
62        async fn sign(&self, _address: Address, _message: &[u8]) -> Result<Signature> {
63            Err(SignError::NoAccount)
64        }
65
66        async fn sign_transaction(
67            &self,
68            _request: MockTxReq,
69            _address: &Address,
70        ) -> Result<MockSignedTx> {
71            Err(SignError::NoAccount)
72        }
73
74        fn sign_typed_data(&self, _address: Address, _payload: &TypedData) -> Result<Signature> {
75            Err(SignError::NoAccount)
76        }
77    }
78
79    #[test]
80    fn clones_trait_object_with_custom_tx_request_type() {
81        let signer: Box<dyn EthSigner<MockSignedTx, MockTxReq>> = Box::new(MockSigner);
82        let cloned: Box<dyn EthSigner<MockSignedTx, MockTxReq>> = dyn_clone::clone_box(&*signer);
83
84        assert!(cloned.accounts().is_empty());
85    }
86}