Skip to content

Commit a0b1d27

Browse files
committed
Implement a minimal BTC/USD price feed feature
1 parent 82493f4 commit a0b1d27

File tree

4 files changed

+80
-0
lines changed

4 files changed

+80
-0
lines changed

crates/cli-client/src/cli/positions.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::config::Config;
99
use crate::error::Error;
1010
use crate::metadata::ContractMetadata;
1111

12+
use crate::price_fetcher::{CoingeckoPriceFetcher, fetch_btc_usd_price};
1213
use coin_store::{Store, UtxoEntry, UtxoFilter, UtxoQueryResult, UtxoStore};
1314
use contracts::option_offer::{OPTION_OFFER_SOURCE, OptionOfferArguments, get_option_offer_address};
1415
use contracts::options::{OPTION_SOURCE, OptionsArguments, get_options_address};
@@ -19,13 +20,23 @@ use simplicityhl::elements::Address;
1920
type ContractInfoResult = Result<Option<(Vec<u8>, Vec<u8>, String)>, coin_store::StoreError>;
2021

2122
impl Cli {
23+
#[allow(clippy::too_many_lines)]
2224
pub(crate) async fn run_positions(&self, config: Config) -> Result<(), Error> {
2325
let wallet = self.get_wallet(&config).await?;
2426

2527
println!("Your Positions:");
2628
println!("===============");
2729
println!();
2830

31+
let fetcher = CoingeckoPriceFetcher::new();
32+
let btc_price = fetch_btc_usd_price(&fetcher)
33+
.map(|x| format!("{x:.2}$"))
34+
.unwrap_or("'Rate limit exceed'".to_string());
35+
36+
println!("Current btc price: {btc_price}");
37+
println!("-----------------------------");
38+
println!();
39+
2940
let user_script_pubkey = wallet.signer().p2pk_address(config.address_params())?.script_pubkey();
3041

3142
let options_filter = UtxoFilter::new().source(OPTION_SOURCE);

crates/cli-client/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,7 @@ pub enum Error {
5555

5656
#[error("Taproot pubkey generation error: {0}")]
5757
TaprootPubkeyGen(#[from] contracts::error::TaprootPubkeyGenError),
58+
59+
#[error("Price feed error: {0}")]
60+
PriceFeed(#[from] crate::price_fetcher::PriceFetcherError),
5861
}

crates/cli-client/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod explorer;
77
mod fee;
88
mod logging;
99
mod metadata;
10+
mod price_fetcher;
1011
mod signing;
1112
mod sync;
1213
mod wallet;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use crate::error::Error;
2+
use serde::Deserialize;
3+
use thiserror::Error;
4+
5+
#[derive(Error, Debug)]
6+
pub enum PriceFetcherError {
7+
#[error("Request error: {0}")]
8+
Request(#[from] minreq::Error),
9+
#[error("Rate limit exceeded (429)")]
10+
RateLimit,
11+
#[error("Response status error: {0}")]
12+
Status(i32),
13+
#[error("Parse error: {0}")]
14+
Parse(String),
15+
}
16+
17+
#[derive(Deserialize)]
18+
struct BitcoinResponse {
19+
bitcoin: BitcoinPrice,
20+
}
21+
22+
#[derive(Deserialize)]
23+
struct BitcoinPrice {
24+
usd: f64,
25+
}
26+
27+
pub trait PriceFetcher {
28+
fn fetch_price(&self) -> Result<f64, Error>;
29+
}
30+
31+
pub struct CoingeckoPriceFetcher;
32+
33+
impl CoingeckoPriceFetcher {
34+
const URL: &'static str = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd&precision=8";
35+
const TIMEOUT_SECS: u64 = 5;
36+
37+
pub fn new() -> Self {
38+
Self {}
39+
}
40+
}
41+
42+
impl PriceFetcher for CoingeckoPriceFetcher {
43+
fn fetch_price(&self) -> Result<f64, Error> {
44+
let resp = minreq::get(Self::URL)
45+
.with_header("User-Agent", "simplicity-dex/1.0")
46+
.with_timeout(Self::TIMEOUT_SECS)
47+
.send()
48+
.map_err(PriceFetcherError::from)?;
49+
50+
let price = match resp.status_code {
51+
200 => resp
52+
.json::<BitcoinResponse>()
53+
.map(|data| data.bitcoin.usd)
54+
.map_err(|e| PriceFetcherError::Parse(e.to_string()))?,
55+
429 => return Err(PriceFetcherError::RateLimit.into()),
56+
status => return Err(PriceFetcherError::Status(status).into()),
57+
};
58+
59+
Ok(price)
60+
}
61+
}
62+
63+
pub fn fetch_btc_usd_price<T: PriceFetcher>(fetcher: &T) -> Result<f64, Error> {
64+
fetcher.fetch_price()
65+
}

0 commit comments

Comments
 (0)