Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 4c93e07

Browse files
Add freeze and thaw functionality to spl-token cli (#511)
* Add arg to enable token mint to freeze * Add freeze/thaw subcommands * Add frozen marker to spl-token accounts * Add todos to simplify args
1 parent e78e265 commit 4c93e07

File tree

3 files changed

+147
-8
lines changed

3 files changed

+147
-8
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

token/cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ version = "2.0.1"
1010

1111
[dependencies]
1212
clap = "2.33.3"
13+
console = "0.11.3"
1314
serde_json = "1.0.57"
1415
solana-account-decoder = { version = "1.3.12" }
1516
solana-clap-utils = { version = "1.3.12"}

token/cli/src/main.rs

Lines changed: 145 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ use clap::{
22
crate_description, crate_name, crate_version, value_t_or_exit, App, AppSettings, Arg,
33
SubCommand,
44
};
5-
use solana_account_decoder::{parse_token::TokenAccountType, UiAccountData};
5+
use console::Emoji;
6+
use solana_account_decoder::{
7+
parse_token::{TokenAccountType, UiAccountState},
8+
UiAccountData,
9+
};
610
use solana_clap_utils::{
711
input_parsers::pubkey_of,
812
input_validators::{is_amount, is_keypair, is_pubkey_or_keypair, is_url},
@@ -26,6 +30,8 @@ use spl_token::{
2630
};
2731
use std::process::exit;
2832

33+
static WARNING: Emoji = Emoji("⚠️", "!");
34+
2935
struct Config {
3036
rpc_client: RpcClient,
3137
verbose: bool,
@@ -74,12 +80,22 @@ fn check_owner_balance(config: &Config, required_balance: u64) -> Result<(), Err
7480
}
7581
}
7682

77-
fn command_create_token(config: &Config, decimals: u8, token: Box<dyn Signer>) -> CommandResult {
83+
fn command_create_token(
84+
config: &Config,
85+
decimals: u8,
86+
token: Box<dyn Signer>,
87+
enable_freeze: bool,
88+
) -> CommandResult {
7889
println!("Creating token {}", token.pubkey());
7990

8091
let minimum_balance_for_rent_exemption = config
8192
.rpc_client
8293
.get_minimum_balance_for_rent_exemption(Mint::LEN)?;
94+
let freeze_authority_pubkey = if enable_freeze {
95+
Some(config.owner.pubkey())
96+
} else {
97+
None
98+
};
8399

84100
let mut transaction = Transaction::new_with_payer(
85101
&[
@@ -94,7 +110,7 @@ fn command_create_token(config: &Config, decimals: u8, token: Box<dyn Signer>) -
94110
&spl_token::id(),
95111
&token.pubkey(),
96112
&config.owner.pubkey(),
97-
None,
113+
freeze_authority_pubkey.as_ref(),
98114
decimals,
99115
)?,
100116
],
@@ -309,6 +325,50 @@ fn command_mint(
309325
Ok(Some(transaction))
310326
}
311327

328+
fn command_freeze(config: &Config, token: Pubkey, account: Pubkey) -> CommandResult {
329+
println!("Freezing account: {}\n Token: {}", account, token);
330+
331+
let mut transaction = Transaction::new_with_payer(
332+
&[freeze_account(
333+
&spl_token::id(),
334+
&account,
335+
&token,
336+
&config.owner.pubkey(),
337+
&[],
338+
)?],
339+
Some(&config.fee_payer.pubkey()),
340+
);
341+
342+
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
343+
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
344+
let mut signers = vec![config.fee_payer.as_ref(), config.owner.as_ref()];
345+
unique_signers!(signers);
346+
transaction.sign(&signers, recent_blockhash);
347+
Ok(Some(transaction))
348+
}
349+
350+
fn command_thaw(config: &Config, token: Pubkey, account: Pubkey) -> CommandResult {
351+
println!("Freezing account: {}\n Token: {}", account, token);
352+
353+
let mut transaction = Transaction::new_with_payer(
354+
&[thaw_account(
355+
&spl_token::id(),
356+
&account,
357+
&token,
358+
&config.owner.pubkey(),
359+
&[],
360+
)?],
361+
Some(&config.fee_payer.pubkey()),
362+
);
363+
364+
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
365+
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
366+
let mut signers = vec![config.fee_payer.as_ref(), config.owner.as_ref()];
367+
unique_signers!(signers);
368+
transaction.sign(&signers, recent_blockhash);
369+
Ok(Some(transaction))
370+
}
371+
312372
fn command_wrap(config: &Config, sol: f64) -> CommandResult {
313373
let account = Keypair::new();
314374
let lamports = sol_to_lamports(sol);
@@ -429,10 +489,20 @@ fn command_accounts(config: &Config, token: Option<Pubkey>) -> CommandResult {
429489
);
430490
} else {
431491
match serde_json::from_value(parsed_account.parsed) {
432-
Ok(TokenAccountType::Account(ui_token_account)) => println!(
433-
"{:<44} {:<44} {}",
434-
address, ui_token_account.mint, ui_token_account.token_amount.ui_amount
435-
),
492+
Ok(TokenAccountType::Account(ui_token_account)) => {
493+
let maybe_frozen = if let UiAccountState::Frozen = ui_token_account.state {
494+
format!(" {} Frozen", WARNING)
495+
} else {
496+
"".to_string()
497+
};
498+
println!(
499+
"{:<44} {:<44} {}{}",
500+
address,
501+
ui_token_account.mint,
502+
ui_token_account.token_amount.ui_amount,
503+
maybe_frozen
504+
)
505+
}
436506
Ok(_) => println!("{:<44} Unsupported token account", address),
437507
Err(err) => println!("{:<44} Account parse failure: {}", address, err),
438508
}
@@ -531,6 +601,14 @@ fn main() {
531601
This may be a keypair file or the ASK keyword. \
532602
[default: randomly generated keypair]"
533603
),
604+
)
605+
.arg(
606+
Arg::with_name("enable_freeze")
607+
.long("enable-freeze")
608+
.takes_value(false)
609+
.help(
610+
"Enable the mint authority to freeze associated token accounts."
611+
),
534612
),
535613
)
536614
.subcommand(
@@ -664,6 +742,50 @@ fn main() {
664742
.help("The token account address of recipient"),
665743
),
666744
)
745+
.subcommand(
746+
SubCommand::with_name("freeze")
747+
.about("Freeze a token account")
748+
.arg(
749+
Arg::with_name("token") // TODO: remove this arg when solana-client v1.3.12+ is published; grab mint from token account state
750+
.validator(is_pubkey_or_keypair)
751+
.value_name("TOKEN_ADDRESS")
752+
.takes_value(true)
753+
.index(1)
754+
.required(true)
755+
.help("The token mint"),
756+
)
757+
.arg(
758+
Arg::with_name("account")
759+
.validator(is_pubkey_or_keypair)
760+
.value_name("TOKEN_ACCOUNT_ADDRESS")
761+
.takes_value(true)
762+
.index(2)
763+
.required(true)
764+
.help("The address of the token account to freeze"),
765+
),
766+
)
767+
.subcommand(
768+
SubCommand::with_name("thaw")
769+
.about("Thaw a token account")
770+
.arg(
771+
Arg::with_name("token") // TODO: remove this arg when solana-client v1.3.12+ is published; grab mint from token account state
772+
.validator(is_pubkey_or_keypair)
773+
.value_name("TOKEN_ADDRESS")
774+
.takes_value(true)
775+
.index(1)
776+
.required(true)
777+
.help("The token mint"),
778+
)
779+
.arg(
780+
Arg::with_name("account")
781+
.validator(is_pubkey_or_keypair)
782+
.value_name("TOKEN_ACCOUNT_ADDRESS")
783+
.takes_value(true)
784+
.index(2)
785+
.required(true)
786+
.help("The address of the token account to thaw"),
787+
),
788+
)
667789
.subcommand(
668790
SubCommand::with_name("balance")
669791
.about("Get token account balance")
@@ -798,7 +920,12 @@ fn main() {
798920
Box::new(Keypair::new())
799921
};
800922

801-
command_create_token(&config, decimals, token)
923+
command_create_token(
924+
&config,
925+
decimals,
926+
token,
927+
arg_matches.is_present("enable_freeze"),
928+
)
802929
}
803930
("create-account", Some(arg_matches)) => {
804931
let token = pubkey_of(arg_matches, "token").unwrap();
@@ -841,6 +968,16 @@ fn main() {
841968
let recipient = pubkey_of(arg_matches, "recipient").unwrap();
842969
command_mint(&config, token, amount, recipient)
843970
}
971+
("freeze", Some(arg_matches)) => {
972+
let token = pubkey_of(arg_matches, "token").unwrap();
973+
let account = pubkey_of(arg_matches, "account").unwrap();
974+
command_freeze(&config, token, account)
975+
}
976+
("thaw", Some(arg_matches)) => {
977+
let token = pubkey_of(arg_matches, "token").unwrap();
978+
let account = pubkey_of(arg_matches, "account").unwrap();
979+
command_thaw(&config, token, account)
980+
}
844981
("wrap", Some(arg_matches)) => {
845982
let amount = value_t_or_exit!(arg_matches, "amount", f64);
846983
command_wrap(&config, amount)

0 commit comments

Comments
 (0)