@alephium/powfi-sdk 0.0.1-rc.1

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 (146) hide show
  1. package/README.md +4 -0
  2. package/clmm/artifacts/BitmapWord.ral.json +125 -0
  3. package/clmm/artifacts/BitmapWordDeployer.ral.json +31 -0
  4. package/clmm/artifacts/CreateConfig.ral.json +37 -0
  5. package/clmm/artifacts/CreateLiquidPool.ral.json +55 -0
  6. package/clmm/artifacts/DexAccount.ral.json +110 -0
  7. package/clmm/artifacts/LiquidityAmountsTest.ral.json +161 -0
  8. package/clmm/artifacts/LiquidityManagmentTest.ral.json +384 -0
  9. package/clmm/artifacts/Pool.ral.json +1530 -0
  10. package/clmm/artifacts/PoolConfig.ral.json +31 -0
  11. package/clmm/artifacts/PoolFactory.ral.json +300 -0
  12. package/clmm/artifacts/PoolRouterDemo.ral.json +49 -0
  13. package/clmm/artifacts/PoolUser.ral.json +89 -0
  14. package/clmm/artifacts/Position.ral.json +183 -0
  15. package/clmm/artifacts/PositionManager.ral.json +416 -0
  16. package/clmm/artifacts/SwapWithoutAccount.ral.json +46 -0
  17. package/clmm/artifacts/TestToken.ral.json +68 -0
  18. package/clmm/artifacts/Tick.ral.json +161 -0
  19. package/clmm/artifacts/TickBitmapTest.ral.json +220 -0
  20. package/clmm/artifacts/constants.ral.json +81 -0
  21. package/clmm/artifacts/structs.ral.json +335 -0
  22. package/clmm/artifacts/ts/BitmapWord.ts +337 -0
  23. package/clmm/artifacts/ts/BitmapWordDeployer.ts +164 -0
  24. package/clmm/artifacts/ts/DexAccount.ts +330 -0
  25. package/clmm/artifacts/ts/LiquidityAmountsTest.ts +464 -0
  26. package/clmm/artifacts/ts/LiquidityManagmentTest.ts +859 -0
  27. package/clmm/artifacts/ts/Pool.ts +2535 -0
  28. package/clmm/artifacts/ts/PoolConfig.ts +179 -0
  29. package/clmm/artifacts/ts/PoolFactory.ts +640 -0
  30. package/clmm/artifacts/ts/PoolUser.ts +237 -0
  31. package/clmm/artifacts/ts/Position.ts +440 -0
  32. package/clmm/artifacts/ts/PositionManager.ts +929 -0
  33. package/clmm/artifacts/ts/TestToken.ts +277 -0
  34. package/clmm/artifacts/ts/Tick.ts +351 -0
  35. package/clmm/artifacts/ts/TickBitmapTest.ts +512 -0
  36. package/clmm/artifacts/ts/constants.ts +17 -0
  37. package/clmm/artifacts/ts/contracts.ts +26 -0
  38. package/clmm/artifacts/ts/deployments.ts +160 -0
  39. package/clmm/artifacts/ts/index.ts +20 -0
  40. package/clmm/artifacts/ts/scripts.ts +76 -0
  41. package/clmm/artifacts/ts/types.ts +105 -0
  42. package/clmm/deployments/.deployments.devnet.json +350 -0
  43. package/clmm/deployments/.deployments.testnet.json +350 -0
  44. package/cpmm/artifacts/dex/DexAccount.ral.json +110 -0
  45. package/cpmm/artifacts/dex/Router.ral.json +361 -0
  46. package/cpmm/artifacts/dex/TokenPair.ral.json +512 -0
  47. package/cpmm/artifacts/dex/TokenPairFactory.ral.json +297 -0
  48. package/cpmm/artifacts/examples/ExampleOracleSimple.ral.json +192 -0
  49. package/cpmm/artifacts/examples/FeeCollectorFactoryImpl.ral.json +185 -0
  50. package/cpmm/artifacts/examples/FeeCollectorPerTokenPairImpl.ral.json +216 -0
  51. package/cpmm/artifacts/examples/FullMathTest.ral.json +123 -0
  52. package/cpmm/artifacts/scripts/AddLiquidity.ral.json +46 -0
  53. package/cpmm/artifacts/scripts/Burn.ral.json +31 -0
  54. package/cpmm/artifacts/scripts/CollectFee.ral.json +25 -0
  55. package/cpmm/artifacts/scripts/CreatePair.ral.json +37 -0
  56. package/cpmm/artifacts/scripts/CreatePairAndAddLiquidity.ral.json +43 -0
  57. package/cpmm/artifacts/scripts/EnableFeeCollector.ral.json +28 -0
  58. package/cpmm/artifacts/scripts/Mint.ral.json +34 -0
  59. package/cpmm/artifacts/scripts/RemoveLiquidity.ral.json +43 -0
  60. package/cpmm/artifacts/scripts/SetFeeCollectorFactory.ral.json +28 -0
  61. package/cpmm/artifacts/scripts/Swap.ral.json +46 -0
  62. package/cpmm/artifacts/scripts/SwapMaxIn.ral.json +46 -0
  63. package/cpmm/artifacts/scripts/SwapMinOut.ral.json +46 -0
  64. package/cpmm/artifacts/test/GetToken.ral.json +31 -0
  65. package/cpmm/artifacts/test/MathTest.ral.json +49 -0
  66. package/cpmm/artifacts/test/TestToken.ral.json +87 -0
  67. package/cpmm/artifacts/ts/DexAccount.ts +329 -0
  68. package/cpmm/artifacts/ts/ExampleOracleSimple.ts +383 -0
  69. package/cpmm/artifacts/ts/FeeCollectorFactoryImpl.ts +227 -0
  70. package/cpmm/artifacts/ts/FeeCollectorPerTokenPairImpl.ts +327 -0
  71. package/cpmm/artifacts/ts/FullMathTest.ts +251 -0
  72. package/cpmm/artifacts/ts/MathTest.ts +183 -0
  73. package/cpmm/artifacts/ts/Router.ts +554 -0
  74. package/cpmm/artifacts/ts/TestToken.ts +312 -0
  75. package/cpmm/artifacts/ts/TokenPair.ts +947 -0
  76. package/cpmm/artifacts/ts/TokenPairFactory.ts +501 -0
  77. package/cpmm/artifacts/ts/contracts.ts +26 -0
  78. package/cpmm/artifacts/ts/deployments.ts +109 -0
  79. package/cpmm/artifacts/ts/index.ts +16 -0
  80. package/cpmm/artifacts/ts/scripts.ts +142 -0
  81. package/cpmm/deployments/.deployments.devnet.json +77 -0
  82. package/cpmm/deployments/.deployments.testnet.json +79 -0
  83. package/lib/index.d.mts +8800 -0
  84. package/lib/index.d.ts +8800 -0
  85. package/lib/index.js +21769 -0
  86. package/lib/index.js.map +1 -0
  87. package/lib/index.mjs +22118 -0
  88. package/lib/index.mjs.map +1 -0
  89. package/package.json +80 -0
  90. package/src/clmm/clmm.ts +607 -0
  91. package/src/clmm/constants.ts +7 -0
  92. package/src/clmm/index.ts +6 -0
  93. package/src/clmm/liquidity.ts +163 -0
  94. package/src/clmm/pool.ts +154 -0
  95. package/src/clmm/tick.ts +335 -0
  96. package/src/clmm/types.ts +155 -0
  97. package/src/common/constants.ts +1 -0
  98. package/src/common/error.ts +46 -0
  99. package/src/common/index.ts +7 -0
  100. package/src/common/logger.ts +82 -0
  101. package/src/common/math.ts +88 -0
  102. package/src/common/numeric.ts +64 -0
  103. package/src/common/types.ts +49 -0
  104. package/src/common/utils.ts +3 -0
  105. package/src/cpmm/constants.ts +2 -0
  106. package/src/cpmm/cpmm.ts +631 -0
  107. package/src/cpmm/index.ts +3 -0
  108. package/src/cpmm/types.ts +113 -0
  109. package/src/index.ts +25 -0
  110. package/src/moduleBase.ts +64 -0
  111. package/src/staking/index.ts +4 -0
  112. package/src/staking/settings.ts +38 -0
  113. package/src/staking/staking.ts +277 -0
  114. package/src/staking/types.ts +15 -0
  115. package/src/staking/utils.ts +25 -0
  116. package/src/token/index.ts +1 -0
  117. package/src/token/token.ts +163 -0
  118. package/src/zeta.ts +105 -0
  119. package/staking/artifacts/AlphStakeAndLock.ral.json +31 -0
  120. package/staking/artifacts/AlphUnstakeVault.ral.json +151 -0
  121. package/staking/artifacts/XAlphStakeVault.ral.json +559 -0
  122. package/staking/artifacts/XAlphToken.ral.json +404 -0
  123. package/staking/artifacts/XAlphUnlockAndStartUnstake.ral.json +31 -0
  124. package/staking/artifacts/examples/GovernanceDemo.ral.json +282 -0
  125. package/staking/artifacts/examples/RewardSharingVault.ral.json +253 -0
  126. package/staking/artifacts/structs.ral.json +47 -0
  127. package/staking/artifacts/ts/AlphUnstakeVault.ts +354 -0
  128. package/staking/artifacts/ts/FullMathTest.ts +175 -0
  129. package/staking/artifacts/ts/GovernanceDemo.ts +726 -0
  130. package/staking/artifacts/ts/RewardSharingVault.ts +559 -0
  131. package/staking/artifacts/ts/TestDynamicArrayByteVec32.ts +431 -0
  132. package/staking/artifacts/ts/TestDynamicSortedArrayForU256.ts +516 -0
  133. package/staking/artifacts/ts/TestMerkleProof.ts +343 -0
  134. package/staking/artifacts/ts/XAlphStakeVault.ts +1120 -0
  135. package/staking/artifacts/ts/XAlphToken.ts +835 -0
  136. package/staking/artifacts/ts/contracts.ts +26 -0
  137. package/staking/artifacts/ts/deployments.ts +109 -0
  138. package/staking/artifacts/ts/index.ts +15 -0
  139. package/staking/artifacts/ts/scripts.ts +35 -0
  140. package/staking/artifacts/ts/types.ts +19 -0
  141. package/staking/artifacts/utils/FullMathTest.ral.json +57 -0
  142. package/staking/artifacts/utils/TestDynamicArrayByteVec32.ral.json +165 -0
  143. package/staking/artifacts/utils/TestDynamicSortedArrayForU256.ral.json +189 -0
  144. package/staking/artifacts/utils/TestMerkleProof.ral.json +134 -0
  145. package/staking/deployments/.deployments.devnet.json +77 -0
  146. package/staking/deployments/.deployments.testnet.json +78 -0
