use std::{
io::{stdin, Cursor, Read},
process::exit,
str::FromStr,
};
use rodio::{source::Source, Decoder, OutputStream};
use structopt_toml::clap::{App, Arg, Shell, SubCommand};
use darkfi::{
cli_desc,
system::sleep,
tx::Transaction,
util::{encoding::base64, parse::decode_base10},
Error, Result,
};
use darkfi_money_contract::model::TokenId;
use darkfi_serial::deserialize_async;
use crate::{money::BALANCE_BASE10_DECIMALS, Drk};
pub async fn parse_tx_from_stdin() -> Result<Transaction> {
let mut buf = String::new();
stdin().read_to_string(&mut buf)?;
let Some(bytes) = base64::decode(buf.trim()) else {
eprintln!("Failed to decode transaction");
exit(2);
};
Ok(deserialize_async(&bytes).await?)
}
pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
let v: Vec<&str> = s.split(':').collect();
if v.len() != 2 {
eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
exit(2);
}
let val0 = decode_base10(v[0], BALANCE_BASE10_DECIMALS, true);
let val1 = decode_base10(v[1], BALANCE_BASE10_DECIMALS, true);
if val0.is_err() || val1.is_err() {
eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
exit(2);
}
Ok((val0.unwrap(), val1.unwrap()))
}
pub async fn parse_token_pair(drk: &Drk, s: &str) -> Result<(TokenId, TokenId)> {
let v: Vec<&str> = s.split(':').collect();
if v.len() != 2 {
eprintln!("Invalid token pair. Use a pair such as:");
eprintln!("WCKD:MLDY");
eprintln!("or");
eprintln!("A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2");
exit(2);
}
let tok0 = drk.get_token(v[0].to_string()).await;
let tok1 = drk.get_token(v[1].to_string()).await;
if tok0.is_err() || tok1.is_err() {
eprintln!("Invalid token pair. Use a pair such as:");
eprintln!("WCKD:MLDY");
eprintln!("or");
eprintln!("A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2");
exit(2);
}
Ok((tok0.unwrap(), tok1.unwrap()))
}
pub async fn kaching() {
const WALLET_MP3: &[u8] = include_bytes!("../wallet.mp3");
let cursor = Cursor::new(WALLET_MP3);
let Ok((_stream, stream_handle)) = OutputStream::try_default() else { return };
let Ok(source) = Decoder::new(cursor) else { return };
if stream_handle.play_raw(source.convert_samples()).is_err() {
return
}
sleep(2).await;
}
pub fn generate_completions(shell: &str) -> Result<()> {
let kaching = SubCommand::with_name("kaching").about("Fun");
let ping =
SubCommand::with_name("ping").about("Send a ping request to the darkfid RPC endpoint");
let shell_arg = Arg::with_name("shell").help("The Shell you want to generate script for");
let completions = SubCommand::with_name("completions")
.about("Generate a SHELL completion script and print to stdout")
.arg(shell_arg);
let initialize =
Arg::with_name("initialize").long("initialize").help("Initialize wallet database");
let keygen =
Arg::with_name("keygen").long("keygen").help("Generate a new keypair in the wallet");
let balance =
Arg::with_name("balance").long("balance").help("Query the wallet for known balances");
let address =
Arg::with_name("address").long("address").help("Get the default address in the wallet");
let addresses =
Arg::with_name("addresses").long("addresses").help("Print all the addresses in the wallet");
let default_address = Arg::with_name("default-address")
.long("default-address")
.takes_value(true)
.help("Set the default address in the wallet");
let secrets =
Arg::with_name("secrets").long("secrets").help("Print all the secret keys from the wallet");
let import_secrets = Arg::with_name("import-secrets")
.long("import-secrets")
.help("Import secret keys from stdin into the wallet, separated by newlines");
let tree = Arg::with_name("tree").long("tree").help("Print the Merkle tree in the wallet");
let coins = Arg::with_name("coins").long("coins").help("Print all the coins in the wallet");
let wallet = SubCommand::with_name("wallet").about("Wallet operations").args(&vec![
initialize,
keygen,
balance,
address,
addresses,
default_address,
secrets,
import_secrets,
tree,
coins,
]);
let spend = SubCommand::with_name("spend")
.about("Read a transaction from stdin and mark its input coins as spent");
let coin = Arg::with_name("coin").help("base58-encoded coin to mark as unspent");
let unspend = SubCommand::with_name("unspend").about("Unspend a coin").arg(coin);
let amount = Arg::with_name("amount").help("Amount to send");
let token = Arg::with_name("token").help("Token ID to send");
let recipient = Arg::with_name("recipient").help("Recipient address");
let spend_hook = Arg::with_name("spend-hook").help("Optional contract spend hook to use");
let user_data = Arg::with_name("user-data").help("Optional user data to use");
let half_split = Arg::with_name("half-split")
.long("half-split")
.help("Split the output coin into two equal halves");
let transfer =
SubCommand::with_name("transfer").about("Create a payment transaction").args(&vec![
amount.clone(),
token.clone(),
recipient.clone(),
spend_hook.clone(),
user_data.clone(),
half_split,
]);
let value_pair = Arg::with_name("value-pair")
.short("v")
.long("value-pair")
.takes_value(true)
.help("Value pair to send:recv (11.55:99.42)");
let token_pair = Arg::with_name("token-pair")
.short("t")
.long("token-pair")
.takes_value(true)
.help("Token pair to send:recv (f00:b4r)");
let init = SubCommand::with_name("init")
.about("Initialize the first half of the atomic swap")
.args(&vec![value_pair, token_pair]);
let join =
SubCommand::with_name("join").about("Build entire swap tx given the first half from stdin");
let inspect = SubCommand::with_name("inspect")
.about("Inspect a swap half or the full swap tx from stdin");
let sign = SubCommand::with_name("sign").about("Sign a swap transaction given from stdin");
let otc = SubCommand::with_name("otc")
.about("OTC atomic swap")
.subcommands(vec![init, join, inspect, sign]);
let attach_fee = SubCommand::with_name("attach-fee")
.about("Attach the fee call to a transaction given from stdin");
let inspect = SubCommand::with_name("inspect").about("Inspect a transaction from stdin");
let broadcast =
SubCommand::with_name("broadcast").about("Read a transaction from stdin and broadcast it");
let subscribe = SubCommand::with_name("subscribe").about(
"This subscription will listen for incoming blocks from darkfid and look \
through their transactions to see if there's any that interest us. \
With `drk` we look at transactions calling the money contract so we can \
find coins sent to us and fill our wallet with the necessary metadata.",
);
let proposer_limit = Arg::with_name("proposer-limit")
.help("The minimum amount of governance tokens needed to open a proposal for this DAO");
let quorum = Arg::with_name("quorum")
.help("Minimal threshold of participating total tokens needed for a proposal to pass");
let approval_ratio = Arg::with_name("approval-ratio")
.help("The ratio of winning votes/total votes needed for a proposal to pass (2 decimals)");
let gov_token_id = Arg::with_name("gov-token-id").help("DAO's governance token ID");
let create = SubCommand::with_name("create").about("Create DAO parameters").args(&vec![
proposer_limit,
quorum,
approval_ratio,
gov_token_id,
]);
let view = SubCommand::with_name("view").about("View DAO data from stdin");
let name = Arg::with_name("name").help("Name identifier for the DAO");
let import = SubCommand::with_name("import")
.about("Import DAO data from stdin")
.args(&vec![name.clone()]);
let opt_name = Arg::with_name("dao-alias").help("Name identifier for the DAO (optional)");
let list = SubCommand::with_name("list")
.about("List imported DAOs (or info about a specific one)")
.args(&vec![opt_name]);
let balance = SubCommand::with_name("balance")
.about("Show the balance of a DAO")
.args(&vec![name.clone()]);
let mint = SubCommand::with_name("mint")
.about("Mint an imported DAO on-chain")
.args(&vec![name.clone()]);
let duration = Arg::with_name("duration").help("Duration of the proposal, in block windows");
let propose_transfer = SubCommand::with_name("propose-transfer")
.about("Create a transfer proposal for a DAO")
.args(&vec![
name.clone(),
duration,
amount,
token,
recipient,
spend_hook.clone(),
user_data.clone(),
]);
let proposals =
SubCommand::with_name("proposals").about("List DAO proposals").args(&vec![name]);
let bulla = Arg::with_name("bulla").help("Bulla identifier for the proposal");
let export = Arg::with_name("export").help("Encrypt the proposal and encode it to base64");
let mint_proposal = Arg::with_name("mint-proposal").help("Create the proposal transaction");
let proposal = SubCommand::with_name("proposal").about("View a DAO proposal data").args(&vec![
bulla.clone(),
export,
mint_proposal,
]);
let proposal_import = SubCommand::with_name("proposal-import")
.about("Import a base64 encoded and encrypted proposal from stdin");
let vote = Arg::with_name("vote").help("Vote (0 for NO, 1 for YES)");
let vote_weight =
Arg::with_name("vote-weight").help("Optional vote weight (amount of governance tokens)");
let vote = SubCommand::with_name("vote").about("Vote on a given proposal").args(&vec![
bulla.clone(),
vote,
vote_weight,
]);
let exec = SubCommand::with_name("exec").about("Execute a DAO proposal").args(&vec![bulla]);
let spend_hook_cmd = SubCommand::with_name("spend-hook")
.about("Print the DAO contract base58-encoded spend hook");
let dao = SubCommand::with_name("dao").about("DAO functionalities").subcommands(vec![
create,
view,
import,
list,
balance,
mint,
propose_transfer,
proposals,
proposal,
proposal_import,
vote,
exec,
spend_hook_cmd,
]);
let reset = Arg::with_name("reset")
.long("reset")
.help("Reset wallet state to provided block height and start scanning");
let scan = SubCommand::with_name("scan")
.about("Scan the blockchain and parse relevant transactions")
.args(&vec![reset]);
let tx_hash = Arg::with_name("tx-hash").help("Transaction hash");
let full = Arg::with_name("full").long("full").help("Print the full transaction information");
let encode = Arg::with_name("encode").long("encode").help("Encode transaction to base58");
let fetch_tx = SubCommand::with_name("fetch-tx")
.about("Fetch a blockchain transaction by hash")
.args(&vec![tx_hash, full, encode]);
let simulate_tx =
SubCommand::with_name("simulate-tx").about("Read a transaction from stdin and simulate it");
let tx_hash = Arg::with_name("tx-hash").help("Fetch specific history record (optional)");
let encode = Arg::with_name("encode")
.long("encode")
.help("Encode specific history record transaction to base58");
let txs_history = SubCommand::with_name("txs-history")
.about("Fetch broadcasted transactions history")
.args(&vec![tx_hash, encode]);
let clear_reverted =
SubCommand::with_name("clear-reverted").about("Remove reverted transactions from history");
let height = Arg::with_name("height").help("Fetch specific height record (optional)");
let scanned_blocks = SubCommand::with_name("scanned-blocks")
.about("Fetch scanned blocks records")
.args(&vec![height]);
let explorer = SubCommand::with_name("explorer")
.about("Explorer related subcommands")
.subcommands(vec![fetch_tx, simulate_tx, txs_history, clear_reverted, scanned_blocks]);
let alias = Arg::with_name("alias").help("Token alias");
let token = Arg::with_name("token").help("Token to create alias for");
let add = SubCommand::with_name("add").about("Create a Token alias").args(&vec![alias, token]);
let alias = Arg::with_name("alias")
.short("a")
.long("alias")
.takes_value(true)
.help("Token alias to search for");
let token = Arg::with_name("token")
.short("t")
.long("token")
.takes_value(true)
.help("Token to search alias for");
let show = SubCommand::with_name("show")
.about(
"Print alias info of optional arguments. \
If no argument is provided, list all the aliases in the wallet.",
)
.args(&vec![alias, token]);
let alias = Arg::with_name("alias").help("Token alias to remove");
let remove = SubCommand::with_name("remove").about("Remove a Token alias").arg(alias);
let alias = SubCommand::with_name("alias")
.about("Manage Token aliases")
.subcommands(vec![add, show, remove]);
let secret_key = Arg::with_name("secret-key").help("Mint authority secret key");
let token_blind = Arg::with_name("token-blind").help("Mint authority token blind");
let import = SubCommand::with_name("import")
.about("Import a mint authority")
.args(&vec![secret_key, token_blind]);
let generate_mint =
SubCommand::with_name("generate-mint").about("Generate a new mint authority");
let list =
SubCommand::with_name("list").about("List token IDs with available mint authorities");
let token = Arg::with_name("token").help("Token ID to mint");
let amount = Arg::with_name("amount").help("Amount to mint");
let recipient = Arg::with_name("recipient").help("Recipient of the minted tokens");
let mint = SubCommand::with_name("mint")
.about("Mint tokens")
.args(&vec![token, amount, recipient, spend_hook, user_data]);
let token = Arg::with_name("token").help("Token ID to freeze");
let freeze = SubCommand::with_name("freeze").about("Freeze a token mint").arg(token);
let token = SubCommand::with_name("token").about("Token functionalities").subcommands(vec![
import,
generate_mint,
list,
mint,
freeze,
]);
let config = Arg::with_name("config")
.short("c")
.long("config")
.takes_value(true)
.help("Configuration file to use");
let network = Arg::with_name("network")
.long("network")
.takes_value(true)
.help("Blockchain network to use");
let command = vec![
kaching,
ping,
completions,
wallet,
spend,
unspend,
transfer,
otc,
attach_fee,
inspect,
broadcast,
subscribe,
dao,
scan,
explorer,
alias,
token,
];
let fun = Arg::with_name("fun")
.short("f")
.long("fun")
.help("Flag indicating whether you want some fun in your life");
let log = Arg::with_name("log")
.short("l")
.long("log")
.takes_value(true)
.help("Set log file to ouput into");
let verbose = Arg::with_name("verbose")
.short("v")
.multiple(true)
.help("Increase verbosity (-vvv supported)");
let mut app = App::new("drk")
.about(cli_desc!())
.args(&vec![config, network, fun, log, verbose])
.subcommands(command);
let shell = match Shell::from_str(shell) {
Ok(s) => s,
Err(e) => return Err(Error::Custom(e)),
};
app.gen_completions_to("./drk", shell, &mut std::io::stdout());
Ok(())
}