Skip to content

Commit

Permalink
Add help text to subcommands (#934)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Dec 12, 2022
1 parent 6798731 commit 2f8f098
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 62 deletions.
2 changes: 1 addition & 1 deletion docs/src/bounty/3.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ To search an ordinal wallet for ordinals with a name in `frequency.tsv`, use
the following [`ord`](https://github.com/casey/ord) command:

```
ord wallet identify --ordinals frequency.tsv
ord wallet satoshis --tsv frequency.tsv
```

### Part 0
Expand Down
6 changes: 3 additions & 3 deletions docs/src/guides/ordinal-hunting.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ wallet is named `foo`:
2. Display any rare ordinals wallet `foo`'s UTXOs:

```sh
ord wallet identify
ord wallet satoshis
```

### Searching for Rare Ordinals in a Non-Bitcoin Core Wallet
Expand Down Expand Up @@ -130,7 +130,7 @@ your wallet of funds.
7. Display your wallet's rare ordinals:

```sh
ord wallet identify
ord wallet satoshis
```

### Searching for Rare Ordinals in a Wallet that Exports Multi-path Descriptors
Expand Down Expand Up @@ -231,7 +231,7 @@ those multiple descriptors into Bitcoin Core.
7. Display your wallet's rare ordinals:

```sh
ord wallet identify
ord wallet satoshis
```

### Exporting Descriptors
Expand Down
12 changes: 11 additions & 1 deletion src/subcommand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,27 @@ mod wallet;

#[derive(Debug, Parser)]
pub(crate) enum Subcommand {
#[clap(about = "List the first satoshis of each reward epoch")]
Epochs,
#[clap(about = "Find a satoshi's current location")]
Find(find::Find),
#[clap(about = "Update the index")]
Index,
#[clap(about = "Display index statistics")]
Info(info::Info),
#[clap(about = "List the satoshis in an output")]
List(list::List),
#[clap(about = "Parse a satoshi from ordinal notation")]
Parse(parse::Parse),
#[clap(about = "Display information about a block's subsidy")]
Subsidy(subsidy::Subsidy),
#[clap(about = "Run the explorer server")]
Server(server::Server),
#[clap(about = "Display Bitcoin supply information")]
Supply,
#[clap(about = "Display satoshi traits")]
Traits(traits::Traits),
#[clap(subcommand)]
#[clap(subcommand, about = "Wallet commands")]
Wallet(wallet::Wallet),
}

Expand Down
54 changes: 30 additions & 24 deletions src/subcommand/wallet.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
use {super::*, transaction_builder::TransactionBuilder};

mod identify;
mod inscribe;
mod inscriptions;
mod receive;
mod satoshis;
mod send;
mod transaction_builder;
mod utxos;

#[derive(Debug, Parser)]
pub(crate) enum Wallet {
#[clap(about = "List wallet satoshis")]
Satoshis(satoshis::Satoshis),
#[clap(about = "Create an inscription")]
Inscribe(inscribe::Inscribe),
#[clap(about = "List wallet inscriptions")]
Inscriptions(inscriptions::Inscriptions),
#[clap(about = "Generate a receive address")]
Receive(receive::Receive),
#[clap(about = "Send a satoshi or inscription")]
Send(send::Send),
#[clap(about = "List wallet UTXOs")]
Utxos(utxos::Utxos),
}

impl Wallet {
pub(crate) fn run(self, options: Options) -> Result {
match self {
Self::Satoshis(satoshis) => satoshis.run(options),
Self::Inscribe(inscribe) => inscribe.run(options),
Self::Inscriptions(inscriptions) => inscriptions.run(options),
Self::Receive(receive) => receive.run(options),
Self::Send(send) => send.run(options),
Self::Utxos(utxos) => utxos.run(options),
}
}
}

fn list_unspent(options: &Options, index: &Index) -> Result<Vec<(OutPoint, Vec<(u64, u64)>)>> {
let client = options.bitcoin_rpc_client()?;

Expand Down Expand Up @@ -56,26 +85,3 @@ fn get_change_addresses(options: &Options, n: usize) -> Result<Vec<Address>> {

Ok(addresses)
}

#[derive(Debug, Parser)]
pub(crate) enum Wallet {
Identify(identify::Identify),
Inscribe(inscribe::Inscribe),
Inscriptions(inscriptions::Inscriptions),
Receive(receive::Receive),
Send(send::Send),
Utxos(utxos::Utxos),
}

impl Wallet {
pub(crate) fn run(self, options: Options) -> Result {
match self {
Self::Identify(identify) => identify.run(options),
Self::Inscribe(inscribe) => inscribe.run(options),
Self::Inscriptions(inscriptions) => inscriptions.run(options),
Self::Receive(receive) => receive.run(options),
Self::Send(send) => send.run(options),
Self::Utxos(utxos) => utxos.run(options),
}
}
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
use super::*;

#[derive(Debug, Parser)]
pub(crate) struct Identify {
pub(crate) struct Satoshis {
#[clap(
long,
help = "Find ordinals listed in first column of tab-separated value file <ORDINALS>."
help = "Find satoshis listed in first column of tab-separated value file <TSV>."
)]
ordinals: Option<PathBuf>,
tsv: Option<PathBuf>,
}

impl Identify {
impl Satoshis {
pub(crate) fn run(&self, options: Options) -> Result {
let index = Index::open(&options)?;
index.update()?;

let utxos = list_unspent(&options, &index)?;

if let Some(path) = &self.ordinals {
for (output, ordinal) in identify_from_tsv(
if let Some(path) = &self.tsv {
for (output, ordinal) in satoshis_from_tsv(
utxos,
&fs::read_to_string(path)
.with_context(|| format!("I/O error reading `{}`", path.display()))?,
)? {
println!("{output}\t{ordinal}");
}
} else {
for (output, ordinal, offset, rarity) in identify_rare(utxos) {
for (output, ordinal, offset, rarity) in rare_satoshis(utxos) {
println!("{output}\t{ordinal}\t{offset}\t{rarity}");
}
}
Expand All @@ -34,7 +34,7 @@ impl Identify {
}
}

fn identify_rare(utxos: Vec<(OutPoint, Vec<(u64, u64)>)>) -> Vec<(OutPoint, Ordinal, u64, Rarity)> {
fn rare_satoshis(utxos: Vec<(OutPoint, Vec<(u64, u64)>)>) -> Vec<(OutPoint, Ordinal, u64, Rarity)> {
utxos
.into_iter()
.flat_map(|(outpoint, ordinal_ranges)| {
Expand All @@ -54,7 +54,7 @@ fn identify_rare(utxos: Vec<(OutPoint, Vec<(u64, u64)>)>) -> Vec<(OutPoint, Ordi
.collect()
}

fn identify_from_tsv(
fn satoshis_from_tsv(
utxos: Vec<(OutPoint, Vec<(u64, u64)>)>,
tsv: &str,
) -> Result<Vec<(OutPoint, &str)>> {
Expand Down Expand Up @@ -115,7 +115,7 @@ mod tests {
#[test]
fn identify_no_rare_ordinals() {
assert_eq!(
identify_rare(vec![(
rare_satoshis(vec![(
outpoint(1),
vec![(51 * COIN_VALUE, 100 * COIN_VALUE), (1234, 5678)],
)]),
Expand All @@ -126,7 +126,7 @@ mod tests {
#[test]
fn identify_one_rare_ordinal() {
assert_eq!(
identify_rare(vec![(
rare_satoshis(vec![(
outpoint(1),
vec![(10, 80), (50 * COIN_VALUE, 100 * COIN_VALUE)],
)]),
Expand All @@ -137,7 +137,7 @@ mod tests {
#[test]
fn identify_two_rare_ordinals() {
assert_eq!(
identify_rare(vec![(
rare_satoshis(vec![(
outpoint(1),
vec![(0, 100), (1050000000000000, 1150000000000000)],
)]),
Expand All @@ -151,7 +151,7 @@ mod tests {
#[test]
fn identify_rare_ordinals_in_different_outpoints() {
assert_eq!(
identify_rare(vec![
rare_satoshis(vec![
(outpoint(1), vec![(50 * COIN_VALUE, 55 * COIN_VALUE)]),
(outpoint(2), vec![(100 * COIN_VALUE, 111 * COIN_VALUE)],),
]),
Expand All @@ -165,55 +165,55 @@ mod tests {
#[test]
fn identify_from_tsv_none() {
assert_eq!(
identify_from_tsv(vec![(outpoint(1), vec![(0, 1)])], "1\n").unwrap(),
satoshis_from_tsv(vec![(outpoint(1), vec![(0, 1)])], "1\n").unwrap(),
vec![]
)
}

#[test]
fn identify_from_tsv_single() {
assert_eq!(
identify_from_tsv(vec![(outpoint(1), vec![(0, 1)])], "0\n").unwrap(),
satoshis_from_tsv(vec![(outpoint(1), vec![(0, 1)])], "0\n").unwrap(),
vec![(outpoint(1), "0"),]
)
}

#[test]
fn identify_from_tsv_two_in_one_range() {
assert_eq!(
identify_from_tsv(vec![(outpoint(1), vec![(0, 2)])], "0\n1\n").unwrap(),
satoshis_from_tsv(vec![(outpoint(1), vec![(0, 2)])], "0\n1\n").unwrap(),
vec![(outpoint(1), "0"), (outpoint(1), "1"),]
)
}

#[test]
fn identify_from_tsv_out_of_order_tsv() {
assert_eq!(
identify_from_tsv(vec![(outpoint(1), vec![(0, 2)])], "1\n0\n").unwrap(),
satoshis_from_tsv(vec![(outpoint(1), vec![(0, 2)])], "1\n0\n").unwrap(),
vec![(outpoint(1), "0"), (outpoint(1), "1"),]
)
}

#[test]
fn identify_from_tsv_out_of_order_ranges() {
assert_eq!(
identify_from_tsv(vec![(outpoint(1), vec![(1, 2), (0, 1)])], "1\n0\n").unwrap(),
satoshis_from_tsv(vec![(outpoint(1), vec![(1, 2), (0, 1)])], "1\n0\n").unwrap(),
vec![(outpoint(1), "0"), (outpoint(1), "1"),]
)
}

#[test]
fn identify_from_tsv_two_in_two_ranges() {
assert_eq!(
identify_from_tsv(vec![(outpoint(1), vec![(0, 1), (1, 2)])], "0\n1\n").unwrap(),
satoshis_from_tsv(vec![(outpoint(1), vec![(0, 1), (1, 2)])], "0\n1\n").unwrap(),
vec![(outpoint(1), "0"), (outpoint(1), "1"),]
)
}

#[test]
fn identify_from_tsv_two_in_two_outputs() {
assert_eq!(
identify_from_tsv(
satoshis_from_tsv(
vec![(outpoint(1), vec![(0, 1)]), (outpoint(2), vec![(1, 2)])],
"0\n1\n"
)
Expand All @@ -225,31 +225,31 @@ mod tests {
#[test]
fn identify_from_tsv_ignores_extra_columns() {
assert_eq!(
identify_from_tsv(vec![(outpoint(1), vec![(0, 1)])], "0\t===\n").unwrap(),
satoshis_from_tsv(vec![(outpoint(1), vec![(0, 1)])], "0\t===\n").unwrap(),
vec![(outpoint(1), "0"),]
)
}

#[test]
fn identify_from_tsv_ignores_empty_lines() {
assert_eq!(
identify_from_tsv(vec![(outpoint(1), vec![(0, 1)])], "0\n\n\n").unwrap(),
satoshis_from_tsv(vec![(outpoint(1), vec![(0, 1)])], "0\n\n\n").unwrap(),
vec![(outpoint(1), "0"),]
)
}

#[test]
fn identify_from_tsv_ignores_comments() {
assert_eq!(
identify_from_tsv(vec![(outpoint(1), vec![(0, 1)])], "0\n#===\n").unwrap(),
satoshis_from_tsv(vec![(outpoint(1), vec![(0, 1)])], "0\n#===\n").unwrap(),
vec![(outpoint(1), "0"),]
)
}

#[test]
fn parse_error_reports_line_and_value() {
assert_eq!(
identify_from_tsv(vec![(outpoint(1), vec![(0, 1)])], "0\n===\n")
satoshis_from_tsv(vec![(outpoint(1), vec![(0, 1)])], "0\n===\n")
.unwrap_err()
.to_string(),
"failed to parse ordinal from string \"===\" on line 2: invalid digit found in string",
Expand Down Expand Up @@ -282,7 +282,7 @@ mod tests {

let start = Instant::now();
assert_eq!(
identify_from_tsv(utxos, &tsv)
satoshis_from_tsv(utxos, &tsv)
.unwrap()
.into_iter()
.map(|(outpoint, s)| (outpoint, s.parse().unwrap()))
Expand Down
16 changes: 8 additions & 8 deletions tests/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ fn reveal_txid_from_inscribe_stdout(stdout: &str) -> Txid {
}

#[test]
fn identify() {
fn satoshis() {
let rpc_server = test_bitcoincore_rpc::spawn();
let second_coinbase = rpc_server.mine_blocks(1)[0].txdata[0].txid();

CommandBuilder::new("--index-ordinals wallet identify")
CommandBuilder::new("--index-ordinals wallet satoshis")
.rpc_server(&rpc_server)
.expected_stdout(format!(
"{}\t{}\t0\tuncommon\n",
Expand All @@ -28,11 +28,11 @@ fn identify() {
}

#[test]
fn identify_from_tsv_success() {
fn satoshis_from_tsv_success() {
let rpc_server = test_bitcoincore_rpc::spawn();
let second_coinbase = rpc_server.mine_blocks(1)[0].txdata[0].txid();

CommandBuilder::new("--index-ordinals wallet identify --ordinals foo.tsv")
CommandBuilder::new("--index-ordinals wallet satoshis --tsv foo.tsv")
.write("foo.tsv", "nvtcsezkbtg")
.rpc_server(&rpc_server)
.expected_stdout(format!(
Expand All @@ -43,9 +43,9 @@ fn identify_from_tsv_success() {
}

#[test]
fn identify_from_tsv_parse_error() {
fn satoshis_from_tsv_parse_error() {
let rpc_server = test_bitcoincore_rpc::spawn();
CommandBuilder::new("wallet identify --ordinals foo.tsv")
CommandBuilder::new("wallet satoshis --tsv foo.tsv")
.write("foo.tsv", "===")
.rpc_server(&rpc_server)
.expected_exit_code(1)
Expand All @@ -56,9 +56,9 @@ fn identify_from_tsv_parse_error() {
}

#[test]
fn identify_from_tsv_file_not_found() {
fn satoshis_from_tsv_file_not_found() {
let rpc_server = test_bitcoincore_rpc::spawn();
CommandBuilder::new("wallet identify --ordinals foo.tsv")
CommandBuilder::new("wallet satoshis --tsv foo.tsv")
.rpc_server(&rpc_server)
.expected_exit_code(1)
.stderr_regex("error: I/O error reading `.*`\nbecause: .*\n")
Expand Down

0 comments on commit 2f8f098

Please sign in to comment.