1use std::{ops::Deref, sync::Arc};
4
5use gmsol_model::price::Price;
6use gmsol_programs::gmsol_store::types::{
7 CreateDepositParams, CreateGlvDepositParams, CreateGlvWithdrawalParams, CreateShiftParams,
8 CreateWithdrawalParams,
9};
10use solana_sdk::pubkey::Pubkey;
11use tsify_next::Tsify;
12use wasm_bindgen::prelude::*;
13
14use crate::{
15 glv::{GlvCalculator, GlvStatus},
16 js::glv::JsGlvModel,
17 market::Value,
18 serde::StringPubkey,
19 simulation::{order::UpdatePriceOptions, SimulationOptions, Simulator},
20};
21
22use crate::js::{
23 market::JsMarketModel, position::JsPosition, virtual_inventory::JsVirtualInventoryModel,
24};
25
26use super::{
27 deposit::{JsDepositSimulationOutput, SimulateDepositArgs},
28 glv_deposit::{JsGlvDepositSimulationOutput, SimulateGlvDepositArgs},
29 glv_withdrawal::{JsGlvWithdrawalSimulationOutput, SimulateGlvWithdrawalArgs},
30 order::{JsOrderSimulationOutput, SimulateOrderArgs},
31 shift::{JsShiftSimulationOutput, SimulateShiftArgs},
32 withdrawal::{JsWithdrawalSimulationOutput, SimulateWithdrawalArgs},
33};
34
35#[wasm_bindgen(js_name = Simulator)]
37#[derive(Clone)]
38pub struct JsSimulator {
39 simulator: Simulator,
40 disable_vis: bool,
41}
42
43impl From<Simulator> for JsSimulator {
44 fn from(simulator: Simulator) -> Self {
45 Self {
46 simulator,
47 disable_vis: false,
48 }
49 }
50}
51
52impl Deref for JsSimulator {
53 type Target = Simulator;
54
55 fn deref(&self) -> &Self::Target {
56 &self.simulator
57 }
58}
59
60#[derive(Debug, serde::Serialize, serde::Deserialize, Tsify)]
62#[tsify(into_wasm_abi, from_wasm_abi)]
63pub struct GetGlvStatusArgs {
64 glv_token: StringPubkey,
65}
66
67#[derive(Debug, serde::Serialize, serde::Deserialize, Tsify)]
69#[tsify(into_wasm_abi, from_wasm_abi)]
70pub struct GetGlvTokenValueArgs {
71 glv_token: StringPubkey,
72 amount: u128,
73 maximize: bool,
74}
75
76#[wasm_bindgen(js_class = Simulator)]
77impl JsSimulator {
78 pub fn get_market(&self, market_token: &str) -> crate::Result<Option<JsMarketModel>> {
80 Ok(self
81 .simulator
82 .get_market(&market_token.parse()?)
83 .map(|market| market.clone().into()))
84 }
85
86 pub fn get_price(&self, token: &str) -> crate::Result<Option<Value>> {
88 Ok(self.simulator.get_price(&token.parse()?).map(|p| Value {
89 min: p.min,
90 max: p.max,
91 }))
92 }
93
94 pub fn get_glv(&self, glv_token: &str) -> crate::Result<Option<JsGlvModel>> {
96 Ok(self
97 .simulator
98 .get_glv(&glv_token.parse()?)
99 .map(|glv| glv.clone().into()))
100 }
101
102 pub fn insert_price(&mut self, token: &str, price: Value) -> crate::Result<()> {
104 let token = token.parse()?;
105 let price = Arc::new(Price {
106 min: price.min,
107 max: price.max,
108 });
109 self.simulator.insert_price(&token, price)?;
110 Ok(())
111 }
112
113 pub fn insert_glv(&mut self, glv: &JsGlvModel) -> crate::Result<()> {
115 self.simulator.insert_glv(glv.model.clone());
116 Ok(())
117 }
118
119 pub fn insert_vi(
121 &mut self,
122 vi_address: &str,
123 vi: &JsVirtualInventoryModel,
124 ) -> crate::Result<()> {
125 let vi_address = vi_address.parse()?;
126 self.simulator.insert_vi(vi_address, vi.model.clone());
127 Ok(())
128 }
129
130 pub fn get_vi(&self, vi_address: &str) -> crate::Result<Option<JsVirtualInventoryModel>> {
132 let vi_address = vi_address.parse()?;
133 Ok(self
134 .simulator
135 .get_vi(&vi_address)
136 .map(|vi| vi.clone().into()))
137 }
138
139 pub fn set_disable_vis(&mut self, disable: bool) {
141 self.disable_vis = disable;
142 }
143
144 pub fn disable_vis(&self) -> bool {
146 self.disable_vis
147 }
148
149 pub fn simulate_order(
151 &mut self,
152 args: SimulateOrderArgs,
153 position: Option<JsPosition>,
154 ) -> crate::Result<JsOrderSimulationOutput> {
155 let SimulateOrderArgs {
156 kind,
157 params,
158 collateral_or_swap_out_token,
159 pay_token,
160 receive_token,
161 swap_path,
162 prefer_swap_out_token_update,
163 skip_limit_price_validation,
164 limit_swap_slippage,
165 update_prices_for_limit_order,
166 } = args;
167 let swap_path = convert_swap_path(swap_path.as_deref());
168 let mut simulation = self
169 .simulator
170 .simulate_order(kind, ¶ms, &collateral_or_swap_out_token)
171 .pay_token(pay_token.as_deref())
172 .receive_token(receive_token.as_deref())
173 .position(position.as_ref().map(|p| &p.position))
174 .swap_path(&swap_path)
175 .build();
176
177 if update_prices_for_limit_order.unwrap_or_default() {
178 simulation = simulation.update_prices(UpdatePriceOptions {
179 prefer_swap_in_token_update: !prefer_swap_out_token_update.unwrap_or_default(),
180 limit_swap_slippage,
181 })?;
182 }
183
184 let output = simulation.execute_with_options(SimulationOptions {
185 skip_limit_price_validation: skip_limit_price_validation.unwrap_or_default(),
186 disable_vis: self.disable_vis,
187 })?;
188 Ok(JsOrderSimulationOutput { output })
189 }
190
191 pub fn simulate_deposit(
193 &mut self,
194 args: SimulateDepositArgs,
195 ) -> crate::Result<JsDepositSimulationOutput> {
196 let SimulateDepositArgs { params } = args;
197
198 let long_swap_path = convert_swap_path(params.long_swap_path.as_deref());
199 let short_swap_path = convert_swap_path(params.short_swap_path.as_deref());
200 let market_token = ¶ms.market_token;
201 let long_pay_token = ¶ms.long_pay_token;
202 let short_pay_token = ¶ms.short_pay_token;
203 let params = CreateDepositParams {
204 execution_lamports: 0,
205 long_token_swap_length: long_swap_path.len().try_into()?,
206 short_token_swap_length: short_swap_path.len().try_into()?,
207 initial_long_token_amount: params.long_pay_amount.unwrap_or_default().try_into()?,
208 initial_short_token_amount: params.short_pay_amount.unwrap_or_default().try_into()?,
209 min_market_token_amount: params.min_receive_amount.unwrap_or_default().try_into()?,
210 should_unwrap_native_token: !params.skip_unwrap_native_on_receive.unwrap_or_default(),
211 };
212
213 let output = self
214 .simulator
215 .simulate_deposit(market_token, ¶ms)
216 .long_pay_token(long_pay_token.as_deref())
217 .long_swap_path(&long_swap_path)
218 .short_pay_token(short_pay_token.as_deref())
219 .short_swap_path(&short_swap_path)
220 .build()
221 .execute_with_options(SimulationOptions {
222 skip_limit_price_validation: false,
223 disable_vis: self.disable_vis,
224 })?;
225
226 Ok(JsDepositSimulationOutput { output })
227 }
228
229 pub fn simulate_withdrawal(
231 &mut self,
232 args: SimulateWithdrawalArgs,
233 ) -> crate::Result<JsWithdrawalSimulationOutput> {
234 let SimulateWithdrawalArgs { params } = args;
235
236 let long_swap_path = convert_swap_path(params.long_swap_path.as_deref());
237 let short_swap_path = convert_swap_path(params.short_swap_path.as_deref());
238 let market_token = ¶ms.market_token;
239 let long_receive_token = ¶ms.long_receive_token;
240 let short_receive_token = ¶ms.short_receive_token;
241 let params = CreateWithdrawalParams {
242 execution_lamports: 0,
243 long_token_swap_path_length: long_swap_path.len().try_into()?,
244 short_token_swap_path_length: short_swap_path.len().try_into()?,
245 market_token_amount: params.market_token_amount.unwrap_or_default().try_into()?,
246 min_long_token_amount: params
247 .min_long_receive_amount
248 .unwrap_or_default()
249 .try_into()?,
250 min_short_token_amount: params
251 .min_short_receive_amount
252 .unwrap_or_default()
253 .try_into()?,
254 should_unwrap_native_token: !params.skip_unwrap_native_on_receive.unwrap_or_default(),
255 };
256
257 let output = self
258 .simulator
259 .simulate_withdrawal(market_token, ¶ms)
260 .long_receive_token(long_receive_token.as_deref())
261 .long_swap_path(&long_swap_path)
262 .short_receive_token(short_receive_token.as_deref())
263 .short_swap_path(&short_swap_path)
264 .build()
265 .execute_with_options(SimulationOptions {
266 skip_limit_price_validation: false,
267 disable_vis: self.disable_vis,
268 })?;
269
270 Ok(JsWithdrawalSimulationOutput { output })
271 }
272
273 pub fn simulate_shift(
275 &mut self,
276 args: SimulateShiftArgs,
277 ) -> crate::Result<JsShiftSimulationOutput> {
278 let SimulateShiftArgs { params } = args;
279
280 let from_market_token = ¶ms.from_market_token;
281 let to_market_token = ¶ms.to_market_token;
282 let params = CreateShiftParams {
283 execution_lamports: 0,
284 from_market_token_amount: params
285 .from_market_token_amount
286 .unwrap_or_default()
287 .try_into()?,
288 min_to_market_token_amount: params
289 .min_to_market_token_amount
290 .unwrap_or_default()
291 .try_into()?,
292 };
293
294 let output = self
295 .simulator
296 .simulate_shift(from_market_token, to_market_token, ¶ms)
297 .build()
298 .execute_with_options(SimulationOptions {
299 skip_limit_price_validation: false,
300 disable_vis: self.disable_vis,
301 })?;
302
303 Ok(JsShiftSimulationOutput { output })
304 }
305
306 pub fn simulate_glv_deposit(
308 &mut self,
309 args: SimulateGlvDepositArgs,
310 ) -> crate::Result<JsGlvDepositSimulationOutput> {
311 let SimulateGlvDepositArgs { params } = args;
312
313 let long_swap_path = convert_swap_path(params.long_swap_path.as_deref());
314 let short_swap_path = convert_swap_path(params.short_swap_path.as_deref());
315 let glv_token = ¶ms.glv_token;
316 let market_token = ¶ms.market_token;
317 let long_pay_token = ¶ms.long_pay_token;
318 let short_pay_token = ¶ms.short_pay_token;
319 let params = CreateGlvDepositParams {
320 execution_lamports: 0,
321 long_token_swap_length: long_swap_path.len().try_into()?,
322 short_token_swap_length: short_swap_path.len().try_into()?,
323 initial_long_token_amount: params.long_pay_amount.unwrap_or_default().try_into()?,
324 initial_short_token_amount: params.short_pay_amount.unwrap_or_default().try_into()?,
325 market_token_amount: params.market_token_amount.unwrap_or_default().try_into()?,
326 min_market_token_amount: params
327 .min_market_token_amount
328 .unwrap_or_default()
329 .try_into()?,
330 min_glv_token_amount: params.min_receive_amount.unwrap_or_default().try_into()?,
331 should_unwrap_native_token: !params.skip_unwrap_native_on_receive.unwrap_or_default(),
332 };
333
334 let output = self
335 .simulator
336 .simulate_glv_deposit(glv_token, market_token, ¶ms)
337 .long_pay_token(long_pay_token.as_deref())
338 .long_swap_path(&long_swap_path)
339 .short_pay_token(short_pay_token.as_deref())
340 .short_swap_path(&short_swap_path)
341 .build()
342 .execute_with_options(SimulationOptions {
343 skip_limit_price_validation: false,
344 disable_vis: self.disable_vis,
345 })?;
346
347 Ok(JsGlvDepositSimulationOutput { output })
348 }
349
350 pub fn simulate_glv_withdrawal(
352 &mut self,
353 args: SimulateGlvWithdrawalArgs,
354 ) -> crate::Result<JsGlvWithdrawalSimulationOutput> {
355 let SimulateGlvWithdrawalArgs { params } = args;
356
357 let long_swap_path = convert_swap_path(params.long_swap_path.as_deref());
358 let short_swap_path = convert_swap_path(params.short_swap_path.as_deref());
359 let glv_token = ¶ms.glv_token;
360 let market_token = ¶ms.market_token;
361 let long_receive_token = ¶ms.long_receive_token;
362 let short_receive_token = ¶ms.short_receive_token;
363 let params = CreateGlvWithdrawalParams {
364 execution_lamports: 0,
365 long_token_swap_length: long_swap_path.len().try_into()?,
366 short_token_swap_length: short_swap_path.len().try_into()?,
367 glv_token_amount: params.glv_token_amount.unwrap_or_default().try_into()?,
368 min_final_long_token_amount: params
369 .min_long_receive_amount
370 .unwrap_or_default()
371 .try_into()?,
372 min_final_short_token_amount: params
373 .min_short_receive_amount
374 .unwrap_or_default()
375 .try_into()?,
376 should_unwrap_native_token: !params.skip_unwrap_native_on_receive.unwrap_or_default(),
377 };
378
379 let output = self
380 .simulator
381 .simulate_glv_withdrawal(glv_token, market_token, ¶ms)
382 .long_receive_token(long_receive_token.as_deref())
383 .long_swap_path(&long_swap_path)
384 .short_receive_token(short_receive_token.as_deref())
385 .short_swap_path(&short_swap_path)
386 .build()
387 .execute_with_options(SimulationOptions {
388 skip_limit_price_validation: false,
389 disable_vis: self.disable_vis,
390 })?;
391
392 Ok(JsGlvWithdrawalSimulationOutput { output })
393 }
394
395 #[wasm_bindgen(js_name = clone)]
397 pub fn js_clone(&self) -> Self {
398 self.clone()
399 }
400
401 pub fn get_glv_status(&self, args: GetGlvStatusArgs) -> crate::Result<GlvStatus> {
403 self.simulator.get_glv_status(&args.glv_token)
404 }
405
406 pub fn get_glv_token_value(&self, args: GetGlvTokenValueArgs) -> crate::Result<u128> {
408 self.simulator
409 .get_glv_token_value(&args.glv_token, args.amount.try_into()?, args.maximize)
410 }
411}
412
413fn convert_swap_path(swap_path: Option<&[StringPubkey]>) -> Vec<Pubkey> {
414 swap_path
415 .map(|path| path.iter().map(|p| **p).collect::<Vec<_>>())
416 .unwrap_or_default()
417}