diff --git a/bobtimus/src/amounts.rs b/bobtimus/src/amounts.rs index 7e71d342..4cdf5fb9 100644 --- a/bobtimus/src/amounts.rs +++ b/bobtimus/src/amounts.rs @@ -1,6 +1,9 @@ use anyhow::{anyhow, Context, Result}; use elements::bitcoin::{Amount, Denomination}; -use rust_decimal::{prelude::ToPrimitive, Decimal}; +use rust_decimal::{ + prelude::{FromPrimitive, ToPrimitive}, + Decimal, RoundingStrategy, +}; use serde::{Deserialize, Serialize}; use std::{convert::TryFrom, fmt::Debug}; @@ -43,6 +46,10 @@ impl Rate { let btc = Decimal::from(satodollars) .checked_div(Decimal::from(self.ask.as_satodollar())) .ok_or_else(|| anyhow!("division overflow"))?; + + // we round to 1 satoshi + let btc = btc.round_dp_with_strategy(8, RoundingStrategy::MidpointAwayFromZero); + let btc = btc .to_f64() .ok_or_else(|| anyhow!("decimal cannot be represented as f64"))?; @@ -109,6 +116,11 @@ impl TryFrom for LiquidUsdt { type Error = anyhow::Error; fn try_from(value: f64) -> Result { + let value = Decimal::from_f64(value) + .with_context(|| format!("LiquidUsdt amount cannot be parsed from float {}", value))? + .round_dp_with_strategy(8, RoundingStrategy::MidpointAwayFromZero) + .to_f64() + .unwrap(); Ok(LiquidUsdt(Amount::from_btc(value)?)) } } @@ -169,4 +181,10 @@ mod tests { assert_eq!(serialized, "{\"ask\":19313.52,\"bid\":19213.53}") } + + #[test] + fn test_rounding_liquid_usdt() { + let amount = LiquidUsdt::try_from(0.0000000123).unwrap(); + assert_eq!(amount.0, Amount::from_sat(1)); + } } diff --git a/extension/wallet/src/lib.rs b/extension/wallet/src/lib.rs index 4b1ba677..f6db9b23 100644 --- a/extension/wallet/src/lib.rs +++ b/extension/wallet/src/lib.rs @@ -2,12 +2,11 @@ use std::str::FromStr; use bip32::{Language, Mnemonic}; use conquer_once::Lazy; -use elements::{ - bitcoin::util::amount::{Amount, Denomination}, - Address, AddressParams, Txid, -}; +use elements::{bitcoin::util::amount::Amount, Address, AddressParams, Txid}; use futures::lock::Mutex; use js_sys::Promise; +use reqwest::Url; +use rust_decimal::{prelude::ToPrimitive, Decimal, RoundingStrategy}; use wasm_bindgen::{prelude::*, JsCast}; use web_sys::window; @@ -22,7 +21,6 @@ mod storage; mod wallet; use crate::{storage::Storage, wallet::*}; -use reqwest::Url; // TODO: make this configurable through extension option UI const DEFAULT_SAT_PER_VBYTE: u64 = 1; @@ -190,7 +188,7 @@ pub async fn make_buy_create_swap_payload( wallet_name: String, usdt: String, ) -> Result { - let usdt = map_err_from_anyhow!(Amount::from_str_in(&usdt, Denomination::Bitcoin))?; + let usdt = map_err_from_anyhow!(parse_to_bitcoin_amount(usdt))?; let payload = map_err_from_anyhow!( wallet::make_buy_create_swap_payload(wallet_name, &LOADED_WALLET, usdt).await )?; @@ -207,7 +205,7 @@ pub async fn make_sell_create_swap_payload( wallet_name: String, btc: String, ) -> Result { - let btc = map_err_from_anyhow!(Amount::from_str_in(&btc, Denomination::Bitcoin))?; + let btc = map_err_from_anyhow!(parse_to_bitcoin_amount(btc))?; let payload = map_err_from_anyhow!( wallet::make_sell_create_swap_payload(wallet_name, &LOADED_WALLET, btc).await )?; @@ -228,7 +226,7 @@ pub async fn make_loan_request( wallet_name: String, collateral: String, ) -> Result { - let collateral = map_err_from_anyhow!(Amount::from_str_in(&collateral, Denomination::Bitcoin))?; + let collateral = map_err_from_anyhow!(parse_to_bitcoin_amount(collateral))?; let loan_request = map_err_from_anyhow!( wallet::make_loan_request(wallet_name, &LOADED_WALLET, collateral).await )?; @@ -423,3 +421,13 @@ impl From for elements::Transaction { from.inner } } + +fn parse_to_bitcoin_amount(amount: String) -> anyhow::Result { + let parsed = Decimal::from_str(amount.as_str())?; + let rounded = parsed + .round_dp_with_strategy(8, RoundingStrategy::MidpointAwayFromZero) + .to_f64() + .ok_or_else(|| anyhow::anyhow!("decimal cannot be represented as f64"))?; + let amount = Amount::from_btc(rounded)?; + Ok(amount) +}