@@ -0,0 +1,163 @@
1
+ import type { TokenInfo } from '@alephium/token-list';
2
+ import { TickUtils } from './tick';
3
+ import { MathUtil } from '../common/math';
4
+ import { Q96 } from 'clmm/artifacts/ts/constants';
5
+ import type { GetPositionAmountsFromPriceProps, GetPositionAmountsFromPriceReturn } from './types';
6
+
7
+ export class ClmmLiquidityUtils {
8
+ static getPositionAmountsFromPrice(
9
+ p: GetPositionAmountsFromPriceProps,
10
+ ): GetPositionAmountsFromPriceReturn {
11
+ if (p.amountBase === 0n || p.amountQuote === 0n) {
12
+ return { newAmountBase: 0n, newAmountQuote: 0n, liquidity: 0n };
13
+ }
14
+ const amounts = [p.amountBase, p.amountQuote];
15
+ const sqrts = [
16
+ TickUtils.getSqrtRatioAtTick(p.lowerTick),
17
+ TickUtils.getSqrtRatioAtTick(p.upperTick),
18
+ ];
19
+ const reverse1 = p.lowerTick > p.upperTick;
20
+ if (reverse1) {
21
+ sqrts.reverse();
22
+ }
23
+ const reverse2 = p.tokenBaseId > p.tokenQuoteId;
24
+ if (reverse2) {
25
+ amounts.reverse();
26
+ }
27
+ const [amount0, amount1, liquidity] = this.getAmountsAndLiquidityAtSqrtPrice(
28
+ p.sqrtRatioX96,
29
+ sqrts[0],
30
+ sqrts[1],
31
+ amounts[0],
32
+ amounts[1],
33
+ );
34
+ return reverse2
35
+ ? { newAmountBase: amount1, newAmountQuote: amount0, liquidity }
36
+ : { newAmountBase: amount0, newAmountQuote: amount1, liquidity };
37
+ }
38
+
39
+ static getAmountsAndLiquidityAtPrice(
40
+ currentPrice: number,
41
+ token0: TokenInfo,
42
+ token1: TokenInfo,
43
+ lowerTick: bigint,
44
+ upperTick: bigint,
45
+ amount0: bigint,
46
+ amount1: bigint,
47
+ ): [bigint, bigint, bigint] {
48
+ const sqrtRatioX96 = TickUtils.priceToSqrtPriceX96(
49
+ currentPrice,
50
+ token0.decimals,
51
+ token1.decimals,
52
+ );
53
+ const sqrtRatioAX96 = TickUtils.getSqrtRatioAtTick(lowerTick);
54
+ const sqrtRatioBX96 = TickUtils.getSqrtRatioAtTick(upperTick);
55
+ return this.getAmountsAndLiquidityAtSqrtPrice(
56
+ sqrtRatioX96,
57
+ sqrtRatioAX96,
58
+ sqrtRatioBX96,
59
+ amount0,
60
+ amount1,
61
+ );
62
+ }
63
+
64
+ static getAmountsAndLiquidityAtSqrtPrice(
65
+ sqrtRatioX96: bigint,
66
+ sqrtRatioAX96: bigint,
67
+ sqrtRatioBX96: bigint,
68
+ amount0: bigint,
69
+ amount1: bigint,
70
+ ): [bigint, bigint, bigint] {
71
+ const liquidity = this.getLiquidityFromAmounts(
72
+ sqrtRatioX96,
73
+ sqrtRatioAX96,
74
+ sqrtRatioBX96,
75
+ amount0,
76
+ amount1,
77
+ );
78
+ const [a0, a1] = this.getAmountsForLiquidity(
79
+ sqrtRatioX96,
80
+ sqrtRatioAX96,
81
+ sqrtRatioBX96,
82
+ -liquidity,
83
+ );
84
+ return [-a0, -a1, liquidity];
85
+ }
86
+
87
+ static getAmountsForLiquidity(
88
+ sqrtRatioX96: bigint,
89
+ sqrtRatioAX96: bigint,
90
+ sqrtRatioBX96: bigint,
91
+ liquidity: bigint,
92
+ ): [bigint, bigint] {
93
+ if (sqrtRatioX96 <= sqrtRatioAX96) {
94
+ const amount0 = this.getToken0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity);
95
+ return [amount0, 0n];
96
+ } else if (sqrtRatioX96 < sqrtRatioBX96) {
97
+ const amount0 = this.getToken0Delta(sqrtRatioX96, sqrtRatioBX96, liquidity);
98
+ const amount1 = this.getToken1Delta(sqrtRatioAX96, sqrtRatioX96, liquidity);
99
+ return [amount0, amount1];
100
+ } else {
101
+ const amount1 = this.getToken1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity);
102
+ return [0n, amount1];
103
+ }
104
+ }
105
+
106
+ static getLiquidityFromAmounts(
107
+ sqrtRatioX96: bigint,
108
+ sqrtRatioAX96: bigint,
109
+ sqrtRatioBX96: bigint,
110
+ amount0: bigint,
111
+ amount1: bigint,
112
+ ): bigint {
113
+ if (sqrtRatioX96 < sqrtRatioAX96) {
114
+ return this.getLiquidityFromToken0(sqrtRatioAX96, sqrtRatioBX96, amount0);
115
+ } else if (sqrtRatioX96 < sqrtRatioBX96) {
116
+ const liquidity0 = this.getLiquidityFromToken0(sqrtRatioX96, sqrtRatioBX96, amount0);
117
+ const liquidity1 = this.getLiquidityFromToken1(sqrtRatioAX96, sqrtRatioX96, amount1);
118
+ return liquidity0 < liquidity1 ? liquidity0 : liquidity1;
119
+ } else {
120
+ return this.getLiquidityFromToken1(sqrtRatioAX96, sqrtRatioBX96, amount1);
121
+ }
122
+ }
123
+
124
+ static getLiquidityFromToken0(
125
+ sqrtRatioAX96: bigint,
126
+ sqrtRatioBX96: bigint,
127
+ amount0: bigint,
128
+ ): bigint {
129
+ const intermediate = MathUtil.alphDiv(sqrtRatioAX96 * sqrtRatioBX96, Q96);
130
+ return MathUtil.alphDiv(amount0 * intermediate, sqrtRatioBX96 - sqrtRatioAX96);
131
+ }
132
+
133
+ static getLiquidityFromToken1(
134
+ sqrtRatioAX96: bigint,
135
+ sqrtRatioBX96: bigint,
136
+ amount1: bigint,
137
+ ): bigint {
138
+ return MathUtil.alphDiv(amount1 * Q96, sqrtRatioBX96 - sqrtRatioAX96);
139
+ }
140
+
141
+ static getAmountDelta(
142
+ sqrtRatioAX96: bigint,
143
+ sqrtRatioBX96: bigint,
144
+ liquidity: bigint,
145
+ zeroForOne: boolean,
146
+ ): bigint {
147
+ if (zeroForOne) {
148
+ return this.getToken0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity);
149
+ } else {
150
+ return this.getToken1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity);
151
+ }
152
+ }
153
+
154
+ static getToken0Delta(sqrtRatioAX96: bigint, sqrtRatioBX96: bigint, liquidity: bigint): bigint {
155
+ const numerator1 = liquidity * Q96;
156
+ const numerator2 = sqrtRatioBX96 - sqrtRatioAX96;
157
+ return MathUtil.alphDiv(numerator1 * numerator2, sqrtRatioBX96 * sqrtRatioAX96);
158
+ }
159
+
160
+ static getToken1Delta(sqrtRatioAX96: bigint, sqrtRatioBX96: bigint, liquidity: bigint): bigint {
161
+ return MathUtil.alphDiv(liquidity * (sqrtRatioBX96 - sqrtRatioAX96), Q96);
162
+ }
163
+ }
@@ -0,0 +1,154 @@
1
+ import {
2
+ binToHex,
3
+ contractIdFromAddress,
4
+ encodePrimitiveValues,
5
+ groupOfAddress,
6
+ isGrouplessAddressWithoutGroupIndex,
7
+ subContractId,
8
+ } from '@alephium/web3';
9
+ import { Pool } from 'clmm/artifacts/ts/Pool';
10
+ import { TickUtils } from './tick';
11
+ import type { LiquidityDistribution } from './types';
12
+ import { ClmmLiquidityUtils } from './liquidity';
13
+ import { MathUtil } from '../common/math';
14
+
15
+ function normalizeAddress(address: string, group: number): string {
16
+ return isGrouplessAddressWithoutGroupIndex(address) ? `${address}:${group}` : address;
17
+ }
18
+
19
+ export class PoolUtils {
20
+ static getPositionId(
21
+ poolAddress: string,
22
+ owner: string,
23
+ tickLower: bigint,
24
+ tickUpper: bigint,
25
+ ): string {
26
+ const group = groupOfAddress(poolAddress);
27
+ const poolId = binToHex(contractIdFromAddress(poolAddress));
28
+ const normalizedOwner = normalizeAddress(owner, group);
29
+ const path = encodePrimitiveValues([
30
+ { type: 'U256', value: Pool.consts.PathPrefixes.Position },
31
+ { type: 'Address', value: normalizedOwner },
32
+ { type: 'I256', value: tickLower },
33
+ { type: 'I256', value: tickUpper },
34
+ ]);
35
+ return subContractId(poolId, binToHex(path), group);
36
+ }
37
+
38
+ static computeSwapStep(
39
+ sqrtPriceX96: bigint,
40
+ sqrtPriceTargetX96: bigint,
41
+ liquidity: bigint,
42
+ amount: bigint,
43
+ feePips: bigint,
44
+ ): [bigint, bigint, bigint, bigint] {
45
+ const zeroForOne = sqrtPriceX96 < sqrtPriceTargetX96;
46
+ const exactIn = amount > 0n;
47
+ let amountIn = 0n;
48
+ let amountOut = 0n;
49
+ let feeAmount = 0n;
50
+ let sqrtPriceNextX96 = 0n;
51
+ if (exactIn) {
52
+ amountIn = -ClmmLiquidityUtils.getAmountDelta(
53
+ sqrtPriceX96,
54
+ sqrtPriceTargetX96,
55
+ -liquidity,
56
+ zeroForOne,
57
+ );
58
+ const amountRemainingLessFee = MathUtil.divFloor(
59
+ amount * (Pool.consts.MAX_PIPS - feePips),
60
+ Pool.consts.MAX_PIPS,
61
+ );
62
+ const sqrtPriceRealTargetX96 = TickUtils.getNextSqrtPrice(
63
+ sqrtPriceX96,
64
+ liquidity,
65
+ amountRemainingLessFee,
66
+ zeroForOne,
67
+ );
68
+ sqrtPriceNextX96 =
69
+ amountRemainingLessFee >= amountIn ? sqrtPriceTargetX96 : sqrtPriceRealTargetX96;
70
+ } else {
71
+ amountOut = ClmmLiquidityUtils.getAmountDelta(
72
+ sqrtPriceTargetX96,
73
+ sqrtPriceX96,
74
+ liquidity,
75
+ zeroForOne,
76
+ );
77
+ const sqrtPriceRealTargetX96 = TickUtils.getNextSqrtPrice(
78
+ sqrtPriceX96,
79
+ liquidity,
80
+ amount,
81
+ zeroForOne,
82
+ );
83
+ sqrtPriceNextX96 = -amount >= amountOut ? sqrtPriceTargetX96 : sqrtPriceRealTargetX96;
84
+ }
85
+ const max = sqrtPriceTargetX96 == sqrtPriceNextX96;
86
+ if (zeroForOne) {
87
+ const amountIn2 = -ClmmLiquidityUtils.getAmountDelta(
88
+ sqrtPriceNextX96,
89
+ sqrtPriceX96,
90
+ -liquidity,
91
+ zeroForOne,
92
+ );
93
+ const amountOut2 = ClmmLiquidityUtils.getAmountDelta(
94
+ sqrtPriceNextX96,
95
+ sqrtPriceX96,
96
+ liquidity,
97
+ zeroForOne,
98
+ );
99
+ amountIn = max && exactIn ? amountIn : amountIn2;
100
+ amountOut = max && !exactIn ? amountOut : amountOut2;
101
+ } else {
102
+ const amountIn2 = -ClmmLiquidityUtils.getAmountDelta(
103
+ sqrtPriceX96,
104
+ sqrtPriceNextX96,
105
+ -liquidity,
106
+ zeroForOne,
107
+ );
108
+ const amountOut2 = ClmmLiquidityUtils.getAmountDelta(
109
+ sqrtPriceX96,
110
+ sqrtPriceNextX96,
111
+ liquidity,
112
+ zeroForOne,
113
+ );
114
+ amountIn = max && exactIn ? amountIn : amountIn2;
115
+ amountOut = max && !exactIn ? amountOut : amountOut2;
116
+ }
117
+ if (!exactIn && amount > -amountOut) amountOut = -amount;
118
+
119
+ if (exactIn && sqrtPriceNextX96 != sqrtPriceTargetX96) {
120
+ feeAmount = amount - amountIn;
121
+ } else {
122
+ feeAmount = MathUtil.divFloor(amountIn * feePips, Pool.consts.MAX_PIPS - feePips);
123
+ }
124
+
125
+ return [sqrtPriceNextX96, amountIn, amountOut, feeAmount];
126
+ }
127
+
128
+ static offlineSwap(
129
+ liqDist: LiquidityDistribution,
130
+ amountSpecified: bigint,
131
+ sqrtPriceX96: bigint,
132
+ ): bigint {
133
+ const exactIn = amountSpecified > 0n;
134
+ let amountCalculated = 0n;
135
+ for (const row of liqDist.rows) {
136
+ const [sqrtPriceNextX96, amountIn, amountOut, feeAmount] = this.computeSwapStep(
137
+ sqrtPriceX96,
138
+ row.sqrtPriceX96,
139
+ liqDist.liquidity,
140
+ amountSpecified,
141
+ liqDist.fee,
142
+ );
143
+ if (exactIn) {
144
+ amountSpecified -= amountIn + feeAmount;
145
+ amountCalculated -= amountOut;
146
+ } else {
147
+ amountSpecified += amountOut;
148
+ amountCalculated += amountIn + feeAmount;
149
+ }
150
+ sqrtPriceX96 = sqrtPriceNextX96;
151
+ }
152
+ return amountCalculated;
153
+ }
154
+ }
@@ -0,0 +1,335 @@
1
+ import {
2
+ MAX_SQRT_RATIO,
3
+ MAX_TICK,
4
+ MIN_SQRT_RATIO,
5
+ MIN_TICK,
6
+ Q128,
7
+ Q32,
8
+ Q64,
9
+ Q96,
10
+ } from 'clmm/artifacts/ts/constants';
11
+ import { U256_MAX } from './constants';
12
+ import Decimal from 'decimal.js';
13
+ import { MathUtil } from '../common/math';
14
+ import { BPS } from '../common/constants';
15
+ import type { TokenInfo } from '@alephium/token-list';
16
+
17
+ export class TickUtils {
18
+ static getSqrtRatioAtTick(tick: bigint): bigint {
19
+ const absTick = tick < 0n ? -tick : tick;
20
+ if (absTick > MAX_TICK) {
21
+ throw new Error(`TickOutOfBounds: ${tick}`);
22
+ }
23
+
24
+ let ratio = (absTick & 0x1n) !== 0n ? 0xfffcb933bd6fad37aa2d162d1a594001n : Q128;
25
+
26
+ if ((absTick & 0x2n) !== 0n) ratio = (ratio * 0xfff97272373d413259a46990580e213an) >> 128n;
27
+ if ((absTick & 0x4n) !== 0n) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdccn) >> 128n;
28
+ if ((absTick & 0x8n) !== 0n) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0n) >> 128n;
29
+ if ((absTick & 0x10n) !== 0n) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644n) >> 128n;
30
+ if ((absTick & 0x20n) !== 0n) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0n) >> 128n;
31
+ if ((absTick & 0x40n) !== 0n) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861n) >> 128n;
32
+ if ((absTick & 0x80n) !== 0n) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053n) >> 128n;
33
+ if ((absTick & 0x100n) !== 0n) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4n) >> 128n;
34
+ if ((absTick & 0x200n) !== 0n) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54n) >> 128n;
35
+ if ((absTick & 0x400n) !== 0n) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3n) >> 128n;
36
+ if ((absTick & 0x800n) !== 0n) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9n) >> 128n;
37
+ if ((absTick & 0x1000n) !== 0n) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825n) >> 128n;
38
+ if ((absTick & 0x2000n) !== 0n) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5n) >> 128n;
39
+ if ((absTick & 0x4000n) !== 0n) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7n) >> 128n;
40
+ if ((absTick & 0x8000n) !== 0n) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6n) >> 128n;
41
+ if ((absTick & 0x10000n) !== 0n) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9n) >> 128n;
42
+ if ((absTick & 0x20000n) !== 0n) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604n) >> 128n;
43
+ if ((absTick & 0x40000n) !== 0n) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98n) >> 128n;
44
+ if ((absTick & 0x80000n) !== 0n) ratio = (ratio * 0x48a170391f7dc42444e8fa2n) >> 128n;
45
+
46
+ if (tick > 0n) {
47
+ ratio = U256_MAX / ratio;
48
+ }
49
+
50
+ return ratio >> 32n;
51
+ }
52
+
53
+ static getTickAtSqrtRatio(sqrtPriceX96: bigint): bigint {
54
+ if (sqrtPriceX96 < MIN_SQRT_RATIO || sqrtPriceX96 >= MAX_SQRT_RATIO) {
55
+ throw new Error('SqrtPriceX96OutOfBounds');
56
+ }
57
+
58
+ const ratio = sqrtPriceX96 << 32n;
59
+ let r = ratio;
60
+ let msb = 0n;
61
+
62
+ if (r > Q128 - 1n) {
63
+ msb |= 0x80n;
64
+ r >>= 0x80n;
65
+ }
66
+ if (r > Q64 - 1n) {
67
+ msb |= 0x40n;
68
+ r >>= 0x40n;
69
+ }
70
+ if (r > Q32 - 1n) {
71
+ msb |= 0x20n;
72
+ r >>= 0x20n;
73
+ }
74
+ if (r > 0xffffn) {
75
+ msb |= 0x10n;
76
+ r >>= 0x10n;
77
+ }
78
+ if (r > 0xffn) {
79
+ msb |= 0x8n;
80
+ r >>= 0x8n;
81
+ }
82
+ if (r > 0xfn) {
83
+ msb |= 0x4n;
84
+ r >>= 0x4n;
85
+ }
86
+ if (r > 0x3n) {
87
+ msb |= 0x2n;
88
+ r >>= 0x2n;
89
+ }
90
+ if (r > 0x1n) {
91
+ msb |= 0x1n;
92
+ }
93
+
94
+ if (msb >= 128n) {
95
+ r = ratio >> (msb - 127n);
96
+ } else {
97
+ r = ratio << (127n - msb);
98
+ }
99
+
100
+ let log_2 = (msb - 128n) << 64n;
101
+
102
+ for (let i = 63n; i >= 50n; i--) {
103
+ r = (r * r) >> 127n;
104
+ const f = r >> 128n;
105
+ log_2 |= f << i;
106
+ r >>= f;
107
+ }
108
+
109
+ const log_sqrt10001 = log_2 * 255738958999603826347141n;
110
+
111
+ const tickLow = (log_sqrt10001 - 3402992956809132418596140100660247210n) >> 128n;
112
+ const tickHi = (log_sqrt10001 + 291339464771989622907027621153398088495n) >> 128n;
113
+
114
+ if (tickLow === tickHi) {
115
+ return tickLow;
116
+ } else {
117
+ if (this.getSqrtRatioAtTick(tickHi) <= sqrtPriceX96) {
118
+ return tickHi;
119
+ } else {
120
+ return tickLow;
121
+ }
122
+ }
123
+ }
124
+
125
+ static priceToSqrtPriceX96(
126
+ price: Decimal.Value,
127
+ token0Decimal: number,
128
+ token1Decimal: number,
129
+ ): bigint {
130
+ const priceDecimal = new Decimal(price);
131
+ if (!priceDecimal.isFinite() || priceDecimal.lte(0)) {
132
+ throw new Error('Invalid price input');
133
+ }
134
+
135
+ const token0Unit = 10n ** BigInt(token0Decimal);
136
+ const token1Amount = BigInt(
137
+ priceDecimal
138
+ .mul(10n ** BigInt(token1Decimal))
139
+ .floor()
140
+ .toFixed(),
141
+ );
142
+ return this.priceSqrt(token1Amount, token0Unit);
143
+ }
144
+
145
+ static sqrtPriceX96ToPrice(
146
+ sqrtPriceX96: bigint,
147
+ token0Decimal: number,
148
+ token1Decimal: number,
149
+ ): number {
150
+ if (sqrtPriceX96 < 0n) {
151
+ throw new Error('Invalid sqrtPriceX96');
152
+ }
153
+
154
+ const sqrtPriceDecimal = new Decimal(sqrtPriceX96.toString());
155
+ const q96Decimal = new Decimal(Q96.toString());
156
+ let priceDecimal = sqrtPriceDecimal.div(q96Decimal).pow(2);
157
+
158
+ const exponentDiff = token0Decimal - token1Decimal;
159
+ if (exponentDiff !== 0) {
160
+ const factor = new Decimal(10).pow(Math.abs(exponentDiff));
161
+ priceDecimal = exponentDiff > 0 ? priceDecimal.mul(factor) : priceDecimal.div(factor);
162
+ }
163
+
164
+ if (!priceDecimal.isFinite()) {
165
+ throw new Error('Price overflow');
166
+ }
167
+
168
+ return priceDecimal.toNumber();
169
+ }
170
+
171
+ static getAlignedTick(
172
+ price: number,
173
+ token0Decimal: number,
174
+ token1Decimal: number,
175
+ tickSpacing: bigint,
176
+ ): bigint {
177
+ const sqrtPriceX96 = this.priceToSqrtPriceX96(price, token0Decimal, token1Decimal);
178
+ const tick0 = this.getTickAtSqrtRatio(sqrtPriceX96);
179
+
180
+ return MathUtil.alphCeil(tick0, tickSpacing) * tickSpacing;
181
+ }
182
+
183
+ static getNextTick(
184
+ tick: bigint,
185
+ tickSpacing: bigint,
186
+ tokenBase: TokenInfo,
187
+ tokenQuote: TokenInfo,
188
+ baseIn: boolean,
189
+ isAdd: boolean,
190
+ ): bigint {
191
+ const reverse = tokenBase.id > tokenQuote.id == baseIn;
192
+ const delta = isAdd == reverse ? tickSpacing : -tickSpacing;
193
+ return tick + delta;
194
+ }
195
+
196
+ static getAlignedPrice(
197
+ priceIn: number,
198
+ tokenBase: TokenInfo,
199
+ tokenQuote: TokenInfo,
200
+ tickSpacing: bigint,
201
+ baseIn: boolean,
202
+ ): { tick: bigint; price: number } {
203
+ const reverse = tokenBase.id > tokenQuote.id == baseIn;
204
+ const [token0, token1] = reverse ? [tokenQuote, tokenBase] : [tokenBase, tokenQuote];
205
+
206
+ const rawPrice = reverse ? 1 / priceIn : priceIn;
207
+ const tick = this.getAlignedTick(rawPrice, token0.decimals, token1.decimals, tickSpacing);
208
+ return this.getPriceFromTick(tick, tokenBase, tokenQuote, baseIn);
209
+ }
210
+
211
+ static getPriceFromTick(
212
+ tick: bigint,
213
+ tokenBase: TokenInfo,
214
+ tokenQuote: TokenInfo,
215
+ baseIn: boolean,
216
+ ): { tick: bigint; price: number } {
217
+ const reverse = tokenBase.id > tokenQuote.id == baseIn;
218
+ const [token0, token1] = reverse ? [tokenQuote, tokenBase] : [tokenBase, tokenQuote];
219
+
220
+ const sqrtPriceX96 = this.getSqrtRatioAtTick(tick);
221
+ const alignedPrice = this.sqrtPriceX96ToPrice(sqrtPriceX96, token0.decimals, token1.decimals);
222
+ const price = reverse ? 1 / alignedPrice : alignedPrice;
223
+ return { tick, price };
224
+ }
225
+
226
+ static getMinPriceFromTick(
227
+ tokenBase: TokenInfo,
228
+ tokenQuote: TokenInfo,
229
+ tickSpacing: bigint,
230
+ baseIn: boolean,
231
+ ): { tick: bigint; price: number } {
232
+ const reverse = tokenBase.id > tokenQuote.id == baseIn;
233
+ const rawTick = reverse ? MAX_TICK : MIN_TICK;
234
+ const tick = (rawTick / tickSpacing) * tickSpacing;
235
+ return this.getPriceFromTick(tick, tokenBase, tokenQuote, baseIn);
236
+ }
237
+
238
+ static getMaxPriceFromTick(
239
+ tokenBase: TokenInfo,
240
+ tokenQuote: TokenInfo,
241
+ tickSpacing: bigint,
242
+ baseIn: boolean,
243
+ ): { tick: bigint; price: number } {
244
+ const reverse = tokenBase.id > tokenQuote.id == baseIn;
245
+ const rawTick = reverse ? MIN_TICK : MAX_TICK;
246
+ const tick = (rawTick / tickSpacing) * tickSpacing;
247
+ return this.getPriceFromTick(tick, tokenBase, tokenQuote, baseIn);
248
+ }
249
+
250
+ static getNextSqrtPrice(
251
+ sqrtPriceX96: bigint,
252
+ liquidity: bigint,
253
+ amount: bigint,
254
+ zeroForOne: boolean,
255
+ ): bigint {
256
+ if (zeroForOne) {
257
+ return this.getNextSqrtPriceFromAmount0(sqrtPriceX96, liquidity, amount);
258
+ } else {
259
+ return this.getNextSqrtPriceFromAmount1(sqrtPriceX96, liquidity, amount);
260
+ }
261
+ }
262
+
263
+ static getNextSqrtPriceFromAmount0(
264
+ sqrtPriceX96: bigint,
265
+ liquidity: bigint,
266
+ amount: bigint,
267
+ ): bigint {
268
+ const numerator1 = liquidity * Q96;
269
+ const product = amount * sqrtPriceX96;
270
+ if (product >= numerator1) {
271
+ throw new Error('Amount0 exceeds available liquidity for the swap');
272
+ }
273
+ const denominator = numerator1 - product;
274
+ return MathUtil.alphDiv(numerator1 * sqrtPriceX96, denominator);
275
+ }
276
+
277
+ static getNextSqrtPriceFromAmount1(
278
+ sqrtPriceX96: bigint,
279
+ liquidity: bigint,
280
+ amount: bigint,
281
+ ): bigint {
282
+ const quotient = MathUtil.alphDiv(amount * Q96, liquidity);
283
+ return sqrtPriceX96 + quotient;
284
+ }
285
+
286
+ static getSqrtPriceLimitX96(sqrtPriceX96: bigint, slippage: bigint, zeroForOne: boolean): bigint {
287
+ if (slippage <= 0n || slippage >= BPS) {
288
+ throw new Error('slippageBps must be in (0, 10000)');
289
+ }
290
+
291
+ const [minBound, maxBound] = this.getSqrtPriceX96Bounds(sqrtPriceX96, slippage);
292
+ let limit = zeroForOne ? minBound : maxBound;
293
+
294
+ if (zeroForOne) {
295
+ if (limit >= sqrtPriceX96) limit = sqrtPriceX96 - 1n;
296
+ if (limit <= MIN_SQRT_RATIO) limit = MIN_SQRT_RATIO + 1n;
297
+ } else {
298
+ if (limit <= sqrtPriceX96) limit = sqrtPriceX96 + 1n;
299
+ if (limit >= MAX_SQRT_RATIO) limit = MAX_SQRT_RATIO - 1n;
300
+ }
301
+ return limit;
302
+ }
303
+
304
+ // Compute slippage bounds for a given sqrt price
305
+ // The slippage is in bps.
306
+ // Returns [minSqrtPriceX96, maxSqrtPriceX96], where:
307
+ // min = floor(sqrtPriceX96 * sqrt(1 - slippage))
308
+ // max = ceil(sqrtPriceX96 * sqrt(1 + slippage))
309
+ // `slippage` is bps: e.g., 100 for 1%, 30 for 0.3%
310
+ static getSqrtPriceX96Bounds(sqrtPriceX96: bigint, slippage: bigint): [bigint, bigint] {
311
+ if (slippage < 0n || slippage >= BPS) {
312
+ throw new Error('Invalid slippageBps; must be in [0, 10000)');
313
+ }
314
+ const numMinus = (BPS - slippage) * Q128;
315
+ const numPlus = (BPS + slippage) * Q128;
316
+ const den = BPS * Q128;
317
+ const factorMinusQ96 = MathUtil.mulDivFloor(
318
+ MathUtil.sqrt(numMinus) * Q96,
319
+ 1n,
320
+ MathUtil.sqrt(den),
321
+ );
322
+ const factorPlusQ96 = MathUtil.mulDivFloor(
323
+ MathUtil.sqrt(numPlus) * Q96,
324
+ 1n,
325
+ MathUtil.sqrt(den),
326
+ );
327
+ const minSqrtPriceX96 = MathUtil.mulDivFloor(sqrtPriceX96, factorMinusQ96, Q96);
328
+ const maxSqrtPriceX96 = (sqrtPriceX96 * factorPlusQ96 + Q96 - 1n) / Q96;
329
+ return [minSqrtPriceX96, maxSqrtPriceX96];
330
+ }
331
+
332
+ private static priceSqrt(r1: bigint, r0: bigint): bigint {
333
+ return MathUtil.sqrt(MathUtil.divFloor(r1 * Q96 * Q96, r0));
334
+ }
335
+ }