Skip to content

Commit

Permalink
WIP: switching to value based API
Browse files Browse the repository at this point in the history
  • Loading branch information
rishflab committed Aug 2, 2021
1 parent 1733c42 commit 4479142
Show file tree
Hide file tree
Showing 14 changed files with 660 additions and 639 deletions.
180 changes: 180 additions & 0 deletions extension/wallet/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use crate::lib_wallet::{FeeEstimatesResponse, Utxo};
use crate::ESPLORA_API_URL;
use anyhow::{anyhow, bail, Context, Result};
use async_trait::async_trait;
use elements::encode::{deserialize, serialize_hex};
use elements::{Address, Transaction, TxOut, Txid};
use futures::stream::FuturesUnordered;
use futures::{StreamExt, TryStreamExt};
use reqwest::StatusCode;
use wasm_bindgen::UnwrapThrowExt;

pub async fn get_txouts<T: Send, FM: Fn(Utxo, TxOut) -> Result<Option<T>> + Copy + Send>(
address: Address,
filter_map: FM,
) -> Result<Vec<T>> {
let utxos = fetch_utxos(address).await?;

let txouts = utxos
.into_iter()
.map(move |utxo| async move {
let mut tx = fetch_transaction(utxo.txid).await?;
let txout = tx.output.remove(utxo.vout as usize);

filter_map(utxo, txout)
})
.collect::<FuturesUnordered<_>>()
.filter_map(|r| std::future::ready(r.transpose()))
.try_collect::<Vec<_>>()
.await?;

Ok(txouts)
}

/// Fetches a transaction.
///
/// This function makes use of the browsers local storage to avoid spamming the underlying source.
/// Transaction never change after they've been mined, hence we can cache those indefinitely.
pub async fn fetch_transaction(txid: Txid) -> Result<Transaction> {
let esplora_url = {
let guard = ESPLORA_API_URL.lock().expect_throw("can get lock");
guard.clone()
};

//todo!(consider caching)

let client = reqwest::Client::new();
let body = client
.get(format!("{}tx/{}/hex", esplora_url, txid))
.send()
.await?;
let body_text = body
.text()
.await
.with_context(|| "response is not a string")?;

Ok(deserialize(&hex::decode(body_text)?)?)
}

/// Fetch the UTXOs of an address.
///
/// UTXOs change over time and as such, this function never uses a cache.
async fn fetch_utxos(address: Address) -> Result<Vec<Utxo>> {
let esplora_url = {
let guard = ESPLORA_API_URL.lock().expect_throw("can get lock");
guard.clone()
};

let path = format!("address/{}/utxo", address);
let esplora_url = esplora_url.join(path.as_str())?;
let response = reqwest::get(esplora_url.clone())
.await
.context("failed to fetch UTXOs")?;

if response.status() == StatusCode::NOT_FOUND {
log::debug!(
"GET {} returned 404, defaulting to empty UTXO set",
esplora_url
);

return Ok(Vec::new());
}

if !response.status().is_success() {
let error_body = response.text().await?;
return Err(anyhow!(
"failed to fetch utxos, esplora returned '{}'",
error_body
));
}

response
.json::<Vec<Utxo>>()
.await
.context("failed to deserialize response")
}

async fn get_fee_estimates() -> Result<FeeEstimatesResponse> {
let esplora_url = {
let guard = ESPLORA_API_URL.lock().expect_throw("can get lock");
guard.clone()
};
let esplora_url = esplora_url.join("fee-estimates")?;

let fee_estimates = reqwest::get(esplora_url.clone())
.await
.with_context(|| format!("failed to GET {}", esplora_url))?
.json()
.await
.context("failed to deserialize fee estimates")?;

Ok(fee_estimates)
}

/// Fetch transaction history for the specified address.
///
/// Returns up to 50 mempool transactions plus the first 25 confirmed
/// transactions. See
/// https://github.com/blockstream/esplora/blob/master/API.md#get-addressaddresstxs
/// for more information.
async fn fetch_transaction_history(address: &Address) -> Result<Vec<Txid>> {
let esplora_url = {
let guard = ESPLORA_API_URL.lock().expect_throw("can get lock");
guard.clone()
};
let path = format!("address/{}/txs", address);
let url = esplora_url.join(path.as_str())?;
let response = reqwest::get(url.clone())
.await
.context("failed to fetch transaction history")?;

if !response.status().is_success() {
let error_body = response.text().await?;
return Err(anyhow!(
"failed to fetch transaction history, esplora returned '{}' from '{}'",
error_body,
url
));
}

#[derive(serde::Deserialize)]
struct HistoryElement {
txid: Txid,
}

let response = response
.json::<Vec<HistoryElement>>()
.await
.context("failed to deserialize response")?;

Ok(response.iter().map(|elem| elem.txid).collect())
}

pub async fn broadcast(tx: Transaction) -> Result<Txid> {
let esplora_url = {
let guard = ESPLORA_API_URL.lock().expect_throw("can get lock");
guard.clone()
};
let esplora_url = esplora_url.join("tx")?;
let client = reqwest::Client::new();

let response = client
.post(esplora_url.clone())
.body(serialize_hex(&tx))
.send()
.await?;

let code = response.status();

if !code.is_success() {
bail!("failed to successfully publish transaction");
}

let txid = response
.text()
.await?
.parse()
.context("failed to parse response body as txid")?;

Ok(txid)
}
Loading

0 comments on commit 4479142

Please sign in to comment.