diff --git a/Cargo.lock b/Cargo.lock index 8ad9c47..c759151 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1522,6 +1522,7 @@ version = "0.11.0-beta.9" dependencies = [ "amplify", "baid64", + "bp-core", "bp-std", "bp-wallet", "clap", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index b408432..6173d5d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -21,6 +21,7 @@ amplify = { workspace = true } baid64 = { workspace = true } strict_types = { workspace = true, features = ["serde"] } commit_verify = { workspace = true } +bp-core = { workspace = true } bp-std = { workspace = true, features = ["serde"] } bp-wallet = { workspace = true, features = ["cli"] } rgb-std = { workspace = true, features = ["serde"] } diff --git a/cli/README.md b/cli/README.md index af0b599..64b3747 100644 --- a/cli/README.md +++ b/cli/README.md @@ -38,7 +38,7 @@ from source ``` $ git clone $ cd rgb/cli -$ cargo install --path --all-features . +$ cargo install --all-features --path . ``` ## Data Directory @@ -274,14 +274,14 @@ globals: assignments: assetOwner: - seal: tapret1st:fb9ae7ae4b70a27e7fdfdefac91b37967b549d65007dbf25470b0817a2ae810a:1 + seal: fb9ae7ae4b70a27e7fdfdefac91b37967b549d65007dbf25470b0817a2ae810a:1 amount: 100000000 # this is 1 million (we have two digits for cents) ``` -Here, we observe a seal value in the form of `closing_method:txid:vout` and here closing method is `tapret1st` (can also -be `opret1st`). This hash, in reality, represents the txid of the previously created PSBT. And `txid:vout` is the -outpoint of a valid UTXO. +Here, we observe a seal value in the form of `txid:vout`. This hash, in +reality, represents the txid of the previously created PSBT. And `txid:vout` is +the outpoint of a valid UTXO. Compile the contract: diff --git a/cli/src/args.rs b/cli/src/args.rs index 4614f5d..41fc153 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -131,7 +131,7 @@ impl RgbArgs { let resolver = self.resolver()?; let from_height = self.from_height.unwrap_or(1); eprint!("Updating witness information starting from height {from_height} ... "); - let res = stock.update_witnesses(resolver, from_height)?; + let res = stock.update_witnesses(resolver, from_height, vec![])?; eprint!("{} transactions were checked and updated", res.succeeded); if res.failed.is_empty() { eprintln!(); @@ -189,7 +189,7 @@ impl RgbArgs { --esplora --mempool or --electrum argument")), } .map_err(WalletError::Resolver)?; - resolver.check(self.general.network)?; + resolver.check_chain_net(self.general.network.into())?; Ok(resolver) } } diff --git a/cli/src/command.rs b/cli/src/command.rs index f14b5c2..31dc62d 100644 --- a/cli/src/command.rs +++ b/cli/src/command.rs @@ -27,6 +27,7 @@ use std::str::FromStr; use amplify::confinement::{SmallOrdMap, TinyOrdMap, TinyOrdSet, U16 as MAX16}; use baid64::DisplayBaid64; +use bp::Txid; use bpstd::psbt::{Psbt, PsbtVer}; use bpstd::seals::SecretSeal; use bpstd::{Sats, XpubDerivable}; @@ -44,9 +45,9 @@ use rgb::schema::SchemaId; use rgb::validation::Validity; use rgb::vm::{RgbIsa, WitnessOrd}; use rgb::{ - Allocation, BundleId, ContractId, DescriptorRgb, GenesisSeal, GraphSeal, Identity, OpId, - OutputSeal, OwnedFraction, RgbDescr, RgbKeychain, RgbWallet, StateType, TokenIndex, - TransferParams, WalletError, WalletProvider, XChain, XOutpoint, XWitnessId, + Allocation, BundleId, ContractId, GenesisSeal, GraphSeal, Identity, OpId, Outpoint, OutputSeal, + OwnedFraction, RgbDescr, RgbKeychain, RgbWallet, StateType, TokenIndex, TransferParams, + WalletError, WalletProvider, }; use rgbstd::interface::{AllocatedState, ContractIface, OwnedIface}; use rgbstd::persistence::{MemContractState, StockError}; @@ -477,7 +478,7 @@ impl Exec for RgbArgs { let resolver = self.resolver()?; eprint!("- validating the contract {} ... ", contract.contract_id()); let contract = contract - .validate(&resolver, self.general.network.is_testnet()) + .validate(&resolver, self.general.network.into()) .map_err(|(status, _)| { eprintln!("failure"); status.to_string() @@ -574,11 +575,11 @@ impl Exec for RgbArgs { WalletAll(&'w RgbWallet>), NoWallet, } - impl<'w> AssignmentsFilter for Filter<'w> { + impl AssignmentsFilter for Filter<'_> { fn should_include( &self, - outpoint: impl Into, - id: Option, + outpoint: impl Into, + id: Option, ) -> bool { match self { Filter::Wallet(wallet) => wallet @@ -589,12 +590,8 @@ impl Exec for RgbArgs { } } } - impl<'w> Filter<'w> { - fn comment(&self, outpoint: XOutpoint) -> &'static str { - let outpoint = outpoint - .into_bp() - .into_bitcoin() - .expect("liquid is not yet supported"); + impl Filter<'_> { + fn comment(&self, outpoint: Outpoint) -> &'static str { match self { Filter::Wallet(rgb) if rgb.wallet().is_unspent(outpoint) => "", Filter::WalletAll(rgb) if rgb.wallet().is_unspent(outpoint) => { @@ -704,7 +701,12 @@ impl Exec for RgbArgs { )) })?; - let mut builder = stock.contract_builder(issuer.clone(), *schema_id, iface_id)?; + let mut builder = stock.contract_builder( + issuer.clone(), + *schema_id, + iface_id, + self.general.network.into(), + )?; let types = builder.type_system().clone(); if let Some(globals) = code.get("globals") { @@ -732,6 +734,7 @@ impl Exec for RgbArgs { .typify(val, sem_id) .expect("global type doesn't match type definition"); + #[allow(deprecated)] let serialized = types .strict_serialize_type::(&typed_val) .expect("internal error"); @@ -771,7 +774,7 @@ impl Exec for RgbArgs { .as_str() .expect("seal must be a string"); let seal = OutputSeal::from_str(seal).expect("invalid seal definition"); - let seal = GenesisSeal::new_random(seal.method, seal.txid, seal.vout); + let seal = GenesisSeal::new_random(seal.txid, seal.vout); // Workaround for borrow checker: let field_name = @@ -784,7 +787,7 @@ impl Exec for RgbArgs { .expect("owned state must be a fungible amount") .as_u64() .expect("fungible state must be an integer"); - let seal = BuilderSeal::Revealed(XChain::Bitcoin(seal)); + let seal = BuilderSeal::Revealed(seal); builder = builder .add_fungible_state(field_name, seal, amount) .expect("invalid global state data"); @@ -835,19 +838,12 @@ impl Exec for RgbArgs { .next() .expect("no addresses left") .addr; - Beneficiary::WitnessVout(Pay2Vout { - address: addr.payload, - method: wallet.wallet().seal_close_method(), - }) + Beneficiary::WitnessVout(Pay2Vout::new(addr.payload)) } (_, Some(outpoint)) => { - let seal = XChain::Bitcoin(GraphSeal::new_random( - wallet.wallet().seal_close_method(), - outpoint.txid, - outpoint.vout, - )); + let seal = GraphSeal::new_random(outpoint.txid, outpoint.vout); wallet.stock_mut().store_secret_seal(seal)?; - Beneficiary::BlindedSeal(*seal.to_secret_seal().as_reduced_unsafe()) + Beneficiary::BlindedSeal(seal.to_secret_seal()) } }; @@ -1049,7 +1045,7 @@ impl Exec for RgbArgs { pub struct ConsignmentInspection { version: ContainerVer, transfer: bool, - terminals: SmallOrdMap>, + terminals: SmallOrdMap, supplements: TinyOrdSet, signatures: TinyOrdMap, } @@ -1251,11 +1247,10 @@ impl Exec for RgbArgs { let mut resolver = self.resolver()?; let consignment = Transfer::load_file(file)?; resolver.add_terminals(&consignment); - let status = - match consignment.validate(&resolver, self.general.network.is_testnet()) { - Ok(consignment) => consignment.into_validation_status(), - Err((status, _)) => status, - }; + let status = match consignment.validate(&resolver, self.general.network.into()) { + Ok(consignment) => consignment.into_validation_status(), + Err((status, _)) => status, + }; if status.validity() == Validity::Valid { eprintln!("The provided consignment is valid") } else { @@ -1269,7 +1264,7 @@ impl Exec for RgbArgs { let transfer = Transfer::load_file(file)?; resolver.add_terminals(&transfer); let valid = transfer - .validate(&resolver, self.general.network.is_testnet()) + .validate(&resolver, self.general.network.into()) .map_err(|(status, _)| status)?; stock.accept_transfer(valid, &resolver)?; eprintln!("Transfer accepted into the stash"); diff --git a/examples/rgb20-demo.con b/examples/rgb20-demo.con index bfeef2c..d01c6ea 100644 --- a/examples/rgb20-demo.con +++ b/examples/rgb20-demo.con @@ -29,6 +29,5 @@ contract Test: NonInflatableAsset owned assetOwner state = 1_000_000__000_000_00 seal = - method := tapret1st txid = #01d46e52c4bdb51931a0eae83e958c78bdef9cac2057b36d55370410edafdd42 vout = 0 diff --git a/examples/rgb20-demo.yaml b/examples/rgb20-demo.yaml index 575c6d7..475973a 100644 --- a/examples/rgb20-demo.yaml +++ b/examples/rgb20-demo.yaml @@ -26,5 +26,5 @@ globals: assignments: assetOwner: - seal: tapret1st:b449f7eaa3f98c145b27ad0eeb7b5679ceb567faef7a52479bc995792b65f804:1 + seal: b449f7eaa3f98c145b27ad0eeb7b5679ceb567faef7a52479bc995792b65f804:1 amount: 100000000 # this is 1 million (we have two digits for cents) diff --git a/psbt/src/lib.rs b/psbt/src/lib.rs index ad736c4..510f312 100644 --- a/psbt/src/lib.rs +++ b/psbt/src/lib.rs @@ -26,10 +26,10 @@ mod rgb; use bp::dbc::opret::OpretProof; use bp::dbc::tapret::TapretProof; +use bp::seals::txout::CloseMethod; pub use bpstd::psbt::*; pub use rgb::*; -use rgbstd::containers::{AnchorSet, Batch, CloseMethodSet, Fascia, PubWitness, XPubWitness}; -use rgbstd::XChain; +use rgbstd::containers::{AnchorSet, Batch, Fascia, PubWitness}; pub use self::rgb::{ ProprietaryKeyRgb, RgbExt, RgbInExt, RgbOutExt, RgbPsbtError, PSBT_GLOBAL_RGB_TRANSITION, @@ -77,7 +77,7 @@ impl RgbPsbt for Psbt { let contract_id = info.transition.contract_id; let mut inputs = info.inputs.release(); for input in self.inputs_mut() { - if inputs.remove(&XChain::Bitcoin(input.prevout().outpoint())) { + if inputs.remove(&input.prevout().outpoint()) { input .set_rgb_consumer(contract_id, info.id) .map_err(|_| EmbedError::PsbtRepeatedInputs)?; @@ -86,7 +86,7 @@ impl RgbPsbt for Psbt { if !inputs.is_empty() { return Err(EmbedError::AbsentInputs); } - self.push_rgb_transition(info.transition, info.method) + self.push_rgb_transition(info.transition) .expect("transitions are unique since they are in BTreeMap indexed by opid"); } Ok(()) @@ -96,30 +96,18 @@ impl RgbPsbt for Psbt { // Convert RGB data to MPCs? Or should we do it at the moment we add them... No, // since we may require more DBC methods with each additional state transition let bundles = self.rgb_bundles_to_mpc()?; - // DBC commitment for the required methods - let methods = bundles - .values() - .flat_map(|b| b.iter()) - .map(|b| CloseMethodSet::from(b.close_method)) - .reduce(|methods, method| methods | method) - .ok_or(RgbPsbtError::NoContracts)?; - let (mut tapret_anchor, mut opret_anchor) = (None, None); - if methods.has_tapret_first() { - tapret_anchor = Some(self.dbc_commit::()?); - } - if methods.has_opret_first() { - opret_anchor = Some(self.dbc_commit::()?); - } - let anchor = match (tapret_anchor, opret_anchor) { - (None, None) => return Err(RgbPsbtError::NoContracts.into()), - (Some(tapret), None) => AnchorSet::Tapret(tapret), - (None, Some(opret)) => AnchorSet::Opret(opret), - (Some(tapret), Some(opret)) => AnchorSet::Double { tapret, opret }, + // DBC commitment for the correct close method + let close_method = self + .rgb_close_method()? + .ok_or(RgbPsbtError::NoCloseMethod)?; + let anchor = match close_method { + CloseMethod::TapretFirst => AnchorSet::Tapret(self.dbc_commit::()?), + CloseMethod::OpretFirst => AnchorSet::Opret(self.dbc_commit::()?), }; // TODO: Use signed transaction here! let witness = PubWitness::with(self.to_unsigned_tx().into()); Ok(Fascia { - witness: XPubWitness::Bitcoin(witness), + witness, anchor, bundles, }) diff --git a/psbt/src/rgb.rs b/psbt/src/rgb.rs index 0a1f14d..c4e389f 100644 --- a/psbt/src/rgb.rs +++ b/psbt/src/rgb.rs @@ -19,7 +19,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::collections::{BTreeMap, BTreeSet}; use amplify::confinement::{Confined, SmallOrdMap, U24}; use amplify::{confinement, FromSliceError}; @@ -28,7 +28,7 @@ use bp::seals::txout::CloseMethod; use bpstd::psbt; use bpstd::psbt::{KeyAlreadyPresent, KeyMap, MpcPsbtError, PropKey, Psbt}; use commit_verify::mpc; -use rgbstd::containers::{BundleDichotomy, VelocityHint}; +use rgbstd::containers::VelocityHint; use rgbstd::{ ContractId, InputMap, MergeReveal, MergeRevealError, OpId, Operation, Transition, TransitionBundle, Vin, @@ -47,9 +47,9 @@ pub const PSBT_RGB_PREFIX: &str = "RGB"; /// Proprietary key subtype for storing RGB state transition in global map. pub const PSBT_GLOBAL_RGB_TRANSITION: u64 = 0x01; -/// Proprietary key subtype for storing information on which closed methods -/// should be used for each of RGB state transitions. -pub const PSBT_GLOBAL_RGB_CLOSE_METHODS: u64 = 0x02; +/// Proprietary key subtype for storing information on which close method +/// should be used. +pub const PSBT_GLOBAL_RGB_CLOSE_METHOD: u64 = 0x02; /// Proprietary key subtype for storing RGB state transition operation id which /// consumes this input. pub const PSBT_IN_RGB_CONSUMED_BY: u64 = 0x01; @@ -67,12 +67,12 @@ pub trait ProprietaryKeyRgb { data: opid.to_vec().into(), } } - /// Constructs [`PSBT_GLOBAL_RGB_CLOSE_METHODS`] proprietary key. - fn rgb_closing_methods(opid: OpId) -> PropKey { + /// Constructs [`PSBT_GLOBAL_RGB_CLOSE_METHOD`] proprietary key. + fn rgb_close_method() -> PropKey { PropKey { identifier: PSBT_RGB_PREFIX.to_owned(), - subtype: PSBT_GLOBAL_RGB_CLOSE_METHODS, - data: opid.to_vec().into(), + subtype: PSBT_GLOBAL_RGB_CLOSE_METHOD, + data: none!(), } } @@ -111,6 +111,9 @@ pub enum RgbPsbtError { /// PSBT contains no contract information NoContracts, + /// PSBT contains no contract consumers information + NoContractConsumers, + /// contract {0} listed in the PSBT has zero known transition information. NoTransitions(ContractId), @@ -118,12 +121,11 @@ pub enum RgbPsbtError { #[from(FromSliceError)] InvalidContractId, - /// state transition {0} doesn't provide information about seal closing - /// methods used by its inputs. - NoCloseMethod(OpId), + /// PSBT doesn't provide information about close method. + NoCloseMethod, - /// invalid close method data for opid {0} - InvalidCloseMethod(OpId), + /// PSBT provides invalid close method information. + InvalidCloseMethod, /// PSBT doesn't specify an output which can host {0} commitment. NoHostOutput(Method), @@ -160,59 +162,44 @@ pub trait RgbExt { fn rgb_transition(&self, opid: OpId) -> Result, RgbPsbtError>; - fn rgb_close_method(&self, opid: OpId) -> Result, RgbPsbtError>; + fn rgb_close_method(&self) -> Result, RgbPsbtError>; - fn push_rgb_transition( - &mut self, - transition: Transition, - method: CloseMethod, - ) -> Result; + fn set_rgb_close_method(&mut self, close_method: CloseMethod); - fn rgb_bundles(&self) -> Result, RgbPsbtError> { + fn push_rgb_transition(&mut self, transition: Transition) -> Result; + + fn rgb_bundles(&self) -> Result, RgbPsbtError> { let mut map = BTreeMap::new(); for contract_id in self.rgb_contract_ids()? { - let mut input_map = HashMap::>::new(); - let mut known_transitions = - HashMap::>::new(); - for (opid, vin) in self.rgb_contract_consumers(contract_id)? { - let (transition, method) = ( - self.rgb_transition(opid)?, - self.rgb_close_method(opid)? - .ok_or(RgbPsbtError::NoCloseMethod(opid))?, - ); - input_map.entry(method).or_default().insert(vin, opid)?; + let mut input_map: SmallOrdMap = SmallOrdMap::new(); + let mut known_transitions: SmallOrdMap = SmallOrdMap::new(); + let contract_consumers = self.rgb_contract_consumers(contract_id)?; + if contract_consumers.is_empty() { + return Err(RgbPsbtError::NoContractConsumers); + } + for (opid, vin) in contract_consumers { + let transition = self.rgb_transition(opid)?; + input_map.insert(vin, opid)?; if let Some(transition) = transition { - known_transitions - .entry(method) - .or_default() - .insert(opid, transition)?; + known_transitions.insert(opid, transition)?; } } - let mut bundles = vec![]; - for (method, input_map) in input_map { - let known_transitions = known_transitions.remove(&method).unwrap_or_default(); - bundles.push(TransitionBundle { - close_method: method, - input_map: InputMap::from( - Confined::try_from(input_map.release()) - .map_err(|_| RgbPsbtError::NoTransitions(contract_id))?, - ), - known_transitions: Confined::try_from(known_transitions.release()) + let bundle = TransitionBundle { + input_map: InputMap::from( + Confined::try_from(input_map.release()) .map_err(|_| RgbPsbtError::NoTransitions(contract_id))?, - }); - } - let mut bundles = bundles.into_iter(); - let first = bundles - .next() - .ok_or(RgbPsbtError::NoTransitions(contract_id))?; - map.insert(contract_id, BundleDichotomy::with(first, bundles.next())); + ), + known_transitions: Confined::try_from(known_transitions.release()) + .map_err(|_| RgbPsbtError::NoTransitions(contract_id))?, + }; + map.insert(contract_id, bundle); } Ok(map) } fn rgb_bundles_to_mpc( &mut self, - ) -> Result, 1, U24>, RgbPsbtError>; + ) -> Result, 1, U24>, RgbPsbtError>; } impl RgbExt for Psbt { @@ -260,8 +247,8 @@ impl RgbExt for Psbt { Ok(Some(transition)) } - fn rgb_close_method(&self, opid: OpId) -> Result, RgbPsbtError> { - let Some(m) = self.proprietary(&PropKey::rgb_closing_methods(opid)) else { + fn rgb_close_method(&self) -> Result, RgbPsbtError> { + let Some(m) = self.proprietary(&PropKey::rgb_close_method()) else { return Ok(None); }; if m.len() == 1 { @@ -269,20 +256,15 @@ impl RgbExt for Psbt { return Ok(Some(method)); } } - Err(RgbPsbtError::InvalidCloseMethod(opid)) + Err(RgbPsbtError::InvalidCloseMethod) } - fn push_rgb_transition( - &mut self, - mut transition: Transition, - method: CloseMethod, - ) -> Result { - let opid = transition.id(); + fn set_rgb_close_method(&mut self, close_method: CloseMethod) { + let _ = self.push_proprietary(PropKey::rgb_close_method(), vec![close_method as u8]); + } - let prev_method = self.rgb_close_method(opid)?; - if matches!(prev_method, Some(prev_method) if prev_method != method) { - return Err(RgbPsbtError::InvalidCloseMethod(opid)); - } + fn push_rgb_transition(&mut self, mut transition: Transition) -> Result { + let opid = transition.id(); let prev_transition = self.rgb_transition(opid)?; if let Some(ref prev_transition) = prev_transition { @@ -300,36 +282,30 @@ impl RgbExt for Psbt { // existed let _ = self.push_proprietary(PropKey::rgb_transition(opid), serialized_transition.release()); - let _ = self.push_proprietary(PropKey::rgb_closing_methods(opid), vec![method as u8]); Ok(prev_transition.is_none()) } fn rgb_bundles_to_mpc( &mut self, - ) -> Result, 1, U24>, RgbPsbtError> { + ) -> Result, 1, U24>, RgbPsbtError> { let bundles = self.rgb_bundles()?; - for (contract_id, bundle) in bundles - .iter() - .flat_map(|(id, b)| b.iter().map(move |b| (id, b))) - { + let close_method = self + .rgb_close_method()? + .ok_or(RgbPsbtError::NoCloseMethod)?; + + let host = self + .outputs_mut() + .find(|output| match close_method { + CloseMethod::OpretFirst => output.is_opret_host(), + CloseMethod::TapretFirst => output.is_tapret_host(), + }) + .ok_or(RgbPsbtError::NoHostOutput(close_method))?; + + for (contract_id, bundle) in &bundles { let protocol_id = mpc::ProtocolId::from(*contract_id); let message = mpc::Message::from(bundle.bundle_id()); - if bundle.close_method == CloseMethod::TapretFirst { - // We need to do it each time due to Rust borrow checker - let tapret_host = self - .outputs_mut() - .find(|output| output.is_tapret_host()) - .ok_or(RgbPsbtError::NoHostOutput(Method::TapretFirst))?; - tapret_host.set_mpc_message(protocol_id, message)?; - } else if bundle.close_method == CloseMethod::OpretFirst { - // We need to do it each time due to Rust borrow checker - let opret_host = self - .outputs_mut() - .find(|output| output.is_opret_host()) - .ok_or(RgbPsbtError::NoHostOutput(Method::OpretFirst))?; - opret_host.set_mpc_message(protocol_id, message)?; - } + host.set_mpc_message(protocol_id, message)?; } let map = Confined::try_from(bundles).map_err(|_| RgbPsbtError::NoContracts)?; diff --git a/src/descriptor.rs b/src/descriptor.rs index 103eee2..8c48b01 100644 --- a/src/descriptor.rs +++ b/src/descriptor.rs @@ -43,7 +43,7 @@ use indexmap::IndexMap; pub struct TapTweakAlreadyAssigned(pub Terminal); pub trait DescriptorRgb: Descriptor { - fn seal_close_method(&self) -> CloseMethod; + fn close_method(&self) -> CloseMethod; fn add_tapret_tweak( &mut self, terminal: Terminal, @@ -227,7 +227,7 @@ impl Descriptor for TapretKey { } impl DescriptorRgb for TapretKey { - fn seal_close_method(&self) -> CloseMethod { CloseMethod::TapretFirst } + fn close_method(&self) -> CloseMethod { CloseMethod::TapretFirst } fn add_tapret_tweak( &mut self, @@ -368,10 +368,10 @@ impl + DeriveCompr + DeriveXOnly> DescriptorR for RgbDescr where Self: Derive { - fn seal_close_method(&self) -> CloseMethod { + fn close_method(&self) -> CloseMethod { match self { RgbDescr::Wpkh(_) => CloseMethod::OpretFirst, - RgbDescr::TapretKey(d) => d.seal_close_method(), + RgbDescr::TapretKey(d) => d.close_method(), } } diff --git a/src/errors.rs b/src/errors.rs index 72d32b8..1eb856e 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -25,6 +25,7 @@ use std::convert::Infallible; use std::io; use amplify::IoError; +use bp::seals::txout::CloseMethod; use bpstd::Psbt; use nonasync::persistence::PersistenceError; use psrgbt::{CommitError, ConstructionError, EmbedError, TapretKeyError}; @@ -146,6 +147,12 @@ pub enum CompositionError { /// the invoice has expired. InvoiceExpired, + /// the invoice doesn't support the contract close method {0} + InvoiceUnsupportsCloseMethod(CloseMethod), + + /// the wallet descriptor doesn't support the contract close method {0} + WalletUnsupportsCloseMethod(CloseMethod), + /// one of the RGB assignments spent require presence of tapret output - /// even this is not a taproot wallet. Unable to create a valid PSBT, manual /// work is needed. @@ -186,6 +193,12 @@ pub enum CompletionError { /// the provided PSBT has conflicting descriptor in the taptweak output. InconclusiveDerivation, + /// the invoice doesn't support the contract close method {0} + InvoiceUnsupportsCloseMethod(CloseMethod), + + /// the wallet descriptor doesn't support the contract close method {0} + WalletUnsupportsCloseMethod(CloseMethod), + #[from] #[display(inner)] MultipleTweaks(TapTweakAlreadyAssigned), diff --git a/src/filters.rs b/src/filters.rs index 2cdbd44..8be3f1f 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -19,29 +19,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -use bp::Bp; use bpwallet::{Layer2, Wallet}; use rgbstd::interface::AssignmentsFilter; -use crate::{DescriptorRgb, XChain, XOutpoint, XWitnessId}; +use crate::{DescriptorRgb, Outpoint, Txid}; pub struct WalletOutpointsFilter<'a, K, D: DescriptorRgb, L2: Layer2>(pub &'a Wallet); // We need manual derivation to ensure we can be copied and cloned even if descriptor is not // copyable/clonable. -impl<'a, K, D: DescriptorRgb, L2: Layer2> Copy for WalletOutpointsFilter<'a, K, D, L2> {} -impl<'a, K, D: DescriptorRgb, L2: Layer2> Clone for WalletOutpointsFilter<'a, K, D, L2> { +impl, L2: Layer2> Copy for WalletOutpointsFilter<'_, K, D, L2> {} +impl, L2: Layer2> Clone for WalletOutpointsFilter<'_, K, D, L2> { fn clone(&self) -> Self { *self } } -impl<'a, K, D: DescriptorRgb, L2: Layer2> AssignmentsFilter - for WalletOutpointsFilter<'a, K, D, L2> -{ - fn should_include(&self, output: impl Into, _: Option) -> bool { - match output.into().into_bp() { - Bp::Bitcoin(outpoint) => self.0.has_outpoint(outpoint), - Bp::Liquid(_) => false, - } +impl, L2: Layer2> AssignmentsFilter for WalletOutpointsFilter<'_, K, D, L2> { + fn should_include(&self, outpoint: impl Into, _: Option) -> bool { + self.0.has_outpoint(outpoint.into()) } } @@ -49,19 +43,14 @@ pub struct WalletUnspentFilter<'a, K, D: DescriptorRgb, L2: Layer2>(pub &'a W // We need manual derivation to ensure we can be copied and cloned even if descriptor is not // copyable/clonable. -impl<'a, K, D: DescriptorRgb, L2: Layer2> Copy for WalletUnspentFilter<'a, K, D, L2> {} -impl<'a, K, D: DescriptorRgb, L2: Layer2> Clone for WalletUnspentFilter<'a, K, D, L2> { +impl, L2: Layer2> Copy for WalletUnspentFilter<'_, K, D, L2> {} +impl, L2: Layer2> Clone for WalletUnspentFilter<'_, K, D, L2> { fn clone(&self) -> Self { *self } } -impl<'a, K, D: DescriptorRgb, L2: Layer2> AssignmentsFilter - for WalletUnspentFilter<'a, K, D, L2> -{ - fn should_include(&self, output: impl Into, _: Option) -> bool { - match output.into().into_bp() { - Bp::Bitcoin(outpoint) => self.0.is_unspent(outpoint), - Bp::Liquid(_) => false, - } +impl, L2: Layer2> AssignmentsFilter for WalletUnspentFilter<'_, K, D, L2> { + fn should_include(&self, outpoint: impl Into, _: Option) -> bool { + self.0.is_unspent(outpoint.into()) } } @@ -69,17 +58,15 @@ pub struct WalletWitnessFilter<'a, K, D: DescriptorRgb, L2: Layer2>(pub &'a W // We need manual derivation to ensure we can be copied and cloned even if descriptor is not // copyable/clonable. -impl<'a, K, D: DescriptorRgb, L2: Layer2> Copy for WalletWitnessFilter<'a, K, D, L2> {} -impl<'a, K, D: DescriptorRgb, L2: Layer2> Clone for WalletWitnessFilter<'a, K, D, L2> { +impl, L2: Layer2> Copy for WalletWitnessFilter<'_, K, D, L2> {} +impl, L2: Layer2> Clone for WalletWitnessFilter<'_, K, D, L2> { fn clone(&self) -> Self { *self } } -impl<'a, K, D: DescriptorRgb, L2: Layer2> AssignmentsFilter - for WalletWitnessFilter<'a, K, D, L2> -{ - fn should_include(&self, _: impl Into, witness_id: Option) -> bool { +impl, L2: Layer2> AssignmentsFilter for WalletWitnessFilter<'_, K, D, L2> { + fn should_include(&self, _: impl Into, witness_id: Option) -> bool { self.0 .history() - .any(|row| !row.our_inputs.is_empty() && witness_id == Some(XChain::Bitcoin(row.txid))) + .any(|row| !row.our_inputs.is_empty() && witness_id == Some(row.txid)) } } diff --git a/src/indexers/any.rs b/src/indexers/any.rs index 08b3b7f..5bc6e09 100644 --- a/src/indexers/any.rs +++ b/src/indexers/any.rs @@ -23,19 +23,17 @@ use std::collections::HashMap; -use bp::Tx; -use bpstd::Network; +use bp::{Tx, Txid}; use rgbstd::containers::Consignment; use rgbstd::validation::{ResolveWitness, WitnessResolverError}; -use rgbstd::XWitnessId; +use rgbstd::ChainNet; -use crate::vm::{WitnessOrd, XWitnessTx}; -use crate::{Txid, XChain}; +use crate::vm::WitnessOrd; // We need to repeat methods of `WitnessResolve` trait here to avoid making // wrappers around resolver types. TODO: Use wrappers instead pub trait RgbResolver: Send { - fn check(&self, network: Network, expected_block_hash: String) -> Result<(), String>; + fn check_chain_net(&self, chain_net: ChainNet) -> Result<(), String>; fn resolve_pub_witness(&self, txid: Txid) -> Result, String>; fn resolve_pub_witness_ord(&self, txid: Txid) -> Result; } @@ -81,16 +79,9 @@ impl AnyResolver { terminal_txes: Default::default(), }) } - pub fn check(&self, network: Network) -> Result<(), String> { - let expected_block_hash = match network { - Network::Mainnet => "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", - Network::Testnet3 => "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943", - Network::Testnet4 => "00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043", - Network::Signet => "00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6", - Network::Regtest => "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206", - } - .to_string(); - self.inner.check(network, expected_block_hash) + + pub fn check_chain_net(&self, chain_net: ChainNet) -> Result<(), String> { + self.inner.check_chain_net(chain_net) } pub fn add_terminals(&mut self, consignment: &Consignment) { @@ -98,56 +89,40 @@ impl AnyResolver { consignment .bundles .iter() - .filter_map(|bw| bw.pub_witness.maybe_map_ref(|w| w.tx().cloned())) - .filter_map(|tx| match tx { - XChain::Bitcoin(tx) => Some(tx), - XChain::Liquid(_) | XChain::Other(_) => None, - }) + .filter_map(|bw| bw.pub_witness.tx().cloned()) .map(|tx| (tx.txid(), tx)), ); } } impl ResolveWitness for AnyResolver { - fn resolve_pub_witness( - &self, - witness_id: XWitnessId, - ) -> Result { - let XWitnessId::Bitcoin(txid) = witness_id else { - return Err(WitnessResolverError::Other( - witness_id, - format!("{} is not supported as layer 1 network", witness_id.layer1()), - )); - }; - - if let Some(tx) = self.terminal_txes.get(&txid) { - return Ok(XWitnessTx::Bitcoin(tx.clone())); + fn resolve_pub_witness(&self, witness_id: Txid) -> Result { + if let Some(tx) = self.terminal_txes.get(&witness_id) { + return Ok(tx.clone()); } self.inner - .resolve_pub_witness(txid) + .resolve_pub_witness(witness_id) .map_err(|e| WitnessResolverError::Other(witness_id, e)) .and_then(|r| r.ok_or(WitnessResolverError::Unknown(witness_id))) - .map(XChain::Bitcoin) } fn resolve_pub_witness_ord( &self, - witness_id: XWitnessId, + witness_id: Txid, ) -> Result { - let XWitnessId::Bitcoin(txid) = witness_id else { - return Err(WitnessResolverError::Other( - witness_id, - format!("{} is not supported as layer 1 network", witness_id.layer1()), - )); - }; - - if self.terminal_txes.contains_key(&txid) { + if self.terminal_txes.contains_key(&witness_id) { return Ok(WitnessOrd::Tentative); } self.inner - .resolve_pub_witness_ord(txid) + .resolve_pub_witness_ord(witness_id) .map_err(|e| WitnessResolverError::Other(witness_id, e)) } + + fn check_chain_net(&self, chain_net: ChainNet) -> Result<(), WitnessResolverError> { + self.inner + .check_chain_net(chain_net) + .map_err(|_| WitnessResolverError::WrongChainNet) + } } diff --git a/src/indexers/electrum_blocking.rs b/src/indexers/electrum_blocking.rs index 98aad72..a8faa0f 100644 --- a/src/indexers/electrum_blocking.rs +++ b/src/indexers/electrum_blocking.rs @@ -25,10 +25,11 @@ use std::iter; use std::num::NonZeroU32; use bp::ConsensusDecode; -use bpstd::{Network, Tx, Txid}; +use bpstd::{Tx, Txid}; use electrum::{Client, ElectrumApi, Param}; pub use electrum::{Config, ConfigBuilder, Error, Socks5Config}; use rgbstd::vm::WitnessPos; +use rgbstd::ChainNet; use super::RgbResolver; use crate::vm::WitnessOrd; @@ -40,20 +41,31 @@ macro_rules! check { } impl RgbResolver for Client { - fn check(&self, network: Network, expected_block_hash: String) -> Result<(), String> { + fn check_chain_net(&self, chain_net: ChainNet) -> Result<(), String> { // check the electrum server is for the correct network - let block_hash = check!(self.block_header(0)).block_hash().to_string(); - if expected_block_hash != block_hash { + let block_hash = check!(self.block_header(0)).block_hash(); + if chain_net.genesis_block_hash() != block_hash { return Err(s!("resolver is for a network different from the wallet's one")); } // check the electrum server has the required functionality (verbose // transactions) - let txid = match network { - Network::Mainnet => "33e794d097969002ee05d336686fc03c9e15a597c1b9827669460fac98799036", - Network::Testnet3 => "5e6560fd518aadbed67ee4a55bdc09f19e619544f5511e9343ebba66d2f62653", - Network::Testnet4 => "7aa0a7ae1e223414cb807e40cd57e667b718e42aaf9306db9102fe28912b7b4e", - Network::Signet => "8153034f45e695453250a8fb7225a5e545144071d8ed7b0d3211efa1f3c92ad8", - Network::Regtest => "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", + let txid = match chain_net { + ChainNet::BitcoinMainnet => { + "33e794d097969002ee05d336686fc03c9e15a597c1b9827669460fac98799036" + } + ChainNet::BitcoinTestnet3 => { + "5e6560fd518aadbed67ee4a55bdc09f19e619544f5511e9343ebba66d2f62653" + } + ChainNet::BitcoinTestnet4 => { + "7aa0a7ae1e223414cb807e40cd57e667b718e42aaf9306db9102fe28912b7b4e" + } + ChainNet::BitcoinSignet => { + "8153034f45e695453250a8fb7225a5e545144071d8ed7b0d3211efa1f3c92ad8" + } + ChainNet::BitcoinRegtest => { + "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" + } + _ => return Err(s!("only bitcoin is supported")), }; if let Err(e) = self.raw_call("blockchain.transaction.get", vec![ Param::String(txid.to_string()), diff --git a/src/indexers/esplora_blocking.rs b/src/indexers/esplora_blocking.rs index e72e2fa..1bcaa78 100644 --- a/src/indexers/esplora_blocking.rs +++ b/src/indexers/esplora_blocking.rs @@ -22,19 +22,20 @@ use std::num::NonZeroU32; use bp::Tx; -use bpstd::{Network, Txid}; +use bpstd::Txid; use esplora::BlockingClient; pub use esplora::{Builder, Config, Error}; use rgbstd::vm::WitnessPos; +use rgbstd::ChainNet; use super::RgbResolver; use crate::vm::WitnessOrd; impl RgbResolver for BlockingClient { - fn check(&self, _network: Network, expected_block_hash: String) -> Result<(), String> { + fn check_chain_net(&self, chain_net: ChainNet) -> Result<(), String> { // check the esplora server is for the correct network - let block_hash = self.block_hash(0)?.to_string(); - if expected_block_hash != block_hash { + let block_hash = self.block_hash(0)?; + if chain_net.genesis_block_hash() != block_hash { return Err(s!("resolver is for a network different from the wallet's one")); } Ok(()) diff --git a/src/indexers/mempool_blocking.rs b/src/indexers/mempool_blocking.rs index 6dc0017..0a1ca10 100644 --- a/src/indexers/mempool_blocking.rs +++ b/src/indexers/mempool_blocking.rs @@ -20,9 +20,10 @@ // limitations under the License. use bp::Tx; -use bpstd::{Network, Txid}; +use bpstd::Txid; use esplora::{BlockingClient, Config, Error}; use rgbstd::vm::WitnessOrd; +use rgbstd::ChainNet; use super::RgbResolver; @@ -56,8 +57,8 @@ impl MemPoolClient { } impl RgbResolver for MemPoolClient { - fn check(&self, network: Network, expected_block_hash: String) -> Result<(), String> { - self.inner.check(network, expected_block_hash) + fn check_chain_net(&self, chain_net: ChainNet) -> Result<(), String> { + self.inner.check_chain_net(chain_net) } fn resolve_pub_witness_ord(&self, txid: Txid) -> Result { diff --git a/src/lib.rs b/src/lib.rs index d37fcf1..0f10d7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,22 +37,25 @@ pub use errors::{CompletionError, CompositionError, PayError, WalletError}; pub use pay::{TransferParams, WalletProvider}; pub use rgbstd::*; pub mod resolvers { + use bp::Tx; + use rgbstd::ChainNet; + #[cfg(any(feature = "electrum_blocking", feature = "esplora_blocking"))] pub use super::indexers::*; pub use super::indexers::{AnyResolver, RgbResolver}; use super::validation::{ResolveWitness, WitnessResolverError}; - use super::vm::{WitnessOrd, XWitnessTx}; - use super::XWitnessId; + use super::vm::WitnessOrd; + use super::Txid; pub struct ContractIssueResolver; impl ResolveWitness for ContractIssueResolver { - fn resolve_pub_witness(&self, _: XWitnessId) -> Result { + fn resolve_pub_witness(&self, _: Txid) -> Result { + panic!("contract issue resolver must not be used for an already-existing contracts") + } + fn resolve_pub_witness_ord(&self, _: Txid) -> Result { panic!("contract issue resolver must not be used for an already-existing contracts") } - fn resolve_pub_witness_ord( - &self, - _: XWitnessId, - ) -> Result { + fn check_chain_net(&self, _: ChainNet) -> Result<(), WitnessResolverError> { panic!("contract issue resolver must not be used for an already-existing contracts") } } diff --git a/src/pay.rs b/src/pay.rs index 8123562..a3a14d4 100644 --- a/src/pay.rs +++ b/src/pay.rs @@ -23,27 +23,27 @@ use std::collections::{BTreeMap, BTreeSet}; use std::marker::PhantomData; use bp::dbc::tapret::TapretProof; -use bp::seals::txout::ExplicitSeal; -use bp::{Outpoint, Sats, ScriptPubkey, Vout}; +use bp::seals::txout::{CloseMethod, ExplicitSeal}; +use bp::{Outpoint, Sats, ScriptPubkey, Tx, Vout}; use bpstd::{psbt, Address}; use bpwallet::{Layer2, Layer2Tx, NoLayer2, TxRow, Wallet, WalletDescr}; use psrgbt::{ - Beneficiary as BpBeneficiary, Psbt, PsbtConstructor, PsbtMeta, RgbPsbt, TapretKeyError, + Beneficiary as BpBeneficiary, Psbt, PsbtConstructor, PsbtMeta, RgbExt, RgbPsbt, TapretKeyError, TxParams, }; -use rgbstd::containers::Transfer; +use rgbstd::containers::{AnchorSet, Transfer}; use rgbstd::interface::AssignmentsFilter; use rgbstd::invoice::{Amount, Beneficiary, InvoiceState, RgbInvoice}; use rgbstd::persistence::{IndexProvider, StashProvider, StateProvider, Stock}; use rgbstd::validation::ResolveWitness; -use rgbstd::{ContractId, DataState, XChain, XOutpoint}; +use rgbstd::{ChainNet, ContractId, DataState}; use crate::invoice::NonFungible; use crate::validation::WitnessResolverError; -use crate::vm::{WitnessOrd, XWitnessTx}; +use crate::vm::WitnessOrd; use crate::{ CompletionError, CompositionError, DescriptorRgb, PayError, RgbKeychain, Txid, - WalletOutpointsFilter, WalletUnspentFilter, WalletWitnessFilter, XWitnessId, + WalletOutpointsFilter, WalletUnspentFilter, WalletWitnessFilter, }; #[derive(Clone, PartialEq, Debug)] @@ -80,18 +80,16 @@ struct ContractOutpointsFilter< } impl< - 'stock, - 'wallet, W: WalletProvider + ?Sized, K, S: StashProvider, H: StateProvider, P: IndexProvider, L2: Layer2, - > AssignmentsFilter for ContractOutpointsFilter<'stock, 'wallet, W, K, S, H, P, L2> + > AssignmentsFilter for ContractOutpointsFilter<'_, '_, W, K, S, H, P, L2> where W::Descr: DescriptorRgb { - fn should_include(&self, output: impl Into, id: Option) -> bool { + fn should_include(&self, output: impl Into, id: Option) -> bool { let output = output.into(); if !self.wallet.filter_unspent().should_include(output, id) { return false; @@ -141,7 +139,8 @@ where Self::Descr: DescriptorRgb mut params: TransferParams, ) -> Result<(Psbt, PsbtMeta), CompositionError> { let contract_id = invoice.contract.ok_or(CompositionError::NoContract)?; - let method = self.descriptor().seal_close_method(); + + let close_method = self.descriptor().close_method(); let iface_name = invoice.iface.clone().ok_or(CompositionError::NoIface)?; let iface = stock.iface(iface_name.clone()).map_err(|e| e.to_string())?; @@ -220,7 +219,7 @@ where Self::Descr: DescriptorRgb Beneficiary::BlindedSeal(_) => vec![], Beneficiary::WitnessVout(pay2vout) => { vec![BpBeneficiary::new( - Address::new(pay2vout.address, invoice.address_network()), + Address::new(*pay2vout, invoice.address_network()), params.min_amount, )] } @@ -228,18 +227,14 @@ where Self::Descr: DescriptorRgb if prev_outputs.is_empty() { return Err(CompositionError::InsufficientState); } - let prev_outpoints = prev_outputs - .iter() - // TODO: Support liquid - .map(|o| o.as_reduced_unsafe()) - .map(|o| Outpoint::new(o.txid, o.vout)); - params.tx.change_keychain = RgbKeychain::for_method(method).into(); + let prev_outpoints = prev_outputs.iter().map(|o| Outpoint::new(o.txid, o.vout)); + params.tx.change_keychain = RgbKeychain::for_method(close_method).into(); let (mut psbt, mut meta) = self.construct_psbt(prev_outpoints, &beneficiaries, params.tx)?; let beneficiary_script = if let Beneficiary::WitnessVout(pay2vout) = invoice.beneficiary.into_inner() { - Some(pay2vout.address.script_pubkey()) + Some(pay2vout.script_pubkey()) } else { None }; @@ -252,11 +247,18 @@ where Self::Descr: DescriptorRgb .change_vout .and_then(|vout| psbt.output(vout.to_usize())) .map(|output| output.script.clone()); - psbt.sort_outputs_by(|output| !output.is_tapret_host()) - .expect("PSBT must be modifiable at this stage"); - if let Some(change_script) = change_script { + if close_method == CloseMethod::OpretFirst { + let output = psbt.construct_output_expect(ScriptPubkey::op_return(&[]), Sats::ZERO); + output.set_opret_host().expect("just created"); + psbt.sort_outputs_by(|output| !output.is_opret_host()) + .expect("PSBT must be modifiable at this stage"); + } else { + psbt.sort_outputs_by(|output| !output.is_tapret_host()) + .expect("PSBT must be modifiable at this stage"); + }; + if let Some(ref change_script) = change_script { for output in psbt.outputs() { - if output.script == change_script { + if output.script == *change_script { meta.change_vout = Some(output.vout()); break; } @@ -265,7 +267,7 @@ where Self::Descr: DescriptorRgb let beneficiary_vout = match invoice.beneficiary.into_inner() { Beneficiary::WitnessVout(pay2vout) => { - let s = pay2vout.address.script_pubkey(); + let s = (*pay2vout).script_pubkey(); let vout = psbt .outputs() .find(|output| output.script == s) @@ -277,15 +279,10 @@ where Self::Descr: DescriptorRgb Beneficiary::BlindedSeal(_) => None, }; let batch = stock - .compose(invoice, prev_outputs, method, beneficiary_vout, |_, _, _| meta.change_vout) + .compose(invoice, prev_outputs, beneficiary_vout, |_, _, _| meta.change_vout) .map_err(|e| e.to_string())?; - let methods = batch.close_method_set(); - if methods.has_opret_first() { - let output = psbt.construct_output_expect(ScriptPubkey::op_return(&[]), Sats::ZERO); - output.set_opret_host().expect("just created"); - } - + psbt.set_rgb_close_method(close_method); psbt.complete_construction(); psbt.rgb_embed(batch)?; Ok((psbt, meta)) @@ -301,7 +298,7 @@ where Self::Descr: DescriptorRgb let contract_id = invoice.contract.ok_or(CompletionError::NoContract)?; let fascia = psbt.rgb_commit()?; - if fascia.anchor.has_tapret() { + if matches!(fascia.anchor, AnchorSet::Tapret(_)) { let output = psbt .dbc_output::() .ok_or(TapretKeyError::NotTaprootOutput)?; @@ -314,50 +311,45 @@ where Self::Descr: DescriptorRgb })?; } - let witness_txid = psbt.txid(); + let witness_id = psbt.txid(); let (beneficiary1, beneficiary2) = match invoice.beneficiary.into_inner() { Beneficiary::WitnessVout(pay2vout) => { - let s = pay2vout.address.script_pubkey(); + let s = (*pay2vout).script_pubkey(); let vout = psbt .outputs() .position(|output| output.script == s) .ok_or(CompletionError::NoBeneficiaryOutput)?; let vout = Vout::from_u32(vout as u32); - let seal = XChain::Bitcoin(ExplicitSeal::new( - pay2vout.method, - Outpoint::new(witness_txid, vout), - )); + let seal = ExplicitSeal::new(Outpoint::new(witness_id, vout)); (None, vec![seal]) } - Beneficiary::BlindedSeal(seal) => (Some(XChain::Bitcoin(seal)), vec![]), + Beneficiary::BlindedSeal(seal) => (Some(seal), vec![]), }; struct FasciaResolver { - witness_id: XWitnessId, + witness_id: Txid, } impl ResolveWitness for FasciaResolver { - fn resolve_pub_witness( - &self, - _: XWitnessId, - ) -> Result { + fn resolve_pub_witness(&self, _: Txid) -> Result { unreachable!() } fn resolve_pub_witness_ord( &self, - witness_id: XWitnessId, + witness_id: Txid, ) -> Result { assert_eq!(witness_id, self.witness_id); Ok(WitnessOrd::Tentative) } + fn check_chain_net(&self, _: ChainNet) -> Result<(), WitnessResolverError> { + unreachable!() + } } stock - .consume_fascia(fascia, FasciaResolver { - witness_id: XChain::Bitcoin(witness_txid), - }) + .consume_fascia(fascia, FasciaResolver { witness_id }) .map_err(|e| e.to_string())?; let transfer = stock - .transfer(contract_id, beneficiary2, beneficiary1) + .transfer(contract_id, beneficiary2, beneficiary1, Some(witness_id)) .map_err(|e| e.to_string())?; Ok(transfer)