Skip to content

Commit d4a3e12

Browse files
committed
Merge #49: Implement price-feed feature
236885d Implement a minimal BTC/USD price feed feature (Oleksandr Zahorodnyi) Pull request description: Usage: cargo run -p cli-client price-feed Fix: #40 ACKs for top commit: KyrylR: ACK 236885d Tree-SHA512: 113b09a76166e56b9ece6de34391f5838b68d747e4dd6828c04a2e8caab8168c4615f05785f8752c9a31f07fb64b9602082d8de7c4ef7a0f58c36cfcd319cf09
2 parents 90b9501 + 236885d commit d4a3e12

File tree

7 files changed

+88
-7
lines changed

7 files changed

+88
-7
lines changed

.gitignore

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ config.toml
66
debug/
77
target/
88

9-
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
10-
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
11-
Cargo.lock
12-
139
# These are backup files generated by rustfmt
1410
**/*.rs.bk
1511

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ contracts = { git = "https://github.com/BlockstreamResearch/simplicity-contracts
2323
cli-helper = { git = "https://github.com/BlockstreamResearch/simplicity-contracts.git", rev = "61616fb", package = "cli" }
2424
simplicityhl-core = { version = "0.4.0", features = ["encoding"] }
2525

26-
simplicityhl = { version = "0.4.0" }
26+
simplicityhl = { version = "0.4.1" }
2727

2828
[patch.crates-io]
2929
simplicity-sys = { git = "https://github.com/BlockstreamResearch/rust-simplicity", tag = "simplicity-sys-0.6.1" }

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

Lines changed: 20 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, PriceFetcherError, 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,32 @@ 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;
32+
let btc_result = tokio::task::spawn_blocking(move || fetch_btc_usd_price(&fetcher))
33+
.await
34+
.unwrap_or_else(|e| Err(PriceFetcherError::Internal(e.to_string())));
35+
36+
let btc_price = match btc_result {
37+
Ok(price) => format!("${price:.2}"),
38+
Err(PriceFetcherError::RateLimit) => "Rate limit exceeded".to_string(),
39+
Err(e) => {
40+
eprintln!("Fetcher error: {e}");
41+
"Price fetcher service unavailable".to_string()
42+
}
43+
};
44+
45+
println!("Current btc price: {btc_price}");
46+
println!("-----------------------------");
47+
println!();
48+
2949
let user_script_pubkey = wallet.signer().p2pk_address(config.address_params())?.script_pubkey();
3050

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

0 commit comments

Comments
 (0)