@paraswap/dex-lib 4.8.36 → 4.8.37-native-dex-math.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/build/dex/aave-gsm/config.js +2 -2
  2. package/build/dex/idex.d.ts +1 -1
  3. package/build/dex/pancakeswap-v3/pancakeswap-v3-pool.d.ts +2 -0
  4. package/build/dex/pancakeswap-v3/pancakeswap-v3-pool.js +5 -0
  5. package/build/dex/pancakeswap-v3/pancakeswap-v3-pool.js.map +1 -1
  6. package/build/dex/pancakeswap-v3/pancakeswap-v3.d.ts +6 -3
  7. package/build/dex/pancakeswap-v3/pancakeswap-v3.js +85 -9
  8. package/build/dex/pancakeswap-v3/pancakeswap-v3.js.map +1 -1
  9. package/build/dex/solidly-v3/solidly-v3-pool.d.ts +3 -0
  10. package/build/dex/solidly-v3/solidly-v3-pool.js +8 -0
  11. package/build/dex/solidly-v3/solidly-v3-pool.js.map +1 -1
  12. package/build/dex/solidly-v3/solidly-v3.d.ts +5 -2
  13. package/build/dex/solidly-v3/solidly-v3.js +84 -8
  14. package/build/dex/solidly-v3/solidly-v3.js.map +1 -1
  15. package/build/dex/uniswap-v3/scripts/measure-calc-time.js +222 -110
  16. package/build/dex/uniswap-v3/scripts/measure-calc-time.js.map +1 -1
  17. package/build/dex/uniswap-v3/types.d.ts +1 -0
  18. package/build/dex/uniswap-v3/types.js.map +1 -1
  19. package/build/dex/uniswap-v3/uniswap-v3-pool.d.ts +2 -0
  20. package/build/dex/uniswap-v3/uniswap-v3-pool.js +5 -0
  21. package/build/dex/uniswap-v3/uniswap-v3-pool.js.map +1 -1
  22. package/build/dex/uniswap-v3/uniswap-v3.d.ts +11 -3
  23. package/build/dex/uniswap-v3/uniswap-v3.js +86 -9
  24. package/build/dex/uniswap-v3/uniswap-v3.js.map +1 -1
  25. package/build/dex/uniswap-v4/types.d.ts +1 -0
  26. package/build/dex/uniswap-v4/uniswap-v4-pool-manager.d.ts +2 -0
  27. package/build/dex/uniswap-v4/uniswap-v4-pool-manager.js +3 -0
  28. package/build/dex/uniswap-v4/uniswap-v4-pool-manager.js.map +1 -1
  29. package/build/dex/uniswap-v4/uniswap-v4-pool.d.ts +3 -0
  30. package/build/dex/uniswap-v4/uniswap-v4-pool.js +18 -0
  31. package/build/dex/uniswap-v4/uniswap-v4-pool.js.map +1 -1
  32. package/build/dex/uniswap-v4/uniswap-v4.d.ts +3 -1
  33. package/build/dex/uniswap-v4/uniswap-v4.js +74 -14
  34. package/build/dex/uniswap-v4/uniswap-v4.js.map +1 -1
  35. package/build/pricing-helper.d.ts +1 -1
  36. package/build/pricing-helper.js +2 -2
  37. package/build/pricing-helper.js.map +1 -1
  38. package/native/Cargo.lock +331 -0
  39. package/native/Cargo.toml +22 -0
  40. package/native/build.rs +5 -0
  41. package/native/package-lock.json +32 -0
  42. package/native/package.json +20 -0
  43. package/native/src/config.rs +73 -0
  44. package/native/src/lib.rs +528 -0
  45. package/native/src/math/bit_math.rs +177 -0
  46. package/native/src/math/full_math.rs +217 -0
  47. package/native/src/math/liquidity_math.rs +72 -0
  48. package/native/src/math/mod.rs +10 -0
  49. package/native/src/math/oracle.rs +493 -0
  50. package/native/src/math/sqrt_price_math.rs +272 -0
  51. package/native/src/math/swap_math.rs +306 -0
  52. package/native/src/math/tick.rs +239 -0
  53. package/native/src/math/tick_bitmap.rs +312 -0
  54. package/native/src/math/tick_math.rs +321 -0
  55. package/native/src/math/unsafe_math.rs +67 -0
  56. package/native/src/pool_state.rs +41 -0
  57. package/native/src/query_outputs.rs +379 -0
  58. package/native/src/v4_query_outputs.rs +255 -0
  59. package/package.json +1 -1
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@paraswap/v3-math-native",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "@paraswap/v3-math-native",
9
+ "version": "0.1.0",
10
+ "devDependencies": {
11
+ "@napi-rs/cli": "^2.18.0"
12
+ }
13
+ },
14
+ "node_modules/@napi-rs/cli": {
15
+ "version": "2.18.4",
16
+ "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.18.4.tgz",
17
+ "integrity": "sha512-SgJeA4df9DE2iAEpr3M2H0OKl/yjtg1BnRI5/JyowS71tUWhrfSu2LT0V3vlHET+g1hBVlrO60PmEXwUEKp8Mg==",
18
+ "dev": true,
19
+ "license": "MIT",
20
+ "bin": {
21
+ "napi": "scripts/index.js"
22
+ },
23
+ "engines": {
24
+ "node": ">= 10"
25
+ },
26
+ "funding": {
27
+ "type": "github",
28
+ "url": "https://github.com/sponsors/Brooooooklyn"
29
+ }
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@paraswap/v3-math-native",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "napi": {
8
+ "name": "v3-math-native",
9
+ "triples": {
10
+ "defaults": true
11
+ }
12
+ },
13
+ "scripts": {
14
+ "build": "napi build --platform --release",
15
+ "build:debug": "napi build --platform"
16
+ },
17
+ "devDependencies": {
18
+ "@napi-rs/cli": "^2.18.0"
19
+ }
20
+ }
@@ -0,0 +1,73 @@
1
+ /// Configures math variant differences between Uniswap V3 forks.
2
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
3
+ pub enum MathVariant {
4
+ /// Standard Uniswap V3: feeProtocol is 4-bit (% 16 / >> 4)
5
+ UniswapV3,
6
+ /// PancakeSwap V3: feeProtocol is 16-bit (% 65536 / >> 16), delta = feeAmount * fp / 10000
7
+ PancakeSwapV3,
8
+ /// Solidly V3: No oracle, no feeProtocol, fee from slot0.fee
9
+ SolidlyV3,
10
+ }
11
+
12
+ impl MathVariant {
13
+ pub fn from_str(s: &str) -> Self {
14
+ match s {
15
+ "pancakeswap_v3" => MathVariant::PancakeSwapV3,
16
+ "solidly_v3" => MathVariant::SolidlyV3,
17
+ _ => MathVariant::UniswapV3,
18
+ }
19
+ }
20
+
21
+ /// Extract the fee protocol value for the given swap direction.
22
+ pub fn fee_protocol(
23
+ &self,
24
+ fee_protocol_raw: ethnum::U256,
25
+ zero_for_one: bool,
26
+ ) -> ethnum::U256 {
27
+ match self {
28
+ MathVariant::SolidlyV3 => ethnum::U256::ZERO, // no protocol fee
29
+ MathVariant::UniswapV3 => {
30
+ if zero_for_one {
31
+ fee_protocol_raw % ethnum::U256::from(16u32)
32
+ } else {
33
+ fee_protocol_raw >> 4
34
+ }
35
+ }
36
+ MathVariant::PancakeSwapV3 => {
37
+ if zero_for_one {
38
+ fee_protocol_raw % ethnum::U256::from(65536u32)
39
+ } else {
40
+ fee_protocol_raw >> 16
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+ /// Calculate protocol fee delta from fee amount.
47
+ /// V3: delta = feeAmount / feeProtocol
48
+ /// PancakeSwap: delta = (feeAmount * feeProtocol) / 10000
49
+ pub fn protocol_fee_delta(
50
+ &self,
51
+ fee_amount: ethnum::U256,
52
+ fee_protocol: ethnum::U256,
53
+ ) -> ethnum::U256 {
54
+ match self {
55
+ MathVariant::SolidlyV3 => ethnum::U256::ZERO,
56
+ MathVariant::UniswapV3 => fee_amount / fee_protocol,
57
+ MathVariant::PancakeSwapV3 => {
58
+ (fee_amount * fee_protocol) / ethnum::U256::from(10000u32)
59
+ }
60
+ }
61
+ }
62
+
63
+ /// Whether this variant uses oracle observations.
64
+ pub fn has_oracle(&self) -> bool {
65
+ !matches!(self, MathVariant::SolidlyV3)
66
+ }
67
+
68
+ /// PancakeSwap V3 and Solidly V3 zero out remaining amounts for BOTH sell and buy at end of cycles.
69
+ /// V3 only zeros for BUY.
70
+ pub fn zero_remaining_for_sell(&self) -> bool {
71
+ matches!(self, MathVariant::SolidlyV3 | MathVariant::PancakeSwapV3)
72
+ }
73
+ }
@@ -0,0 +1,528 @@
1
+ pub mod config;
2
+ pub mod math;
3
+ pub mod pool_state;
4
+ pub mod query_outputs;
5
+ pub mod v4_query_outputs;
6
+
7
+ use ethnum::{I256, U256};
8
+ use napi::bindgen_prelude::*;
9
+ use napi_derive::napi;
10
+ use rayon::prelude::*;
11
+ use std::collections::HashMap;
12
+
13
+ /// Set the number of threads rayon uses for parallel queries.
14
+ /// Call once at startup. Defaults to all available cores if not called.
15
+ #[napi]
16
+ pub fn set_thread_count(n: u32) {
17
+ rayon::ThreadPoolBuilder::new()
18
+ .num_threads(n as usize)
19
+ .build_global()
20
+ .ok();
21
+ }
22
+
23
+ use config::MathVariant;
24
+ use math::oracle::OracleObservation;
25
+ use math::tick::TickInfo;
26
+ use pool_state::PoolState;
27
+
28
+ // ---- NAPI type definitions for JS interop ----
29
+
30
+ #[napi(object)]
31
+ pub struct JsTickEntry {
32
+ pub key: i32,
33
+ pub liquidity_gross: BigInt,
34
+ pub liquidity_net: BigInt,
35
+ }
36
+
37
+ #[napi(object)]
38
+ pub struct JsBitmapEntry {
39
+ pub key: i32,
40
+ pub value: BigInt,
41
+ }
42
+
43
+ #[napi(object)]
44
+ pub struct JsObservationEntry {
45
+ pub key: i32,
46
+ pub block_timestamp: BigInt,
47
+ pub tick_cumulative: BigInt,
48
+ pub seconds_per_liquidity_cumulative_x128: BigInt,
49
+ pub initialized: bool,
50
+ }
51
+
52
+ #[napi(object)]
53
+ pub struct JsPoolStateInit {
54
+ pub variant: String,
55
+ pub bitmap_range: i32,
56
+ pub block_timestamp: BigInt,
57
+ pub tick_spacing: BigInt,
58
+ pub fee: BigInt,
59
+ pub sqrt_price_x96: BigInt,
60
+ pub tick: BigInt,
61
+ pub observation_index: i32,
62
+ pub observation_cardinality: i32,
63
+ pub observation_cardinality_next: i32,
64
+ pub fee_protocol: BigInt,
65
+ pub liquidity: BigInt,
66
+ pub max_liquidity_per_tick: BigInt,
67
+ pub start_tick_bitmap: BigInt,
68
+ pub lowest_known_tick: BigInt,
69
+ pub highest_known_tick: BigInt,
70
+ pub tick_bitmap: Vec<JsBitmapEntry>,
71
+ pub ticks: Vec<JsTickEntry>,
72
+ pub observations: Vec<JsObservationEntry>,
73
+ }
74
+
75
+ #[napi(object)]
76
+ pub struct JsOutputResult {
77
+ pub outputs: Vec<BigInt>,
78
+ pub tick_counts: Vec<i32>,
79
+ }
80
+
81
+ // ---- Conversion helpers ----
82
+ // napi::bindgen_prelude::BigInt stores { sign_bit: bool, words: Vec<u64> }
83
+ // words are little-endian u64 limbs.
84
+
85
+ fn bigint_to_u256(bi: &BigInt) -> U256 {
86
+ let words = &bi.words;
87
+ let low = words.first().copied().unwrap_or(0) as u128
88
+ | (words.get(1).copied().unwrap_or(0) as u128) << 64;
89
+ let high = words.get(2).copied().unwrap_or(0) as u128
90
+ | (words.get(3).copied().unwrap_or(0) as u128) << 64;
91
+ U256::from_words(high, low)
92
+ }
93
+
94
+ fn bigint_to_i256(bi: &BigInt) -> I256 {
95
+ let u = bigint_to_u256(bi);
96
+ let val = u.as_i256();
97
+ if bi.sign_bit {
98
+ -val
99
+ } else {
100
+ val
101
+ }
102
+ }
103
+
104
+ fn u256_to_bigint(val: U256) -> BigInt {
105
+ let (high, low) = val.into_words();
106
+ let mut words = vec![
107
+ low as u64,
108
+ (low >> 64) as u64,
109
+ high as u64,
110
+ (high >> 64) as u64,
111
+ ];
112
+ // Trim trailing zeros for cleaner representation
113
+ while words.len() > 1 && *words.last().unwrap() == 0 {
114
+ words.pop();
115
+ }
116
+ BigInt {
117
+ sign_bit: false,
118
+ words,
119
+ }
120
+ }
121
+
122
+ // ---- The main NAPI class ----
123
+
124
+ #[napi]
125
+ pub struct RustPoolHandle {
126
+ state: PoolState,
127
+ }
128
+
129
+ #[napi]
130
+ impl RustPoolHandle {
131
+ /// Create a new Rust-owned pool state from JS data.
132
+ #[napi(factory)]
133
+ pub fn create(init: JsPoolStateInit) -> Result<Self> {
134
+ let variant = MathVariant::from_str(&init.variant);
135
+
136
+ let mut tick_bitmap = HashMap::with_capacity(init.tick_bitmap.len());
137
+ for entry in &init.tick_bitmap {
138
+ tick_bitmap.insert(entry.key as i16, bigint_to_u256(&entry.value));
139
+ }
140
+
141
+ let mut ticks = HashMap::with_capacity(init.ticks.len());
142
+ for entry in &init.ticks {
143
+ ticks.insert(
144
+ entry.key,
145
+ TickInfo {
146
+ liquidity_gross: bigint_to_u256(&entry.liquidity_gross),
147
+ liquidity_net: bigint_to_i256(&entry.liquidity_net),
148
+ initialized: true,
149
+ },
150
+ );
151
+ }
152
+
153
+ let mut observations = HashMap::with_capacity(init.observations.len());
154
+ for entry in &init.observations {
155
+ observations.insert(
156
+ entry.key as u16,
157
+ OracleObservation {
158
+ block_timestamp: bigint_to_u256(&entry.block_timestamp),
159
+ tick_cumulative: bigint_to_i256(&entry.tick_cumulative),
160
+ seconds_per_liquidity_cumulative_x128: bigint_to_u256(
161
+ &entry.seconds_per_liquidity_cumulative_x128,
162
+ ),
163
+ initialized: entry.initialized,
164
+ },
165
+ );
166
+ }
167
+
168
+ let start_tick_bitmap = bigint_to_i256(&init.start_tick_bitmap);
169
+
170
+ // bitmap_range is the total half-width (TICK_BITMAP_BUFFER + TICK_BITMAP_TO_USE)
171
+ // passed from TS since it varies per DEX and per network.
172
+ let start_i16 = start_tick_bitmap.as_i32() as i16;
173
+ let range = init.bitmap_range as i16;
174
+ let bitmap_range_lower = start_i16 - range;
175
+ let bitmap_range_upper = start_i16 + range;
176
+
177
+ Ok(Self {
178
+ state: PoolState {
179
+ block_timestamp: bigint_to_u256(&init.block_timestamp),
180
+ tick_spacing: bigint_to_i256(&init.tick_spacing),
181
+ fee: bigint_to_u256(&init.fee),
182
+ sqrt_price_x96: bigint_to_u256(&init.sqrt_price_x96),
183
+ tick: bigint_to_i256(&init.tick),
184
+ observation_index: init.observation_index as u16,
185
+ observation_cardinality: init.observation_cardinality as u16,
186
+ observation_cardinality_next: init.observation_cardinality_next as u16,
187
+ fee_protocol: bigint_to_u256(&init.fee_protocol),
188
+ liquidity: bigint_to_u256(&init.liquidity),
189
+ max_liquidity_per_tick: bigint_to_u256(&init.max_liquidity_per_tick),
190
+ tick_bitmap,
191
+ ticks,
192
+ observations,
193
+ start_tick_bitmap,
194
+ lowest_known_tick: bigint_to_i256(&init.lowest_known_tick),
195
+ highest_known_tick: bigint_to_i256(&init.highest_known_tick),
196
+ bitmap_range_lower,
197
+ bitmap_range_upper,
198
+ variant,
199
+ },
200
+ })
201
+ }
202
+
203
+ /// HOT PATH: Price N amounts in one call (BigInt version).
204
+ /// side: 0 = SELL, 1 = BUY
205
+ #[napi]
206
+ pub fn query_outputs(
207
+ &self,
208
+ amounts: Vec<BigInt>,
209
+ zero_for_one: bool,
210
+ side: u8,
211
+ ) -> Result<JsOutputResult> {
212
+ let amounts_u256: Vec<U256> = amounts.iter().map(|a| bigint_to_u256(a)).collect();
213
+
214
+ let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
215
+ query_outputs::query_outputs(&self.state, &amounts_u256, zero_for_one, side)
216
+ }));
217
+
218
+ match result {
219
+ Ok(output) => {
220
+ let outputs: Vec<BigInt> =
221
+ output.outputs.iter().map(|v| u256_to_bigint(*v)).collect();
222
+
223
+ Ok(JsOutputResult {
224
+ outputs,
225
+ tick_counts: output.tick_counts,
226
+ })
227
+ }
228
+ Err(panic_info) => {
229
+ let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
230
+ s.to_string()
231
+ } else if let Some(s) = panic_info.downcast_ref::<String>() {
232
+ s.clone()
233
+ } else {
234
+ "Unknown panic in query_outputs".to_string()
235
+ };
236
+ Err(Error::new(Status::GenericFailure, msg))
237
+ }
238
+ }
239
+ }
240
+
241
+ }
242
+
243
+ // ---- Pool Registry: batch parallel queries ----
244
+
245
+ fn build_pool_state(init: &JsPoolStateInit) -> Result<PoolState> {
246
+ let variant = MathVariant::from_str(&init.variant);
247
+
248
+ let mut tick_bitmap = HashMap::with_capacity(init.tick_bitmap.len());
249
+ for entry in &init.tick_bitmap {
250
+ tick_bitmap.insert(entry.key as i16, bigint_to_u256(&entry.value));
251
+ }
252
+
253
+ let mut ticks = HashMap::with_capacity(init.ticks.len());
254
+ for entry in &init.ticks {
255
+ ticks.insert(
256
+ entry.key,
257
+ TickInfo {
258
+ liquidity_gross: bigint_to_u256(&entry.liquidity_gross),
259
+ liquidity_net: bigint_to_i256(&entry.liquidity_net),
260
+ initialized: true,
261
+ },
262
+ );
263
+ }
264
+
265
+ let mut observations = HashMap::with_capacity(init.observations.len());
266
+ for entry in &init.observations {
267
+ observations.insert(
268
+ entry.key as u16,
269
+ OracleObservation {
270
+ block_timestamp: bigint_to_u256(&entry.block_timestamp),
271
+ tick_cumulative: bigint_to_i256(&entry.tick_cumulative),
272
+ seconds_per_liquidity_cumulative_x128: bigint_to_u256(
273
+ &entry.seconds_per_liquidity_cumulative_x128,
274
+ ),
275
+ initialized: entry.initialized,
276
+ },
277
+ );
278
+ }
279
+
280
+ let start_tick_bitmap = bigint_to_i256(&init.start_tick_bitmap);
281
+ let start_i16 = start_tick_bitmap.as_i32() as i16;
282
+ let range = init.bitmap_range as i16;
283
+
284
+ Ok(PoolState {
285
+ block_timestamp: bigint_to_u256(&init.block_timestamp),
286
+ tick_spacing: bigint_to_i256(&init.tick_spacing),
287
+ fee: bigint_to_u256(&init.fee),
288
+ sqrt_price_x96: bigint_to_u256(&init.sqrt_price_x96),
289
+ tick: bigint_to_i256(&init.tick),
290
+ observation_index: init.observation_index as u16,
291
+ observation_cardinality: init.observation_cardinality as u16,
292
+ observation_cardinality_next: init.observation_cardinality_next as u16,
293
+ fee_protocol: bigint_to_u256(&init.fee_protocol),
294
+ liquidity: bigint_to_u256(&init.liquidity),
295
+ max_liquidity_per_tick: bigint_to_u256(&init.max_liquidity_per_tick),
296
+ tick_bitmap,
297
+ ticks,
298
+ observations,
299
+ start_tick_bitmap,
300
+ lowest_known_tick: bigint_to_i256(&init.lowest_known_tick),
301
+ highest_known_tick: bigint_to_i256(&init.highest_known_tick),
302
+ bitmap_range_lower: start_i16 - range,
303
+ bitmap_range_upper: start_i16 + range,
304
+ variant,
305
+ })
306
+ }
307
+
308
+ #[napi(object)]
309
+ pub struct JsPoolQueryResult {
310
+ pub key: String,
311
+ pub outputs: Vec<BigInt>,
312
+ pub tick_counts: Vec<i32>,
313
+ }
314
+
315
+ #[napi]
316
+ pub struct RustPoolRegistry {
317
+ pools: HashMap<String, PoolState>,
318
+ }
319
+
320
+ #[napi]
321
+ impl RustPoolRegistry {
322
+ #[napi(constructor)]
323
+ pub fn new() -> Self {
324
+ Self {
325
+ pools: HashMap::new(),
326
+ }
327
+ }
328
+
329
+ /// Register or update a pool.
330
+ #[napi]
331
+ pub fn set_pool(&mut self, key: String, init: JsPoolStateInit) -> Result<()> {
332
+ let state = build_pool_state(&init)?;
333
+ self.pools.insert(key, state);
334
+ Ok(())
335
+ }
336
+
337
+ /// Remove a pool.
338
+ #[napi]
339
+ pub fn remove_pool(&mut self, key: String) {
340
+ self.pools.remove(&key);
341
+ }
342
+
343
+ /// Query multiple pools in parallel. Returns results for all registered pools
344
+ /// whose keys are in the provided list.
345
+ /// Each pool is queried with the SAME amounts and direction.
346
+ #[napi]
347
+ pub fn query_many(
348
+ &self,
349
+ keys: Vec<String>,
350
+ amounts: Vec<BigInt>,
351
+ zero_for_one: bool,
352
+ side: u8,
353
+ ) -> Result<Vec<JsPoolQueryResult>> {
354
+ let amounts_u256: Vec<U256> = amounts.iter().map(|a| bigint_to_u256(a)).collect();
355
+
356
+ // Collect references to pools that exist
357
+ let pool_refs: Vec<(&str, &PoolState)> = keys
358
+ .iter()
359
+ .filter_map(|k| self.pools.get(k).map(|p| (k.as_str(), p)))
360
+ .collect();
361
+
362
+ // Run all pool queries in parallel using rayon
363
+ let results: Vec<Result<JsPoolQueryResult>> = pool_refs
364
+ .par_iter()
365
+ .map(|(key, pool)| {
366
+ let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
367
+ query_outputs::query_outputs(pool, &amounts_u256, zero_for_one, side)
368
+ }));
369
+
370
+ match result {
371
+ Ok(output) => {
372
+ let outputs: Vec<BigInt> =
373
+ output.outputs.iter().map(|v| u256_to_bigint(*v)).collect();
374
+ Ok(JsPoolQueryResult {
375
+ key: key.to_string(),
376
+ outputs,
377
+ tick_counts: output.tick_counts,
378
+ })
379
+ }
380
+ Err(_) => Ok(JsPoolQueryResult {
381
+ key: key.to_string(),
382
+ outputs: vec![],
383
+ tick_counts: vec![],
384
+ }),
385
+ }
386
+ })
387
+ .collect();
388
+
389
+ results.into_iter().collect()
390
+ }
391
+
392
+ #[napi]
393
+ pub fn pool_count(&self) -> u32 {
394
+ self.pools.len() as u32
395
+ }
396
+ }
397
+
398
+ // ---- V4 Pool Registry ----
399
+
400
+ #[napi(object)]
401
+ pub struct JsV4PoolStateInit {
402
+ pub sqrt_price_x96: BigInt,
403
+ pub tick: BigInt,
404
+ pub protocol_fee: BigInt,
405
+ pub lp_fee: BigInt,
406
+ pub liquidity: BigInt,
407
+ pub tick_spacing: BigInt,
408
+ pub fee_growth_global0_x128: BigInt,
409
+ pub fee_growth_global1_x128: BigInt,
410
+ pub bitmap_range: i32,
411
+ pub start_tick_bitmap: BigInt,
412
+ pub tick_bitmap: Vec<JsBitmapEntry>,
413
+ pub ticks: Vec<JsTickEntry>,
414
+ }
415
+
416
+ #[napi(object)]
417
+ pub struct JsV4QueryResult {
418
+ pub key: String,
419
+ pub outputs: Vec<BigInt>,
420
+ }
421
+
422
+ fn build_v4_pool_state(init: &JsV4PoolStateInit) -> v4_query_outputs::V4PoolState {
423
+ let mut tick_bitmap = HashMap::with_capacity(init.tick_bitmap.len());
424
+ for entry in &init.tick_bitmap {
425
+ tick_bitmap.insert(entry.key as i16, bigint_to_u256(&entry.value));
426
+ }
427
+
428
+ let mut ticks = HashMap::with_capacity(init.ticks.len());
429
+ for entry in &init.ticks {
430
+ ticks.insert(
431
+ entry.key,
432
+ TickInfo {
433
+ liquidity_gross: bigint_to_u256(&entry.liquidity_gross),
434
+ liquidity_net: bigint_to_i256(&entry.liquidity_net),
435
+ initialized: true,
436
+ },
437
+ );
438
+ }
439
+
440
+ v4_query_outputs::V4PoolState {
441
+ sqrt_price_x96: bigint_to_u256(&init.sqrt_price_x96),
442
+ tick: bigint_to_i256(&init.tick),
443
+ protocol_fee: bigint_to_u256(&init.protocol_fee),
444
+ lp_fee: bigint_to_u256(&init.lp_fee),
445
+ liquidity: bigint_to_u256(&init.liquidity),
446
+ tick_spacing: bigint_to_i256(&init.tick_spacing),
447
+ fee_growth_global0_x128: bigint_to_u256(&init.fee_growth_global0_x128),
448
+ fee_growth_global1_x128: bigint_to_u256(&init.fee_growth_global1_x128),
449
+ tick_bitmap,
450
+ ticks,
451
+ }
452
+ }
453
+
454
+ #[napi]
455
+ pub struct RustV4PoolRegistry {
456
+ pools: HashMap<String, (v4_query_outputs::V4PoolState, I256)>, // state + tickSpacing
457
+ }
458
+
459
+ #[napi]
460
+ impl RustV4PoolRegistry {
461
+ #[napi(constructor)]
462
+ pub fn new() -> Self {
463
+ Self {
464
+ pools: HashMap::new(),
465
+ }
466
+ }
467
+
468
+ #[napi]
469
+ pub fn set_pool(&mut self, key: String, init: JsV4PoolStateInit) {
470
+ let tick_spacing = bigint_to_i256(&init.tick_spacing);
471
+ let state = build_v4_pool_state(&init);
472
+ self.pools.insert(key, (state, tick_spacing));
473
+ }
474
+
475
+ #[napi]
476
+ pub fn remove_pool(&mut self, key: String) {
477
+ self.pools.remove(&key);
478
+ }
479
+
480
+ #[napi]
481
+ pub fn query_many(
482
+ &self,
483
+ keys: Vec<String>,
484
+ amounts: Vec<BigInt>,
485
+ zero_for_one: bool,
486
+ side: u8,
487
+ ) -> Vec<JsV4QueryResult> {
488
+ let amounts_u256: Vec<U256> = amounts.iter().map(|a| bigint_to_u256(a)).collect();
489
+
490
+ let pool_refs: Vec<(&str, &v4_query_outputs::V4PoolState, I256)> = keys
491
+ .iter()
492
+ .filter_map(|k| {
493
+ self.pools
494
+ .get(k)
495
+ .map(|(p, ts)| (k.as_str(), p, *ts))
496
+ })
497
+ .collect();
498
+
499
+ pool_refs
500
+ .par_iter()
501
+ .map(|(key, pool, tick_spacing)| {
502
+ let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
503
+ v4_query_outputs::query_outputs(pool, *tick_spacing, &amounts_u256, zero_for_one, side)
504
+ }));
505
+
506
+ match result {
507
+ Ok(outputs) => {
508
+ let bigint_outputs: Vec<BigInt> =
509
+ outputs.iter().map(|v| u256_to_bigint(*v)).collect();
510
+ JsV4QueryResult {
511
+ key: key.to_string(),
512
+ outputs: bigint_outputs,
513
+ }
514
+ }
515
+ Err(_) => JsV4QueryResult {
516
+ key: key.to_string(),
517
+ outputs: vec![],
518
+ },
519
+ }
520
+ })
521
+ .collect()
522
+ }
523
+
524
+ #[napi]
525
+ pub fn pool_count(&self) -> u32 {
526
+ self.pools.len() as u32
527
+ }
528
+ }