Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON output for wallet commands #1359

Merged
merged 9 commits into from
Jan 25, 2023
Merged
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ mod rarity;
mod representation;
mod sat;
mod sat_point;
mod subcommand;
pub mod subcommand;
mod tally;
mod templates;

Expand Down
63 changes: 62 additions & 1 deletion src/rarity.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::*;

#[derive(Debug, PartialEq, PartialOrd)]
pub(crate) enum Rarity {
pub enum Rarity {
Common,
Uncommon,
Rare,
Expand Down Expand Up @@ -52,6 +52,40 @@ impl From<Sat> for Rarity {
}
}

impl FromStr for Rarity {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"common" => Ok(Self::Common),
"uncommon" => Ok(Self::Uncommon),
"rare" => Ok(Self::Rare),
"epic" => Ok(Self::Epic),
"legendary" => Ok(Self::Legendary),
"mythic" => Ok(Self::Mythic),
_ => Err(anyhow!("invalid rarity: {s}")),
}
}
}

impl Serialize for Rarity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}

impl<'de> Deserialize<'de> for Rarity {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(DeserializeFromStr::deserialize(deserializer)?.0)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -95,4 +129,31 @@ mod tests {
assert_eq!(Sat(2067187500000000).rarity(), Rarity::Legendary);
assert_eq!(Sat(2067187500000000 + 1).rarity(), Rarity::Common);
}

#[test]
fn from_str_and_deserialize_ok() {
#[track_caller]
fn case(s: &str, expected: Rarity) {
let actual = s.parse::<Rarity>().unwrap();
assert_eq!(actual, expected);
let round_trip = actual.to_string().parse::<Rarity>().unwrap();
assert_eq!(round_trip, expected);
let serialized = serde_json::to_string(&expected).unwrap();
assert!(serde_json::from_str::<Rarity>(&serialized).is_ok());
}

case("common", Rarity::Common);
case("uncommon", Rarity::Uncommon);
case("rare", Rarity::Rare);
case("epic", Rarity::Epic);
case("legendary", Rarity::Legendary);
case("mythic", Rarity::Mythic);
}

#[test]
fn from_str_err() {
"abc".parse::<Rarity>().unwrap_err();

"".parse::<Rarity>().unwrap_err();
}
}
2 changes: 1 addition & 1 deletion src/sat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::*;

#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Ord, PartialOrd, Deserialize, Serialize)]
#[serde(transparent)]
pub(crate) struct Sat(pub(crate) u64);
pub struct Sat(pub(crate) u64);

impl Sat {
pub(crate) const LAST: Self = Self(Self::SUPPLY - 1);
Expand Down
25 changes: 25 additions & 0 deletions src/sat_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ impl Serialize for SatPoint {
}
}

impl<'de> Deserialize<'de> for SatPoint {
raphjaph marked this conversation as resolved.
Show resolved Hide resolved
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(DeserializeFromStr::deserialize(deserializer)?.0)
}
}

impl FromStr for SatPoint {
type Err = Error;

Expand Down Expand Up @@ -87,4 +96,20 @@ mod tests {
.parse::<SatPoint>()
.unwrap_err();
}

#[test]
fn deserialize_ok() {
assert_eq!(
serde_json::from_str::<SatPoint>(
"\"1111111111111111111111111111111111111111111111111111111111111111:1:1\""
)
.unwrap(),
SatPoint {
outpoint: "1111111111111111111111111111111111111111111111111111111111111111:1"
.parse()
.unwrap(),
offset: 1,
}
);
}
}
2 changes: 1 addition & 1 deletion src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ mod server;
mod subsidy;
mod supply;
mod traits;
pub(crate) mod wallet;
pub mod wallet;

