lwk_wasm 0.11.0

Liquid Wallet Kit - WASM
Documentation
use std::fmt::Display;

use lwk_wollet::{elements, UnvalidatedRecipient, Validated};
use wasm_bindgen::prelude::*;

use crate::{
    liquidex::ValidatedLiquidexProposal, Address, AssetId, Contract, Error, Network, OutPoint,
    Pset, Transaction, Wollet,
};

/// Wrapper of [`lwk_wollet::TxBuilder`]
#[wasm_bindgen]
#[derive(Debug)]
pub struct TxBuilder {
    inner: lwk_wollet::TxBuilder,
}

impl From<lwk_wollet::TxBuilder> for TxBuilder {
    fn from(value: lwk_wollet::TxBuilder) -> Self {
        Self { inner: value }
    }
}

impl From<TxBuilder> for lwk_wollet::TxBuilder {
    fn from(value: TxBuilder) -> Self {
        value.inner
    }
}

#[wasm_bindgen]
impl TxBuilder {
    /// Creates a transaction builder
    #[wasm_bindgen(constructor)]
    pub fn new(network: &Network) -> TxBuilder {
        TxBuilder {
            inner: lwk_wollet::TxBuilder::new(network.into()),
        }
    }

    /// Build the transaction
    pub fn finish(self, wollet: &Wollet) -> Result<Pset, Error> {
        Ok(self.inner.finish(wollet.as_ref())?.into())
    }

    /// Build the transaction for AMP0
    #[cfg(all(feature = "serial", target_arch = "wasm32"))]
    #[wasm_bindgen(js_name = finishForAmp0)]
    pub fn finish_for_amp0(self, wollet: &Wollet) -> Result<crate::amp0::Amp0Pset, Error> {
        Ok(self.inner.finish_for_amp0(wollet.as_ref())?.into())
    }

    /// Set the fee rate
    #[wasm_bindgen(js_name = feeRate)]
    pub fn fee_rate(self, fee_rate: Option<f32>) -> TxBuilder {
        self.inner.fee_rate(fee_rate).into()
    }

    /// Select all available L-BTC inputs
    #[wasm_bindgen(js_name = drainLbtcWallet)]
    pub fn drain_lbtc_wallet(self) -> TxBuilder {
        self.inner.drain_lbtc_wallet().into()
    }

    /// Sets the address to drain excess L-BTC to
    #[wasm_bindgen(js_name = drainLbtcTo)]
    pub fn drain_lbtc_to(self, address: Address) -> TxBuilder {
        self.inner.drain_lbtc_to(address.into()).into()
    }

    /// Add a recipient receiving L-BTC
    ///
    /// Errors if address's network is incompatible
    #[wasm_bindgen(js_name = addLbtcRecipient)]
    pub fn add_lbtc_recipient(self, address: &Address, satoshi: u64) -> Result<TxBuilder, Error> {
        let unvalidated_recipient = UnvalidatedRecipient::lbtc(address.to_string(), satoshi);
        // TODO error variant should contain the TxBuilder so that caller can recover it
        Ok(self
            .inner
            .add_unvalidated_recipient(&unvalidated_recipient)?
            .into())
    }

    /// Add a recipient receiving the given asset
    ///
    /// Errors if address's network is incompatible
    #[wasm_bindgen(js_name = addRecipient)]
    pub fn add_recipient(
        self,
        address: &Address,
        satoshi: u64,
        asset: &AssetId,
    ) -> Result<TxBuilder, Error> {
        let unvalidated_recipient = UnvalidatedRecipient {
            satoshi,
            address: address.to_string(),
            asset: asset.to_string(),
        };
        Ok(self
            .inner
            .add_unvalidated_recipient(&unvalidated_recipient)?
            .into())
    }

    /// Burn satoshi units of the given asset
    #[wasm_bindgen(js_name = addBurn)]
    pub fn add_burn(self, satoshi: u64, asset: &AssetId) -> TxBuilder {
        let unvalidated_recipient = UnvalidatedRecipient::burn(asset.to_string(), satoshi);
        self.inner
            .add_unvalidated_recipient(&unvalidated_recipient)
            .expect("recipient can't be invalid")
            .into()
    }

    /// Add explicit recipient
    #[wasm_bindgen(js_name = addExplicitRecipient)]
    pub fn add_explicit_recipient(
        self,
        address: Address,
        satoshi: u64,
        asset: &AssetId,
    ) -> Result<TxBuilder, Error> {
        Ok(self
            .inner
            .add_explicit_recipient(&address.into(), satoshi, (*asset).into())?
            .into())
    }

    /// Issue an asset, wrapper of [`lwk_wollet::TxBuilder::issue_asset()`]
    #[wasm_bindgen(js_name = issueAsset)]
    pub fn issue_asset(
        self,
        asset_sats: u64,
        asset_receiver: Option<Address>,
        token_sats: u64,
        token_receiver: Option<Address>,
        contract: Option<Contract>,
    ) -> Result<TxBuilder, Error> {
        Ok(self
            .inner
            .issue_asset(
                asset_sats,
                asset_receiver.map(Into::into),
                token_sats,
                token_receiver.map(Into::into),
                contract.map(Into::into),
            )?
            .into())
    }

