use anchor_client::solana_sdk::compute_budget::ComputeBudgetInstruction;
use mpl_candy_guard::{
accounts::Route as RouteAccount, guards::FreezeInstruction, instruction::Route,
instructions::RouteArgs, state::GuardType,
};
use super::*;
pub struct UnlockFundsArgs {
pub keypair: Option<String>,
pub rpc_url: Option<String>,
pub cache: String,
pub config: String,
pub candy_guard: Option<String>,
pub candy_machine: Option<String>,
pub destination: Option<String>,
pub label: Option<String>,
pub token: bool,
pub priority_fee: u64,
}
pub fn process_unlock_funds(args: UnlockFundsArgs) -> Result<()> {
let sugar_config = sugar_setup(args.keypair.clone(), args.rpc_url.clone())?;
let client = setup_client(&sugar_config)?;
let program = client.program(mpl_candy_guard::ID);
let candy_guard_id = match args.candy_guard {
Some(ref candy_guard_id) => candy_guard_id.to_owned(),
None => {
let cache = load_cache(&args.cache, false)?;
cache.program.candy_guard
}
};
let candy_machine_id = match args.candy_machine {
Some(ref candy_machine_id) => candy_machine_id.to_owned(),
None => {
let cache = load_cache(&args.cache, false)?;
cache.program.candy_machine
}
};
let candy_guard = Pubkey::from_str(&candy_guard_id)
.map_err(|_| anyhow!("Failed to parse candy guard id: {}", &candy_guard_id))?;
let candy_machine = Pubkey::from_str(&candy_machine_id)
.map_err(|_| anyhow!("Failed to parse candy machine id: {}", &candy_guard_id))?;
println!(
"{} {}Loading freeze escrow information",
style("[1/2]").bold().dim(),
LOOKING_GLASS_EMOJI
);
let pb = spinner_with_style();
pb.set_message("Connecting...");
let (destination_address, freeze_guard) = match args.destination {
Some(ref destination_address) => {
let address = Pubkey::from_str(destination_address).map_err(|_| {
anyhow!(
"Failed to parse destination address: {}",
&destination_address
)
})?;
(
address,
if args.token {
GuardType::FreezeTokenPayment
} else {
GuardType::FreezeSolPayment
},
)
}
None => {
let (destination_address, freeze_guard) = get_destination(
&program,
&candy_guard,
get_config_data(&args.config)?,
&args.label,
)?;
(
destination_address,
if freeze_guard.is_some() {
GuardType::FreezeTokenPayment
} else {
GuardType::FreezeSolPayment
},
)
}
};
let (freeze_escrow, _) = find_freeze_pda(&candy_guard, &candy_machine, &destination_address);
let account_data = program
.rpc()
.get_account_data(&freeze_escrow)
.map_err(|_| anyhow!("Could not load freeze escrow"))?;
if account_data.is_empty() {
return Err(anyhow!("Freeze escrow account not found"));
}
pb.finish_with_message("Done");
println!(
"\n{} {}Unlocking treasury funds",
style("[2/2]").bold().dim(),
MONEY_BAG_EMOJI
);
let pb = spinner_with_style();
pb.set_message("Sending unlock funds transaction...");
let signature = unlock_funds(
&program,
&candy_guard,
&candy_machine,
&destination_address,
&args.label,
freeze_guard,
&args.priority_fee,
)?;
pb.finish_with_message(format!(
"{} {}",
style("Unlock funds signature:").bold(),
signature
));
Ok(())
}
pub fn unlock_funds<C: Deref<Target = impl Signer> + Clone>(
program: &Program<C>,
candy_guard_id: &Pubkey,
candy_machine_id: &Pubkey,
destination: &Pubkey,
label: &Option<String>,
freeze_guard: GuardType,
priority_fee: &u64,
) -> Result<Signature> {
let mut remaining_accounts = Vec::with_capacity(4);
let (freeze_pda, _) = find_freeze_pda(candy_guard_id, candy_machine_id, destination);
remaining_accounts.push(AccountMeta {
pubkey: freeze_pda,
is_signer: false,
is_writable: true,
});
remaining_accounts.push(AccountMeta {
pubkey: program.payer(),
is_signer: true,
is_writable: false,
});
match freeze_guard {
GuardType::FreezeSolPayment => {
remaining_accounts.push(AccountMeta {
pubkey: *destination,
is_signer: false,
is_writable: true,
});
remaining_accounts.push(AccountMeta {
pubkey: system_program::id(),
is_signer: false,
is_writable: false,
});
}
GuardType::FreezeTokenPayment => {
let account_data = program
.rpc()
.get_account_data(destination)
.map_err(|_| anyhow!("Could not load destination account"))?;
let destination_account = spl_token::state::Account::unpack(&account_data)?;
remaining_accounts.push(AccountMeta {
pubkey: get_associated_token_address(&freeze_pda, &destination_account.mint),
is_signer: false,
is_writable: true,
});
remaining_accounts.push(AccountMeta {
pubkey: destination.to_owned(),
is_signer: false,
is_writable: true,
});
remaining_accounts.push(AccountMeta {
pubkey: spl_token::ID,
is_signer: false,
is_writable: false,
});
remaining_accounts.push(AccountMeta {
pubkey: system_program::id(),
is_signer: false,
is_writable: false,
});
}
_ => return Err(anyhow!("Invalid freeze guard type: {freeze_guard:?}")),
};
let priority_fee_ix = ComputeBudgetInstruction::set_compute_unit_price(*priority_fee);
let builder = program
.request()
.instruction(priority_fee_ix)
.accounts(RouteAccount {
candy_guard: *candy_guard_id,
candy_machine: *candy_machine_id,
payer: program.payer(),
})
.accounts(remaining_accounts)
.args(Route {
args: RouteArgs {
data: vec![FreezeInstruction::UnlockFunds as u8],
guard: freeze_guard,
},
label: label.to_owned(),
});
let sig = builder.send()?;
Ok(sig)
}