From 04ab19e5f00280b9c6761187e9374d5341c17d9f Mon Sep 17 00:00:00 2001 From: lilbonekit Date: Mon, 1 Jun 2026 13:01:42 +0300 Subject: [PATCH 01/10] wasm: expose external utxos --- lwk_wasm/src/tx_builder.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lwk_wasm/src/tx_builder.rs b/lwk_wasm/src/tx_builder.rs index bc58b340f..077e1c713 100644 --- a/lwk_wasm/src/tx_builder.rs +++ b/lwk_wasm/src/tx_builder.rs @@ -4,8 +4,8 @@ use lwk_wollet::{elements, UnvalidatedRecipient, Validated}; use wasm_bindgen::prelude::*; use crate::{ - liquidex::ValidatedLiquidexProposal, Address, AssetId, Contract, Error, Network, OutPoint, - Pset, Transaction, Wollet, + liquidex::ValidatedLiquidexProposal, Address, AssetId, Contract, Error, ExternalUtxo, Network, + OutPoint, Pset, Transaction, Wollet, }; /// A transaction builder @@ -205,6 +205,16 @@ impl TxBuilder { self.inner.set_wallet_utxos(outpoints).into() } + /// Adds external UTXOs + /// + /// Note: unblinded UTXOs with the same scriptpubkeys as the wallet, are considered external. + #[wasm_bindgen(js_name = addExternalUtxos)] + pub fn add_external_utxos(self, utxos: Vec) -> Result { + let utxos: Vec = + utxos.iter().map(lwk_wollet::ExternalUtxo::from).collect(); + Ok(self.inner.add_external_utxos(utxos)?.into()) + } + /// Return a string representation of the transaction builder (mostly for debugging) #[wasm_bindgen(js_name = toString)] pub fn to_string_js(&self) -> String { From 9fed0fa38b7ce94e6228499810676c942be23fab Mon Sep 17 00:00:00 2001 From: lilbonekit Date: Tue, 16 Jun 2026 00:12:55 +0300 Subject: [PATCH 02/10] feat(tx_builder): add explicit script output and IssuanceRecipient support --- lwk_wasm/src/tx_builder.rs | 23 +++++-- lwk_wollet/src/lib.rs | 1 + lwk_wollet/src/pset_create.rs | 70 +++++++++++++++++++- lwk_wollet/src/tx_builder.rs | 116 ++++++++++++++++++++++++++-------- 4 files changed, 175 insertions(+), 35 deletions(-) diff --git a/lwk_wasm/src/tx_builder.rs b/lwk_wasm/src/tx_builder.rs index 077e1c713..feb505d1f 100644 --- a/lwk_wasm/src/tx_builder.rs +++ b/lwk_wasm/src/tx_builder.rs @@ -5,7 +5,7 @@ use wasm_bindgen::prelude::*; use crate::{ liquidex::ValidatedLiquidexProposal, Address, AssetId, Contract, Error, ExternalUtxo, Network, - OutPoint, Pset, Transaction, Wollet, + OutPoint, Pset, Script, Transaction, Wollet, }; /// A transaction builder @@ -125,6 +125,19 @@ impl TxBuilder { .into()) } + /// Add an unblinded output with an arbitrary script pubkey to the post-issuance output list. + #[wasm_bindgen(js_name = addPostIssuanceScriptOutput)] + pub fn add_post_issuance_script_output( + self, + script_pubkey: &Script, + satoshi: u64, + asset: &AssetId, + ) -> TxBuilder { + self.inner + .add_post_issuance_script_output(script_pubkey.as_ref().clone(), satoshi, (*asset).into()) + .into() + } + /// Issue an asset /// /// There will be `asset_sats` units of this asset that will be received by @@ -274,19 +287,19 @@ mod tests { 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, add_input_rangeproofs: true, is_liquidex_make: false, liquidex_proposals: [] }"); + assert_eq!(builder.to_string(), "TxBuilder { network: Liquid, recipients: [], post_issuance_recipients: [], fee_rate: 100.0, ct_discount: true, issuance_request: None, drain_lbtc: false, drain_to: None, external_utxos: [], selected_utxos: None, add_input_rangeproofs: true, 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, add_input_rangeproofs: true, is_liquidex_make: false, liquidex_proposals: [] }"); + assert_eq!(builder.to_string(), "TxBuilder { network: Liquid, recipients: [], post_issuance_recipients: [], fee_rate: 200.0, ct_discount: true, issuance_request: None, drain_lbtc: false, drain_to: None, external_utxos: [], selected_utxos: None, add_input_rangeproofs: true, 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, add_input_rangeproofs: true, is_liquidex_make: false, liquidex_proposals: [] }"); + assert_eq!(builder.to_string(), "TxBuilder { network: Liquid, recipients: [Recipient { satoshi: 1000, script_pubkey: Script(OP_RETURN), blinding_pubkey: None, asset: 6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d }], post_issuance_recipients: [], fee_rate: 200.0, ct_discount: true, issuance_request: None, drain_lbtc: false, drain_to: None, external_utxos: [], selected_utxos: None, add_input_rangeproofs: true, 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 }]), add_input_rangeproofs: true, is_liquidex_make: false, liquidex_proposals: [] }"); + assert_eq!(builder.to_string(), "TxBuilder { network: Liquid, recipients: [Recipient { satoshi: 1000, script_pubkey: Script(OP_RETURN), blinding_pubkey: None, asset: 6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d }], post_issuance_recipients: [], 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 }]), add_input_rangeproofs: true, is_liquidex_make: false, liquidex_proposals: [] }"); } } diff --git a/lwk_wollet/src/lib.rs b/lwk_wollet/src/lib.rs index 8d74016b9..28c134cdc 100644 --- a/lwk_wollet/src/lib.rs +++ b/lwk_wollet/src/lib.rs @@ -133,6 +133,7 @@ pub use crate::model::{ WalletTxOut, }; pub use crate::pegin::fed_peg_script; +pub use crate::pset_create::IssuanceRecipient; #[cfg(feature = "registry")] pub use crate::registry::RegistryAssetData; pub use crate::tx_details::{TxDetails, TxOpt, TxOutDetails, TxsOpt}; diff --git a/lwk_wollet/src/pset_create.rs b/lwk_wollet/src/pset_create.rs index 24557a630..64c19677d 100644 --- a/lwk_wollet/src/pset_create.rs +++ b/lwk_wollet/src/pset_create.rs @@ -3,7 +3,7 @@ use crate::contract::Contract; use crate::elements::confidential::AssetBlindingFactor; use crate::elements::issuance::ContractHash; use crate::elements::pset::{Output, PartiallySignedTransaction}; -use crate::elements::{Address, AssetId, OutPoint, Transaction, TxOut, TxOutSecrets, Txid}; +use crate::elements::{Address, AssetId, OutPoint, Script, Transaction, TxOut, TxOutSecrets, Txid}; use crate::error::Error; use crate::hashes::Hash; use crate::model::{Recipient, WalletTxOut}; @@ -19,10 +19,76 @@ pub const SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS: usize = 256; // We make issuance and reissuance are mutually exclusive for simplicity pub enum IssuanceRequest { None, - Issuance(u64, Option
, u64, Option
, Option), + Issuance( + Vec, + u64, + Option
, + Option, + ), Reissuance(AssetId, u64, Option
, Option), } +/// Recipient of newly issued asset units. +#[derive(Debug, Clone)] +pub struct IssuanceRecipient { + satoshi: u64, + script_pubkey: Option