gmsol_sdk/js/instructions/
create_order.rs1use 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#[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#[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 = ¶ms.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#[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 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 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#[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}