From e792438bc96dce1306ba444025858294829feafe Mon Sep 17 00:00:00 2001 From: Jose D Robles Date: Sun, 6 Aug 2023 07:56:10 +0200 Subject: [PATCH] Adding full transfer method (#310) * Adding full transfer method * Updating FullRgbTransferRequest struct * Adding self pay to * Bitcoin_inputs * Match for btc_utxo * Remove assert * Full Transfer (PSBT + Dustless + Transfer) (#312) * refactor: add full transfer operation * refactor: improviment psbt operation * feat: add internal tests * fix: improvement tests * fix: coin selection * fix: add btc change utxo * fix: bitcoin derivation indexes * Fix merge conflict. * Add debug statement. Bump version to 0.6.3-rc.1. * fix: genesis import and reduce esplora calls * Bump bitmask-core to 0.6.3-rc.2. * fix: blank transition for different contract types (#317) * fix: watcher next utxos (#319) * Bump bitmask-core to 0.6.3-rc.4. * Not skipping is_spent in allocationdetail * Adding watcher_unspent_utxos to web interface * Switch to defaulting to blockstream Esplora API. Update to bitmask-core 0.6.3-rc.5. * Tests are failing. Trying mempool.space API again. * Update to using our own mempool instance. * Configure to use our proxy to external mempool. --------- Co-authored-by: Armando CD Co-authored-by: Hunter Trujillo Co-authored-by: Armando Dutra Co-authored-by: Hunter Beast --- .env | 4 +- Cargo.lock | 6 +- Cargo.toml | 2 +- src/bin/bitmaskd.rs | 46 ++++- src/rgb.rs | 315 ++++++++++++++++++++++++++--- src/rgb/constants.rs | 2 + src/rgb/import.rs | 29 +-- src/rgb/prefetch.rs | 10 +- src/rgb/psbt.rs | 112 +++++----- src/rgb/wallet.rs | 11 +- src/structs.rs | 70 ++++++- src/web.rs | 33 ++- tests/rgb.rs | 1 + tests/rgb/integration/dustless.rs | 5 +- tests/rgb/integration/fungibles.rs | 16 +- tests/rgb/integration/import.rs | 29 ++- tests/rgb/integration/internal.rs | 148 ++++++++++++++ tests/rgb/integration/states.rs | 31 ++- tests/rgb/integration/transfers.rs | 274 ++++++++++++++++++++++++- tests/rgb/integration/udas.rs | 19 +- tests/rgb/integration/utils.rs | 39 +++- tests/rgb/unit/psbt.rs | 2 +- 22 files changed, 1056 insertions(+), 148 deletions(-) create mode 100644 tests/rgb/integration/internal.rs diff --git a/.env b/.env index c5a7ec0b..8be19343 100644 --- a/.env +++ b/.env @@ -5,8 +5,8 @@ BITCOIN_ELECTRUM_API_MAINNET=18.217.213.66:50001 BITCOIN_ELECTRUM_API_TESTNET=18.217.213.66:60001 BITCOIN_ELECTRUM_API_SIGNET=18.217.213.66:60601 BITCOIN_ELECTRUM_API_REGTEST=localhost:50001 -BITCOIN_EXPLORER_API_MAINNET=https://mempool.space/api -BITCOIN_EXPLORER_API_TESTNET=https://mempool.space/testnet/api +BITCOIN_EXPLORER_API_MAINNET=https://mempool.diba.io/api +BITCOIN_EXPLORER_API_TESTNET=https://mempool.diba.io/testnet/api BITCOIN_EXPLORER_API_SIGNET=https://mutinynet.com/api BITCOIN_EXPLORER_API_REGTEST=http://localhost:3000/regtest/api # BITCOIN_EXPLORER_API_MAINNET=http://127.0.0.1:3000 diff --git a/Cargo.lock b/Cargo.lock index a297b2f0..360e3dce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -580,7 +580,7 @@ checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" [[package]] name = "bitmask-core" -version = "0.6.2" +version = "0.6.3-rc.7" dependencies = [ "amplify", "anyhow", @@ -2649,7 +2649,7 @@ dependencies = [ [[package]] name = "rgb-std" version = "0.10.4" -source = "git+https://github.com/crisdut/rgb-wallet?branch=release/bmc-v0.6-rc17#b7e4b4bd1a901f4c805005844b71c834ef53253b" +source = "git+https://github.com/crisdut/rgb-wallet?branch=release/bmc-v0.6-rc17#b6806e7fee07e9c7ee5bc20df6a8fde2678e026c" dependencies = [ "amplify", "baid58", @@ -2668,7 +2668,7 @@ dependencies = [ [[package]] name = "rgb-wallet" version = "0.10.4" -source = "git+https://github.com/crisdut/rgb-wallet?branch=release/bmc-v0.6-rc17#b7e4b4bd1a901f4c805005844b71c834ef53253b" +source = "git+https://github.com/crisdut/rgb-wallet?branch=release/bmc-v0.6-rc17#b6806e7fee07e9c7ee5bc20df6a8fde2678e026c" dependencies = [ "amplify", "baid58", diff --git a/Cargo.toml b/Cargo.toml index 9dfe6c2f..dc4059d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bitmask-core" -version = "0.6.2" +version = "0.6.3-rc.7" authors = [ "Jose Diego Robles ", "Hunter Trujillo ", diff --git a/src/bin/bitmaskd.rs b/src/bin/bitmaskd.rs index e17c9795..4c5ac8ba 100644 --- a/src/bin/bitmaskd.rs +++ b/src/bin/bitmaskd.rs @@ -6,7 +6,7 @@ use std::{env, fs::OpenOptions, io::ErrorKind, net::SocketAddr, str::FromStr}; use amplify::hex::ToHex; use anyhow::Result; use axum::{ - body::Bytes, + body::{Bytes, Full}, extract::Path, headers::{authorization::Bearer, Authorization, CacheControl}, http::StatusCode, @@ -21,15 +21,16 @@ use bitmask_core::{ constants::{get_marketplace_seed, get_network, get_udas_utxo, switch_network}, rgb::{ accept_transfer, clear_watcher as rgb_clear_watcher, create_invoice, create_psbt, - create_watcher, import as rgb_import, issue_contract, list_contracts, list_interfaces, - list_schemas, reissue_contract, transfer_asset, watcher_address, + create_watcher, full_transfer_asset, import as rgb_import, issue_contract, list_contracts, + list_interfaces, list_schemas, reissue_contract, transfer_asset, watcher_address, watcher_details as rgb_watcher_details, watcher_next_address, watcher_next_utxo, watcher_utxo, }, structs::{ - AcceptRequest, FileMetadata, ImportRequest, InvoiceRequest, IssueAssetRequest, - IssueRequest, MediaInfo, PsbtRequest, ReIssueRequest, RgbTransferRequest, SecretString, - SelfIssueRequest, SignPsbtRequest, WatcherRequest, + AcceptRequest, FileMetadata, FullRgbTransferRequest, ImportRequest, InvoiceRequest, + IssueAssetRequest, IssueRequest, MediaInfo, PsbtFeeRequest, PsbtRequest, ReIssueRequest, + RgbTransferRequest, SecretString, SelfFullRgbTransferRequest, SelfIssueRequest, + SignPsbtRequest, WatcherRequest, }, }; use carbonado::file; @@ -136,6 +137,38 @@ async fn pay( Ok((StatusCode::OK, Json(transfer_res))) } +#[axum_macros::debug_handler] +async fn self_pay( + Json(self_pay_req): Json, +) -> Result { + info!("POST /self_pay {self_pay_req:?}"); + + let issuer_keys = save_mnemonic( + &SecretString(get_marketplace_seed().await), + &SecretString("".to_string()), + ) + .await?; + + let sk = issuer_keys.private.nostr_prv.as_ref(); + + let fee = self_pay_req + .fee + .map_or(PsbtFeeRequest::Value(1000), PsbtFeeRequest::Value); + + let request = FullRgbTransferRequest { + contract_id: self_pay_req.contract_id, + iface: self_pay_req.iface, + rgb_invoice: self_pay_req.rgb_invoice, + descriptor: SecretString(issuer_keys.public.rgb_udas_descriptor_xpub.clone()), + change_terminal: self_pay_req.terminal, + fee, + }; + + let transfer_res = full_transfer_asset(sk, request).await?; + + Ok((StatusCode::OK, Json(transfer_res))) +} + async fn accept( TypedHeader(auth): TypedHeader>, Json(accept_req): Json, @@ -483,6 +516,7 @@ async fn main() -> Result<()> { // .route("/psbt", post(psbt)) // .route("/sign", post(sign_psbt)) .route("/pay", post(pay)) + .route("/selfpay", post(self_pay)) .route("/accept", post(accept)) .route("/contracts", get(contracts)) .route("/contract/:id", get(contract_detail)) diff --git a/src/rgb.rs b/src/rgb.rs index 162b9aa8..dbe310ba 100644 --- a/src/rgb.rs +++ b/src/rgb.rs @@ -1,4 +1,7 @@ -use std::{collections::BTreeMap, str::FromStr}; +use std::{ + collections::{BTreeMap, HashSet}, + str::FromStr, +}; use ::psbt::serialize::Serialize; use amplify::hex::ToHex; @@ -8,12 +11,14 @@ use bitcoin_30::bip32::ExtendedPubKey; use bitcoin_scripts::address::AddressNetwork; use garde::Validate; use miniscript_crate::DescriptorPublicKey; +use rgb::TerminalPath; use rgbstd::{ containers::BindleContent, contract::ContractId, + interface::TypedState, persistence::{Stash, Stock}, }; -use rgbwallet::psbt::DbcPsbtError; +use rgbwallet::{psbt::DbcPsbtError, RgbInvoice}; use strict_encoding::StrictSerialize; use thiserror::Error; @@ -49,12 +54,13 @@ use crate::{ wallet::list_allocations, }, structs::{ - AcceptRequest, AcceptResponse, AssetType, ContractMetadata, ContractResponse, - ContractsResponse, ImportRequest, InterfaceDetail, InterfacesResponse, InvoiceRequest, - InvoiceResponse, IssueMetaRequest, IssueMetadata, IssueRequest, IssueResponse, - NewCollectible, NextAddressResponse, NextUtxoResponse, NextUtxosResponse, PsbtFeeRequest, + AcceptRequest, AcceptResponse, AllocationDetail, AllocationValue, AssetType, + ContractMetadata, ContractResponse, ContractsResponse, FullRgbTransferRequest, + ImportRequest, InterfaceDetail, InterfacesResponse, InvoiceRequest, InvoiceResponse, + IssueMetaRequest, IssueMetadata, IssueRequest, IssueResponse, NewCollectible, + NextAddressResponse, NextUtxoResponse, NextUtxosResponse, PsbtFeeRequest, PsbtInputRequest, PsbtRequest, PsbtResponse, ReIssueRequest, ReIssueResponse, RgbTransferRequest, - RgbTransferResponse, SchemaDetail, SchemasResponse, UDADetail, UtxoResponse, + RgbTransferResponse, SchemaDetail, SchemasResponse, SecretString, UDADetail, UtxoResponse, WatcherDetailResponse, WatcherRequest, WatcherResponse, WatcherUtxoResponse, }, validators::RGBContext, @@ -62,7 +68,10 @@ use crate::{ use self::{ carbonado::{retrieve_wallets, store_wallets}, - constants::{CARBONADO_UNAVAILABLE, RGB_DEFAULT_NAME, STOCK_UNAVAILABLE}, + constants::{ + BITCOIN_DEFAULT_FETCH_LIMIT, CARBONADO_UNAVAILABLE, RGB_DEFAULT_FETCH_LIMIT, + RGB_DEFAULT_NAME, STOCK_UNAVAILABLE, + }, contract::{export_contract, ExportContractError}, import::{import_contract, ImportContractError}, prefetch::{ @@ -142,8 +151,13 @@ pub async fn issue_contract(sk: &str, request: IssueRequest) -> Result { let mut fetch_wallet = wallet.to_owned(); for contract_type in [AssetType::RGB20, AssetType::RGB21] { - prefetch_resolver_utxos(contract_type as u32, &mut fetch_wallet, &mut resolver) - .await; + prefetch_resolver_utxos( + contract_type as u32, + &mut fetch_wallet, + &mut resolver, + Some(RGB_DEFAULT_FETCH_LIMIT), + ) + .await; } Some(fetch_wallet) @@ -329,8 +343,13 @@ pub async fn reissue_contract( Some(wallet) => { let mut fetch_wallet = wallet.to_owned(); for contract_type in [AssetType::RGB20, AssetType::RGB21] { - prefetch_resolver_utxos(contract_type as u32, &mut fetch_wallet, &mut resolver) - .await; + prefetch_resolver_utxos( + contract_type as u32, + &mut fetch_wallet, + &mut resolver, + Some(RGB_DEFAULT_FETCH_LIMIT), + ) + .await; } Some(fetch_wallet) @@ -448,7 +467,6 @@ pub async fn create_invoice( .into_iter() .map(|(f, e)| (f, e.to_string())) .collect(); - println!("{:#?}", errors); return Err(InvoiceError::Validation(errors)); } @@ -503,6 +521,8 @@ pub enum TransferError { Accept(AcceptTransferError), /// Consignment cannot be encoded. WrongConsig(String), + /// Occurs an error in export step. {0} + Export(ExportContractError), } pub async fn create_psbt(sk: &str, request: PsbtRequest) -> Result { @@ -512,7 +532,6 @@ pub async fn create_psbt(sk: &str, request: PsbtRequest) -> Result Result Result { + if let Err(err) = request.validate(&RGBContext::default()) { + let errors = err + .flatten() + .into_iter() + .map(|(f, e)| (f, e.to_string())) + .collect(); + return Err(TransferError::Validation(errors)); + } + + let mut stock = retrieve_stock(sk, ASSETS_STOCK).await.map_err(|_| { + TransferError::Retrive( + CARBONADO_UNAVAILABLE.to_string(), + STOCK_UNAVAILABLE.to_string(), + ) + })?; + + let rgb_account = retrieve_wallets(sk, ASSETS_WALLETS).await.map_err(|_| { + TransferError::Retrive( + CARBONADO_UNAVAILABLE.to_string(), + WALLET_UNAVAILABLE.to_string(), + ) + })?; + + if rgb_account.wallets.get("default").is_none() { + return Err(TransferError::NoWatcher); + } + + let mut wallet = Some(rgb_account.wallets.get("default").unwrap().to_owned()); + let contract_id = ContractId::from_str(&request.contract_id).map_err(|_| { + let mut errors = BTreeMap::new(); + errors.insert("contract_id".to_string(), "invalid contract id".to_string()); + TransferError::Validation(errors) + })?; + + let invoice = RgbInvoice::from_str(&request.rgb_invoice).map_err(|_| { + let mut errors = BTreeMap::new(); + errors.insert( + "rgb_invoice".to_string(), + "invalid rgb invoice data".to_string(), + ); + TransferError::Validation(errors) + })?; + + let mut resolver = ExplorerResolver { + explorer_url: BITCOIN_EXPLORER_API.read().await.to_string(), + ..Default::default() + }; + + if let TypedState::Amount(amount) = invoice.owned_state { + let contract = export_contract(contract_id, &mut stock, &mut resolver, &mut wallet) + .map_err(TransferError::Export)?; + + let allocations: Vec = contract + .allocations + .into_iter() + .filter(|x| x.is_mine && !x.is_spent) + .collect(); + + let total: u64 = allocations + .clone() + .into_iter() + .filter(|a| a.is_mine && !a.is_spent) + .map(|a| match a.value { + AllocationValue::Value(value) => value.to_owned(), + AllocationValue::UDA(_) => 1, + }) + .sum(); + + if total < amount { + let mut errors = BTreeMap::new(); + errors.insert("rgb_invoice".to_string(), "insufficient state".to_string()); + return Err(TransferError::Validation(errors)); + } + + let wildcard_terminal = "/*/*"; + let mut universal_desc = request.descriptor.to_string(); + for contract_type in [ + AssetType::RGB20, + AssetType::RGB21, + AssetType::Contract, + AssetType::Bitcoin, + ] { + let contract_index = contract_type as u32; + let terminal_step = format!("/{contract_index}/*"); + if universal_desc.contains(&terminal_step) { + universal_desc = universal_desc.replace(&terminal_step, wildcard_terminal); + break; + } + } + + // Get All Assets UTXOs + let mut total = 0; + let mut asset_inputs = vec![]; + for alloc in allocations.into_iter() { + match alloc.value { + AllocationValue::Value(alloc_value) => { + total += alloc_value; + let input = PsbtInputRequest { + descriptor: SecretString(universal_desc.clone()), + utxo: alloc.utxo, + utxo_terminal: alloc.derivation, + tapret: None, + }; + + asset_inputs.push(input); + + if amount <= total { + break; + } + } + AllocationValue::UDA(_) => { + let input = PsbtInputRequest { + descriptor: SecretString(universal_desc.clone()), + utxo: alloc.utxo, + utxo_terminal: alloc.derivation, + tapret: None, + }; + asset_inputs.push(input); + break; + } + } + } + + // Get All Bitcoin UTXOs + let mut bitcoin_inputs = vec![]; + if let PsbtFeeRequest::Value(fee_amount) = request.fee { + let bitcoin_indexes = [0, 1]; + let mut wallet = wallet.unwrap(); + let mut all_unspents = vec![]; + for bitcoin_index in bitcoin_indexes { + prefetch_resolver_utxos( + bitcoin_index, + &mut wallet, + &mut resolver, + Some(BITCOIN_DEFAULT_FETCH_LIMIT), + ) + .await; + prefetch_resolver_utxo_status(bitcoin_index, &mut wallet, &mut resolver).await; + + sync_wallet(bitcoin_index, &mut wallet, &mut resolver); + + let mut unspent_utxos = next_utxos(bitcoin_index, wallet.clone(), &mut resolver) + .map_err(|_| { + TransferError::Retrive( + "Esplora".to_string(), + "Retrieve Unspent UTXO unavaliable".to_string(), + ) + })?; + + all_unspents.append(&mut unspent_utxos); + } + + for utxo in all_unspents { + let TerminalPath { app, index } = utxo.derivation.terminal; + let btc_input = PsbtInputRequest { + descriptor: SecretString(universal_desc.clone()), + utxo: utxo.outpoint.to_string(), + utxo_terminal: format!("/{app}/{index}"), + tapret: None, + }; + bitcoin_inputs.push(btc_input); + + if fee_amount < (total + utxo.amount) { + break; + } else { + total += utxo.amount; + } + } + } + + let psbt_req = PsbtRequest { + bitcoin_inputs, + bitcoin_changes: vec![], + fee: request.fee, + asset_inputs, + asset_descriptor_change: None, + asset_terminal_change: Some(request.change_terminal), + }; + + let psbt_response = create_psbt(sk, psbt_req).await?; + + transfer_asset( + sk, + RgbTransferRequest { + psbt: psbt_response.psbt, + rgb_invoice: request.rgb_invoice, + terminal: psbt_response.terminal, + }, + ) + .await + } else { + let mut errors = BTreeMap::new(); + errors.insert( + "rgb_invoice".to_string(), + "invalid rgb invoice data".to_string(), + ); + Err(TransferError::Validation(errors)) + } +} + pub async fn accept_transfer( sk: &str, request: AcceptRequest, @@ -690,7 +912,6 @@ pub async fn accept_transfer( .into_iter() .map(|(f, e)| (f, e.to_string())) .collect(); - println!("{:#?}", errors); return Err(TransferError::Validation(errors)); } @@ -741,8 +962,13 @@ pub async fn get_contract(sk: &str, contract_id: &str) -> Result { let mut fetch_wallet = wallet.to_owned(); for contract_type in [AssetType::RGB20, AssetType::RGB21] { - prefetch_resolver_utxos(contract_type as u32, &mut fetch_wallet, &mut resolver) - .await; + prefetch_resolver_utxos( + contract_type as u32, + &mut fetch_wallet, + &mut resolver, + Some(RGB_DEFAULT_FETCH_LIMIT), + ) + .await; } Some(fetch_wallet) @@ -778,8 +1004,13 @@ pub async fn list_contracts(sk: &str) -> Result { Some(wallet) => { let mut fetch_wallet = wallet.to_owned(); for contract_type in [AssetType::RGB20, AssetType::RGB21] { - prefetch_resolver_utxos(contract_type as u32, &mut fetch_wallet, &mut resolver) - .await; + prefetch_resolver_utxos( + contract_type as u32, + &mut fetch_wallet, + &mut resolver, + Some(RGB_DEFAULT_FETCH_LIMIT), + ) + .await; } Some(fetch_wallet) } @@ -877,7 +1108,6 @@ pub async fn import(sk: &str, request: ImportRequest) -> Result Result { let mut fetch_wallet = wallet.to_owned(); - prefetch_resolver_utxos(import.clone() as u32, &mut fetch_wallet, &mut resolver).await; + prefetch_resolver_utxos( + import.clone() as u32, + &mut fetch_wallet, + &mut resolver, + Some(RGB_DEFAULT_FETCH_LIMIT), + ) + .await; Some(fetch_wallet) } _ => None, @@ -928,6 +1164,7 @@ pub async fn import(sk: &str, request: ImportRequest) -> Result Result Result Result Result< ..Default::default() }; - prefetch_resolver_utxos(iface_index, &mut wallet, &mut resolver).await; + prefetch_resolver_utxos( + iface_index, + &mut wallet, + &mut resolver, + Some(RGB_DEFAULT_FETCH_LIMIT), + ) + .await; prefetch_resolver_utxo_status(iface_index, &mut wallet, &mut resolver).await; sync_wallet(iface_index, &mut wallet, &mut resolver); - let utxos = next_utxos(iface_index, wallet.clone(), &mut resolver)? + let utxos: HashSet = next_utxos(iface_index, wallet.clone(), &mut resolver)? .into_iter() .map(|x| UtxoResponse { outpoint: x.outpoint.to_string(), @@ -1195,7 +1450,9 @@ pub async fn watcher_unspent_utxos(sk: &str, name: &str, iface: &str) -> Result< .insert(RGB_DEFAULT_NAME.to_string(), wallet); store_wallets(sk, ASSETS_WALLETS, &rgb_account).await?; - Ok(NextUtxosResponse { utxos }) + Ok(NextUtxosResponse { + utxos: utxos.into_iter().collect(), + }) } pub async fn clear_stock(sk: &str) { diff --git a/src/rgb/constants.rs b/src/rgb/constants.rs index 78b59848..66821adc 100644 --- a/src/rgb/constants.rs +++ b/src/rgb/constants.rs @@ -5,6 +5,8 @@ pub const RGB_PSBT_TAPRET: &str = "TAPRET"; pub const RGB_DEFAULT_NAME: &str = "default"; pub const RGB_OLDEST_VERSION: [u8; 8] = [0; 8]; pub const RGB_STRICT_TYPE_VERSION: [u8; 8] = *b"rgbst161"; +pub const RGB_DEFAULT_FETCH_LIMIT: u32 = 10; +pub const BITCOIN_DEFAULT_FETCH_LIMIT: u32 = 20; // General Errors #[cfg(target_arch = "wasm32")] diff --git a/src/rgb/import.rs b/src/rgb/import.rs index cfada05f..9b268458 100644 --- a/src/rgb/import.rs +++ b/src/rgb/import.rs @@ -3,11 +3,11 @@ use amplify::{ hex::FromHex, }; use bech32::{decode, FromBase32}; -use rgb_schemata::{nia_schema, uda_schema}; +use rgb_schemata::{nia_rgb20, nia_schema, uda_rgb21, uda_schema}; use rgbstd::{ containers::Contract, contract::Genesis, - interface::{rgb20, rgb21}, + interface::{rgb20, rgb21, IfacePair}, persistence::{Inventory, Stash, Stock}, resolvers::ResolveHeight, validation::ResolveTx, @@ -70,19 +70,22 @@ pub fn contract_from_genesis( asset_type: AssetType, stock: Option<&mut Stock>, ) -> Contract { - let schema = match asset_type { - AssetType::RGB20 => nia_schema(), - AssetType::RGB21 => uda_schema(), - _ => nia_schema(), + let (schema, iface, iimpl) = match asset_type { + AssetType::RGB20 => (nia_schema(), rgb20(), nia_rgb20()), + AssetType::RGB21 => (uda_schema(), rgb21(), uda_rgb21()), + _ => (nia_schema(), rgb20(), nia_rgb20()), }; if let Some(stock) = stock { - match asset_type { - AssetType::RGB20 => stock.import_iface(rgb20()), - AssetType::RGB21 => stock.import_iface(rgb21()), - _ => stock.import_iface(rgb20()), - } - .expect("import iface failed"); + stock + .import_iface(iface.clone()) + .expect("import iface failed"); } - Contract::new(schema, genesis) + let mut contract = Contract::new(schema, genesis); + contract + .ifaces + .insert(iface.iface_id(), IfacePair::with(iface, iimpl)) + .expect("import iface pair failed"); + + contract } diff --git a/src/rgb/prefetch.rs b/src/rgb/prefetch.rs index 70bf5ca6..0d0fe747 100644 --- a/src/rgb/prefetch.rs +++ b/src/rgb/prefetch.rs @@ -55,6 +55,7 @@ pub async fn prefetch_resolver_utxos( iface_index: u32, wallet: &mut RgbWallet, explorer: &mut ExplorerResolver, + limit: Option, ) { } @@ -290,14 +291,19 @@ pub async fn prefetch_resolver_utxos( iface_index: u32, wallet: &mut RgbWallet, explorer: &mut ExplorerResolver, + limit: Option, ) { use std::collections::HashSet; let esplora_client: EsploraBlockchain = EsploraBlockchain::new(&explorer.explorer_url, 1).with_concurrency(6); - let step = 100; let index = 0; + let mut step = 100; + if let Some(limit) = limit { + step = limit; + } + let mut utxos = bset![]; let scripts = wallet.descr.derive(iface_index, index..step); @@ -395,11 +401,11 @@ pub async fn prefetch_resolver_waddress( let esplora_client: EsploraBlockchain = EsploraBlockchain::new(&explorer.explorer_url, 1).with_concurrency(6); + let index = 0; let mut step = 100; if let Some(limit) = limit { step = limit; } - let index = 0; let sc = AddressCompat::from_str(address).expect("invalid address"); let script = ScriptBuf::from_hex(&sc.script_pubkey().to_hex()).expect("invalid script"); diff --git a/src/rgb/psbt.rs b/src/rgb/psbt.rs index 4dedb2ff..feb8b66c 100644 --- a/src/rgb/psbt.rs +++ b/src/rgb/psbt.rs @@ -35,7 +35,7 @@ use wallet::{ use crate::{ rgb::{constants::RGB_PSBT_TAPRET, structs::AddressAmount}, - structs::{PsbtInputRequest, SecretString}, + structs::{AssetType, PsbtInputRequest, SecretString}, }; use crate::rgb::structs::AddressFormatParseError; @@ -82,8 +82,8 @@ pub enum PsbtInputError { pub fn create_psbt( psbt_inputs: Vec, psbt_outputs: Vec, - asset_terminal_change: String, bitcoin_fee: u64, + terminal_change: Option, wallet: Option, tx_resolver: &impl ResolveTx, ) -> Result<(Psbt, String), CreatePsbtError> { @@ -95,21 +95,22 @@ pub fn create_psbt( // Define "Universal" Descriptor let psbt_input = psbt_inputs[0].clone(); - let input_terminal = psbt_input - .utxo_terminal - .parse::>() - .map_err(CreatePsbtError::WrongTerminal)?; - - let contract_index = match input_terminal.first() { - Some(index) => index, - _ => { - return Err(CreatePsbtError::WrongTerminal( - bip32::Error::InvalidChildNumberFormat, - )) + let wildcard_terminal = "/*/*"; + let mut descriptor_pub = psbt_input.descriptor.to_string(); + for contract_type in [ + AssetType::RGB20, + AssetType::RGB21, + AssetType::Contract, + AssetType::Bitcoin, + ] { + let contract_index = contract_type as u32; + let terminal_step = format!("/{contract_index}/*"); + if descriptor_pub.contains(&terminal_step) { + descriptor_pub = descriptor_pub.replace(&terminal_step, wildcard_terminal); + break; } - }; - let terminal_step = format!("/{contract_index}/*"); - let descriptor_pub = psbt_input.descriptor.0.replace(&terminal_step, "/*/*"); + } + let global_descriptor: &Descriptor = &Descriptor::from_str(&descriptor_pub) .map_err(|op| CreatePsbtError::WrongDescriptor(op.to_string()))?; @@ -180,14 +181,18 @@ pub fn create_psbt( }]; // Change Terminal Derivation - let change_derivation = asset_terminal_change - .parse::>() - .map_err(CreatePsbtError::WrongTerminal)?; + let mut change_index = DerivationSubpath::new(); + if let Some(terminal_change) = terminal_change { + change_index = terminal_change + .parse::>() + .map_err(CreatePsbtError::WrongTerminal)?; + } + let mut psbt = Psbt::construct( global_descriptor, &inputs, &outputs, - change_derivation.to_vec(), + change_index.to_vec(), bitcoin_fee, tx_resolver, ) @@ -226,7 +231,7 @@ pub fn create_psbt( } } - Ok((psbt, asset_terminal_change)) + Ok((psbt, change_index.to_string())) } fn complete_input_desc( @@ -372,8 +377,8 @@ pub fn save_commit(terminal: &str, commit: Vec, wallet: &mut RgbWallet) { // TODO: [Experimental] Review with Diba Team pub fn fee_estimate( assets_inputs: Vec, - asset_descriptor_change: SecretString, - asset_terminal_change: String, + asset_descriptor_change: Option, + asset_terminal_change: Option, bitcoin_inputs: Vec, bitcoin_changes: Vec, fee_rate: f32, @@ -408,7 +413,7 @@ where // Main Recipient total_spent = vout_value - total_spent; - let target_script = get_recipient_script(&asset_descriptor_change, &asset_terminal_change) + let target_script = get_recipient_script(asset_descriptor_change, asset_terminal_change) .expect("invalid derivation"); let excess = decide_change(total_spent, fee_rate, &target_script); @@ -422,29 +427,42 @@ where } } -fn get_recipient_script(descriptor_pub: &SecretString, bitcoin_terminal: &str) -> Option