tx_sitter_client/
lib.rs

1use std::sync::Arc;
2
3use aws_sdk_lambda as lambda;
4use aws_sdk_lambda::primitives::Blob;
5use eyre::Context;
6use serde_json::Value;
7use types::transaction::TxSitterTransaction;
8use types::tx_sitter::{ReqByRelayerIdAndStatus, TransactionStatus};
9
10use self::rpc::TxSitterRpcClient;
11use crate::types::transaction::{TransactionResponse, TxSitterTransactionInput};
12use crate::types::tx_sitter::{ReqTransactionById, TransactionRequest};
13
14pub mod rpc;
15pub mod types;
16
17#[derive(Debug, Clone)]
18pub struct TxSitterConfigOptions {
19    pub send_lambda_name: Option<String>,
20    pub rpc_lambda_name: Option<String>,
21    pub transactions_lambda_name: Option<String>,
22}
23
24#[derive(Debug, Clone)]
25pub struct TxSitterConfig {
26    pub send_lambda_name: String,
27    pub rpc_lambda_name: String,
28    pub transactions_lambda_name: String,
29}
30
31impl From<TxSitterConfigOptions> for TxSitterConfig {
32    fn from(new_config: TxSitterConfigOptions) -> Self {
33        TxSitterConfig {
34            send_lambda_name: new_config.send_lambda_name.unwrap_or_default(),
35            rpc_lambda_name: new_config.rpc_lambda_name.unwrap_or_default(),
36            transactions_lambda_name: new_config.transactions_lambda_name.unwrap_or_default(),
37        }
38    }
39}
40
41#[derive(Clone, Debug)]
42pub struct TxSitterClient {
43    inner: Arc<TxSitterInner>,
44}
45
46#[derive(Debug)]
47struct TxSitterInner {
48    client: lambda::Client,
49    config: TxSitterConfig,
50}
51
52impl TxSitterClient {
53    // Existing clients will use this method
54    pub async fn new(config: TxSitterConfig) -> Self {
55        let aws_config = aws_config::load_from_env().await;
56        let client = lambda::Client::new(&aws_config);
57
58        let inner = Arc::new(TxSitterInner { client, config });
59
60        Self { inner }
61    }
62
63    // New clients can opt to use this method
64    pub async fn new_with_options(config: TxSitterConfigOptions) -> Self {
65        // Convert `TxSitterConfigNew` to `TxSitterConfig` using `From` trait
66        Self::new(config.into()).await
67    }
68
69    pub fn get_provider(&self, relayer_id: &str) -> TxSitterRpcClient {
70        TxSitterRpcClient::new(relayer_id.into(), self.inner.clone())
71    }
72
73    pub async fn relay_transaction(
74        &self,
75        transaction: TxSitterTransactionInput,
76    ) -> eyre::Result<String> {
77        tracing::info!(?transaction, "Sending a tx-sitter transaction");
78
79        let payload = serde_json::to_string(&transaction)?;
80
81        let res = self
82            .inner
83            .client
84            .invoke()
85            .function_name(self.inner.config.send_lambda_name.clone())
86            .payload(Blob::new(payload))
87            .send()
88            .await?;
89
90        if let Some(payload_blob) = res.payload {
91            let val = String::from_utf8(payload_blob.into_inner())?;
92            let response: TransactionResponse =
93                serde_json::from_str(&val).context("Invalid transaction response")?;
94
95            Ok(response.result)
96        } else {
97            Err(eyre::eyre!("No payload found"))
98        }
99    }
100
101    pub async fn get_transaction_by_relayer_and_status(
102        &self,
103        relayer_id: &str,
104        status: TransactionStatus,
105    ) -> eyre::Result<Vec<TxSitterTransactionInput>> {
106        let get_tx_by_id_request = TransactionRequest::RelayerId(ReqByRelayerIdAndStatus {
107            relayer_id: relayer_id.into(),
108            status,
109        });
110
111        let payload = serde_json::to_string(&get_tx_by_id_request)?;
112
113        let res = self
114            .inner
115            .client
116            .invoke()
117            .function_name(self.inner.config.transactions_lambda_name.clone())
118            .payload(Blob::new(payload))
119            .send()
120            .await?;
121
122        if let Some(payload_blob) = res.payload {
123            let val = String::from_utf8(payload_blob.into_inner())?;
124
125            Ok(serde_json::from_str(&val).context("Invalid transaction payload")?)
126        } else {
127            Err(eyre::eyre!("No payload found"))
128        }
129    }
130
131    pub async fn get_transaction_by_id(&self, tx_id: &str) -> eyre::Result<TxSitterTransaction> {
132        let get_tx_by_id_request = TransactionRequest::TransactionId(ReqTransactionById {
133            transaction_id: tx_id.into(),
134        });
135
136        let payload = serde_json::to_string(&get_tx_by_id_request)?;
137
138        let res = self
139            .inner
140            .client
141            .invoke()
142            .function_name(self.inner.config.transactions_lambda_name.clone())
143            .payload(Blob::new(payload))
144            .send()
145            .await?;
146
147        if let Some(payload_blob) = res.payload {
148            let val = String::from_utf8(payload_blob.into_inner())?;
149            let json: Value = serde_json::from_str(&val)?;
150
151            let transactions = json
152                .get("transactions")
153                .and_then(|arr| arr.get(0))
154                .ok_or_else(|| eyre::eyre!("Transaction not found or invalid format"))?;
155
156            Ok(serde_json::from_value(transactions.clone())
157                .context("Invalid transaction payload")?)
158        } else {
159            Err(eyre::eyre!("No payload found"))
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use std::str::FromStr;
167    use std::sync::Arc;
168
169    use ethers::middleware::providers::Provider;
170    use ethers::prelude::{abigen, H160};
171    use ethers::providers::Middleware;
172    use ethers::types::H256;
173    use serde::de::Unexpected::Option;
174
175    use crate::types::transaction::{TransactionPriority, TxSitterTransactionInput};
176    use crate::{TxSitterClient, TxSitterConfig, TxSitterConfigOptions};
177
178    const ARN_SEND_LAMBDA: &str = "arn:aws:lambda:us-east-1:487223788601:function:TxSitter-staging-SendLambda3E928DB8-WenqWs8QhyxS";
179    const ARN_RPC_LAMBDA: &str = "arn:aws:lambda:us-east-1:487223788601:function:TxSitter-staging-RpcLambda7CA8B6A4-Vs2aA9M1uONc";
180    const ARN_TRANSACTION_LAMBDA: &str = "arn:aws:lambda:us-east-1:487223788601:function:TxSitter-staging-TransactionsLambda59148499-0IhhSKx9rR44";
181
182    #[tokio::test]
183    async fn test_relayer_transaction() -> eyre::Result<()> {
184        let relayer_id = "e2c0c380-957a-4705-b4c0-d420fcc49de5";
185
186        let tx_sitter_client = TxSitterClient::new(TxSitterConfig {
187            send_lambda_name: ARN_SEND_LAMBDA.into(),
188            rpc_lambda_name: ARN_RPC_LAMBDA.into(),
189            transactions_lambda_name: ARN_TRANSACTION_LAMBDA.into(),
190        })
191        .await;
192
193        let payload: TxSitterTransactionInput = TxSitterTransactionInput {
194            to: "0x86C5608362B3fBBeB721140472229392f754eF87".to_string(),
195            value: "8".to_string(),
196            gas_limit: "200000".to_string(),
197            relayer_id: relayer_id.into(),
198            data: String::new(),
199            transaction_id: None,
200            priority: TransactionPriority::Fastest.into(),
201            transaction_type: None,
202            metadata: None,
203        };
204
205        let tx_id = tx_sitter_client.relay_transaction(payload).await?;
206
207        println!("tx_id: {}", tx_id);
208
209        Ok(())
210    }
211
212    #[tokio::test]
213    async fn test_get_transaction() -> eyre::Result<()> {
214        let tx_sitter_client = TxSitterClient::new(TxSitterConfig {
215            send_lambda_name: ARN_SEND_LAMBDA.into(),
216            rpc_lambda_name: ARN_RPC_LAMBDA.into(),
217            transactions_lambda_name: ARN_TRANSACTION_LAMBDA.into(),
218        })
219        .await;
220
221        let tx = tx_sitter_client
222            .get_transaction_by_id(
223                "0x1710628b0b829588b3d151b65323718ffbf3b421fad98072c84ab6705a3fb8f4",
224            )
225            .await?;
226
227        println!("tx_hash = {}", tx.tx_hash);
228
229        Ok(())
230    }
231
232    #[tokio::test]
233    async fn test_optional_constructor() -> eyre::Result<()> {
234        let tx_sitter_client = TxSitterClient::new_with_options(TxSitterConfigOptions {
235            send_lambda_name: Some(ARN_SEND_LAMBDA.into()),
236            rpc_lambda_name: Some(ARN_RPC_LAMBDA.into()),
237            transactions_lambda_name: Some(ARN_TRANSACTION_LAMBDA.into()),
238        })
239        .await;
240
241        let tx = tx_sitter_client
242            .get_transaction_by_id(
243                "0x1710628b0b829588b3d151b65323718ffbf3b421fad98072c84ab6705a3fb8f4",
244            )
245            .await?;
246
247        println!("tx_hash = {}", tx.tx_hash);
248
249        Ok(())
250    }
251
252    #[tokio::test]
253    async fn test_relayer_rpc() -> eyre::Result<()> {
254        let relayer_id = "e2c0c380-957a-4705-b4c0-d420fcc49de5";
255
256        let tx_sitter_client = TxSitterClient::new(TxSitterConfig {
257            send_lambda_name: ARN_SEND_LAMBDA.into(),
258            rpc_lambda_name: ARN_RPC_LAMBDA.into(),
259            transactions_lambda_name: ARN_TRANSACTION_LAMBDA.into(),
260        })
261        .await;
262
263        let rpc = tx_sitter_client.get_provider(relayer_id);
264        let provider = Provider::new(rpc);
265
266        let tx_hash =
267            hex_literal::hex!("4a44d3d0e30d681741b8ed63c3f41560491123ae2ccd599078abd846f8575f2b");
268        let tx_hash = H256(tx_hash);
269        let tx = provider.get_transaction(tx_hash).await?;
270
271        println!("tx: {tx:#?}",);
272
273        abigen!(
274            IERC20,
275            r#"[
276                function totalSupply() external view returns (uint256)
277            ]"#;
278        );
279
280        let provider = Arc::new(provider);
281        let address = H160::from_str("0xf132e6112e358a36fccf4660d082c9dae3da7411")?;
282        let state_bridge = IERC20::new(address, provider.clone());
283        let response = state_bridge.total_supply().call().await?;
284
285        println!("Contract call response: {:#?}", response);
286
287        Ok(())
288    }
289}