Skip to content

Commit 6fb65df

Browse files
committed
Extract stateless functions into wallet library module
1 parent 5ff9ab6 commit 6fb65df

11 files changed

+152
-247
lines changed

bobtimus/src/lib.rs

+10-9
Original file line numberDiff line numberDiff line change
@@ -327,19 +327,20 @@ where
327327
)
328328
.unwrap();
329329

330+
let elementsd_client = self.elementsd.clone();
331+
|amount, asset| async move { Self::find_inputs(&elementsd_client, asset, amount).await };
332+
330333
let lender1 = lender0
331-
.interpret(
334+
.build_loan_transaction(
332335
&mut self.rng,
333336
&SECP256K1,
334-
{
335-
let elementsd_client = self.elementsd.clone();
336-
|amount, asset| async move {
337-
Self::find_inputs(&elementsd_client, asset, amount).await
338-
}
339-
},
340-
payload.into(),
337+
todo!(),
338+
(payload.collateral_amount, payload.collateral_inputs),
339+
todo!(),
340+
todo!(),
341+
todo!(),
342+
(payload.borrower_pk, payload.borrower_address),
341343
timelock,
342-
self.rate_service.latest_rate().bid.as_satodollar(),
343344
)
344345
.await
345346
.unwrap();

bobtimus/src/loan.rs

+3-15
Original file line numberDiff line numberDiff line change
@@ -63,23 +63,11 @@ pub struct Interest {
6363
pub struct LoanRequest {
6464
#[serde(with = "::elements::bitcoin::util::amount::serde::as_sat")]
6565
pub collateral_amount: Amount,
66-
collateral_inputs: Vec<Input>,
66+
pub collateral_inputs: Vec<Input>,
6767
#[serde(with = "::elements::bitcoin::util::amount::serde::as_sat")]
6868
fee_sats_per_vbyte: Amount,
69-
borrower_pk: PublicKey,
69+
pub borrower_pk: PublicKey,
7070
/// Loan term in days
7171
pub term: u32,
72-
borrower_address: Address,
73-
}
74-
75-
impl From<LoanRequest> for baru::loan::LoanRequest {
76-
fn from(loan_request: LoanRequest) -> Self {
77-
baru::loan::LoanRequest::new(
78-
loan_request.collateral_amount,
79-
loan_request.collateral_inputs,
80-
loan_request.fee_sats_per_vbyte,
81-
loan_request.borrower_pk,
82-
loan_request.borrower_address,
83-
)
84-
}
72+
pub borrower_address: Address,
8573
}

extension/wallet/src/esplora.rs

+39-4
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ use anyhow::{anyhow, bail, Context, Result};
22
use async_trait::async_trait;
33
use elements::{
44
encode::{deserialize, serialize_hex},
5-
Address, TxOut,
5+
Address, BlockHash, TxOut,
66
};
77
use elements::{Transaction, Txid};
88
use futures::{stream::FuturesUnordered, TryStreamExt};
99
use reqwest::{StatusCode, Url};
1010

1111
use crate::cache_storage::CacheStorage;
1212
use crate::ESPLORA_API_URL;
13+
use baru::client::GetTxOuts;
14+
use wasm_bindgen::UnwrapThrowExt;
1315

1416
pub struct EsploraClient {
1517
base_url: Url,
@@ -97,9 +99,43 @@ impl EsploraClient {
9799
}
98100
}
99101

102+
/// Represents a UTXO as it is modeled by esplora.
103+
///
104+
/// We ignore the commitments and asset IDs because we need to fetch the full transaction anyway.
105+
/// Hence, we don't even bother with deserializing it here.
106+
#[derive(serde::Deserialize, Debug, PartialEq, Clone, Copy)]
107+
pub struct Utxo {
108+
pub txid: Txid,
109+
pub vout: u32,
110+
pub status: UtxoStatus,
111+
}
112+
113+
impl From<Utxo> for baru::Utxo {
114+
fn from(utxo: Utxo) -> Self {
115+
baru::Utxo {
116+
txid: utxo.txid,
117+
vout: utxo.vout,
118+
status: baru::UtxoStatus {
119+
confirmed: utxo.status.confirmed,
120+
block_height: utxo.status.block_height,
121+
block_hash: utxo.status.block_hash,
122+
block_time: utxo.status.block_time,
123+
},
124+
}
125+
}
126+
}
127+
128+
#[derive(serde::Deserialize, Debug, PartialEq, Clone, Copy)]
129+
pub struct UtxoStatus {
130+
pub confirmed: bool,
131+
pub block_height: Option<u64>,
132+
pub block_hash: Option<BlockHash>,
133+
pub block_time: Option<u64>,
134+
}
135+
100136
#[async_trait]
101137
impl GetTxOuts for EsploraClient {
102-
async fn get_txouts(&self, address: Address) -> Result<Vec<(Utxo, TxOut)>> {
138+
async fn get_txouts(&self, address: Address) -> Result<Vec<(baru::Utxo, TxOut)>> {
103139
let base_url = self.base_url.clone();
104140
let utxos = fetch_utxos(base_url.clone(), address).await?;
105141

@@ -110,8 +146,7 @@ impl GetTxOuts for EsploraClient {
110146
async move {
111147
let mut tx = fetch_transaction(base_url, utxo.txid).await?;
112148
let txout = tx.output.remove(utxo.vout as usize);
113-
114-
Result::<_, anyhow::Error>::Ok((utxo, txout))
149+
Result::<_, anyhow::Error>::Ok((utxo.into(), txout))
115150
}
116151
})
117152
.collect::<FuturesUnordered<_>>()

extension/wallet/src/lib.rs

+2-30
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use baru::wallet::Chain;
22
use baru::wallet::Wallet;
33
use bip32::{Language, Mnemonic};
44
use conquer_once::Lazy;
5-
use elements::{bitcoin::util::amount::Amount, Address, AddressParams, Txid};
5+
use elements::{bitcoin::util::amount::Amount, Address, AddressParams, AssetId, Txid};
66
use futures::lock::Mutex;
77
use js_sys::Promise;
88
use reqwest::Url;
@@ -25,6 +25,7 @@ mod protocol;
2525
mod storage;
2626
mod wallet;
2727

28+
use crate::wallet::bip39_seed_words_w;
2829
pub use protocol::*;
2930

3031
static LOADED_WALLET: Lazy<Mutex<Option<Wallet>>> = Lazy::new(Mutex::default);
@@ -167,35 +168,6 @@ pub fn setup() {
167168
handler.forget();
168169
}
169170

170-
/// Generates 24 random seed words in english
171-
#[wasm_bindgen]
172-
pub fn bip39_seed_words() -> Result<JsValue, JsValue> {
173-
let mnemonic = wallet::bip39_seed_words(Language::English);
174-
let words = mnemonic.phrase();
175-
176-
Ok(JsValue::from_str(words))
177-
}
178-
179-
/// Create a new wallet from the given seed words (mnemonic) with the given name and password.
180-
///
181-
/// Fails if the seed words are invalid or if the wallet with this name already exists.
182-
/// The created wallet will be automatically loaded.
183-
#[wasm_bindgen]
184-
pub async fn create_new_bip39_wallet(
185-
name: String,
186-
seed_words: String,
187-
password: String,
188-
) -> Result<JsValue, JsValue> {
189-
let mnemonic = Mnemonic::new(seed_words, Language::English)
190-
.map_err(|e| JsValue::from_str(format!("Could not parse seed words: {:?}", e).as_str()))?;
191-
192-
map_err_from_anyhow!(
193-
wallet::create_from_bip39(name, mnemonic, password, &LOADED_WALLET).await
194-
)?;
195-
196-
Ok(JsValue::null())
197-
}
198-
199171
/// Retrieve the latest block height from Esplora
200172
#[wasm_bindgen]
201173
pub async fn get_block_height() -> Result<JsValue, JsValue> {

extension/wallet/src/protocol/extract_loan.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -90,19 +90,19 @@ async fn extract_loan_w(
9090
let loan_txid = borrower.loan_transaction().txid();
9191
let loan_details = LoanDetails::new(
9292
btc_asset_id,
93-
borrower1.collateral_amount(),
93+
borrower.collateral_amount(),
9494
collateral_balance,
9595
usdt_asset_id,
96-
borrower1.principal_amount(),
96+
borrower.principal_amount(),
9797
principal_balance,
98-
*timelock,
98+
*timelock as u64,
9999
loan_txid,
100100
);
101101

102102
storage
103103
.set_item(
104104
"borrower_state",
105-
serde_json::to_string(&(borrower1, loan_details.clone())).map_err(Error::Serialize)?,
105+
serde_json::to_string(&(borrower, loan_details.clone())).map_err(Error::Serialize)?,
106106
)
107107
.map_err(Error::Save)?;
108108

extension/wallet/src/protocol/make_create_swap_payload.rs

+23-63
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::esplora::EsploraClient;
22
use crate::wallet::current;
33
use crate::{BTC_ASSET_ID, ESPLORA_API_URL, LOADED_WALLET, USDT_ASSET_ID};
4+
use baru::avg_vbytes;
45
use baru::wallet::Wallet;
56
use elements::bitcoin::secp256k1::SecretKey;
67
use elements::bitcoin::util::amount::Denomination;
@@ -72,91 +73,50 @@ async fn make_create_swap_payload(
7273
sell_asset: AssetId,
7374
fee_asset: AssetId,
7475
) -> Result<CreateSwapPayload, Error> {
76+
let (bobs_fee_rate, fee_offset) = if fee_asset == sell_asset {
77+
// Bob currently hardcodes a fee-rate of 1 sat / vbyte, hence
78+
// there is no need for us to perform fee estimation. Later
79+
// on, both parties should probably agree on a block-target
80+
// and use the same estimation service.
81+
let bobs_fee_rate = Amount::ONE_SAT;
82+
let fee_offset = calculate_fee_offset(bobs_fee_rate);
83+
84+
(bobs_fee_rate.as_sat() as f32, fee_offset)
85+
} else {
86+
(0.0, Amount::ZERO)
87+
};
88+
7589
let client = {
7690
let guard = ESPLORA_API_URL.lock().expect_throw("can get lock");
7791
EsploraClient::new(guard.clone())
7892
};
7993

80-
let (utxos, secret_key, address, blinding_key) = {
94+
let (outputs, secret_key, address, blinding_key) = {
8195
let mut wallet = current(&wallet_name, &LOADED_WALLET)
8296
.await
8397
.map_err(Error::LoadWallet)?;
8498
wallet.sync(&client).await.map_err(Error::SyncWallet)?;
99+
let outputs = wallet
100+
.coin_selection(sell_amount, sell_asset, bobs_fee_rate, fee_offset)
101+
.map_err(Error::CoinSelection)?;
85102
(
86-
wallet.utxos(),
103+
outputs,
87104
wallet.secret_key(),
88105
wallet.address(),
89106
wallet.blinding_secret_key(),
90107
)
91108
};
92109

93-
let utxos = utxos
94-
.into_iter()
95-
.filter_map(|(utxo, txout)| {
96-
let outpoint = OutPoint {
97-
txid: utxo.txid,
98-
vout: utxo.vout,
99-
};
100-
101-
let unblinded_txout = match txout.unblind(SECP256K1, secret_key) {
102-
Ok(txout) => txout,
103-
Err(_) => {
104-
log::warn!("could not unblind utxo {}, ignoring", outpoint);
105-
return None;
106-
}
107-
};
108-
let candidate_asset = unblinded_txout.asset;
109-
110-
if candidate_asset == sell_asset {
111-
Some(coin_selection::Utxo {
112-
outpoint,
113-
value: unblinded_txout.value,
114-
script_pubkey: txout.script_pubkey,
115-
asset: candidate_asset,
116-
})
117-
} else {
118-
log::debug!(
119-
"utxo {} with asset id {} is not the sell asset, ignoring",
120-
outpoint,
121-
candidate_asset
122-
);
123-
None
124-
}
125-
})
126-
.collect::<Vec<_>>();
127-
128-
let (bobs_fee_rate, fee_offset) = if fee_asset == sell_asset {
129-
// Bob currently hardcodes a fee-rate of 1 sat / vbyte, hence
130-
// there is no need for us to perform fee estimation. Later
131-
// on, both parties should probably agree on a block-target
132-
// and use the same estimation service.
133-
let bobs_fee_rate = Amount::from_sat(1);
134-
let fee_offset = calculate_fee_offset(bobs_fee_rate);
135-
136-
(bobs_fee_rate, fee_offset)
137-
} else {
138-
(Amount::ZERO, Amount::ZERO)
139-
};
140-
141-
let output = coin_select(
142-
utxos,
143-
sell_amount,
144-
bobs_fee_rate.as_sat() as f32,
145-
fee_offset,
146-
)
147-
.map_err(Error::CoinSelection)?;
148-
149110
Ok(CreateSwapPayload {
150111
address,
151-
alice_inputs: output
152-
.coins
112+
alice_inputs: outputs
153113
.into_iter()
154114
.map(|utxo| SwapUtxo {
155-
outpoint: utxo.outpoint,
115+
outpoint: utxo.txin,
156116
blinding_key,
157117
})
158118
.collect(),
159-
amount: output.target_amount,
119+
amount: sell_amount,
160120
})
161121
}
162122

@@ -167,7 +127,7 @@ pub enum Error {
167127
#[error("Could not sync waller: {0}")]
168128
SyncWallet(anyhow::Error),
169129
#[error("Coin selection: {0}")]
170-
CoinSelection(coin_selection::Error),
130+
CoinSelection(anyhow::Error),
171131
}
172132
/// Calculate the fee offset required for the coin selection algorithm.
173133
///

0 commit comments

Comments
 (0)