Skip to main content

gmsol_sdk/js/instructions/
create_order.rs

1use std::collections::{hash_map, HashMap, HashSet};
2
3use gmsol_solana_utils::{AtomicGroup, IntoAtomicGroup, ParallelGroup};
4use serde::{Deserialize, Serialize};
5
6use tsify_next::Tsify;
7use wasm_bindgen::prelude::*;
8
9use crate::{
10    builders::{
11        callback::Callback,
12        order::{
13            CreateOrder, CreateOrderHint, CreateOrderKind, CreateOrderParams, PreparePosition,
14        },
15        token::{PrepareTokenAccounts, WrapNative},
16        user::PrepareUser,
17        StoreProgram,
18    },
19    js::instructions::BuildTransactionOptions,
20    serde::StringPubkey,
21};
22
23use super::{TransactionGroup, TransactionGroupOptions};
24
25/// Options for creating orders.
26#[derive(Debug, Serialize, Deserialize, Tsify)]
27#[tsify(into_wasm_abi, from_wasm_abi)]
28pub struct CreateOrderOptions {
29    recent_blockhash: String,
30    #[serde(default)]
31    compute_unit_price_micro_lamports: Option<u64>,
32    #[serde(default)]
33    compute_unit_min_priority_lamports: Option<u64>,
34    payer: StringPubkey,
35    collateral_or_swap_out_token: StringPubkey,
36    hints: HashMap<StringPubkey, CreateOrderHint>,
37    #[serde(default)]
38    program: Option<StoreProgram>,
39    #[serde(default)]
40    pay_token: Option<StringPubkey>,
41    #[serde(default)]
42    receive_token: Option<StringPubkey>,
43    #[serde(default)]
44    swap_path: Option<Vec<StringPubkey>>,
45    #[serde(default)]
46    skip_wrap_native_on_pay: Option<bool>,
47    #[serde(default)]
48    skip_unwrap_native_on_receive: Option<bool>,
49    #[serde(default)]
50    callback: Option<Callback>,
51    #[serde(default)]
52    transaction_group: TransactionGroupOptions,
53    #[serde(default)]
54    force_create_positions_in_parallel: Option<bool>,
55    #[serde(default)]
56    force_create_positions: Option<bool>,
57}
58
59/// Create transaction builder for create-order ixs.
60#[wasm_bindgen]
61pub fn create_orders_builder(
62    kind: CreateOrderKind,
63    orders: Vec<CreateOrderParams>,
64    options: CreateOrderOptions,
65) -> crate::Result<CreateOrdersBuilder> {
66    let pay_token = options
67        .pay_token
68        .unwrap_or(options.collateral_or_swap_out_token);
69    let wrap_native = (kind.is_increase() || kind.is_swap())
70        && (pay_token.0 == WrapNative::NATIVE_MINT
71            && !options.skip_wrap_native_on_pay.unwrap_or_default());
72
73    let mut tokens = HashSet::default();
74
75    if kind.is_decrease() || kind.is_swap() {
76        let receive_token = options
77            .receive_token
78            .unwrap_or(options.collateral_or_swap_out_token);
79        tokens.insert(receive_token);
80    }
81
82    if wrap_native {
83        tokens.insert(WrapNative::NATIVE_MINT.into());
84    }
85
86    let hints = &options.hints;
87    let force_create_positions_in_parallel = options
88        .force_create_positions_in_parallel
89        .unwrap_or_default();
90    let force_create_positions =
91        options.force_create_positions.unwrap_or_default() || force_create_positions_in_parallel;
92
93    let mut positions = HashMap::<StringPubkey, _>::default();
94    let create = orders
95        .into_iter()
96        .map(|params| {
97            let market_token = &params.market_token;
98            let hint = hints.get(market_token).ok_or_else(|| {
99                crate::Error::custom(format!("hint for {} is not provided", market_token.0))
100            })?;
101
102            let program = options.program.clone().unwrap_or_default();
103            let payer = options.payer;
104            let collateral_or_swap_out_token = options.collateral_or_swap_out_token;
105            if !kind.is_swap() {
106                tokens.insert(hint.long_token);
107                tokens.insert(hint.short_token);
108
109                if force_create_positions && !force_create_positions_in_parallel {
110                    let prepare = PreparePosition::builder()
111                        .program(program.clone())
112                        .collateral_token(collateral_or_swap_out_token)
113                        .kind(kind)
114                        .params(params.clone())
115                        .payer(payer)
116                        .build();
117
118                    if let hash_map::Entry::Vacant(e) =
119                        positions.entry(prepare.position_address().into())
120                    {
121                        let ag = prepare.into_atomic_group(hint)?;
122                        e.insert(ag);
123                    }
124                }
125            }
126
127            let amount = params.amount;
128            let create = CreateOrder::builder()
129                .program(program)
130                .payer(payer)
131                .kind(kind)
132                .collateral_or_swap_out_token(collateral_or_swap_out_token)
133                .params(params)
134                .pay_token(options.pay_token)
135                .receive_token(options.receive_token)
136                .swap_path(options.swap_path.clone().unwrap_or_default())
137                .unwrap_native_on_receive(
138                    !options.skip_unwrap_native_on_receive.unwrap_or_default(),
139                )
140                .callback(options.callback.clone())
141                .skip_position_creation(
142                    force_create_positions && !force_create_positions_in_parallel,
143                )
144                .force_position_creation(force_create_positions_in_parallel)
145                .build()
146                .into_atomic_group(hint)?;
147
148            let ag = if wrap_native {
149                let mut wrap = WrapNative::builder()
150                    .owner(options.payer)
151                    .lamports(amount.try_into().map_err(crate::Error::custom)?)
152                    .build()
153                    .into_atomic_group(&true)?;
154                wrap.merge(create);
155                wrap
156            } else {
157                create
158            };
159
160            Ok(ag)
161        })
162        .collect::<crate::Result<Vec<_>>>()?;
163
164    Ok(CreateOrdersBuilder {
165        payer: options.payer,
166        tokens,
167        positions,
168        create,
169        transaction_group: options.transaction_group,
170        build: BuildTransactionOptions {
171            recent_blockhash: options.recent_blockhash,
172            compute_unit_price_micro_lamports: options.compute_unit_price_micro_lamports,
173            compute_unit_min_priority_lamports: options.compute_unit_min_priority_lamports,
174        },
175    })
176}
177
178/// Builder for create-order ixs.
179#[wasm_bindgen]
180pub struct CreateOrdersBuilder {
181    payer: StringPubkey,
182    tokens: HashSet<StringPubkey>,
183    positions: HashMap<StringPubkey, AtomicGroup>,
184    create: Vec<AtomicGroup>,
185    transaction_group: TransactionGroupOptions,
186    build: BuildTransactionOptions,
187}
188
189#[wasm_bindgen]
190impl CreateOrdersBuilder {
191    /// Build transactions.
192    pub fn build_with_options(
193        self,
194        transaction_group: Option<TransactionGroupOptions>,
195        build: Option<BuildTransactionOptions>,
196    ) -> crate::Result<TransactionGroup> {
197        let mut group = transaction_group.unwrap_or(self.transaction_group).build();
198
199        let prepare_user = PrepareUser::builder()
200            .payer(self.payer)
201            .build()
202            .into_atomic_group(&())?;
203
204        let prepare = PrepareTokenAccounts::builder()
205            .owner(self.payer)
206            .payer(self.payer)
207            .tokens(self.tokens)
208            .build()
209            .into_atomic_group(&())?;
210
211        let build = build.unwrap_or(self.build);
212        TransactionGroup::new(
213            group
214                .add(prepare_user)?
215                .add(prepare)?
216                .add(self.positions.into_values().collect::<ParallelGroup>())?
217                .add(self.create.into_iter().collect::<ParallelGroup>())?
218                .optimize(false),
219            &build.recent_blockhash,
220            build.compute_unit_price_micro_lamports,
221            build.compute_unit_min_priority_lamports,
222        )
223    }
224
225    /// Merge with the other [`CreateOrderBuilder`].
226    pub fn merge(&mut self, other: &mut Self) -> crate::Result<()> {
227        if self.payer != other.payer {
228            return Err(crate::Error::custom(format!(
229                "payer mismatch: this = {}, other = {}",
230                self.payer, other.payer
231            )));
232        }
233        for token in other.tokens.iter() {
234            self.tokens.insert(*token);
235        }
236        for (position, ag) in other.positions.drain() {
237            self.positions.entry(position).or_insert(ag);
238        }
239        self.create.append(&mut other.create);
240        Ok(())
241    }
242}
243
244/// Build transactions for creating orders.
245#[wasm_bindgen]
246pub fn create_orders(
247    kind: CreateOrderKind,
248    orders: Vec<CreateOrderParams>,
249    options: CreateOrderOptions,
250) -> crate::Result<TransactionGroup> {
251    create_orders_builder(kind, orders, options)?.build_with_options(None, None)
252}