Up until now we've hardcoded the transaction hex in our main()
function. Let's now turn our program into a command line application that can accept any transaction hex from the user.
Our goal is to do something like the following, which will return the decoded transaction:
$ cargo run -- 010000000242d5c1d6f7308bbe95c0f6e1301dd73a8da77d2155b0773bc297ac47f9cd7380010000006a4730440220771361aae55e84496b9e7b06e0a53dd122a1425f85840af7a52b20fa329816070220221dd92132e82ef9c133cb1a106b64893892a11acf2cfa1adb7698dcdc02f01b0121030077be25dc482e7f4abad60115416881fe4ef98af33c924cd8b20ca4e57e8bd5feffffff75c87cc5f3150eefc1c04c0246e7e0b370e64b17d6226c44b333a6f4ca14b49c000000006b483045022100e0d85fece671d367c8d442a96230954cdda4b9cf95e9edc763616d05d93e944302202330d520408d909575c5f6976cc405b3042673b601f4f2140b2e4d447e671c47012103c43afccd37aae7107f5a43f5b7b223d034e7583b77c8cd1084d86895a7341abffeffffff02ebb10f00000000001976a9144ef88a0b04e3ad6d1888da4be260d6735e0d308488ac508c1e000000000017a91476c0c8f2fc403c5edaea365f6a284317b9cdf7258700000000
We'll use the popular clap crate to parse command line arguments. There's a good tutorial for getting started here.
First, we'll add clap
to our Cargo.toml
:
[package]
name = "transaction_decoder_20"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hex = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.115"
sha2 = "0.10.8"
clap = "4.5.4"
Next, we'll bring the relevant modules into scope in our lib.rs file with use clap::{arg, value_parser, Command};
. Let's now add a function for parsing the user-provided command line arguments:
lib.rs
pub fn get_arg() -> String {
let matches = Command::new("Bitcoin Transaction Decoder")
.version("1.0")
.about("Decodes a raw transaction")
.arg(
arg!([RAW_TRANSACTION])
.value_parser(value_parser!(String))
.required(true)
)
.get_matches();
matches
.get_one::<String>("RAW_TRANSACTION")
.cloned()
.expect("raw transaction is required")
}
Let's look at what we're doing here more closely and then we'll walk through some examples with the command line:
- We're using the
Command
builder to set up our command line program. We can chain different methods to provide more information about our program and what arguments it expects. - We establish the
RAW_TRANSACTION
argument with the providedarg!
macro. This will be a required argument so we set.required(true)
and it will be parsed as a string. If we wanted to, we could set up additional command line arguments and flags here. - When we call
.get_matches()
, this will return a structArgMatches
, which is a container for the parsed results. If there are any errors when.get_matches
is called, such as if a required argument has not been provided, this method will exit the program and print out the error. - In order to access the parsed
String
from ourArgMatches
, we call theget_one
method on the field we're looking for, which we've namedRAW_TRANSACTION
. This returns anOption<&T>
, which in this case is anOption<&String>
. AnOption
is an enum with two variants, aSome(value)
orNone
. So there's either a value contained inSome
or no value at all. - The Option wraps a
&String
, but what we want is aString
and not&String
. So we can leverage theOption
enum'scloned
method to convert this option to anOption<String>
. - Lastly, we call
.expect
on ourOption
. This will return the containedSome
value and panic if it'sNone
. We normally don't want to panic, but in this case it makes sense to do so. Technically theOption
should always be theSome
variant at this point unless something very unexpected has happened.get_matches
will fail and print an error message if no argument is provided.
Next we simply need to update our main()
function in main.rs
to call this public method and pass in the result to our run
method:
main.rs
fn main() {
match transaction_decoder::run(transaction_decoder::get_arg()) {
Ok(json) => println!("{}", json),
Err(e) => eprintln!("{}", e),
}
}
Pretty straightforward, right? Let's test this out from the command line:
$ cargo run -- -help
We can run our command line program by typing cargo run
followed by two dashes, --
a space and then either our RAW_TRANSACTION argument or a built-in named argument. -help
is built-in with our clap
setup and provides basic information about our command line program.
You should see something like this:
Decodes a raw transaction
Usage: transaction_decoder <RAW_TRANSACTION>
Arguments:
<RAW_TRANSACTION>
Options:
-h, --help Print help
-V, --version Print version
So, --help
and --version
are two named, optional arguments we have available. There is a required positional argument called RAW_TRANSACTION
. Let's see what happens if we don't pass in any arguments:
$ cargo run --
We should get an error:
error: the following required arguments were not provided:
<RAW_TRANSACTION>
Usage: transaction_decoder <RAW_TRANSACTION>
For more information, try '--help'.
Ok, that's helpful. Let's pass in our raw transaction hex now:
$ cargo run -- 010000000242d5c1d6f7308bbe95c0f6e1301dd73a8da77d2155b0773bc297ac47f9cd7380010000006a4730440220771361aae55e84496b9e7b06e0a53dd122a1425f85840af7a52b20fa329816070220221dd92132e82ef9c133cb1a106b64893892a11acf2cfa1adb7698dcdc02f01b0121030077be25dc482e7f4abad60115416881fe4ef98af33c924cd8b20ca4e57e8bd5feffffff75c87cc5f3150eefc1c04c0246e7e0b370e64b17d6226c44b333a6f4ca14b49c000000006b483045022100e0d85fece671d367c8d442a96230954cdda4b9cf95e9edc763616d05d93e944302202330d520408d909575c5f6976cc405b3042673b601f4f2140b2e4d447e671c47012103c43afccd37aae7107f5a43f5b7b223d034e7583b77c8cd1084d86895a7341abffeffffff02ebb10f00000000001976a9144ef88a0b04e3ad6d1888da4be260d6735e0d308488ac508c1e000000000017a91476c0c8f2fc403c5edaea365f6a284317b9cdf7258700000000
And that works! It prints out our decoded transaction. Nice!
What happens if we pass in a bad hex string:
$ cargo run -- abc
We'll get the expected error message:
Hex decoding error: Odd number of digits
Pretty cool! You now have a command line program. Take some time to go through the clap
documentation and the different tutorials to familiarize yourself with all of the functionality available.