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 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 pub async fn new_with_options(config: TxSitterConfigOptions) -> Self {
65 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}