Add token CLI copy that can create a bridge
This commit is contained in:
parent
8f333ba0fd
commit
90e75b3d22
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Hendrik Hofstadt <hendrik@nexantic.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = "2.33.0"
|
||||||
|
solana-clap-utils = { version = "1.2.17"}
|
||||||
|
solana-cli-config = { version = "1.2.17" }
|
||||||
|
solana-logger = { version = "1.2.17" }
|
||||||
|
solana-sdk = { version = "1.2.17" }
|
||||||
|
solana-client = { version = "1.2.17" }
|
||||||
|
solana-faucet = "1.2.17"
|
||||||
|
spl-token = { version = "1.0.6" }
|
||||||
|
wormhole-bridge = { path = "../bridge" }
|
|
@ -0,0 +1,767 @@
|
||||||
|
use std::net::{IpAddr, SocketAddr};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::thread::sleep;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::{mem::size_of, process::exit};
|
||||||
|
|
||||||
|
use clap::{
|
||||||
|
crate_description, crate_name, crate_version, value_t, value_t_or_exit, App, AppSettings, Arg,
|
||||||
|
SubCommand,
|
||||||
|
};
|
||||||
|
use solana_clap_utils::{
|
||||||
|
input_parsers::{keypair_of, pubkey_of},
|
||||||
|
input_validators::{is_amount, is_keypair, is_pubkey_or_keypair, is_url},
|
||||||
|
};
|
||||||
|
use solana_client::rpc_client::RpcClient;
|
||||||
|
use solana_faucet::faucet::request_airdrop_transaction;
|
||||||
|
use solana_sdk::hash::Hash;
|
||||||
|
use solana_sdk::{
|
||||||
|
native_token::*,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
signature::{read_keypair_file, Keypair, Signer},
|
||||||
|
system_instruction,
|
||||||
|
transaction::Transaction,
|
||||||
|
};
|
||||||
|
use spl_token::{
|
||||||
|
self,
|
||||||
|
instruction::*,
|
||||||
|
state::{Account, Mint},
|
||||||
|
};
|
||||||
|
|
||||||
|
use spl_bridge::instruction::initialize;
|
||||||
|
use spl_bridge::state::BridgeConfig;
|
||||||
|
use spl_bridge::syscalls::RawKey;
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
rpc_client: RpcClient,
|
||||||
|
owner: Keypair,
|
||||||
|
fee_payer: Keypair,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error = Box<dyn std::error::Error>;
|
||||||
|
type CommmandResult = Result<Option<Transaction>, Error>;
|
||||||
|
|
||||||
|
fn requestAirdrop(config: &Config, request_sol: u64) -> CommmandResult {
|
||||||
|
let (blockhash, _fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
|
let faucet_addr = SocketAddr::new(IpAddr::from_str("127.0.0.1").unwrap(), 9900);
|
||||||
|
match {
|
||||||
|
let mut retries = 5;
|
||||||
|
loop {
|
||||||
|
let result = FaucetKeypair::new_keypair(
|
||||||
|
&faucet_addr,
|
||||||
|
&config.owner.pubkey(),
|
||||||
|
LAMPORTS_PER_SOL * request_sol,
|
||||||
|
blockhash,
|
||||||
|
);
|
||||||
|
if result.is_ok() || retries == 0 {
|
||||||
|
break result;
|
||||||
|
}
|
||||||
|
retries -= 1;
|
||||||
|
sleep(Duration::from_secs(1));
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
Ok(kp) => Ok(Some(kp.airdrop_transaction())),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FaucetKeypair {
|
||||||
|
transaction: Transaction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FaucetKeypair {
|
||||||
|
fn new_keypair(
|
||||||
|
faucet_addr: &SocketAddr,
|
||||||
|
to_pubkey: &Pubkey,
|
||||||
|
lamports: u64,
|
||||||
|
blockhash: Hash,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let transaction = request_airdrop_transaction(faucet_addr, to_pubkey, lamports, blockhash)?;
|
||||||
|
Ok(Self { transaction })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn airdrop_transaction(&self) -> Transaction {
|
||||||
|
self.transaction.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_fee_payer_balance(config: &Config, required_balance: u64) -> Result<(), Error> {
|
||||||
|
let balance = config.rpc_client.get_balance(&config.fee_payer.pubkey())?;
|
||||||
|
if balance < required_balance {
|
||||||
|
Err(format!(
|
||||||
|
"Fee payer, {}, has insufficient balance: {} required, {} available",
|
||||||
|
config.fee_payer.pubkey(),
|
||||||
|
lamports_to_sol(required_balance),
|
||||||
|
lamports_to_sol(balance)
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_owner_balance(config: &Config, required_balance: u64) -> Result<(), Error> {
|
||||||
|
let balance = config.rpc_client.get_balance(&config.owner.pubkey())?;
|
||||||
|
if balance < required_balance {
|
||||||
|
Err(format!(
|
||||||
|
"Owner, {}, has insufficient balance: {} required, {} available",
|
||||||
|
config.owner.pubkey(),
|
||||||
|
lamports_to_sol(required_balance),
|
||||||
|
lamports_to_sol(balance)
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_request_airdrop(config: &Config) -> CommmandResult {
|
||||||
|
let token = Keypair::new();
|
||||||
|
println!("Requesting airdrop");
|
||||||
|
|
||||||
|
let minimum_balance_for_rent_exemption = config
|
||||||
|
.rpc_client
|
||||||
|
.get_minimum_balance_for_rent_exemption(size_of::<Mint>())?;
|
||||||
|
|
||||||
|
let mut transaction: Transaction = requestAirdrop(config, 20)?.unwrap();
|
||||||
|
|
||||||
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
|
check_fee_payer_balance(
|
||||||
|
config,
|
||||||
|
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()),
|
||||||
|
)?;
|
||||||
|
transaction.sign(
|
||||||
|
&[&config.fee_payer, &config.owner, &token],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
Ok(Some(transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_deploy_bridge(config: &Config) -> CommmandResult {
|
||||||
|
println!("Deploying bridge program");
|
||||||
|
|
||||||
|
let minimum_balance_for_rent_exemption = config
|
||||||
|
.rpc_client
|
||||||
|
.get_minimum_balance_for_rent_exemption(size_of::<Mint>())?;
|
||||||
|
|
||||||
|
let ix = initialize(
|
||||||
|
&Pubkey::from_str("5x4kJ1G4UgJc3yNsznZpxEAB2vrnJirSBxZDu1uwaqnZ").unwrap(),
|
||||||
|
&config.owner.pubkey(),
|
||||||
|
RawKey {
|
||||||
|
x: [8; 32],
|
||||||
|
y: [2; 32],
|
||||||
|
},
|
||||||
|
&BridgeConfig {
|
||||||
|
vaa_expiration_time: 200000000,
|
||||||
|
token_program: spl_token::id(),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
println!("bridge: {}, ", ix.accounts[0].pubkey.to_string());
|
||||||
|
println!("payer: {}, ", ix.accounts[3].pubkey.to_string());
|
||||||
|
let mut transaction = Transaction::new_with_payer(&[ix], Some(&config.fee_payer.pubkey()));
|
||||||
|
|
||||||
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
|
check_fee_payer_balance(
|
||||||
|
config,
|
||||||
|
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()),
|
||||||
|
)?;
|
||||||
|
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||||
|
Ok(Some(transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_create_token(config: &Config) -> CommmandResult {
|
||||||
|
let token = Keypair::new();
|
||||||
|
println!("Creating token {}", token.pubkey());
|
||||||
|
|
||||||
|
let minimum_balance_for_rent_exemption = config
|
||||||
|
.rpc_client
|
||||||
|
.get_minimum_balance_for_rent_exemption(size_of::<Mint>())?;
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[
|
||||||
|
system_instruction::create_account(
|
||||||
|
&config.fee_payer.pubkey(),
|
||||||
|
&token.pubkey(),
|
||||||
|
minimum_balance_for_rent_exemption,
|
||||||
|
size_of::<Mint>() as u64,
|
||||||
|
&spl_token::id(),
|
||||||
|
),
|
||||||
|
initialize_mint(
|
||||||
|
&spl_token::id(),
|
||||||
|
&token.pubkey(),
|
||||||
|
None,
|
||||||
|
Some(&config.owner.pubkey()),
|
||||||
|
0,
|
||||||
|
9, // hard code 9 decimal places to match the sol/lamports relationship
|
||||||
|
)?,
|
||||||
|
],
|
||||||
|
Some(&config.fee_payer.pubkey()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
|
check_fee_payer_balance(
|
||||||
|
config,
|
||||||
|
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()),
|
||||||
|
)?;
|
||||||
|
transaction.sign(
|
||||||
|
&[&config.fee_payer, &config.owner, &token],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
Ok(Some(transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_create_account(config: &Config, token: Pubkey) -> CommmandResult {
|
||||||
|
let account = Keypair::new();
|
||||||
|
println!("Creating account {}", account.pubkey());
|
||||||
|
|
||||||
|
let minimum_balance_for_rent_exemption = config
|
||||||
|
.rpc_client
|
||||||
|
.get_minimum_balance_for_rent_exemption(size_of::<Account>())?;
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[
|
||||||
|
system_instruction::create_account(
|
||||||
|
&config.fee_payer.pubkey(),
|
||||||
|
&account.pubkey(),
|
||||||
|
minimum_balance_for_rent_exemption,
|
||||||
|
size_of::<Account>() as u64,
|
||||||
|
&spl_token::id(),
|
||||||
|
),
|
||||||
|
initialize_account(
|
||||||
|
&spl_token::id(),
|
||||||
|
&account.pubkey(),
|
||||||
|
&token,
|
||||||
|
&config.owner.pubkey(),
|
||||||
|
)?,
|
||||||
|
],
|
||||||
|
Some(&config.fee_payer.pubkey()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
|
check_fee_payer_balance(
|
||||||
|
config,
|
||||||
|
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()),
|
||||||
|
)?;
|
||||||
|
transaction.sign(
|
||||||
|
&[&config.fee_payer, &config.owner, &account],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
Ok(Some(transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_assign(config: &Config, account: Pubkey, new_owner: Pubkey) -> CommmandResult {
|
||||||
|
println!(
|
||||||
|
"Assigning {}\n Current owner: {}\n New owner: {}",
|
||||||
|
account,
|
||||||
|
config.owner.pubkey(),
|
||||||
|
new_owner
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[set_owner(
|
||||||
|
&spl_token::id(),
|
||||||
|
&account,
|
||||||
|
&new_owner,
|
||||||
|
&config.owner.pubkey(),
|
||||||
|
&[],
|
||||||
|
)?],
|
||||||
|
Some(&config.fee_payer.pubkey()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
|
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
|
||||||
|
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||||
|
Ok(Some(transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_transfer(
|
||||||
|
config: &Config,
|
||||||
|
sender: Pubkey,
|
||||||
|
ui_amount: f64,
|
||||||
|
recipient: Pubkey,
|
||||||
|
) -> CommmandResult {
|
||||||
|
println!(
|
||||||
|
"Transfer {} tokens\n Sender: {}\n Recipient: {}",
|
||||||
|
ui_amount, sender, recipient
|
||||||
|
);
|
||||||
|
let amount = sol_to_lamports(ui_amount);
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[transfer(
|
||||||
|
&spl_token::id(),
|
||||||
|
&sender,
|
||||||
|
&recipient,
|
||||||
|
&config.owner.pubkey(),
|
||||||
|
&[],
|
||||||
|
amount,
|
||||||
|
)?],
|
||||||
|
Some(&config.fee_payer.pubkey()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
|
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
|
||||||
|
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||||
|
Ok(Some(transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_burn(config: &Config, source: Pubkey, ui_amount: f64) -> CommmandResult {
|
||||||
|
println!("Burn {} tokens\n Source: {}", ui_amount, source);
|
||||||
|
let amount = sol_to_lamports(ui_amount);
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[burn(
|
||||||
|
&spl_token::id(),
|
||||||
|
&source,
|
||||||
|
&config.owner.pubkey(),
|
||||||
|
&[],
|
||||||
|
amount,
|
||||||
|
)?],
|
||||||
|
Some(&config.fee_payer.pubkey()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
|
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
|
||||||
|
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||||
|
Ok(Some(transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_mint(
|
||||||
|
config: &Config,
|
||||||
|
token: Pubkey,
|
||||||
|
ui_amount: f64,
|
||||||
|
recipient: Pubkey,
|
||||||
|
) -> CommmandResult {
|
||||||
|
println!(
|
||||||
|
"Mint {} tokens\n Token: {}\n Recipient: {}",
|
||||||
|
ui_amount, token, recipient
|
||||||
|
);
|
||||||
|
let amount = sol_to_lamports(ui_amount);
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[mint_to(
|
||||||
|
&spl_token::id(),
|
||||||
|
&token,
|
||||||
|
&recipient,
|
||||||
|
&config.owner.pubkey(),
|
||||||
|
&[],
|
||||||
|
amount,
|
||||||
|
)?],
|
||||||
|
Some(&config.fee_payer.pubkey()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
|
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
|
||||||
|
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||||
|
Ok(Some(transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_wrap(config: &Config, sol: f64) -> CommmandResult {
|
||||||
|
let account = Keypair::new();
|
||||||
|
let lamports = sol_to_lamports(sol);
|
||||||
|
println!("Wrapping {} SOL into {}", sol, account.pubkey());
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[
|
||||||
|
system_instruction::create_account(
|
||||||
|
&config.owner.pubkey(),
|
||||||
|
&account.pubkey(),
|
||||||
|
lamports,
|
||||||
|
size_of::<Account>() as u64,
|
||||||
|
&spl_token::id(),
|
||||||
|
),
|
||||||
|
initialize_account(
|
||||||
|
&spl_token::id(),
|
||||||
|
&account.pubkey(),
|
||||||
|
&spl_token::native_mint::id(),
|
||||||
|
&config.owner.pubkey(),
|
||||||
|
)?,
|
||||||
|
],
|
||||||
|
Some(&config.fee_payer.pubkey()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
|
check_owner_balance(config, lamports)?;
|
||||||
|
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
|
||||||
|
transaction.sign(
|
||||||
|
&[&config.fee_payer, &config.owner, &account],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
Ok(Some(transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_unwrap(config: &Config, address: Pubkey) -> CommmandResult {
|
||||||
|
println!("Unwrapping {}", address);
|
||||||
|
println!(
|
||||||
|
" Amount: {} SOL\n Recipient: {}",
|
||||||
|
lamports_to_sol(config.rpc_client.get_balance(&address)?),
|
||||||
|
config.owner.pubkey()
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[close_account(
|
||||||
|
&spl_token::id(),
|
||||||
|
&address,
|
||||||
|
&config.owner.pubkey(),
|
||||||
|
&config.owner.pubkey(),
|
||||||
|
&[],
|
||||||
|
)?],
|
||||||
|
Some(&config.fee_payer.pubkey()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
|
||||||
|
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
|
||||||
|
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
|
||||||
|
Ok(Some(transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_balance(_config: &Config, address: Pubkey) -> CommmandResult {
|
||||||
|
println!("balance {}", address);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_supply(_config: &Config, address: Pubkey) -> CommmandResult {
|
||||||
|
println!("supply {}", address);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_accounts(_config: &Config, token: Option<Pubkey>) -> CommmandResult {
|
||||||
|
println!("accounts {:?}", token);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let matches = App::new(crate_name!())
|
||||||
|
.about(crate_description!())
|
||||||
|
.version(crate_version!())
|
||||||
|
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||||
|
.arg({
|
||||||
|
let arg = Arg::with_name("config_file")
|
||||||
|
.short("C")
|
||||||
|
.long("config")
|
||||||
|
.value_name("PATH")
|
||||||
|
.takes_value(true)
|
||||||
|
.global(true)
|
||||||
|
.help("Configuration file to use");
|
||||||
|
if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE {
|
||||||
|
arg.default_value(&config_file)
|
||||||
|
} else {
|
||||||
|
arg
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("json_rpc_url")
|
||||||
|
.long("url")
|
||||||
|
.value_name("URL")
|
||||||
|
.takes_value(true)
|
||||||
|
.validator(is_url)
|
||||||
|
.help("JSON RPC URL for the cluster. Default from the configuration file."),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("owner")
|
||||||
|
.long("owner")
|
||||||
|
.value_name("KEYPAIR")
|
||||||
|
.validator(is_keypair)
|
||||||
|
.takes_value(true)
|
||||||
|
.help(
|
||||||
|
"Specify the token owner account. \
|
||||||
|
This may be a keypair file, the ASK keyword. \
|
||||||
|
Defaults to the client keypair.",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("fee_payer")
|
||||||
|
.long("fee-payer")
|
||||||
|
.value_name("KEYPAIR")
|
||||||
|
.validator(is_keypair)
|
||||||
|
.takes_value(true)
|
||||||
|
.help(
|
||||||
|
"Specify the fee-payer account. \
|
||||||
|
This may be a keypair file, the ASK keyword. \
|
||||||
|
Defaults to the client keypair.",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand(SubCommand::with_name("create-token").about("Create a new token"))
|
||||||
|
.subcommand(SubCommand::with_name("create-bridge").about("Create a new bridge"))
|
||||||
|
.subcommand(SubCommand::with_name("airdrop").about("Request an airdrop"))
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("create-account")
|
||||||
|
.about("Create a new token account")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("token")
|
||||||
|
.validator(is_pubkey_or_keypair)
|
||||||
|
.value_name("TOKEN_ADDRESS")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(1)
|
||||||
|
.required(true)
|
||||||
|
.help("The token that the account will hold"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("assign")
|
||||||
|
.about("Assign a token or token account to a new owner")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("address")
|
||||||
|
.validator(is_pubkey_or_keypair)
|
||||||
|
.value_name("TOKEN_ADDRESS")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(1)
|
||||||
|
.required(true)
|
||||||
|
.help("The address of the token account"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("new_owner")
|
||||||
|
.validator(is_pubkey_or_keypair)
|
||||||
|
.value_name("OWNER_ADDRESS")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(2)
|
||||||
|
.required(true)
|
||||||
|
.help("The address of the new owner"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("transfer")
|
||||||
|
.about("Transfer tokens between accounts")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("sender")
|
||||||
|
.validator(is_pubkey_or_keypair)
|
||||||
|
.value_name("SENDER_TOKEN_ACCOUNT_ADDRESS")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(1)
|
||||||
|
.required(true)
|
||||||
|
.help("The token account address of the sender"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("amount")
|
||||||
|
.validator(is_amount)
|
||||||
|
.value_name("TOKEN_AMOUNT")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(2)
|
||||||
|
.required(true)
|
||||||
|
.help("Amount to send, in tokens"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("recipient")
|
||||||
|
.validator(is_pubkey_or_keypair)
|
||||||
|
.value_name("RECIPIENT_TOKEN_ACCOUNT_ADDRESS")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(3)
|
||||||
|
.required(true)
|
||||||
|
.help("The token account address of recipient"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("burn")
|
||||||
|
.about("Burn tokens from an account")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("source")
|
||||||
|
.validator(is_pubkey_or_keypair)
|
||||||
|
.value_name("SOURCE_TOKEN_ACCOUNT_ADDRESS")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(1)
|
||||||
|
.required(true)
|
||||||
|
.help("The token account address to burn from"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("amount")
|
||||||
|
.validator(is_amount)
|
||||||
|
.value_name("TOKEN_AMOUNT")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(2)
|
||||||
|
.required(true)
|
||||||
|
.help("Amount to burn, in tokens"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("mint")
|
||||||
|
.about("Mint new tokens")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("token")
|
||||||
|
.validator(is_pubkey_or_keypair)
|
||||||
|
.value_name("TOKEN_ADDRESS")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(1)
|
||||||
|
.required(true)
|
||||||
|
.help("The token to mint"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("amount")
|
||||||
|
.validator(is_amount)
|
||||||
|
.value_name("TOKEN_AMOUNT")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(2)
|
||||||
|
.required(true)
|
||||||
|
.help("Amount to mint, in tokens"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("recipient")
|
||||||
|
.validator(is_pubkey_or_keypair)
|
||||||
|
.value_name("RECIPIENT_TOKEN_ACCOUNT_ADDRESS")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(3)
|
||||||
|
.required(true)
|
||||||
|
.help("The token account address of recipient"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("balance")
|
||||||
|
.about("Get token account balance")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("address")
|
||||||
|
.validator(is_pubkey_or_keypair)
|
||||||
|
.value_name("TOKEN_ACCOUNT_ADDRESS")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(1)
|
||||||
|
.required(true)
|
||||||
|
.help("The token account address"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("supply")
|
||||||
|
.about("Get token supply")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("address")
|
||||||
|
.validator(is_pubkey_or_keypair)
|
||||||
|
.value_name("TOKEN_ADDRESS")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(1)
|
||||||
|
.required(true)
|
||||||
|
.help("The token address"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("accounts")
|
||||||
|
.about("List all token accounts by owner")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("token")
|
||||||
|
.validator(is_pubkey_or_keypair)
|
||||||
|
.value_name("TOKEN_ADDRESS")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(1)
|
||||||
|
.help("Limit results to the given token. [Default: list accounts for all tokens]"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("wrap")
|
||||||
|
.about("Wrap native SOL in a SOL token account")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("amount")
|
||||||
|
.validator(is_amount)
|
||||||
|
.value_name("AMOUNT")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(1)
|
||||||
|
.required(true)
|
||||||
|
.help("Amount of SOL to wrap"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("unwrap")
|
||||||
|
.about("Unwrap a SOL token account")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("address")
|
||||||
|
.validator(is_pubkey_or_keypair)
|
||||||
|
.value_name("TOKEN_ACCOUNT_ADDRESS")
|
||||||
|
.takes_value(true)
|
||||||
|
.index(1)
|
||||||
|
.required(true)
|
||||||
|
.help("The address of the token account to unwrap"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.get_matches();
|
||||||
|
|
||||||
|
let config = {
|
||||||
|
let cli_config = if let Some(config_file) = matches.value_of("config_file") {
|
||||||
|
solana_cli_config::Config::load(config_file).unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
solana_cli_config::Config::default()
|
||||||
|
};
|
||||||
|
let json_rpc_url = value_t!(matches, "json_rpc_url", String)
|
||||||
|
.unwrap_or_else(|_| cli_config.json_rpc_url.clone());
|
||||||
|
|
||||||
|
let client_keypair = || {
|
||||||
|
read_keypair_file(&cli_config.keypair_path).unwrap_or_else(|err| {
|
||||||
|
eprintln!("Unable to read {}: {}", cli_config.keypair_path, err);
|
||||||
|
exit(1)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let owner = keypair_of(&matches, "owner").unwrap_or_else(client_keypair);
|
||||||
|
let fee_payer = keypair_of(&matches, "fee_payer").unwrap_or_else(client_keypair);
|
||||||
|
|
||||||
|
Config {
|
||||||
|
rpc_client: RpcClient::new(json_rpc_url),
|
||||||
|
owner,
|
||||||
|
fee_payer,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
solana_logger::setup_with_default("solana=info");
|
||||||
|
|
||||||
|
let _ = match matches.subcommand() {
|
||||||
|
("airdrop", Some(_arg_matches)) => command_request_airdrop(&config),
|
||||||
|
("create-token", Some(_arg_matches)) => command_create_token(&config),
|
||||||
|
("create-bridge", Some(_arg_matches)) => command_deploy_bridge(&config),
|
||||||
|
("create-account", Some(arg_matches)) => {
|
||||||
|
let token = pubkey_of(arg_matches, "token").unwrap();
|
||||||
|
command_create_account(&config, token)
|
||||||
|
}
|
||||||
|
("assign", Some(arg_matches)) => {
|
||||||
|
let address = pubkey_of(arg_matches, "address").unwrap();
|
||||||
|
let new_owner = pubkey_of(arg_matches, "new_owner").unwrap();
|
||||||
|
command_assign(&config, address, new_owner)
|
||||||
|
}
|
||||||
|
("transfer", Some(arg_matches)) => {
|
||||||
|
let sender = pubkey_of(arg_matches, "sender").unwrap();
|
||||||
|
let amount = value_t_or_exit!(arg_matches, "amount", f64);
|
||||||
|
let recipient = pubkey_of(arg_matches, "recipient").unwrap();
|
||||||
|
command_transfer(&config, sender, amount, recipient)
|
||||||
|
}
|
||||||
|
("burn", Some(arg_matches)) => {
|
||||||
|
let source = pubkey_of(arg_matches, "source").unwrap();
|
||||||
|
let amount = value_t_or_exit!(arg_matches, "amount", f64);
|
||||||
|
command_burn(&config, source, amount)
|
||||||
|
}
|
||||||
|
("mint", Some(arg_matches)) => {
|
||||||
|
let token = pubkey_of(arg_matches, "token").unwrap();
|
||||||
|
let amount = value_t_or_exit!(arg_matches, "amount", f64);
|
||||||
|
let recipient = pubkey_of(arg_matches, "recipient").unwrap();
|
||||||
|
command_mint(&config, token, amount, recipient)
|
||||||
|
}
|
||||||
|
("wrap", Some(arg_matches)) => {
|
||||||
|
let amount = value_t_or_exit!(arg_matches, "amount", f64);
|
||||||
|
command_wrap(&config, amount)
|
||||||
|
}
|
||||||
|
("unwrap", Some(arg_matches)) => {
|
||||||
|
let address = pubkey_of(arg_matches, "address").unwrap();
|
||||||
|
command_unwrap(&config, address)
|
||||||
|
}
|
||||||
|
("balance", Some(arg_matches)) => {
|
||||||
|
let address = pubkey_of(arg_matches, "address").unwrap();
|
||||||
|
command_balance(&config, address)
|
||||||
|
}
|
||||||
|
("supply", Some(arg_matches)) => {
|
||||||
|
let address = pubkey_of(arg_matches, "address").unwrap();
|
||||||
|
command_supply(&config, address)
|
||||||
|
}
|
||||||
|
("accounts", Some(arg_matches)) => {
|
||||||
|
let token = pubkey_of(arg_matches, "token");
|
||||||
|
command_accounts(&config, token)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
.and_then(|transaction| {
|
||||||
|
if let Some(transaction) = transaction {
|
||||||
|
// TODO: Upgrade to solana-client 1.3 and
|
||||||
|
// `send_and_confirm_transaction_with_spinner_and_commitment()` with single
|
||||||
|
// confirmation by default for better UX
|
||||||
|
let signature = config
|
||||||
|
.rpc_client
|
||||||
|
.send_and_confirm_transaction_with_spinner(&transaction)?;
|
||||||
|
println!("Signature: {}", signature);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.map_err(|err| {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue