@byreal-io/byreal-clmm-sdk 0.2.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 (181) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +341 -0
  3. package/dist/esm/client/chain.d.ts +227 -0
  4. package/dist/esm/client/chain.d.ts.map +1 -0
  5. package/dist/esm/client/chain.js +1123 -0
  6. package/dist/esm/client/chain.js.map +1 -0
  7. package/dist/esm/client/index.d.ts +4 -0
  8. package/dist/esm/client/index.d.ts.map +1 -0
  9. package/dist/esm/client/index.js +4 -0
  10. package/dist/esm/client/index.js.map +1 -0
  11. package/dist/esm/client/models.d.ts +160 -0
  12. package/dist/esm/client/models.d.ts.map +1 -0
  13. package/dist/esm/client/models.js +2 -0
  14. package/dist/esm/client/models.js.map +1 -0
  15. package/dist/esm/client/utils.d.ts +103 -0
  16. package/dist/esm/client/utils.d.ts.map +1 -0
  17. package/dist/esm/client/utils.js +238 -0
  18. package/dist/esm/client/utils.js.map +1 -0
  19. package/dist/esm/constants.d.ts +19 -0
  20. package/dist/esm/constants.d.ts.map +1 -0
  21. package/dist/esm/constants.js +27 -0
  22. package/dist/esm/constants.js.map +1 -0
  23. package/dist/esm/index.d.ts +5 -0
  24. package/dist/esm/index.d.ts.map +1 -0
  25. package/dist/esm/index.js +5 -0
  26. package/dist/esm/index.js.map +1 -0
  27. package/dist/esm/instructions/baseInstruction.d.ts +74 -0
  28. package/dist/esm/instructions/baseInstruction.d.ts.map +1 -0
  29. package/dist/esm/instructions/baseInstruction.js +577 -0
  30. package/dist/esm/instructions/baseInstruction.js.map +1 -0
  31. package/dist/esm/instructions/constants.d.ts +29 -0
  32. package/dist/esm/instructions/constants.d.ts.map +1 -0
  33. package/dist/esm/instructions/constants.js +32 -0
  34. package/dist/esm/instructions/constants.js.map +1 -0
  35. package/dist/esm/instructions/getRawData.d.ts +60 -0
  36. package/dist/esm/instructions/getRawData.d.ts.map +1 -0
  37. package/dist/esm/instructions/getRawData.js +105 -0
  38. package/dist/esm/instructions/getRawData.js.map +1 -0
  39. package/dist/esm/instructions/index.d.ts +9 -0
  40. package/dist/esm/instructions/index.d.ts.map +1 -0
  41. package/dist/esm/instructions/index.js +9 -0
  42. package/dist/esm/instructions/index.js.map +1 -0
  43. package/dist/esm/instructions/instruction.d.ts +137 -0
  44. package/dist/esm/instructions/instruction.d.ts.map +1 -0
  45. package/dist/esm/instructions/instruction.js +152 -0
  46. package/dist/esm/instructions/instruction.js.map +1 -0
  47. package/dist/esm/instructions/layout.d.ts +217 -0
  48. package/dist/esm/instructions/layout.d.ts.map +1 -0
  49. package/dist/esm/instructions/layout.js +203 -0
  50. package/dist/esm/instructions/layout.js.map +1 -0
  51. package/dist/esm/instructions/libs/marshmallow/bufferLayout.d.ts +107 -0
  52. package/dist/esm/instructions/libs/marshmallow/bufferLayout.d.ts.map +1 -0
  53. package/dist/esm/instructions/libs/marshmallow/bufferLayout.js +49 -0
  54. package/dist/esm/instructions/libs/marshmallow/bufferLayout.js.map +1 -0
  55. package/dist/esm/instructions/libs/marshmallow/index.d.ts +88 -0
  56. package/dist/esm/instructions/libs/marshmallow/index.d.ts.map +1 -0
  57. package/dist/esm/instructions/libs/marshmallow/index.js +256 -0
  58. package/dist/esm/instructions/libs/marshmallow/index.js.map +1 -0
  59. package/dist/esm/instructions/models.d.ts +39 -0
  60. package/dist/esm/instructions/models.d.ts.map +1 -0
  61. package/dist/esm/instructions/models.js +14 -0
  62. package/dist/esm/instructions/models.js.map +1 -0
  63. package/dist/esm/instructions/pda.d.ts +52 -0
  64. package/dist/esm/instructions/pda.d.ts.map +1 -0
  65. package/dist/esm/instructions/pda.js +47 -0
  66. package/dist/esm/instructions/pda.js.map +1 -0
  67. package/dist/esm/instructions/target/idl/byreal_amm_v3.json +7242 -0
  68. package/dist/esm/instructions/target/idl/byreal_amm_v3_test.json +7680 -0
  69. package/dist/esm/instructions/target/types/byreal_amm_v3.d.ts +5329 -0
  70. package/dist/esm/instructions/target/types/byreal_amm_v3.d.ts.map +1 -0
  71. package/dist/esm/instructions/target/types/byreal_amm_v3.js +2 -0
  72. package/dist/esm/instructions/target/types/byreal_amm_v3.js.map +1 -0
  73. package/dist/esm/instructions/target/types/raydium_amm_v3.d.ts +4149 -0
  74. package/dist/esm/instructions/target/types/raydium_amm_v3.d.ts.map +1 -0
  75. package/dist/esm/instructions/target/types/raydium_amm_v3.js +2 -0
  76. package/dist/esm/instructions/target/types/raydium_amm_v3.js.map +1 -0
  77. package/dist/esm/instructions/utils/binaryUtils.d.ts +11 -0
  78. package/dist/esm/instructions/utils/binaryUtils.d.ts.map +1 -0
  79. package/dist/esm/instructions/utils/binaryUtils.js +77 -0
  80. package/dist/esm/instructions/utils/binaryUtils.js.map +1 -0
  81. package/dist/esm/instructions/utils/fetchWalletTokenAccounts.d.ts +42 -0
  82. package/dist/esm/instructions/utils/fetchWalletTokenAccounts.d.ts.map +1 -0
  83. package/dist/esm/instructions/utils/fetchWalletTokenAccounts.js +63 -0
  84. package/dist/esm/instructions/utils/fetchWalletTokenAccounts.js.map +1 -0
  85. package/dist/esm/instructions/utils/getTickArrayBitmapExtension.d.ts +8 -0
  86. package/dist/esm/instructions/utils/getTickArrayBitmapExtension.d.ts.map +1 -0
  87. package/dist/esm/instructions/utils/getTickArrayBitmapExtension.js +22 -0
  88. package/dist/esm/instructions/utils/getTickArrayBitmapExtension.js.map +1 -0
  89. package/dist/esm/instructions/utils/getTickArrayInfo.d.ts +16 -0
  90. package/dist/esm/instructions/utils/getTickArrayInfo.d.ts.map +1 -0
  91. package/dist/esm/instructions/utils/getTickArrayInfo.js +75 -0
  92. package/dist/esm/instructions/utils/getTickArrayInfo.js.map +1 -0
  93. package/dist/esm/instructions/utils/index.d.ts +21 -0
  94. package/dist/esm/instructions/utils/index.d.ts.map +1 -0
  95. package/dist/esm/instructions/utils/index.js +21 -0
  96. package/dist/esm/instructions/utils/index.js.map +1 -0
  97. package/dist/esm/instructions/utils/liquidityMath.d.ts +30 -0
  98. package/dist/esm/instructions/utils/liquidityMath.d.ts.map +1 -0
  99. package/dist/esm/instructions/utils/liquidityMath.js +138 -0
  100. package/dist/esm/instructions/utils/liquidityMath.js.map +1 -0
  101. package/dist/esm/instructions/utils/mathUtils.d.ts +11 -0
  102. package/dist/esm/instructions/utils/mathUtils.d.ts.map +1 -0
  103. package/dist/esm/instructions/utils/mathUtils.js +36 -0
  104. package/dist/esm/instructions/utils/mathUtils.js.map +1 -0
  105. package/dist/esm/instructions/utils/models.d.ts +93 -0
  106. package/dist/esm/instructions/utils/models.d.ts.map +1 -0
  107. package/dist/esm/instructions/utils/models.js +13 -0
  108. package/dist/esm/instructions/utils/models.js.map +1 -0
  109. package/dist/esm/instructions/utils/poolStateUtils.d.ts +134 -0
  110. package/dist/esm/instructions/utils/poolStateUtils.d.ts.map +1 -0
  111. package/dist/esm/instructions/utils/poolStateUtils.js +137 -0
  112. package/dist/esm/instructions/utils/poolStateUtils.js.map +1 -0
  113. package/dist/esm/instructions/utils/poolUtils.d.ts +136 -0
  114. package/dist/esm/instructions/utils/poolUtils.d.ts.map +1 -0
  115. package/dist/esm/instructions/utils/poolUtils.js +219 -0
  116. package/dist/esm/instructions/utils/poolUtils.js.map +1 -0
  117. package/dist/esm/instructions/utils/position.d.ts +36 -0
  118. package/dist/esm/instructions/utils/position.d.ts.map +1 -0
  119. package/dist/esm/instructions/utils/position.js +86 -0
  120. package/dist/esm/instructions/utils/position.js.map +1 -0
  121. package/dist/esm/instructions/utils/sqrtPriceMath.d.ts +14 -0
  122. package/dist/esm/instructions/utils/sqrtPriceMath.d.ts.map +1 -0
  123. package/dist/esm/instructions/utils/sqrtPriceMath.js +168 -0
  124. package/dist/esm/instructions/utils/sqrtPriceMath.js.map +1 -0
  125. package/dist/esm/instructions/utils/swapMath.d.ts +48 -0
  126. package/dist/esm/instructions/utils/swapMath.d.ts.map +1 -0
  127. package/dist/esm/instructions/utils/swapMath.js +263 -0
  128. package/dist/esm/instructions/utils/swapMath.js.map +1 -0
  129. package/dist/esm/instructions/utils/tick.d.ts +106 -0
  130. package/dist/esm/instructions/utils/tick.d.ts.map +1 -0
  131. package/dist/esm/instructions/utils/tick.js +390 -0
  132. package/dist/esm/instructions/utils/tick.js.map +1 -0
  133. package/dist/esm/instructions/utils/tickArrayUtils.d.ts +50 -0
  134. package/dist/esm/instructions/utils/tickArrayUtils.d.ts.map +1 -0
  135. package/dist/esm/instructions/utils/tickArrayUtils.js +157 -0
  136. package/dist/esm/instructions/utils/tickArrayUtils.js.map +1 -0
  137. package/dist/esm/instructions/utils/tickMath.d.ts +18 -0
  138. package/dist/esm/instructions/utils/tickMath.d.ts.map +1 -0
  139. package/dist/esm/instructions/utils/tickMath.js +34 -0
  140. package/dist/esm/instructions/utils/tickMath.js.map +1 -0
  141. package/dist/esm/instructions/utils/tickarrayBitmap.d.ts +38 -0
  142. package/dist/esm/instructions/utils/tickarrayBitmap.d.ts.map +1 -0
  143. package/dist/esm/instructions/utils/tickarrayBitmap.js +157 -0
  144. package/dist/esm/instructions/utils/tickarrayBitmap.js.map +1 -0
  145. package/dist/esm/instructions/utils/transfer.d.ts +7 -0
  146. package/dist/esm/instructions/utils/transfer.d.ts.map +1 -0
  147. package/dist/esm/instructions/utils/transfer.js +78 -0
  148. package/dist/esm/instructions/utils/transfer.js.map +1 -0
  149. package/dist/esm/utils/accountInfo.d.ts +27 -0
  150. package/dist/esm/utils/accountInfo.d.ts.map +1 -0
  151. package/dist/esm/utils/accountInfo.js +80 -0
  152. package/dist/esm/utils/accountInfo.js.map +1 -0
  153. package/dist/esm/utils/checkV0TxSize.d.ts +8 -0
  154. package/dist/esm/utils/checkV0TxSize.d.ts.map +1 -0
  155. package/dist/esm/utils/checkV0TxSize.js +18 -0
  156. package/dist/esm/utils/checkV0TxSize.js.map +1 -0
  157. package/dist/esm/utils/estimateComputeUnits.d.ts +11 -0
  158. package/dist/esm/utils/estimateComputeUnits.d.ts.map +1 -0
  159. package/dist/esm/utils/estimateComputeUnits.js +48 -0
  160. package/dist/esm/utils/estimateComputeUnits.js.map +1 -0
  161. package/dist/esm/utils/generatePubKey.d.ts +10 -0
  162. package/dist/esm/utils/generatePubKey.d.ts.map +1 -0
  163. package/dist/esm/utils/generatePubKey.js +14 -0
  164. package/dist/esm/utils/generatePubKey.js.map +1 -0
  165. package/dist/esm/utils/index.d.ts +9 -0
  166. package/dist/esm/utils/index.d.ts.map +1 -0
  167. package/dist/esm/utils/index.js +11 -0
  168. package/dist/esm/utils/index.js.map +1 -0
  169. package/dist/esm/utils/token.d.ts +9 -0
  170. package/dist/esm/utils/token.d.ts.map +1 -0
  171. package/dist/esm/utils/token.js +16 -0
  172. package/dist/esm/utils/token.js.map +1 -0
  173. package/dist/esm/utils/transactionUtils.d.ts +84 -0
  174. package/dist/esm/utils/transactionUtils.d.ts.map +1 -0
  175. package/dist/esm/utils/transactionUtils.js +138 -0
  176. package/dist/esm/utils/transactionUtils.js.map +1 -0
  177. package/dist/esm/utils/validateAndParsePublicKey.d.ts +15 -0
  178. package/dist/esm/utils/validateAndParsePublicKey.d.ts.map +1 -0
  179. package/dist/esm/utils/validateAndParsePublicKey.js +42 -0
  180. package/dist/esm/utils/validateAndParsePublicKey.js.map +1 -0
  181. package/package.json +66 -0
