1use crate::crypto::address::Address;
2use crate::crypto::bip32::DerivedSecretKey;
3use crate::crypto::bip44::ChildNumber;
4use crate::crypto::SecretKey;
5use crate::signer::{RosettaAccount, RosettaPublicKey, Signer};
6use crate::types::{
7 AccountBalanceRequest, AccountCoinsRequest, AccountFaucetRequest, AccountIdentifier, Amount,
8 BlockIdentifier, BlockTransaction, Coin, ConstructionMetadataRequest,
9 ConstructionSubmitRequest, PublicKey, SearchTransactionsRequest, SearchTransactionsResponse,
10 TransactionIdentifier,
11};
12use crate::{BlockchainConfig, Client, TransactionBuilder};
13use anyhow::{Context as _, Result};
14use futures::{Future, Stream};
15use rosetta_core::types::{
16 Block, BlockRequest, BlockTransactionRequest, BlockTransactionResponse, CallRequest,
17 CallResponse, PartialBlockIdentifier,
18};
19use serde_json::{json, Value};
20use std::pin::Pin;
21use std::task::{Context, Poll};
22use surf::utils::async_trait;
23
24pub enum GenericTransactionBuilder {
25 Ethereum(rosetta_tx_ethereum::EthereumTransactionBuilder),
26 Polkadot(rosetta_tx_polkadot::PolkadotTransactionBuilder),
27}
28
29impl GenericTransactionBuilder {
30 pub fn new(config: &BlockchainConfig) -> Result<Self> {
31 Ok(match config.blockchain {
32 "astar" => Self::Ethereum(Default::default()),
33 "ethereum" => Self::Ethereum(Default::default()),
34 "polkadot" => Self::Polkadot(Default::default()),
35 _ => anyhow::bail!("unsupported blockchain"),
36 })
37 }
38
39 pub fn transfer(&self, address: &Address, amount: u128) -> Result<serde_json::Value> {
40 Ok(match self {
41 Self::Ethereum(tx) => serde_json::to_value(tx.transfer(address, amount)?)?,
42 Self::Polkadot(tx) => serde_json::to_value(tx.transfer(address, amount)?)?,
43 })
44 }
45
46 pub fn method_call(
47 &self,
48 contract: &str,
49 method: &str,
50 params: &[String],
51 amount: u128,
52 ) -> Result<serde_json::Value> {
53 Ok(match self {
54 Self::Ethereum(tx) => {
55 serde_json::to_value(tx.method_call(contract, method, params, amount)?)?
56 }
57 Self::Polkadot(tx) => {
58 serde_json::to_value(tx.method_call(contract, method, params, amount)?)?
59 }
60 })
61 }
62
63 pub fn deploy_contract(&self, contract_binary: Vec<u8>) -> Result<serde_json::Value> {
64 Ok(match self {
65 Self::Ethereum(tx) => serde_json::to_value(tx.deploy_contract(contract_binary)?)?,
66 Self::Polkadot(tx) => serde_json::to_value(tx.deploy_contract(contract_binary)?)?,
67 })
68 }
69
70 pub fn create_and_sign(
71 &self,
72 config: &BlockchainConfig,
73 metadata_params: serde_json::Value,
74 metadata: serde_json::Value,
75 secret_key: &SecretKey,
76 ) -> Vec<u8> {
77 match self {
78 Self::Ethereum(tx) => {
79 let metadata_params = serde_json::from_value(metadata_params).unwrap();
80 let metadata = serde_json::from_value(metadata).unwrap();
81 tx.create_and_sign(config, &metadata_params, &metadata, secret_key)
82 }
83 Self::Polkadot(tx) => {
84 let metadata_params = serde_json::from_value(metadata_params).unwrap();
85 let metadata = serde_json::from_value(metadata).unwrap();
86 tx.create_and_sign(config, &metadata_params, &metadata, secret_key)
87 }
88 }
89 }
90}
91
92pub struct Wallet {
94 config: BlockchainConfig,
95 client: Client,
96 account: AccountIdentifier,
97 secret_key: DerivedSecretKey,
98 public_key: PublicKey,
99 tx: GenericTransactionBuilder,
100}
101
102impl Wallet {
103 pub fn new(config: BlockchainConfig, signer: &Signer, client: Client) -> Result<Self> {
105 let tx = GenericTransactionBuilder::new(&config)?;
106 let secret_key = if config.bip44 {
107 signer
108 .bip44_account(config.algorithm, config.coin, 0)?
109 .derive(ChildNumber::non_hardened_from_u32(0))?
110 } else {
111 signer.master_key(config.algorithm)?.clone()
112 };
113 let public_key = secret_key.public_key();
114 let account = public_key.to_address(config.address_format).to_rosetta();
115 let public_key = public_key.to_rosetta();
116 Ok(Self {
117 config,
118 client,
119 account,
120 secret_key,
121 public_key,
122 tx,
123 })
124 }
125
126 pub fn config(&self) -> &BlockchainConfig {
128 &self.config
129 }
130
131 pub fn client(&self) -> &Client {
133 &self.client
134 }
135
136 pub fn public_key(&self) -> &PublicKey {
138 &self.public_key
139 }
140
141 pub fn account(&self) -> &AccountIdentifier {
143 &self.account
144 }
145
146 pub async fn status(&self) -> Result<BlockIdentifier> {
148 let status = self.client.network_status(self.config.network()).await?;
149 Ok(status.current_block_identifier)
150 }
151
152 pub async fn balance(&self) -> Result<Amount> {
154 let balance = self
155 .client
156 .account_balance(&AccountBalanceRequest {
157 network_identifier: self.config.network(),
158 account_identifier: self.account.clone(),
159 block_identifier: None,
160 currencies: Some(vec![self.config.currency()]),
161 })
162 .await?;
163 Ok(balance.balances[0].clone())
164 }
165
166 pub async fn block(&self, data: PartialBlockIdentifier) -> Result<Block> {
169 let req = BlockRequest {
170 network_identifier: self.config.network(),
171 block_identifier: data,
172 };
173 let block = self.client.block(&req).await?;
174 block.block.context("block not found")
175 }
176
177 pub async fn block_transaction(
182 &self,
183 block_identifer: BlockIdentifier,
184 tx_identifier: TransactionIdentifier,
185 ) -> Result<BlockTransactionResponse> {
186 let req = BlockTransactionRequest {
187 network_identifier: self.config.network(),
188 block_identifier: block_identifer,
189 transaction_identifier: tx_identifier,
190 };
191 let block = self.client.block_transaction(&req).await?;
192 Ok(block)
193 }
194
195 pub async fn call(&self, method: String, params: &serde_json::Value) -> Result<CallResponse> {
199 let req = CallRequest {
200 network_identifier: self.config.network(),
201 method,
202 parameters: params.clone(),
203 };
204 let response = self.client.call(&req).await?;
205 Ok(response)
206 }
207
208 pub async fn coins(&self) -> Result<Vec<Coin>> {
210 let coins = self
211 .client
212 .account_coins(&AccountCoinsRequest {
213 network_identifier: self.config.network(),
214 account_identifier: self.account.clone(),
215 include_mempool: false,
216 currencies: Some(vec![self.config.currency()]),
217 })
218 .await?;
219 Ok(coins.coins)
220 }
221
222 pub async fn metadata(&self, metadata_params: serde_json::Value) -> Result<serde_json::Value> {
226 let req = ConstructionMetadataRequest {
227 network_identifier: self.config.network(),
228 options: Some(metadata_params),
229 public_keys: vec![self.public_key.clone()],
230 };
231 let response = self.client.construction_metadata(&req).await?;
232 Ok(response.metadata)
233 }
234
235 pub async fn submit(&self, transaction: &[u8]) -> Result<TransactionIdentifier> {
239 let req = ConstructionSubmitRequest {
240 network_identifier: self.config.network(),
241 signed_transaction: hex::encode(transaction),
242 };
243 let submit = self.client.construction_submit(&req).await?;
244 Ok(submit.transaction_identifier)
245 }
246
247 pub async fn construct(&self, metadata_params: Value) -> Result<TransactionIdentifier> {
249 let metadata = self.metadata(metadata_params.clone()).await?;
250 let transaction = self.tx.create_and_sign(
251 &self.config,
252 metadata_params,
253 metadata,
254 self.secret_key.secret_key(),
255 );
256 self.submit(&transaction).await
257 }
258
259 pub async fn transfer(
264 &self,
265 account: &AccountIdentifier,
266 amount: u128,
267 ) -> Result<TransactionIdentifier> {
268 let address = Address::new(self.config.address_format, account.address.clone());
269 let metadata_params = self.tx.transfer(&address, amount)?;
270 self.construct(metadata_params).await
271 }
272
273 pub async fn faucet(&self, faucet_parameter: u128) -> Result<TransactionIdentifier> {
277 let req = AccountFaucetRequest {
278 network_identifier: self.config.network(),
279 account_identifier: self.account.clone(),
280 faucet_parameter,
281 };
282 let resp = self.client.account_faucet(&req).await?;
283 Ok(resp.transaction_identifier)
284 }
285
286 pub async fn transaction(&self, tx: TransactionIdentifier) -> Result<BlockTransaction> {
290 let req = SearchTransactionsRequest {
291 network_identifier: self.config().network(),
292 operator: None,
293 max_block: None,
294 offset: None,
295 limit: None,
296 transaction_identifier: Some(tx),
297 account_identifier: None,
298 coin_identifier: None,
299 currency: None,
300 status: None,
301 r#type: None,
302 address: None,
303 success: None,
304 };
305 let resp = self.client.search_transactions(&req).await?;
306 anyhow::ensure!(resp.transactions.len() == 1);
307 Ok(resp.transactions[0].clone())
308 }
309
310 pub fn transactions(&self, limit: u16) -> TransactionStream {
312 let req = SearchTransactionsRequest {
313 network_identifier: self.config().network(),
314 operator: None,
315 max_block: None,
316 offset: None,
317 limit: Some(limit as i64),
318 transaction_identifier: None,
319 account_identifier: Some(self.account.clone()),
320 coin_identifier: None,
321 currency: None,
322 status: None,
323 r#type: None,
324 address: None,
325 success: None,
326 };
327 TransactionStream::new(self.client.clone(), req)
328 }
329}
330
331#[async_trait]
333pub trait EthereumExt {
334 async fn eth_deploy_contract(&self, bytecode: Vec<u8>) -> Result<TransactionIdentifier>;
336 async fn eth_view_call(
338 &self,
339 contract_address: &str,
340 method_signature: &str,
341 params: &[String],
342 ) -> Result<CallResponse>;
343 async fn eth_send_call(
345 &self,
346 contract_address: &str,
347 method_signature: &str,
348 params: &[String],
349 amount: u128,
350 ) -> Result<TransactionIdentifier>;
351 async fn eth_send_call_estimate_gas(
353 &self,
354 contract_address: &str,
355 method_signature: &str,
356 params: &[String],
357 amount: u128,
358 ) -> Result<u128>;
359 async fn eth_storage(&self, contract_address: &str, storage_slot: &str)
361 -> Result<CallResponse>;
362 async fn eth_storage_proof(
364 &self,
365 contract_address: &str,
366 storage_slot: &str,
367 ) -> Result<CallResponse>;
368 async fn eth_transaction_receipt(&self, tx_hash: &str) -> Result<CallResponse>;
370}
371
372#[async_trait]
373impl EthereumExt for Wallet {
374 async fn eth_deploy_contract(&self, bytecode: Vec<u8>) -> Result<TransactionIdentifier> {
375 let metadata_params = self.tx.deploy_contract(bytecode)?;
376 self.construct(metadata_params).await
377 }
378
379 async fn eth_send_call(
380 &self,
381 contract_address: &str,
382 method_signature: &str,
383 params: &[String],
384 amount: u128,
385 ) -> Result<TransactionIdentifier> {
386 let metadata_params =
387 self.tx
388 .method_call(contract_address, method_signature, params, amount)?;
389 self.construct(metadata_params).await
390 }
391
392 async fn eth_send_call_estimate_gas(
393 &self,
394 contract_address: &str,
395 method_signature: &str,
396 params: &[String],
397 amount: u128,
398 ) -> Result<u128> {
399 let metadata_params =
400 self.tx
401 .method_call(contract_address, method_signature, params, amount)?;
402 let metadata = self.metadata(metadata_params).await?;
403 let metadata: rosetta_config_ethereum::EthereumMetadata = serde_json::from_value(metadata)?;
404 Ok(rosetta_tx_ethereum::U256(metadata.gas_limit).as_u128())
405 }
406
407 async fn eth_view_call(
408 &self,
409 contract_address: &str,
410 method_signature: &str,
411 params: &[String],
412 ) -> Result<CallResponse> {
413 let method = format!("{}-{}-call", contract_address, method_signature);
414 self.call(method, &json!(params)).await
415 }
416
417 async fn eth_storage(
418 &self,
419 contract_address: &str,
420 storage_slot: &str,
421 ) -> Result<CallResponse> {
422 let method = format!("{}-{}-storage", contract_address, storage_slot);
423 self.call(method, &json!({})).await
424 }
425
426 async fn eth_storage_proof(
427 &self,
428 contract_address: &str,
429 storage_slot: &str,
430 ) -> Result<CallResponse> {
431 let method = format!("{}-{}-storage_proof", contract_address, storage_slot);
432 self.call(method, &json!({})).await
433 }
434
435 async fn eth_transaction_receipt(&self, tx_hash: &str) -> Result<CallResponse> {
436 let call_method = format!("{}--transaction_receipt", tx_hash);
437 self.call(call_method, &json!({})).await
438 }
439}
440
441pub struct TransactionStream {
443 client: Client,
444 request: SearchTransactionsRequest,
445 future: Option<Pin<Box<dyn Future<Output = Result<SearchTransactionsResponse>> + 'static>>>,
446 finished: bool,
447 total_count: Option<i64>,
448}
449
450impl TransactionStream {
451 fn new(client: Client, mut request: SearchTransactionsRequest) -> Self {
452 request.offset = Some(0);
453 Self {
454 client,
455 request,
456 future: None,
457 finished: false,
458 total_count: None,
459 }
460 }
461
462 pub fn total_count(&self) -> Option<i64> {
464 self.total_count
465 }
466}
467
468impl Stream for TransactionStream {
469 type Item = Result<Vec<BlockTransaction>>;
470
471 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
472 loop {
473 if self.finished {
474 return Poll::Ready(None);
475 } else if let Some(future) = self.future.as_mut() {
476 futures::pin_mut!(future);
477 match future.poll(cx) {
478 Poll::Pending => return Poll::Pending,
479 Poll::Ready(Ok(response)) => {
480 self.future.take();
481 self.request.offset = response.next_offset;
482 self.total_count = Some(response.total_count);
483 if response.transactions.len() < self.request.limit.unwrap() as _ {
484 self.finished = true;
485 }
486 if response.transactions.is_empty() {
487 continue;
488 }
489 return Poll::Ready(Some(Ok(response.transactions)));
490 }
491 Poll::Ready(Err(error)) => {
492 self.future.take();
493 return Poll::Ready(Some(Err(error)));
494 }
495 };
496 } else {
497 let client = self.client.clone();
498 let request = self.request.clone();
499 self.future = Some(Box::pin(async move {
500 client.search_transactions(&request).await
501 }));
502 }
503 }
504 }
505}