Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions clients/cli/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use {
crate::{
close_stuck_escrow::{command_close_stuck_escrow, CloseStuckEscrowArgs},
config::Config,
create_escrow_account::{command_create_escrow_account, CreateEscrowAccountArgs},
create_mint::{command_create_mint, CreateMintArgs},
Expand Down Expand Up @@ -97,6 +98,9 @@ pub enum Command {
Unwrap(UnwrapArgs),
/// Create an account used to escrow unwrapped tokens
CreateEscrowAccount(CreateEscrowAccountArgs),
/// Close a stuck escrow account when a mint has closed and re-created with
/// different mint extensions
CloseStuckEscrow(CloseStuckEscrowArgs),
}

impl Command {
Expand All @@ -112,6 +116,7 @@ impl Command {
Command::FindPdas(args) => command_get_pdas(config, args).await,
Command::Unwrap(args) => command_unwrap(config, args, matches, wallet_manager).await,
Command::CreateEscrowAccount(args) => command_create_escrow_account(config, args).await,
Command::CloseStuckEscrow(args) => command_close_stuck_escrow(config, args).await,
}
}
}
136 changes: 136 additions & 0 deletions clients/cli/src/close_stuck_escrow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use {
crate::{
common::{get_account_owner, parse_pubkey, parse_token_program, process_transaction},
config::Config,
output::{format_output, println_display},
CommandResult,
},
clap::Args,
serde_derive::{Deserialize, Serialize},
serde_with::{serde_as, DisplayFromStr},
solana_cli_output::{display::writeln_name_value, QuietDisplay, VerboseDisplay},
solana_pubkey::Pubkey,
solana_signature::Signature,
solana_signer::Signer,
solana_transaction::Transaction,
spl_token_wrap::{
get_escrow_address, get_wrapped_mint_address, get_wrapped_mint_authority,
instruction::close_stuck_escrow,
},
std::fmt::{Display, Formatter},
};

#[derive(Clone, Debug, Args)]
pub struct CloseStuckEscrowArgs {
/// The address of the unwrapped mint
#[clap(value_parser = parse_pubkey)]
pub unwrapped_mint: Pubkey,

/// The address of the account to send lamports to
#[clap(value_parser = parse_pubkey)]
pub destination: Pubkey,

/// The address of the token program for the wrapped mint
#[clap(value_parser = parse_token_program)]
pub wrapped_token_program: Pubkey,
}

#[serde_as]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CloseStuckEscrowOutput {
#[serde_as(as = "DisplayFromStr")]
pub unwrapped_mint: Pubkey,

#[serde_as(as = "DisplayFromStr")]
pub escrow_account: Pubkey,

pub signatures: Vec<Signature>,
}

impl Display for CloseStuckEscrowOutput {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln_name_value(f, "Unwrapped mint:", &self.unwrapped_mint.to_string())?;
writeln_name_value(f, "Escrow account:", &self.escrow_account.to_string())?;

writeln!(f, "Signers:")?;
for signature in &self.signatures {
writeln!(f, " {signature}")?;
}

Ok(())
}
}

impl QuietDisplay for CloseStuckEscrowOutput {
fn write_str(&self, _: &mut dyn std::fmt::Write) -> std::fmt::Result {
Ok(())
}
}
impl VerboseDisplay for CloseStuckEscrowOutput {}

pub async fn command_close_stuck_escrow(
config: &Config,
args: CloseStuckEscrowArgs,
) -> CommandResult {
let unwrapped_token_program =
get_account_owner(&config.rpc_client, &args.unwrapped_mint).await?;

// CloseStuckEscrow only works with spl-token-2022 unwrapped mints due to
// extension requirements
if unwrapped_token_program != spl_token_2022::id() {
return Err(format!(
"CloseStuckEscrow only works with spl-token-2022 unwrapped mints. Unwrapped mint {} \
uses program {}",
args.unwrapped_mint, unwrapped_token_program
)
.into());
}

let wrapped_mint = get_wrapped_mint_address(&args.unwrapped_mint, &args.wrapped_token_program);
let wrapped_mint_authority = get_wrapped_mint_authority(&wrapped_mint);
let escrow_account = get_escrow_address(
&args.unwrapped_mint,
&unwrapped_token_program,
&args.wrapped_token_program,
);

if !config.dry_run {
println_display(
config,
format!(
"Closing stuck escrow account {} for unwrapped mint {}",
escrow_account, args.unwrapped_mint,
),
);
}

let instruction = close_stuck_escrow(
&spl_token_wrap::id(),
&escrow_account,
&args.destination,
&args.unwrapped_mint,
&wrapped_mint,
&wrapped_mint_authority,
);

let latest_blockhash = config.rpc_client.get_latest_blockhash().await?;
let payer = config.fee_payer()?;

let transaction = Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[&*payer],
latest_blockhash,
);

process_transaction(config, transaction.clone()).await?;

let output = CloseStuckEscrowOutput {
unwrapped_mint: args.unwrapped_mint,
escrow_account,
signatures: transaction.signatures,
};

Ok(format_output(config, output))
}
1 change: 1 addition & 0 deletions clients/cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod cli;
mod close_stuck_escrow;
mod common;
mod config;
mod create_escrow_account;
Expand Down
Loading