@hawksightco/hawk-sdk 1.3.169 → 1.3.171

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 (37) hide show
  1. package/dist/src/addresses.d.ts +1 -0
  2. package/dist/src/addresses.d.ts.map +1 -1
  3. package/dist/src/addresses.js +2 -1
  4. package/dist/src/classes/Transactions.d.ts +72 -1
  5. package/dist/src/classes/Transactions.d.ts.map +1 -1
  6. package/dist/src/classes/Transactions.js +560 -0
  7. package/dist/src/classes/TxGenerator.d.ts +72 -1
  8. package/dist/src/classes/TxGenerator.d.ts.map +1 -1
  9. package/dist/src/classes/TxGenerator.js +332 -0
  10. package/dist/src/functions.d.ts +18 -1
  11. package/dist/src/functions.d.ts.map +1 -1
  12. package/dist/src/functions.js +148 -0
  13. package/dist/src/idl/jupiter-idl.d.ts +36 -0
  14. package/dist/src/idl/jupiter-idl.d.ts.map +1 -1
  15. package/dist/src/idl/jupiter-idl.js +36 -0
  16. package/dist/src/ixGenerator/IyfMainIxGenerator.d.ts +6 -0
  17. package/dist/src/ixGenerator/IyfMainIxGenerator.d.ts.map +1 -1
  18. package/dist/src/ixGenerator/IyfMainIxGenerator.js +22 -0
  19. package/dist/src/ixGenerator/MeteoraDlmmIxGenerator.d.ts +167 -1
  20. package/dist/src/ixGenerator/MeteoraDlmmIxGenerator.d.ts.map +1 -1
  21. package/dist/src/ixGenerator/MeteoraDlmmIxGenerator.js +517 -0
  22. package/dist/src/meteora/index.d.ts +2 -0
  23. package/dist/src/meteora/index.d.ts.map +1 -0
  24. package/dist/src/meteora/index.js +17 -0
  25. package/dist/src/meteora/liquidityStrategy.d.ts +268 -0
  26. package/dist/src/meteora/liquidityStrategy.d.ts.map +1 -0
  27. package/dist/src/meteora/liquidityStrategy.js +1069 -0
  28. package/dist/src/meteora.d.ts +1 -0
  29. package/dist/src/meteora.d.ts.map +1 -1
  30. package/dist/src/meteora.js +6 -2
  31. package/dist/src/types.d.ts +139 -0
  32. package/dist/src/types.d.ts.map +1 -1
  33. package/dist/src/types.js +16 -1
  34. package/package.json +7 -3
  35. package/test/artifacts/temp/.gitignore +2 -0
  36. package/test/artifacts/temp/accounts/.gitignore +2 -0
  37. package/test/visualization/output/.gitignore +2 -0