fn print_json(output: impl Serialize) -> Result {
serde_json::to_writer_pretty(io::stdout(), &output)?;
Expand Down
14 changes: 7 additions & 7 deletions src/subcommand/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ use {
transaction_builder::TransactionBuilder,
};

mod balance;
pub mod balance;
pub(crate) mod create;
pub(crate) mod inscribe;
mod inscriptions;
mod outputs;
mod receive;
pub mod inscriptions;
pub mod outputs;
pub mod receive;
mod restore;
mod sats;
mod send;
pub mod sats;
pub mod send;
pub(crate) mod transaction_builder;
mod transactions;
pub mod transactions;

#[derive(Debug, Parser)]
pub(crate) enum Wallet {
Expand Down
7 changes: 6 additions & 1 deletion src/subcommand/wallet/balance.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use super::*;
use std::collections::BTreeSet;

#[derive(Serialize, Deserialize)]
pub struct Output {
pub cardinal: u64,
}

pub(crate) fn run(options: Options) -> Result {
let index = Index::open(&options)?;
index.update()?;
Expand All @@ -18,7 +23,7 @@ pub(crate) fn run(options: Options) -> Result {
}
}

println!("{}", balance);
print_json(Output { cardinal: balance })?;

Ok(())
}
10 changes: 5 additions & 5 deletions src/subcommand/wallet/inscriptions.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use super::*;

#[derive(Serialize)]
struct Output {
inscription: InscriptionId,
location: SatPoint,
explorer: String,
#[derive(Serialize, Deserialize)]
pub struct Output {
pub inscription: InscriptionId,
pub location: SatPoint,
pub explorer: String,
}

pub(crate) fn run(options: Options) -> Result {
Expand Down
16 changes: 14 additions & 2 deletions src/subcommand/wallet/outputs.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
use super::*;

#[derive(Serialize, Deserialize)]
pub struct Output {
pub output: OutPoint,
pub amount: u64,
}

pub(crate) fn run(options: Options) -> Result {
for (outpoint, amount) in get_unspent_outputs(&options)? {
println!("{outpoint}\t{}", amount.to_sat());
let mut outputs = Vec::new();
for (output, amount) in get_unspent_outputs(&options)? {
outputs.push(Output {
output,
amount: amount.to_sat(),
});
}

print_json(outputs)?;

Ok(())
}
7 changes: 6 additions & 1 deletion src/subcommand/wallet/receive.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use super::*;

#[derive(Deserialize, Serialize)]
pub struct Output {
pub address: Address,
}

pub(crate) fn run(options: Options) -> Result {
let address = options
.bitcoin_rpc_client_for_wallet_command(false)?
.get_new_address(None, Some(bitcoincore_rpc::json::AddressType::Bech32m))?;

println!("{}", address);
print_json(Output { address })?;

Ok(())
}
34 changes: 30 additions & 4 deletions src/subcommand/wallet/sats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ pub(crate) struct Sats {
tsv: Option<PathBuf>,
}

#[derive(Serialize, Deserialize)]
pub struct OutputTsv {
pub sat: String,
pub output: OutPoint,
}

#[derive(Serialize, Deserialize)]
pub struct OutputRare {
pub sat: Sat,
pub output: OutPoint,
pub offset: u64,
pub rarity: Rarity,
}

impl Sats {
pub(crate) fn run(&self, options: Options) -> Result {
let index = Index::open(&options)?;
Expand All @@ -17,17 +31,29 @@ impl Sats {
let utxos = get_unspent_output_ranges(&options, &index)?;

if let Some(path) = &self.tsv {
for (output, sat) in sats_from_tsv(
let mut output = Vec::new();
for (outpoint, sat) in sats_from_tsv(
utxos,
&fs::read_to_string(path)
.with_context(|| format!("I/O error reading `{}`", path.display()))?,
)? {
println!("{output}\t{sat}");
output.push(OutputTsv {
sat: sat.into(),
output: outpoint,
});
}
print_json(output)?;
} else {
for (output, sat, offset, rarity) in rare_sats(utxos) {
println!("{output}\t{sat}\t{offset}\t{rarity}");
let mut output = Vec::new();
for (outpoint, sat, offset, rarity) in rare_sats(utxos) {
output.push(OutputRare {
sat,
output: outpoint,
offset,
rarity,
});
}
print_json(output)?;
}

Ok(())
Expand Down
7 changes: 6 additions & 1 deletion src/subcommand/wallet/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ pub(crate) struct Send {
fee_rate: FeeRate,
}

#[derive(Serialize, Deserialize)]
pub struct Output {
pub transaction: Txid,
}

impl Send {
pub(crate) fn run(self, options: Options) -> Result {
let client = options.bitcoin_rpc_client_for_wallet_command(false)?;
Expand Down Expand Up @@ -62,7 +67,7 @@ impl Send {
let txid =
client.send_to_address(&self.address, amount, None, None, None, None, None, None)?;

println!("{txid}");
print_json(Output { transaction: txid })?;

return Ok(());
}
Expand Down
14 changes: 13 additions & 1 deletion src/subcommand/wallet/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@ pub(crate) struct Transactions {
limit: Option<u16>,
}

#[derive(Serialize, Deserialize)]
pub struct Output {
pub transaction: Txid,
pub confirmations: i32,
}

impl Transactions {
pub(crate) fn run(self, options: Options) -> Result {
let mut output = Vec::new();
for tx in options
.bitcoin_rpc_client_for_wallet_command(false)?
.list_transactions(
Expand All @@ -17,9 +24,14 @@ impl Transactions {
None,
)?
{
println!("{}\t{}", tx.info.txid, tx.info.confirmations);
output.push(Output {
transaction: tx.info.txid,
confirmations: tx.info.confirmations,
});
}

print_json(output)?;

Ok(())
}
}
2 changes: 1 addition & 1 deletion tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use {
self::{command_builder::CommandBuilder, expected::Expected, test_server::TestServer},
bip39::Mnemonic,
bitcoin::{blockdata::constants::COIN_VALUE, Address, Network, OutPoint, Txid},
bitcoin::{blockdata::constants::COIN_VALUE, Network, OutPoint, Txid},
executable_path::executable_path,
pretty_assertions::assert_eq as pretty_assert_eq,
regex::Regex,
Expand Down
Loading