    /// Reissue an asset, wrapper of [`lwk_wollet::TxBuilder::reissue_asset()`]
    #[wasm_bindgen(js_name = reissueAsset)]
    pub fn reissue_asset(
        self,
        asset_to_reissue: AssetId,
        satoshi_to_reissue: u64,
        asset_receiver: Option<Address>,
        issuance_tx: Option<Transaction>,
    ) -> Result<TxBuilder, Error> {
        Ok(self
            .inner
            .reissue_asset(
                asset_to_reissue.into(),
                satoshi_to_reissue,
                asset_receiver.map(Into::into),
                issuance_tx.map(Into::into),
            )?
            .into())
    }

    /// Manual coin selection, wrapper of [`lwk_wollet::TxBuilder::set_wallet_utxos()`]
    #[wasm_bindgen(js_name = setWalletUtxos)]
    pub fn set_wallet_utxos(self, outpoints: Vec<OutPoint>) -> TxBuilder {
        let outpoints: Vec<elements::OutPoint> = outpoints.into_iter().map(Into::into).collect();
        self.inner.set_wallet_utxos(outpoints).into()
    }

    #[wasm_bindgen(js_name = toString)]
    pub fn to_string_js(&self) -> String {
        self.to_string()
    }

    #[wasm_bindgen(js_name = liquidexMake)]
    pub fn liquidex_make(
        self,
        utxo: OutPoint,
        address: Address,
        satoshi: u64,
        asset_id: AssetId,
    ) -> Result<TxBuilder, Error> {
        Ok(self
            .inner
            .liquidex_make(utxo.into(), &(address.into()), satoshi, asset_id.into())?
            .into())
    }

    #[wasm_bindgen(js_name = liquidexTake)]
    pub fn liquidex_take(self, proposals: Vec<ValidatedLiquidexProposal>) -> Result<Self, Error> {
        let proposals: Vec<lwk_wollet::LiquidexProposal<Validated>> =
            proposals.into_iter().map(Into::into).collect();
        Ok(self.inner.liquidex_take(proposals)?.into())
    }
}

impl Display for TxBuilder {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self.inner)
    }
}

#[cfg(all(test, target_arch = "wasm32"))]
mod tests {
    use wasm_bindgen_test::*;

    use crate::{Network, OutPoint};

    use super::TxBuilder;

    wasm_bindgen_test_configure!(run_in_browser);

    #[wasm_bindgen_test]
    fn test_builder() {
        let network = Network::mainnet();
        let policy = network.policy_asset();

        let mut builder = TxBuilder::new(&network);
        assert_eq!(builder.to_string(), "TxBuilder { network: Liquid, recipients: [], fee_rate: 100.0, ct_discount: true, issuance_request: None, drain_lbtc: false, drain_to: None, external_utxos: [], selected_utxos: None, is_liquidex_make: false, liquidex_proposals: [] }");

        builder = builder.fee_rate(Some(200.0));
        assert_eq!(builder.to_string(), "TxBuilder { network: Liquid, recipients: [], fee_rate: 200.0, ct_discount: true, issuance_request: None, drain_lbtc: false, drain_to: None, external_utxos: [], selected_utxos: None, is_liquidex_make: false, liquidex_proposals: [] }");

        builder = builder.add_burn(1000, &policy);
        assert_eq!(builder.to_string(), "TxBuilder { network: Liquid, recipients: [Recipient { satoshi: 1000, script_pubkey: Script(OP_RETURN), blinding_pubkey: None, asset: 6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d }], fee_rate: 200.0, ct_discount: true, issuance_request: None, drain_lbtc: false, drain_to: None, external_utxos: [], selected_utxos: None, is_liquidex_make: false, liquidex_proposals: [] }");

        let o = OutPoint::new(
            "[elements]b93dbfb3fa1929b6f82ed46c4a5d8e1c96239ca8b3d9fce00c321d7dadbdf6e0:0",
        )
        .unwrap();
        builder = builder.set_wallet_utxos(vec![o]);
        assert_eq!(builder.to_string(), "TxBuilder { network: Liquid, recipients: [Recipient { satoshi: 1000, script_pubkey: Script(OP_RETURN), blinding_pubkey: None, asset: 6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d }], fee_rate: 200.0, ct_discount: true, issuance_request: None, drain_lbtc: false, drain_to: None, external_utxos: [], selected_utxos: Some([OutPoint { txid: b93dbfb3fa1929b6f82ed46c4a5d8e1c96239ca8b3d9fce00c321d7dadbdf6e0, vout: 0 }]), is_liquidex_make: false, liquidex_proposals: [] }");
    }
}