@@ -0,0 +1,1123 @@
1
+ import { TOKEN_PROGRAM_ID, NATIVE_MINT, createInitializeAccountInstruction, createCloseAccountInstruction, createAssociatedTokenAccountIdempotentInstruction, AccountLayout, TOKEN_2022_PROGRAM_ID, } from '@solana/spl-token';
2
+ import { PublicKey, SystemProgram } from '@solana/web3.js';
3
+ import BN from 'bn.js';
4
+ import { Decimal } from 'decimal.js';
5
+ import { BYREAL_CLMM_PROGRAM_ID, U64_IGNORE_RANGE } from '../constants.js';
6
+ import { PositionUtils, RawDataUtils, SqrtPriceMath, TickMath, TickUtils, Instruction, getATAAddress, getPdaMintExAccount, getPdaTickArrayAddress, PoolUtils, getTickArrayBitmapExtension, getTickArrayInfo, getPdaExBitmapAccount, MIN_SQRT_PRICE_X64, MAX_SQRT_PRICE_X64, TickArrayUtils, } from '../instructions/index.js';
7
+ import { generatePubKey } from '../utils/generatePubKey.js';
8
+ import { makeTransaction, sendTransaction, estimateComputeUnits, DEFAULT_COMPUTE_UNIT_PRICE } from '../utils/index.js';
9
+ import { alignPriceToTickPrice, calculateApr, calculateRewardApr, calculateRangeAprs, getAmountAFromAmountB, getAmountBFromAmountA, getTokenProgramId, } from './utils.js';
10
+ /*
11
+ * Chain class: Encapsulates chain-level operations related to CLMM (Concentrated Liquidity Market Maker)
12
+
13
+ * Includes position, pool, token information retrieval, liquidity operations, fee collection, etc.
14
+ * Mainly depends on Solana web3.js, @solana/spl-token instruction tools
15
+ */
16
+ export class Chain {
17
+ connection;
18
+ programId;
19
+ // Cache rent fee calculation results
20
+ rentFeeCache = {};
21
+ /**
22
+ * Constructor
23
+ * @param params.connection Solana chain connection object
24
+ * @param params.programId CLMM program ID, default is CLMM_PROGRAM_ID
25
+ */
26
+ constructor(params) {
27
+ const { connection, programId = BYREAL_CLMM_PROGRAM_ID } = params;
28
+ this.connection = connection;
29
+ this.programId = programId;
30
+ }
31
+ /**
32
+ * Get all CLMM position information for a specified account
33
+ * @param userAddress User wallet address
34
+ * @returns Promise<IPersonalPositionLayout[]> Position information list
35
+ */
36
+ async getRawPositionInfoListByUserAddress(userAddress) {
37
+ return RawDataUtils.getRawPositionInfoListByUserAddress({
38
+ connection: this.connection,
39
+ programId: this.programId,
40
+ userAddress,
41
+ });
42
+ }
43
+ /**
44
+ * Get the corresponding position information based on the NFT mint address
45
+ * @param nftMint NFT mint address
46
+ * @returns Promise<IPersonalPositionLayout | null> Position information
47
+ */
48
+ async getRawPositionInfoByNftMint(nftMint) {
49
+ return RawDataUtils.getRawPositionInfoByNftMint({
50
+ connection: this.connection,
51
+ programId: this.programId,
52
+ nftMint,
53
+ });
54
+ }
55
+ /**
56
+ * Get the corresponding pool information based on the pool address
57
+ * @param poolId Pool address or PublicKey
58
+ * @returns Promise<IPoolLayoutWithId> Pool information
59
+ */
60
+ async getRawPoolInfoByPoolId(poolId) {
61
+ const poolInfo = await RawDataUtils.getRawPoolInfoByPoolId({
62
+ connection: this.connection,
63
+ poolId,
64
+ });
65
+ if (!poolInfo)
66
+ throw new Error(`pool info not found, poolId: ${String(poolId)}`);
67
+ return poolInfo;
68
+ }
69
+ /**
70
+ * Get the corresponding token information based on the token mint address
71
+ * @param mintAddress Token mint address
72
+ * @returns Promise<...> Token information
73
+ */
74
+ async getRawTokenInfoByMint(mintAddress) {
75
+ const tokenInfo = await RawDataUtils.getRawTokenInfoByMint({
76
+ connection: this.connection,
77
+ mintAddress,
78
+ });
79
+ if (!tokenInfo)
80
+ throw new Error(`token info not found, mintAddress: ${String(mintAddress)}`);
81
+ return tokenInfo;
82
+ }
83
+ /**
84
+ * Get the simplified token information (including address, precision, and programId)
85
+ * @param mintAddress Token mint address
86
+ * @returns Promise<ITokenInfo>
87
+ */
88
+ async getTokenInfoByMint(mintAddress) {
89
+ const tokenInfo = await this.getRawTokenInfoByMint(mintAddress);
90
+ if (!tokenInfo)
91
+ throw new Error(`token info not found, mintAddress: ${String(mintAddress)}`);
92
+ return {
93
+ address: mintAddress.toBase58(),
94
+ decimals: tokenInfo.decimals,
95
+ programId: tokenInfo.owner.toBase58(),
96
+ };
97
+ }
98
+ /**
99
+ * Get the detailed position information, including price range, token amount, fee, etc.
100
+ * @param nftMint NFT mint address
101
+ * @returns Promise<{...}> Detailed position information
102
+ */
103
+ async getPositionInfoByNftMint(nftMint) {
104
+ const rawPositionInfo = await this.getRawPositionInfoByNftMint(nftMint);
105
+ if (!rawPositionInfo)
106
+ return null;
107
+ const rawPoolInfo = await this.getRawPoolInfoByPoolId(rawPositionInfo.poolId);
108
+ const { mintDecimalsA, mintDecimalsB, tickSpacing, programId } = rawPoolInfo;
109
+ // Calculate the price corresponding to tickLower/tickUpper
110
+ const priceLower = TickMath.getPriceFromTick({
111
+ tick: rawPositionInfo.tickLower,
112
+ decimalsA: mintDecimalsA,
113
+ decimalsB: mintDecimalsB,
114
+ });
115
+ const priceUpper = TickMath.getPriceFromTick({
116
+ tick: rawPositionInfo.tickUpper,
117
+ decimalsA: mintDecimalsA,
118
+ decimalsB: mintDecimalsB,
119
+ });
120
+ // Calculate the actual token amount held in the position
121
+ const { amountA, amountB } = PositionUtils.getAmountsFromLiquidity({
122
+ poolInfo: rawPoolInfo,
123
+ ownerPosition: rawPositionInfo,
124
+ liquidity: rawPositionInfo.liquidity,
125
+ slippage: 0,
126
+ add: false,
127
+ epochInfo: await this.connection.getEpochInfo(),
128
+ });
129
+ // Calculate the amount of tokens displayed on the UI
130
+ const [pooledAmountA, pooledAmountB] = [
131
+ new Decimal(amountA.amount.toString()).div(10 ** mintDecimalsA),
132
+ new Decimal(amountB.amount.toString()).div(10 ** mintDecimalsB),
133
+ ];
134
+ // Get the tickArray address
135
+ const [tickLowerArrayAddress, tickUpperArrayAddress] = [
136
+ TickUtils.getTickArrayAddressByTick(new PublicKey(programId), new PublicKey(rawPositionInfo.poolId), rawPositionInfo.tickLower, tickSpacing),
137
+ TickUtils.getTickArrayAddressByTick(new PublicKey(programId), new PublicKey(rawPositionInfo.poolId), rawPositionInfo.tickUpper, tickSpacing),
138
+ ];
139
+ // Get the tickArray data
140
+ const tickArrayRes = await this.connection.getMultipleAccountsInfo([tickLowerArrayAddress, tickUpperArrayAddress]);
141
+ if (!tickArrayRes[0] || !tickArrayRes[1])
142
+ throw new Error('tick data not found');
143
+ // Parse as containers (supports both fixed and dynamic tick arrays)
144
+ const tickArrayLowerContainer = TickArrayUtils.parseTickArrayContainer(tickArrayRes[0].data, tickLowerArrayAddress);
145
+ const tickArrayUpperContainer = TickArrayUtils.parseTickArrayContainer(tickArrayRes[1].data, tickUpperArrayAddress);
146
+ // Get the tick state using container helper
147
+ const tickLowerState = TickArrayUtils.getTickStateFromContainer(tickArrayLowerContainer, rawPositionInfo.tickLower, rawPoolInfo.tickSpacing);
148
+ const tickUpperState = TickArrayUtils.getTickStateFromContainer(tickArrayUpperContainer, rawPositionInfo.tickUpper, rawPoolInfo.tickSpacing);
149
+ // Validate tick states
150
+ if (!tickLowerState || !tickUpperState) {
151
+ throw new Error('Tick state not found in tick array');
152
+ }
153
+ // Calculate the fee (original logic unchanged)
154
+ const tokenFees = PositionUtils.getPositionFees(rawPoolInfo, rawPositionInfo, tickLowerState, tickUpperState);
155
+ // Filter out abnormal fees
156
+ const [tokenFeeAmountA, tokenFeeAmountB] = [
157
+ tokenFees.tokenFeeAmountA.gte(new BN(0)) && tokenFees.tokenFeeAmountA.lt(U64_IGNORE_RANGE)
158
+ ? tokenFees.tokenFeeAmountA
159
+ : new BN(0),
160
+ tokenFees.tokenFeeAmountB.gte(new BN(0)) && tokenFees.tokenFeeAmountB.lt(U64_IGNORE_RANGE)
161
+ ? tokenFees.tokenFeeAmountB
162
+ : new BN(0),
163
+ ];
164
+ return {
165
+ priceLower,
166
+ priceUpper,
167
+ uiPriceLower: priceLower.toFixed(mintDecimalsA),
168
+ uiPriceUpper: priceUpper.toFixed(mintDecimalsB),
169
+ tokenA: {
170
+ address: rawPoolInfo.mintA,
171
+ decimals: rawPoolInfo.mintDecimalsA,
172
+ amount: amountA.amount,
173
+ feeAmount: tokenFeeAmountA,
174
+ uiAmount: pooledAmountA.toString(),
175
+ uiFeeAmount: new Decimal(tokenFeeAmountA.toString())
176
+ .dividedBy(new Decimal(10).pow(mintDecimalsA))
177
+ .toFixed(mintDecimalsA),
178
+ },
179
+ tokenB: {
180
+ address: rawPoolInfo.mintB,
181
+ decimals: rawPoolInfo.mintDecimalsB,
182
+ amount: amountB.amount,
183
+ feeAmount: tokenFeeAmountB,
184
+ uiAmount: pooledAmountB.toString(),
185
+ uiFeeAmount: new Decimal(tokenFeeAmountB.toString())
186
+ .dividedBy(new Decimal(10).pow(mintDecimalsB))
187
+ .toFixed(mintDecimalsB),
188
+ },
189
+ rawPositionInfo,
190
+ rawPoolInfo,
191
+ };
192
+ }
193
+ /**
194
+ * Create position instructions on the chain (does not directly send transactions)
195
+ * @param params Parameters required for creating a position
196
+ * @returns IInstructionReturn Contains instructions, signers, and transaction objects
197
+ */
198
+ async createPositionInstructions(params) {
199
+ const { userAddress, poolInfo, tickLower, tickUpper, base, baseAmount, otherAmountMax, transactionOptions } = params;
200
+ const { mintA, mintB } = poolInfo;
201
+ // Calculate the actual required tokenA/B amount
202
+ const amountA = base === 'MintA' ? baseAmount : otherAmountMax;
203
+ const amountB = base === 'MintB' ? baseAmount : otherAmountMax;
204
+ const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
205
+ userAddress,
206
+ mintA,
207
+ mintB,
208
+ amountA,
209
+ amountB,
210
+ });
211
+ // Generate position creation instructions
212
+ const { instructions: positionInstructions, signers: positionSigners } = await Instruction.openPositionFromBaseInstruction({
213
+ poolInfo,
214
+ ownerInfo: {
215
+ feePayer: userAddress,
216
+ wallet: userAddress,
217
+ tokenAccountA,
218
+ tokenAccountB,
219
+ },
220
+ tickLower,
221
+ tickUpper,
222
+ base,
223
+ baseAmount,
224
+ otherAmountMax,
225
+ withMetadata: 'create',
226
+ });
227
+ // Merge all instructions: ATA creation → pre → position → end
228
+ const instructions = [
229
+ ...preInstructions, // SOL/WSOL handling
230
+ ...positionInstructions, // Position creation
231
+ ...endInstructions, // Cleanup
232
+ ];
233
+ const signers = [...positionSigners];
234
+ // Construct transaction object
235
+ const transaction = await makeTransaction({
236
+ connection: this.connection,
237
+ payerPublicKey: userAddress,
238
+ instructions,
239
+ signers,
240
+ options: transactionOptions,
241
+ });
242
+ return {
243
+ instructions,
244
+ signers,
245
+ nftAddress: positionSigners[0].publicKey.toString(),
246
+ transaction,
247
+ };
248
+ }
249
+ /**
250
+ * Calculate the rent fee required for creating a position
251
+ *
252
+ * @param params Parameters required for creating a position and options
253
+ */
254
+ async calculateCreatePositionFee(params) {
255
+ const { userAddress, poolInfo, tickLower, tickUpper, base, baseAmount, otherAmountMax, transactionOptions, useCache = true, } = params;
256
+ const computeUnitPrice = transactionOptions?.computeUnitPrice || DEFAULT_COMPUTE_UNIT_PRICE;
257
+ // Define account size constants (this is fixed hardcode)
258
+ const ACCOUNT_SIZES = {
259
+ NFT_MINT: 480,
260
+ NFT_HOLDER: 170,
261
+ PERSONAL_POSITION: 281,
262
+ TICK_ARRAY: 10240,
263
+ };
264
+ const { programId, poolId, tickSpacing } = poolInfo;
265
+ const tickArrayLowerStartIndex = TickUtils.getTickArrayStartIndexByTick(tickLower, tickSpacing);
266
+ const tickArrayUpperStartIndex = TickUtils.getTickArrayStartIndexByTick(tickUpper, tickSpacing);
267
+ const { publicKey: tickArrayLower } = getPdaTickArrayAddress(programId, poolId, tickArrayLowerStartIndex);
268
+ const { publicKey: tickArrayUpper } = getPdaTickArrayAddress(programId, poolId, tickArrayUpperStartIndex);
269
+ const [blockhashData, tickArrayLowerInfo, tickArrayUpperInfo, nftMintRentLamports, nftHolderRentLamports, personalPositionRentLamports, tickArrayRentLamports,] = await Promise.all([
270
+ this.connection.getLatestBlockhash(),
271
+ this.connection.getAccountInfo(tickArrayLower),
272
+ this.connection.getAccountInfo(tickArrayUpper),
273
+ this.estimateRentFee(ACCOUNT_SIZES.NFT_MINT, useCache),
274
+ this.estimateRentFee(ACCOUNT_SIZES.NFT_HOLDER, useCache),
275
+ this.estimateRentFee(ACCOUNT_SIZES.PERSONAL_POSITION, useCache),
276
+ this.estimateRentFee(ACCOUNT_SIZES.TICK_ARRAY, useCache),
277
+ ]);
278
+ // Check if the tick array exists
279
+ const isTickArrayLowerExists = !!tickArrayLowerInfo;
280
+ const isTickArrayUpperExists = !!tickArrayUpperInfo;
281
+ // Prepare instructions to estimate compute units
282
+ const { mintA, mintB } = poolInfo;
283
+ const amountA = base === 'MintA' ? baseAmount : otherAmountMax;
284
+ const amountB = base === 'MintB' ? baseAmount : otherAmountMax;
285
+ const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
286
+ userAddress,
287
+ mintA,
288
+ mintB,
289
+ amountA,
290
+ amountB,
291
+ });
292
+ let positionInstructions = [];
293
+ // Try to generate position creation instructions
294
+ try {
295
+ const { instructions } = await Instruction.openPositionFromBaseInstruction({
296
+ poolInfo,
297
+ ownerInfo: {
298
+ feePayer: userAddress,
299
+ wallet: userAddress,
300
+ tokenAccountA,
301
+ tokenAccountB,
302
+ },
303
+ tickLower,
304
+ tickUpper,
305
+ base,
306
+ baseAmount,
307
+ otherAmountMax,
308
+ withMetadata: 'create',
309
+ });
310
+ positionInstructions = instructions;
311
+ }
312
+ catch {
313
+ // console.error('error ==> ', error);
314
+ }
315
+ // Estimate compute units and transaction fees
316
+ const computeUnits = await estimateComputeUnits(this.connection, [...preInstructions, ...positionInstructions, ...endInstructions], userAddress, blockhashData.blockhash);
317
+ // 1 SOL = 10^9 lamports, 1 lamport = 10^6 microLamports
318
+ const transactionNetFee = (computeUnits * computeUnitPrice) / 10 ** 15;
319
+ const refundableFees = (nftMintRentLamports + nftHolderRentLamports + personalPositionRentLamports) / 10 ** 9;
320
+ // Unrefundable fees include transaction fees and newly created shared tick array accounts
321
+ let createTickFee = 0;
322
+ if (!isTickArrayLowerExists) {
323
+ createTickFee += tickArrayRentLamports / 10 ** 9;
324
+ }
325
+ if (!isTickArrayUpperExists) {
326
+ createTickFee += tickArrayRentLamports / 10 ** 9;
327
+ }
328
+ return {
329
+ unRefundableFees: transactionNetFee + createTickFee, // Unrefundable fees
330
+ transactionNetFee, // Transaction network fee
331
+ refundableFees, // Refundable fees
332
+ createTickFee, // Create tick array account fee
333
+ };
334
+ }
335
+ /**
336
+ * Create a new position and send a transaction
337
+ * @param params Parameters required for creating a position, including a signature callback
338
+ * @returns Promise<string> Transaction signature
339
+ */
340
+ async createPosition(params) {
341
+ const { signerCallback } = params;
342
+ const { transaction } = await this.createPositionInstructions(params);
343
+ return sendTransaction({
344
+ connection: this.connection,
345
+ signTx: () => signerCallback(transaction),
346
+ });
347
+ }
348
+ /**
349
+ * Close the specified position (only when the liquidity is 0, it can be closed)
350
+ * @param params.userAddress User wallet address
351
+ * @param params.nftMint NFT mint address
352
+ * @returns IInstructionReturn Contains instructions and transaction objects
353
+ */
354
+ async closePositionInstructions(params) {
355
+ const { userAddress, nftMint } = params;
356
+ // Get position detailed information
357
+ const positionInfo = await this.getPositionInfoByNftMint(nftMint);
358
+ if (!positionInfo)
359
+ throw new Error('Position not found');
360
+ const mintA = positionInfo.rawPoolInfo.mintA;
361
+ const mintB = positionInfo.rawPoolInfo.mintB;
362
+ // Handle SOL/WSOL packaging
363
+ const { preInstructions, endInstructions } = await this.handleTokenAccount({
364
+ userAddress,
365
+ mintA,
366
+ mintB,
367
+ });
368
+ // Generate close position instructions
369
+ const { instructions: closeInstructions } = await Instruction.closePositionInstruction({
370
+ programId: this.programId,
371
+ nftMint,
372
+ ownerWallet: userAddress,
373
+ });
374
+ // Merge all instructions
375
+ const instructions = [...preInstructions, ...closeInstructions, ...endInstructions];
376
+ // Construct transaction object
377
+ const transaction = await makeTransaction({
378
+ connection: this.connection,
379
+ payerPublicKey: userAddress,
380
+ instructions,
381
+ });
382
+ return {
383
+ instructions,
384
+ transaction,
385
+ };
386
+ }
387
+ /**
388
+ * Close the specified position and send a transaction
389
+ * @param params.userAddress User wallet address
390
+ * @param params.nftMint NFT mint address
391
+ * @param params.signerCallback Signature callback
392
+ * @returns Promise<string> Transaction signature
393
+ */
394
+ async closePosition(params) {
395
+ const { userAddress, nftMint, signerCallback } = params;
396
+ const { transaction } = await this.closePositionInstructions({
397
+ userAddress,
398
+ nftMint,
399
+ });
400
+ return sendTransaction({
401
+ connection: this.connection,
402
+ signTx: () => signerCallback(transaction),
403
+ });
404
+ }
405
+ /**
406
+ * Partially remove position liquidity, generate chain instructions
407
+ * @param params Contains user, position, removed liquidity amount, slippage, etc.
408
+ * @returns IInstructionReturn
409
+ */
410
+ async decreaseLiquidityInstructions(params) {
411
+ // Slippage is set to 2% by default
412
+ const { userAddress, nftMint, liquidity, slippage = 0.02 } = params;
413
+ // Get position raw information
414
+ const positionInfo = await this.getRawPositionInfoByNftMint(nftMint);
415
+ if (!positionInfo)
416
+ throw new Error('Position not found');
417
+ // Check if the removed liquidity amount is valid
418
+ if (liquidity.gt(positionInfo.liquidity))
419
+ throw new Error('Liquidity is greater than position liquidity');
420
+ // Get pool information
421
+ const poolInfo = await this.getRawPoolInfoByPoolId(positionInfo.poolId);
422
+ // Handle SOL/WSOL packaging
423
+ const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
424
+ userAddress,
425
+ mintA: poolInfo.mintA,
426
+ mintB: poolInfo.mintB,
427
+ });
428
+ // Calculate the expected token amount after removing liquidity (considering slippage)
429
+ const { amountSlippageA, amountSlippageB } = PositionUtils.getAmountsFromLiquidity({
430
+ poolInfo,
431
+ ownerPosition: positionInfo,
432
+ liquidity,
433
+ slippage,
434
+ add: false,
435
+ epochInfo: await this.connection.getEpochInfo(),
436
+ });
437
+ // Calculate the minimum accepted token amount
438
+ const amountMinA = amountSlippageA.amount;
439
+ const amountMinB = amountSlippageB.amount;
440
+ // Generate remove liquidity instructions
441
+ const { instructions: decreaseInstructions } = await Instruction.decreaseLiquidityInstructions({
442
+ poolInfo,
443
+ ownerPosition: positionInfo,
444
+ ownerInfo: {
445
+ wallet: userAddress,
446
+ tokenAccountA,
447
+ tokenAccountB,
448
+ },
449
+ liquidity,
450
+ amountMinA,
451
+ amountMinB,
452
+ });
453
+ // Merge all instructions
454
+ const instructions = [...preInstructions, ...decreaseInstructions, ...endInstructions];
455
+ // Construct transaction object
456
+ const transaction = await makeTransaction({
457
+ connection: this.connection,
458
+ payerPublicKey: userAddress,
459
+ instructions,
460
+ });
461
+ return {
462
+ instructions,
463
+ transaction,
464
+ };
465
+ }
466
+ /**
467
+ * Partially remove position liquidity and send a transaction
468
+ * @param params Contains signature callback, etc.
469
+ * @returns Promise<string> Transaction signature
470
+ */
471
+ async decreaseLiquidity(params) {
472
+ const { signerCallback } = params;
473
+ const { transaction } = await this.decreaseLiquidityInstructions(params);
474
+ return sendTransaction({
475
+ connection: this.connection,
476
+ signTx: () => signerCallback(transaction),
477
+ });
478
+ }
479
+ /**
480
+ * Remove all position liquidity (optional to automatically close position)
481
+ * @param params.closePosition Whether to close position automatically
482
+ * @param params Other parameters are the same as decreaseLiquidityInstructions
483
+ * @returns IInstructionReturn
484
+ */
485
+ async decreaseFullLiquidityInstructions(params) {
486
+ // Slippage is set to 2% by default
487
+ const { userAddress, nftMint, closePosition = true, slippage = 0.02 } = params;
488
+ // Get position raw information
489
+ const positionInfo = await this.getRawPositionInfoByNftMint(nftMint);
490
+ if (!positionInfo)
491
+ throw new Error('Position not found');
492
+ // Get pool information
493
+ const poolInfo = await this.getRawPoolInfoByPoolId(positionInfo.poolId);
494
+ // Handle SOL/WSOL packaging
495
+ const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
496
+ userAddress,
497
+ mintA: poolInfo.mintA,
498
+ mintB: poolInfo.mintB,
499
+ });
500
+ // Use all position liquidity
501
+ const liquidity = positionInfo.liquidity;
502
+ // Calculate the expected token amount after removing liquidity (considering slippage)
503
+ const { amountSlippageA, amountSlippageB } = PositionUtils.getAmountsFromLiquidity({
504
+ poolInfo,
505
+ ownerPosition: positionInfo,
506
+ liquidity,
507
+ slippage,
508
+ add: false, // When reducing liquidity, set to false
509
+ epochInfo: await this.connection.getEpochInfo(),
510
+ });
511
+ // Minimum token amount after slippage adjustment
512
+ const amountMinA = amountSlippageA.amount;
513
+ const amountMinB = amountSlippageB.amount;
514
+ // Generate remove liquidity instructions
515
+ const { instructions: decreaseInstructions } = await Instruction.decreaseLiquidityInstructions({
516
+ poolInfo,
517
+ ownerPosition: positionInfo,
518
+ ownerInfo: {
519
+ wallet: userAddress,
520
+ tokenAccountA,
521
+ tokenAccountB,
522
+ },
523
+ liquidity,
524
+ amountMinA,
525
+ amountMinB,
526
+ });
527
+ // Merge all instructions
528
+ const instructions = [...preInstructions, ...decreaseInstructions, ...endInstructions];
529
+ // If you need to close the position, append the close instruction
530
+ if (closePosition) {
531
+ const { instructions: closeInstructions } = await Instruction.closePositionInstruction({
532
+ programId: this.programId,
533
+ nftMint,
534
+ ownerWallet: userAddress,
535
+ });
536
+ instructions.push(...closeInstructions);
537
+ }
538
+ // Construct transaction object
539
+ const transaction = await makeTransaction({
540
+ connection: this.connection,
541
+ payerPublicKey: userAddress,
542
+ instructions,
543
+ });
544
+ return {
545
+ instructions,
546
+ transaction,
547
+ };
548
+ }
549
+ /**
550
+ * Remove all position liquidity and send a transaction
551
+ * @param params Contains signature callback, etc.
552
+ * @returns Promise<string> Transaction signature
553
+ */
554
+ async decreaseFullLiquidity(params) {
555
+ const { signerCallback } = params;
556
+ const { transaction } = await this.decreaseFullLiquidityInstructions(params);
557
+ return sendTransaction({
558
+ connection: this.connection,
559
+ signTx: () => signerCallback(transaction),
560
+ });
561
+ }
562
+ /**
563
+ * Collect fees for a single position (essentially removing 0 liquidity)
564
+ * @param params Contains user, position, etc.
565
+ * @returns IInstructionReturn
566
+ */
567
+ async collectFeesInstructions(params) {
568
+ const { userAddress, nftMint } = params;
569
+ // Reuse the decreaseLiquidity function, pass in liquidity as 0
570
+ return await this.decreaseLiquidityInstructions({
571
+ userAddress,
572
+ nftMint,
573
+ liquidity: new BN(0),
574
+ });
575
+ }
576
+ /**
577
+ * Collect fees for all positions of a user, automatically batch to avoid exceeding transaction size limit
578
+ * @param params.userAddress User wallet address
579
+ * @param params.nftMintList NFT mint list
580
+ * @returns { instructionsList, transactions } Batch instructions and transaction objects
581
+ */
582
+ async collectAllPositionFeesInstructions(params) {
583
+ const { userAddress, nftMintList } = params;
584
+ // Cache pool information to avoid duplicate requests
585
+ const poolInfoMap = new Map();
586
+ const allInstructions = [];
587
+ // let currentInstructions: TransactionInstruction[] = [];
588
+ // Get rent exemption lamports
589
+ const rentExemptLamports = await this.estimateRentFee(AccountLayout.span);
590
+ for (const nftMint of nftMintList) {
591
+ try {
592
+ const positionInfo = await this.getRawPositionInfoByNftMint(nftMint);
593
+ if (!positionInfo)
594
+ throw new Error(`Position not found: ${nftMint.toBase58()}`);
595
+ const poolId = positionInfo.poolId.toBase58();
596
+ // Get or cache pool information
597
+ if (!poolInfoMap.has(poolId)) {
598
+ poolInfoMap.set(poolId, await this.getRawPoolInfoByPoolId(positionInfo.poolId));
599
+ }
600
+ const poolInfo = poolInfoMap.get(poolId);
601
+ if (!poolInfo)
602
+ throw new Error(`Pool not found: ${poolId}`);
603
+ // Handle SOL/WSOL related
604
+ const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
605
+ userAddress,
606
+ mintA: poolInfo.mintA,
607
+ mintB: poolInfo.mintB,
608
+ rentExemptLamports,
609
+ });
610
+ // Generate instructions to collect fees (essentially removing 0 liquidity)
611
+ const { instructions: decreaseInstructions } = await Instruction.decreaseLiquidityInstructions({
612
+ poolInfo,
613
+ ownerPosition: positionInfo,
614
+ ownerInfo: {
615
+ wallet: userAddress,
616
+ tokenAccountA,
617
+ tokenAccountB,
618
+ },
619
+ liquidity: new BN(0),
620
+ amountMinA: new BN(0),
621
+ amountMinB: new BN(0),
622
+ });
623
+ // All instructions for the current NFT
624
+ const nftInstructions = [...preInstructions, ...decreaseInstructions, ...endInstructions];
625
+ // Check if adding this NFT's instructions will exceed the transaction size limit
626
+ // const tempInstructions = [...currentInstructions, ...nftInstructions];
627
+ // const isValidSize = checkV0TxSize({
628
+ // instructions: tempInstructions,
629
+ // payer: userAddress,
630
+ // });
631
+ // if (!isValidSize && currentInstructions.length > 0) {
632
+ // If adding will exceed the limit and there are existing instructions, save the current batch and start a new batch
633
+ allInstructions.push(nftInstructions);
634
+ // currentInstructions = nftInstructions;
635
+ // } else {
636
+ // // If it doesn't exceed the limit or the current batch is empty, add to the current batch
637
+ // currentInstructions.push(...nftInstructions);
638
+ // }
639
+ }
640
+ catch (error) {
641
+ console.error('[collectAllPositionFeesInstructions] Collect position fees failed:', {
642
+ nftMint: nftMint.toBase58(),
643
+ error,
644
+ });
645
+ }
646
+ }
647
+ // Ensure the last batch of instructions is also added
648
+ // if (currentInstructions.length > 0) {
649
+ // allInstructions.push(currentInstructions);
650
+ // }
651
+ // Create a transaction for each batch of instructions
652
+ const transactions = [];
653
+ for (const instructions of allInstructions) {
654
+ try {
655
+ const transaction = await makeTransaction({
656
+ connection: this.connection,
657
+ payerPublicKey: userAddress,
658
+ instructions,
659
+ });
660
+ transactions.push(transaction);
661
+ }
662
+ catch (error) {
663
+ console.error('[collectAllPositionFeesInstructions] Create transaction failed:', error);
664
+ }
665
+ }
666
+ return { instructionsList: allInstructions, transactions };
667
+ }
668
+ /**
669
+ * Collect fees for a single position and send a transaction
670
+ * @param params Contains signature callback, etc.
671
+ * @returns Promise<string> Transaction signature
672
+ */
673
+ async collectFees(params) {
674
+ try {
675
+ const { signerCallback } = params;
676
+ const { transaction } = await this.collectFeesInstructions(params);
677
+ return await sendTransaction({
678
+ connection: this.connection,
679
+ signTx: async () => await signerCallback(transaction),
680
+ });
681
+ }
682
+ catch (error) {
683
+ console.warn('collectFees failed:', error);
684
+ throw error;
685
+ }
686
+ }
687
+ /**
688
+ * Add liquidity to an existing position, generate chain instructions
689
+ * @param params Contains user, position, liquidity amount, etc.
690
+ * @returns IInstructionReturn
691
+ */
692
+ async addLiquidityInstructions(params) {
693
+ const { userAddress, nftMint, base, baseAmount, otherAmountMax, computeBudgetOptions = {} } = params;
694
+ // Get position raw information
695
+ const ownerPosition = await this.getRawPositionInfoByNftMint(nftMint);
696
+ if (!ownerPosition)
697
+ throw new Error('Position not found');
698
+ // Get pool information
699
+ const poolInfo = await this.getRawPoolInfoByPoolId(ownerPosition.poolId);
700
+ // Calculate the required amount
701
+ const amountA = base === 'MintA' ? baseAmount : otherAmountMax;
702
+ const amountB = base === 'MintB' ? baseAmount : otherAmountMax;
703
+ // Handle SOL/WSOL packaging
704
+ const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
705
+ userAddress,
706
+ mintA: poolInfo.mintA,
707
+ mintB: poolInfo.mintB,
708
+ amountA,
709
+ amountB,
710
+ });
711
+ // Generate add liquidity instructions
712
+ const { instructions: increaseInstructions } = await Instruction.increasePositionFromBaseInstructions({
713
+ poolInfo,
714
+ ownerPosition,
715
+ ownerInfo: {
716
+ wallet: userAddress,
717
+ tokenAccountA,
718
+ tokenAccountB,
719
+ },
720
+ base,
721
+ baseAmount,
722
+ otherAmountMax,
723
+ });
724
+ // Merge all instructions
725
+ const instructions = [...preInstructions, ...increaseInstructions, ...endInstructions];
726
+ // Construct transaction object
727
+ const transaction = await makeTransaction({
728
+ connection: this.connection,
729
+ payerPublicKey: userAddress,
730
+ instructions,
731
+ options: {
732
+ ...computeBudgetOptions,
733
+ },
734
+ });
735
+ return {
736
+ instructions,
737
+ transaction,
738
+ };
739
+ }
740
+ /**
741
+ * Add liquidity to an existing position and send a transaction
742
+ * @param params Contains signature callback, etc.
743
+ * @returns Promise<string> Transaction signature
744
+ */
745
+ async addLiquidity(params) {
746
+ const { signerCallback } = params;
747
+ const { transaction } = await this.addLiquidityInstructions(params);
748
+ return sendTransaction({
749
+ connection: this.connection,
750
+ signTx: () => signerCallback(transaction),
751
+ });
752
+ }
753
+ /**
754
+ * Create pool instructions
755
+ * @param params Create pool parameters
756
+ * @returns IInstructionReturn
757
+ */
758
+ async createPoolInstructions(params) {
759
+ const { userAddress, poolManager, openTime, mintA, mintB, ammConfigId, initialPrice } = params;
760
+ // Convert price to BN format
761
+ const initialPriceDecimal = new Decimal(initialPrice);
762
+ const initialPriceX64 = SqrtPriceMath.priceToSqrtPriceX64(initialPriceDecimal, mintA.decimals, mintB.decimals);
763
+ const mintAAddress = new PublicKey(mintA.address);
764
+ const mintBAddress = new PublicKey(mintB.address);
765
+ // Handle Token-2022 extension account
766
+ const extendMintAccount = [];
767
+ const fetchAccounts = [];
768
+ if (mintA.programId === TOKEN_2022_PROGRAM_ID.toBase58()) {
769
+ fetchAccounts.push(getPdaMintExAccount(this.programId, mintAAddress).publicKey);
770
+ }
771
+ if (mintB.programId === TOKEN_2022_PROGRAM_ID.toBase58()) {
772
+ fetchAccounts.push(getPdaMintExAccount(this.programId, mintBAddress).publicKey);
773
+ }
774
+ // Verify account existence
775
+ if (fetchAccounts.length > 0) {
776
+ const extMintRes = await this.connection.getMultipleAccountsInfo(fetchAccounts);
777
+ extMintRes.forEach((r, idx) => {
778
+ if (r)
779
+ extendMintAccount.push(fetchAccounts[idx]);
780
+ });
781
+ }
782
+ // Generate pool creation instructions
783
+ const { instructions } = await Instruction.createPoolInstruction({
784
+ programId: this.programId,
785
+ owner: userAddress,
786
+ poolManager,
787
+ mintA,
788
+ mintB,
789
+ ammConfigId,
790
+ initialPriceX64,
791
+ openTime,
792
+ extendMintAccount,
793
+ });
794
+ // Construct transaction object
795
+ const transaction = await makeTransaction({
796
+ connection: this.connection,
797
+ payerPublicKey: userAddress,
798
+ instructions,
799
+ });
800
+ return {
801
+ instructions,
802
+ transaction,
803
+ };
804
+ }
805
+ /**
806
+ * Create a new pool and send a transaction
807
+ * @param params Contains signature callback, etc.
808
+ * @returns Promise<string> Transaction signature
809
+ */
810
+ async createPool(params) {
811
+ const { signerCallback } = params;
812
+ const { transaction } = await this.createPoolInstructions(params);
813
+ return sendTransaction({
814
+ connection: this.connection,
815
+ signTx: () => signerCallback(transaction),
816
+ });
817
+ }
818
+ async qouteSwap(params) {
819
+ // Slippage is set to 2% by default
820
+ const { poolInfo, inputTokenMint, amountIn, priceLimit = new Decimal(0), slippage = 0.02, catchLiquidityInsufficient, } = params;
821
+ let sqrtPriceLimitX64;
822
+ const isInputMintA = inputTokenMint.toBase58() === poolInfo.mintA.toBase58();
823
+ // TODO: Consider fee calculation for token2022 in the future
824
+ if (priceLimit.equals(new Decimal(0))) {
825
+ sqrtPriceLimitX64 = isInputMintA ? MIN_SQRT_PRICE_X64.add(new BN(1)) : MAX_SQRT_PRICE_X64.sub(new BN(1));
826
+ }
827
+ else {
828
+ sqrtPriceLimitX64 = SqrtPriceMath.priceToSqrtPriceX64(priceLimit, poolInfo.mintDecimalsA, poolInfo.mintDecimalsB);
829
+ }
830
+ const exBitmapInfo = await getTickArrayBitmapExtension(this.programId, poolInfo.poolId, this.connection);
831
+ const ammConfig = await RawDataUtils.getRawAmmConfigByConfigId({
832
+ connection: this.connection,
833
+ configId: poolInfo.ammConfig,
834
+ });
835
+ const tickArrayInfo = await getTickArrayInfo({
836
+ connection: this.connection,
837
+ poolInfo,
838
+ exBitmapInfo,
839
+ });
840
+ if (!exBitmapInfo || !ammConfig)
841
+ throw new Error('Failed to get tick array bitmap extension or amm config');
842
+ const { allTrade, expectedAmountOut, remainingAccounts, executionPrice, feeAmount } = await PoolUtils.getOutputAmountAndRemainAccounts({
843
+ poolInfo,
844
+ exBitmapInfo,
845
+ ammConfig,
846
+ tickArrayInfo,
847
+ inputTokenMint,
848
+ inputAmount: amountIn,
849
+ sqrtPriceLimitX64,
850
+ catchLiquidityInsufficient,
851
+ });
852
+ const minAmountOut = expectedAmountOut
853
+ .mul(new BN(Math.floor((1 - slippage) * 10000000000)))
854
+ .div(new BN(10000000000));
855
+ return {
856
+ allTrade,
857
+ isInputMintA,
858
+ amountIn,
859
+ expectedAmountOut,
860
+ minAmountOut,
861
+ remainingAccounts,
862
+ executionPrice,
863
+ feeAmount,
864
+ };
865
+ }
866
+ async swapInstructions(params) {
867
+ const { poolInfo, quoteReturn, userAddress } = params;
868
+ // Determine amounts based on which token is input
869
+ const amountA = quoteReturn.isInputMintA ? quoteReturn.amountIn : new BN(0);
870
+ const amountB = !quoteReturn.isInputMintA ? quoteReturn.amountIn : new BN(0);
871
+ const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
872
+ userAddress,
873
+ mintA: poolInfo.mintA,
874
+ mintB: poolInfo.mintB,
875
+ amountA,
876
+ amountB,
877
+ });
878
+ const exBitmapAddress = getPdaExBitmapAccount(poolInfo.programId, poolInfo.poolId).publicKey;
879
+ // quoteReturn.isBaseIn
880
+ const { instructions: swapInstruction } = await Instruction.swapBaseInInstruction({
881
+ poolInfo,
882
+ ownerInfo: {
883
+ wallet: userAddress,
884
+ tokenAccountA,
885
+ tokenAccountB,
886
+ },
887
+ amount: quoteReturn.amountIn,
888
+ // The minimum output amount after slippage calculation is passed here
889
+ otherAmountThreshold: quoteReturn.minAmountOut,
890
+ sqrtPriceLimitX64: quoteReturn.executionPrice,
891
+ isInputMintA: quoteReturn.isInputMintA,
892
+ tickArray: quoteReturn.remainingAccounts,
893
+ exTickArrayBitmap: exBitmapAddress,
894
+ });
895
+ const instructions = [...preInstructions, ...swapInstruction, ...endInstructions];
896
+ const transaction = await makeTransaction({
897
+ connection: this.connection,
898
+ payerPublicKey: userAddress,
899
+ instructions,
900
+ });
901
+ return {
902
+ transaction,
903
+ instructions,
904
+ };
905
+ }
906
+ /**
907
+ * Create swap exact out instructions
908
+ * @param params Contains pool info, quote return, and user address
909
+ * @returns IInstructionReturn
910
+ */
911
+ async swapExactOutInstructions(params) {
912
+ const { poolInfo, quoteReturn, userAddress } = params;
913
+ // For exact output, we need to determine input amounts
914
+ // If outputting tokenA, we input tokenB
915
+ // If outputting tokenB, we input tokenA
916
+ const isInputMintA = !quoteReturn.isOutputMintA;
917
+ const amountA = isInputMintA ? quoteReturn.maxAmountIn : new BN(0);
918
+ const amountB = !isInputMintA ? quoteReturn.maxAmountIn : new BN(0);
919
+ const { tokenAccountA, tokenAccountB, preInstructions, endInstructions } = await this.handleTokenAccount({
920
+ userAddress,
921
+ mintA: poolInfo.mintA,
922
+ mintB: poolInfo.mintB,
923
+ amountA,
924
+ amountB,
925
+ });
926
+ const exBitmapAddress = getPdaExBitmapAccount(poolInfo.programId, poolInfo.poolId).publicKey;
927
+ // For exact output, we need to use swapBaseOutInstruction
928
+ const { instructions: swapInstruction } = await Instruction.swapBaseOutInstruction({
929
+ poolInfo,
930
+ ownerInfo: {
931
+ wallet: userAddress,
932
+ tokenAccountA,
933
+ tokenAccountB,
934
+ },
935
+ amount: quoteReturn.amountOut,
936
+ // The maximum input amount (including slippage) is passed here
937
+ otherAmountThreshold: quoteReturn.maxAmountIn,
938
+ sqrtPriceLimitX64: quoteReturn.executionPrice,
939
+ isOutputMintA: quoteReturn.isOutputMintA,
940
+ tickArray: quoteReturn.remainingAccounts,
941
+ exTickArrayBitmap: exBitmapAddress,
942
+ });
943
+ const instructions = [...preInstructions, ...swapInstruction, ...endInstructions];
944
+ const transaction = await makeTransaction({
945
+ connection: this.connection,
946
+ payerPublicKey: userAddress,
947
+ instructions,
948
+ });
949
+ return {
950
+ transaction,
951
+ instructions,
952
+ };
953
+ }
954
+ async swap(params) {
955
+ const { signerCallback } = params;
956
+ const { transaction } = await this.swapInstructions(params);
957
+ return sendTransaction({
958
+ connection: this.connection,
959
+ signTx: () => signerCallback(transaction),
960
+ });
961
+ }
962
+ /**
963
+ * Execute swap exact out transaction
964
+ * @param params Contains signature callback, etc.
965
+ * @returns Promise<string> Transaction signature
966
+ */
967
+ async swapExactOut(params) {
968
+ const { signerCallback } = params;
969
+ const { transaction } = await this.swapExactOutInstructions(params);
970
+ return sendTransaction({
971
+ connection: this.connection,
972
+ signTx: () => signerCallback(transaction),
973
+ });
974
+ }
975
+ /**
976
+ * Quote swap exact output - calculate required input amount for desired output
977
+ * @param params Quote parameters including output amount and slippage
978
+ * @returns Quote result with expected input amount and other swap details
979
+ */
980
+ async quoteSwapExactOut(params) {
981
+ const { poolInfo, outputTokenMint, amountOut, priceLimit = new Decimal(0), slippage = 0.02, catchLiquidityInsufficient, } = params;
982
+ let sqrtPriceLimitX64;
983
+ const isOutputMintA = outputTokenMint.toBase58() === poolInfo.mintA.toBase58();
984
+ // For exact output, we need to determine if we're inputting token A or B
985
+ // If outputting A, we input B (zeroForOne = false)
986
+ // If outputting B, we input A (zeroForOne = true)
987
+ const zeroForOne = !isOutputMintA;
988
+ if (priceLimit.equals(new Decimal(0))) {
989
+ sqrtPriceLimitX64 = zeroForOne ? MIN_SQRT_PRICE_X64.add(new BN(1)) : MAX_SQRT_PRICE_X64.sub(new BN(1));
990
+ }
991
+ else {
992
+ sqrtPriceLimitX64 = SqrtPriceMath.priceToSqrtPriceX64(priceLimit, poolInfo.mintDecimalsA, poolInfo.mintDecimalsB);
993
+ }
994
+ const exBitmapInfo = await getTickArrayBitmapExtension(this.programId, poolInfo.poolId, this.connection);
995
+ const ammConfig = await RawDataUtils.getRawAmmConfigByConfigId({
996
+ connection: this.connection,
997
+ configId: poolInfo.ammConfig,
998
+ });
999
+ const tickArrayInfo = await getTickArrayInfo({
1000
+ connection: this.connection,
1001
+ poolInfo,
1002
+ exBitmapInfo,
1003
+ });
1004
+ if (!exBitmapInfo || !ammConfig)
1005
+ throw new Error('Failed to get tick array bitmap extension or amm config');
1006
+ const { allTrade, expectedAmountIn, remainingAccounts, executionPrice, feeAmount } = await PoolUtils.getInputAmountAndRemainAccounts({
1007
+ poolInfo,
1008
+ exBitmapInfo,
1009
+ ammConfig,
1010
+ tickArrayInfo,
1011
+ outputTokenMint,
1012
+ outputAmount: amountOut,
1013
+ sqrtPriceLimitX64,
1014
+ catchLiquidityInsufficient,
1015
+ });
1016
+ // For exact output, we calculate max input amount with slippage
1017
+ // This is the maximum amount user is willing to pay
1018
+ const maxAmountIn = expectedAmountIn.mul(new BN(Math.ceil((1 + slippage) * 10000000000))).div(new BN(10000000000));
1019
+ return {
1020
+ allTrade,
1021
+ isOutputMintA,
1022
+ amountOut,
1023
+ expectedAmountIn,
1024
+ maxAmountIn,
1025
+ remainingAccounts,
1026
+ executionPrice,
1027
+ feeAmount,
1028
+ };
1029
+ }
1030
+ /**
1031
+ * Handle SOL/WSOL packaging logic, automatically generate related instructions
1032
+ *
1033
+ * @param params.userAddress User wallet address
1034
+ * @param params.mintA Pool tokenA mint
1035
+ * @param params.mintB Pool tokenB mint
1036
+ * @param params.amountA Optional, tokenA quantity
1037
+ * @param params.amountB Optional, tokenB quantity
1038
+ * @param params.rentExemptLamports Optional, WSOL account rent exemption lamports
1039
+ * @returns tokenAccountA/B, pre-instructions, post-instructions
1040
+ */
1041
+ async handleTokenAccount(params) {
1042
+ const { userAddress, mintA, mintB, amountA, amountB } = params;
1043
+ // Check if there is SOL involved
1044
+ const isTokenASOL = mintA.toString() === NATIVE_MINT.toString();
1045
+ const isTokenBSOL = mintB.toString() === NATIVE_MINT.toString();
1046
+ // Default to use ATA account
1047
+ const tokenProgramIdA = await getTokenProgramId(this.connection, mintA);
1048
+ const tokenProgramIdB = await getTokenProgramId(this.connection, mintB);
1049
+ let tokenAccountA = getATAAddress(userAddress, mintA, tokenProgramIdA).publicKey;
1050
+ let tokenAccountB = getATAAddress(userAddress, mintB, tokenProgramIdB).publicKey;
1051
+ const preInstructions = [];
1052
+ const endInstructions = [];
1053
+ if (!isTokenASOL) {
1054
+ const accA = await this.connection.getAccountInfo(tokenAccountA);
1055
+ if (!accA) {
1056
+ preInstructions.push(createAssociatedTokenAccountIdempotentInstruction(userAddress, tokenAccountA, userAddress, mintA, tokenProgramIdA));
1057
+ }
1058
+ }
1059
+ if (!isTokenBSOL) {
1060
+ const accB = await this.connection.getAccountInfo(tokenAccountB);
1061
+ if (!accB) {
1062
+ preInstructions.push(createAssociatedTokenAccountIdempotentInstruction(userAddress, tokenAccountB, userAddress, mintB, tokenProgramIdB));
1063
+ }
1064
+ }
1065
+ if (!isTokenASOL && !isTokenBSOL) {
1066
+ return { tokenAccountA, tokenAccountB, preInstructions, endInstructions, tokenProgramIdA, tokenProgramIdB };
1067
+ }
1068
+ // Handle SOL -> WSOL packaging
1069
+ if (isTokenASOL || isTokenBSOL) {
1070
+ const newAccount = generatePubKey({
1071
+ fromPublicKey: userAddress,
1072
+ programId: TOKEN_PROGRAM_ID,
1073
+ });
1074
+ const wsolAccount = newAccount.publicKey;
1075
+ const rentExemptLamports = params.rentExemptLamports || (await this.estimateRentFee(AccountLayout.span));
1076
+ // Calculate how much SOL is needed
1077
+ let amountNeeded = 0;
1078
+ if (isTokenASOL && amountA) {
1079
+ amountNeeded = amountA.toNumber();
1080
+ }
1081
+ else if (isTokenBSOL && amountB) {
1082
+ amountNeeded = amountB.toNumber();
1083
+ }
1084
+ // Create WSOL account
1085
+ preInstructions.push(SystemProgram.createAccountWithSeed({
1086
+ fromPubkey: userAddress,
1087
+ basePubkey: userAddress,
1088
+ seed: newAccount.seed,
1089
+ newAccountPubkey: wsolAccount,
1090
+ space: AccountLayout.span,
1091
+ lamports: rentExemptLamports + amountNeeded,
1092
+ programId: TOKEN_PROGRAM_ID,
1093
+ }));
1094
+ // Initialize WSOL account
1095
+ preInstructions.push(createInitializeAccountInstruction(wsolAccount, NATIVE_MINT, userAddress));
1096
+ // Add instructions to close WSOL account
1097
+ endInstructions.push(createCloseAccountInstruction(wsolAccount, userAddress, userAddress, []));
1098
+ // Update the corresponding token account
1099
+ if (isTokenASOL) {
1100
+ tokenAccountA = wsolAccount;
1101
+ }
1102
+ if (isTokenBSOL) {
1103
+ tokenAccountB = wsolAccount;
1104
+ }
1105
+ }
1106
+ return { tokenAccountA, tokenAccountB, preInstructions, endInstructions, tokenProgramIdA, tokenProgramIdB };
1107
+ }
1108
+ async estimateRentFee(space, useCache = true) {
1109
+ if (useCache && this.rentFeeCache[space] !== undefined) {
1110
+ return this.rentFeeCache[space];
1111
+ }
1112
+ const lamports = await this.connection.getMinimumBalanceForRentExemption(space);
1113
+ this.rentFeeCache[space] = lamports;
1114
+ return lamports;
1115
+ }
1116
+ calculateApr = calculateApr;
1117
+ calculateRewardApr = calculateRewardApr;
1118
+ calculateRangeAprs = calculateRangeAprs;
1119
+ alignPriceToTickPrice = alignPriceToTickPrice;
1120
+ getAmountBFromAmountA = getAmountBFromAmountA;
1121
+ getAmountAFromAmountB = getAmountAFromAmountB;
1122
+ }
1123
+ //# sourceMappingURL=chain.js.map