diff --git a/Cargo.toml b/Cargo.toml index b651b09..e818a82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,5 +31,5 @@ rgb = { git = "https://github.com/rgb-org/rgb-sdk" } [dependencies.lnpbp] git = "https://github.com/lnp-bp/rust-lnpbp" -branch = "feat-wallet" +branch = "staging" features = ["use-tor", "use-tokio", "use-log", "use-daemons", "use-rgb"] diff --git a/src/accounts/mod.rs b/src/accounts/mod.rs index d74a043..bc335f3 100644 --- a/src/accounts/mod.rs +++ b/src/accounts/mod.rs @@ -13,11 +13,12 @@ // If not, see . -use std::{io, fs, fmt, hash::Hash}; +use std::{io, fs, fmt, hash::Hash, convert::TryInto}; use std::path::PathBuf; use std::collections::HashMap; use rand::{thread_rng, RngCore}; +use lnpbp::bp; use lnpbp::bitcoin; use bitcoin::secp256k1; use bitcoin::util::bip32::{ExtendedPubKey, DerivationPath, ChildNumber}; @@ -26,7 +27,6 @@ use bitcoin_wallet::{account::Seed, context::SecpContext}; use lnpbp::csv::serialize::{self, network::*, storage::*}; use crate::error::Error; -use lnpbp::miniscript::bitcoin::hashes::core::fmt::Formatter; #[derive(Debug)] @@ -128,7 +128,7 @@ impl Keyring { let context = SecpContext::new(); let encrypted = seed.encrypt(passphrase) .expect("Encryption failed"); - let master_key = context.master_private_key(bitcoin::Network::Bitcoin, &seed) + let master_key = context.master_private_key(bp::Network::Mainnet.try_into().unwrap(), &seed) .expect("Public key generation failed"); let xpubkey = context.extended_public_from_private(&master_key); Keyring::Hierarchical { diff --git a/src/commands/fungible.rs b/src/commands/fungible.rs index 8e9baed..b7b946a 100644 --- a/src/commands/fungible.rs +++ b/src/commands/fungible.rs @@ -15,6 +15,7 @@ use std::{path::PathBuf, str::FromStr}; use clap::Clap; +use regex::Regex; use lnpbp::bitcoin; use bitcoin::{TxIn, OutPoint}; @@ -23,6 +24,18 @@ use lnpbp::{bp, rgb}; use ::rgb::fungible; +fn ticker_validator(name: String) -> Result<(), String> { + let re = Regex::new(r"^[A-Z]{3,8}$").expect("Regex parse failure"); + if !re.is_match(&name) { + Err("Ticker name must be between 2 and 8 chars, contain no spaces and \ + consist only of capital letters\ + ".to_string()) + } else { + Ok(()) + } +} + + /// Defines information required to generate bitcoin transaction output from /// command-line argument #[derive(Clone, Debug, Display)] @@ -67,74 +80,83 @@ pub enum Command { }, /// Creates a new asset - Issue { - /// Limit for the total supply (by default equals to the `amount` - #[clap(short, long)] - supply: Option, - - /// Enables secondary issuance/inflation; takes UTXO seal definition - /// as its value - #[clap(short, long, requires("supply"))] - inflatable: Option, - - /// Precision, i.e. number of digits reserved for fractional part - #[clap(short, long, default_value="0")] - precision: u8, + Issue(Issue), - /// Dust limit for asset transfers; defaults to no limit - #[clap(short="D", long, default_value="0")] - dust_limit: rgb::data::Amount, - - /// Filename to export asset genesis to; - /// saves into data dir if not provided - #[clap(short, long)] - output: Option, - - /// Asset ticker (will be capitalized) - ticker: String, - - /// Asset title - title: String, - - /// Asset description - #[clap(short, long)] - description: Option, + /// Transfers some asset to another party + Pay(Pay) +} - /// Asset allocation, in form of @: - #[clap(min_values=1)] - allocate: Vec, - }, +#[derive(Clap, Clone, Debug, Display)] +#[display_from(Debug)] +pub struct Issue { + /// Limit for the total supply (by default equals to the `amount` + #[clap(short, long)] + pub supply: Option, + + /// Enables secondary issuance/inflation; takes UTXO seal definition + /// as its value + #[clap(short, long, requires("supply"))] + pub inflatable: Option, + + /// Precision, i.e. number of digits reserved for fractional part + #[clap(short, long, default_value="0")] + pub precision: u8, + + /// Dust limit for asset transfers; defaults to no limit + #[clap(short="D", long)] + pub dust_limit: Option, + + /// Filename to export asset genesis to; + /// saves into data dir if not provided + #[clap(short, long)] + pub output: Option, + + /// Asset ticker + #[clap(validator=ticker_validator)] + pub ticker: String, + + /// Asset title + pub title: String, + + /// Asset description + #[clap(short, long)] + pub description: Option, + + /// Asset allocation, in form of @: + #[clap(min_values=1)] + pub allocate: Vec, +} - /// Transfers some asset to another party - Pay { - /// Use custom commitment output for generated witness transaction - #[clap(long)] - commit_txout: Option, +#[derive(Clap, Clone, Debug, Display)] +#[display_from(Debug)] +pub struct Pay { + /// Use custom commitment output for generated witness transaction + #[clap(long)] + commit_txout: Option, - /// Adds output(s) to generated witness transaction - #[clap(long)] - txout: Vec, + /// Adds output(s) to generated witness transaction + #[clap(long)] + txout: Vec, - /// Adds input(s) to generated witness transaction - #[clap(long)] - txin: Vec, + /// Adds input(s) to generated witness transaction + #[clap(long)] + txin: Vec, - /// Allocates other assets to custom outputs - #[clap(short, long)] - allocate: Vec, + /// Allocates other assets to custom outputs + #[clap(short, long)] + allocate: Vec, - /// Saves witness transaction to a file instead of publishing it - #[clap(short, long)] - transaction: Option, + /// Saves witness transaction to a file instead of publishing it + #[clap(short, long)] + transaction: Option, - /// Saves proof data to a file instead of sending it to the remote party - #[clap(short, long)] - proof: Option, + /// Saves proof data to a file instead of sending it to the remote party + #[clap(short, long)] + proof: Option, - /// Invoice to pay - invoice: fungible::Invoice, + /// Invoice to pay + invoice: fungible::Invoice, - /// Overrides amount provided in the invoice - amount: rgb::data::Amount, - } -} + /// Overrides amount provided in the invoice + amount: rgb::data::Amount, +} \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index d71a8de..1452c6e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,8 +13,10 @@ // If not, see . -use std::path::PathBuf; +use std::{io, fs, path::PathBuf}; use clap::Clap; +use lnpbp::bp; +use lnpbp::rgb::commit::TransitionCommitment; use crate::constants::*; use crate::commands::*; @@ -48,8 +50,8 @@ pub struct Opts { pub bpd_subscr: String, /// Network to use - #[clap(global=true, short, long, default_value="testnet", env="KALEIDOSCOPE_NETWORK")] - pub network: lnpbp::bitcoin::Network, + #[clap(global=true, short, long, default_value="signet", env="KALEIDOSCOPE_NETWORK")] + pub network: bp::Network, #[clap(subcommand)] pub command: Command @@ -63,7 +65,7 @@ pub struct Opts { #[display_from(Debug)] pub struct Config { pub verbose: u8, - pub network: lnpbp::bitcoin::Network, + pub network: bp::Network, pub data_dir: PathBuf, pub bpd_api: String, pub bpd_subscr: String, @@ -91,7 +93,7 @@ impl Default for Config { fn default() -> Self { Self { verbose: 0, - network: lnpbp::bitcoin::Network::Testnet, + network: bp::Network::Signet, data_dir: DATA_DIR.parse().expect("Parse of DATA_DIR constant has failed"), bpd_api: BPD_API_ADDR.to_string(), bpd_subscr: BPD_PUSH_ADDR.to_string() @@ -104,6 +106,8 @@ impl Default for Config { pub enum DataItem { Root, KeyringVault, + ContractsVault, + ContractGenesis(TransitionCommitment) } impl Config { @@ -112,7 +116,24 @@ impl Config { match item { DataItem::Root => (), DataItem::KeyringVault => path.push("vault.dat"), + DataItem::ContractsVault => { + path.push("contracts"); + if !path.exists() { + fs::create_dir_all(path.clone()).unwrap(); + } + }, + DataItem::ContractGenesis(cmt) => { + path = self.data_path(DataItem::ContractsVault); + path.push(format!("{}", cmt)); + path.set_extension("rgb"); + }, } path } + + pub fn data_writer(&self, item: DataItem) -> Result { + let file_name = self.data_path(item); + let file = fs::File::create(file_name)?; + Ok(io::BufWriter::new(file)) + } } diff --git a/src/error.rs b/src/error.rs index 99af38c..0767005 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,6 +17,7 @@ use std::io; use tokio::task::JoinError; use lnpbp::csv::serialize; +use lnpbp::rgb; #[derive(Debug, Display, From)] @@ -43,6 +44,9 @@ pub enum Error { OperationNotSupported(String), UnknownKeyring(usize), + + #[derive_from] + FungibleSchemataError(rgb::schemata::fungible::Error) } impl std::error::Error for Error { } diff --git a/src/main.rs b/src/main.rs index c61ef40..28ce08d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -118,6 +118,11 @@ async fn main() -> Result<(), Error> { runtime.account_create(name, derivation_path, description.unwrap_or_default()), _ => unimplemented!() }, + Command::Fungible(subcommand) => match subcommand { + fungible::Command::Issue(issue) => + runtime.fungible_issue(issue), + _ => unimplemented!() + } //Command::Query { query } => runtime.command_query(query).await?, _ => unimplemented!() } diff --git a/src/runtime.rs b/src/runtime.rs index fd05d27..49fc857 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -24,10 +24,15 @@ use bitcoin::network::constants::Network; use bitcoin::Address; use bitcoin_wallet::{account::*, context::*}; +use crate::lnpbp::rgb::commit::Identifiable; +use lnpbp::rgb::data::amount; +use lnpbp::rgb::Rgb1; + use super::*; use crate::constants::*; use crate::error::Error; use crate::accounts::{KeyringManager, Account}; +use lnpbp::csv::Storage; pub struct Runtime { @@ -113,6 +118,32 @@ impl Runtime { println!("New account {} successfully added to the default keyring and saved to the vault", name); Ok(()) } + + pub fn fungible_issue(mut self, issue: commands::fungible::Issue) -> Result<(), Error> { + info!("Issuing asset with parameters {}", issue); + let balances = issue.allocate.iter().map(|alloc| { + let confidential = amount::Confidential::from(alloc.amount); + (alloc.seal, confidential.commitment) + }).collect(); + let genesis = Rgb1::issue( + self.config.network, + &issue.ticker, + &issue.title, + issue.description.as_deref(), + balances, + issue.precision, + issue.supply, + issue.dust_limit + )?; + + let asset_id = genesis.commitment() + .expect("Probability of the commitment generation failure is less than negligible"); + println!("New asset {} is issued with ContractId={}", issue.ticker, asset_id); + + genesis.storage_serialize(self.config.data_writer(DataItem::ContractGenesis(asset_id))?)?; + + Ok(()) + } } impl Drop for Runtime {