|
@ -1,20 +1,29 @@ |
|
|
use crate::model::WalletInfo; |
|
|
use crate::model::WalletInfo; |
|
|
use anyhow::{Context, Result}; |
|
|
use anyhow::{anyhow, bail, Context, Result}; |
|
|
use bdk::bitcoin::util::bip32::ExtendedPrivKey; |
|
|
use bdk::bitcoin::util::bip32::ExtendedPrivKey; |
|
|
use bdk::bitcoin::{Amount, PublicKey}; |
|
|
use bdk::bitcoin::util::psbt::PartiallySignedTransaction; |
|
|
|
|
|
use bdk::bitcoin::{Amount, PublicKey, Transaction, Txid}; |
|
|
use bdk::blockchain::{ElectrumBlockchain, NoopProgress}; |
|
|
use bdk::blockchain::{ElectrumBlockchain, NoopProgress}; |
|
|
use bdk::wallet::AddressIndex; |
|
|
use bdk::wallet::AddressIndex; |
|
|
use bdk::KeychainKind; |
|
|
use bdk::{electrum_client, Error, KeychainKind, SignOptions}; |
|
|
use cfd_protocol::{PartyParams, WalletExt}; |
|
|
use cfd_protocol::{PartyParams, WalletExt}; |
|
|
|
|
|
use rocket::serde::json::Value; |
|
|
use std::path::Path; |
|
|
use std::path::Path; |
|
|
|
|
|
use std::sync::Arc; |
|
|
use std::time::SystemTime; |
|
|
use std::time::SystemTime; |
|
|
|
|
|
use tokio::sync::Mutex; |
|
|
|
|
|
|
|
|
const SLED_TREE_NAME: &str = "wallet"; |
|
|
const SLED_TREE_NAME: &str = "wallet"; |
|
|
|
|
|
|
|
|
pub struct Wallet<B = ElectrumBlockchain, D = bdk::sled::Tree> { |
|
|
#[derive(Clone)] |
|
|
wallet: bdk::Wallet<B, D>, |
|
|
pub struct Wallet { |
|
|
|
|
|
wallet: Arc<Mutex<bdk::Wallet<ElectrumBlockchain, bdk::sled::Tree>>>, |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[derive(thiserror::Error, Debug, Clone, Copy)] |
|
|
|
|
|
#[error("The transaction is already in the blockchain")] |
|
|
|
|
|
pub struct TransactionAlreadyInBlockchain; |
|
|
|
|
|
|
|
|
impl Wallet { |
|
|
impl Wallet { |
|
|
pub async fn new( |
|
|
pub async fn new( |
|
|
electrum_rpc_url: &str, |
|
|
electrum_rpc_url: &str, |
|
@ -35,23 +44,27 @@ impl Wallet { |
|
|
ElectrumBlockchain::from(client), |
|
|
ElectrumBlockchain::from(client), |
|
|
)?; |
|
|
)?; |
|
|
|
|
|
|
|
|
|
|
|
let wallet = Arc::new(Mutex::new(wallet)); |
|
|
|
|
|
|
|
|
Ok(Self { wallet }) |
|
|
Ok(Self { wallet }) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
pub fn build_party_params( |
|
|
pub async fn build_party_params( |
|
|
&self, |
|
|
&self, |
|
|
amount: Amount, |
|
|
amount: Amount, |
|
|
identity_pk: PublicKey, |
|
|
identity_pk: PublicKey, |
|
|
) -> Result<PartyParams> { |
|
|
) -> Result<PartyParams> { |
|
|
self.wallet.build_party_params(amount, identity_pk) |
|
|
let wallet = self.wallet.lock().await; |
|
|
|
|
|
wallet.build_party_params(amount, identity_pk) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
pub fn sync(&self) -> Result<WalletInfo> { |
|
|
pub async fn sync(&self) -> Result<WalletInfo> { |
|
|
self.wallet.sync(NoopProgress, None)?; |
|
|
let wallet = self.wallet.lock().await; |
|
|
|
|
|
wallet.sync(NoopProgress, None)?; |
|
|
|
|
|
|
|
|
let balance = self.wallet.get_balance()?; |
|
|
let balance = wallet.get_balance()?; |
|
|
|
|
|
|
|
|
let address = self.wallet.get_address(AddressIndex::LastUnused)?.address; |
|
|
let address = wallet.get_address(AddressIndex::LastUnused)?.address; |
|
|
|
|
|
|
|
|
let wallet_info = WalletInfo { |
|
|
let wallet_info = WalletInfo { |
|
|
balance: Amount::from_sat(balance), |
|
|
balance: Amount::from_sat(balance), |
|
@ -61,4 +74,86 @@ impl Wallet { |
|
|
|
|
|
|
|
|
Ok(wallet_info) |
|
|
Ok(wallet_info) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub async fn sign( |
|
|
|
|
|
&self, |
|
|
|
|
|
mut psbt: PartiallySignedTransaction, |
|
|
|
|
|
) -> Result<PartiallySignedTransaction> { |
|
|
|
|
|
let wallet = self.wallet.lock().await; |
|
|
|
|
|
|
|
|
|
|
|
wallet |
|
|
|
|
|
.sign( |
|
|
|
|
|
&mut psbt, |
|
|
|
|
|
SignOptions { |
|
|
|
|
|
trust_witness_utxo: true, |
|
|
|
|
|
..Default::default() |
|
|
|
|
|
}, |
|
|
|
|
|
) |
|
|
|
|
|
.context("could not sign transaction")?; |
|
|
|
|
|
|
|
|
|
|
|
Ok(psbt) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub async fn try_broadcast_transaction(&self, tx: Transaction) -> Result<Txid> { |
|
|
|
|
|
let wallet = self.wallet.lock().await; |
|
|
|
|
|
// TODO: Optimize this match to be a map_err / more readable in general
|
|
|
|
|
|
let txid = tx.txid(); |
|
|
|
|
|
match wallet.broadcast(tx) { |
|
|
|
|
|
Ok(txid) => Ok(txid), |
|
|
|
|
|
Err(e) => { |
|
|
|
|
|
if let Error::Electrum(electrum_client::Error::Protocol(ref value)) = e { |
|
|
|
|
|
let error_code = match parse_rpc_protocol_error_code(value) { |
|
|
|
|
|
Ok(error_code) => error_code, |
|
|
|
|
|
Err(inner) => { |
|
|
|
|
|
eprintln!("Failed to parse error code from RPC message: {}", inner); |
|
|
|
|
|
return Err(anyhow!(e)); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
if error_code == i64::from(RpcErrorCode::RpcVerifyAlreadyInChain) { |
|
|
|
|
|
return Ok(txid); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Err(anyhow!(e)) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn parse_rpc_protocol_error_code(error_value: &Value) -> anyhow::Result<i64> { |
|
|
|
|
|
let json_map = match error_value { |
|
|
|
|
|
serde_json::Value::Object(map) => map, |
|
|
|
|
|
_ => bail!("Json error is not json object "), |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let error_code_value = match json_map.get("code") { |
|
|
|
|
|
Some(val) => val, |
|
|
|
|
|
None => bail!("No error code field"), |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let error_code_number = match error_code_value { |
|
|
|
|
|
serde_json::Value::Number(num) => num, |
|
|
|
|
|
_ => bail!("Error code is not a number"), |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
if let Some(int) = error_code_number.as_i64() { |
|
|
|
|
|
Ok(int) |
|
|
|
|
|
} else { |
|
|
|
|
|
bail!("Error code is not an unsigned integer") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Bitcoin error codes: https://github.com/bitcoin/bitcoin/blob/97d3500601c1d28642347d014a6de1e38f53ae4e/src/rpc/protocol.h#L23
|
|
|
|
|
|
pub enum RpcErrorCode { |
|
|
|
|
|
/// Transaction or block was rejected by network rules. Error code -27.
|
|
|
|
|
|
RpcVerifyAlreadyInChain, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
impl From<RpcErrorCode> for i64 { |
|
|
|
|
|
fn from(code: RpcErrorCode) -> Self { |
|
|
|
|
|
match code { |
|
|
|
|
|
RpcErrorCode::RpcVerifyAlreadyInChain => -27, |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|