Skip to main content

gmsol_sdk/js/simulation/
simulator.rs

1//! JS binding for [`Simulator`].
2
3use 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/// A JS binding for [`Simulator`].
36#[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/// Arguments for GLV status calculations.
61#[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/// Arguments for GLV status calculations.
68#[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    /// Get market by its market token.
79    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    /// Get price for the given token.
87    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    /// Get GLV by its GLV token.
95    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    /// Upsert the prices for the given token.
103    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    /// Upsert a GLV model.
114    pub fn insert_glv(&mut self, glv: &JsGlvModel) -> crate::Result<()> {
115        self.simulator.insert_glv(glv.model.clone());
116        Ok(())
117    }
118
119    /// Insert a virtual inventory model.
120    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    /// Get virtual inventory model by address.
131    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    /// Set whether to disable virtual inventories for simulations.
140    pub fn set_disable_vis(&mut self, disable: bool) {
141        self.disable_vis = disable;
142    }
143
144    /// Get whether virtual inventories are disabled.
145    pub fn disable_vis(&self) -> bool {
146        self.disable_vis
147    }
148
149    /// Simulate an order execution.
150    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, &params, &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    /// Simulate a deposit execution.
192    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 = &params.market_token;
201        let long_pay_token = &params.long_pay_token;
202        let short_pay_token = &params.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, &params)
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    /// Simulate a withdrawal execution.
230    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 = &params.market_token;
239        let long_receive_token = &params.long_receive_token;
240        let short_receive_token = &params.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, &params)
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    /// Simulate a shift execution.
274    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 = &params.from_market_token;
281        let to_market_token = &params.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, &params)
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    /// Simulate a GLV deposit execution.
307    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 = &params.glv_token;
316        let market_token = &params.market_token;
317        let long_pay_token = &params.long_pay_token;
318        let short_pay_token = &params.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, &params)
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    /// Simulate a GLV withdrawal execution.
351    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 = &params.glv_token;
360        let market_token = &params.market_token;
361        let long_receive_token = &params.long_receive_token;
362        let short_receive_token = &params.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, &params)
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    /// Create a clone of this simulator.
396    #[wasm_bindgen(js_name = clone)]
397    pub fn js_clone(&self) -> Self {
398        self.clone()
399    }
400
401    /// Calculates GLV status.
402    pub fn get_glv_status(&self, args: GetGlvStatusArgs) -> crate::Result<GlvStatus> {
403        self.simulator.get_glv_status(&args.glv_token)
404    }
405
406    /// Calculates GLV token value.
407    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}