rosetta_client/
lib.rs

1//! Rosetta client.
2#![deny(missing_docs)]
3#![deny(warnings)]
4use crate::types::Amount;
5use anyhow::{Context, Result};
6use fraction::{BigDecimal, BigInt};
7use std::path::Path;
8
9pub use crate::client::Client;
10pub use crate::mnemonic::{generate_mnemonic, MnemonicStore};
11pub use crate::signer::{RosettaAccount, RosettaPublicKey, Signer};
12pub use crate::wallet::EthereumExt;
13pub use crate::wallet::Wallet;
14pub use rosetta_core::{crypto, types, BlockchainConfig, TransactionBuilder};
15
16mod client;
17mod mnemonic;
18mod signer;
19mod wallet;
20
21/// Converts an amount to a human readable string.
22pub fn amount_to_string(amount: &Amount) -> Result<String> {
23    let value = BigInt::parse_bytes(amount.value.as_bytes(), 10)
24        .ok_or_else(|| anyhow::anyhow!("invalid amount {:?}", amount))?;
25    let decimals = BigInt::pow(&10u32.into(), amount.currency.decimals);
26    let value = BigDecimal::from(value) / BigDecimal::from(decimals);
27    Ok(format!("{:.256} {}", value, amount.currency.symbol))
28}
29
30/// Parses a string into an amount using the equation `amount * 10 ** decimals`.
31///
32/// Example:
33/// `string_to_amount("1.1", 10)` converts 1.1 dot into 11_000_000_000 planc.
34pub fn string_to_amount(amount: &str, decimals: u32) -> Result<u128> {
35    let (amount, decimals): (u128, u32) = if let Some((main, rest)) = amount.split_once('.') {
36        let decimals = decimals
37            .checked_sub(rest.chars().count() as _)
38            .context("too many decimals")?;
39        let mut amount = main.to_string();
40        amount.push_str(rest);
41        (amount.parse()?, decimals)
42    } else {
43        (amount.parse()?, decimals)
44    };
45    amount
46        .checked_mul(u128::pow(10, decimals))
47        .context("u128 overflow")
48}
49
50/// Returns a blockchain config for a given blockchain and network.
51pub fn create_config(blockchain: &str, network: &str) -> Result<BlockchainConfig> {
52    match blockchain {
53        "bitcoin" => rosetta_config_bitcoin::config(network),
54        "ethereum" => rosetta_config_ethereum::config(network),
55        "astar" => rosetta_config_astar::config(network),
56        "polkadot" => rosetta_config_polkadot::config(network),
57        _ => anyhow::bail!("unsupported blockchain"),
58    }
59}
60
61/// Returns a signer for a given keyfile.
62pub fn create_signer(_keyfile: Option<&Path>) -> Result<Signer> {
63    let store = MnemonicStore::new(_keyfile)?;
64    let mnemonic = store.get_or_generate_mnemonic()?;
65    Signer::new(&mnemonic, "")
66}
67
68/// Returns a client instance.
69/// Parameters:
70/// - `blockchain`: blockchain name e.g. "bitcoin", "ethereum".
71/// - `network`: network name e.g. "dev".
72/// - `url`: rosetta server url.
73pub async fn create_client(
74    blockchain: Option<String>,
75    network: Option<String>,
76    url: Option<String>,
77) -> Result<(BlockchainConfig, Client)> {
78    let (blockchain, network) = if let (Some(blockchain), Some(network)) = (blockchain, network) {
79        (blockchain, network)
80    } else if let Some(url) = url.as_ref() {
81        let network = Client::new(url)?.network_list().await?[0].clone();
82        (network.blockchain, network.network)
83    } else {
84        anyhow::bail!("requires url or blockchain argument");
85    };
86    let config = create_config(&blockchain, &network)?;
87    let url = url.unwrap_or_else(|| config.connector_url());
88    let client = Client::new(&url)?;
89    Ok((config, client))
90}
91
92/// Returns a wallet instance.
93/// Parameters:
94/// - `blockchain`: blockchain name e.g. "bitcoin", "ethereum".
95/// - `network`: network name e.g. "dev".
96/// - `url`: rosetta server url.
97/// - `keyfile`: path to a keyfile.
98pub async fn create_wallet(
99    blockchain: Option<String>,
100    network: Option<String>,
101    url: Option<String>,
102    keyfile: Option<&Path>,
103) -> Result<Wallet> {
104    let (config, client) = create_client(blockchain, network, url).await?;
105    let signer = create_signer(keyfile)?;
106    Wallet::new(config, &signer, client)
107}