@alcorexchange/alcor-swap-sdk 1.0.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 (110) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc +12 -0
  3. package/LICENSE +21 -0
  4. package/build/entities/baseCurrency.js +27 -0
  5. package/build/entities/currency.js +2 -0
  6. package/build/entities/fractions/currencyAmount.js +80 -0
  7. package/build/entities/fractions/fraction.js +117 -0
  8. package/build/entities/fractions/index.js +11 -0
  9. package/build/entities/fractions/percent.js +46 -0
  10. package/build/entities/fractions/price.js +73 -0
  11. package/build/entities/index.js +26 -0
  12. package/build/entities/pool.js +248 -0
  13. package/build/entities/position.js +364 -0
  14. package/build/entities/route.js +70 -0
  15. package/build/entities/tick.js +23 -0
  16. package/build/entities/tickDataProvider.js +30 -0
  17. package/build/entities/tickListDataProvider.js +35 -0
  18. package/build/entities/token.js +53 -0
  19. package/build/entities/trade.js +464 -0
  20. package/build/index.js +19 -0
  21. package/build/internalConstants.js +54 -0
  22. package/build/utils/computeAllRoutes.js +39 -0
  23. package/build/utils/encodeSqrtRatioX64.js +21 -0
  24. package/build/utils/fullMath.js +22 -0
  25. package/build/utils/index.js +30 -0
  26. package/build/utils/isSorted.js +18 -0
  27. package/build/utils/liquidityMath.js +23 -0
  28. package/build/utils/maxLiquidityForAmounts.js +86 -0
  29. package/build/utils/mostSignificantBit.js +27 -0
  30. package/build/utils/nearestUsableTick.js +26 -0
  31. package/build/utils/positionLibrary.js +22 -0
  32. package/build/utils/priceTickConversions.js +51 -0
  33. package/build/utils/sortedInsert.js +39 -0
  34. package/build/utils/sqrt.js +33 -0
  35. package/build/utils/sqrtPriceMath.js +92 -0
  36. package/build/utils/swapMath.js +86 -0
  37. package/build/utils/tickLibrary.js +61 -0
  38. package/build/utils/tickList.js +110 -0
  39. package/build/utils/tickMath.js +122 -0
  40. package/jest.config.js +40 -0
  41. package/nodemon.json +6 -0
  42. package/package.json +51 -0
  43. package/src/entities/baseCurrency.ts +53 -0
  44. package/src/entities/currency.ts +3 -0
  45. package/src/entities/fractions/currencyAmount.ts +129 -0
  46. package/src/entities/fractions/fraction.ts +190 -0
  47. package/src/entities/fractions/index.ts +4 -0
  48. package/src/entities/fractions/percent.ts +54 -0
  49. package/src/entities/fractions/price.ts +127 -0
  50. package/src/entities/index.ts +11 -0
  51. package/src/entities/pool.ts +399 -0
  52. package/src/entities/position.ts +591 -0
  53. package/src/entities/route.ts +84 -0
  54. package/src/entities/tick.ts +48 -0
  55. package/src/entities/tickDataProvider.ts +43 -0
  56. package/src/entities/tickListDataProvider.ts +37 -0
  57. package/src/entities/token.ts +56 -0
  58. package/src/entities/trade.ts +650 -0
  59. package/src/index.ts +3 -0
  60. package/src/internalConstants.ts +58 -0
  61. package/src/utils/computeAllRoutes.ts +64 -0
  62. package/src/utils/encodeSqrtRatioX64.ts +20 -0
  63. package/src/utils/fullMath.ts +17 -0
  64. package/src/utils/index.ts +14 -0
  65. package/src/utils/isSorted.ts +17 -0
  66. package/src/utils/liquidityMath.ts +17 -0
  67. package/src/utils/maxLiquidityForAmounts.ts +127 -0
  68. package/src/utils/mostSignificantBit.ts +25 -0
  69. package/src/utils/nearestUsableTick.ts +23 -0
  70. package/src/utils/positionLibrary.ts +37 -0
  71. package/src/utils/priceTickConversions.ts +57 -0
  72. package/src/utils/sortedInsert.ts +35 -0
  73. package/src/utils/sqrt.ts +31 -0
  74. package/src/utils/sqrtPriceMath.ts +169 -0
  75. package/src/utils/swapMath.ts +175 -0
  76. package/src/utils/tickLibrary.ts +88 -0
  77. package/src/utils/tickList.ts +147 -0
  78. package/src/utils/tickMath.ts +166 -0
  79. package/test/bestTradeExactOut.test.ts +266 -0
  80. package/test/currencyAmount.test.js +92 -0
  81. package/test/currencyAmount.test.ts +114 -0
  82. package/test/encodeSqrtRatioX64.test.ts +33 -0
  83. package/test/fixtures/pools.json +276 -0
  84. package/test/fixtures/ticks.json +608 -0
  85. package/test/fraction.test.js +87 -0
  86. package/test/fraction.test.ts +176 -0
  87. package/test/isSorted.test.ts +52 -0
  88. package/test/maxLiquidityForAmounts.test copy.ts +256 -0
  89. package/test/mostSignificantBit.test.ts +32 -0
  90. package/test/nearestUsableTick.test.ts +54 -0
  91. package/test/percent.test.js +52 -0
  92. package/test/percent.test.ts +68 -0
  93. package/test/pool.test.ts +377 -0
  94. package/test/position.test.ts +579 -0
  95. package/test/positionLibrary.test.ts +31 -0
  96. package/test/price.test.js +57 -0
  97. package/test/price.test.ts +62 -0
  98. package/test/priceTickConversions.test.ts +137 -0
  99. package/test/sqrtPriceMath.test.ts +113 -0
  100. package/test/tick.test.js +22 -0
  101. package/test/tick.test.ts +28 -0
  102. package/test/tickDataProvider.test.ts +17 -0
  103. package/test/tickLibrary.test.ts +111 -0
  104. package/test/tickList.test.ts +215 -0
  105. package/test/tickListDataProvider.test.ts +64 -0
  106. package/test/tickMath.test.ts +66 -0
  107. package/test/token.test.ts +58 -0
  108. package/test/trade.test.ts +210 -0
  109. package/test2.ts +73 -0
  110. package/tsconfig.json +24 -0
