use clap::{Parser, Subcommand, ValueEnum};
use std::path::PathBuf;
fn parse_chunk_size(s: &str) -> Result<usize, String> {
let size: usize = s
.parse()
.map_err(|_| format!("'{}' is not a valid number", s))?;
if size < 1024 {
return Err(format!("chunk size {} is too small (minimum: 1024)", size));
}
if size > 31000 {
return Err(format!("chunk size {} is too large (maximum: 31000)", size));
}
Ok(size)
}
#[derive(Parser)]
#[command(name = "thru")]
#[command(about = "Command-line interface for the Thru blockchain")]
#[command(version = thru_base::get_version!())]
pub struct Cli {
#[arg(long, global = true)]
pub json: bool,
#[arg(long, global = true)]
pub quiet: bool,
#[arg(long = "url", global = true)]
pub url: Option<String>,
#[arg(long = "network", global = true)]
pub network: Option<String>,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
#[command(name = "getversion")]
GetVersion,
#[command(name = "gethealth")]
GetHealth,
#[command(name = "getstatus")]
GetStatus,
#[command(name = "getheight")]
GetHeight,
#[command(name = "getaccountinfo")]
GetAccountInfo {
account: Option<String>,
#[arg(long)]
data_start: Option<usize>,
#[arg(long)]
data_len: Option<usize>,
},
#[command(name = "getbalance")]
GetBalance {
account: Option<String>,
},
#[command(name = "getslotmetrics")]
GetSlotMetrics {
slot: u64,
end_slot: Option<u64>,
},
#[command(name = "transfer")]
Transfer {
src: String,
dst: String,
value: u64,
},
#[command(name = "uploader")]
Uploader {
#[command(subcommand)]
subcommand: UploaderCommands,
},
#[command(name = "abi")]
Abi {
#[command(subcommand)]
subcommand: AbiCommands,
},
#[command(name = "keys")]
Keys {
#[command(subcommand)]
subcommand: KeysCommands,
},
#[command(name = "account")]
Account {
#[command(subcommand)]
subcommand: AccountCommands,
},
#[command(name = "program")]
Program {
#[command(subcommand)]
subcommand: ProgramCommands,
},
#[command(name = "txn")]
Txn {
#[command(subcommand)]
subcommand: TxnCommands,
},
#[command(name = "util")]
Util {
#[command(subcommand)]
subcommand: UtilCommands,
},
#[command(name = "token")]
Token {
#[command(subcommand)]
subcommand: TokenCommands,
},
#[command(name = "faucet")]
Faucet {
#[command(subcommand)]
subcommand: FaucetCommands,
},
#[command(name = "registrar")]
Registrar {
#[command(subcommand)]
subcommand: RegistrarCommands,
},
#[command(name = "nameservice")]
NameService {
#[command(subcommand)]
subcommand: NameServiceCommands,
},
#[command(name = "wthru")]
Wthru {
#[command(subcommand)]
subcommand: WthruCommands,
},
#[command(name = "validator")]
Validator {
#[command(subcommand)]
subcommand: ValidatorCommands,
},
#[command(name = "dev")]
Dev {
#[command(subcommand)]
subcommand: DevCommands,
},
#[command(name = "network")]
Network {
#[command(subcommand)]
subcommand: Option<NetworkCommands>,
},
#[command(name = "debug")]
Debug {
#[command(subcommand)]
subcommand: DebugCommands,
},
}
#[derive(Subcommand)]
pub enum ProgramCommands {
Create {
#[arg(long)]
manager: Option<String>,
#[arg(long)]
ephemeral: bool,
seed: String,
#[arg(long)]
authority: Option<String>,
program_file: String,
#[arg(long, value_parser = parse_chunk_size, default_value = "30720")]
chunk_size: usize,
},
Upgrade {
#[arg(long)]
manager: Option<String>,
#[arg(long)]
ephemeral: bool,
seed: String,
program_file: String,
#[arg(long, value_parser = parse_chunk_size, default_value = "30720")]
chunk_size: usize,
},
SetPause {
#[arg(long)]
manager: Option<String>,
#[arg(long)]
ephemeral: bool,
seed: String,
paused: String,
},
Destroy {
#[arg(long)]
manager: Option<String>,
#[arg(long)]
ephemeral: bool,
seed: String,
#[arg(long)]
fee_payer: Option<String>,
},
Finalize {
#[arg(long)]
manager: Option<String>,
#[arg(long)]
ephemeral: bool,
seed: String,
#[arg(long)]
fee_payer: Option<String>,
},
SetAuthority {
#[arg(long)]
manager: Option<String>,
#[arg(long)]
ephemeral: bool,
seed: String,
authority_candidate: String,
},
ClaimAuthority {
#[arg(long)]
manager: Option<String>,
#[arg(long)]
ephemeral: bool,
seed: String,
#[arg(long)]
fee_payer: Option<String>,
},
DeriveAddress {
program_id: String,
seed: String,
#[arg(long)]
ephemeral: bool,
},
DeriveManagerAccounts {
#[arg(long)]
manager: Option<String>,
seed: String,
#[arg(long)]
ephemeral: bool,
},
SeedToHex {
seed: String,
},
DeriveProgramAccount {
#[arg(long)]
manager: Option<String>,
seed: String,
#[arg(long)]
ephemeral: bool,
},
Status {
#[arg(long)]
manager: Option<String>,
seed: String,
#[arg(long)]
ephemeral: bool,
},
}
#[derive(Subcommand)]
pub enum AbiCommands {
#[command(name = "account")]
Account {
#[command(subcommand)]
subcommand: AbiAccountCommands,
},
Codegen {
#[arg(short = 'f', long = "files", value_name = "FILE", required = true)]
files: Vec<PathBuf>,
#[arg(short = 'i', long = "include-dir", value_name = "DIR")]
include_dirs: Vec<PathBuf>,
#[arg(short = 'l', long = "language", value_enum)]
language: AbiLanguage,
#[arg(
short = 'o',
long = "output",
value_name = "DIR",
default_value = "generated"
)]
output_dir: PathBuf,
#[arg(short = 'v', long = "verbose")]
verbose: bool,
},
Analyze {
#[arg(short = 'f', long = "files", value_name = "FILE", required = true)]
files: Vec<PathBuf>,
#[arg(short = 'i', long = "include-dir", value_name = "DIR")]
include_dirs: Vec<PathBuf>,
#[arg(long = "print-ir")]
print_ir: bool,
#[arg(long = "ir-format", value_enum, default_value = "json")]
ir_format: AbiIrFormat,
#[arg(long = "print-footprint", value_name = "TYPE")]
print_footprint: Option<String>,
#[arg(long = "print-validate", value_name = "TYPE")]
print_validate: Option<String>,
},
Reflect {
#[arg(short = 'f', long = "abi-file", required = true)]
abi_files: Vec<PathBuf>,
#[arg(short = 'i', long = "include-dir")]
include_dirs: Vec<PathBuf>,
#[arg(short = 't', long = "type-name", required = true)]
type_name: String,
#[arg(short = 'd', long = "data-file", required = true)]
data_file: PathBuf,
#[arg(short = 'p', long = "pretty")]
pretty: bool,
#[arg(
long = "values-only",
conflicts_with_all = ["validate_only", "include_byte_offsets"]
)]
values_only: bool,
#[arg(
long = "validate-only",
conflicts_with_all = ["values_only", "include_byte_offsets"]
)]
validate_only: bool,
#[arg(long = "show-params")]
show_params: bool,
#[arg(
long = "include-byte-offsets",
conflicts_with_all = ["validate_only", "values_only"]
)]
include_byte_offsets: bool,
},
Flatten {
#[arg(short = 'f', long = "file", required = true)]
file: PathBuf,
#[arg(short = 'i', long = "include-dir")]
include_dirs: Vec<PathBuf>,
#[arg(short = 'o', long = "output", required = true)]
output: PathBuf,
#[arg(short = 'v', long = "verbose")]
verbose: bool,
},
#[command(name = "prep-for-publish")]
PrepForPublish {
#[arg(short = 'f', long = "file", required = true)]
file: PathBuf,
#[arg(short = 'i', long = "include-dir")]
include_dirs: Vec<PathBuf>,
#[arg(short = 'n', long = "target-network", required = true)]
target_network: String,
#[arg(short = 'o', long = "output", required = true)]
output: PathBuf,
#[arg(short = 'v', long = "verbose")]
verbose: bool,
},
Bundle {
#[arg(short = 'f', long = "file", required = true)]
file: PathBuf,
#[arg(short = 'i', long = "include-dir")]
include_dirs: Vec<PathBuf>,
#[arg(short = 'o', long = "output", required = true)]
output: PathBuf,
#[arg(short = 'v', long = "verbose")]
verbose: bool,
},
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub enum AbiAccountType {
Program,
#[value(name = "third-party")]
ThirdParty,
Standalone,
}
#[derive(Subcommand)]
pub enum AbiAccountCommands {
Create {
#[arg(long)]
ephemeral: bool,
#[arg(long = "account-type", value_enum, default_value = "program")]
account_type: AbiAccountType,
#[arg(long = "target-program")]
target_program: Option<String>,
#[arg(long = "fee-payer")]
fee_payer: Option<String>,
#[arg(long, conflicts_with = "publisher")]
authority: Option<String>,
#[arg(long, conflicts_with = "authority")]
publisher: Option<String>,
seed: String,
abi_file: String,
},
Upgrade {
#[arg(long)]
ephemeral: bool,
#[arg(long = "account-type", value_enum, default_value = "program")]
account_type: AbiAccountType,
#[arg(long = "target-program")]
target_program: Option<String>,
#[arg(long = "fee-payer")]
fee_payer: Option<String>,
#[arg(long, conflicts_with = "publisher")]
authority: Option<String>,
#[arg(long, conflicts_with = "authority")]
publisher: Option<String>,
seed: String,
abi_file: String,
},
Finalize {
#[arg(long)]
ephemeral: bool,
#[arg(long = "account-type", value_enum, default_value = "program")]
account_type: AbiAccountType,
#[arg(long = "target-program")]
target_program: Option<String>,
#[arg(long = "fee-payer")]
fee_payer: Option<String>,
#[arg(long, conflicts_with = "publisher")]
authority: Option<String>,
#[arg(long, conflicts_with = "authority")]
publisher: Option<String>,
seed: String,
},
Close {
#[arg(long)]
ephemeral: bool,
#[arg(long = "account-type", value_enum, default_value = "program")]
account_type: AbiAccountType,
#[arg(long = "target-program")]
target_program: Option<String>,
#[arg(long = "fee-payer")]
fee_payer: Option<String>,
#[arg(long, conflicts_with = "publisher")]
authority: Option<String>,
#[arg(long, conflicts_with = "authority")]
publisher: Option<String>,
seed: String,
},
Get {
abi_account: String,
#[arg(long = "include-data", alias = "data")]
include_data: bool,
#[arg(long = "out")]
out: Option<String>,
},
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub enum AbiLanguage {
C,
Rust,
#[value(name = "typescript")]
TypeScript,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub enum AbiIrFormat {
Json,
Protobuf,
}
#[derive(Subcommand)]
pub enum UploaderCommands {
Upload {
#[arg(long)]
uploader: Option<String>,
#[arg(long, value_parser = parse_chunk_size, default_value = "30720")]
chunk_size: usize,
seed: String,
#[arg(value_name = "FILE")]
program_file: String,
},
Cleanup {
#[arg(long)]
uploader: Option<String>,
seed: String,
},
Status {
#[arg(long)]
uploader: Option<String>,
seed: String,
},
}
#[derive(Subcommand)]
pub enum KeysCommands {
List,
Add {
#[arg(long)]
overwrite: bool,
name: String,
key: String,
},
Get {
name: String,
},
Generate {
#[arg(long)]
overwrite: bool,
name: String,
},
#[command(name = "rm")]
Remove {
#[arg(long)]
force: bool,
name: String,
},
}
#[derive(Subcommand)]
pub enum NetworkCommands {
Add {
name: String,
#[arg(long)]
url: String,
#[arg(long = "auth-token")]
auth_token: Option<String>,
},
#[command(name = "use")]
Use {
name: String,
},
Set {
name: String,
#[arg(long)]
url: Option<String>,
#[arg(long = "auth-token")]
auth_token: Option<String>,
},
#[command(name = "rm")]
Remove {
name: String,
},
}
#[derive(Subcommand)]
pub enum TxnCommands {
Sign {
program: String,
instruction_data: String,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long, default_value = "1")]
fee: u64,
#[arg(long, default_value = "1000000000")]
compute_units: u32,
#[arg(long, default_value = "10000")]
state_units: u16,
#[arg(long, default_value = "10000")]
memory_units: u16,
#[arg(long, default_value = "100")]
expiry_after: u32,
#[arg(long)]
readwrite_accounts: Vec<String>,
#[arg(long)]
readonly_accounts: Vec<String>,
},
Execute {
program: String,
instruction_data: String,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long, default_value = "1")]
fee: u64,
#[arg(long, default_value = "300000000")]
compute_units: u32,
#[arg(long, default_value = "10000")]
state_units: u16,
#[arg(long, default_value = "10000")]
memory_units: u16,
#[arg(long, default_value = "100")]
expiry_after: u32,
#[arg(long, default_value = "30")]
timeout: u64,
#[arg(long)]
readwrite_accounts: Vec<String>,
#[arg(long)]
readonly_accounts: Vec<String>,
},
#[command(name = "make-state-proof")]
MakeStateProof {
proof_type: String,
account: String,
#[arg(long)]
slot: Option<u64>,
},
Get {
signature: String,
#[arg(long = "retry-count", value_name = "COUNT", default_value = "1", value_parser = clap::value_parser!(u32).range(1..=60))]
retry_count: u32,
},
Debug {
signature: String,
#[arg(long = "state-before", alias = "include-state-before")]
include_state_before: bool,
#[arg(long = "state-after", alias = "include-state-after")]
include_state_after: bool,
#[arg(long = "account-data", alias = "include-account-data")]
include_account_data: bool,
#[arg(long, value_name = "FILE")]
output_trace: Option<String>,
#[arg(long)]
inline_trace: bool,
#[arg(long = "memory-dump", alias = "include-memory-dump")]
include_memory_dump: bool,
},
Sort {
pubkeys: Vec<String>,
},
}
#[derive(Subcommand)]
pub enum AccountCommands {
Create {
key_name: Option<String>,
},
Info {
key_name: Option<String>,
},
#[command(name = "transactions")]
Transactions {
account: Option<String>,
#[arg(long = "page-size")]
page_size: Option<u32>,
#[arg(long = "page-token")]
page_token: Option<String>,
},
Compress {
target_account: String,
fee_payer: Option<String>,
},
Decompress {
target_account: String,
fee_payer: Option<String>,
},
PrepareDecompression {
account: String,
},
}
#[derive(Subcommand)]
pub enum UtilCommands {
#[command(name = "convert")]
Convert {
#[command(subcommand)]
subcommand: ConvertCommands,
},
}
#[derive(Subcommand)]
pub enum ConvertCommands {
#[command(name = "pubkey")]
Pubkey {
#[command(subcommand)]
subcommand: PubkeyConvertCommands,
},
#[command(name = "signature")]
Signature {
#[command(subcommand)]
subcommand: SignatureConvertCommands,
},
}
#[derive(Subcommand)]
pub enum PubkeyConvertCommands {
#[command(name = "hex-to-thrufmt")]
HexToThruFmt {
hex_pubkey: String,
},
#[command(name = "thrufmt-to-hex")]
ThruFmtToHex {
thrufmt_pubkey: String,
},
}
#[derive(Subcommand)]
pub enum SignatureConvertCommands {
#[command(name = "hex-to-thrufmt")]
HexToThruFmt {
hex_signature: String,
},
#[command(name = "thrufmt-to-hex")]
ThruFmtToHex {
thrufmt_signature: String,
},
}
#[derive(Subcommand)]
pub enum TokenCommands {
InitializeMint {
creator: String,
#[arg(long)]
mint_authority: Option<String>,
#[arg(long)]
freeze_authority: Option<String>,
#[arg(long, default_value = "9")]
decimals: u8,
ticker: String,
seed: String,
#[arg(long)]
state_proof: Option<String>,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
},
InitializeAccount {
mint: String,
owner: String,
seed: String,
#[arg(long)]
state_proof: Option<String>,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
},
Transfer {
from: String,
to: String,
amount: u64,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
},
MintTo {
mint: String,
to: String,
authority: String,
amount: u64,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
},
Burn {
account: String,
mint: String,
authority: String,
amount: u64,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
},
CloseAccount {
account: String,
destination: String,
authority: String,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
},
FreezeAccount {
account: String,
mint: String,
authority: String,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
},
ThawAccount {
account: String,
mint: String,
authority: String,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
},
DeriveTokenAccount {
mint: String,
owner: String,
#[arg(long)]
seed: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
},
DeriveMintAccount {
creator: String,
seed: String,
#[arg(long = "token-program")]
token_program: Option<String>,
},
}
#[derive(Subcommand)]
pub enum FaucetCommands {
Deposit {
account: String,
amount: u64,
#[arg(long)]
fee_payer: Option<String>,
},
Withdraw {
account: String,
amount: u64,
#[arg(long)]
fee_payer: Option<String>,
},
}
#[derive(Subcommand)]
pub enum RegistrarCommands {
InitializeRegistry {
#[arg(long = "name-service-program")]
name_service_program: Option<String>,
root_registrar_account: String,
treasurer_account: String,
token_mint_account: String,
#[arg(long = "token-program")]
token_program: Option<String>,
price_per_year: u64,
#[arg(default_value = "thru")]
root_domain_name: String,
#[arg(long)]
config_proof: Option<String>,
#[arg(long)]
registrar_proof: Option<String>,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "thru-registrar-program", alias = "thru-name-service-program")]
thru_registrar_program: Option<String>,
},
PurchaseDomain {
domain_name: String,
years: u8,
config_account: String,
payer_token_account: String,
#[arg(long)]
lease_proof: Option<String>,
#[arg(long)]
domain_proof: Option<String>,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "thru-registrar-program", alias = "thru-name-service-program")]
thru_registrar_program: Option<String>,
},
RenewLease {
lease_account: String,
years: u8,
config_account: String,
payer_token_account: String,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "thru-registrar-program", alias = "thru-name-service-program")]
thru_registrar_program: Option<String>,
},
ClaimExpiredDomain {
lease_account: String,
years: u8,
config_account: String,
payer_token_account: String,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "thru-registrar-program", alias = "thru-name-service-program")]
thru_registrar_program: Option<String>,
},
}
#[derive(Subcommand)]
pub enum NameServiceCommands {
#[command(name = "append-record")]
AppendRecord {
domain_account: String,
key: String,
value: String,
#[arg(long)]
owner: Option<String>,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "name-service-program")]
name_service_program: Option<String>,
},
#[command(name = "delete-record")]
DeleteRecord {
domain_account: String,
key: String,
#[arg(long)]
owner: Option<String>,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "name-service-program")]
name_service_program: Option<String>,
},
DeriveConfigAccount {
#[arg(long = "thru-registrar-program", alias = "thru-name-service-program")]
thru_registrar_program: Option<String>,
},
#[command(name = "derive-domain-account")]
DeriveDomainAccount {
parent_account: String,
domain_name: String,
#[arg(long = "name-service-program")]
name_service_program: Option<String>,
},
DeriveLeaseAccount {
domain_name: String,
#[arg(long = "thru-registrar-program", alias = "thru-name-service-program")]
thru_registrar_program: Option<String>,
},
#[command(name = "derive-registrar-account")]
DeriveRegistrarAccount {
root_name: String,
#[arg(long = "name-service-program")]
name_service_program: Option<String>,
},
#[command(name = "init-root")]
InitRoot {
root_name: String,
#[arg(long = "name-service-program")]
name_service_program: Option<String>,
#[arg(long)]
registrar_account: Option<String>,
#[arg(long)]
authority: Option<String>,
#[arg(long)]
proof: Option<String>,
#[arg(long)]
fee_payer: Option<String>,
},
#[command(name = "list-records")]
ListRecords {
domain_account: String,
#[arg(long = "name-service-program")]
name_service_program: Option<String>,
},
#[command(name = "register-subdomain")]
RegisterSubdomain {
domain_name: String,
parent_account: String,
#[arg(long = "name-service-program")]
name_service_program: Option<String>,
#[arg(long)]
domain_account: Option<String>,
#[arg(long)]
owner: Option<String>,
#[arg(long)]
authority: Option<String>,
#[arg(long)]
proof: Option<String>,
#[arg(long)]
fee_payer: Option<String>,
},
#[command(name = "resolve")]
Resolve {
domain_account: String,
#[arg(long)]
key: Option<String>,
#[arg(long = "name-service-program")]
name_service_program: Option<String>,
},
#[command(name = "unregister-subdomain")]
UnregisterSubdomain {
domain_account: String,
#[arg(long)]
owner: Option<String>,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "name-service-program")]
name_service_program: Option<String>,
},
}
#[derive(Subcommand)]
pub enum WthruCommands {
Initialize {
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "program")]
program: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
},
Deposit {
dest_token_account: String,
amount: u64,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "program")]
program: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
#[arg(long = "skip-transfer")]
skip_transfer: bool,
},
Withdraw {
wthru_token_account: String,
recipient: String,
amount: u64,
#[arg(long)]
fee_payer: Option<String>,
#[arg(long = "program")]
program: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
},
}
#[derive(Subcommand)]
pub enum ValidatorCommands {
Activate {
#[arg(long = "source-token-account")]
source_token_account: String,
#[arg(long = "token-amount")]
token_amount: u64,
#[arg(
long = "bls-pubkey",
conflicts_with = "bls_seed",
required_unless_present = "bls_seed"
)]
bls_pubkey: Option<String>,
#[arg(long = "bls-seed", conflicts_with = "bls_pubkey")]
bls_seed: Option<u64>,
#[arg(long = "claim-authority")]
claim_authority: Option<String>,
#[arg(long = "fee-payer")]
fee_payer: Option<String>,
#[arg(long = "program")]
program: Option<String>,
#[arg(long = "attestor-table")]
attestor_table: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
#[arg(long = "converted-vault")]
converted_vault: Option<String>,
},
Deactivate {
#[arg(long = "dest-token-account")]
dest_token_account: String,
#[arg(long = "fee-payer")]
fee_payer: Option<String>,
#[arg(long = "program")]
program: Option<String>,
#[arg(long = "attestor-table")]
attestor_table: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
#[arg(long = "unclaimed-vault")]
unclaimed_vault: Option<String>,
},
#[command(name = "convert-tokens")]
ConvertTokens {
#[arg(long = "source-token-account")]
source_token_account: String,
#[arg(long = "token-amount")]
token_amount: u64,
#[arg(long = "fee-payer")]
fee_payer: Option<String>,
#[arg(long = "program")]
program: Option<String>,
#[arg(long = "attestor-table")]
attestor_table: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
#[arg(long = "converted-vault")]
converted_vault: Option<String>,
},
Claim {
#[arg(long = "subject-attestor")]
subject_attestor: String,
#[arg(long = "dest-token-account")]
dest_token_account: String,
#[arg(long = "fee-payer")]
fee_payer: Option<String>,
#[arg(long = "program")]
program: Option<String>,
#[arg(long = "attestor-table")]
attestor_table: Option<String>,
#[arg(long = "token-program")]
token_program: Option<String>,
#[arg(long = "unclaimed-vault")]
unclaimed_vault: Option<String>,
},
#[command(name = "set-claim-authority")]
SetClaimAuthority {
#[arg(long = "subject-attestor")]
subject_attestor: String,
#[arg(long = "new-claim-authority")]
new_claim_authority: String,
#[arg(long = "fee-payer")]
fee_payer: Option<String>,
#[arg(long = "program")]
program: Option<String>,
#[arg(long = "attestor-table")]
attestor_table: Option<String>,
},
Table {
#[arg(long = "attestor-table")]
attestor_table: Option<String>,
},
Info {
validator: String,
#[arg(long = "attestor-table")]
attestor_table: Option<String>,
},
}
#[derive(Subcommand)]
pub enum DevCommands {
#[command(name = "toolchain")]
Toolchain {
#[command(subcommand)]
subcommand: ToolchainCommands,
},
#[command(name = "sdk")]
Sdk {
#[command(subcommand)]
subcommand: SdkCommands,
},
#[command(name = "init")]
Init {
#[command(subcommand)]
subcommand: InitCommands,
},
}
#[derive(Subcommand)]
pub enum ToolchainCommands {
Install {
#[arg(long)]
version: Option<String>,
#[arg(long)]
path: Option<String>,
#[arg(long)]
repo: Option<String>,
},
Update {
#[arg(long)]
path: Option<String>,
#[arg(long)]
repo: Option<String>,
},
Uninstall {
#[arg(long)]
path: Option<String>,
#[arg(long)]
force: bool,
},
Path {
#[arg(long)]
path: Option<String>,
},
}
#[derive(Subcommand)]
pub enum SdkCommands {
Install {
language: String,
#[arg(long)]
version: Option<String>,
#[arg(long)]
path: Option<String>,
#[arg(long)]
repo: Option<String>,
},
Update {
language: String,
#[arg(long)]
path: Option<String>,
#[arg(long)]
repo: Option<String>,
},
Uninstall {
language: String,
#[arg(long)]
path: Option<String>,
#[arg(long)]
force: bool,
},
Path {
language: String,
#[arg(long)]
path: Option<String>,
},
}
#[derive(Subcommand)]
pub enum InitCommands {
#[command(name = "c")]
C {
project_name: String,
#[arg(long)]
path: Option<String>,
},
#[command(name = "cpp")]
Cpp {
project_name: String,
#[arg(long)]
path: Option<String>,
},
#[command(name = "rust")]
Rust {
project_name: String,
#[arg(long)]
path: Option<String>,
},
}
#[derive(Subcommand)]
pub enum DebugCommands {
#[command(name = "resolve")]
Resolve {
#[arg(long)]
elf: std::path::PathBuf,
#[arg(long, group = "input")]
response: Option<std::path::PathBuf>,
#[arg(long, group = "input")]
signature: Option<String>,
#[arg(long, default_value = "20")]
trace_tail: usize,
#[arg(long, default_value = "5")]
context: u32,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_abi_codegen_command() {
let cli = Cli::try_parse_from([
"thru",
"abi",
"codegen",
"-f",
"input.abi.yaml",
"-l",
"rust",
])
.expect("codegen command should parse");
match cli.command {
Commands::Abi {
subcommand:
AbiCommands::Codegen {
files,
include_dirs,
language,
output_dir,
verbose,
},
} => {
assert_eq!(files, vec![PathBuf::from("input.abi.yaml")]);
assert!(include_dirs.is_empty());
assert_eq!(language, AbiLanguage::Rust);
assert_eq!(output_dir, PathBuf::from("generated"));
assert!(!verbose);
}
_ => panic!("unexpected command"),
}
}
#[test]
fn parses_abi_account_create_command() {
let cli = Cli::try_parse_from([
"thru",
"abi",
"account",
"create",
"seed-1",
"program.abi.yaml",
])
.expect("abi account create should parse");
match cli.command {
Commands::Abi {
subcommand:
AbiCommands::Account {
subcommand:
AbiAccountCommands::Create {
ephemeral,
account_type,
target_program,
seed,
fee_payer,
authority,
publisher,
abi_file,
},
},
} => {
assert!(!ephemeral);
assert_eq!(account_type, AbiAccountType::Program);
assert_eq!(target_program, None);
assert_eq!(seed, "seed-1");
assert_eq!(fee_payer, None);
assert_eq!(authority, None);
assert_eq!(publisher, None);
assert_eq!(abi_file, "program.abi.yaml");
}
_ => panic!("unexpected command"),
}
}
#[test]
fn parses_abi_account_get_include_data_flag() {
let cli = Cli::try_parse_from(["thru", "abi", "account", "get", "ta123", "--include-data"])
.expect("abi account get should parse");
match cli.command {
Commands::Abi {
subcommand:
AbiCommands::Account {
subcommand:
AbiAccountCommands::Get {
abi_account,
include_data,
out,
},
},
} => {
assert_eq!(abi_account, "ta123");
assert!(include_data);
assert_eq!(out, None);
}
_ => panic!("unexpected command"),
}
}
#[test]
fn parses_abi_account_upgrade_third_party_command() {
let cli = Cli::try_parse_from([
"thru",
"abi",
"account",
"upgrade",
"--account-type",
"third-party",
"--target-program",
"ta_target",
"--publisher",
"alice",
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"program.abi.yaml",
])
.expect("third-party abi account upgrade should parse");
match cli.command {
Commands::Abi {
subcommand:
AbiCommands::Account {
subcommand:
AbiAccountCommands::Upgrade {
account_type,
publisher,
authority,
target_program,
seed,
abi_file,
..
},
},
} => {
assert_eq!(account_type, AbiAccountType::ThirdParty);
assert_eq!(publisher.as_deref(), Some("alice"));
assert_eq!(authority, None);
assert_eq!(target_program.as_deref(), Some("ta_target"));
assert_eq!(
seed,
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
);
assert_eq!(abi_file, "program.abi.yaml");
}
_ => panic!("unexpected command"),
}
}
#[test]
fn parses_abi_account_finalize_standalone_authority_alias() {
let cli = Cli::try_parse_from([
"thru",
"abi",
"account",
"finalize",
"--account-type",
"standalone",
"--authority",
"alice",
"my-abi-seed",
])
.expect("standalone abi account finalize should parse authority alias");
match cli.command {
Commands::Abi {
subcommand:
AbiCommands::Account {
subcommand:
AbiAccountCommands::Finalize {
account_type,
target_program,
authority,
publisher,
seed,
..
},
},
} => {
assert_eq!(account_type, AbiAccountType::Standalone);
assert_eq!(target_program, None);
assert_eq!(authority.as_deref(), Some("alice"));
assert_eq!(publisher, None);
assert_eq!(seed, "my-abi-seed");
}
_ => panic!("unexpected command"),
}
}
#[test]
fn parses_abi_account_close_command() {
let cli = Cli::try_parse_from([
"thru",
"abi",
"account",
"close",
"--fee-payer",
"bob",
"--authority",
"alice",
"seed-1",
])
.expect("abi account close should parse");
match cli.command {
Commands::Abi {
subcommand:
AbiCommands::Account {
subcommand:
AbiAccountCommands::Close {
account_type,
target_program,
fee_payer,
authority,
publisher,
seed,
..
},
},
} => {
assert_eq!(account_type, AbiAccountType::Program);
assert_eq!(target_program, None);
assert_eq!(fee_payer.as_deref(), Some("bob"));
assert_eq!(authority.as_deref(), Some("alice"));
assert_eq!(publisher, None);
assert_eq!(seed, "seed-1");
}
_ => panic!("unexpected command"),
}
}
#[test]
fn rejects_conflicting_abi_account_actor_flags() {
let result = Cli::try_parse_from([
"thru",
"abi",
"account",
"create",
"--account-type",
"standalone",
"--authority",
"alice",
"--publisher",
"bob",
"my-abi-seed",
"program.abi.yaml",
]);
assert!(
result.is_err(),
"conflicting abi account actor flags should fail"
);
}
#[test]
fn parses_network_use_command() {
let cli =
Cli::try_parse_from(["thru", "network", "use", "1"]).expect("network use should parse");
match cli.command {
Commands::Network {
subcommand: Some(NetworkCommands::Use { name }),
} => assert_eq!(name, "1"),
_ => panic!("unexpected command"),
}
}
#[test]
fn parses_bare_network_command() {
let cli = Cli::try_parse_from(["thru", "network"]).expect("bare network should parse");
match cli.command {
Commands::Network { subcommand: None } => {}
_ => panic!("unexpected command"),
}
}
#[test]
fn rejects_legacy_network_set_default_alias() {
let cli = Cli::try_parse_from(["thru", "network", "set-default", "1"]);
assert!(
cli.is_err(),
"legacy network set-default alias should no longer parse"
);
}
#[test]
fn rejects_legacy_network_list_subcommand() {
let cli = Cli::try_parse_from(["thru", "network", "list"]);
assert!(
cli.is_err(),
"legacy network list subcommand should no longer parse"
);
}
#[test]
fn parses_network_remove_command() {
let cli =
Cli::try_parse_from(["thru", "network", "rm", "1"]).expect("network rm should parse");
match cli.command {
Commands::Network {
subcommand: Some(NetworkCommands::Remove { name }),
} => assert_eq!(name, "1"),
_ => panic!("unexpected command"),
}
}
#[test]
fn rejects_conflicting_abi_reflect_flags() {
let result = Cli::try_parse_from([
"thru",
"abi",
"reflect",
"-f",
"input.abi.yaml",
"-t",
"MyType",
"-d",
"data.bin",
"--validate-only",
"--values-only",
]);
assert!(result.is_err(), "conflicting reflect flags should fail");
}
#[test]
fn abi_reflect_short_v_is_no_longer_values_only() {
let result = Cli::try_parse_from([
"thru",
"abi",
"reflect",
"-f",
"input.abi.yaml",
"-t",
"MyType",
"-d",
"data.bin",
"-v",
]);
assert!(
result.is_err(),
"short -v should no longer parse for abi reflect"
);
}
#[test]
fn parses_abi_prep_for_publish_command() {
let cli = Cli::try_parse_from([
"thru",
"abi",
"prep-for-publish",
"-f",
"root.abi.yaml",
"--target-network",
"mainnet",
"-o",
"publish.abi.yaml",
])
.expect("prep-for-publish command should parse");
match cli.command {
Commands::Abi {
subcommand:
AbiCommands::PrepForPublish {
file,
include_dirs,
target_network,
output,
verbose,
},
} => {
assert_eq!(file, PathBuf::from("root.abi.yaml"));
assert!(include_dirs.is_empty());
assert_eq!(target_network, "mainnet");
assert_eq!(output, PathBuf::from("publish.abi.yaml"));
assert!(!verbose);
}
_ => panic!("unexpected command"),
}
}
#[test]
fn parses_txn_debug_inline_trace_flag() {
let cli = Cli::try_parse_from([
"thru",
"txn",
"debug",
"ts11111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
"--inline-trace",
"--output-trace",
"trace.log",
"--state-before",
])
.expect("txn debug command should parse");
match cli.command {
Commands::Txn {
subcommand:
TxnCommands::Debug {
signature,
include_state_before,
include_state_after,
include_account_data,
output_trace,
inline_trace,
include_memory_dump,
},
} => {
assert_eq!(
signature,
"ts11111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
);
assert!(include_state_before);
assert!(!include_state_after);
assert!(!include_account_data);
assert_eq!(output_trace.as_deref(), Some("trace.log"));
assert!(inline_trace);
assert!(!include_memory_dump);
}
_ => panic!("unexpected command"),
}
}
#[test]
fn parses_txn_debug_legacy_include_flag_aliases() {
let cli = Cli::try_parse_from([
"thru",
"txn",
"debug",
"ts11111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
"--include-state-after",
"--include-account-data",
"--include-memory-dump",
])
.expect("legacy txn debug flag aliases should continue to parse");
match cli.command {
Commands::Txn {
subcommand:
TxnCommands::Debug {
include_state_before,
include_state_after,
include_account_data,
include_memory_dump,
..
},
} => {
assert!(!include_state_before);
assert!(include_state_after);
assert!(include_account_data);
assert!(include_memory_dump);
}
_ => panic!("unexpected command"),
}
}
#[test]
fn parses_consensus_validator_activate_command() {
let cli = Cli::try_parse_from([
"thru",
"validator",
"activate",
"--source-token-account",
"ta_source",
"--token-amount",
"1000",
"--bls-pubkey",
&"11".repeat(96),
"--claim-authority",
"alice",
"--fee-payer",
"bob",
"--converted-vault",
"ta_vault",
])
.expect("validator activate should parse");
match cli.command {
Commands::Validator {
subcommand:
ValidatorCommands::Activate {
source_token_account,
token_amount,
bls_pubkey,
bls_seed,
claim_authority,
fee_payer,
converted_vault,
..
},
} => {
assert_eq!(source_token_account, "ta_source");
assert_eq!(token_amount, 1000);
assert_eq!(bls_pubkey.as_deref(), Some("11".repeat(96).as_str()));
assert_eq!(bls_seed, None);
assert_eq!(claim_authority.as_deref(), Some("alice"));
assert_eq!(fee_payer.as_deref(), Some("bob"));
assert_eq!(converted_vault.as_deref(), Some("ta_vault"));
}
_ => panic!("unexpected command"),
}
}
#[test]
fn parses_consensus_validator_convert_tokens_command() {
let cli = Cli::try_parse_from([
"thru",
"validator",
"convert-tokens",
"--source-token-account",
"ta_source",
"--token-amount",
"250",
"--token-program",
"ta_token",
])
.expect("validator convert-tokens should parse");
match cli.command {
Commands::Validator {
subcommand:
ValidatorCommands::ConvertTokens {
source_token_account,
token_amount,
token_program,
..
},
} => {
assert_eq!(source_token_account, "ta_source");
assert_eq!(token_amount, 250);
assert_eq!(token_program.as_deref(), Some("ta_token"));
}
_ => panic!("unexpected command"),
}
}
#[test]
fn parses_consensus_validator_set_claim_authority_command() {
let cli = Cli::try_parse_from([
"thru",
"validator",
"set-claim-authority",
"--subject-attestor",
"alice",
"--new-claim-authority",
"bob",
"--attestor-table",
"ta_table",
])
.expect("validator set-claim-authority should parse");
match cli.command {
Commands::Validator {
subcommand:
ValidatorCommands::SetClaimAuthority {
subject_attestor,
new_claim_authority,
attestor_table,
..
},
} => {
assert_eq!(subject_attestor, "alice");
assert_eq!(new_claim_authority, "bob");
assert_eq!(attestor_table.as_deref(), Some("ta_table"));
}
_ => panic!("unexpected command"),
}
}
#[test]
fn parses_validator_table_command() {
let cli =
Cli::try_parse_from(["thru", "validator", "table", "--attestor-table", "ta_table"])
.expect("validator table should parse");
match cli.command {
Commands::Validator {
subcommand: ValidatorCommands::Table { attestor_table },
} => {
assert_eq!(attestor_table.as_deref(), Some("ta_table"));
}
_ => panic!("unexpected command"),
}
}
#[test]
fn parses_validator_activate_with_bls_seed() {
let cli = Cli::try_parse_from([
"thru",
"validator",
"activate",
"--source-token-account",
"ta_source",
"--token-amount",
"1000",
"--bls-seed",
"17",
])
.expect("validator activate with bls seed should parse");
match cli.command {
Commands::Validator {
subcommand:
ValidatorCommands::Activate {
bls_pubkey,
bls_seed,
..
},
} => {
assert_eq!(bls_pubkey, None);
assert_eq!(bls_seed, Some(17));
}
_ => panic!("unexpected command"),
}
}
#[test]
fn parses_validator_info_command() {
let cli = Cli::try_parse_from([
"thru",
"validator",
"info",
"alice",
"--attestor-table",
"ta_table",
])
.expect("validator info should parse");
match cli.command {
Commands::Validator {
subcommand:
ValidatorCommands::Info {
validator,
attestor_table,
},
} => {
assert_eq!(validator, "alice");
assert_eq!(attestor_table.as_deref(), Some("ta_table"));
}
_ => panic!("unexpected command"),
}
}
#[test]
fn rejects_debug_re_execute_command() {
let result = Cli::try_parse_from([
"thru",
"debug",
"re-execute",
"ts11111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
]);
assert!(result.is_err(), "debug re-execute should no longer parse");
}
}