use anchor_spl::associated_token::{self, get_associated_token_address_with_program_id};
use gmsol_programs::{
anchor_lang::system_program,
gmsol_store::client::{accounts, args},
};
use gmsol_solana_utils::{AtomicGroup, IntoAtomicGroup, ProgramExt};
use typed_builder::TypedBuilder;
use crate::{
builders::{
callback::{Callback, CallbackParams},
utils::get_ata_or_owner,
StoreProgram,
},
serde::StringPubkey,
};
#[cfg_attr(js, derive(tsify_next::Tsify))]
#[cfg_attr(js, tsify(from_wasm_abi))]
#[cfg_attr(serde, derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, TypedBuilder)]
pub struct CloseOrder {
#[cfg_attr(serde, serde(default))]
#[builder(default)]
pub program: StoreProgram,
#[builder(setter(into))]
pub payer: StringPubkey,
#[builder(setter(into))]
pub order: StringPubkey,
#[builder(setter(into), default = "cancel".to_string())]
pub reason: String,
}
#[cfg_attr(js, derive(tsify_next::Tsify))]
#[cfg_attr(js, tsify(from_wasm_abi))]
#[cfg_attr(serde, derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, TypedBuilder)]
pub struct CloseOrderHint {
#[builder(setter(into))]
pub owner: StringPubkey,
#[builder(setter(into))]
pub receiver: StringPubkey,
#[builder(setter(into))]
pub rent_receiver: StringPubkey,
#[builder(setter(into))]
pub referrer: Option<StringPubkey>,
#[builder(setter(into))]
pub initial_collateral_token: Option<StringPubkey>,
#[builder(setter(into))]
pub final_output_token: Option<StringPubkey>,
#[builder(setter(into))]
pub long_token: Option<StringPubkey>,
#[builder(setter(into))]
pub short_token: Option<StringPubkey>,
pub should_unwrap_native_token: bool,
pub callback: Option<Callback>,
}
impl IntoAtomicGroup for CloseOrder {
type Hint = CloseOrderHint;
fn into_atomic_group(self, hint: &Self::Hint) -> gmsol_solana_utils::Result<AtomicGroup> {
let token_program_id = anchor_spl::token::ID;
let payer = self.payer.0;
let owner = hint.owner.0;
let receiver = hint.receiver.0;
let user = self.program.find_user_address(&owner);
let referrer_user = hint
.referrer
.as_ref()
.map(|referrer| self.program.find_user_address(referrer));
let order = self.order.0;
let initial_collateral_token = hint.initial_collateral_token.as_deref().copied();
let final_output_token = hint.final_output_token.as_deref().copied();
let long_token = hint.long_token.as_deref().copied();
let short_token = hint.short_token.as_deref().copied();
let CallbackParams {
callback_authority,
callback_program,
callback_shared_data_account,
callback_partitioned_data_account,
..
} = self.program.get_callback_params(hint.callback.as_ref());
let close = self
.program
.anchor_instruction(args::CloseOrderV2 {
reason: self.reason,
})
.anchor_accounts(
accounts::CloseOrderV2 {
executor: payer,
store: self.program.store.0,
store_wallet: self.program.find_store_wallet_address(),
owner,
receiver,
rent_receiver: hint.rent_receiver.0,
user,
referrer_user,
order,
initial_collateral_token,
final_output_token,
long_token,
short_token,
initial_collateral_token_escrow: initial_collateral_token.as_ref().map(
|token| {
get_associated_token_address_with_program_id(
&order,
token,
&token_program_id,
)
},
),
final_output_token_escrow: final_output_token.as_ref().map(|token| {
get_associated_token_address_with_program_id(
&order,
token,
&token_program_id,
)
}),
long_token_escrow: long_token.as_ref().map(|token| {
get_associated_token_address_with_program_id(
&order,
token,
&token_program_id,
)
}),
short_token_escrow: short_token.as_ref().map(|token| {
get_associated_token_address_with_program_id(
&order,
token,
&token_program_id,
)
}),
initial_collateral_token_ata: initial_collateral_token.as_ref().map(|token| {
get_ata_or_owner(&owner, token, hint.should_unwrap_native_token)
}),
final_output_token_ata: final_output_token.as_ref().map(|token| {
get_ata_or_owner(&receiver, token, hint.should_unwrap_native_token)
}),
long_token_ata: long_token.as_ref().map(|token| {
get_ata_or_owner(&receiver, token, hint.should_unwrap_native_token)
}),
short_token_ata: short_token.as_ref().map(|token| {
get_ata_or_owner(&receiver, token, hint.should_unwrap_native_token)
}),
system_program: system_program::ID,
token_program: token_program_id,
associated_token_program: associated_token::ID,
event_authority: self.program.find_event_authority_address(),
program: self.program.id.0,
callback_authority,
callback_program,
callback_shared_data_account,
callback_partitioned_data_account,
},
true,
)
.build();
Ok(AtomicGroup::with_instructions(&payer, Some(close)))
}
}
#[cfg(test)]
mod tests {
use gmsol_solana_utils::transaction_builder::default_before_sign;
use solana_sdk::pubkey::Pubkey;
use super::*;
#[test]
fn close_order() -> crate::Result<()> {
use anchor_spl::token::spl_token::native_mint::ID as NATIVE_MINT;
let order = Pubkey::new_unique();
let payer = Pubkey::new_unique();
let owner = Pubkey::new_unique();
let receiver = Pubkey::new_unique();
let rent_receiver = Pubkey::new_unique();
let referrer = Some(Pubkey::new_unique().into());
let initial_collateral_token = Some(NATIVE_MINT.into());
let final_output_token = None;
let long_token = Some(Pubkey::new_unique().into());
let short_token = Some(Pubkey::new_unique().into());
CloseOrder::builder()
.payer(payer)
.order(order)
.build()
.into_atomic_group(
&CloseOrderHint::builder()
.owner(owner)
.receiver(receiver)
.rent_receiver(rent_receiver)
.referrer(referrer)
.initial_collateral_token(initial_collateral_token)
.final_output_token(final_output_token)
.long_token(long_token)
.short_token(short_token)
.should_unwrap_native_token(true)
.callback(None)
.build(),
)?
.partially_signed_transaction_with_blockhash_and_options(
Default::default(),
Default::default(),
None,
default_before_sign,
)?;
Ok(())
}
}