sol-trade-sdk 0.1.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/README.md +390 -0
- package/dist/chunk-MMQAMIKR.mjs +3735 -0
- package/dist/chunk-NEZDFAYA.mjs +7744 -0
- package/dist/clients-VITWK7B6.mjs +1370 -0
- package/dist/index-1BK_FXsW.d.mts +2327 -0
- package/dist/index-1BK_FXsW.d.ts +2327 -0
- package/dist/index.d.mts +2659 -0
- package/dist/index.d.ts +2659 -0
- package/dist/index.js +13265 -0
- package/dist/index.mjs +562 -0
- package/dist/perf/index.d.mts +2 -0
- package/dist/perf/index.d.ts +2 -0
- package/dist/perf/index.js +3742 -0
- package/dist/perf/index.mjs +214 -0
- package/package.json +101 -0
- package/src/__tests__/complete_sdk.test.ts +354 -0
- package/src/__tests__/hotpath.test.ts +486 -0
- package/src/__tests__/nonce.test.ts +45 -0
- package/src/__tests__/sdk.test.ts +425 -0
- package/src/address-lookup/index.ts +197 -0
- package/src/cache/cache.ts +308 -0
- package/src/calc/index.ts +1058 -0
- package/src/calc/pumpfun.ts +124 -0
- package/src/common/bonding_curve.ts +272 -0
- package/src/common/compute-budget.ts +148 -0
- package/src/common/confirm-any-signature.ts +184 -0
- package/src/common/fast-timing.ts +481 -0
- package/src/common/fast_fn.ts +150 -0
- package/src/common/gas-fee-strategy.ts +253 -0
- package/src/common/map-pool.ts +23 -0
- package/src/common/nonce.ts +40 -0
- package/src/common/sdk-log.ts +460 -0
- package/src/common/seed.ts +381 -0
- package/src/common/spl-token.ts +578 -0
- package/src/common/subscription-handle.ts +644 -0
- package/src/common/trading-utils.ts +239 -0
- package/src/common/wsol-manager.ts +325 -0
- package/src/compute/compute_budget_manager.ts +187 -0
- package/src/compute/index.ts +21 -0
- package/src/constants/index.ts +96 -0
- package/src/execution/execution.ts +532 -0
- package/src/execution/index.ts +42 -0
- package/src/hotpath/executor.ts +464 -0
- package/src/hotpath/index.ts +64 -0
- package/src/hotpath/state.ts +435 -0
- package/src/index.ts +2117 -0
- package/src/instruction/bonk_builder.ts +730 -0
- package/src/instruction/index.ts +24 -0
- package/src/instruction/meteora_damm_v2_builder.ts +509 -0
- package/src/instruction/pumpfun_builder.ts +1183 -0
- package/src/instruction/pumpswap.ts +1123 -0
- package/src/instruction/raydium_amm_v4_builder.ts +692 -0
- package/src/instruction/raydium_cpmm_builder.ts +795 -0
- package/src/middleware/traits.ts +407 -0
- package/src/params/index.ts +483 -0
- package/src/perf/compiler-optimization.ts +529 -0
- package/src/perf/hardware.ts +631 -0
- package/src/perf/index.ts +9 -0
- package/src/perf/kernel-bypass.ts +656 -0
- package/src/perf/protocol.ts +682 -0
- package/src/perf/realtime.ts +592 -0
- package/src/perf/simd.ts +668 -0
- package/src/perf/syscall-bypass.ts +331 -0
- package/src/perf/ultra-low-latency.ts +505 -0
- package/src/perf/zero-copy.ts +589 -0
- package/src/pool/pool.ts +294 -0
- package/src/rpc/client.ts +345 -0
- package/src/sdk-errors.ts +13 -0
- package/src/security/index.ts +26 -0
- package/src/security/secure-key.ts +303 -0
- package/src/security/validators.ts +281 -0
- package/src/seed/pda.ts +262 -0
- package/src/serialization/index.ts +28 -0
- package/src/serialization/serialization.ts +288 -0
- package/src/swqos/clients.ts +1754 -0
- package/src/swqos/index.ts +50 -0
- package/src/swqos/providers.ts +1707 -0
- package/src/trading/core/async-executor.ts +702 -0
- package/src/trading/core/confirmation-monitor.ts +711 -0
- package/src/trading/core/index.ts +82 -0
- package/src/trading/core/retry-handler.ts +683 -0
- package/src/trading/core/transaction-pool.ts +780 -0
- package/src/trading/executor.ts +385 -0
- package/src/trading/factory.ts +282 -0
- package/src/trading/index.ts +30 -0
- package/src/types.ts +8 -0
- package/src/utils/index.ts +155 -0
|
@@ -0,0 +1,1058 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculation utilities for Sol Trade SDK
|
|
3
|
+
*
|
|
4
|
+
* Security features:
|
|
5
|
+
* - Overflow/underflow protection
|
|
6
|
+
* - Input validation
|
|
7
|
+
* - Bounds checking
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { PublicKey } from '@solana/web3.js';
|
|
11
|
+
import BN from 'bn.js';
|
|
12
|
+
|
|
13
|
+
// ===== Security Constants =====
|
|
14
|
+
|
|
15
|
+
const MAX_SAFE_BIGINT = BigInt('18446744073709551615'); // 2^64 - 1
|
|
16
|
+
const MAX_BASIS_POINTS = BigInt(10000);
|
|
17
|
+
|
|
18
|
+
/// Maximum slippage in basis points (99.99% = 9999 bps)
|
|
19
|
+
/// This prevents the wrap amount from doubling when slippage is 100%
|
|
20
|
+
const MAX_SLIPPAGE_BASIS_POINTS = BigInt(9999);
|
|
21
|
+
|
|
22
|
+
// ===== Error Classes =====
|
|
23
|
+
|
|
24
|
+
export class CalculationError extends Error {
|
|
25
|
+
constructor(message: string) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'CalculationError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ===== Validation Functions =====
|
|
32
|
+
|
|
33
|
+
function validateAmount(amount: bigint, name: string = 'amount'): void {
|
|
34
|
+
if (amount < BigInt(0)) {
|
|
35
|
+
throw new CalculationError(`${name} cannot be negative: ${amount}`);
|
|
36
|
+
}
|
|
37
|
+
if (amount > MAX_SAFE_BIGINT) {
|
|
38
|
+
throw new CalculationError(`${name} exceeds maximum safe value: ${amount}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function validateBasisPoints(basisPoints: bigint): void {
|
|
43
|
+
if (basisPoints < BigInt(0) || basisPoints > MAX_BASIS_POINTS) {
|
|
44
|
+
throw new CalculationError(`Basis points must be between 0 and 10000, got ${basisPoints}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function checkOverflow(a: bigint, b: bigint, operation: 'multiply' | 'add'): void {
|
|
49
|
+
if (operation === 'multiply') {
|
|
50
|
+
// Check if multiplication would overflow
|
|
51
|
+
if (a !== BigInt(0) && b > MAX_SAFE_BIGINT / a) {
|
|
52
|
+
throw new CalculationError(`Multiplication overflow: ${a} * ${b}`);
|
|
53
|
+
}
|
|
54
|
+
} else if (operation === 'add') {
|
|
55
|
+
// Check if addition would overflow
|
|
56
|
+
if (a > MAX_SAFE_BIGINT - b) {
|
|
57
|
+
throw new CalculationError(`Addition overflow: ${a} + ${b}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ===== Common Calculation Functions =====
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Compute fee based on amount and fee basis points
|
|
66
|
+
* Includes overflow protection
|
|
67
|
+
*/
|
|
68
|
+
export function computeFee(amount: bigint, feeBasisPoints: bigint): bigint {
|
|
69
|
+
validateAmount(amount, 'amount');
|
|
70
|
+
validateBasisPoints(feeBasisPoints);
|
|
71
|
+
|
|
72
|
+
checkOverflow(amount, feeBasisPoints, 'multiply');
|
|
73
|
+
return ceilDiv(amount * feeBasisPoints, BigInt(10000));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Ceiling division with zero check
|
|
78
|
+
*/
|
|
79
|
+
export function ceilDiv(a: bigint, b: bigint): bigint {
|
|
80
|
+
if (b === BigInt(0)) {
|
|
81
|
+
throw new CalculationError('Division by zero');
|
|
82
|
+
}
|
|
83
|
+
validateAmount(a, 'dividend');
|
|
84
|
+
validateAmount(b, 'divisor');
|
|
85
|
+
|
|
86
|
+
checkOverflow(a, b - BigInt(1), 'add');
|
|
87
|
+
return (a + b - BigInt(1)) / b;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Calculate buy amount with slippage protection
|
|
92
|
+
* Includes overflow protection and validation
|
|
93
|
+
*
|
|
94
|
+
* Note: Basis points are clamped to MAX_SLIPPAGE_BASIS_POINTS (9999 = 99.99%)
|
|
95
|
+
* to prevent the amount from doubling when basisPoints = 10000.
|
|
96
|
+
*/
|
|
97
|
+
export function calculateWithSlippageBuy(amount: bigint, basisPoints: bigint): bigint {
|
|
98
|
+
validateAmount(amount, 'amount');
|
|
99
|
+
|
|
100
|
+
// Clamp basis points to max 9999 (99.99%) to prevent amount doubling at 100%
|
|
101
|
+
const bps = basisPoints > MAX_SLIPPAGE_BASIS_POINTS ? MAX_SLIPPAGE_BASIS_POINTS : basisPoints;
|
|
102
|
+
|
|
103
|
+
checkOverflow(amount, bps, 'multiply');
|
|
104
|
+
const slippageAmount = (amount * bps) / BigInt(10000);
|
|
105
|
+
|
|
106
|
+
checkOverflow(amount, slippageAmount, 'add');
|
|
107
|
+
return amount + slippageAmount;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Calculate sell amount with slippage protection
|
|
112
|
+
* Includes underflow protection
|
|
113
|
+
*
|
|
114
|
+
* 100% from Rust: src/utils/calc/common.rs calculate_with_slippage_sell
|
|
115
|
+
*
|
|
116
|
+
* Note: Returns 1n if amount <= basisPoints / 10000n to ensure minimum output.
|
|
117
|
+
*/
|
|
118
|
+
export function calculateWithSlippageSell(amount: bigint, basisPoints: bigint): bigint {
|
|
119
|
+
validateAmount(amount, 'amount');
|
|
120
|
+
validateBasisPoints(basisPoints);
|
|
121
|
+
|
|
122
|
+
// Rust: if amount <= basis_points / 10000 { 1 } else { ... }
|
|
123
|
+
if (amount <= basisPoints / BigInt(10000)) {
|
|
124
|
+
return BigInt(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
checkOverflow(amount, basisPoints, 'multiply');
|
|
128
|
+
const slippageAmount = (amount * basisPoints) / BigInt(10000);
|
|
129
|
+
|
|
130
|
+
return amount - slippageAmount;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ===== PumpFun Constants =====
|
|
134
|
+
// Values from Rust: src/instruction/utils/pumpfun.rs global_constants
|
|
135
|
+
export const PUMPFUN_CONSTANTS = {
|
|
136
|
+
FEE_BASIS_POINTS: BigInt(95), // Protocol fee (NOT 100!)
|
|
137
|
+
CREATOR_FEE: BigInt(30), // Creator fee (NOT 50!)
|
|
138
|
+
INITIAL_VIRTUAL_TOKEN_RESERVES: BigInt('1073000000000000'),
|
|
139
|
+
INITIAL_VIRTUAL_SOL_RESERVES: BigInt('30000000000'),
|
|
140
|
+
INITIAL_REAL_TOKEN_RESERVES: BigInt('793100000000000'), // Fixed: was 793000000000000
|
|
141
|
+
TOKEN_TOTAL_SUPPLY: BigInt('1000000000000000'),
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Calculate buy token amount from SOL amount for PumpFun
|
|
146
|
+
*/
|
|
147
|
+
export function getBuyTokenAmountFromSolAmount(
|
|
148
|
+
virtualTokenReserves: bigint,
|
|
149
|
+
virtualSolReserves: bigint,
|
|
150
|
+
realTokenReserves: bigint,
|
|
151
|
+
hasCreator: boolean,
|
|
152
|
+
amount: bigint
|
|
153
|
+
): bigint {
|
|
154
|
+
if (amount === BigInt(0) || virtualTokenReserves === BigInt(0)) {
|
|
155
|
+
return BigInt(0);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let totalFeeBasisPoints = PUMPFUN_CONSTANTS.FEE_BASIS_POINTS;
|
|
159
|
+
if (hasCreator) {
|
|
160
|
+
totalFeeBasisPoints += PUMPFUN_CONSTANTS.CREATOR_FEE;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const inputAmount =
|
|
164
|
+
(amount * BigInt(10000)) / (totalFeeBasisPoints + BigInt(10000));
|
|
165
|
+
const denominator = virtualSolReserves + inputAmount;
|
|
166
|
+
|
|
167
|
+
let tokensReceived =
|
|
168
|
+
(inputAmount * virtualTokenReserves) / denominator;
|
|
169
|
+
|
|
170
|
+
if (tokensReceived > realTokenReserves) {
|
|
171
|
+
tokensReceived = realTokenReserves;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Minimum token protection
|
|
175
|
+
if (tokensReceived <= BigInt(100) * BigInt(1000000)) {
|
|
176
|
+
if (amount > BigInt(10000000)) {
|
|
177
|
+
// > 0.01 SOL
|
|
178
|
+
tokensReceived = BigInt('25547619000000000');
|
|
179
|
+
} else {
|
|
180
|
+
tokensReceived = BigInt('255476000000000');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return tokensReceived;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Calculate sell SOL amount from token amount for PumpFun
|
|
189
|
+
*/
|
|
190
|
+
export function getSellSolAmountFromTokenAmount(
|
|
191
|
+
virtualTokenReserves: bigint,
|
|
192
|
+
virtualSolReserves: bigint,
|
|
193
|
+
hasCreator: boolean,
|
|
194
|
+
amount: bigint
|
|
195
|
+
): bigint {
|
|
196
|
+
if (amount === BigInt(0) || virtualTokenReserves === BigInt(0)) {
|
|
197
|
+
return BigInt(0);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const numerator = amount * virtualSolReserves;
|
|
201
|
+
const denominator = virtualTokenReserves + amount;
|
|
202
|
+
|
|
203
|
+
const solCost = numerator / denominator;
|
|
204
|
+
|
|
205
|
+
let totalFeeBasisPoints = PUMPFUN_CONSTANTS.FEE_BASIS_POINTS;
|
|
206
|
+
if (hasCreator) {
|
|
207
|
+
totalFeeBasisPoints += PUMPFUN_CONSTANTS.CREATOR_FEE;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const fee = computeFee(solCost, totalFeeBasisPoints);
|
|
211
|
+
|
|
212
|
+
if (solCost < fee) {
|
|
213
|
+
return BigInt(0);
|
|
214
|
+
}
|
|
215
|
+
return solCost - fee;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ===== PumpSwap Constants =====
|
|
219
|
+
// Values from Rust: src/instruction/utils/pumpswap.rs accounts
|
|
220
|
+
export const PUMPSWAP_CONSTANTS = {
|
|
221
|
+
LP_FEE_BASIS_POINTS: BigInt(25), // 0.25% (was 20)
|
|
222
|
+
PROTOCOL_FEE_BASIS_POINTS: BigInt(5), // 0.05% (was 20)
|
|
223
|
+
COIN_CREATOR_FEE_BASIS_POINTS: BigInt(5), // 0.05% (was 10)
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export interface BuyBaseInputResult {
|
|
227
|
+
internalQuoteAmount: bigint;
|
|
228
|
+
uiQuote: bigint;
|
|
229
|
+
maxQuote: bigint;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export interface BuyQuoteInputResult {
|
|
233
|
+
base: bigint;
|
|
234
|
+
internalQuoteWithoutFees: bigint;
|
|
235
|
+
maxQuote: bigint;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface SellBaseInputResult {
|
|
239
|
+
uiQuote: bigint;
|
|
240
|
+
minQuote: bigint;
|
|
241
|
+
internalQuoteAmountOut: bigint;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface SellQuoteInputResult {
|
|
245
|
+
internalRawQuote: bigint;
|
|
246
|
+
base: bigint;
|
|
247
|
+
minQuote: bigint;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Calculate quote needed to buy base tokens on PumpSwap
|
|
252
|
+
*/
|
|
253
|
+
export function buyBaseInputInternal(
|
|
254
|
+
base: bigint,
|
|
255
|
+
slippageBasisPoints: bigint,
|
|
256
|
+
baseReserve: bigint,
|
|
257
|
+
quoteReserve: bigint,
|
|
258
|
+
hasCoinCreator: boolean
|
|
259
|
+
): BuyBaseInputResult {
|
|
260
|
+
if (baseReserve === BigInt(0) || quoteReserve === BigInt(0)) {
|
|
261
|
+
throw new Error('Invalid input: reserves cannot be zero');
|
|
262
|
+
}
|
|
263
|
+
if (base > baseReserve) {
|
|
264
|
+
throw new Error('Cannot buy more base tokens than pool reserves');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const numerator = quoteReserve * base;
|
|
268
|
+
const denominator = baseReserve - base;
|
|
269
|
+
|
|
270
|
+
if (denominator === BigInt(0)) {
|
|
271
|
+
throw new Error('Pool would be depleted');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const quoteAmountIn = ceilDiv(numerator, denominator);
|
|
275
|
+
|
|
276
|
+
const lpFee = computeFee(quoteAmountIn, PUMPSWAP_CONSTANTS.LP_FEE_BASIS_POINTS);
|
|
277
|
+
const protocolFee = computeFee(quoteAmountIn, PUMPSWAP_CONSTANTS.PROTOCOL_FEE_BASIS_POINTS);
|
|
278
|
+
let coinCreatorFee = BigInt(0);
|
|
279
|
+
if (hasCoinCreator) {
|
|
280
|
+
coinCreatorFee = computeFee(quoteAmountIn, PUMPSWAP_CONSTANTS.COIN_CREATOR_FEE_BASIS_POINTS);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const totalQuote = quoteAmountIn + lpFee + protocolFee + coinCreatorFee;
|
|
284
|
+
const maxQuote = calculateWithSlippageBuy(totalQuote, slippageBasisPoints);
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
internalQuoteAmount: quoteAmountIn,
|
|
288
|
+
uiQuote: totalQuote,
|
|
289
|
+
maxQuote,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Calculate base tokens received for quote input on PumpSwap
|
|
295
|
+
*/
|
|
296
|
+
export function buyQuoteInputInternal(
|
|
297
|
+
quote: bigint,
|
|
298
|
+
slippageBasisPoints: bigint,
|
|
299
|
+
baseReserve: bigint,
|
|
300
|
+
quoteReserve: bigint,
|
|
301
|
+
hasCoinCreator: boolean
|
|
302
|
+
): BuyQuoteInputResult {
|
|
303
|
+
if (baseReserve === BigInt(0) || quoteReserve === BigInt(0)) {
|
|
304
|
+
throw new Error('Invalid input: reserves cannot be zero');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let totalFeeBps =
|
|
308
|
+
PUMPSWAP_CONSTANTS.LP_FEE_BASIS_POINTS +
|
|
309
|
+
PUMPSWAP_CONSTANTS.PROTOCOL_FEE_BASIS_POINTS;
|
|
310
|
+
if (hasCoinCreator) {
|
|
311
|
+
totalFeeBps += PUMPSWAP_CONSTANTS.COIN_CREATOR_FEE_BASIS_POINTS;
|
|
312
|
+
}
|
|
313
|
+
const denominator = BigInt(10000) + totalFeeBps;
|
|
314
|
+
|
|
315
|
+
const effectiveQuote = (quote * BigInt(10000)) / denominator;
|
|
316
|
+
|
|
317
|
+
const numerator = baseReserve * effectiveQuote;
|
|
318
|
+
const denominatorEffective = quoteReserve + effectiveQuote;
|
|
319
|
+
|
|
320
|
+
if (denominatorEffective === BigInt(0)) {
|
|
321
|
+
throw new Error('Pool would be depleted');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const baseAmountOut = numerator / denominatorEffective;
|
|
325
|
+
const maxQuote = calculateWithSlippageBuy(quote, slippageBasisPoints);
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
base: baseAmountOut,
|
|
329
|
+
internalQuoteWithoutFees: effectiveQuote,
|
|
330
|
+
maxQuote,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Calculate quote received for selling base tokens on PumpSwap
|
|
336
|
+
*/
|
|
337
|
+
export function sellBaseInputInternal(
|
|
338
|
+
base: bigint,
|
|
339
|
+
slippageBasisPoints: bigint,
|
|
340
|
+
baseReserve: bigint,
|
|
341
|
+
quoteReserve: bigint,
|
|
342
|
+
hasCoinCreator: boolean
|
|
343
|
+
): SellBaseInputResult {
|
|
344
|
+
if (baseReserve === BigInt(0) || quoteReserve === BigInt(0)) {
|
|
345
|
+
throw new Error('Invalid input: reserves cannot be zero');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const quoteAmountOut =
|
|
349
|
+
(quoteReserve * base) / (baseReserve + base);
|
|
350
|
+
|
|
351
|
+
const lpFee = computeFee(quoteAmountOut, PUMPSWAP_CONSTANTS.LP_FEE_BASIS_POINTS);
|
|
352
|
+
const protocolFee = computeFee(quoteAmountOut, PUMPSWAP_CONSTANTS.PROTOCOL_FEE_BASIS_POINTS);
|
|
353
|
+
let coinCreatorFee = BigInt(0);
|
|
354
|
+
if (hasCoinCreator) {
|
|
355
|
+
coinCreatorFee = computeFee(quoteAmountOut, PUMPSWAP_CONSTANTS.COIN_CREATOR_FEE_BASIS_POINTS);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const totalFees = lpFee + protocolFee + coinCreatorFee;
|
|
359
|
+
if (totalFees > quoteAmountOut) {
|
|
360
|
+
throw new Error('Fees exceed output');
|
|
361
|
+
}
|
|
362
|
+
const finalQuote = quoteAmountOut - totalFees;
|
|
363
|
+
const minQuote = calculateWithSlippageSell(finalQuote, slippageBasisPoints);
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
uiQuote: finalQuote,
|
|
367
|
+
minQuote,
|
|
368
|
+
internalQuoteAmountOut: quoteAmountOut,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Calculate base needed to receive quote amount on PumpSwap
|
|
374
|
+
*/
|
|
375
|
+
export function sellQuoteInputInternal(
|
|
376
|
+
quote: bigint,
|
|
377
|
+
slippageBasisPoints: bigint,
|
|
378
|
+
baseReserve: bigint,
|
|
379
|
+
quoteReserve: bigint,
|
|
380
|
+
hasCoinCreator: boolean
|
|
381
|
+
): SellQuoteInputResult {
|
|
382
|
+
if (baseReserve === BigInt(0) || quoteReserve === BigInt(0)) {
|
|
383
|
+
throw new Error('Invalid input: reserves cannot be zero');
|
|
384
|
+
}
|
|
385
|
+
if (quote > quoteReserve) {
|
|
386
|
+
throw new Error('Cannot receive more than pool reserves');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
let coinCreatorFee = BigInt(0);
|
|
390
|
+
if (hasCoinCreator) {
|
|
391
|
+
coinCreatorFee = PUMPSWAP_CONSTANTS.COIN_CREATOR_FEE_BASIS_POINTS;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const rawQuote = calculateQuoteAmountOut(
|
|
395
|
+
quote,
|
|
396
|
+
PUMPSWAP_CONSTANTS.LP_FEE_BASIS_POINTS,
|
|
397
|
+
PUMPSWAP_CONSTANTS.PROTOCOL_FEE_BASIS_POINTS,
|
|
398
|
+
coinCreatorFee
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
if (rawQuote >= quoteReserve) {
|
|
402
|
+
throw new Error('Invalid input: desired amount exceeds reserve');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const baseAmountIn = ceilDiv(
|
|
406
|
+
baseReserve * rawQuote,
|
|
407
|
+
quoteReserve - rawQuote
|
|
408
|
+
);
|
|
409
|
+
const minQuote = calculateWithSlippageSell(quote, slippageBasisPoints);
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
internalRawQuote: rawQuote,
|
|
413
|
+
base: baseAmountIn,
|
|
414
|
+
minQuote,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function calculateQuoteAmountOut(
|
|
419
|
+
userQuoteAmountOut: bigint,
|
|
420
|
+
lpFeeBasisPoints: bigint,
|
|
421
|
+
protocolFeeBasisPoints: bigint,
|
|
422
|
+
coinCreatorFeeBasisPoints: bigint
|
|
423
|
+
): bigint {
|
|
424
|
+
const totalFeeBasisPoints =
|
|
425
|
+
lpFeeBasisPoints + protocolFeeBasisPoints + coinCreatorFeeBasisPoints;
|
|
426
|
+
const denominator = BigInt(10000) - totalFeeBasisPoints;
|
|
427
|
+
return ceilDiv(userQuoteAmountOut * BigInt(10000), denominator);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ===== Bonk Constants =====
|
|
431
|
+
|
|
432
|
+
export const BONK_CONSTANTS = {
|
|
433
|
+
PROTOCOL_FEE_RATE: BigInt(25), // 0.25%
|
|
434
|
+
PLATFORM_FEE_RATE: BigInt(100), // 1%
|
|
435
|
+
SHARE_FEE_RATE: BigInt(0), // 0%
|
|
436
|
+
DEFAULT_VIRTUAL_BASE: BigInt('1073025605596382'),
|
|
437
|
+
DEFAULT_VIRTUAL_QUOTE: BigInt('30000852951'),
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Calculate output amount for Bonk
|
|
442
|
+
*/
|
|
443
|
+
export function getBonkAmountOut(
|
|
444
|
+
amountIn: bigint,
|
|
445
|
+
virtualBase: bigint,
|
|
446
|
+
virtualQuote: bigint
|
|
447
|
+
): bigint {
|
|
448
|
+
if (virtualBase === BigInt(0) || virtualQuote === BigInt(0)) {
|
|
449
|
+
return BigInt(0);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const amountOut = (amountIn * virtualQuote) / virtualBase;
|
|
453
|
+
return amountOut;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Calculate input amount needed for Bonk
|
|
458
|
+
*/
|
|
459
|
+
export function getBonkAmountIn(
|
|
460
|
+
amountOut: bigint,
|
|
461
|
+
virtualBase: bigint,
|
|
462
|
+
virtualQuote: bigint
|
|
463
|
+
): bigint {
|
|
464
|
+
if (virtualBase === BigInt(0) || virtualQuote === BigInt(0)) {
|
|
465
|
+
return BigInt(0);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const totalFeeRate =
|
|
469
|
+
BONK_CONSTANTS.PROTOCOL_FEE_RATE +
|
|
470
|
+
BONK_CONSTANTS.PLATFORM_FEE_RATE +
|
|
471
|
+
BONK_CONSTANTS.SHARE_FEE_RATE;
|
|
472
|
+
const amountIn =
|
|
473
|
+
((amountOut * BigInt(10000)) / (BigInt(10000) - totalFeeRate) * virtualBase) /
|
|
474
|
+
virtualQuote;
|
|
475
|
+
|
|
476
|
+
return amountIn;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ===== Raydium Calculations =====
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Calculate output amount for Raydium AMM V4
|
|
483
|
+
*/
|
|
484
|
+
export function raydiumAmmV4GetAmountOut(
|
|
485
|
+
amountIn: bigint,
|
|
486
|
+
inputReserve: bigint,
|
|
487
|
+
outputReserve: bigint
|
|
488
|
+
): bigint {
|
|
489
|
+
if (inputReserve === BigInt(0) || outputReserve === BigInt(0)) {
|
|
490
|
+
return BigInt(0);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Apply 0.25% fee
|
|
494
|
+
const amountInWithFee = amountIn * BigInt(9975);
|
|
495
|
+
const numerator = amountInWithFee * outputReserve;
|
|
496
|
+
const denominator = inputReserve * BigInt(10000) + amountInWithFee;
|
|
497
|
+
|
|
498
|
+
return numerator / denominator;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Calculate input amount needed for Raydium AMM V4
|
|
503
|
+
*/
|
|
504
|
+
export function raydiumAmmV4GetAmountIn(
|
|
505
|
+
amountOut: bigint,
|
|
506
|
+
inputReserve: bigint,
|
|
507
|
+
outputReserve: bigint
|
|
508
|
+
): bigint {
|
|
509
|
+
if (inputReserve === BigInt(0) || outputReserve === BigInt(0) || amountOut >= outputReserve) {
|
|
510
|
+
return BigInt(0);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const numerator = inputReserve * amountOut * BigInt(10000);
|
|
514
|
+
const denominator = (outputReserve - amountOut) * BigInt(9975);
|
|
515
|
+
|
|
516
|
+
return ceilDiv(numerator, denominator);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Calculate output amount for Raydium CPMM
|
|
521
|
+
*/
|
|
522
|
+
export function raydiumCpmmGetAmountOut(
|
|
523
|
+
amountIn: bigint,
|
|
524
|
+
inputReserve: bigint,
|
|
525
|
+
outputReserve: bigint
|
|
526
|
+
): bigint {
|
|
527
|
+
if (inputReserve === BigInt(0) || outputReserve === BigInt(0)) {
|
|
528
|
+
return BigInt(0);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const amountOut = (amountIn * outputReserve) / (inputReserve + amountIn);
|
|
532
|
+
return amountOut;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// ===== Meteora DAMM V2 Calculations =====
|
|
536
|
+
|
|
537
|
+
export interface MeteoraSwapResult {
|
|
538
|
+
amountOut: bigint;
|
|
539
|
+
minAmountOut: bigint;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Compute swap amount for Meteora DAMM V2
|
|
544
|
+
*/
|
|
545
|
+
export function meteoraDammV2ComputeSwapAmount(
|
|
546
|
+
tokenAReserve: bigint,
|
|
547
|
+
tokenBReserve: bigint,
|
|
548
|
+
isAToB: boolean,
|
|
549
|
+
amountIn: bigint,
|
|
550
|
+
slippageBasisPoints: bigint
|
|
551
|
+
): MeteoraSwapResult {
|
|
552
|
+
if (amountIn === BigInt(0)) {
|
|
553
|
+
return { amountOut: BigInt(0), minAmountOut: BigInt(0) };
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
let amountOut: bigint;
|
|
557
|
+
|
|
558
|
+
if (isAToB) {
|
|
559
|
+
// Swapping token A for token B
|
|
560
|
+
if (tokenAReserve === BigInt(0)) {
|
|
561
|
+
return { amountOut: BigInt(0), minAmountOut: BigInt(0) };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Constant product: b_out = (b_reserve * a_in) / (a_reserve + a_in)
|
|
565
|
+
const numerator = tokenBReserve * amountIn;
|
|
566
|
+
const denominator = tokenAReserve + amountIn;
|
|
567
|
+
|
|
568
|
+
if (denominator === BigInt(0)) {
|
|
569
|
+
return { amountOut: BigInt(0), minAmountOut: BigInt(0) };
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
amountOut = numerator / denominator;
|
|
573
|
+
} else {
|
|
574
|
+
// Swapping token B for token A
|
|
575
|
+
if (tokenBReserve === BigInt(0)) {
|
|
576
|
+
return { amountOut: BigInt(0), minAmountOut: BigInt(0) };
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Constant product: a_out = (a_reserve * b_in) / (b_reserve + b_in)
|
|
580
|
+
const numerator = tokenAReserve * amountIn;
|
|
581
|
+
const denominator = tokenBReserve + amountIn;
|
|
582
|
+
|
|
583
|
+
if (denominator === BigInt(0)) {
|
|
584
|
+
return { amountOut: BigInt(0), minAmountOut: BigInt(0) };
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
amountOut = numerator / denominator;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Apply slippage
|
|
591
|
+
const minAmountOut = calculateWithSlippageSell(amountOut, slippageBasisPoints);
|
|
592
|
+
|
|
593
|
+
return { amountOut, minAmountOut };
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Calculate current price (token B per token A) for Meteora DAMM V2
|
|
598
|
+
*/
|
|
599
|
+
export function meteoraDammV2CalculatePrice(
|
|
600
|
+
tokenAReserve: bigint,
|
|
601
|
+
tokenBReserve: bigint
|
|
602
|
+
): number {
|
|
603
|
+
if (tokenAReserve === BigInt(0)) {
|
|
604
|
+
return 0.0;
|
|
605
|
+
}
|
|
606
|
+
return Number(tokenBReserve) / Number(tokenAReserve);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Calculate liquidity (geometric mean of reserves) for Meteora DAMM V2
|
|
611
|
+
*/
|
|
612
|
+
export function meteoraDammV2CalculateLiquidity(
|
|
613
|
+
tokenAReserve: bigint,
|
|
614
|
+
tokenBReserve: bigint
|
|
615
|
+
): bigint {
|
|
616
|
+
if (tokenAReserve === BigInt(0) || tokenBReserve === BigInt(0)) {
|
|
617
|
+
return BigInt(0);
|
|
618
|
+
}
|
|
619
|
+
return BigInt(Math.floor(Math.sqrt(Number(tokenAReserve) * Number(tokenBReserve))));
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Calculate output amount with fee consideration for Meteora DAMM V2
|
|
624
|
+
*/
|
|
625
|
+
export function meteoraDammV2GetAmountOut(
|
|
626
|
+
amountIn: bigint,
|
|
627
|
+
inputReserve: bigint,
|
|
628
|
+
outputReserve: bigint,
|
|
629
|
+
feeBasisPoints: bigint
|
|
630
|
+
): bigint {
|
|
631
|
+
if (inputReserve === BigInt(0) || outputReserve === BigInt(0) || amountIn === BigInt(0)) {
|
|
632
|
+
return BigInt(0);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Apply fee
|
|
636
|
+
const amountInAfterFee = (amountIn * (BigInt(10000) - feeBasisPoints)) / BigInt(10000);
|
|
637
|
+
|
|
638
|
+
const numerator = amountInAfterFee * outputReserve;
|
|
639
|
+
const denominator = inputReserve + amountInAfterFee;
|
|
640
|
+
|
|
641
|
+
return numerator / denominator;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Calculate input amount needed for desired output for Meteora DAMM V2
|
|
646
|
+
*/
|
|
647
|
+
export function meteoraDammV2GetAmountIn(
|
|
648
|
+
amountOut: bigint,
|
|
649
|
+
inputReserve: bigint,
|
|
650
|
+
outputReserve: bigint,
|
|
651
|
+
feeBasisPoints: bigint
|
|
652
|
+
): bigint {
|
|
653
|
+
if (inputReserve === BigInt(0) || outputReserve === BigInt(0) || amountOut >= outputReserve) {
|
|
654
|
+
return BigInt(0);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const numerator = inputReserve * amountOut * BigInt(10000);
|
|
658
|
+
const denominator = (outputReserve - amountOut) * (BigInt(10000) - feeBasisPoints);
|
|
659
|
+
|
|
660
|
+
return ceilDiv(numerator, denominator);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// ===== Utility Functions =====
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Calculate price impact percentage
|
|
667
|
+
*/
|
|
668
|
+
export function calculatePriceImpact(reserveIn: bigint, amountIn: bigint): number {
|
|
669
|
+
if (reserveIn === BigInt(0)) {
|
|
670
|
+
return 0;
|
|
671
|
+
}
|
|
672
|
+
return Number((amountIn * BigInt(10000)) / reserveIn) / 100;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Calculate price from reserves
|
|
677
|
+
*/
|
|
678
|
+
export function calculatePrice(
|
|
679
|
+
quoteReserve: bigint,
|
|
680
|
+
baseReserve: bigint,
|
|
681
|
+
quoteDecimals: number,
|
|
682
|
+
baseDecimals: number
|
|
683
|
+
): number {
|
|
684
|
+
if (baseReserve === BigInt(0)) {
|
|
685
|
+
return 0;
|
|
686
|
+
}
|
|
687
|
+
const quoteAdjusted = Number(quoteReserve) / Math.pow(10, quoteDecimals);
|
|
688
|
+
const baseAdjusted = Number(baseReserve) / Math.pow(10, baseDecimals);
|
|
689
|
+
return quoteAdjusted / baseAdjusted;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Convert lamports to SOL
|
|
694
|
+
*/
|
|
695
|
+
export function lamportsToSol(lamports: bigint | number): number {
|
|
696
|
+
return Number(lamports) / 1e9;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// ===== Price Calculation Functions - from Rust: src/utils/price/ =====
|
|
700
|
+
|
|
701
|
+
const DEFAULT_TOKEN_DECIMALS = 6;
|
|
702
|
+
const SOL_DECIMALS = 9;
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Calculate the price of token in WSOL
|
|
706
|
+
* 100% from Rust: src/utils/price/bonk.rs price_token_in_wsol
|
|
707
|
+
*/
|
|
708
|
+
export function priceTokenInWsol(
|
|
709
|
+
virtualBase: bigint,
|
|
710
|
+
virtualQuote: bigint,
|
|
711
|
+
realBase: bigint,
|
|
712
|
+
realQuote: bigint
|
|
713
|
+
): number {
|
|
714
|
+
return priceBaseInQuoteWithVirtual(
|
|
715
|
+
virtualBase,
|
|
716
|
+
virtualQuote,
|
|
717
|
+
realBase,
|
|
718
|
+
realQuote,
|
|
719
|
+
DEFAULT_TOKEN_DECIMALS,
|
|
720
|
+
SOL_DECIMALS
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Calculate the price of base in quote with virtual reserves
|
|
726
|
+
* 100% from Rust: src/utils/price/bonk.rs price_base_in_quote
|
|
727
|
+
*/
|
|
728
|
+
export function priceBaseInQuoteWithVirtual(
|
|
729
|
+
virtualBase: bigint,
|
|
730
|
+
virtualQuote: bigint,
|
|
731
|
+
realBase: bigint,
|
|
732
|
+
realQuote: bigint,
|
|
733
|
+
baseDecimals: number,
|
|
734
|
+
quoteDecimals: number
|
|
735
|
+
): number {
|
|
736
|
+
// Calculate decimal places difference
|
|
737
|
+
const decimalDiff = quoteDecimals - baseDecimals;
|
|
738
|
+
const decimalFactor = decimalDiff >= 0
|
|
739
|
+
? Math.pow(10, decimalDiff)
|
|
740
|
+
: 1.0 / Math.pow(10, -decimalDiff);
|
|
741
|
+
|
|
742
|
+
// Calculate reserves state before price calculation
|
|
743
|
+
const quoteReserves = virtualQuote + realQuote;
|
|
744
|
+
const baseReserves = virtualBase > realBase ? virtualBase - realBase : BigInt(0);
|
|
745
|
+
|
|
746
|
+
if (baseReserves === BigInt(0)) {
|
|
747
|
+
return 0.0;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (decimalFactor === 0.0) {
|
|
751
|
+
return 0.0;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Use floating point calculation to avoid precision loss
|
|
755
|
+
const price = (Number(quoteReserves) / Number(baseReserves)) / decimalFactor;
|
|
756
|
+
|
|
757
|
+
return price;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Calculate the token price in quote based on base and quote reserves
|
|
762
|
+
* 100% from Rust: src/utils/price/common.rs price_base_in_quote
|
|
763
|
+
*/
|
|
764
|
+
export function priceBaseInQuoteFromReserves(
|
|
765
|
+
baseReserve: bigint,
|
|
766
|
+
quoteReserve: bigint,
|
|
767
|
+
baseDecimals: number,
|
|
768
|
+
quoteDecimals: number
|
|
769
|
+
): number {
|
|
770
|
+
const base = Number(baseReserve) / Math.pow(10, baseDecimals);
|
|
771
|
+
const quote = Number(quoteReserve) / Math.pow(10, quoteDecimals);
|
|
772
|
+
if (base === 0.0) {
|
|
773
|
+
return 0.0;
|
|
774
|
+
}
|
|
775
|
+
return quote / base;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Calculate the token price in base based on base and quote reserves
|
|
780
|
+
* 100% from Rust: src/utils/price/common.rs price_quote_in_base
|
|
781
|
+
*/
|
|
782
|
+
export function priceQuoteInBase(
|
|
783
|
+
baseReserve: bigint,
|
|
784
|
+
quoteReserve: bigint,
|
|
785
|
+
baseDecimals: number,
|
|
786
|
+
quoteDecimals: number
|
|
787
|
+
): number {
|
|
788
|
+
const base = Number(baseReserve) / Math.pow(10, baseDecimals);
|
|
789
|
+
const quote = Number(quoteReserve) / Math.pow(10, quoteDecimals);
|
|
790
|
+
if (quote === 0.0) {
|
|
791
|
+
return 0.0;
|
|
792
|
+
}
|
|
793
|
+
return base / quote;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// ===== PumpFun Price Calculation =====
|
|
797
|
+
|
|
798
|
+
// Constants from Rust: src/instruction/utils/pumpfun.rs global_constants
|
|
799
|
+
const LAMPORTS_PER_SOL = 1_000_000_000;
|
|
800
|
+
const SCALE = 1_000_000; // 6 decimals for tokens
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Calculate the token price in SOL based on virtual reserves
|
|
804
|
+
* 100% from Rust: src/utils/price/pumpfun.rs price_token_in_sol
|
|
805
|
+
*/
|
|
806
|
+
export function priceTokenInSol(
|
|
807
|
+
virtualSolReserves: bigint,
|
|
808
|
+
virtualTokenReserves: bigint
|
|
809
|
+
): number {
|
|
810
|
+
const vSol = Number(virtualSolReserves) / LAMPORTS_PER_SOL;
|
|
811
|
+
const vTokens = Number(virtualTokenReserves) / SCALE;
|
|
812
|
+
if (vTokens === 0.0) {
|
|
813
|
+
return 0.0;
|
|
814
|
+
}
|
|
815
|
+
return vSol / vTokens;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// ===== Bonk Calculations - from Rust: src/utils/calc/bonk.rs =====
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Calculates the amount of tokens to receive when buying with SOL
|
|
822
|
+
* 100% from Rust: src/utils/calc/bonk.rs get_buy_token_amount_from_sol_amount
|
|
823
|
+
*/
|
|
824
|
+
export function getBonkBuyTokenAmountFromSolAmount(
|
|
825
|
+
amountIn: bigint,
|
|
826
|
+
virtualBase: bigint,
|
|
827
|
+
virtualQuote: bigint,
|
|
828
|
+
realBase: bigint,
|
|
829
|
+
realQuote: bigint,
|
|
830
|
+
slippageBasisPoints: bigint
|
|
831
|
+
): bigint {
|
|
832
|
+
const amountInU128 = amountIn;
|
|
833
|
+
|
|
834
|
+
// Fee rates from Bonk - 100% from Rust: src/instruction/utils/bonk.rs accounts
|
|
835
|
+
const PROTOCOL_FEE_RATE = BigInt(25); // 0.25%
|
|
836
|
+
const PLATFORM_FEE_RATE = BigInt(100); // 1%
|
|
837
|
+
const SHARE_FEE_RATE = BigInt(0); // 0%
|
|
838
|
+
|
|
839
|
+
// Calculate fees
|
|
840
|
+
const protocolFee = (amountInU128 * PROTOCOL_FEE_RATE) / BigInt(10000);
|
|
841
|
+
const platformFee = (amountInU128 * PLATFORM_FEE_RATE) / BigInt(10000);
|
|
842
|
+
const shareFee = (amountInU128 * SHARE_FEE_RATE) / BigInt(10000);
|
|
843
|
+
|
|
844
|
+
// Calculate net input after fees
|
|
845
|
+
const amountInNet = amountInU128 - protocolFee - platformFee - shareFee;
|
|
846
|
+
|
|
847
|
+
// Calculate total reserves
|
|
848
|
+
const inputReserve = virtualQuote + realQuote;
|
|
849
|
+
const outputReserve = virtualBase - realBase;
|
|
850
|
+
|
|
851
|
+
// Apply constant product formula
|
|
852
|
+
const numerator = amountInNet * outputReserve;
|
|
853
|
+
const denominator = inputReserve + amountInNet;
|
|
854
|
+
let amountOut = numerator / denominator;
|
|
855
|
+
|
|
856
|
+
// Apply slippage
|
|
857
|
+
amountOut = amountOut - (amountOut * slippageBasisPoints) / BigInt(10000);
|
|
858
|
+
|
|
859
|
+
return amountOut;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Calculates the amount of SOL to receive when selling tokens
|
|
864
|
+
* 100% from Rust: src/utils/calc/bonk.rs get_sell_sol_amount_from_token_amount
|
|
865
|
+
*/
|
|
866
|
+
export function getBonkSellSolAmountFromTokenAmount(
|
|
867
|
+
amountIn: bigint,
|
|
868
|
+
virtualBase: bigint,
|
|
869
|
+
virtualQuote: bigint,
|
|
870
|
+
realBase: bigint,
|
|
871
|
+
realQuote: bigint,
|
|
872
|
+
slippageBasisPoints: bigint
|
|
873
|
+
): bigint {
|
|
874
|
+
const amountInU128 = amountIn;
|
|
875
|
+
|
|
876
|
+
// For sell, input_reserve is token reserves, output_reserve is SOL reserves
|
|
877
|
+
const inputReserve = virtualBase - realBase;
|
|
878
|
+
const outputReserve = virtualQuote + realQuote;
|
|
879
|
+
|
|
880
|
+
// Use constant product formula
|
|
881
|
+
const numerator = amountInU128 * outputReserve;
|
|
882
|
+
const denominator = inputReserve + amountInU128;
|
|
883
|
+
const solAmountOut = numerator / denominator;
|
|
884
|
+
|
|
885
|
+
// Fee rates from Bonk - 100% from Rust: src/instruction/utils/bonk.rs accounts
|
|
886
|
+
const PROTOCOL_FEE_RATE = BigInt(25); // 0.25%
|
|
887
|
+
const PLATFORM_FEE_RATE = BigInt(100); // 1%
|
|
888
|
+
const SHARE_FEE_RATE = BigInt(0); // 0%
|
|
889
|
+
|
|
890
|
+
// Calculate fees
|
|
891
|
+
const protocolFee = (solAmountOut * PROTOCOL_FEE_RATE) / BigInt(10000);
|
|
892
|
+
const platformFee = (solAmountOut * PLATFORM_FEE_RATE) / BigInt(10000);
|
|
893
|
+
const shareFee = (solAmountOut * SHARE_FEE_RATE) / BigInt(10000);
|
|
894
|
+
|
|
895
|
+
// Net SOL after fees
|
|
896
|
+
const solAmountNet = solAmountOut - protocolFee - platformFee - shareFee;
|
|
897
|
+
|
|
898
|
+
// Apply slippage
|
|
899
|
+
const finalAmount = solAmountNet - (solAmountNet * slippageBasisPoints) / BigInt(10000);
|
|
900
|
+
|
|
901
|
+
return finalAmount;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// ===== Raydium CPMM Calculations - from Rust: src/utils/calc/raydium_cpmm.rs =====
|
|
905
|
+
|
|
906
|
+
export interface RaydiumCpmmComputeSwapParams {
|
|
907
|
+
allTrade: boolean;
|
|
908
|
+
amountIn: bigint;
|
|
909
|
+
amountOut: bigint;
|
|
910
|
+
minAmountOut: bigint;
|
|
911
|
+
fee: bigint;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
export interface RaydiumCpmmSwapResult {
|
|
915
|
+
newInputVaultAmount: bigint;
|
|
916
|
+
newOutputVaultAmount: bigint;
|
|
917
|
+
inputAmount: bigint;
|
|
918
|
+
outputAmount: bigint;
|
|
919
|
+
tradeFee: bigint;
|
|
920
|
+
protocolFee: bigint;
|
|
921
|
+
fundFee: bigint;
|
|
922
|
+
creatorFee: bigint;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// Raydium CPMM fee constants
|
|
926
|
+
const RAYDIUM_CPMM_FEE_RATE_DENOMINATOR = BigInt(1_000_000);
|
|
927
|
+
const RAYDIUM_CPMM_TRADE_FEE_RATE = BigInt(2500);
|
|
928
|
+
const RAYDIUM_CPMM_CREATOR_FEE_RATE = BigInt(0);
|
|
929
|
+
const RAYDIUM_CPMM_PROTOCOL_FEE_RATE = BigInt(120000);
|
|
930
|
+
const RAYDIUM_CPMM_FUND_FEE_RATE = BigInt(40000);
|
|
931
|
+
|
|
932
|
+
function computeRaydiumCpmmTradingFee(amount: bigint, feeRate: bigint): bigint {
|
|
933
|
+
const numerator = amount * feeRate;
|
|
934
|
+
return (numerator + RAYDIUM_CPMM_FEE_RATE_DENOMINATOR - BigInt(1)) / RAYDIUM_CPMM_FEE_RATE_DENOMINATOR;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function computeRaydiumCpmmProtocolFundFee(amount: bigint, feeRate: bigint): bigint {
|
|
938
|
+
const numerator = amount * feeRate;
|
|
939
|
+
return numerator / RAYDIUM_CPMM_FEE_RATE_DENOMINATOR;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Computes swap parameters for Raydium CPMM
|
|
944
|
+
* 100% from Rust: src/utils/calc/raydium_cpmm.rs compute_swap_amount
|
|
945
|
+
*/
|
|
946
|
+
export function computeRaydiumCpmmSwapAmount(
|
|
947
|
+
baseReserve: bigint,
|
|
948
|
+
quoteReserve: bigint,
|
|
949
|
+
isBaseIn: boolean,
|
|
950
|
+
amountIn: bigint,
|
|
951
|
+
slippageBasisPoints: bigint
|
|
952
|
+
): RaydiumCpmmComputeSwapParams {
|
|
953
|
+
const [inputReserve, outputReserve] = isBaseIn
|
|
954
|
+
? [baseReserve, quoteReserve]
|
|
955
|
+
: [quoteReserve, baseReserve];
|
|
956
|
+
|
|
957
|
+
// Calculate swap
|
|
958
|
+
const tradeFee = computeRaydiumCpmmTradingFee(amountIn, RAYDIUM_CPMM_TRADE_FEE_RATE);
|
|
959
|
+
const inputAmountLessFees = amountIn - tradeFee;
|
|
960
|
+
|
|
961
|
+
const protocolFee = computeRaydiumCpmmProtocolFundFee(tradeFee, RAYDIUM_CPMM_PROTOCOL_FEE_RATE);
|
|
962
|
+
const fundFee = computeRaydiumCpmmProtocolFundFee(tradeFee, RAYDIUM_CPMM_FUND_FEE_RATE);
|
|
963
|
+
|
|
964
|
+
// Calculate output
|
|
965
|
+
const outputAmountSwapped = (outputReserve * inputAmountLessFees) / (inputReserve + inputAmountLessFees);
|
|
966
|
+
const outputAmount = outputAmountSwapped; // Creator fee is 0
|
|
967
|
+
|
|
968
|
+
// Calculate min amount out with slippage
|
|
969
|
+
const minAmountOut = outputAmount - (outputAmount * slippageBasisPoints) / BigInt(10000);
|
|
970
|
+
|
|
971
|
+
const allTrade = true;
|
|
972
|
+
|
|
973
|
+
return {
|
|
974
|
+
allTrade,
|
|
975
|
+
amountIn,
|
|
976
|
+
amountOut: outputAmount,
|
|
977
|
+
minAmountOut,
|
|
978
|
+
fee: tradeFee,
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// ===== Raydium AMM V4 Calculations - from Rust: src/utils/calc/raydium_amm_v4.rs =====
|
|
983
|
+
|
|
984
|
+
const RAYDIUM_AMM_V4_SWAP_FEE_NUMERATOR = BigInt(25);
|
|
985
|
+
const RAYDIUM_AMM_V4_SWAP_FEE_DENOMINATOR = BigInt(10000);
|
|
986
|
+
const RAYDIUM_AMM_V4_TRADE_FEE_NUMERATOR = BigInt(25);
|
|
987
|
+
const RAYDIUM_AMM_V4_TRADE_FEE_DENOMINATOR = BigInt(10000);
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* Computes swap parameters for Raydium AMM V4
|
|
991
|
+
* 100% from Rust: src/utils/calc/raydium_amm_v4.rs compute_swap_amount
|
|
992
|
+
*/
|
|
993
|
+
export function computeRaydiumAmmV4SwapAmount(
|
|
994
|
+
baseReserve: bigint,
|
|
995
|
+
quoteReserve: bigint,
|
|
996
|
+
isBaseIn: boolean,
|
|
997
|
+
amountIn: bigint,
|
|
998
|
+
slippageBasisPoints: bigint
|
|
999
|
+
): RaydiumCpmmComputeSwapParams {
|
|
1000
|
+
const [inputReserve, outputReserve] = isBaseIn
|
|
1001
|
+
? [baseReserve, quoteReserve]
|
|
1002
|
+
: [quoteReserve, baseReserve];
|
|
1003
|
+
|
|
1004
|
+
// Calculate trade fee
|
|
1005
|
+
const tradeFeeNumerator = amountIn * RAYDIUM_AMM_V4_TRADE_FEE_NUMERATOR;
|
|
1006
|
+
const tradeFee = (tradeFeeNumerator + RAYDIUM_AMM_V4_TRADE_FEE_DENOMINATOR - BigInt(1)) / RAYDIUM_AMM_V4_TRADE_FEE_DENOMINATOR;
|
|
1007
|
+
|
|
1008
|
+
const inputAmountLessFees = amountIn - tradeFee;
|
|
1009
|
+
|
|
1010
|
+
// Calculate swap fee
|
|
1011
|
+
const swapFeeNumerator = tradeFee * RAYDIUM_AMM_V4_SWAP_FEE_NUMERATOR;
|
|
1012
|
+
const swapFee = swapFeeNumerator / RAYDIUM_AMM_V4_SWAP_FEE_DENOMINATOR;
|
|
1013
|
+
|
|
1014
|
+
// Calculate output
|
|
1015
|
+
const outputAmountSwapped = (outputReserve * inputAmountLessFees) / (inputReserve + inputAmountLessFees);
|
|
1016
|
+
const outputAmount = outputAmountSwapped - swapFee;
|
|
1017
|
+
|
|
1018
|
+
// Calculate min amount out with slippage
|
|
1019
|
+
const minAmountOut = outputAmount - (outputAmount * slippageBasisPoints) / BigInt(10000);
|
|
1020
|
+
|
|
1021
|
+
return {
|
|
1022
|
+
allTrade: true,
|
|
1023
|
+
amountIn,
|
|
1024
|
+
amountOut: outputAmount,
|
|
1025
|
+
minAmountOut,
|
|
1026
|
+
fee: tradeFee,
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// ===== Raydium CLMM Price Calculations - from Rust: src/utils/price/raydium_clmm.rs =====
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* Calculate the price of token0 in token1 from sqrt price
|
|
1034
|
+
* 100% from Rust: src/utils/price/raydium_clmm.rs price_token0_in_token1
|
|
1035
|
+
*/
|
|
1036
|
+
export function priceToken0InToken1(
|
|
1037
|
+
sqrtPriceX64: bigint,
|
|
1038
|
+
decimalsToken0: number,
|
|
1039
|
+
decimalsToken1: number
|
|
1040
|
+
): number {
|
|
1041
|
+
const sqrtPrice = Number(sqrtPriceX64) / Math.pow(2, 64); // Q64.64 to float
|
|
1042
|
+
const priceRaw = sqrtPrice * sqrtPrice; // Price without decimal adjustment
|
|
1043
|
+
const scale = Math.pow(10, decimalsToken0 - decimalsToken1);
|
|
1044
|
+
return priceRaw * scale;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Calculate the price of token1 in token0 from sqrt price
|
|
1049
|
+
* 100% from Rust: src/utils/price/raydium_clmm.rs price_token1_in_token0
|
|
1050
|
+
*/
|
|
1051
|
+
export function priceToken1InToken0(
|
|
1052
|
+
sqrtPriceX64: bigint,
|
|
1053
|
+
decimalsToken0: number,
|
|
1054
|
+
decimalsToken1: number
|
|
1055
|
+
): number {
|
|
1056
|
+
return 1.0 / priceToken0InToken1(sqrtPriceX64, decimalsToken0, decimalsToken1);
|
|
1057
|
+
}
|
|
1058
|
+
|