use anyhow::Context as _;
use clap::Args as ClapArgs;
use cow_settlement_client::cow_settlement_interface::{
data::order::OrderAccount, instruction::reclaim_order::ReclaimOrder, parse_settlement_error,
Pubkey,
};
use solana_sdk::{
account::Account,
signature::Signer,
transaction::{Transaction, TransactionError},
};
use crate::cmd::Context;
use crate::utils::{
output::{print_failures, print_summary},
parsing::OrderArg,
};
#[derive(ClapArgs)]
pub struct ReclaimArgs {
#[arg(required = true, value_parser = OrderArg::parse)]
orders: Vec<OrderArg>,
}
struct ResolvedOrder {
arg: OrderArg,
pda: Pubkey,
account: Account,
}
pub fn run(ctx: Context, args: ReclaimArgs) -> anyhow::Result<()> {
let orders: Vec<ResolvedOrder> = {
let pdas: Vec<Pubkey> = args
.orders
.iter()
.map(|order| order.pda(&ctx.program_id))
.collect();
let accounts = ctx
.rpc
.get_multiple_accounts(&pdas)
.context("failed to fetch order accounts")?;
args.orders
.iter()
.zip(pdas)
.zip(accounts)
.map(|((&arg, pda), account)| {
let account = account
.context("order account not found")
.with_context(|| format!("bad order {arg}, nothing was reclaimed"))?;
anyhow::Ok(ResolvedOrder { arg, pda, account })
})
.collect::<anyhow::Result<Vec<_>>>()?
};
let mut ixs = Vec::new();
for order in &orders {
let reclaim_recipient = reclaim_recipient(&order.account, &ctx.program_id)
.with_context(|| format!("bad order {}, nothing was reclaimed", order.arg))?;
ixs.push(
ReclaimOrder {
program_id: ctx.program_id,
order_pda: order.pda,
reclaim_recipient,
owner: Some(ctx.payer.pubkey()),
}
.instruction(),
);
}
let blockhash = ctx
.rpc
.get_latest_blockhash()
.context("failed to fetch blockhash")?;
let tx = Transaction::new_signed_with_payer(
&ixs,
Some(&ctx.payer.pubkey()),
&[&ctx.payer],
blockhash,
);
let sig = ctx.rpc.send_and_confirm_transaction(&tx).map_err(|e| {
if let Some(TransactionError::InstructionError(index, err)) = e.get_transaction_error() {
if let Some(order) = orders.get(usize::from(index)) {
let reason = match parse_settlement_error(err) {
Ok(err) => format!("{err:?}"),
Err(err) => err.to_string(),
};
print_failures(&[(&order.arg.to_string(), &reason)]);
}
}
anyhow::Error::new(e).context("transaction failed, nothing was reclaimed")
})?;
let mut summary: Vec<(&str, &dyn ToString)> = vec![("signature", &sig)];
summary.extend(
orders
.iter()
.map(|order| ("orderPda", &order.pda as &dyn ToString)),
);
print_summary(&summary);
Ok(())
}
fn reclaim_recipient(account: &Account, program_id: &Pubkey) -> anyhow::Result<Pubkey> {
anyhow::ensure!(
account.owner == *program_id,
"account is owned by {}",
account.owner
);
let order = OrderAccount::attach(&account.data[..])
.map_err(|e| anyhow::anyhow!("account data is not an order: {e}"))?;
Ok(order.created_by())
}