use ::bitcoin::{Amount, OutPoint, secp256k1::SecretKey};
use thiserror::Error;
use crate::{
musig::MusigError,
script::{BitcoinSwapScript, LiquidSwapScript},
};
pub mod bitcoin;
mod context;
pub mod liquid;
pub mod utils;
pub(crate) use self::bitcoin::{
bitcoin_tx_fee, decode_tx_hex, encode_tx_hex, parse_destination, sat_per_vb,
};
pub use self::{
bitcoin::{
CLAIM_VSIZE, KEYPATH_VSIZE, MAX_DUST, OPENING_VSIZE, REFUND_VSIZE, extract_preimage,
},
context::{ChainCtx, LiquidChainCtx},
liquid::{liquid_spend_input, validate_liquid_prevout},
};
#[derive(Clone, Debug, Error)]
pub enum TxError {
#[error("no inputs")]
NoInputs,
#[error("duplicate input {0}")]
DuplicateInput(String),
#[error("{count} inputs exceed the limit of {max}")]
TooManyInputs { count: usize, max: usize },
#[error("input amount overflow")]
InputOverflow,
#[error("fee overflow")]
FeeOverflow,
#[error("fee {fee_sat} sat exceeds input value {input_sat} sat")]
InsufficientValue { input_sat: u64, fee_sat: u64 },
#[error("invalid sighash: {0}")]
InvalidSighash(String),
#[error("invalid destination address: {0}")]
InvalidDestination(String),
#[error("invalid fee rate: {0} sat/vB")]
InvalidFeeRate(f64),
#[error("output value {output_sat} sat is below the minimum {minimum_sat} sat")]
DustOutput { output_sat: u64, minimum_sat: u64 },
#[error("fee {fee_sat} sat is below required fee {required_sat} sat")]
InsufficientFee { fee_sat: u64, required_sat: u64 },
#[error("invalid prevout: {0}")]
InvalidPrevout(String),
#[error("unsupported Liquid asset {actual}; expected policy asset {policy_asset}")]
UnsupportedLiquidAsset {
actual: elements::issuance::AssetId,
policy_asset: elements::issuance::AssetId,
},
#[error("invalid Liquid blinding factors: {0}")]
InvalidBlindingFactors(String),
#[error("invalid Liquid network: {0}")]
InvalidLiquidNetwork(String),
#[error("invalid Liquid transaction: {0}")]
InvalidLiquidTransaction(String),
#[error("invalid key: {0}")]
InvalidKey(String),
#[error("confidential output: {0}")]
ConfidentialOutput(String),
#[error("tx decode: {0}")]
Decode(String),
#[error(transparent)]
Musig(#[from] MusigError),
}
#[allow(clippy::large_enum_variant)]
pub enum KeypathSpendParams {
Bitcoin {
inputs: Vec<(OutPoint, Amount)>,
script: BitcoinSwapScript,
claim_privkey: SecretKey,
refund_privkey: SecretKey,
},
Liquid {
inputs: Vec<(elements::OutPoint, elements::TxOut)>,
blinding_key: SecretKey,
script: LiquidSwapScript,
genesis_hash: elements::BlockHash,
policy_asset: elements::AssetId,
claim_privkey: SecretKey,
refund_privkey: SecretKey,
},
}
pub type ClaimByKeypathParams = KeypathSpendParams;
pub type RefundByKeypathParams = KeypathSpendParams;
#[allow(clippy::large_enum_variant)]
pub enum RefundByCsvParams {
Bitcoin {
inputs: Vec<(OutPoint, Amount)>,
script: BitcoinSwapScript,
refund_privkey: SecretKey,
},
Liquid {
inputs: Vec<(elements::OutPoint, elements::TxOut)>,
blinding_key: SecretKey,
script: LiquidSwapScript,
genesis_hash: elements::BlockHash,
policy_asset: elements::AssetId,
refund_privkey: SecretKey,
},
}
#[allow(clippy::large_enum_variant)]
pub enum ClaimByPreimageParams {
Bitcoin {
inputs: Vec<(OutPoint, Amount)>,
script: BitcoinSwapScript,
claim_privkey: SecretKey,
preimage: [u8; 32],
},
Liquid {
inputs: Vec<(elements::OutPoint, elements::TxOut)>,
blinding_key: SecretKey,
script: LiquidSwapScript,
genesis_hash: elements::BlockHash,
policy_asset: elements::AssetId,
claim_privkey: SecretKey,
preimage: [u8; 32],
},
}
pub fn build_claim_by_keypath(
params: ClaimByKeypathParams,
destination: &str,
fee_rate: f64,
) -> Result<String, TxError> {
build_keypath_spend(params, destination, fee_rate)
}
pub fn build_refund_by_keypath(
params: RefundByKeypathParams,
destination: &str,
fee_rate: f64,
) -> Result<String, TxError> {
build_keypath_spend(params, destination, fee_rate)
}
fn build_keypath_spend(
params: KeypathSpendParams,
destination: &str,
fee_rate: f64,
) -> Result<String, TxError> {
match params {
KeypathSpendParams::Bitcoin {
inputs,
script,
claim_privkey,
refund_privkey,
} => {
let tx = bitcoin::spend_by_keypath(
&inputs,
&script,
&claim_privkey,
&refund_privkey,
&parse_destination(destination)?,
sat_per_vb(fee_rate)?,
)?;
Ok(encode_tx_hex(&tx))
}
KeypathSpendParams::Liquid {
inputs,
blinding_key,
script,
genesis_hash,
policy_asset,
claim_privkey,
refund_privkey,
} => {
let destination = liquid::parse_destination(destination)?;
let tx = liquid::spend_by_keypath(
&claim_privkey,
&refund_privkey,
&destination,
fee_rate,
&inputs,
&blinding_key,
script,
genesis_hash,
policy_asset,
)?;
Ok(liquid::encode_tx_hex(&tx))
}
}
}
pub fn build_refund_by_csv(
params: RefundByCsvParams,
destination: &str,
fee_rate: f64,
) -> Result<String, TxError> {
match params {
RefundByCsvParams::Bitcoin {
inputs,
script,
refund_privkey,
} => {
let tx = bitcoin::refund_by_csv(
&inputs,
&script,
&refund_privkey,
&parse_destination(destination)?,
sat_per_vb(fee_rate)?,
)?;
Ok(encode_tx_hex(&tx))
}
RefundByCsvParams::Liquid {
inputs,
blinding_key,
script,
genesis_hash,
policy_asset,
refund_privkey,
} => {
let destination = liquid::parse_destination(destination)?;
let tx = liquid::refund_by_csv(
&inputs,
&blinding_key,
&script,
&refund_privkey,
&destination,
fee_rate,
genesis_hash,
policy_asset,
)?;
Ok(liquid::encode_tx_hex(&tx))
}
}
}
pub fn build_claim_by_preimage(
params: ClaimByPreimageParams,
destination: &str,
fee_rate: f64,
) -> Result<String, TxError> {
match params {
ClaimByPreimageParams::Bitcoin {
inputs,
script,
claim_privkey,
preimage,
} => {
let tx = bitcoin::claim_by_preimage(
&inputs,
&script,
&claim_privkey,
&preimage,
&parse_destination(destination)?,
sat_per_vb(fee_rate)?,
)?;
Ok(encode_tx_hex(&tx))
}
ClaimByPreimageParams::Liquid {
inputs,
blinding_key,
script,
genesis_hash,
policy_asset,
claim_privkey,
preimage,
} => {
let destination = liquid::parse_destination(destination)?;
let tx = liquid::claim_by_preimage(
&inputs,
&blinding_key,
&script,
&claim_privkey,
&preimage,
&destination,
fee_rate,
genesis_hash,
policy_asset,
)?;
Ok(liquid::encode_tx_hex(&tx))
}
}
}