@@ -0,0 +1,1069 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.StrategyType = void 0;
7
+ exports.getQPriceFromId = getQPriceFromId;
8
+ exports.buildSpotStrategyParameters = buildSpotStrategyParameters;
9
+ exports.resetUninvolvedLiquidityParams = resetUninvolvedLiquidityParams;
10
+ exports.getAmountXForBin = getAmountXForBin;
11
+ exports.getAmountYForBin = getAmountYForBin;
12
+ exports.calculateChunkAmounts = calculateChunkAmounts;
13
+ exports.buildCurveStrategyParameters = buildCurveStrategyParameters;
14
+ exports.buildBidAskStrategyParameters = buildBidAskStrategyParameters;
15
+ exports.buildStrategyParameters = buildStrategyParameters;
16
+ exports.chunkBinRange = chunkBinRange;
17
+ exports.buildBitFlagAndNegateStrategyParameters = buildBitFlagAndNegateStrategyParameters;
18
+ exports.toAmountIntoBins = toAmountIntoBins;
19
+ exports.chunkDepositParameters = chunkDepositParameters;
20
+ const bn_js_1 = __importDefault(require("bn.js"));
21
+ /**
22
+ * Liquidity strategy types matching Meteora DLMM SDK.
23
+ */
24
+ var StrategyType;
25
+ (function (StrategyType) {
26
+ /** Uniform distribution - equal value per bin (adjusted for price) */
27
+ StrategyType[StrategyType["SPOT"] = 0] = "SPOT";
28
+ /** Curve distribution - decreasing amounts away from active bin */
29
+ StrategyType[StrategyType["CURVE"] = 1] = "CURVE";
30
+ /** Bid-Ask distribution - increasing amounts away from active bin */
31
+ StrategyType[StrategyType["BID_ASK"] = 2] = "BID_ASK";
32
+ })(StrategyType || (exports.StrategyType = StrategyType = {}));
33
+ /**
34
+ * Scale offset used for fixed-point arithmetic in price calculations.
35
+ * Matches DLMM SDK's SCALE_OFFSET = 64
36
+ */
37
+ const SCALE_OFFSET = 64;
38
+ /**
39
+ * Constants for Q64.64 fixed-point math (matching Meteora SDK)
40
+ */
41
+ const ONE = new bn_js_1.default(1).shln(SCALE_OFFSET);
42
+ const MAX = new bn_js_1.default(2).pow(new bn_js_1.default(128)).sub(new bn_js_1.default(1));
43
+ const MAX_EXPONENTIAL = new bn_js_1.default(0x80000);
44
+ const BASIS_POINT_MAX = 10000;
45
+ /**
46
+ * Binary exponentiation for Q64.64 fixed-point numbers.
47
+ * Matches Meteora SDK's pow function from u64xu64_math.ts
48
+ *
49
+ * @param base - Base value in Q64.64 format
50
+ * @param exp - Exponent (can be negative)
51
+ * @returns base^exp in Q64.64 format
52
+ */
53
+ function pow(base, exp) {
54
+ let invert = exp.isNeg();
55
+ if (exp.isZero()) {
56
+ return ONE;
57
+ }
58
+ exp = invert ? exp.abs() : exp;
59
+ if (exp.gt(MAX_EXPONENTIAL)) {
60
+ return new bn_js_1.default(0);
61
+ }
62
+ let squaredBase = base;
63
+ let result = ONE;
64
+ // For base >= 1, invert first for better precision
65
+ if (squaredBase.gte(result)) {
66
+ squaredBase = MAX.div(squaredBase);
67
+ invert = !invert;
68
+ }
69
+ if (!exp.and(new bn_js_1.default(0x1)).isZero()) {
70
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
71
+ }
72
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
73
+ if (!exp.and(new bn_js_1.default(0x2)).isZero()) {
74
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
75
+ }
76
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
77
+ if (!exp.and(new bn_js_1.default(0x4)).isZero()) {
78
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
79
+ }
80
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
81
+ if (!exp.and(new bn_js_1.default(0x8)).isZero()) {
82
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
83
+ }
84
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
85
+ if (!exp.and(new bn_js_1.default(0x10)).isZero()) {
86
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
87
+ }
88
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
89
+ if (!exp.and(new bn_js_1.default(0x20)).isZero()) {
90
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
91
+ }
92
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
93
+ if (!exp.and(new bn_js_1.default(0x40)).isZero()) {
94
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
95
+ }
96
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
97
+ if (!exp.and(new bn_js_1.default(0x80)).isZero()) {
98
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
99
+ }
100
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
101
+ if (!exp.and(new bn_js_1.default(0x100)).isZero()) {
102
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
103
+ }
104
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
105
+ if (!exp.and(new bn_js_1.default(0x200)).isZero()) {
106
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
107
+ }
108
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
109
+ if (!exp.and(new bn_js_1.default(0x400)).isZero()) {
110
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
111
+ }
112
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
113
+ if (!exp.and(new bn_js_1.default(0x800)).isZero()) {
114
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
115
+ }
116
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
117
+ if (!exp.and(new bn_js_1.default(0x1000)).isZero()) {
118
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
119
+ }
120
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
121
+ if (!exp.and(new bn_js_1.default(0x2000)).isZero()) {
122
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
123
+ }
124
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
125
+ if (!exp.and(new bn_js_1.default(0x4000)).isZero()) {
126
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
127
+ }
128
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
129
+ if (!exp.and(new bn_js_1.default(0x8000)).isZero()) {
130
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
131
+ }
132
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
133
+ if (!exp.and(new bn_js_1.default(0x10000)).isZero()) {
134
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
135
+ }
136
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
137
+ if (!exp.and(new bn_js_1.default(0x20000)).isZero()) {
138
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
139
+ }
140
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET);
141
+ if (!exp.and(new bn_js_1.default(0x40000)).isZero()) {
142
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET);
143
+ }
144
+ if (result.isZero()) {
145
+ return new bn_js_1.default(0);
146
+ }
147
+ if (invert) {
148
+ result = MAX.div(result);
149
+ }
150
+ return result;
151
+ }
152
+ /**
153
+ * Calculates the Q-price base factor: (1 + binStep/10000) in Q64.64 format.
154
+ * This represents the price multiplier between adjacent bins.
155
+ *
156
+ * @param binStep - The bin step in basis points (e.g., 100 = 1%)
157
+ * @returns Base factor in Q64.64 fixed-point format
158
+ */
159
+ function getQPriceBaseFactor(binStep) {
160
+ const bps = binStep.shln(SCALE_OFFSET).div(new bn_js_1.default(BASIS_POINT_MAX));
161
+ return ONE.add(bps);
162
+ }
163
+ /**
164
+ * Calculates the Q-price from a bin ID.
165
+ * Price = (1 + binStep/10000)^binId in Q64.64 format.
166
+ *
167
+ * Uses binary exponentiation matching Meteora SDK for precision.
168
+ *
169
+ * @param binId - The bin ID (can be negative)
170
+ * @param binStep - The bin step in basis points
171
+ * @returns Price in Q64.64 fixed-point format
172
+ */
173
+ function getQPriceFromId(binId, binStep) {
174
+ return pow(getQPriceBaseFactor(binStep), binId);
175
+ }
176
+ /**
177
+ * Calculates the sum of price weights for bins on the ask side (above active bin).
178
+ *
179
+ * For SPOT strategy, amount in each bin = x0 * price_weight
180
+ * where price_weight = (1 + binStep/10000)^(-(activeId + deltaId))
181
+ *
182
+ * Total amount = x0 * sum(price_weights)
183
+ * Therefore: x0 = totalAmount / sum(price_weights)
184
+ *
185
+ * @param activeId - The active bin ID
186
+ * @param minDeltaId - Minimum delta ID (relative to active bin)
187
+ * @param maxDeltaId - Maximum delta ID (relative to active bin)
188
+ * @param binStep - The bin step in basis points
189
+ * @returns Sum of price weights in Q64.64 format
190
+ */
191
+ function getSumOfPriceWeights(activeId, minDeltaId, maxDeltaId, binStep) {
192
+ let totalWeight = new bn_js_1.default(0);
193
+ const baseFactor = getQPriceBaseFactor(binStep);
194
+ const minBinId = activeId.add(minDeltaId);
195
+ const maxBinId = activeId.add(maxDeltaId);
196
+ // Start from maxBinId and iterate down
197
+ // price = (1 + binStep/10000)^(-binId)
198
+ // We start with the price at -maxBinId and multiply by baseFactor as binId decreases
199
+ let currentPrice = getQPriceFromId(maxBinId.neg(), binStep);
200
+ for (let binId = maxBinId.toNumber(); binId >= minBinId.toNumber(); binId--) {
201
+ totalWeight = totalWeight.add(currentPrice);
202
+ // Moving to lower binId means multiplying price by baseFactor
203
+ currentPrice = currentPrice.mul(baseFactor).shrn(SCALE_OFFSET);
204
+ }
205
+ return totalWeight;
206
+ }
207
+ /**
208
+ * Finds x0 for SPOT strategy on the ask side (bins above active bin).
209
+ *
210
+ * For SPOT strategy with deltaX = 0:
211
+ * - Each bin gets: amountX = x0 * price_weight
212
+ * - Total amount = x0 * sum(price_weights)
213
+ * - Therefore: x0 = totalAmount / sum(price_weights)
214
+ *
215
+ * @param amountX - Total X amount to distribute
216
+ * @param minDeltaId - Minimum delta ID (relative to active bin), must be >= 0 for ask side
217
+ * @param maxDeltaId - Maximum delta ID (relative to active bin)
218
+ * @param binStep - The bin step in basis points
219
+ * @param activeId - The active bin ID
220
+ * @returns x0 value for the strategy
221
+ */
222
+ function findX0ForSpot(amountX, minDeltaId, maxDeltaId, binStep, activeId) {
223
+ if (minDeltaId.gt(maxDeltaId) || amountX.isZero() || amountX.isNeg()) {
224
+ return new bn_js_1.default(0);
225
+ }
226
+ const totalWeight = getSumOfPriceWeights(activeId, minDeltaId, maxDeltaId, binStep);
227
+ if (totalWeight.isZero()) {
228
+ return new bn_js_1.default(0);
229
+ }
230
+ // x0 = amountX / totalWeight (in Q64.64)
231
+ // x0 = (amountX << 64) / totalWeight
232
+ return amountX.shln(SCALE_OFFSET).div(totalWeight);
233
+ }
234
+ /**
235
+ * Finds y0 for SPOT strategy on the bid side (bins below active bin).
236
+ *
237
+ * For SPOT strategy with deltaY = 0:
238
+ * - Each bin gets: amountY = y0 (constant per bin)
239
+ * - Total amount = y0 * num_bins
240
+ * - Therefore: y0 = totalAmount / num_bins
241
+ *
242
+ * @param amountY - Total Y amount to distribute
243
+ * @param minDeltaId - Minimum delta ID (relative to active bin), must be < 0 for bid side
244
+ * @param maxDeltaId - Maximum delta ID (relative to active bin)
245
+ * @returns y0 value for the strategy
246
+ */
247
+ function findY0ForSpot(amountY, minDeltaId, maxDeltaId) {
248
+ if (minDeltaId.gt(maxDeltaId) || amountY.isZero() || amountY.isNeg()) {
249
+ return new bn_js_1.default(0);
250
+ }
251
+ // Number of bins = maxDeltaId - minDeltaId + 1
252
+ const numBins = maxDeltaId.sub(minDeltaId).addn(1);
253
+ if (numBins.isZero()) {
254
+ return new bn_js_1.default(0);
255
+ }
256
+ // y0 = amountY / numBins
257
+ return amountY.div(numBins);
258
+ }
259
+ /**
260
+ * Builds liquidity strategy parameters for SPOT (uniform) distribution.
261
+ *
262
+ * This function calculates x0 and y0 values that will distribute the given amounts
263
+ * uniformly across the specified bin range when used with the rebalanceLiquidity instruction.
264
+ *
265
+ * The function handles three cases:
266
+ * 1. Position entirely below active bin (bid side only) → only Y deposited
267
+ * 2. Position entirely above active bin (ask side only) → only X deposited
268
+ * 3. Position spans active bin → both X and Y deposited
269
+ *
270
+ * @param amountX - Total X amount to deposit
271
+ * @param amountY - Total Y amount to deposit
272
+ * @param minDeltaId - Minimum delta ID (lowerBinId - activeId)
273
+ * @param maxDeltaId - Maximum delta ID (upperBinId - activeId)
274
+ * @param binStep - The bin step in basis points
275
+ * @param activeId - The active bin ID
276
+ * @param favorXInActiveId - Whether X is favored in the active bin (affects bid/ask boundary)
277
+ * @returns Strategy parameters { x0, y0, deltaX, deltaY }
278
+ */
279
+ function buildSpotStrategyParameters(amountX, amountY, minDeltaId, maxDeltaId, binStep, activeId, favorXInActiveId = false) {
280
+ // Invalid range
281
+ if (minDeltaId.gt(maxDeltaId)) {
282
+ return {
283
+ x0: new bn_js_1.default(0),
284
+ y0: new bn_js_1.default(0),
285
+ deltaX: new bn_js_1.default(0),
286
+ deltaY: new bn_js_1.default(0),
287
+ };
288
+ }
289
+ // Determine bid/ask boundary based on favorXInActiveId
290
+ // - If favorXInActiveId = true: active bin is ask side (X), bid side ends at deltaId = -1
291
+ // - If favorXInActiveId = false: active bin is bid side (Y), ask side starts at deltaId = 1
292
+ const bidSideEndDeltaId = favorXInActiveId ? new bn_js_1.default(-1) : new bn_js_1.default(0);
293
+ const askSideStartDeltaId = favorXInActiveId ? new bn_js_1.default(0) : new bn_js_1.default(1);
294
+ // Case 1: Position entirely on bid side (below active bin) - Y only
295
+ const depositOnlyY = maxDeltaId.lte(bidSideEndDeltaId);
296
+ // Case 2: Position entirely on ask side (above active bin) - X only
297
+ const depositOnlyX = minDeltaId.gte(askSideStartDeltaId);
298
+ if (depositOnlyY) {
299
+ // Only Y token - all bins are on bid side
300
+ const y0 = findY0ForSpot(amountY, minDeltaId, maxDeltaId);
301
+ return {
302
+ x0: new bn_js_1.default(0),
303
+ y0,
304
+ deltaX: new bn_js_1.default(0),
305
+ deltaY: new bn_js_1.default(0),
306
+ };
307
+ }
308
+ if (depositOnlyX) {
309
+ // Only X token - all bins are on ask side
310
+ const x0 = findX0ForSpot(amountX, minDeltaId, maxDeltaId, binStep, activeId);
311
+ return {
312
+ x0,
313
+ y0: new bn_js_1.default(0),
314
+ deltaX: new bn_js_1.default(0),
315
+ deltaY: new bn_js_1.default(0),
316
+ };
317
+ }
318
+ // Case 3: Position spans active bin - both X and Y
319
+ // Y goes to bid side bins (minDeltaId to bidSideEndDeltaId)
320
+ // X goes to ask side bins (askSideStartDeltaId to maxDeltaId)
321
+ const y0 = findY0ForSpot(amountY, minDeltaId, bidSideEndDeltaId);
322
+ const x0 = findX0ForSpot(amountX, askSideStartDeltaId, maxDeltaId, binStep, activeId);
323
+ return {
324
+ x0,
325
+ y0,
326
+ deltaX: new bn_js_1.default(0),
327
+ deltaY: new bn_js_1.default(0),
328
+ };
329
+ }
330
+ /**
331
+ * Resets strategy parameters for a chunk that may not span the full position range.
332
+ *
333
+ * When splitting a position into chunks, each chunk may only cover part of the range:
334
+ * - If chunk is entirely on bid side → zero out X parameters
335
+ * - If chunk is entirely on ask side → zero out Y parameters
336
+ * - If chunk spans active bin → keep both X and Y parameters
337
+ *
338
+ * @param minDeltaId - Chunk's minimum delta ID
339
+ * @param maxDeltaId - Chunk's maximum delta ID
340
+ * @param favorXInActiveId - Whether X is favored in the active bin
341
+ * @param params - Original strategy parameters from buildSpotStrategyParameters
342
+ * @returns Adjusted parameters for the chunk
343
+ */
344
+ function resetUninvolvedLiquidityParams(minDeltaId, maxDeltaId, favorXInActiveId, params) {
345
+ const bidSideEndDeltaId = favorXInActiveId ? new bn_js_1.default(-1) : new bn_js_1.default(0);
346
+ const askSideStartDeltaId = bidSideEndDeltaId.addn(1);
347
+ let { x0, y0, deltaX, deltaY } = params;
348
+ // If chunk is entirely on bid side (maxDeltaId <= bidSideEndDeltaId) → zero out X
349
+ if (maxDeltaId.lte(bidSideEndDeltaId)) {
350
+ x0 = new bn_js_1.default(0);
351
+ deltaX = new bn_js_1.default(0);
352
+ }
353
+ // If chunk is entirely on ask side (minDeltaId >= askSideStartDeltaId) → zero out Y
354
+ if (minDeltaId.gte(askSideStartDeltaId)) {
355
+ y0 = new bn_js_1.default(0);
356
+ deltaY = new bn_js_1.default(0);
357
+ }
358
+ return { x0, y0, deltaX, deltaY };
359
+ }
360
+ /**
361
+ * Calculates the actual X amount that will be deposited into a bin on the ask side.
362
+ *
363
+ * For SPOT strategy: amountX = x0 * price_weight
364
+ * where price_weight = (1 + binStep/10000)^(-(activeId + deltaId))
365
+ *
366
+ * @param x0 - Base X amount from strategy parameters
367
+ * @param deltaId - Delta ID relative to active bin
368
+ * @param binStep - The bin step in basis points
369
+ * @param activeId - The active bin ID
370
+ * @returns Amount of X for this bin
371
+ */
372
+ function getAmountXForBin(x0, deltaId, binStep, activeId) {
373
+ if (x0.isZero()) {
374
+ return new bn_js_1.default(0);
375
+ }
376
+ const binId = activeId.add(deltaId);
377
+ const priceWeight = getQPriceFromId(binId.neg(), binStep);
378
+ // amountX = x0 * priceWeight >> 64
379
+ return x0.mul(priceWeight).shrn(SCALE_OFFSET);
380
+ }
381
+ /**
382
+ * Calculates the actual Y amount that will be deposited into a bin on the bid side.
383
+ *
384
+ * For SPOT strategy: amountY = y0 (constant per bin)
385
+ *
386
+ * @param y0 - Base Y amount from strategy parameters
387
+ * @returns Amount of Y for this bin
388
+ */
389
+ function getAmountYForBin(y0) {
390
+ return y0;
391
+ }
392
+ /**
393
+ * Calculates total X and Y amounts for a chunk based on strategy parameters.
394
+ *
395
+ * This iterates through each bin in the chunk and sums up the amounts.
396
+ *
397
+ * @param activeId - The active bin ID
398
+ * @param minDeltaId - Chunk's minimum delta ID
399
+ * @param maxDeltaId - Chunk's maximum delta ID
400
+ * @param params - Strategy parameters (after resetUninvolvedLiquidityParams)
401
+ * @param binStep - The bin step in basis points
402
+ * @param favorXInActiveId - Whether X is favored in the active bin
403
+ * @returns Total X and Y amounts for the chunk
404
+ */
405
+ function calculateChunkAmounts(activeId, minDeltaId, maxDeltaId, params, binStep, favorXInActiveId) {
406
+ const { x0, y0 } = params;
407
+ const bidSideEndDeltaId = favorXInActiveId ? -1 : 0;
408
+ const askSideStartDeltaId = bidSideEndDeltaId + 1;
409
+ let totalXAmount = new bn_js_1.default(0);
410
+ let totalYAmount = new bn_js_1.default(0);
411
+ for (let deltaId = minDeltaId.toNumber(); deltaId <= maxDeltaId.toNumber(); deltaId++) {
412
+ if (deltaId <= bidSideEndDeltaId) {
413
+ // Bid side - Y token
414
+ totalYAmount = totalYAmount.add(y0);
415
+ }
416
+ else {
417
+ // Ask side - X token
418
+ const amountX = getAmountXForBin(x0, new bn_js_1.default(deltaId), binStep, activeId);
419
+ totalXAmount = totalXAmount.add(amountX);
420
+ }
421
+ }
422
+ return { totalXAmount, totalYAmount };
423
+ }
424
+ // =============================================================================
425
+ // CURVE STRATEGY
426
+ // =============================================================================
427
+ /**
428
+ * Finds base y0 for CURVE strategy.
429
+ *
430
+ * CURVE strategy has liquidity concentrated near active bin, decreasing away from it.
431
+ * Formula: amountY = y0 + deltaY * m where m = distance from active bin
432
+ * Setting deltaY = -y0 / (m1 + 1) gives decreasing amounts.
433
+ *
434
+ * @param amountY - Total Y amount to distribute
435
+ * @param minDeltaId - Minimum delta ID (negative for bid side)
436
+ * @param maxDeltaId - Maximum delta ID (negative for bid side)
437
+ * @returns Base y0 value
438
+ */
439
+ function findBaseY0ForCurve(amountY, minDeltaId, maxDeltaId) {
440
+ if (minDeltaId.gt(maxDeltaId) || amountY.lte(new bn_js_1.default(0))) {
441
+ return new bn_js_1.default(0);
442
+ }
443
+ if (minDeltaId.eq(maxDeltaId)) {
444
+ return amountY;
445
+ }
446
+ // m1 = -minDeltaId, m2 = -maxDeltaId (distances from active bin)
447
+ const m1 = minDeltaId.neg();
448
+ const m2 = maxDeltaId.neg();
449
+ // sum(amounts) = y0 * (m1-m2+1) + deltaY * (m1*(m1+1)/2 - m2*(m2-1)/2)
450
+ // set deltaY = -y0 / (m1 + 1)
451
+ // A = (m1-m2+1) - (m1*(m1+1)/2 - m2*(m2-1)/2) / (m1+1)
452
+ // y0 = amountY / A
453
+ const b = m1.sub(m2).addn(1);
454
+ const c = m1.mul(m1.addn(1)).divn(2);
455
+ const d = m2.mul(m2.subn(1)).divn(2);
456
+ const a = b.sub(c.sub(d).div(m1.addn(1)));
457
+ if (a.isZero()) {
458
+ return amountY;
459
+ }
460
+ return amountY.div(a);
461
+ }
462
+ /**
463
+ * Finds y0 and deltaY for CURVE strategy with iterative refinement.
464
+ *
465
+ * @param amountY - Total Y amount to distribute
466
+ * @param minDeltaId - Minimum delta ID
467
+ * @param maxDeltaId - Maximum delta ID
468
+ * @param activeId - Active bin ID
469
+ * @returns { base: y0, delta: deltaY }
470
+ */
471
+ function findY0AndDeltaYForCurve(amountY, minDeltaId, maxDeltaId, activeId) {
472
+ if (minDeltaId.gt(maxDeltaId) || amountY.isZero()) {
473
+ return { base: new bn_js_1.default(0), delta: new bn_js_1.default(0) };
474
+ }
475
+ let baseY0 = findBaseY0ForCurve(amountY, minDeltaId, maxDeltaId);
476
+ const m1 = minDeltaId.neg();
477
+ // deltaY = -y0 / (m1 + 1)
478
+ const deltaY = baseY0.neg().div(m1.addn(1));
479
+ // Iterative refinement to ensure we don't exceed amountY
480
+ while (true) {
481
+ const totalAmountY = calculateBidSideAmount(activeId, minDeltaId, maxDeltaId, deltaY, baseY0);
482
+ if (totalAmountY.gt(amountY)) {
483
+ baseY0 = baseY0.subn(1);
484
+ }
485
+ else {
486
+ return { base: baseY0, delta: deltaY };
487
+ }
488
+ }
489
+ }
490
+ /**
491
+ * Finds x0 and deltaX for CURVE strategy that produces linear decrease from active bin.
492
+ *
493
+ * For CURVE, we want the ask side to linearly decrease away from the active bin,
494
+ * mirroring the bid side's behavior.
495
+ *
496
+ * The on-chain formula for ask side is:
497
+ * amountX = (x0 + deltaX * delta) * priceWeight
498
+ *
499
+ * For linear decrease in baseAmount (x0 + deltaX * delta):
500
+ * - At minDelta: baseAmount = x0 + deltaX * minDelta (highest)
501
+ * - At maxDelta: baseAmount approaches 0
502
+ *
503
+ * Using deltaX = -x0 / (maxDelta + 1) gives:
504
+ * - At delta=0: baseAmount = x0 (would be highest, but minDelta >= 1)
505
+ * - At delta=maxDelta+1: baseAmount = 0
506
+ *
507
+ * The final amountX will be approximately linear because the decreasing baseAmount
508
+ * partially compensates for the exponentially decaying priceWeight.
509
+ *
510
+ * @param amountX - Total X amount to distribute
511
+ * @param minDeltaId - Minimum delta ID (should be >= 1 for ask side)
512
+ * @param maxDeltaId - Maximum delta ID
513
+ * @param binStep - Bin step in basis points
514
+ * @param activeId - Active bin ID
515
+ * @returns { x0, deltaX } parameters
516
+ */
517
+ function findX0AndDeltaXForCurveMirrored(amountX, minDeltaId, maxDeltaId, binStep, activeId) {
518
+ if (minDeltaId.gt(maxDeltaId) || amountX.lte(new bn_js_1.default(0))) {
519
+ return { base: new bn_js_1.default(0), delta: new bn_js_1.default(0) };
520
+ }
521
+ const numBins = maxDeltaId.sub(minDeltaId).addn(1).toNumber();
522
+ if (numBins === 1) {
523
+ // Single bin: just use x0 directly
524
+ const priceWeight = getQPriceFromId(activeId.add(minDeltaId).neg(), binStep);
525
+ const x0 = amountX.shln(SCALE_OFFSET).div(priceWeight);
526
+ return { base: x0, delta: new bn_js_1.default(0) };
527
+ }
528
+ // Meteora's approach:
529
+ // deltaX = -x0 / maxDelta
530
+ // x0 = amountX * 2^64 / (B - C)
531
+ // where:
532
+ // B = sum of priceWeights: p(m1) + p(m1+1) + ... + p(m2)
533
+ // C = weighted sum / m2: (m1*p(m1) + ... + m2*p(m2)) / m2
534
+ const m1 = minDeltaId.toNumber();
535
+ const m2 = maxDeltaId.toNumber();
536
+ let b = new bn_js_1.default(0);
537
+ let c = new bn_js_1.default(0);
538
+ for (let m = m1; m <= m2; m++) {
539
+ const binId = activeId.addn(m);
540
+ const pm = getQPriceFromId(binId.neg(), binStep);
541
+ b = b.add(pm);
542
+ const cDelta = new bn_js_1.default(m).mul(pm).div(maxDeltaId);
543
+ c = c.add(cDelta);
544
+ }
545
+ const denominator = b.sub(c);
546
+ if (denominator.isZero() || denominator.isNeg()) {
547
+ return { base: new bn_js_1.default(0), delta: new bn_js_1.default(0) };
548
+ }
549
+ let baseX0 = amountX.shln(SCALE_OFFSET).div(denominator);
550
+ const deltaX = baseX0.neg().div(maxDeltaId);
551
+ // Iterative refinement to ensure we don't exceed amountX
552
+ while (true) {
553
+ const totalAmountX = calculateAskSideAmount(activeId, binStep, minDeltaId, maxDeltaId, deltaX, baseX0);
554
+ if (totalAmountX.gt(amountX)) {
555
+ baseX0 = baseX0.subn(1);
556
+ }
557
+ else {
558
+ return { base: baseX0, delta: deltaX };
559
+ }
560
+ }
561
+ }
562
+ /**
563
+ * Finds x0 and deltaX for CURVE strategy.
564
+ *
565
+ * This is a wrapper that delegates to findX0AndDeltaXForCurveMirrored which
566
+ * produces a shape that mirrors the bid side's linear decrease.
567
+ *
568
+ * @param amountX - Total X amount to distribute
569
+ * @param minDeltaId - Minimum delta ID
570
+ * @param maxDeltaId - Maximum delta ID
571
+ * @param binStep - Bin step in basis points
572
+ * @param activeId - Active bin ID
573
+ * @returns { base: x0, delta: deltaX }
574
+ */
575
+ function findX0AndDeltaXForCurve(amountX, minDeltaId, maxDeltaId, binStep, activeId) {
576
+ return findX0AndDeltaXForCurveMirrored(amountX, minDeltaId, maxDeltaId, binStep, activeId);
577
+ }
578
+ /**
579
+ * Builds liquidity strategy parameters for CURVE distribution.
580
+ *
581
+ * CURVE strategy concentrates liquidity near the active bin with amounts
582
+ * decreasing as you move away. This is achieved with negative deltaX/deltaY.
583
+ *
584
+ * @param amountX - Total X amount to deposit
585
+ * @param amountY - Total Y amount to deposit
586
+ * @param minDeltaId - Minimum delta ID (lowerBinId - activeId)
587
+ * @param maxDeltaId - Maximum delta ID (upperBinId - activeId)
588
+ * @param binStep - The bin step in basis points
589
+ * @param activeId - The active bin ID
590
+ * @param favorXInActiveId - Whether X is favored in the active bin
591
+ * @returns Strategy parameters { x0, y0, deltaX, deltaY }
592
+ */
593
+ function buildCurveStrategyParameters(amountX, amountY, minDeltaId, maxDeltaId, binStep, activeId, favorXInActiveId = false) {
594
+ if (minDeltaId.gt(maxDeltaId)) {
595
+ return { x0: new bn_js_1.default(0), y0: new bn_js_1.default(0), deltaX: new bn_js_1.default(0), deltaY: new bn_js_1.default(0) };
596
+ }
597
+ const bidSideEndDeltaId = favorXInActiveId ? new bn_js_1.default(-1) : new bn_js_1.default(0);
598
+ const askSideStartDeltaId = favorXInActiveId ? new bn_js_1.default(0) : new bn_js_1.default(1);
599
+ const depositOnlyY = maxDeltaId.lte(bidSideEndDeltaId);
600
+ const depositOnlyX = minDeltaId.gte(askSideStartDeltaId);
601
+ if (depositOnlyY) {
602
+ const { base: y0, delta: deltaY } = findY0AndDeltaYForCurve(amountY, minDeltaId, maxDeltaId, activeId);
603
+ return { x0: new bn_js_1.default(0), y0, deltaX: new bn_js_1.default(0), deltaY };
604
+ }
605
+ if (depositOnlyX) {
606
+ const { base: x0, delta: deltaX } = findX0AndDeltaXForCurve(amountX, minDeltaId, maxDeltaId, binStep, activeId);
607
+ return { x0, y0: new bn_js_1.default(0), deltaX, deltaY: new bn_js_1.default(0) };
608
+ }
609
+ // Both sides - calculate independently
610
+ const { base: y0, delta: deltaY } = findY0AndDeltaYForCurve(amountY, minDeltaId, bidSideEndDeltaId, activeId);
611
+ const { base: x0, delta: deltaX } = findX0AndDeltaXForCurve(amountX, askSideStartDeltaId, maxDeltaId, binStep, activeId);
612
+ return { x0, y0, deltaX, deltaY };
613
+ }
614
+ // =============================================================================
615
+ // BID_ASK STRATEGY
616
+ // =============================================================================
617
+ /**
618
+ * Finds base deltaY for BID_ASK strategy.
619
+ *
620
+ * BID_ASK strategy has liquidity increasing away from active bin.
621
+ * Formula: amountY = y0 + deltaY * m
622
+ * Setting y0 = -deltaY * (m2 - 1) gives increasing amounts.
623
+ *
624
+ * @param amountY - Total Y amount to distribute
625
+ * @param minDeltaId - Minimum delta ID
626
+ * @param maxDeltaId - Maximum delta ID
627
+ * @returns Base deltaY value
628
+ */
629
+ function findBaseDeltaYForBidAsk(amountY, minDeltaId, maxDeltaId) {
630
+ if (minDeltaId.gt(maxDeltaId) || amountY.lte(new bn_js_1.default(0))) {
631
+ return new bn_js_1.default(0);
632
+ }
633
+ if (minDeltaId.eq(maxDeltaId)) {
634
+ return amountY;
635
+ }
636
+ const m1 = minDeltaId.neg();
637
+ const m2 = maxDeltaId.neg();
638
+ // sum(amounts) = y0 * (m1-m2+1) + deltaY * (m1*(m1+1)/2 - m2*(m2-1)/2)
639
+ // set y0 = -deltaY * (m2 - 1)
640
+ // A = (-m2+1) * (m1-m2+1) + (m1*(m1+1)/2 - m2*(m2-1)/2)
641
+ // deltaY = amountY / A
642
+ const b = m2.neg().addn(1).mul(m1.sub(m2).addn(1));
643
+ const c = m1.mul(m1.addn(1)).divn(2);
644
+ const d = m2.mul(m2.subn(1)).divn(2);
645
+ const a = b.add(c.sub(d));
646
+ if (a.isZero()) {
647
+ return amountY;
648
+ }
649
+ return amountY.div(a);
650
+ }
651
+ /**
652
+ * Finds y0 and deltaY for BID_ASK strategy with iterative refinement.
653
+ *
654
+ * @param amountY - Total Y amount to distribute
655
+ * @param minDeltaId - Minimum delta ID
656
+ * @param maxDeltaId - Maximum delta ID
657
+ * @param activeId - Active bin ID
658
+ * @returns { base: y0, delta: deltaY }
659
+ */
660
+ function findY0AndDeltaYForBidAsk(amountY, minDeltaId, maxDeltaId, activeId) {
661
+ if (minDeltaId.gt(maxDeltaId) || amountY.isZero()) {
662
+ return { base: new bn_js_1.default(0), delta: new bn_js_1.default(0) };
663
+ }
664
+ const baseDeltaY = findBaseDeltaYForBidAsk(amountY, minDeltaId, maxDeltaId);
665
+ const m2 = maxDeltaId.neg();
666
+ // y0 is calculated once from initial baseDeltaY and kept constant (matches Meteora)
667
+ const y0 = baseDeltaY.neg().mul(m2.subn(1));
668
+ // Binary search for the correct deltaY that doesn't exceed amountY
669
+ let low = new bn_js_1.default(0);
670
+ let high = baseDeltaY.clone();
671
+ let result = new bn_js_1.default(0);
672
+ while (low.lte(high)) {
673
+ const mid = low.add(high).shrn(1);
674
+ const totalAmountY = calculateBidSideAmount(activeId, minDeltaId, maxDeltaId, mid, y0);
675
+ if (totalAmountY.lte(amountY)) {
676
+ result = mid;
677
+ low = mid.addn(1);
678
+ }
679
+ else {
680
+ high = mid.subn(1);
681
+ }
682
+ }
683
+ return { base: y0, delta: result };
684
+ }
685
+ /**
686
+ * Calculates the critical delta beyond which BID_ASK amounts will start decreasing.
687
+ *
688
+ * For BID_ASK, amountX = (x0 + deltaX * delta) * priceWeight
689
+ * where priceWeight decays exponentially as delta increases.
690
+ *
691
+ * The critical point is approximately 1 / (binStep / 10000) = 10000 / binStep.
692
+ * Beyond this point, exponential decay overwhelms linear growth.
693
+ *
694
+ * @param binStep - Bin step in basis points
695
+ * @returns Critical delta value
696
+ */
697
+ function getCriticalDeltaForBidAsk(binStep) {
698
+ // Critical delta ≈ 1 / (binStep / 10000) = 10000 / binStep
699
+ // Using floor to be conservative
700
+ return Math.floor(10000 / binStep.toNumber());
701
+ }
702
+ /**
703
+ * Finds base deltaX for BID_ASK strategy.
704
+ *
705
+ * Formula: amountX = (x0 + deltaX * m) * price_weight
706
+ * Setting x0 = -m1 * deltaX + deltaX gives increasing amounts.
707
+ *
708
+ * IMPORTANT: This formula only produces monotonically increasing amounts
709
+ * when the range is within the critical delta (approximately 10000/binStep bins).
710
+ * For wider ranges, consider using multiple smaller positions.
711
+ *
712
+ * @param amountX - Total X amount to distribute
713
+ * @param minDeltaId - Minimum delta ID
714
+ * @param maxDeltaId - Maximum delta ID
715
+ * @param binStep - Bin step in basis points
716
+ * @param activeId - Active bin ID
717
+ * @returns Base deltaX value
718
+ */
719
+ function findBaseDeltaXForBidAsk(amountX, minDeltaId, maxDeltaId, binStep, activeId) {
720
+ if (minDeltaId.gt(maxDeltaId) || amountX.lte(new bn_js_1.default(0))) {
721
+ return new bn_js_1.default(0);
722
+ }
723
+ // sum(amounts) = x0 * B + deltaX * C
724
+ // where B = sum(price_weights), C = sum(m * price_weight)
725
+ // setting x0 = -m1 * deltaX + deltaX = deltaX * (1 - m1)
726
+ // sum = deltaX * (1 - m1) * B + deltaX * C = deltaX * ((1-m1)*B + C)
727
+ // deltaX = amountX / ((1-m1)*B + C)
728
+ let b = new bn_js_1.default(0);
729
+ let c = new bn_js_1.default(0);
730
+ const m1 = minDeltaId;
731
+ const m2 = maxDeltaId.addn(1); // +1 to ensure no zero amount at active bin
732
+ for (let m = m1.toNumber(); m <= m2.toNumber(); m++) {
733
+ const binId = activeId.addn(m);
734
+ const pm = getQPriceFromId(binId.neg(), binStep);
735
+ const bDelta = m1.mul(pm);
736
+ b = b.add(bDelta);
737
+ const cDelta = new bn_js_1.default(m).mul(pm);
738
+ c = c.add(cDelta);
739
+ }
740
+ const denominator = c.sub(b);
741
+ if (denominator.isZero()) {
742
+ return new bn_js_1.default(0);
743
+ }
744
+ return amountX.shln(SCALE_OFFSET).div(denominator);
745
+ }
746
+ /**
747
+ * Finds x0 and deltaX for BID_ASK strategy with iterative refinement.
748
+ *
749
+ * @param amountX - Total X amount to distribute
750
+ * @param minDeltaId - Minimum delta ID
751
+ * @param maxDeltaId - Maximum delta ID
752
+ * @param binStep - Bin step in basis points
753
+ * @param activeId - Active bin ID
754
+ * @returns { base: x0, delta: deltaX }
755
+ */
756
+ function findX0AndDeltaXForBidAsk(amountX, minDeltaId, maxDeltaId, binStep, activeId) {
757
+ if (minDeltaId.gt(maxDeltaId) || amountX.lte(new bn_js_1.default(0)) || amountX.isZero()) {
758
+ return { base: new bn_js_1.default(0), delta: new bn_js_1.default(0) };
759
+ }
760
+ const baseDeltaX = findBaseDeltaXForBidAsk(amountX, minDeltaId, maxDeltaId, binStep, activeId);
761
+ // x0 is calculated once from initial baseDeltaX and kept constant (matches Meteora)
762
+ const x0 = minDeltaId.neg().mul(baseDeltaX).add(baseDeltaX);
763
+ // Binary search for the correct deltaX that doesn't exceed amountX
764
+ let low = new bn_js_1.default(0);
765
+ let high = baseDeltaX.clone();
766
+ let result = new bn_js_1.default(0);
767
+ while (low.lte(high)) {
768
+ const mid = low.add(high).shrn(1);
769
+ const totalAmountX = calculateAskSideAmount(activeId, binStep, minDeltaId, maxDeltaId, mid, x0);
770
+ if (totalAmountX.lte(amountX)) {
771
+ result = mid;
772
+ low = mid.addn(1);
773
+ }
774
+ else {
775
+ high = mid.subn(1);
776
+ }
777
+ }
778
+ return { base: x0, delta: result };
779
+ }
780
+ /**
781
+ * Builds liquidity strategy parameters for BID_ASK distribution.
782
+ *
783
+ * BID_ASK strategy has liquidity increasing away from the active bin.
784
+ * This is achieved with positive deltaX/deltaY values.
785
+ *
786
+ * @param amountX - Total X amount to deposit
787
+ * @param amountY - Total Y amount to deposit
788
+ * @param minDeltaId - Minimum delta ID (lowerBinId - activeId)
789
+ * @param maxDeltaId - Maximum delta ID (upperBinId - activeId)
790
+ * @param binStep - The bin step in basis points
791
+ * @param activeId - The active bin ID
792
+ * @param favorXInActiveId - Whether X is favored in the active bin
793
+ * @returns Strategy parameters { x0, y0, deltaX, deltaY }
794
+ */
795
+ function buildBidAskStrategyParameters(amountX, amountY, minDeltaId, maxDeltaId, binStep, activeId, favorXInActiveId = false) {
796
+ if (minDeltaId.gt(maxDeltaId)) {
797
+ return { x0: new bn_js_1.default(0), y0: new bn_js_1.default(0), deltaX: new bn_js_1.default(0), deltaY: new bn_js_1.default(0) };
798
+ }
799
+ const bidSideEndDeltaId = favorXInActiveId ? new bn_js_1.default(-1) : new bn_js_1.default(0);
800
+ const askSideStartDeltaId = favorXInActiveId ? new bn_js_1.default(0) : new bn_js_1.default(1);
801
+ const depositOnlyY = maxDeltaId.lte(bidSideEndDeltaId);
802
+ const depositOnlyX = minDeltaId.gte(askSideStartDeltaId);
803
+ if (depositOnlyY) {
804
+ const { base: y0, delta: deltaY } = findY0AndDeltaYForBidAsk(amountY, minDeltaId, maxDeltaId, activeId);
805
+ return { x0: new bn_js_1.default(0), y0, deltaX: new bn_js_1.default(0), deltaY };
806
+ }
807
+ if (depositOnlyX) {
808
+ const { base: x0, delta: deltaX } = findX0AndDeltaXForBidAsk(amountX, minDeltaId, maxDeltaId, binStep, activeId);
809
+ return { x0, y0: new bn_js_1.default(0), deltaX, deltaY: new bn_js_1.default(0) };
810
+ }
811
+ // Both sides
812
+ const { base: y0, delta: deltaY } = findY0AndDeltaYForBidAsk(amountY, minDeltaId, bidSideEndDeltaId, activeId);
813
+ const { base: x0, delta: deltaX } = findX0AndDeltaXForBidAsk(amountX, askSideStartDeltaId, maxDeltaId, binStep, activeId);
814
+ return { x0, y0, deltaX, deltaY };
815
+ }
816
+ // =============================================================================
817
+ // HELPER FUNCTIONS FOR AMOUNT CALCULATION
818
+ // =============================================================================
819
+ /**
820
+ * Calculates total Y amount for bid side bins using given parameters.
821
+ *
822
+ * @param activeId - Active bin ID
823
+ * @param minDeltaId - Minimum delta ID
824
+ * @param maxDeltaId - Maximum delta ID
825
+ * @param deltaY - Delta Y per bin
826
+ * @param y0 - Base Y amount
827
+ * @returns Total Y amount
828
+ */
829
+ function calculateBidSideAmount(activeId, minDeltaId, maxDeltaId, deltaY, y0) {
830
+ let totalAmount = new bn_js_1.default(0);
831
+ const minBinId = activeId.add(minDeltaId);
832
+ const maxBinId = activeId.add(maxDeltaId);
833
+ for (let binId = minBinId.toNumber(); binId <= maxBinId.toNumber(); binId++) {
834
+ const deltaBin = activeId.toNumber() - binId;
835
+ const totalDeltaY = deltaY.muln(deltaBin);
836
+ const amountY = y0.add(totalDeltaY);
837
+ if (amountY.gtn(0)) {
838
+ totalAmount = totalAmount.add(amountY);
839
+ }
840
+ }
841
+ return totalAmount;
842
+ }
843
+ /**
844
+ * Calculates total X amount for ask side bins using given parameters.
845
+ *
846
+ * @param activeId - Active bin ID
847
+ * @param binStep - Bin step in basis points
848
+ * @param minDeltaId - Minimum delta ID
849
+ * @param maxDeltaId - Maximum delta ID
850
+ * @param deltaX - Delta X per bin
851
+ * @param x0 - Base X amount
852
+ * @returns Total X amount
853
+ */
854
+ function calculateAskSideAmount(activeId, binStep, minDeltaId, maxDeltaId, deltaX, x0) {
855
+ let totalAmount = new bn_js_1.default(0);
856
+ const baseFactor = getQPriceBaseFactor(binStep);
857
+ const minBinId = activeId.add(minDeltaId);
858
+ const maxBinId = activeId.add(maxDeltaId);
859
+ // Start from maxBinId and iterate down
860
+ let inverseBasePrice = getQPriceFromId(maxBinId.neg(), binStep);
861
+ for (let binId = maxBinId.toNumber(); binId >= minBinId.toNumber(); binId--) {
862
+ const delta = binId - activeId.toNumber();
863
+ const totalDeltaX = deltaX.muln(delta);
864
+ const baseAmount = x0.add(totalDeltaX);
865
+ // Only calculate if baseAmount is positive (can't shift right on negative BN)
866
+ if (baseAmount.gtn(0)) {
867
+ const amountX = baseAmount.mul(inverseBasePrice).shrn(SCALE_OFFSET);
868
+ if (amountX.gtn(0)) {
869
+ totalAmount = totalAmount.add(amountX);
870
+ }
871
+ }
872
+ inverseBasePrice = inverseBasePrice.mul(baseFactor).shrn(SCALE_OFFSET);
873
+ }
874
+ return totalAmount;
875
+ }
876
+ // =============================================================================
877
+ // UNIFIED STRATEGY BUILDER
878
+ // =============================================================================
879
+ /**
880
+ * Builds liquidity strategy parameters for the specified strategy type.
881
+ *
882
+ * This is the main entry point that dispatches to the appropriate strategy builder.
883
+ *
884
+ * @param strategyType - The type of strategy (SPOT, CURVE, or BID_ASK)
885
+ * @param amountX - Total X amount to deposit
886
+ * @param amountY - Total Y amount to deposit
887
+ * @param minDeltaId - Minimum delta ID (lowerBinId - activeId)
888
+ * @param maxDeltaId - Maximum delta ID (upperBinId - activeId)
889
+ * @param binStep - The bin step in basis points
890
+ * @param activeId - The active bin ID
891
+ * @param favorXInActiveId - Whether X is favored in the active bin
892
+ * @returns Strategy parameters { x0, y0, deltaX, deltaY }
893
+ */
894
+ function buildStrategyParameters(strategyType, amountX, amountY, minDeltaId, maxDeltaId, binStep, activeId, favorXInActiveId = false) {
895
+ switch (strategyType) {
896
+ case StrategyType.SPOT:
897
+ return buildSpotStrategyParameters(amountX, amountY, minDeltaId, maxDeltaId, binStep, activeId, favorXInActiveId);
898
+ case StrategyType.CURVE:
899
+ return buildCurveStrategyParameters(amountX, amountY, minDeltaId, maxDeltaId, binStep, activeId, favorXInActiveId);
900
+ case StrategyType.BID_ASK:
901
+ return buildBidAskStrategyParameters(amountX, amountY, minDeltaId, maxDeltaId, binStep, activeId, favorXInActiveId);
902
+ default:
903
+ // Default to SPOT if unknown strategy type
904
+ return buildSpotStrategyParameters(amountX, amountY, minDeltaId, maxDeltaId, binStep, activeId, favorXInActiveId);
905
+ }
906
+ }
907
+ /**
908
+ * Divides a bin range into chunks of specified size.
909
+ *
910
+ * This is similar to Meteora's `chunkBinRange` but with a configurable chunk size
911
+ * instead of a fixed 70-bin limit.
912
+ *
913
+ * @param minBinId - The starting bin ID of the range (absolute bin ID)
914
+ * @param maxBinId - The ending bin ID of the range (absolute bin ID)
915
+ * @param chunkSize - Maximum number of bins per chunk
916
+ * @returns Array of bin range chunks
917
+ */
918
+ function chunkBinRange(minBinId, maxBinId, chunkSize) {
919
+ const chunkedBinRange = [];
920
+ let startBinId = minBinId;
921
+ while (startBinId <= maxBinId) {
922
+ const endBinId = Math.min(startBinId + chunkSize - 1, maxBinId);
923
+ chunkedBinRange.push({
924
+ lowerBinId: startBinId,
925
+ upperBinId: endBinId,
926
+ });
927
+ startBinId += chunkSize;
928
+ }
929
+ return chunkedBinRange;
930
+ }
931
+ /**
932
+ * Converts strategy parameters to on-chain format with bit flag for negative values.
933
+ *
934
+ * The Solana program uses unsigned integers, so negative values are stored as
935
+ * positive with a bit flag indicating the sign.
936
+ *
937
+ * Bit flags:
938
+ * - Bit 0 (0b0001): x0 is negative
939
+ * - Bit 1 (0b0010): y0 is negative
940
+ * - Bit 2 (0b0100): deltaX is negative
941
+ * - Bit 3 (0b1000): deltaY is negative
942
+ *
943
+ * @param x0 - Base X amount (may be negative)
944
+ * @param y0 - Base Y amount (may be negative)
945
+ * @param deltaX - Delta X per bin (may be negative)
946
+ * @param deltaY - Delta Y per bin (may be negative)
947
+ * @returns Parameters with absolute values and bit flag
948
+ */
949
+ function buildBitFlagAndNegateStrategyParameters(x0, y0, deltaX, deltaY) {
950
+ let bitFlag = 0;
951
+ if (x0.isNeg()) {
952
+ bitFlag |= 0b0001;
953
+ x0 = x0.neg();
954
+ }
955
+ if (y0.isNeg()) {
956
+ bitFlag |= 0b0010;
957
+ y0 = y0.neg();
958
+ }
959
+ if (deltaX.isNeg()) {
960
+ bitFlag |= 0b0100;
961
+ deltaX = deltaX.neg();
962
+ }
963
+ if (deltaY.isNeg()) {
964
+ bitFlag |= 0b1000;
965
+ deltaY = deltaY.neg();
966
+ }
967
+ return {
968
+ bitFlag,
969
+ x0,
970
+ y0,
971
+ deltaX,
972
+ deltaY,
973
+ };
974
+ }
975
+ /**
976
+ * Calculates amounts for each bin in a range.
977
+ *
978
+ * This function iterates through all bins and calculates the X/Y amount
979
+ * for each bin based on the strategy parameters.
980
+ *
981
+ * @param activeId - The active bin ID
982
+ * @param minDeltaId - Minimum delta ID relative to active bin
983
+ * @param maxDeltaId - Maximum delta ID relative to active bin
984
+ * @param deltaX - Delta X per bin
985
+ * @param deltaY - Delta Y per bin
986
+ * @param x0 - Base X amount
987
+ * @param y0 - Base Y amount
988
+ * @param binStep - Bin step in basis points
989
+ * @param favorXInActiveBin - Whether X is favored in active bin
990
+ * @returns Array of { binId, amountX, amountY } for each bin
991
+ */
992
+ function toAmountIntoBins(activeId, minDeltaId, maxDeltaId, deltaX, deltaY, x0, y0, binStep, favorXInActiveBin) {
993
+ const results = [];
994
+ const bidSideEndDeltaId = favorXInActiveBin ? -1 : 0;
995
+ for (let delta = minDeltaId.toNumber(); delta <= maxDeltaId.toNumber(); delta++) {
996
+ const binId = activeId.toNumber() + delta;
997
+ if (delta <= bidSideEndDeltaId) {
998
+ // Bid side - Y token
999
+ const distance = -delta; // Distance from active bin (positive)
1000
+ const amountY = y0.add(deltaY.muln(distance));
1001
+ results.push({
1002
+ binId,
1003
+ amountX: new bn_js_1.default(0),
1004
+ amountY: amountY.gtn(0) ? amountY : new bn_js_1.default(0),
1005
+ });
1006
+ }
1007
+ else {
1008
+ // Ask side - X token
1009
+ const priceWeight = getQPriceFromId(new bn_js_1.default(binId).neg(), binStep);
1010
+ const baseAmount = x0.add(deltaX.muln(delta));
1011
+ const amountX = baseAmount.mul(priceWeight).shrn(SCALE_OFFSET);
1012
+ results.push({
1013
+ binId,
1014
+ amountX: amountX.gtn(0) ? amountX : new bn_js_1.default(0),
1015
+ amountY: new bn_js_1.default(0),
1016
+ });
1017
+ }
1018
+ }
1019
+ return results;
1020
+ }
1021
+ /**
1022
+ * Chunks a deposit into multiple transactions based on bin range.
1023
+ *
1024
+ * This function:
1025
+ * 1. Takes pre-built strategy parameters for the entire position
1026
+ * 2. Chunks the bin range into smaller ranges
1027
+ * 3. For each chunk, adjusts params and calculates amounts
1028
+ *
1029
+ * @param params - Strategy parameters from buildStrategyParameters (for entire position)
1030
+ * @param minDeltaId - Minimum delta ID (lowerBinId - activeId)
1031
+ * @param maxDeltaId - Maximum delta ID (upperBinId - activeId)
1032
+ * @param activeId - Current active bin ID
1033
+ * @param binStep - Bin step in basis points
1034
+ * @param chunkSize - Maximum bins per chunk
1035
+ * @param favorXInActiveBin - Whether X is favored in active bin
1036
+ * @returns Array of chunked deposit parameters
1037
+ */
1038
+ function chunkDepositParameters(params, minDeltaId, maxDeltaId, activeId, binStep, chunkSize, favorXInActiveBin = false) {
1039
+ // Convert to absolute bin IDs for chunking
1040
+ const lowerBinId = activeId.add(minDeltaId).toNumber();
1041
+ const upperBinId = activeId.add(maxDeltaId).toNumber();
1042
+ // Chunk the bin range
1043
+ const chunks = chunkBinRange(lowerBinId, upperBinId, chunkSize);
1044
+ // For each chunk, calculate the deposit parameters
1045
+ const result = [];
1046
+ for (const chunk of chunks) {
1047
+ const chunkMinDeltaId = new bn_js_1.default(chunk.lowerBinId).sub(activeId);
1048
+ const chunkMaxDeltaId = new bn_js_1.default(chunk.upperBinId).sub(activeId);
1049
+ // Reset uninvolved params for this chunk
1050
+ const chunkParams = resetUninvolvedLiquidityParams(chunkMinDeltaId, chunkMaxDeltaId, favorXInActiveBin, params);
1051
+ // Calculate amounts for this chunk using toAmountIntoBins
1052
+ const binsAmounts = toAmountIntoBins(activeId, chunkMinDeltaId, chunkMaxDeltaId, chunkParams.deltaX, chunkParams.deltaY, chunkParams.x0, chunkParams.y0, binStep, favorXInActiveBin);
1053
+ // Sum up amounts
1054
+ const { totalXAmount, totalYAmount } = binsAmounts.reduce((acc, bin) => ({
1055
+ totalXAmount: acc.totalXAmount.add(bin.amountX),
1056
+ totalYAmount: acc.totalYAmount.add(bin.amountY),
1057
+ }), { totalXAmount: new bn_js_1.default(0), totalYAmount: new bn_js_1.default(0) });
1058
+ result.push({
1059
+ lowerBinId: chunk.lowerBinId,
1060
+ upperBinId: chunk.upperBinId,
1061
+ minDeltaId: chunkMinDeltaId,
1062
+ maxDeltaId: chunkMaxDeltaId,
1063
+ params: chunkParams,
1064
+ maxAmountX: totalXAmount,
1065
+ maxAmountY: totalYAmount,
1066
+ });
1067
+ }
1068
+ return result;
1069
+ }