13 changes: 8 additions & 5 deletions sn_cli/src/operations/config.rs
Expand Up @@ -669,7 +669,10 @@ mod read_current_node_config {
mod add_network {
use super::{Config, NetworkInfo};
use assert_fs::prelude::*;
use color_eyre::{eyre::eyre, Result};
use color_eyre::{
eyre::{bail, eyre},
Result,
};
use predicates::prelude::*;
use std::collections::BTreeSet;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
Expand Down Expand Up @@ -786,7 +789,7 @@ mod add_network {
assert_eq!(network_name, "new_network");
match network_info {
NetworkInfo::NodeConfig(_) => {
eyre!("node config doesn't apply to this test");
bail!("node config doesn't apply to this test");
}
NetworkInfo::ConnInfoLocation(path) => {
assert_eq!(*path, node_config_file.path().display().to_string());
Expand Down Expand Up @@ -893,7 +896,7 @@ mod add_network {
assert_eq!(network_name, "new_network");
match network_info {
NetworkInfo::NodeConfig(_) => {
eyre!("node config doesn't apply to this test");
bail!("node config doesn't apply to this test");
}
NetworkInfo::ConnInfoLocation(url) => {
assert_eq!(*url, String::from(url));
Expand Down Expand Up @@ -974,7 +977,7 @@ mod add_network {
assert_eq!(hex::encode(public_key.to_bytes()), genesis_key);
}
NetworkInfo::ConnInfoLocation(_) => {
eyre!("connection info doesn't apply to this test");
bail!("connection info doesn't apply to this test");
}
}

Expand Down Expand Up @@ -1050,7 +1053,7 @@ mod add_network {
assert_eq!(hex::encode(public_key.to_bytes()), genesis_key);
}
NetworkInfo::ConnInfoLocation(_) => {
eyre!("connection info doesn't apply to this test");
bail!("connection info doesn't apply to this test");
}
}

Expand Down
21 changes: 17 additions & 4 deletions sn_cli/src/subcommands/cat.rs
Expand Up @@ -12,7 +12,7 @@ use super::{
};
use color_eyre::{eyre::WrapErr, Result};
use comfy_table::Table;
use sn_api::{resolver::SafeData, Safe};
use sn_api::{resolver::SafeData, ContentType, Safe, SafeUrl};
use std::io::{self, Write};
use structopt::StructOpt;
use tokio::time::{sleep, Duration};
Expand Down Expand Up @@ -104,10 +104,23 @@ pub async fn cat_commander(cmd: CatCommands, output_fmt: OutputFmt, safe: &Safe)
SafeData::SafeKey { .. } => {
println!("No content to show since the URL targets a SafeKey. Use the 'dog' command to obtain additional information about the targeted SafeKey.");
}
SafeData::Multimap { .. }
SafeData::Multimap { xorurl, data, .. } => {
let safeurl = SafeUrl::from_xorurl(xorurl)?;
if safeurl.content_type() == ContentType::Wallet {
println!("WALLET: {:?}", data);
for (_entry_hash, (name, dbc)) in data {
let name = std::str::from_utf8(name)?;
println!("name: {}, dbc: {:?}", name, dbc);
}
} else {
println!("Type of content not supported yet by 'cat' command.")
}
}
SafeData::PrivateRegister { .. }
| SafeData::NrsEntry { .. }
| SafeData::PrivateRegister { .. }
| SafeData::PublicRegister { .. } => unimplemented!(),
| SafeData::PublicRegister { .. } => {
println!("Type of content not supported yet by 'cat' command.")
}
}

Ok(())
Expand Down
8 changes: 8 additions & 0 deletions sn_cli/src/subcommands/mod.rs
Expand Up @@ -19,6 +19,7 @@ pub mod nrs;
pub mod safe_id;
pub mod setup;
pub mod update;
pub mod wallet;
pub mod xorurl;

use structopt::{clap::AppSettings, StructOpt};
Expand Down Expand Up @@ -114,6 +115,13 @@ pub enum SubCommands {
)]
/// Manage keys on the SAFE Network
Keys(keys::KeysSubCommands),
#[structopt(
name = "wallet",
no_version,
global_settings(&[AppSettings::DisableVersion]),
)]
/// Manage wallets on the SAFE Network
Wallet(wallet::WalletSubCommands),
/// Obtain the XOR-URL of data without uploading it to the network, or decode XOR-URLs
Xorurl {
/// subcommands
Expand Down
132 changes: 132 additions & 0 deletions sn_cli/src/subcommands/wallet.rs
@@ -0,0 +1,132 @@
// Copyright 2022 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use super::{
helpers::{get_from_arg_or_stdin, serialise_output},
OutputFmt,
};
use color_eyre::{eyre::eyre, Result};
use sn_api::Safe;
use structopt::StructOpt;

#[derive(StructOpt, Debug)]
pub enum WalletSubCommands {
#[structopt(name = "create")]
/// Create a new Wallet
Create {
/// Preload with a DBC
#[structopt(long = "preload")]
preload: Option<String>,
},
#[structopt(name = "balance")]
/// Query a Wallet's total balance
Balance {
/// The target Wallet to check the total balance
target: Option<String>,
},
#[structopt(name = "deposit")]
/// Deposit a spendable DBC into a Wallet
Deposit {
/// The target Wallet to deposit the spendable DBC on
target: String,
/// The name to give this spendable DBC
#[structopt(long = "name")]
name: Option<String>,
/// The DBC to desposit (hex encoded)
dbc: String,
},
#[structopt(name = "reissue")]
/// Reissue a DBC from a Wallet to a SafeKey
Reissue {
/// Number of safecoins to reissue
amount: String,
/// Source Wallet URL
#[structopt(long = "from")]
from: String,
/// The receiving SafeKey URL or public key, otherwise pulled from stdin if not provided
#[structopt(long = "to")]
to: Option<String>,
},
}

pub async fn wallet_commander(
cmd: WalletSubCommands,
output_fmt: OutputFmt,
safe: &Safe,
) -> Result<()> {
match cmd {
WalletSubCommands::Create { preload: _ } => {
// Create wallet
let wallet_xorurl = safe.wallet_create().await?;

if OutputFmt::Pretty == output_fmt {
println!("Wallet created at: \"{}\"", wallet_xorurl);
} else {
println!("{}", serialise_output(&wallet_xorurl, output_fmt));
}

Ok(())
}
WalletSubCommands::Balance { target } => {
let target = get_from_arg_or_stdin(
target,
Some("...awaiting Wallet address/location from STDIN stream..."),
)?;

let balance = safe.wallet_balance(&target).await?;

if OutputFmt::Pretty == output_fmt {
println!(
"Wallet at \"{}\" has a total balance of {} safecoins",
target, balance
);
} else {
println!("{}", balance);
}

Ok(())
}
WalletSubCommands::Deposit { target, name, dbc } => {
let mut dbc_str =
hex::decode(dbc).map_err(|err| eyre!("Couldn't hex-decode DBC: {:?}", err))?;

dbc_str.reverse();
let dbc = bincode::deserialize(&dbc_str)
.map_err(|err| eyre!("Couldn't deserialise DBC: {:?}", err))?;

let the_name = safe.wallet_deposit(&target, name.as_deref(), &dbc).await?;

if OutputFmt::Pretty == output_fmt {
println!(
"Spendable DBC deposited with name '{}' in Wallet located at \"{}\"",
the_name, target
);
} else {
println!("{}", target);
}

Ok(())
}
WalletSubCommands::Reissue { amount, from, to } => {
let destination = get_from_arg_or_stdin(
to,
Some("...awaiting destination Wallet/SafeKey URL, or public key, from STDIN stream..."),
)?;

let dbc = safe.wallet_reissue(&from, &amount, &destination).await?;

if OutputFmt::Pretty == output_fmt {
println!("Success. Reissued DBC: {:?}", dbc);
} else {
println!("{:?}", dbc)
}

Ok(())
}
}
}