@stabbleorg/mclmm-sdk 0.1.12 → 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/lib/__tests__/fixtures/pool-states.d.ts +84 -0
- package/lib/__tests__/fixtures/pool-states.d.ts.map +1 -0
- package/lib/__tests__/fixtures/tick-arrays.d.ts +42 -0
- package/lib/__tests__/fixtures/tick-arrays.d.ts.map +1 -0
- package/lib/__tests__/helpers/integration-mocks.d.ts +82 -0
- package/lib/__tests__/helpers/integration-mocks.d.ts.map +1 -0
- package/lib/__tests__/integration/performance.test.d.ts +12 -0
- package/lib/__tests__/integration/performance.test.d.ts.map +1 -0
- package/lib/__tests__/integration/swap-flow.test.d.ts +12 -0
- package/lib/__tests__/integration/swap-flow.test.d.ts.map +1 -0
- package/lib/__tests__/setup.d.ts +19 -0
- package/lib/__tests__/setup.d.ts.map +1 -0
- package/lib/__tests__/unit/math-utils.test.d.ts +11 -0
- package/lib/__tests__/unit/math-utils.test.d.ts.map +1 -0
- package/lib/__tests__/unit/pool-data-manager.test.d.ts +16 -0
- package/lib/__tests__/unit/pool-data-manager.test.d.ts.map +1 -0
- package/lib/__tests__/unit/price-api-client.test.d.ts +2 -0
- package/lib/__tests__/unit/price-api-client.test.d.ts.map +1 -0
- package/lib/__tests__/unit/swap-manager.test.d.ts +14 -0
- package/lib/__tests__/unit/swap-manager.test.d.ts.map +1 -0
- package/lib/__tests__/unit/swap-math-engine.test.d.ts +14 -0
- package/lib/__tests__/unit/swap-math-engine.test.d.ts.map +1 -0
- package/lib/api/config.d.ts +6 -0
- package/lib/api/config.d.ts.map +1 -1
- package/lib/client.d.ts +2 -0
- package/lib/client.d.ts.map +1 -1
- package/lib/constants.d.ts +41 -1
- package/lib/constants.d.ts.map +1 -1
- package/lib/generated/accounts/ammConfig.d.ts +1 -1
- package/lib/generated/accounts/ammConfig.d.ts.map +1 -1
- package/lib/generated/accounts/observationState.d.ts +1 -1
- package/lib/generated/accounts/observationState.d.ts.map +1 -1
- package/lib/generated/accounts/operationState.d.ts +1 -1
- package/lib/generated/accounts/operationState.d.ts.map +1 -1
- package/lib/generated/accounts/personalPositionState.d.ts +1 -1
- package/lib/generated/accounts/personalPositionState.d.ts.map +1 -1
- package/lib/generated/accounts/poolState.d.ts +1 -1
- package/lib/generated/accounts/poolState.d.ts.map +1 -1
- package/lib/generated/accounts/protocolPositionState.d.ts +1 -1
- package/lib/generated/accounts/protocolPositionState.d.ts.map +1 -1
- package/lib/generated/accounts/supportMintAssociated.d.ts +1 -1
- package/lib/generated/accounts/supportMintAssociated.d.ts.map +1 -1
- package/lib/generated/accounts/tickArrayBitmapExtension.d.ts +1 -1
- package/lib/generated/accounts/tickArrayBitmapExtension.d.ts.map +1 -1
- package/lib/generated/accounts/tickArrayState.d.ts +1 -1
- package/lib/generated/accounts/tickArrayState.d.ts.map +1 -1
- package/lib/generated/instructions/closePosition.d.ts +1 -1
- package/lib/generated/instructions/closePosition.d.ts.map +1 -1
- package/lib/generated/instructions/closeProtocolPosition.d.ts +1 -1
- package/lib/generated/instructions/closeProtocolPosition.d.ts.map +1 -1
- package/lib/generated/instructions/collectFundFee.d.ts +1 -1
- package/lib/generated/instructions/collectFundFee.d.ts.map +1 -1
- package/lib/generated/instructions/collectProtocolFee.d.ts +1 -1
- package/lib/generated/instructions/collectProtocolFee.d.ts.map +1 -1
- package/lib/generated/instructions/collectRemainingRewards.d.ts +1 -1
- package/lib/generated/instructions/collectRemainingRewards.d.ts.map +1 -1
- package/lib/generated/instructions/createAmmConfig.d.ts +1 -1
- package/lib/generated/instructions/createAmmConfig.d.ts.map +1 -1
- package/lib/generated/instructions/createOperationAccount.d.ts +1 -1
- package/lib/generated/instructions/createOperationAccount.d.ts.map +1 -1
- package/lib/generated/instructions/createPool.d.ts +1 -1
- package/lib/generated/instructions/createPool.d.ts.map +1 -1
- package/lib/generated/instructions/createSupportMintAssociated.d.ts +1 -1
- package/lib/generated/instructions/createSupportMintAssociated.d.ts.map +1 -1
- package/lib/generated/instructions/decreaseLiquidityV2.d.ts +1 -1
- package/lib/generated/instructions/decreaseLiquidityV2.d.ts.map +1 -1
- package/lib/generated/instructions/increaseLiquidityV2.d.ts +1 -1
- package/lib/generated/instructions/increaseLiquidityV2.d.ts.map +1 -1
- package/lib/generated/instructions/initializeReward.d.ts +1 -1
- package/lib/generated/instructions/initializeReward.d.ts.map +1 -1
- package/lib/generated/instructions/openPositionWithToken22Nft.d.ts +1 -1
- package/lib/generated/instructions/openPositionWithToken22Nft.d.ts.map +1 -1
- package/lib/generated/instructions/setRewardParams.d.ts +1 -1
- package/lib/generated/instructions/setRewardParams.d.ts.map +1 -1
- package/lib/generated/instructions/swapRouterBaseIn.d.ts +1 -1
- package/lib/generated/instructions/swapRouterBaseIn.d.ts.map +1 -1
- package/lib/generated/instructions/swapV2.d.ts +1 -1
- package/lib/generated/instructions/swapV2.d.ts.map +1 -1
- package/lib/generated/instructions/transferRewardOwner.d.ts +1 -1
- package/lib/generated/instructions/transferRewardOwner.d.ts.map +1 -1
- package/lib/generated/instructions/updateAmmConfig.d.ts +1 -1
- package/lib/generated/instructions/updateAmmConfig.d.ts.map +1 -1
- package/lib/generated/instructions/updateOperationAccount.d.ts +1 -1
- package/lib/generated/instructions/updateOperationAccount.d.ts.map +1 -1
- package/lib/generated/instructions/updatePoolStatus.d.ts +1 -1
- package/lib/generated/instructions/updatePoolStatus.d.ts.map +1 -1
- package/lib/generated/instructions/updateRewardInfos.d.ts +1 -1
- package/lib/generated/instructions/updateRewardInfos.d.ts.map +1 -1
- package/lib/generated/programs/ammV3.d.ts.map +1 -1
- package/lib/generated/shared/index.d.ts.map +1 -1
- package/lib/generated/types/closePositionEvent.d.ts +16 -0
- package/lib/generated/types/closePositionEvent.d.ts.map +1 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4555 -1835
- package/lib/index.mjs +4548 -1834
- package/lib/managers/index.d.ts +7 -0
- package/lib/managers/index.d.ts.map +1 -0
- package/lib/managers/pool-data-manager.d.ts +132 -0
- package/lib/managers/pool-data-manager.d.ts.map +1 -0
- package/lib/managers/price-api-client.d.ts +295 -0
- package/lib/managers/price-api-client.d.ts.map +1 -0
- package/lib/position-manager.d.ts.map +1 -1
- package/lib/swap.d.ts +832 -2
- package/lib/swap.d.ts.map +1 -1
- package/lib/types.d.ts +23 -4
- package/lib/types.d.ts.map +1 -1
- package/lib/utils/index.d.ts +5 -2
- package/lib/utils/index.d.ts.map +1 -1
- package/lib/utils/math.d.ts +386 -0
- package/lib/utils/math.d.ts.map +1 -1
- package/lib/utils/tickQuery.d.ts +62 -1
- package/lib/utils/tickQuery.d.ts.map +1 -1
- package/package.json +13 -5
package/lib/swap.d.ts
CHANGED
|
@@ -1,6 +1,836 @@
|
|
|
1
|
+
import type { Account, Address, Instruction, TransactionSigner } from "@solana/kit";
|
|
2
|
+
import { type PoolState, type TickArrayState, type AmmConfig } from "./generated";
|
|
3
|
+
import { ClmmSdkConfig, SwapParams, SwapQuote } from "./types";
|
|
4
|
+
import { type PriceApiConfig } from "./managers";
|
|
5
|
+
import BN from "bn.js";
|
|
6
|
+
import Decimal from "decimal.js";
|
|
7
|
+
/** Swap Math Engine class for performing mathematical operations related to swaps
|
|
8
|
+
*
|
|
9
|
+
* Why this exists as a separate class: Separates pure mathematical calculations from
|
|
10
|
+
* orchestration logic. This allows the math to be:
|
|
11
|
+
* 1. Tested in isolation without mocking RPC calls
|
|
12
|
+
* 2. Reused across different swap implementations
|
|
13
|
+
* 3. Easily audited for correctness without business logic noise
|
|
14
|
+
*/
|
|
15
|
+
export interface DetailedSwapQuote extends SwapQuote {
|
|
16
|
+
crossedTicks?: number;
|
|
17
|
+
endPrice?: Decimal;
|
|
18
|
+
priceImpactBreakdown?: {
|
|
19
|
+
fees: number;
|
|
20
|
+
slippage: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export interface SwapCalculationParams {
|
|
24
|
+
pool: PoolState;
|
|
25
|
+
ammConfig: AmmConfig;
|
|
26
|
+
amountIn: BN;
|
|
27
|
+
zeroForOne: boolean;
|
|
28
|
+
slippageTolerance: number;
|
|
29
|
+
poolAddress: Address;
|
|
30
|
+
}
|
|
31
|
+
export interface AccurateSwapParams extends SwapCalculationParams {
|
|
32
|
+
tickArrayCache: {
|
|
33
|
+
[key: string]: Account<TickArrayState>;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export declare class SwapMathEngine {
|
|
37
|
+
/**
|
|
38
|
+
* Simple swap calculation using current pool state only
|
|
39
|
+
*
|
|
40
|
+
* Why "simple": This method trades accuracy for speed. Instead of fetching and
|
|
41
|
+
* traversing potentially dozens of tick arrays from RPC, it assumes liquidity
|
|
42
|
+
* is concentrated at the current price. This is accurate for small swaps where
|
|
43
|
+
* the price doesn't move much, but becomes less accurate for large swaps that
|
|
44
|
+
* would cross multiple ticks.
|
|
45
|
+
*
|
|
46
|
+
* When to use: For UI quote displays, small swaps (<1% of liquidity), or when
|
|
47
|
+
* you need sub-second response times. The caching layer makes this very fast
|
|
48
|
+
* for repeated quotes.
|
|
49
|
+
*
|
|
50
|
+
* When NOT to use: For building actual swap instructions. Use calculateAccurateSwap
|
|
51
|
+
* instead to ensure the on-chain execution matches your quote.
|
|
52
|
+
*/
|
|
53
|
+
calculateSimpleSwap(params: SwapCalculationParams): Promise<SwapQuote>;
|
|
54
|
+
/**
|
|
55
|
+
* Calculate swap using full tick traversal
|
|
56
|
+
*
|
|
57
|
+
* Why this is necessary: In CLMM (Uniswap V3 style) pools, liquidity is not
|
|
58
|
+
* uniformly distributed. It's concentrated in specific price ranges (ticks).
|
|
59
|
+
* A large swap may cross multiple ticks, each with different liquidity depths.
|
|
60
|
+
*
|
|
61
|
+
* To get an accurate quote, we must:
|
|
62
|
+
* 1. Fetch all tick arrays the swap might touch
|
|
63
|
+
* 2. Simulate walking through each tick, consuming liquidity
|
|
64
|
+
* 3. Track price impact as we cross tick boundaries
|
|
65
|
+
*
|
|
66
|
+
* Why not always use this: It requires multiple RPC calls to fetch tick arrays,
|
|
67
|
+
* making it slower and more expensive than simple calculation. Use it when
|
|
68
|
+
* accuracy matters more than speed (e.g., before executing large swaps).
|
|
69
|
+
*
|
|
70
|
+
* Trade-off: 3-5 RPC calls for accuracy vs. 2 RPC calls for speed.
|
|
71
|
+
*/
|
|
72
|
+
calculateAccurateSwap(params: AccurateSwapParams): Promise<DetailedSwapQuote>;
|
|
73
|
+
/**
|
|
74
|
+
* Calculates the square root price limit for a swap based on slippage tolerance
|
|
75
|
+
*
|
|
76
|
+
* Why sqrt price: CLMM pools store price as sqrt(price) * 2^64 for mathematical
|
|
77
|
+
* efficiency. Computing swaps in sqrt space avoids expensive square root operations.
|
|
78
|
+
*
|
|
79
|
+
* Why we need limits: On-chain swaps need a price boundary to prevent front-running.
|
|
80
|
+
* If a swap would move the price beyond this limit, it reverts, protecting users
|
|
81
|
+
* from MEV attacks and unexpected price movement.
|
|
82
|
+
*
|
|
83
|
+
* Why apply slippage here: Rather than using arbitrary limits (like ±1%), we use
|
|
84
|
+
* the user's actual slippage tolerance. This respects their risk preference while
|
|
85
|
+
* providing protection.
|
|
86
|
+
*
|
|
87
|
+
* Why the min/max clamping: Solana programs panic on overflow. We must ensure the
|
|
88
|
+
* calculated limit stays within valid price bounds to prevent transaction failures.
|
|
89
|
+
*/
|
|
90
|
+
calculateSqrtPriceLimit(sqrtPriceX64: BN, zeroForOne: boolean, slippageTolerance: number): BN;
|
|
91
|
+
}
|
|
1
92
|
/**
|
|
93
|
+
* Swap Manager implementation
|
|
94
|
+
*
|
|
95
|
+
* Why this wrapper class: SwapQuoteResult provides a clean interface over the raw
|
|
96
|
+
* quote data. Instead of forcing users to manually convert between base units and
|
|
97
|
+
* human-readable decimals, or calculate price impact percentages, we provide getters
|
|
98
|
+
* that handle these conversions automatically.
|
|
99
|
+
*
|
|
100
|
+
* Why getters instead of upfront calculation: Lazy evaluation. Not every consumer
|
|
101
|
+
* needs every metric (some just need amountOut, others need full impact analysis).
|
|
102
|
+
* Getters let consumers pay only for what they use.
|
|
103
|
+
*/
|
|
104
|
+
export declare class SwapQuoteResult {
|
|
105
|
+
readonly quote: SwapQuote;
|
|
106
|
+
private readonly decIn;
|
|
107
|
+
private readonly decOut;
|
|
108
|
+
private readonly zeroForOne;
|
|
109
|
+
constructor(quote: SwapQuote, decIn: number, decOut: number, zeroForOne: boolean);
|
|
110
|
+
/**
|
|
111
|
+
* Gets the execution price (output per input)
|
|
112
|
+
*
|
|
113
|
+
* Why this conversion: Token amounts are stored in base units (e.g., lamports for SOL),
|
|
114
|
+
* but users think in decimal units (SOL, USDC). We convert both sides to human-readable
|
|
115
|
+
* before calculating the rate to avoid precision loss from integer division.
|
|
116
|
+
*/
|
|
117
|
+
get executionPrice(): Decimal;
|
|
118
|
+
priceUnitsLabel(inputSymbol: string, outputSymbol: string): string;
|
|
119
|
+
get isZeroForOne(): boolean;
|
|
120
|
+
get priceImpactPercent(): number;
|
|
121
|
+
get minOutput(): BN;
|
|
122
|
+
get totalFee(): BN;
|
|
123
|
+
get feePercent(): number;
|
|
124
|
+
get hasAcceptableImpact(): boolean;
|
|
125
|
+
get hasHighImpact(): boolean;
|
|
126
|
+
get hasCriticalImpact(): boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Estimates compute units needed for the swap transaction
|
|
129
|
+
*
|
|
130
|
+
* Why we estimate this: Solana transactions have compute budgets. Underestimating
|
|
131
|
+
* causes transaction failures. Overestimating wastes priority fees. We provide a
|
|
132
|
+
* reasonable estimate based on swap complexity.
|
|
133
|
+
*
|
|
134
|
+
* Why 50k base + 20k per hop: Empirically derived from on-chain program measurements.
|
|
135
|
+
* Simple swaps use ~50k CU. Multi-hop routing adds ~20k per additional pool.
|
|
136
|
+
*
|
|
137
|
+
* Why this matters: Users can use this to calculate priority fees before sending
|
|
138
|
+
* transactions, improving UX and reducing failed transactions.
|
|
139
|
+
*/
|
|
140
|
+
get estimatedComputeUnits(): number;
|
|
141
|
+
toJSON(): SwapQuote;
|
|
142
|
+
}
|
|
143
|
+
export interface SwapSimulation {
|
|
144
|
+
quote: SwapQuote;
|
|
145
|
+
willSucceed: boolean;
|
|
146
|
+
errors: string[];
|
|
147
|
+
warnings: string[];
|
|
148
|
+
}
|
|
149
|
+
export interface DetailedPriceImpact {
|
|
150
|
+
totalImpact: number;
|
|
151
|
+
breakdown: {
|
|
152
|
+
fees: number;
|
|
153
|
+
slippage: number;
|
|
154
|
+
crossedTicks: number;
|
|
155
|
+
};
|
|
156
|
+
comparison: {
|
|
157
|
+
marketPrice: Decimal;
|
|
158
|
+
executionPrice: Decimal;
|
|
159
|
+
difference: number;
|
|
160
|
+
};
|
|
161
|
+
priceSnapshots: {
|
|
162
|
+
priceBefore: Decimal;
|
|
163
|
+
priceAfter: Decimal;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export interface SwapManagerConfig {
|
|
167
|
+
/**
|
|
168
|
+
* Price API configuration
|
|
169
|
+
*
|
|
170
|
+
* Configuration for REST API price fetching from team's infrastructure.
|
|
171
|
+
* This avoids exposing RPC URIs and handles rate limiting centrally.
|
|
172
|
+
*
|
|
173
|
+
* Configuration:
|
|
174
|
+
* - baseUrl
|
|
175
|
+
* - timeout: Optional request timeout (default: 5000ms)
|
|
176
|
+
*
|
|
177
|
+
* When enabled, provides:
|
|
178
|
+
* - Pre-swap price validation against market prices
|
|
179
|
+
* - Price staleness detection
|
|
180
|
+
* - Batch price fetching for multi-pool operations
|
|
181
|
+
* - Automatic retry with concurrency control
|
|
182
|
+
*/
|
|
183
|
+
priceApiConfig?: PriceApiConfig;
|
|
184
|
+
/**
|
|
185
|
+
* Enable market price validation before swaps (requires priceApiConfig)
|
|
186
|
+
*
|
|
187
|
+
* When enabled, SwapManager will fetch current market prices from the REST API
|
|
188
|
+
* and compare them against on-chain pool state. Useful for:
|
|
189
|
+
* - Detecting stale pool data
|
|
190
|
+
* - Identifying potential MEV opportunities
|
|
191
|
+
* - Validating quote accuracy
|
|
192
|
+
*
|
|
193
|
+
* Default: false (to maintain backward compatibility)
|
|
194
|
+
*/
|
|
195
|
+
enablePriceValidation?: boolean;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* SwapManager - Orchestrates swap operations for CLMM pools
|
|
199
|
+
*
|
|
200
|
+
* Why this architecture: We separate concerns into layers:
|
|
201
|
+
* 1. SwapMathEngine: Pure calculations (testable, auditable)
|
|
202
|
+
* 2. PoolDataManager: Data fetching and caching (optimizes RPC calls)
|
|
203
|
+
* 3. PriceApiClient: REST API price fetching (team's infrastructure)
|
|
204
|
+
* 4. SwapManager: Orchestration (combines everything into simple APIs)
|
|
205
|
+
*
|
|
206
|
+
* This separation allows:
|
|
207
|
+
* - Testing math independently of RPC infrastructure
|
|
208
|
+
* - Reusing pool data across multiple quotes
|
|
209
|
+
* - Optional price validation via REST API
|
|
210
|
+
* - Swapping out implementations without changing interfaces
|
|
211
|
+
*
|
|
212
|
+
* Why caching is critical: RPC calls are slow (100-500ms) and rate-limited.
|
|
213
|
+
* Without caching, getting a quote for UI display would block for half a second.
|
|
214
|
+
* With caching, subsequent quotes for the same pool are instant (< 1ms).
|
|
215
|
+
*
|
|
216
|
+
* Why 2-second cache TTL: Per team spec, this balances freshness with RPC efficiency
|
|
217
|
+
* for low-traffic applications similar to app.stabble.org. More aggressive than
|
|
218
|
+
* traditional 5-10s caching but appropriate for current expected usage patterns.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```typescript
|
|
222
|
+
* // Basic usage (no price API)
|
|
223
|
+
* const swapManager = new SwapManager(config);
|
|
2
224
|
*
|
|
3
|
-
*
|
|
225
|
+
* // With REST API for price validation
|
|
226
|
+
* const swapManager = new SwapManager(config, {
|
|
227
|
+
* priceApiConfig: {
|
|
228
|
+
* baseUrl: 'https://mclmm-api.stabble.org', // or dev URL
|
|
229
|
+
* timeout: 5000
|
|
230
|
+
* },
|
|
231
|
+
* enablePriceValidation: true
|
|
232
|
+
* });
|
|
4
233
|
*
|
|
5
|
-
|
|
234
|
+
* const quote = await swapManager.getSwapQuote(poolAddress, {
|
|
235
|
+
* tokenIn: tokenAAddress,
|
|
236
|
+
* tokenOut: tokenBAddress,
|
|
237
|
+
* amountIn: new BN(1000000),
|
|
238
|
+
* slippageTolerance: 0.01
|
|
239
|
+
* });
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
export declare class SwapManager {
|
|
243
|
+
private readonly config;
|
|
244
|
+
private readonly managerConfig?;
|
|
245
|
+
private readonly poolDataManager;
|
|
246
|
+
private readonly mathEngine;
|
|
247
|
+
private readonly priceApiClient?;
|
|
248
|
+
private readonly quoteCache;
|
|
249
|
+
private readonly quoteCacheTTL;
|
|
250
|
+
private readonly maxCacheSize;
|
|
251
|
+
/**
|
|
252
|
+
* Creates a new SwapManager instance
|
|
253
|
+
*
|
|
254
|
+
* @param config - SDK configuration with RPC client and logging
|
|
255
|
+
* @param managerConfig - Optional swap manager configuration
|
|
256
|
+
* @param managerConfig.priceApiConfig - REST API config for price validation
|
|
257
|
+
* @param managerConfig.enablePriceValidation - Enable price validation before swaps
|
|
258
|
+
*/
|
|
259
|
+
constructor(config: ClmmSdkConfig, managerConfig?: SwapManagerConfig | undefined);
|
|
260
|
+
private log;
|
|
261
|
+
/**
|
|
262
|
+
* Clears the quote cache
|
|
263
|
+
*
|
|
264
|
+
* Why you'd call this: After major market events (e.g., oracle updates, large trades),
|
|
265
|
+
* cached quotes may no longer reflect reality. Manual clearing lets you force fresh
|
|
266
|
+
* calculations without waiting for TTL expiry.
|
|
267
|
+
*
|
|
268
|
+
* Why not clear automatically more often: Caching exists because RPC calls are slow.
|
|
269
|
+
* Over-clearing defeats the purpose and makes the UI feel sluggish.
|
|
270
|
+
*/
|
|
271
|
+
clearQuoteCache(): void;
|
|
272
|
+
/**
|
|
273
|
+
* Clears all caches (quotes and pool data)
|
|
274
|
+
*
|
|
275
|
+
* Removes all cached data including swap quotes and pool states.
|
|
276
|
+
* Use this for a complete cache reset.
|
|
277
|
+
*/
|
|
278
|
+
clearAllCaches(): void;
|
|
279
|
+
/**
|
|
280
|
+
* Get comprehensive cache metrics for observability
|
|
281
|
+
*
|
|
282
|
+
* Provides detailed metrics about cache performance including:
|
|
283
|
+
* - Hit/miss rates for pool and config data
|
|
284
|
+
* - Current cache sizes
|
|
285
|
+
* - In-flight request counts
|
|
286
|
+
* - Error cache statistics
|
|
287
|
+
*
|
|
288
|
+
* Use this to monitor cache effectiveness and diagnose performance issues.
|
|
289
|
+
*
|
|
290
|
+
* @returns Detailed cache metrics
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```typescript
|
|
294
|
+
* const metrics = swapManager.getCacheMetrics();
|
|
295
|
+
*
|
|
296
|
+
* console.log('Pool cache hit rate:', metrics.poolData.pool.hitRate);
|
|
297
|
+
* console.log('Config cache hit rate:', metrics.poolData.config.hitRate);
|
|
298
|
+
* console.log('Quote cache size:', metrics.quoteCache.size);
|
|
299
|
+
* console.log('In-flight requests:', metrics.poolData.pool.inFlight);
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
302
|
+
getCacheMetrics(): {
|
|
303
|
+
poolData: {
|
|
304
|
+
pool: {
|
|
305
|
+
hits: number;
|
|
306
|
+
misses: number;
|
|
307
|
+
errors: number;
|
|
308
|
+
hitRate: number;
|
|
309
|
+
cacheSize: number;
|
|
310
|
+
inFlight: number;
|
|
311
|
+
errorCacheSize: number;
|
|
312
|
+
};
|
|
313
|
+
config: {
|
|
314
|
+
hits: number;
|
|
315
|
+
misses: number;
|
|
316
|
+
errors: number;
|
|
317
|
+
hitRate: number;
|
|
318
|
+
cacheSize: number;
|
|
319
|
+
inFlight: number;
|
|
320
|
+
errorCacheSize: number;
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
quoteCache: {
|
|
324
|
+
size: number;
|
|
325
|
+
maxSize: number;
|
|
326
|
+
ttl: number;
|
|
327
|
+
};
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Reset all cache metrics to zero
|
|
331
|
+
*
|
|
332
|
+
* Clears metric counters without clearing cached data.
|
|
333
|
+
* Useful for starting fresh metric collection after configuration changes
|
|
334
|
+
* or for periodic metric reporting.
|
|
335
|
+
*/
|
|
336
|
+
resetCacheMetrics(): void;
|
|
337
|
+
getAtaAddresses(owner: Address, mints: Address[]): Promise<Address[]>;
|
|
338
|
+
/**
|
|
339
|
+
* Estimates compute units and calculates priority fees for a swap transaction
|
|
340
|
+
*
|
|
341
|
+
* Why this exists: Solana's fee market is complex. Users need to:
|
|
342
|
+
* 1. Set compute budget (too low = failure, too high = wasted CU)
|
|
343
|
+
* 2. Set priority fee (too low = slow confirmation, too high = wasted SOL)
|
|
344
|
+
*
|
|
345
|
+
* Why three priority levels: Different users have different urgency needs.
|
|
346
|
+
* - Low: Batch operations where speed doesn't matter (100 microLamports/CU)
|
|
347
|
+
* - Medium: Normal trades where reasonable speed is expected (1,000 microLamports/CU)
|
|
348
|
+
* - High: Time-sensitive arbitrage or MEV protection (10,000 microLamports/CU)
|
|
349
|
+
*
|
|
350
|
+
* Why these specific microLamport values: Derived from observing Solana fee markets.
|
|
351
|
+
* These values provide reasonable service levels without overpaying.
|
|
352
|
+
*
|
|
353
|
+
* Priority fee calculation: `totalFee = (computeUnits × microLamportsPerCU) / 1,000,000`
|
|
354
|
+
* This converts from microLamports (1/1,000,000 of a lamport) to lamports.
|
|
355
|
+
*
|
|
356
|
+
* @param quote - Swap quote result containing estimated compute units
|
|
357
|
+
* @param priorityLevel - Priority level for transaction confirmation (default: "medium")
|
|
358
|
+
* - "low": 100 microLamports/CU (~0.05-5 lamports for typical swaps)
|
|
359
|
+
* - "medium": 1,000 microLamports/CU (~0.5-50 lamports for typical swaps)
|
|
360
|
+
* - "high": 10,000 microLamports/CU (~5-500 lamports for typical swaps)
|
|
361
|
+
*
|
|
362
|
+
* @returns Object containing:
|
|
363
|
+
* - computeUnits: Estimated compute units needed (50k base + 20k per hop)
|
|
364
|
+
* - microLamportsPerCU: Price per compute unit in microLamports
|
|
365
|
+
* - totalPriorityFeeLamports: Total priority fee in lamports
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* ```typescript
|
|
369
|
+
* const quote = await swapManager.getSwapQuote(poolAddress, {
|
|
370
|
+
* tokenIn: usdcAddress,
|
|
371
|
+
* tokenOut: solAddress,
|
|
372
|
+
* amountIn: new BN(1_000_000),
|
|
373
|
+
* });
|
|
374
|
+
*
|
|
375
|
+
* // Estimate with medium priority (default)
|
|
376
|
+
* const budget = swapManager.estimateComputeBudget(quote);
|
|
377
|
+
* console.log(`Compute units: ${budget.computeUnits}`);
|
|
378
|
+
* console.log(`Priority fee: ${budget.totalPriorityFeeLamports} lamports`);
|
|
379
|
+
*
|
|
380
|
+
* // High priority for time-sensitive trades
|
|
381
|
+
* const urgentBudget = swapManager.estimateComputeBudget(quote, "high");
|
|
382
|
+
*
|
|
383
|
+
* // Use in transaction
|
|
384
|
+
* import { ComputeBudgetProgram } from "@solana/web3.js";
|
|
385
|
+
*
|
|
386
|
+
* const tx = new Transaction()
|
|
387
|
+
* .add(
|
|
388
|
+
* ComputeBudgetProgram.setComputeUnitLimit({
|
|
389
|
+
* units: budget.computeUnits
|
|
390
|
+
* })
|
|
391
|
+
* )
|
|
392
|
+
* .add(
|
|
393
|
+
* ComputeBudgetProgram.setComputeUnitPrice({
|
|
394
|
+
* microLamports: budget.microLamportsPerCU
|
|
395
|
+
* })
|
|
396
|
+
* )
|
|
397
|
+
* .add(swapInstruction);
|
|
398
|
+
* ```
|
|
399
|
+
*/
|
|
400
|
+
estimateComputeBudget(quote: SwapQuoteResult, priorityLevel?: "low" | "medium" | "high"): {
|
|
401
|
+
computeUnits: number;
|
|
402
|
+
microLamportsPerCU: number;
|
|
403
|
+
totalPriorityFeeLamports: number;
|
|
404
|
+
};
|
|
405
|
+
/**
|
|
406
|
+
* Determines if a cached quote is still valid
|
|
407
|
+
*
|
|
408
|
+
* Uses time-based TTL (2 seconds per team spec). This balances freshness
|
|
409
|
+
* (quotes aren't too stale) with performance (enough caching to avoid RPC spam)
|
|
410
|
+
* for low-traffic applications.
|
|
411
|
+
*
|
|
412
|
+
* Why 2 seconds: Team spec recommends 2-second polling/caching to match
|
|
413
|
+
* app.stabble.org traffic patterns. More aggressive than traditional 5-10s
|
|
414
|
+
* but appropriate for current expected usage.
|
|
415
|
+
*/
|
|
416
|
+
private isCacheValid;
|
|
417
|
+
/**
|
|
418
|
+
* Evicts oldest entry when cache is full (LRU eviction)
|
|
419
|
+
*
|
|
420
|
+
* Why LRU (Least Recently Used): Simple and effective. The oldest entries are
|
|
421
|
+
* least likely to be requested again. More sophisticated strategies (LFU, ARC)
|
|
422
|
+
* add complexity for minimal benefit in this use case.
|
|
423
|
+
*
|
|
424
|
+
* Why 1000 max size: Balances memory usage with hit rate. Each entry is ~500 bytes,
|
|
425
|
+
* so 1000 entries = ~500KB. This is negligible memory-wise but large enough to
|
|
426
|
+
* cover most active pools in a session.
|
|
427
|
+
*
|
|
428
|
+
* Why we need a cap at all: Without limits, cache could grow unbounded if someone
|
|
429
|
+
* queries many different pools or amounts. This would cause memory leaks in
|
|
430
|
+
* long-running applications.
|
|
431
|
+
*/
|
|
432
|
+
private evictOldestCacheEntry;
|
|
433
|
+
/**
|
|
434
|
+
* Gets a swap quote using simple calculation (no tick traversal)
|
|
435
|
+
*
|
|
436
|
+
* Why this is the default: For 90% of swaps (small amounts, liquid pools), simple
|
|
437
|
+
* calculation is accurate enough and 3-5x faster than accurate calculation. Users
|
|
438
|
+
* get instant feedback in UIs without sacrificing meaningful accuracy.
|
|
439
|
+
*
|
|
440
|
+
* Why caching matters here: UI components often request quotes repeatedly as users
|
|
441
|
+
* type amounts. Without caching, each keystroke would trigger RPC calls. With
|
|
442
|
+
* caching, only the first request hits RPC; subsequent requests are instant.
|
|
443
|
+
*
|
|
444
|
+
* Cache behavior:
|
|
445
|
+
* - Quotes are cached for 5 seconds by default (balances freshness with performance)
|
|
446
|
+
* - If price streaming is enabled, cache is invalidated immediately on price changes
|
|
447
|
+
* - Cache uses LRU eviction with a max size of 1000 entries (prevents memory leaks)
|
|
448
|
+
*
|
|
449
|
+
* @param poolAddress - Address of the CLMM pool
|
|
450
|
+
* @param params - Swap parameters including tokens and amount
|
|
451
|
+
* @param params.tokenIn - Address of the input token
|
|
452
|
+
* @param params.tokenOut - Address of the output token
|
|
453
|
+
* @param params.amountIn - Amount of input token to swap (in base units)
|
|
454
|
+
* @param params.slippageTolerance - Optional slippage tolerance (0-1, default: 0.01 = 1%)
|
|
455
|
+
* @param params.poolState - Optional: Pre-fetched pool state to avoid RPC call (optimization for frontends with API data)
|
|
456
|
+
* @param params.ammConfig - Optional: Pre-fetched AMM config to avoid RPC call
|
|
457
|
+
* @param options - Optional fetch configuration
|
|
458
|
+
* @param options.signal - AbortSignal to cancel the request if needed
|
|
459
|
+
* @param options.allowStale - Return stale data immediately and refresh in background
|
|
460
|
+
*
|
|
461
|
+
* @returns Swap quote result with execution price, impact, fees, and minimum output
|
|
462
|
+
*
|
|
463
|
+
* @throws {ClmmError} PRICE_SLIPPAGE if slippage tolerance is invalid
|
|
464
|
+
* @throws {ClmmError} SWAP_AMOUNT_CANNOT_BE_ZERO if amount is <= 0
|
|
465
|
+
* @throws {ClmmError} POOL_NOT_FOUND if pool doesn't contain the token pair
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* ```typescript
|
|
469
|
+
* // Basic usage (SDK fetches pool state via RPC)
|
|
470
|
+
* const quote = await swapManager.getSwapQuote(poolAddress, {
|
|
471
|
+
* tokenIn: usdcAddress,
|
|
472
|
+
* tokenOut: solAddress,
|
|
473
|
+
* amountIn: new BN(1_000_000), // 1 USDC (6 decimals)
|
|
474
|
+
* slippageTolerance: 0.005 // 0.5%
|
|
475
|
+
* });
|
|
476
|
+
*
|
|
477
|
+
* // Optimized for frontend with API data (avoids redundant RPC calls)
|
|
478
|
+
* const poolData = await fetch('/api/pools/' + poolAddress).then(r => r.json());
|
|
479
|
+
* const quote = await swapManager.getSwapQuote(poolAddress, {
|
|
480
|
+
* tokenIn: usdcAddress,
|
|
481
|
+
* tokenOut: solAddress,
|
|
482
|
+
* amountIn: new BN(1_000_000),
|
|
483
|
+
* poolState: poolData.state, // Pass pre-fetched data
|
|
484
|
+
* ammConfig: poolData.ammConfig
|
|
485
|
+
* });
|
|
486
|
+
*
|
|
487
|
+
* // With abort signal for cancellable requests
|
|
488
|
+
* const controller = new AbortController();
|
|
489
|
+
* const quote = await swapManager.getSwapQuote(poolAddress, {
|
|
490
|
+
* tokenIn: usdcAddress,
|
|
491
|
+
* tokenOut: solAddress,
|
|
492
|
+
* amountIn: new BN(1_000_000),
|
|
493
|
+
* }, {
|
|
494
|
+
* signal: controller.signal
|
|
495
|
+
* });
|
|
496
|
+
*
|
|
497
|
+
* // With stale-while-revalidate for better UX
|
|
498
|
+
* const quote = await swapManager.getSwapQuote(poolAddress, {
|
|
499
|
+
* tokenIn: usdcAddress,
|
|
500
|
+
* tokenOut: solAddress,
|
|
501
|
+
* amountIn: new BN(1_000_000),
|
|
502
|
+
* }, {
|
|
503
|
+
* allowStale: true // Returns immediately, refreshes in background
|
|
504
|
+
* });
|
|
505
|
+
*
|
|
506
|
+
* console.log(`Price: ${quote.executionPrice}`);
|
|
507
|
+
* console.log(`Impact: ${quote.priceImpactPercent}%`);
|
|
508
|
+
* console.log(`Min output: ${quote.minimumAmountOut}`);
|
|
509
|
+
* ```
|
|
510
|
+
*/
|
|
511
|
+
getSwapQuote(poolAddress: Address, params: Omit<SwapParams, "wallet"> & {
|
|
512
|
+
tokenIn: Address;
|
|
513
|
+
tokenOut: Address;
|
|
514
|
+
poolState?: PoolState;
|
|
515
|
+
ammConfig?: AmmConfig;
|
|
516
|
+
}, options?: {
|
|
517
|
+
signal?: AbortSignal;
|
|
518
|
+
allowStale?: boolean;
|
|
519
|
+
}): Promise<SwapQuoteResult>;
|
|
520
|
+
/**
|
|
521
|
+
* Gets a swap quote using accurate calculation (with tick array traversal)
|
|
522
|
+
*
|
|
523
|
+
* Why this exists alongside getSwapQuote: Simple calculation assumes liquidity is
|
|
524
|
+
* concentrated at current price. For small swaps this is fine. But large swaps cross
|
|
525
|
+
* many ticks, each with different liquidity. Without traversing ticks, we'd
|
|
526
|
+
* underestimate price impact and users would get less than quoted.
|
|
527
|
+
*
|
|
528
|
+
* When to use this over getSwapQuote:
|
|
529
|
+
* 1. Large swaps (>5% of pool liquidity) - price impact is non-linear
|
|
530
|
+
* 2. Before building swap instructions - ensures on-chain result matches quote
|
|
531
|
+
* 3. When users demand precision - some use cases can't tolerate estimation error
|
|
532
|
+
*
|
|
533
|
+
* Cost trade-off: 2-5 additional RPC calls to fetch tick arrays, adding 200-500ms
|
|
534
|
+
* latency. Worth it for accuracy when executing valuable swaps.
|
|
535
|
+
*
|
|
536
|
+
* The method:
|
|
537
|
+
* 1. Fetches required tick arrays based on swap size estimation
|
|
538
|
+
* 2. Simulates tick-by-tick swap execution
|
|
539
|
+
* 3. Returns detailed quote with crossed ticks and price impact breakdown
|
|
540
|
+
*
|
|
541
|
+
* @param poolAddress - Address of the CLMM pool
|
|
542
|
+
* @param params - Swap parameters including tokens and amount
|
|
543
|
+
* @param params.tokenIn - Address of the input token
|
|
544
|
+
* @param params.tokenOut - Address of the output token
|
|
545
|
+
* @param params.amountIn - Amount of input token to swap (in base units)
|
|
546
|
+
* @param params.slippageTolerance - Optional slippage tolerance (0-1, default: 0.01 = 1%)
|
|
547
|
+
* @param params.tickArrayCount - Optional override for number of tick arrays to fetch
|
|
548
|
+
* @param options - Optional fetch configuration
|
|
549
|
+
* @param options.signal - AbortSignal to cancel the request if needed
|
|
550
|
+
* @param options.allowStale - Return stale data immediately and refresh in background
|
|
551
|
+
*
|
|
552
|
+
* @returns Detailed swap quote with tick information and price breakdown
|
|
553
|
+
*
|
|
554
|
+
* @throws {ClmmError} PRICE_SLIPPAGE if slippage tolerance is invalid
|
|
555
|
+
* @throws {ClmmError} SWAP_AMOUNT_CANNOT_BE_ZERO if amount is <= 0
|
|
556
|
+
* @throws {ClmmError} POOL_NOT_FOUND if pool doesn't contain the token pair
|
|
557
|
+
*
|
|
558
|
+
* @example
|
|
559
|
+
* ```typescript
|
|
560
|
+
* const quote = await swapManager.getAccurateSwapQuote(poolAddress, {
|
|
561
|
+
* tokenIn: usdcAddress,
|
|
562
|
+
* tokenOut: solAddress,
|
|
563
|
+
* amountIn: new BN(10_000_000_000), // 10,000 USDC (large swap)
|
|
564
|
+
* slippageTolerance: 0.02 // 2%
|
|
565
|
+
* });
|
|
566
|
+
*
|
|
567
|
+
* console.log(`Crossed ${quote.crossedTicks} ticks`);
|
|
568
|
+
* console.log(`End price: ${quote.endPrice}`);
|
|
569
|
+
* console.log(`Price impact breakdown:`, quote.priceImpactBreakdown);
|
|
570
|
+
* ```
|
|
571
|
+
*/
|
|
572
|
+
getAccurateSwapQuote(poolAddress: Address, params: Omit<SwapParams, "wallet"> & {
|
|
573
|
+
tokenIn: Address;
|
|
574
|
+
tokenOut: Address;
|
|
575
|
+
tickArrayCount?: number;
|
|
576
|
+
}, options?: {
|
|
577
|
+
signal?: AbortSignal;
|
|
578
|
+
allowStale?: boolean;
|
|
579
|
+
}): Promise<DetailedSwapQuote>;
|
|
580
|
+
private fetchTickArraysForSwap;
|
|
581
|
+
/**
|
|
582
|
+
* Estimates the number of tick arrays needed for a swap.
|
|
583
|
+
* Uses a conservative approach with safety buffers to prevent mid-swap failures.
|
|
584
|
+
*
|
|
585
|
+
* Why estimation is necessary: We need to know which tick arrays to fetch before
|
|
586
|
+
* we can calculate the accurate swap. But we can't know exactly how many ticks
|
|
587
|
+
* we'll cross without doing the calculation. Chicken-and-egg problem.
|
|
588
|
+
*
|
|
589
|
+
* Solution: Conservative estimation. We estimate based on swap size relative to
|
|
590
|
+
* liquidity, then add a safety buffer. Better to fetch extra arrays (slight
|
|
591
|
+
* performance cost) than to fetch too few and have the swap fail on-chain.
|
|
592
|
+
*
|
|
593
|
+
* Why the +3 buffer: In sparse liquidity situations, the swap might skip over
|
|
594
|
+
* initialized ticks faster than expected. The buffer ensures we have enough
|
|
595
|
+
* arrays even in worst-case scenarios. Orca and Uniswap use similar buffers.
|
|
596
|
+
*
|
|
597
|
+
* Why cap at 20: Prevents pathological cases (e.g., nearly empty pools) from
|
|
598
|
+
* fetching dozens of arrays, while still covering very large swaps or sparse
|
|
599
|
+
* liquidity scenarios. 20 arrays cover ~400 ticks, enough for most edge cases.
|
|
600
|
+
*
|
|
601
|
+
* @param pool - Current pool state
|
|
602
|
+
* @param amountIn - Amount being swapped
|
|
603
|
+
* @returns Number of tick arrays to fetch (includes safety buffer)
|
|
604
|
+
*/
|
|
605
|
+
private estimateTickArrayCount;
|
|
606
|
+
private getRequiredTickArrays;
|
|
607
|
+
getBatchQuotes(poolAddress: Address, params: Omit<SwapParams, "wallet"> & {
|
|
608
|
+
amounts: BN[];
|
|
609
|
+
tokenIn: Address;
|
|
610
|
+
tokenOut: Address;
|
|
611
|
+
}, options?: {
|
|
612
|
+
signal?: AbortSignal;
|
|
613
|
+
allowStale?: boolean;
|
|
614
|
+
}): Promise<Map<string, SwapQuoteResult>>;
|
|
615
|
+
calculateOptimalSlippage(quote: SwapQuote, options?: {
|
|
616
|
+
riskTolerance?: "low" | "medium" | "high";
|
|
617
|
+
maxSlippage?: number;
|
|
618
|
+
}): number;
|
|
619
|
+
getDetailedPriceImpact(poolAddress: Address, params: Omit<SwapParams, "wallet">, options?: {
|
|
620
|
+
signal?: AbortSignal;
|
|
621
|
+
allowStale?: boolean;
|
|
622
|
+
}): Promise<DetailedPriceImpact>;
|
|
623
|
+
/**
|
|
624
|
+
* Simulates a swap to validate it will succeed and identify potential issues
|
|
625
|
+
*
|
|
626
|
+
* Why simulation matters: On-chain transactions cost money and can't be undone.
|
|
627
|
+
* Running a simulation first catches problems before they cost users SOL in
|
|
628
|
+
* transaction fees. This is especially important for swaps that might fail due to:
|
|
629
|
+
* - Excessive slippage (price moved since quote)
|
|
630
|
+
* - Insufficient output (amount too small after fees)
|
|
631
|
+
* - Math errors (overflow, underflow)
|
|
632
|
+
*
|
|
633
|
+
* Why we check price impact thresholds: Different impact levels warrant different
|
|
634
|
+
* user warnings:
|
|
635
|
+
* - < 5%: Normal, no warning
|
|
636
|
+
* - 5-15%: High impact, warn user they might get sandwich attacked
|
|
637
|
+
* - > 15%: Critical, likely to fail or result in terrible execution
|
|
638
|
+
*
|
|
639
|
+
* Why we validate output amount: Some swaps result in fractional output amounts
|
|
640
|
+
* that round to zero. Better to catch this in simulation than discover it after
|
|
641
|
+
* the transaction succeeds but user got nothing.
|
|
642
|
+
*
|
|
643
|
+
* Use this before building instructions to catch problems early and provide
|
|
644
|
+
* better user feedback.
|
|
645
|
+
*
|
|
646
|
+
* @param poolAddress - Address of the CLMM pool
|
|
647
|
+
* @param params - Swap parameters to simulate
|
|
648
|
+
* @param options - Optional fetch configuration
|
|
649
|
+
* @param options.signal - AbortSignal to cancel the request if needed
|
|
650
|
+
* @param options.allowStale - Return stale data immediately and refresh in background
|
|
651
|
+
*
|
|
652
|
+
* @returns Simulation result with success flag, quote, errors, and warnings
|
|
653
|
+
*
|
|
654
|
+
* @example
|
|
655
|
+
* ```typescript
|
|
656
|
+
* const simulation = await swapManager.simulateSwap(poolAddress, {
|
|
657
|
+
* tokenIn: usdcAddress,
|
|
658
|
+
* tokenOut: solAddress,
|
|
659
|
+
* amountIn: new BN(100_000_000_000), // Large amount
|
|
660
|
+
* slippageTolerance: 0.01
|
|
661
|
+
* });
|
|
662
|
+
*
|
|
663
|
+
* if (!simulation.willSucceed) {
|
|
664
|
+
* console.error('Swap will fail:', simulation.errors);
|
|
665
|
+
* return;
|
|
666
|
+
* }
|
|
667
|
+
*
|
|
668
|
+
* if (simulation.warnings.length > 0) {
|
|
669
|
+
* console.warn('Warnings:', simulation.warnings);
|
|
670
|
+
* // Show user confirmation dialog
|
|
671
|
+
* }
|
|
672
|
+
*
|
|
673
|
+
* // Proceed with swap
|
|
674
|
+
* const instruction = await swapManager.buildSwapInstruction(
|
|
675
|
+
* poolAddress,
|
|
676
|
+
* wallet,
|
|
677
|
+
* params,
|
|
678
|
+
* simulation.quote
|
|
679
|
+
* );
|
|
680
|
+
* ```
|
|
681
|
+
*/
|
|
682
|
+
simulateSwap(poolAddress: Address, params: SwapParams, options?: {
|
|
683
|
+
signal?: AbortSignal;
|
|
684
|
+
allowStale?: boolean;
|
|
685
|
+
}): Promise<SwapSimulation>;
|
|
686
|
+
/**
|
|
687
|
+
* Builds a Solana instruction for executing a swap
|
|
688
|
+
*
|
|
689
|
+
* Why this is complex: A swap instruction needs many accounts and parameters:
|
|
690
|
+
* - Pool and config accounts (state to read)
|
|
691
|
+
* - Token vaults (where tokens are held)
|
|
692
|
+
* - User token accounts (where tokens come from/go to)
|
|
693
|
+
* - Observation account (for TWAP oracle)
|
|
694
|
+
* - Price limits (for MEV protection)
|
|
695
|
+
*
|
|
696
|
+
* This method handles all that complexity so users just provide tokens and amounts.
|
|
697
|
+
*
|
|
698
|
+
* Why we accept priorQuote: Common pattern is to show a quote to the user, get
|
|
699
|
+
* confirmation, then execute. If we re-ran simulation here, the price might have
|
|
700
|
+
* changed between display and execution. Accepting priorQuote lets users execute
|
|
701
|
+
* exactly what they confirmed, with slippage protection handling price movement.
|
|
702
|
+
*
|
|
703
|
+
* Why we simulate if no quote provided: Catch-all safety net. If someone builds
|
|
704
|
+
* an instruction without checking first, we prevent obviously broken swaps from
|
|
705
|
+
* being sent on-chain.
|
|
706
|
+
*
|
|
707
|
+
* The instruction is ready to be added to a transaction and sent to the network.
|
|
708
|
+
*
|
|
709
|
+
* @param poolAddress - Address of the CLMM pool
|
|
710
|
+
* @param payer - Transaction signer (wallet that will execute the swap)
|
|
711
|
+
* @param params - Swap parameters
|
|
712
|
+
* @param params.tokenIn - Input token address
|
|
713
|
+
* @param params.tokenOut - Output token address
|
|
714
|
+
* @param params.amountIn - Amount to swap (in base units)
|
|
715
|
+
* @param params.slippageTolerance - Optional slippage tolerance (0-1)
|
|
716
|
+
* @param params.sqrtPriceLimitX64 - Optional custom price limit (advanced)
|
|
717
|
+
* @param priorQuote - Optional pre-computed quote (skips simulation if provided)
|
|
718
|
+
*
|
|
719
|
+
* @returns Solana instruction ready to be executed
|
|
720
|
+
*
|
|
721
|
+
* @throws {ClmmError} SWAP_SIMULATION_FAILED if pre-execution simulation fails
|
|
722
|
+
*
|
|
723
|
+
* @example
|
|
724
|
+
* ```typescript
|
|
725
|
+
* // Get quote first (recommended for displaying to user)
|
|
726
|
+
* const quote = await swapManager.getAccurateSwapQuote(poolAddress, {
|
|
727
|
+
* tokenIn: usdcAddress,
|
|
728
|
+
* tokenOut: solAddress,
|
|
729
|
+
* amountIn: new BN(1_000_000),
|
|
730
|
+
* slippageTolerance: 0.01
|
|
731
|
+
* });
|
|
732
|
+
*
|
|
733
|
+
* // Build instruction using the quote
|
|
734
|
+
* const instruction = await swapManager.buildSwapInstruction(
|
|
735
|
+
* poolAddress,
|
|
736
|
+
* wallet,
|
|
737
|
+
* {
|
|
738
|
+
* tokenIn: usdcAddress,
|
|
739
|
+
* tokenOut: solAddress,
|
|
740
|
+
* amountIn: new BN(1_000_000),
|
|
741
|
+
* slippageTolerance: 0.01
|
|
742
|
+
* },
|
|
743
|
+
* quote // Reuse quote to avoid re-simulation
|
|
744
|
+
* );
|
|
745
|
+
*
|
|
746
|
+
* // Add to transaction and send
|
|
747
|
+
* const tx = new Transaction().add(instruction);
|
|
748
|
+
* ```
|
|
749
|
+
*/
|
|
750
|
+
buildSwapInstruction(poolAddress: Address, payer: TransactionSigner, params: SwapParams & {
|
|
751
|
+
sqrtPriceLimitX64?: BN;
|
|
752
|
+
}, priorQuote?: SwapQuote, options?: {
|
|
753
|
+
signal?: AbortSignal;
|
|
754
|
+
}): Promise<Instruction>;
|
|
755
|
+
getCurrentPrice(poolAddress: Address, options?: {
|
|
756
|
+
signal?: AbortSignal;
|
|
757
|
+
allowStale?: boolean;
|
|
758
|
+
}): Promise<Decimal>;
|
|
759
|
+
/**
|
|
760
|
+
* Validates pool price against market price from REST API
|
|
761
|
+
*
|
|
762
|
+
* Why this matters: On-chain pool state can become stale between blocks or lag
|
|
763
|
+
* behind market prices due to arbitrage delays. Comparing against the REST API
|
|
764
|
+
* helps detect:
|
|
765
|
+
* - Stale pool data (price hasn't updated recently)
|
|
766
|
+
* - Arbitrage opportunities (on-chain price differs from market)
|
|
767
|
+
* - Potential MEV risks (large price discrepancies)
|
|
768
|
+
*
|
|
769
|
+
* This uses the enhanced PriceApiClient with:
|
|
770
|
+
* - Automatic retry on failures
|
|
771
|
+
* - Concurrency control
|
|
772
|
+
* - Abort signal support
|
|
773
|
+
* - Staleness detection
|
|
774
|
+
*
|
|
775
|
+
* @param poolAddress - Pool to validate
|
|
776
|
+
* @param opts - Optional configuration
|
|
777
|
+
* @param opts.signal - AbortSignal to cancel the request
|
|
778
|
+
* @param opts.maxDivergence - Maximum acceptable price divergence (0-1, default: 0.05 = 5%)
|
|
779
|
+
* @returns Validation result with price comparison
|
|
780
|
+
*
|
|
781
|
+
* @throws {Error} If priceApiConfig is not configured
|
|
782
|
+
*
|
|
783
|
+
* @example
|
|
784
|
+
* ```typescript
|
|
785
|
+
* const validation = await swapManager.validatePoolPrice(poolAddress, {
|
|
786
|
+
* maxDivergence: 0.02 // 2% tolerance
|
|
787
|
+
* });
|
|
788
|
+
*
|
|
789
|
+
* if (!validation.isValid) {
|
|
790
|
+
* console.warn('Pool price diverges from market:', validation);
|
|
791
|
+
* // Maybe increase slippage or wait for arbitrage
|
|
792
|
+
* }
|
|
793
|
+
* ```
|
|
794
|
+
*/
|
|
795
|
+
validatePoolPrice(poolAddress: Address, opts?: {
|
|
796
|
+
signal?: AbortSignal;
|
|
797
|
+
maxDivergence?: number;
|
|
798
|
+
}): Promise<{
|
|
799
|
+
isValid: boolean;
|
|
800
|
+
onChainPrice: Decimal;
|
|
801
|
+
marketPrice?: Decimal;
|
|
802
|
+
divergence?: number;
|
|
803
|
+
divergencePercent?: number;
|
|
804
|
+
warning?: string;
|
|
805
|
+
}>;
|
|
806
|
+
/**
|
|
807
|
+
* Gets swap quote with optional market price validation
|
|
808
|
+
*
|
|
809
|
+
* Enhanced version of getSwapQuote that validates pool price against market
|
|
810
|
+
* prices when enablePriceValidation is enabled in config.
|
|
811
|
+
*
|
|
812
|
+
* @param poolAddress - Pool address
|
|
813
|
+
* @param params - Swap parameters
|
|
814
|
+
* @param opts - Optional configuration
|
|
815
|
+
* @param opts.signal - AbortSignal to cancel the request
|
|
816
|
+
* @param opts.skipValidation - Skip validation even if enabled (for performance)
|
|
817
|
+
* @returns Swap quote result with optional validation info
|
|
818
|
+
*/
|
|
819
|
+
getValidatedSwapQuote(poolAddress: Address, params: Omit<SwapParams, "wallet"> & {
|
|
820
|
+
tokenIn: Address;
|
|
821
|
+
tokenOut: Address;
|
|
822
|
+
}, opts?: {
|
|
823
|
+
signal?: AbortSignal;
|
|
824
|
+
skipValidation?: boolean;
|
|
825
|
+
}): Promise<{
|
|
826
|
+
quote: SwapQuoteResult;
|
|
827
|
+
validation?: {
|
|
828
|
+
isValid: boolean;
|
|
829
|
+
onChainPrice: Decimal;
|
|
830
|
+
marketPrice?: Decimal;
|
|
831
|
+
divergencePercent?: number;
|
|
832
|
+
warning?: string;
|
|
833
|
+
};
|
|
834
|
+
}>;
|
|
835
|
+
}
|
|
6
836
|
//# sourceMappingURL=swap.d.ts.map
|