From 7ad91e6815a479e025e805c5ebc44fcff44a0a83 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 18 Dec 2023 21:15:56 +0100 Subject: [PATCH] runtime, cli: payment workflow --- Cargo.lock | 64 ++++++++--------- Cargo.toml | 3 +- cli/src/command.rs | 76 +++++++++++--------- cli/src/resolver.rs | 10 +-- psbt/Cargo.toml | 2 +- psbt/src/lib.rs | 2 +- src/descriptor.rs | 8 +++ src/lib.rs | 1 + src/pay.rs | 164 +++++++++++++++++++++++++++++++++++--------- src/runtime.rs | 5 +- 10 files changed, 223 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0be30a..7c1ae81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -425,7 +425,7 @@ dependencies = [ [[package]] name = "bp-util" version = "0.11.0-beta.2" -source = "git+https://github.com/BP-WG/bp-wallet?branch=v0.11#07dc6312f8aa80f4b8299d720fcdbd6858f08f82" +source = "git+https://github.com/BP-WG/bp-wallet?branch=v0.11#6418b4eaef62b78c092714582818046b6e2a2d03" dependencies = [ "amplify", "base64", @@ -447,7 +447,7 @@ dependencies = [ [[package]] name = "bp-wallet" version = "0.11.0-beta.2" -source = "git+https://github.com/BP-WG/bp-wallet?branch=v0.11#07dc6312f8aa80f4b8299d720fcdbd6858f08f82" +source = "git+https://github.com/BP-WG/bp-wallet?branch=v0.11#6418b4eaef62b78c092714582818046b6e2a2d03" dependencies = [ "amplify", "bp-esplora", @@ -1006,9 +1006,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -1021,7 +1021,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2", "tokio", "tower-service", "tracing", @@ -1371,24 +1371,6 @@ dependencies = [ "strict_encoding", ] -[[package]] -name = "psbt-rgb" -version = "0.11.0-alpha.2" -dependencies = [ - "amplify", - "baid58", - "bp-core", - "bp-std", - "commit_verify", - "getrandom", - "psbt", - "rand", - "rgb-std", - "strict_encoding", - "wasm-bindgen", - "wasm-bindgen-test", -] - [[package]] name = "quote" version = "1.0.33" @@ -1539,7 +1521,7 @@ dependencies = [ [[package]] name = "rgb-invoice" version = "0.11.0-beta.2" -source = "git+https://github.com/RGB-WG/rgb-std?branch=v0.11#b65cf9ec35a4748862dd02b59ca72fb6b7370692" +source = "git+https://github.com/RGB-WG/rgb-std?branch=v0.11#f73f03bf8dca1953ae79d852e44f34255f876c7c" dependencies = [ "amplify", "baid58", @@ -1563,6 +1545,24 @@ dependencies = [ "strict_encoding", ] +[[package]] +name = "rgb-psbt" +version = "0.11.0-alpha.2" +dependencies = [ + "amplify", + "baid58", + "bp-core", + "bp-std", + "commit_verify", + "getrandom", + "psbt", + "rand", + "rgb-std", + "strict_encoding", + "wasm-bindgen", + "wasm-bindgen-test", +] + [[package]] name = "rgb-runtime" version = "0.11.0-alpha.2" @@ -1577,8 +1577,8 @@ dependencies = [ "descriptors", "indexmap 2.1.0", "log", - "psbt", "rgb-persist-fs", + "rgb-psbt", "rgb-std", "serde", "serde_yaml", @@ -1588,7 +1588,7 @@ dependencies = [ [[package]] name = "rgb-std" version = "0.11.0-beta.2" -source = "git+https://github.com/RGB-WG/rgb-std?branch=v0.11#b65cf9ec35a4748862dd02b59ca72fb6b7370692" +source = "git+https://github.com/RGB-WG/rgb-std?branch=v0.11#f73f03bf8dca1953ae79d852e44f34255f876c7c" dependencies = [ "amplify", "baid58", @@ -1955,16 +1955,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.5.5" @@ -2192,7 +2182,7 @@ dependencies = [ "libc", "mio", "pin-project-lite", - "socket2 0.5.5", + "socket2", "windows-sys 0.48.0", ] diff --git a/Cargo.toml b/Cargo.toml index 768213e..3d06a57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ bp-esplora = "0.11.0-beta.1" descriptors = "0.11.0-beta.1" psbt = { version = "0.11.0-beta.1", features = ["client-side-validation"] } rgb-std = { version = "0.11.0-beta.2", features = ["fs"] } +rgb-psbt = { version = "0.11.0-alpha.2", path = "psbt" } indexmap = "2.0.2" chrono = "0.4.31" serde_crate = { package = "serde", version = "1", features = ["derive"] } @@ -70,8 +71,8 @@ bp-std = { workspace = true } bp-wallet = { workspace = true, features = ["fs"] } bp-esplora = { workspace = true, optional = true } descriptors = { workspace = true } -psbt = { workspace = true } rgb-std = { workspace = true } +rgb-psbt = { workspace = true } rgb-persist-fs = { version = "0.11.0-alpha", path = "fs" } indexmap = { workspace = true } chrono = { workspace = true } diff --git a/cli/src/command.rs b/cli/src/command.rs index e55a873..1a5a7ff 100644 --- a/cli/src/command.rs +++ b/cli/src/command.rs @@ -20,21 +20,22 @@ // limitations under the License. use std::fs; +use std::fs::File; use std::path::PathBuf; use std::str::FromStr; use amplify::confinement::U16; use bp_util::{Config, Exec}; use bpstd::{Sats, Txid}; -use bpwallet::{Invoice, TxParams}; -use rgb_rt::{DescriptorRgb, RgbDescr, RgbKeychain, RuntimeError}; +use psbt::PsbtVer; +use rgb_rt::{DescriptorRgb, RgbDescr, RgbKeychain, RuntimeError, TransferParams}; use rgbstd::containers::{Bindle, Transfer, UniversalBindle}; use rgbstd::contract::{ContractId, GenesisSeal, GraphSeal, StateType}; use rgbstd::interface::{ContractBuilder, FilterExclude, IfaceId, SchemaIfaces}; use rgbstd::invoice::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport}; use rgbstd::persistence::{Inventory, Stash}; use rgbstd::schema::SchemaId; -use rgbstd::SealDefinition; +use rgbstd::XSeal; use seals::txout::{CloseMethod, ExplicitSeal}; use strict_types::encoding::{FieldName, TypeName}; use strict_types::StrictVal; @@ -139,10 +140,19 @@ pub enum Command { /// Transfer RGB assets #[display("transfer")] Transfer { + /// Encode PSBT as V2 + #[clap(short = '2')] + v2: bool, + /// Method for single-use-seals #[clap(long, default_value = "tapret1st")] method: CloseMethod, + /// Amount of satoshis which should be paid to the address-based + /// beneficiary + #[clap(long, default_value = "2000")] + sats: Sats, + /// Invoice data invoice: RgbInvoice, @@ -295,7 +305,7 @@ impl Exec for RgbArgs { contract, file, } => { - let mut runtime = self.rgb_runtime(&config)?; + let runtime = self.rgb_runtime(&config)?; let bindle = runtime .export_contract(*contract) .map_err(|err| err.to_string())?; @@ -471,7 +481,7 @@ impl Exec for RgbArgs { .expect("seal must be a string"); let seal = ExplicitSeal::::from_str(seal).expect("invalid seal definition"); - let seal = GenesisSeal::from(seal); + let seal = XSeal::Bitcoin(GenesisSeal::from(seal)); // Workaround for borrow checker: let field_name = @@ -537,7 +547,7 @@ impl Exec for RgbArgs { .next() .expect("no addresses left") .addr; - Beneficiary::WitnessUtxo(addr) + Beneficiary::WitnessVoutBitcoin(addr) } (_, Some(outpoint)) => { let seal = GraphSeal::new( @@ -545,7 +555,7 @@ impl Exec for RgbArgs { outpoint.txid, outpoint.vout, ); - runtime.store_seal_secret(SealDefinition::Bitcoin(seal))?; + runtime.store_seal_secret(XSeal::Bitcoin(seal))?; Beneficiary::BlindedSeal(seal.to_concealed_seal()) } }; @@ -565,40 +575,36 @@ impl Exec for RgbArgs { } #[allow(unused_variables)] Command::Transfer { + v2, method, invoice, fee, - psbt: psbt_filename, + sats, + psbt: psbt_file, consignment: out_file, } => { - // 1. BP Wallet: Do coin selection (using Layer2 components) - // 2. BP Wallet: Construct PSBT prototype (no state transitions) - // ... complete PSBT structure updates in multi-party protocols - // 3. RGB Std: Prepare stencil - main state transition and blank state - // transitions - // 4. RGB PSBT: Embed stencil into PSBT - // ... complete PSBT client-side updates in multi-party protocols - // 5. RGB PSBT: Anchorize PSBT, extract disclosure - // 6. RGB Std: Merge disclosure into the stash, cache and index - // 7. RGB Std: Prepare consignment - let mut runtime = self.rgb_runtime(&config)?; - // TODO: Support lock time and RBFs - let params = TxParams::with(*fee); + let params = TransferParams::with(*fee, *sats); - eprint!("Constructing PSBT ... "); - let mut psbt = runtime - .wallet_mut() - .construct_psbt(coins, Invoice, params)?; - eprintln!("success"); - - eprint!("Constructing transfer consignment ... "); - let transfer = runtime - .pay(invoice, &mut psbt, method) + let (psbt, meta, transfer) = runtime + .pay(invoice, *method, params) .map_err(|err| err.to_string())?; + transfer.save(&out_file)?; - eprintln!("success"); + + let ver = if *v2 { PsbtVer::V2 } else { PsbtVer::V0 }; + eprintln!("{}", serde_yaml::to_string(&psbt).unwrap()); + match psbt_file { + Some(file_name) => { + let mut psbt_file = File::create(file_name)?; + psbt.encode(ver, &mut psbt_file)?; + } + None => match ver { + PsbtVer::V0 => println!("{psbt}"), + PsbtVer::V2 => println!("{psbt:#}"), + }, + } } Command::Inspect { file, format } => { let bindle = UniversalBindle::load_file(file)?; @@ -651,7 +657,11 @@ impl Exec for RgbArgs { format!("{root_dir}/stash/geneses/{id}.yaml"), serde_yaml::to_string(runtime.genesis(id)?)?, )?; - for (no, suppl) in runtime.contract_suppl(id).into_iter().flatten().enumerate() + for (no, suppl) in runtime + .contract_suppl_all(id) + .into_iter() + .flatten() + .enumerate() { fs::write( format!("{root_dir}/stash/geneses/{id}.suppl.{no:03}.yaml"), @@ -665,7 +675,7 @@ impl Exec for RgbArgs { serde_yaml::to_string(runtime.bundle(id)?)?, )?; } - for id in runtime.anchor_ids()? { + for id in runtime.witness_ids()? { fs::write( format!("{root_dir}/stash/anchors/{id}.yaml"), serde_yaml::to_string(runtime.anchor(id)?)?, diff --git a/cli/src/resolver.rs b/cli/src/resolver.rs index 6634d77..cdc1f67 100644 --- a/cli/src/resolver.rs +++ b/cli/src/resolver.rs @@ -24,7 +24,7 @@ use std::convert::Infallible; use bpstd::{Tx, Txid}; use rgbstd::resolvers::ResolveHeight; use rgbstd::validation::{ResolveTx, TxResolverError}; -use rgbstd::{Anchor, Layer1, WitnessAnchor}; +use rgbstd::{Layer1, WitnessAnchor, XAnchor}; use crate::RgbArgs; @@ -32,12 +32,12 @@ use crate::RgbArgs; pub struct PanickingResolver; impl ResolveHeight for PanickingResolver { type Error = Infallible; - fn resolve_anchor(&mut self, _: &Anchor) -> Result { + fn resolve_anchor(&mut self, _: &XAnchor) -> Result { unreachable!("PanickingResolver must be used only for newly issued contract validation") } } impl ResolveTx for PanickingResolver { - fn resolve_tx(&self, _: Layer1, _: Txid) -> Result { + fn resolve_bp_tx(&self, _: Layer1, _: Txid) -> Result { unreachable!("PanickingResolver must be used only for newly issued contract validation") } } @@ -48,12 +48,12 @@ impl RgbArgs { struct DumbResolver(); impl ResolveHeight for DumbResolver { type Error = Infallible; - fn resolve_anchor(&mut self, _: &Anchor) -> Result { + fn resolve_anchor(&mut self, _: &XAnchor) -> Result { todo!() } } impl ResolveTx for DumbResolver { - fn resolve_tx(&self, _: Layer1, _: Txid) -> Result { todo!() } + fn resolve_bp_tx(&self, _: Layer1, _: Txid) -> Result { todo!() } } DumbResolver::default() } diff --git a/psbt/Cargo.toml b/psbt/Cargo.toml index 97e6ffe..6cee557 100644 --- a/psbt/Cargo.toml +++ b/psbt/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "psbt-rgb" +name = "rgb-psbt" version = { workspace = true } description = "Partially signed bitcoin transaction RGB extensions" keywords = ["bitcoin", "invoices", "rgb", "smart-contracts", "psbt"] diff --git a/psbt/src/lib.rs b/psbt/src/lib.rs index 3dd8750..1f04226 100644 --- a/psbt/src/lib.rs +++ b/psbt/src/lib.rs @@ -26,7 +26,7 @@ mod rgb; use bp::dbc::opret::OpretProof; use bp::dbc::tapret::TapretProof; -use psbt::{DbcPsbtError, Psbt}; +pub use psbt::*; use rgbstd::containers::{Batch, Fascia, XchainOutpoint}; use rgbstd::{AnchorSet, XAnchor}; diff --git a/src/descriptor.rs b/src/descriptor.rs index 19cec01..9b4e50b 100644 --- a/src/descriptor.rs +++ b/src/descriptor.rs @@ -25,6 +25,7 @@ use std::{iter, vec}; use amplify::Wrapper; use bp::dbc::tapret::TapretCommitment; +use bp::dbc::Method; use bp::seals::txout::CloseMethod; use bpstd::{ CompressedPk, Derive, DeriveCompr, DeriveSet, DeriveXOnly, DerivedScript, Idx, IdxBase, @@ -64,6 +65,13 @@ impl RgbKeychain { k == Self::Rgb as u8 || k == Self::Tapret as u8 } pub fn is_seal(self) -> bool { self == Self::Rgb || self == Self::Tapret } + + pub const fn for_method(method: Method) -> Self { + match method { + Method::OpretFirst => Self::Rgb, + Method::TapretFirst => Self::Tapret, + } + } } impl FromStr for RgbKeychain { diff --git a/src/lib.rs b/src/lib.rs index a3cc7b7..9aa6698 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,4 +33,5 @@ mod descriptor; mod pay; pub use descriptor::{DescriptorRgb, RgbDescr, RgbKeychain, TapretKey}; +pub use pay::{CompletionError, CompositionError, PayError, TransferParams}; pub use runtime::{Runtime, RuntimeError}; diff --git a/src/pay.rs b/src/pay.rs index 5dc6aff..1a5599e 100644 --- a/src/pay.rs +++ b/src/pay.rs @@ -22,19 +22,32 @@ use std::convert::Infallible; use bp::seals::txout::CloseMethod; -use bp::{Outpoint, Sats, Vout}; -use bpwallet::{Beneficiary as BpBeneficiary, PsbtMeta, TxParams}; -use psbt::Psbt; +use bp::{Outpoint, Sats, ScriptPubkey, Vout}; +use bpwallet::{Beneficiary as BpBeneficiary, ConstructionError, PsbtMeta, TxParams}; +use psbt::{CommitError, EmbedError, Psbt, RgbPsbt}; use rgbstd::containers::{Bindle, BuilderSeal, Transfer}; -use rgbstd::interface::{BuilderError, FilterIncludeAll}; +use rgbstd::interface::{ContractError, FilterIncludeAll}; use rgbstd::invoice::{Beneficiary, InvoiceState, RgbInvoice}; -use rgbstd::persistence::{ConsignerError, Inventory, InventoryError, Stash}; +use rgbstd::persistence::{ + ComposeError, ConsignerError, Inventory, InventoryError, Stash, StashError, +}; +use rgbstd::XSeal; -use crate::Runtime; +use crate::{RgbKeychain, Runtime}; #[derive(Debug, Display, Error, From)] -#[display(doc_comments)] +#[display(inner)] pub enum PayError { + #[from] + Composition(CompositionError), + + #[from] + Completion(CompletionError), +} + +#[derive(Debug, Display, Error, From)] +#[display(doc_comments)] +pub enum CompositionError { /// unspecified contract. NoContract, @@ -56,20 +69,59 @@ pub enum PayError { /// the invoice has expired. InvoiceExpired, + /// 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. + TapretRequired, + /// non-fungible state is not yet supported by the invoices. Unsupported, + #[from] + #[display(inner)] + Construction(ConstructionError), + + #[from] + #[display(inner)] + Interface(ContractError), + #[from] #[display(inner)] Inventory(InventoryError), #[from] #[display(inner)] - Builder(BuilderError), + Stash(StashError), + + #[from] + #[display(inner)] + Compose(ComposeError), + + #[from] + #[display(inner)] + Embed(EmbedError), +} + +#[derive(Debug, Display, Error, From)] +#[display(doc_comments)] +pub enum CompletionError { + /// unspecified contract. + NoContract, + + /// the provided PSBT doesn't pay any sats to the RGB beneficiary address. + NoBeneficiaryOutput, + + #[from] + #[display(inner)] + Inventory(InventoryError), #[from] #[display(inner)] Consigner(ConsignerError), + + #[from] + #[display(inner)] + Commit(CommitError), } #[derive(Clone, PartialEq, Debug)] @@ -78,9 +130,18 @@ pub struct TransferParams { pub min_amount: Sats, } +impl TransferParams { + pub fn with(fee: Sats, min_amount: Sats) -> Self { + TransferParams { + tx: TxParams::with(fee), + min_amount, + } + } +} + impl Runtime { pub fn pay( - &self, + &mut self, invoice: &RgbInvoice, method: CloseMethod, params: TransferParams, @@ -92,32 +153,37 @@ impl Runtime { } pub fn construct_psbt( - &self, + &mut self, invoice: &RgbInvoice, method: CloseMethod, params: TransferParams, - ) -> Result<(Psbt, PsbtMeta), PayError> { - let contract_id = invoice.contract.ok_or(PayError::NoContract)?; + ) -> Result<(Psbt, PsbtMeta), CompositionError> { + let contract_id = invoice.contract.ok_or(CompositionError::NoContract)?; - let iface_name = invoice.iface.ok_or(PayError::NoIface)?; + let iface_name = invoice.iface.clone().ok_or(CompositionError::NoIface)?; let iface = self.stock().iface_by_name(&iface_name)?; let contract = self.contract_iface_named(contract_id, iface_name)?; let operation = invoice .operation - .or_else(|| iface.default_operation) - .ok_or(PayError::NoOperation)?; + .as_ref() + .or_else(|| iface.default_operation.as_ref()) + .ok_or(CompositionError::NoOperation)?; let assignment_name = invoice .assignment + .as_ref() .or_else(|| { iface .transitions - .get(&operation) - .and_then(|t| t.default_assignment) + .get(operation) + .and_then(|t| t.default_assignment.as_ref()) }) - .ok_or(PayError::NoAssignment)?; + .cloned() + .ok_or(CompositionError::NoAssignment)?; let outputs = match invoice.owned_state { InvoiceState::Amount(amount) => { - let mut state = contract.fungible(assignment_name, &FilterIncludeAll)?; + let mut state = contract + .fungible(assignment_name, &FilterIncludeAll)? + .into_inner(); state.sort_by_key(|a| a.value); let mut sum = 0u64; state @@ -127,35 +193,67 @@ impl Runtime { if sum >= amount { false } else { - sum += amount; + sum += a.value; true } }) .map(|a| a.owner) .collect::>() } - _ => return Err(PayError::Unsupported), + _ => return Err(CompositionError::Unsupported), }; let beneficiary = match invoice.beneficiary { - Beneficiary::BlindedSeal(seal) => BpBeneficiary::with_max(self.wallet().next_address()), + Beneficiary::BlindedSeal(_) => BpBeneficiary::with_max( + self.wallet_mut() + .next_address(RgbKeychain::for_method(method), true), + ), Beneficiary::WitnessVoutBitcoin(addr) => BpBeneficiary::new(addr, params.min_amount), }; - let (mut psbt, meta) = self - .wallet() - .construct_psbt(&outputs, [beneficiary], params.tx)?; + let outpoints = outputs + .iter() + .filter_map(|o| o.reduce_to_bp()) + .map(|o| Outpoint::new(o.txid, o.vout)); + let (mut psbt, meta) = + self.wallet_mut() + .construct_psbt(outpoints, &[beneficiary], params.tx)?; + let (beneficiary_vout, beneficiary_script) = match invoice.beneficiary { + Beneficiary::WitnessVoutBitcoin(addr) => { + let s = addr.script_pubkey(); + let vout = psbt + .outputs() + .position(|output| output.script == s) + .map(|vout| Vout::from_u32(vout as u32)); + (vout, s) + } + Beneficiary::BlindedSeal(_) => (None, none!()), + }; let batch = - self.compose(&invoice, outputs, method, meta.change_vout, |_, _, _| meta.change_vout)?; + self.compose(&invoice, outputs, method, beneficiary_vout, |_, _, _| meta.change_vout)?; + + let methods = batch.close_method_set(); + if methods.has_tapret_first() { + let output = psbt + .outputs_mut() + .find(|o| o.script.is_p2tr() && &o.script != &beneficiary_script) + .ok_or(CompositionError::TapretRequired)?; + output.set_tapret_host().expect("just created"); + } + if methods.has_opret_first() { + let output = psbt.construct_output_expect(ScriptPubkey::op_return(&[]), Sats::ZERO); + output.set_opret_host().expect("just created"); + } + psbt.rgb_embed(batch)?; Ok((psbt, meta)) } pub fn transfer( - &self, + &mut self, invoice: &RgbInvoice, psbt: &mut Psbt, - ) -> Result, PayError> { - let contract_id = invoice.contract.ok_or(PayError::NoContract)?; + ) -> Result, CompletionError> { + let contract_id = invoice.contract.ok_or(CompletionError::NoContract)?; let beneficiary = match invoice.beneficiary { Beneficiary::WitnessVoutBitcoin(addr) => { @@ -163,17 +261,17 @@ impl Runtime { let vout = psbt .outputs() .position(|output| output.script == s) - .ok_or(PayError::NoBeneficiaryOutput)?; + .ok_or(CompletionError::NoBeneficiaryOutput)?; let witness_txid = psbt.txid(); - BuilderSeal::Revealed( + BuilderSeal::Revealed(XSeal::Bitcoin( Outpoint::new(witness_txid, Vout::from_u32(vout as u32)).into(), - ) + )) } Beneficiary::BlindedSeal(seal) => BuilderSeal::Concealed(seal), }; let fascia = psbt.rgb_commit()?; - self.stock().consume(fascia)?; + self.stock_mut().consume(fascia)?; let transfer = self.stock().transfer(contract_id, [beneficiary])?; Ok(transfer) diff --git a/src/runtime.rs b/src/runtime.rs index 7b81d01..4b17b3f 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -26,6 +26,7 @@ use std::ops::{Deref, DerefMut}; use std::path::PathBuf; use std::{fs, io}; +use amplify::IoError; use bpstd::{AddressNetwork, Network, XpubDerivable}; use bpwallet::Wallet; use rgbfs::StockFs; @@ -42,7 +43,8 @@ use crate::{DescriptorRgb, RgbDescr}; #[display(inner)] pub enum RuntimeError { #[from] - Io(io::Error), + #[from(io::Error)] + Io(IoError), #[from] Serialize(SerializeError), @@ -99,6 +101,7 @@ impl From for RuntimeError { #[derive(Getters)] pub struct Runtime = RgbDescr, K = XpubDerivable> { stock_path: PathBuf, + #[getter(as_mut)] stock: Stock, #[getter(as_mut)] wallet: Wallet,