@@ -0,0 +1,399 @@
1
+ import { CurrencyAmount, Price } from "./fractions";
2
+ import { Token } from "./token";
3
+ import { BigintIsh, FeeAmount, TICK_SPACINGS } from "../internalConstants";
4
+ import JSBI from "jsbi";
5
+ import invariant from "tiny-invariant";
6
+ import { NEGATIVE_ONE, ONE, Q128, ZERO } from "../internalConstants";
7
+ import { LiquidityMath } from "../utils/liquidityMath";
8
+ import { SwapMath } from "../utils/swapMath";
9
+ import { TickMath } from "../utils/tickMath";
10
+ import { Tick, TickConstructorArgs } from "./tick";
11
+ import { NoTickDataProvider, TickDataProvider } from "./tickDataProvider";
12
+ import { TickListDataProvider } from "./tickListDataProvider";
13
+
14
+ export interface PoolConstructorArgs {
15
+ id: number,
16
+ tokenA: Token,
17
+ tokenB: Token,
18
+ fee: FeeAmount,
19
+ sqrtPriceX64: BigintIsh,
20
+ liquidity: BigintIsh,
21
+ tickCurrent: number,
22
+ feeGrowthGlobalAX64: BigintIsh,
23
+ feeGrowthGlobalBX64: BigintIsh,
24
+ ticks:
25
+ | TickDataProvider
26
+ | (Tick | TickConstructorArgs)[]
27
+ }
28
+
29
+ interface StepComputations {
30
+ sqrtPriceStartX64: JSBI;
31
+ tickNext: number;
32
+ initialized: boolean;
33
+ sqrtPriceNextX64: JSBI;
34
+ amountIn: JSBI;
35
+ amountOut: JSBI;
36
+ feeAmount: JSBI;
37
+ }
38
+
39
+ /**
40
+ * By default, pools will not allow operations that require ticks.
41
+ */
42
+ const NO_TICK_DATA_PROVIDER_DEFAULT = new NoTickDataProvider();
43
+
44
+ /**
45
+ * Represents a V3 pool
46
+ */
47
+ export class Pool {
48
+ // public readonly id: number;
49
+ public readonly id: number;
50
+ public readonly tokenA: Token;
51
+ public readonly tokenB: Token;
52
+ public readonly fee: FeeAmount;
53
+ public readonly sqrtPriceX64: JSBI;
54
+ public readonly liquidity: JSBI;
55
+ public readonly tickCurrent: number;
56
+ public readonly feeGrowthGlobalAX64: JSBI;
57
+ public readonly feeGrowthGlobalBX64: JSBI;
58
+ public readonly tickDataProvider: TickDataProvider;
59
+
60
+ private _tokenAPrice?: Price<Token, Token>;
61
+ private _tokenBPrice?: Price<Token, Token>;
62
+
63
+ /**
64
+ * Construct a pool
65
+ * @param tokenA One of the tokens in the pool
66
+ * @param tokenB The other token in the pool
67
+ * @param fee The fee in hundredths of a bips of the input amount of every swap that is collected by the pool
68
+ * @param sqrtPriceX64 The sqrt of the current ratio of amounts of tokenB to tokenA
69
+ * @param liquidity The current value of in range liquidity
70
+ * @param tickCurrent The current tick of the pool
71
+ * @param ticks The current state of the pool ticks or a data provider that can return tick data
72
+ */
73
+ public constructor({
74
+ id,
75
+ tokenA,
76
+ tokenB,
77
+ fee,
78
+ sqrtPriceX64,
79
+ liquidity,
80
+ tickCurrent,
81
+ ticks = NO_TICK_DATA_PROVIDER_DEFAULT,
82
+ feeGrowthGlobalAX64 = 0,
83
+ feeGrowthGlobalBX64 = 0,
84
+ }: PoolConstructorArgs) {
85
+ invariant(Number.isInteger(fee) && fee < 1_000_000, "FEE");
86
+
87
+ const tickCurrentSqrtRatioX64 = TickMath.getSqrtRatioAtTick(tickCurrent);
88
+ const nextTickSqrtRatioX64 = TickMath.getSqrtRatioAtTick(tickCurrent + 1);
89
+ invariant(
90
+ JSBI.greaterThanOrEqual(
91
+ JSBI.BigInt(sqrtPriceX64),
92
+ tickCurrentSqrtRatioX64
93
+ ) &&
94
+ JSBI.lessThanOrEqual(JSBI.BigInt(sqrtPriceX64), nextTickSqrtRatioX64),
95
+ "PRICE_BOUNDS"
96
+ );
97
+ // always create a copy of the list since we want the pool's tick list to be immutable
98
+ this.id = id;
99
+ this.fee = fee;
100
+ this.sqrtPriceX64 = JSBI.BigInt(sqrtPriceX64);
101
+ this.liquidity = JSBI.BigInt(liquidity);
102
+ this.tickCurrent = tickCurrent;
103
+ this.feeGrowthGlobalAX64 = JSBI.BigInt(feeGrowthGlobalAX64);
104
+ this.feeGrowthGlobalBX64 = JSBI.BigInt(feeGrowthGlobalBX64);
105
+
106
+ this.tickDataProvider = Array.isArray(ticks)
107
+ ? new TickListDataProvider(ticks, TICK_SPACINGS[fee])
108
+ : ticks;
109
+
110
+ [this.tokenA, this.tokenB] = tokenA.sortsBefore(tokenB)
111
+ ? [tokenA, tokenB]
112
+ : [tokenB, tokenA];
113
+ }
114
+
115
+ /**
116
+ * Returns true if the token is either tokenA or tokenB
117
+ * @param token The token to check
118
+ * @returns True if token is either tokenA or token
119
+ */
120
+ public involvesToken(token: Token): boolean {
121
+ return token.equals(this.tokenA) || token.equals(this.tokenB);
122
+ }
123
+
124
+ /**
125
+ * Returns the current mid price of the pool in terms of tokenA, i.e. the ratio of tokenB over tokenA
126
+ */
127
+ public get tokenAPrice(): Price<Token, Token> {
128
+ return (
129
+ this._tokenAPrice ??
130
+ (this._tokenAPrice = new Price(
131
+ this.tokenA,
132
+ this.tokenB,
133
+ Q128,
134
+ JSBI.multiply(this.sqrtPriceX64, this.sqrtPriceX64)
135
+ ))
136
+ );
137
+ }
138
+
139
+ /**
140
+ * Returns the current mid price of the pool in terms of tokenB, i.e. the ratio of tokenA over tokenB
141
+ */
142
+ public get tokenBPrice(): Price<Token, Token> {
143
+ return (
144
+ this._tokenBPrice ??
145
+ (this._tokenBPrice = new Price(
146
+ this.tokenB,
147
+ this.tokenA,
148
+ JSBI.multiply(this.sqrtPriceX64, this.sqrtPriceX64),
149
+ Q128
150
+ ))
151
+ );
152
+ }
153
+
154
+ /**
155
+ * Return the price of the given token in terms of the other token in the pool.
156
+ * @param token The token to return price of
157
+ * @returns The price of the given token, in terms of the other.
158
+ */
159
+ public priceOf(token: Token): Price<Token, Token> {
160
+ invariant(this.involvesToken(token), "TOKEN");
161
+ return token.equals(this.tokenA) ? this.tokenAPrice : this.tokenBPrice;
162
+ }
163
+
164
+ /**
165
+ * Given an input amount of a token, return the computed output amount, and a pool with state updated after the trade
166
+ * @param inputAmount The input amount for which to quote the output amount
167
+ * @param sqrtPriceLimitX64 The Q64.96 sqrt price limit
168
+ * @returns The output amount and the pool with updated state
169
+ */
170
+ public async getOutputAmount(
171
+ inputAmount: CurrencyAmount<Token>,
172
+ sqrtPriceLimitX64?: JSBI
173
+ ): Promise<[CurrencyAmount<Token>, Pool]> {
174
+ invariant(this.involvesToken(inputAmount.currency), "TOKEN");
175
+
176
+ const zeroForOne = inputAmount.currency.equals(this.tokenA);
177
+
178
+ const {
179
+ amountCalculated: outputAmount,
180
+ sqrtPriceX64,
181
+ liquidity,
182
+ tickCurrent,
183
+ } = await this.swap(zeroForOne, inputAmount.quotient, sqrtPriceLimitX64);
184
+ const outputToken = zeroForOne ? this.tokenB : this.tokenA;
185
+ return [
186
+ CurrencyAmount.fromRawAmount(
187
+ outputToken,
188
+ JSBI.multiply(outputAmount, NEGATIVE_ONE)
189
+ ),
190
+ new Pool({
191
+ id: this.id,
192
+ tokenA: this.tokenA,
193
+ tokenB: this.tokenB,
194
+ fee: this.fee,
195
+ sqrtPriceX64,
196
+ liquidity,
197
+ tickCurrent,
198
+ ticks: this.tickDataProvider,
199
+ feeGrowthGlobalAX64: this.feeGrowthGlobalAX64,
200
+ feeGrowthGlobalBX64: this.feeGrowthGlobalBX64,
201
+ })
202
+ ];
203
+ }
204
+
205
+ /**
206
+ * Given a desired output amount of a token, return the computed input amount and a pool with state updated after the trade
207
+ * @param outputAmount the output amount for which to quote the input amount
208
+ * @param sqrtPriceLimitX64 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this value after the swap. If one for zero, the price cannot be greater than this value after the swap
209
+ * @returns The input amount and the pool with updated state
210
+ */
211
+ public async getInputAmount(
212
+ outputAmount: CurrencyAmount<Token>,
213
+ sqrtPriceLimitX64?: JSBI
214
+ ): Promise<[CurrencyAmount<Token>, Pool]> {
215
+ const zeroForOne = outputAmount.currency.equals(this.tokenB);
216
+
217
+ const {
218
+ amountCalculated: inputAmount,
219
+ sqrtPriceX64,
220
+ liquidity,
221
+ tickCurrent,
222
+ } = await this.swap(
223
+ zeroForOne,
224
+ JSBI.multiply(outputAmount.quotient, NEGATIVE_ONE),
225
+ sqrtPriceLimitX64
226
+ );
227
+ const inputToken = zeroForOne ? this.tokenA : this.tokenB;
228
+ return [
229
+ CurrencyAmount.fromRawAmount(inputToken, inputAmount),
230
+ new Pool({
231
+ id: this.id,
232
+ tokenA: this.tokenA,
233
+ tokenB: this.tokenB,
234
+ fee: this.fee,
235
+ sqrtPriceX64,
236
+ liquidity,
237
+ tickCurrent,
238
+ ticks: this.tickDataProvider,
239
+ feeGrowthGlobalAX64: this.feeGrowthGlobalAX64,
240
+ feeGrowthGlobalBX64: this.feeGrowthGlobalBX64,
241
+ }),
242
+ ];
243
+ }
244
+
245
+ /**
246
+ * Executes a swap
247
+ * @param zeroForOne Whether the amount in is tokenA or tokenB
248
+ * @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
249
+ * @param sqrtPriceLimitX64 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this value after the swap. If one for zero, the price cannot be greater than this value after the swap
250
+ * @returns amountCalculated
251
+ * @returns sqrtPriceX64
252
+ * @returns liquidity
253
+ * @returns tickCurrent
254
+ */
255
+ private async swap(
256
+ zeroForOne: boolean,
257
+ amountSpecified: JSBI,
258
+ sqrtPriceLimitX64?: JSBI
259
+ ): Promise<{
260
+ amountCalculated: JSBI;
261
+ sqrtPriceX64: JSBI;
262
+ liquidity: JSBI;
263
+ tickCurrent: number;
264
+ }> {
265
+ if (!sqrtPriceLimitX64)
266
+ sqrtPriceLimitX64 = zeroForOne
267
+ ? JSBI.add(TickMath.MIN_SQRT_RATIO, ONE)
268
+ : JSBI.subtract(TickMath.MAX_SQRT_RATIO, ONE);
269
+
270
+ if (zeroForOne) {
271
+ invariant(
272
+ JSBI.greaterThan(sqrtPriceLimitX64, TickMath.MIN_SQRT_RATIO),
273
+ "RATIO_MIN"
274
+ );
275
+ invariant(
276
+ JSBI.lessThan(sqrtPriceLimitX64, this.sqrtPriceX64),
277
+ "RATIO_CURRENT"
278
+ );
279
+ } else {
280
+ invariant(
281
+ JSBI.lessThan(sqrtPriceLimitX64, TickMath.MAX_SQRT_RATIO),
282
+ "RATIO_MAX"
283
+ );
284
+ invariant(
285
+ JSBI.greaterThan(sqrtPriceLimitX64, this.sqrtPriceX64),
286
+ "RATIO_CURRENT"
287
+ );
288
+ }
289
+
290
+ const exactInput = JSBI.greaterThanOrEqual(amountSpecified, ZERO);
291
+
292
+ // keep track of swap state
293
+
294
+ const state = {
295
+ amountSpecifiedRemaining: amountSpecified,
296
+ amountCalculated: ZERO,
297
+ sqrtPriceX64: this.sqrtPriceX64,
298
+ tick: this.tickCurrent,
299
+ liquidity: this.liquidity,
300
+ };
301
+
302
+ // start swap while loop
303
+ while (
304
+ JSBI.notEqual(state.amountSpecifiedRemaining, ZERO) &&
305
+ state.sqrtPriceX64 != sqrtPriceLimitX64
306
+ ) {
307
+ const step: Partial<StepComputations> = {};
308
+ step.sqrtPriceStartX64 = state.sqrtPriceX64;
309
+
310
+ // because each iteration of the while loop rounds, we can't optimize this code (relative to the smart contract)
311
+ // by simply traversing to the next available tick, we instead need to exactly replicate
312
+ // tickBitmap.nextInitializedTickWithinOneWord
313
+ [step.tickNext, step.initialized] =
314
+ await this.tickDataProvider.nextInitializedTickWithinOneWord(
315
+ state.tick,
316
+ zeroForOne,
317
+ this.tickSpacing
318
+ );
319
+
320
+ if (step.tickNext < TickMath.MIN_TICK) {
321
+ step.tickNext = TickMath.MIN_TICK;
322
+ } else if (step.tickNext > TickMath.MAX_TICK) {
323
+ step.tickNext = TickMath.MAX_TICK;
324
+ }
325
+
326
+ step.sqrtPriceNextX64 = TickMath.getSqrtRatioAtTick(step.tickNext);
327
+ [state.sqrtPriceX64, step.amountIn, step.amountOut, step.feeAmount] =
328
+ SwapMath.computeSwapStep(
329
+ state.sqrtPriceX64,
330
+ (
331
+ zeroForOne
332
+ ? JSBI.lessThan(step.sqrtPriceNextX64, sqrtPriceLimitX64)
333
+ : JSBI.greaterThan(step.sqrtPriceNextX64, sqrtPriceLimitX64)
334
+ )
335
+ ? sqrtPriceLimitX64
336
+ : step.sqrtPriceNextX64,
337
+ state.liquidity,
338
+ state.amountSpecifiedRemaining,
339
+ this.fee
340
+ );
341
+
342
+ if (exactInput) {
343
+ state.amountSpecifiedRemaining = JSBI.subtract(
344
+ state.amountSpecifiedRemaining,
345
+ JSBI.add(step.amountIn, step.feeAmount)
346
+ );
347
+ state.amountCalculated = JSBI.subtract(
348
+ state.amountCalculated,
349
+ step.amountOut
350
+ );
351
+ } else {
352
+ state.amountSpecifiedRemaining = JSBI.add(
353
+ state.amountSpecifiedRemaining,
354
+ step.amountOut
355
+ );
356
+ state.amountCalculated = JSBI.add(
357
+ state.amountCalculated,
358
+ JSBI.add(step.amountIn, step.feeAmount)
359
+ );
360
+ }
361
+
362
+ // TODO
363
+ if (JSBI.equal(state.sqrtPriceX64, step.sqrtPriceNextX64)) {
364
+ // if the tick is initialized, run the tick transition
365
+ if (step.initialized) {
366
+ let liquidityNet = JSBI.BigInt(
367
+ (await this.tickDataProvider.getTick(step.tickNext)).liquidityNet
368
+ );
369
+ // if we're moving leftward, we interpret liquidityNet as the opposite sign
370
+ // safe because liquidityNet cannot be type(int128).min
371
+ if (zeroForOne)
372
+ liquidityNet = JSBI.multiply(liquidityNet, NEGATIVE_ONE);
373
+
374
+ state.liquidity = LiquidityMath.addDelta(
375
+ state.liquidity,
376
+ liquidityNet
377
+ );
378
+ }
379
+
380
+ state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext;
381
+ } else if (JSBI.notEqual(state.sqrtPriceX64, step.sqrtPriceStartX64)) {
382
+ // updated comparison function
383
+ // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved
384
+ state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX64);
385
+ }
386
+ }
387
+
388
+ return {
389
+ amountCalculated: state.amountCalculated,
390
+ sqrtPriceX64: state.sqrtPriceX64,
391
+ liquidity: state.liquidity,
392
+ tickCurrent: state.tick,
393
+ };
394
+ }
395
+
396
+ public get tickSpacing(): number {
397
+ return TICK_SPACINGS[this.fee];
398
+ }
399
+ }