@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.
- package/LICENSE +21 -0
- package/README.md +341 -0
- package/dist/esm/client/chain.d.ts +227 -0
- package/dist/esm/client/chain.d.ts.map +1 -0
- package/dist/esm/client/chain.js +1123 -0
- package/dist/esm/client/chain.js.map +1 -0
- package/dist/esm/client/index.d.ts +4 -0
- package/dist/esm/client/index.d.ts.map +1 -0
- package/dist/esm/client/index.js +4 -0
- package/dist/esm/client/index.js.map +1 -0
- package/dist/esm/client/models.d.ts +160 -0
- package/dist/esm/client/models.d.ts.map +1 -0
- package/dist/esm/client/models.js +2 -0
- package/dist/esm/client/models.js.map +1 -0
- package/dist/esm/client/utils.d.ts +103 -0
- package/dist/esm/client/utils.d.ts.map +1 -0
- package/dist/esm/client/utils.js +238 -0
- package/dist/esm/client/utils.js.map +1 -0
- package/dist/esm/constants.d.ts +19 -0
- package/dist/esm/constants.d.ts.map +1 -0
- package/dist/esm/constants.js +27 -0
- package/dist/esm/constants.js.map +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/instructions/baseInstruction.d.ts +74 -0
- package/dist/esm/instructions/baseInstruction.d.ts.map +1 -0
- package/dist/esm/instructions/baseInstruction.js +577 -0
- package/dist/esm/instructions/baseInstruction.js.map +1 -0
- package/dist/esm/instructions/constants.d.ts +29 -0
- package/dist/esm/instructions/constants.d.ts.map +1 -0
- package/dist/esm/instructions/constants.js +32 -0
- package/dist/esm/instructions/constants.js.map +1 -0
- package/dist/esm/instructions/getRawData.d.ts +60 -0
- package/dist/esm/instructions/getRawData.d.ts.map +1 -0
- package/dist/esm/instructions/getRawData.js +105 -0
- package/dist/esm/instructions/getRawData.js.map +1 -0
- package/dist/esm/instructions/index.d.ts +9 -0
- package/dist/esm/instructions/index.d.ts.map +1 -0
- package/dist/esm/instructions/index.js +9 -0
- package/dist/esm/instructions/index.js.map +1 -0
- package/dist/esm/instructions/instruction.d.ts +137 -0
- package/dist/esm/instructions/instruction.d.ts.map +1 -0
- package/dist/esm/instructions/instruction.js +152 -0
- package/dist/esm/instructions/instruction.js.map +1 -0
- package/dist/esm/instructions/layout.d.ts +217 -0
- package/dist/esm/instructions/layout.d.ts.map +1 -0
- package/dist/esm/instructions/layout.js +203 -0
- package/dist/esm/instructions/layout.js.map +1 -0
- package/dist/esm/instructions/libs/marshmallow/bufferLayout.d.ts +107 -0
- package/dist/esm/instructions/libs/marshmallow/bufferLayout.d.ts.map +1 -0
- package/dist/esm/instructions/libs/marshmallow/bufferLayout.js +49 -0
- package/dist/esm/instructions/libs/marshmallow/bufferLayout.js.map +1 -0
- package/dist/esm/instructions/libs/marshmallow/index.d.ts +88 -0
- package/dist/esm/instructions/libs/marshmallow/index.d.ts.map +1 -0
- package/dist/esm/instructions/libs/marshmallow/index.js +256 -0
- package/dist/esm/instructions/libs/marshmallow/index.js.map +1 -0
- package/dist/esm/instructions/models.d.ts +39 -0
- package/dist/esm/instructions/models.d.ts.map +1 -0
- package/dist/esm/instructions/models.js +14 -0
- package/dist/esm/instructions/models.js.map +1 -0
- package/dist/esm/instructions/pda.d.ts +52 -0
- package/dist/esm/instructions/pda.d.ts.map +1 -0
- package/dist/esm/instructions/pda.js +47 -0
- package/dist/esm/instructions/pda.js.map +1 -0
- package/dist/esm/instructions/target/idl/byreal_amm_v3.json +7242 -0
- package/dist/esm/instructions/target/idl/byreal_amm_v3_test.json +7680 -0
- package/dist/esm/instructions/target/types/byreal_amm_v3.d.ts +5329 -0
- package/dist/esm/instructions/target/types/byreal_amm_v3.d.ts.map +1 -0
- package/dist/esm/instructions/target/types/byreal_amm_v3.js +2 -0
- package/dist/esm/instructions/target/types/byreal_amm_v3.js.map +1 -0
- package/dist/esm/instructions/target/types/raydium_amm_v3.d.ts +4149 -0
- package/dist/esm/instructions/target/types/raydium_amm_v3.d.ts.map +1 -0
- package/dist/esm/instructions/target/types/raydium_amm_v3.js +2 -0
- package/dist/esm/instructions/target/types/raydium_amm_v3.js.map +1 -0
- package/dist/esm/instructions/utils/binaryUtils.d.ts +11 -0
- package/dist/esm/instructions/utils/binaryUtils.d.ts.map +1 -0
- package/dist/esm/instructions/utils/binaryUtils.js +77 -0
- package/dist/esm/instructions/utils/binaryUtils.js.map +1 -0
- package/dist/esm/instructions/utils/fetchWalletTokenAccounts.d.ts +42 -0
- package/dist/esm/instructions/utils/fetchWalletTokenAccounts.d.ts.map +1 -0
- package/dist/esm/instructions/utils/fetchWalletTokenAccounts.js +63 -0
- package/dist/esm/instructions/utils/fetchWalletTokenAccounts.js.map +1 -0
- package/dist/esm/instructions/utils/getTickArrayBitmapExtension.d.ts +8 -0
- package/dist/esm/instructions/utils/getTickArrayBitmapExtension.d.ts.map +1 -0
- package/dist/esm/instructions/utils/getTickArrayBitmapExtension.js +22 -0
- package/dist/esm/instructions/utils/getTickArrayBitmapExtension.js.map +1 -0
- package/dist/esm/instructions/utils/getTickArrayInfo.d.ts +16 -0
- package/dist/esm/instructions/utils/getTickArrayInfo.d.ts.map +1 -0
- package/dist/esm/instructions/utils/getTickArrayInfo.js +75 -0
- package/dist/esm/instructions/utils/getTickArrayInfo.js.map +1 -0
- package/dist/esm/instructions/utils/index.d.ts +21 -0
- package/dist/esm/instructions/utils/index.d.ts.map +1 -0
- package/dist/esm/instructions/utils/index.js +21 -0
- package/dist/esm/instructions/utils/index.js.map +1 -0
- package/dist/esm/instructions/utils/liquidityMath.d.ts +30 -0
- package/dist/esm/instructions/utils/liquidityMath.d.ts.map +1 -0
- package/dist/esm/instructions/utils/liquidityMath.js +138 -0
- package/dist/esm/instructions/utils/liquidityMath.js.map +1 -0
- package/dist/esm/instructions/utils/mathUtils.d.ts +11 -0
- package/dist/esm/instructions/utils/mathUtils.d.ts.map +1 -0
- package/dist/esm/instructions/utils/mathUtils.js +36 -0
- package/dist/esm/instructions/utils/mathUtils.js.map +1 -0
- package/dist/esm/instructions/utils/models.d.ts +93 -0
- package/dist/esm/instructions/utils/models.d.ts.map +1 -0
- package/dist/esm/instructions/utils/models.js +13 -0
- package/dist/esm/instructions/utils/models.js.map +1 -0
- package/dist/esm/instructions/utils/poolStateUtils.d.ts +134 -0
- package/dist/esm/instructions/utils/poolStateUtils.d.ts.map +1 -0
- package/dist/esm/instructions/utils/poolStateUtils.js +137 -0
- package/dist/esm/instructions/utils/poolStateUtils.js.map +1 -0
- package/dist/esm/instructions/utils/poolUtils.d.ts +136 -0
- package/dist/esm/instructions/utils/poolUtils.d.ts.map +1 -0
- package/dist/esm/instructions/utils/poolUtils.js +219 -0
- package/dist/esm/instructions/utils/poolUtils.js.map +1 -0
- package/dist/esm/instructions/utils/position.d.ts +36 -0
- package/dist/esm/instructions/utils/position.d.ts.map +1 -0
- package/dist/esm/instructions/utils/position.js +86 -0
- package/dist/esm/instructions/utils/position.js.map +1 -0
- package/dist/esm/instructions/utils/sqrtPriceMath.d.ts +14 -0
- package/dist/esm/instructions/utils/sqrtPriceMath.d.ts.map +1 -0
- package/dist/esm/instructions/utils/sqrtPriceMath.js +168 -0
- package/dist/esm/instructions/utils/sqrtPriceMath.js.map +1 -0
- package/dist/esm/instructions/utils/swapMath.d.ts +48 -0
- package/dist/esm/instructions/utils/swapMath.d.ts.map +1 -0
- package/dist/esm/instructions/utils/swapMath.js +263 -0
- package/dist/esm/instructions/utils/swapMath.js.map +1 -0
- package/dist/esm/instructions/utils/tick.d.ts +106 -0
- package/dist/esm/instructions/utils/tick.d.ts.map +1 -0
- package/dist/esm/instructions/utils/tick.js +390 -0
- package/dist/esm/instructions/utils/tick.js.map +1 -0
- package/dist/esm/instructions/utils/tickArrayUtils.d.ts +50 -0
- package/dist/esm/instructions/utils/tickArrayUtils.d.ts.map +1 -0
- package/dist/esm/instructions/utils/tickArrayUtils.js +157 -0
- package/dist/esm/instructions/utils/tickArrayUtils.js.map +1 -0
- package/dist/esm/instructions/utils/tickMath.d.ts +18 -0
- package/dist/esm/instructions/utils/tickMath.d.ts.map +1 -0
- package/dist/esm/instructions/utils/tickMath.js +34 -0
- package/dist/esm/instructions/utils/tickMath.js.map +1 -0
- package/dist/esm/instructions/utils/tickarrayBitmap.d.ts +38 -0
- package/dist/esm/instructions/utils/tickarrayBitmap.d.ts.map +1 -0
- package/dist/esm/instructions/utils/tickarrayBitmap.js +157 -0
- package/dist/esm/instructions/utils/tickarrayBitmap.js.map +1 -0
- package/dist/esm/instructions/utils/transfer.d.ts +7 -0
- package/dist/esm/instructions/utils/transfer.d.ts.map +1 -0
- package/dist/esm/instructions/utils/transfer.js +78 -0
- package/dist/esm/instructions/utils/transfer.js.map +1 -0
- package/dist/esm/utils/accountInfo.d.ts +27 -0
- package/dist/esm/utils/accountInfo.d.ts.map +1 -0
- package/dist/esm/utils/accountInfo.js +80 -0
- package/dist/esm/utils/accountInfo.js.map +1 -0
- package/dist/esm/utils/checkV0TxSize.d.ts +8 -0
- package/dist/esm/utils/checkV0TxSize.d.ts.map +1 -0
- package/dist/esm/utils/checkV0TxSize.js +18 -0
- package/dist/esm/utils/checkV0TxSize.js.map +1 -0
- package/dist/esm/utils/estimateComputeUnits.d.ts +11 -0
- package/dist/esm/utils/estimateComputeUnits.d.ts.map +1 -0
- package/dist/esm/utils/estimateComputeUnits.js +48 -0
- package/dist/esm/utils/estimateComputeUnits.js.map +1 -0
- package/dist/esm/utils/generatePubKey.d.ts +10 -0
- package/dist/esm/utils/generatePubKey.d.ts.map +1 -0
- package/dist/esm/utils/generatePubKey.js +14 -0
- package/dist/esm/utils/generatePubKey.js.map +1 -0
- package/dist/esm/utils/index.d.ts +9 -0
- package/dist/esm/utils/index.d.ts.map +1 -0
- package/dist/esm/utils/index.js +11 -0
- package/dist/esm/utils/index.js.map +1 -0
- package/dist/esm/utils/token.d.ts +9 -0
- package/dist/esm/utils/token.d.ts.map +1 -0
- package/dist/esm/utils/token.js +16 -0
- package/dist/esm/utils/token.js.map +1 -0
- package/dist/esm/utils/transactionUtils.d.ts +84 -0
- package/dist/esm/utils/transactionUtils.d.ts.map +1 -0
- package/dist/esm/utils/transactionUtils.js +138 -0
- package/dist/esm/utils/transactionUtils.js.map +1 -0
- package/dist/esm/utils/validateAndParsePublicKey.d.ts +15 -0
- package/dist/esm/utils/validateAndParsePublicKey.d.ts.map +1 -0
- package/dist/esm/utils/validateAndParsePublicKey.js +42 -0
- package/dist/esm/utils/validateAndParsePublicKey.js.map +1 -0
- 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
|