outsmart 2.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +568 -0
- package/dist/cli.d.ts +44 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1251 -0
- package/dist/cli.js.map +1 -0
- package/dist/dex/byreal-clmm.d.ts +16 -0
- package/dist/dex/byreal-clmm.d.ts.map +1 -0
- package/dist/dex/byreal-clmm.js +39 -0
- package/dist/dex/byreal-clmm.js.map +1 -0
- package/dist/dex/dflow.d.ts +27 -0
- package/dist/dex/dflow.d.ts.map +1 -0
- package/dist/dex/dflow.js +200 -0
- package/dist/dex/dflow.js.map +1 -0
- package/dist/dex/fusion-amm.d.ts +44 -0
- package/dist/dex/fusion-amm.d.ts.map +1 -0
- package/dist/dex/fusion-amm.js +546 -0
- package/dist/dex/fusion-amm.js.map +1 -0
- package/dist/dex/futarchy-amm.d.ts +32 -0
- package/dist/dex/futarchy-amm.d.ts.map +1 -0
- package/dist/dex/futarchy-amm.js +443 -0
- package/dist/dex/futarchy-amm.js.map +1 -0
- package/dist/dex/futarchy-idl.d.ts +2568 -0
- package/dist/dex/futarchy-idl.d.ts.map +1 -0
- package/dist/dex/futarchy-idl.js +2570 -0
- package/dist/dex/futarchy-idl.js.map +1 -0
- package/dist/dex/futarchy-launchpad.d.ts +68 -0
- package/dist/dex/futarchy-launchpad.d.ts.map +1 -0
- package/dist/dex/futarchy-launchpad.js +377 -0
- package/dist/dex/futarchy-launchpad.js.map +1 -0
- package/dist/dex/index.d.ts +88 -0
- package/dist/dex/index.d.ts.map +1 -0
- package/dist/dex/index.js +159 -0
- package/dist/dex/index.js.map +1 -0
- package/dist/dex/jupiter-ultra.d.ts +27 -0
- package/dist/dex/jupiter-ultra.d.ts.map +1 -0
- package/dist/dex/jupiter-ultra.js +369 -0
- package/dist/dex/jupiter-ultra.js.map +1 -0
- package/dist/dex/meteora-damm-v1.d.ts +36 -0
- package/dist/dex/meteora-damm-v1.d.ts.map +1 -0
- package/dist/dex/meteora-damm-v1.js +314 -0
- package/dist/dex/meteora-damm-v1.js.map +1 -0
- package/dist/dex/meteora-damm-v2.d.ts +103 -0
- package/dist/dex/meteora-damm-v2.d.ts.map +1 -0
- package/dist/dex/meteora-damm-v2.js +1146 -0
- package/dist/dex/meteora-damm-v2.js.map +1 -0
- package/dist/dex/meteora-dbc.d.ts +38 -0
- package/dist/dex/meteora-dbc.d.ts.map +1 -0
- package/dist/dex/meteora-dbc.js +374 -0
- package/dist/dex/meteora-dbc.js.map +1 -0
- package/dist/dex/meteora-dlmm.d.ts +79 -0
- package/dist/dex/meteora-dlmm.d.ts.map +1 -0
- package/dist/dex/meteora-dlmm.js +735 -0
- package/dist/dex/meteora-dlmm.js.map +1 -0
- package/dist/dex/orca.d.ts +31 -0
- package/dist/dex/orca.d.ts.map +1 -0
- package/dist/dex/orca.js +536 -0
- package/dist/dex/orca.js.map +1 -0
- package/dist/dex/pancakeswap-clmm.d.ts +16 -0
- package/dist/dex/pancakeswap-clmm.d.ts.map +1 -0
- package/dist/dex/pancakeswap-clmm.js +39 -0
- package/dist/dex/pancakeswap-clmm.js.map +1 -0
- package/dist/dex/pumpfun-amm.d.ts +46 -0
- package/dist/dex/pumpfun-amm.d.ts.map +1 -0
- package/dist/dex/pumpfun-amm.js +692 -0
- package/dist/dex/pumpfun-amm.js.map +1 -0
- package/dist/dex/pumpfun.d.ts +41 -0
- package/dist/dex/pumpfun.d.ts.map +1 -0
- package/dist/dex/pumpfun.js +555 -0
- package/dist/dex/pumpfun.js.map +1 -0
- package/dist/dex/raydium-amm-v4.d.ts +11 -0
- package/dist/dex/raydium-amm-v4.d.ts.map +1 -0
- package/dist/dex/raydium-amm-v4.js +649 -0
- package/dist/dex/raydium-amm-v4.js.map +1 -0
- package/dist/dex/raydium-clmm.d.ts +12 -0
- package/dist/dex/raydium-clmm.d.ts.map +1 -0
- package/dist/dex/raydium-clmm.js +675 -0
- package/dist/dex/raydium-clmm.js.map +1 -0
- package/dist/dex/raydium-cpmm.d.ts +10 -0
- package/dist/dex/raydium-cpmm.d.ts.map +1 -0
- package/dist/dex/raydium-cpmm.js +613 -0
- package/dist/dex/raydium-cpmm.js.map +1 -0
- package/dist/dex/raydium-launchlab.d.ts +12 -0
- package/dist/dex/raydium-launchlab.d.ts.map +1 -0
- package/dist/dex/raydium-launchlab.js +530 -0
- package/dist/dex/raydium-launchlab.js.map +1 -0
- package/dist/dex/shared/clmm-base.d.ts +58 -0
- package/dist/dex/shared/clmm-base.d.ts.map +1 -0
- package/dist/dex/shared/clmm-base.js +891 -0
- package/dist/dex/shared/clmm-base.js.map +1 -0
- package/dist/dex/types.d.ts +601 -0
- package/dist/dex/types.d.ts.map +1 -0
- package/dist/dex/types.js +137 -0
- package/dist/dex/types.js.map +1 -0
- package/dist/dexscreener/index.d.ts +2 -0
- package/dist/dexscreener/index.d.ts.map +1 -0
- package/dist/dexscreener/index.js +18 -0
- package/dist/dexscreener/index.js.map +1 -0
- package/dist/dexscreener/info.d.ts +22 -0
- package/dist/dexscreener/info.d.ts.map +1 -0
- package/dist/dexscreener/info.js +104 -0
- package/dist/dexscreener/info.js.map +1 -0
- package/dist/helpers/check_balance.d.ts +10 -0
- package/dist/helpers/check_balance.d.ts.map +1 -0
- package/dist/helpers/check_balance.js +34 -0
- package/dist/helpers/check_balance.js.map +1 -0
- package/dist/helpers/config.d.ts +51 -0
- package/dist/helpers/config.d.ts.map +1 -0
- package/dist/helpers/config.js +118 -0
- package/dist/helpers/config.js.map +1 -0
- package/dist/helpers/index.d.ts +8 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +29 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/helpers/logger.d.ts +27 -0
- package/dist/helpers/logger.d.ts.map +1 -0
- package/dist/helpers/logger.js +39 -0
- package/dist/helpers/logger.js.map +1 -0
- package/dist/helpers/token-2022.d.ts +32 -0
- package/dist/helpers/token-2022.d.ts.map +1 -0
- package/dist/helpers/token-2022.js +48 -0
- package/dist/helpers/token-2022.js.map +1 -0
- package/dist/helpers/unwrap_sol.d.ts +2 -0
- package/dist/helpers/unwrap_sol.d.ts.map +1 -0
- package/dist/helpers/unwrap_sol.js +67 -0
- package/dist/helpers/unwrap_sol.js.map +1 -0
- package/dist/helpers/util.d.ts +698 -0
- package/dist/helpers/util.d.ts.map +1 -0
- package/dist/helpers/util.js +181 -0
- package/dist/helpers/util.js.map +1 -0
- package/dist/helpers/utils.d.ts +10 -0
- package/dist/helpers/utils.d.ts.map +1 -0
- package/dist/helpers/utils.js +97 -0
- package/dist/helpers/utils.js.map +1 -0
- package/dist/helpers/wrap_sol.d.ts +3 -0
- package/dist/helpers/wrap_sol.d.ts.map +1 -0
- package/dist/helpers/wrap_sol.js +88 -0
- package/dist/helpers/wrap_sol.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/transactions/bloXroute_tips_tx_executor.d.ts +4 -0
- package/dist/transactions/bloXroute_tips_tx_executor.d.ts.map +1 -0
- package/dist/transactions/bloXroute_tips_tx_executor.js +70 -0
- package/dist/transactions/bloXroute_tips_tx_executor.js.map +1 -0
- package/dist/transactions/index.d.ts +6 -0
- package/dist/transactions/index.d.ts.map +1 -0
- package/dist/transactions/index.js +30 -0
- package/dist/transactions/index.js.map +1 -0
- package/dist/transactions/jito_tips_tx_executor.d.ts +15 -0
- package/dist/transactions/jito_tips_tx_executor.d.ts.map +1 -0
- package/dist/transactions/jito_tips_tx_executor.js +99 -0
- package/dist/transactions/jito_tips_tx_executor.js.map +1 -0
- package/dist/transactions/landing/index.d.ts +30 -0
- package/dist/transactions/landing/index.d.ts.map +1 -0
- package/dist/transactions/landing/index.js +60 -0
- package/dist/transactions/landing/index.js.map +1 -0
- package/dist/transactions/landing/nonce-manager.d.ts +116 -0
- package/dist/transactions/landing/nonce-manager.d.ts.map +1 -0
- package/dist/transactions/landing/nonce-manager.js +393 -0
- package/dist/transactions/landing/nonce-manager.js.map +1 -0
- package/dist/transactions/landing/orchestrator.d.ts +104 -0
- package/dist/transactions/landing/orchestrator.d.ts.map +1 -0
- package/dist/transactions/landing/orchestrator.js +329 -0
- package/dist/transactions/landing/orchestrator.js.map +1 -0
- package/dist/transactions/landing/providers/astralane.d.ts +12 -0
- package/dist/transactions/landing/providers/astralane.d.ts.map +1 -0
- package/dist/transactions/landing/providers/astralane.js +132 -0
- package/dist/transactions/landing/providers/astralane.js.map +1 -0
- package/dist/transactions/landing/providers/blockrazor.d.ts +11 -0
- package/dist/transactions/landing/providers/blockrazor.d.ts.map +1 -0
- package/dist/transactions/landing/providers/blockrazor.js +134 -0
- package/dist/transactions/landing/providers/blockrazor.js.map +1 -0
- package/dist/transactions/landing/providers/bloxroute.d.ts +12 -0
- package/dist/transactions/landing/providers/bloxroute.d.ts.map +1 -0
- package/dist/transactions/landing/providers/bloxroute.js +102 -0
- package/dist/transactions/landing/providers/bloxroute.js.map +1 -0
- package/dist/transactions/landing/providers/flashblock.d.ts +10 -0
- package/dist/transactions/landing/providers/flashblock.d.ts.map +1 -0
- package/dist/transactions/landing/providers/flashblock.js +102 -0
- package/dist/transactions/landing/providers/flashblock.js.map +1 -0
- package/dist/transactions/landing/providers/helius-sender.d.ts +11 -0
- package/dist/transactions/landing/providers/helius-sender.d.ts.map +1 -0
- package/dist/transactions/landing/providers/helius-sender.js +101 -0
- package/dist/transactions/landing/providers/helius-sender.js.map +1 -0
- package/dist/transactions/landing/providers/jito.d.ts +16 -0
- package/dist/transactions/landing/providers/jito.d.ts.map +1 -0
- package/dist/transactions/landing/providers/jito.js +110 -0
- package/dist/transactions/landing/providers/jito.js.map +1 -0
- package/dist/transactions/landing/providers/nextblock.d.ts +11 -0
- package/dist/transactions/landing/providers/nextblock.d.ts.map +1 -0
- package/dist/transactions/landing/providers/nextblock.js +109 -0
- package/dist/transactions/landing/providers/nextblock.js.map +1 -0
- package/dist/transactions/landing/providers/node1.d.ts +11 -0
- package/dist/transactions/landing/providers/node1.d.ts.map +1 -0
- package/dist/transactions/landing/providers/node1.js +101 -0
- package/dist/transactions/landing/providers/node1.js.map +1 -0
- package/dist/transactions/landing/providers/nozomi.d.ts +11 -0
- package/dist/transactions/landing/providers/nozomi.d.ts.map +1 -0
- package/dist/transactions/landing/providers/nozomi.js +124 -0
- package/dist/transactions/landing/providers/nozomi.js.map +1 -0
- package/dist/transactions/landing/providers/soyas.d.ts +16 -0
- package/dist/transactions/landing/providers/soyas.d.ts.map +1 -0
- package/dist/transactions/landing/providers/soyas.js +192 -0
- package/dist/transactions/landing/providers/soyas.js.map +1 -0
- package/dist/transactions/landing/providers/stellium.d.ts +11 -0
- package/dist/transactions/landing/providers/stellium.d.ts.map +1 -0
- package/dist/transactions/landing/providers/stellium.js +102 -0
- package/dist/transactions/landing/providers/stellium.js.map +1 -0
- package/dist/transactions/landing/providers/zero-slot.d.ts +10 -0
- package/dist/transactions/landing/providers/zero-slot.d.ts.map +1 -0
- package/dist/transactions/landing/providers/zero-slot.js +92 -0
- package/dist/transactions/landing/providers/zero-slot.js.map +1 -0
- package/dist/transactions/landing/tip-accounts.d.ts +22 -0
- package/dist/transactions/landing/tip-accounts.d.ts.map +1 -0
- package/dist/transactions/landing/tip-accounts.js +140 -0
- package/dist/transactions/landing/tip-accounts.js.map +1 -0
- package/dist/transactions/landing/types.d.ts +98 -0
- package/dist/transactions/landing/types.d.ts.map +1 -0
- package/dist/transactions/landing/types.js +30 -0
- package/dist/transactions/landing/types.js.map +1 -0
- package/dist/transactions/nozomi/tx-submission.d.ts +14 -0
- package/dist/transactions/nozomi/tx-submission.d.ts.map +1 -0
- package/dist/transactions/nozomi/tx-submission.js +107 -0
- package/dist/transactions/nozomi/tx-submission.js.map +1 -0
- package/dist/transactions/send-rpc.d.ts +54 -0
- package/dist/transactions/send-rpc.d.ts.map +1 -0
- package/dist/transactions/send-rpc.js +126 -0
- package/dist/transactions/send-rpc.js.map +1 -0
- package/dist/transactions/simple_tx_executor.d.ts +10 -0
- package/dist/transactions/simple_tx_executor.d.ts.map +1 -0
- package/dist/transactions/simple_tx_executor.js +33 -0
- package/dist/transactions/simple_tx_executor.js.map +1 -0
- package/package.json +112 -0
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared CLMM (Concentrated Liquidity Market Maker) Base
|
|
4
|
+
*
|
|
5
|
+
* This module provides the common CLMM swap logic shared by:
|
|
6
|
+
* - byreal-clmm (program: REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2)
|
|
7
|
+
* - pancakeswap-clmm (program: HpNfyc2Saw7RKkQd8nEL4khUcuPhQ7WwY1B2qjx8jxFq)
|
|
8
|
+
*
|
|
9
|
+
* Both are forks of the Raydium CLMM program with identical:
|
|
10
|
+
* - Account layout (pool state, tick arrays, bitmap extension)
|
|
11
|
+
* - Instruction format (swap_v2 discriminator + args)
|
|
12
|
+
* - PDA derivation seeds
|
|
13
|
+
*
|
|
14
|
+
* The ONLY difference is the program ID. This base module is parameterized
|
|
15
|
+
* by programId so each adapter just passes its own.
|
|
16
|
+
*
|
|
17
|
+
* Source: 100x-algo-bots/trading-modules/byreal-clmm/ and pancakeswap-clmm/
|
|
18
|
+
*/
|
|
19
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
20
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.ClmmBaseAdapter = void 0;
|
|
24
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
25
|
+
const spl_token_1 = require("@solana/spl-token");
|
|
26
|
+
const bn_js_1 = __importDefault(require("bn.js"));
|
|
27
|
+
const decimal_js_1 = __importDefault(require("decimal.js"));
|
|
28
|
+
const raydium_sdk_v2_1 = require("@raydium-io/raydium-sdk-v2");
|
|
29
|
+
const config_1 = require("../../helpers/config");
|
|
30
|
+
const landing_1 = require("../../transactions/landing");
|
|
31
|
+
const send_rpc_1 = require("../../transactions/send-rpc");
|
|
32
|
+
const types_1 = require("../types");
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Constants
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
const USD1_MINT_STR = "USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB";
|
|
37
|
+
const MEMO_PROGRAM_ID = new web3_js_1.PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
|
|
38
|
+
const SIX_DECIMAL_MINTS = new Set([types_1.USDC_MINT, types_1.USDT_MINT, USD1_MINT_STR]);
|
|
39
|
+
/** swap_v2 discriminator — identical across Raydium/Byreal/PancakeSwap CLMM forks */
|
|
40
|
+
const SWAP_V2_DISCRIMINATOR = Buffer.from([43, 4, 237, 11, 26, 201, 30, 98]);
|
|
41
|
+
/** Tick array constants */
|
|
42
|
+
const TICK_ARRAY_SIZE = 60;
|
|
43
|
+
const TICK_ARRAY_BITMAP_SIZE = 512;
|
|
44
|
+
const MAX_TICK = 443636;
|
|
45
|
+
const MIN_TICK = -443636;
|
|
46
|
+
const MIN_SQRT_PRICE_X64 = new bn_js_1.default("4295048016");
|
|
47
|
+
const MAX_SQRT_PRICE_X64 = new bn_js_1.default("79226673515401279992447579055");
|
|
48
|
+
// Extension bitmap constants
|
|
49
|
+
const EXTENSION_TICKARRAY_BITMAP_SIZE = 14;
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// PDA derivation — CLMM pool accounts
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
/**
|
|
54
|
+
* Convert i32 to 4-byte big-endian buffer.
|
|
55
|
+
* Note: Byreal & PancakeSwap CLMM forks use big-endian for tick array
|
|
56
|
+
* start index in PDA derivation (differs from Raydium CLMM which uses LE).
|
|
57
|
+
*/
|
|
58
|
+
function i32ToBytes(num) {
|
|
59
|
+
const buf = Buffer.alloc(4);
|
|
60
|
+
buf.writeInt32BE(num, 0);
|
|
61
|
+
return buf;
|
|
62
|
+
}
|
|
63
|
+
function deriveTickArray(programId, poolId, startIndex) {
|
|
64
|
+
const [addr] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("tick_array"), poolId.toBuffer(), i32ToBytes(startIndex)], programId);
|
|
65
|
+
return addr;
|
|
66
|
+
}
|
|
67
|
+
function deriveTickArrayBitmapExtension(programId, poolId) {
|
|
68
|
+
const [addr] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("pool_tick_array_bitmap_extension"), poolId.toBuffer()], programId);
|
|
69
|
+
return addr;
|
|
70
|
+
}
|
|
71
|
+
function deriveObservationState(programId, poolId) {
|
|
72
|
+
const [addr] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("observation"), poolId.toBuffer()], programId);
|
|
73
|
+
return addr;
|
|
74
|
+
}
|
|
75
|
+
function decodePoolState(data) {
|
|
76
|
+
let offset = 8; // skip discriminator
|
|
77
|
+
// Bump [1 byte]
|
|
78
|
+
offset += 1;
|
|
79
|
+
// ammConfig [32 bytes]
|
|
80
|
+
const ammConfig = new web3_js_1.PublicKey(data.subarray(offset, offset + 32));
|
|
81
|
+
offset += 32;
|
|
82
|
+
// owner [32 bytes]
|
|
83
|
+
offset += 32;
|
|
84
|
+
// tokenMint0 [32 bytes]
|
|
85
|
+
const tokenMint0 = new web3_js_1.PublicKey(data.subarray(offset, offset + 32));
|
|
86
|
+
offset += 32;
|
|
87
|
+
// tokenMint1 [32 bytes]
|
|
88
|
+
const tokenMint1 = new web3_js_1.PublicKey(data.subarray(offset, offset + 32));
|
|
89
|
+
offset += 32;
|
|
90
|
+
// tokenVault0 [32 bytes]
|
|
91
|
+
const tokenVault0 = new web3_js_1.PublicKey(data.subarray(offset, offset + 32));
|
|
92
|
+
offset += 32;
|
|
93
|
+
// tokenVault1 [32 bytes]
|
|
94
|
+
const tokenVault1 = new web3_js_1.PublicKey(data.subarray(offset, offset + 32));
|
|
95
|
+
offset += 32;
|
|
96
|
+
// observationKey [32 bytes]
|
|
97
|
+
const observationKey = new web3_js_1.PublicKey(data.subarray(offset, offset + 32));
|
|
98
|
+
offset += 32;
|
|
99
|
+
// mintDecimals0 [1 byte]
|
|
100
|
+
const mintDecimals0 = data.readUInt8(offset);
|
|
101
|
+
offset += 1;
|
|
102
|
+
// mintDecimals1 [1 byte]
|
|
103
|
+
const mintDecimals1 = data.readUInt8(offset);
|
|
104
|
+
offset += 1;
|
|
105
|
+
// tickSpacing [2 bytes]
|
|
106
|
+
const tickSpacing = data.readUInt16LE(offset);
|
|
107
|
+
offset += 2;
|
|
108
|
+
// liquidity [16 bytes]
|
|
109
|
+
const liquidity = new bn_js_1.default(data.subarray(offset, offset + 16), "le");
|
|
110
|
+
offset += 16;
|
|
111
|
+
// sqrtPriceX64 [16 bytes]
|
|
112
|
+
const sqrtPriceX64 = new bn_js_1.default(data.subarray(offset, offset + 16), "le");
|
|
113
|
+
offset += 16;
|
|
114
|
+
// tickCurrent [4 bytes]
|
|
115
|
+
const tickCurrent = data.readInt32LE(offset);
|
|
116
|
+
offset += 4;
|
|
117
|
+
// padding [4 bytes]
|
|
118
|
+
offset += 4;
|
|
119
|
+
// feeGrowthGlobal0X64 [16]
|
|
120
|
+
offset += 16;
|
|
121
|
+
// feeGrowthGlobal1X64 [16]
|
|
122
|
+
offset += 16;
|
|
123
|
+
// protocolFeesToken0 [8]
|
|
124
|
+
offset += 8;
|
|
125
|
+
// protocolFeesToken1 [8]
|
|
126
|
+
offset += 8;
|
|
127
|
+
// swapInAmountToken0 [16]
|
|
128
|
+
offset += 16;
|
|
129
|
+
// swapOutAmountToken1 [16]
|
|
130
|
+
offset += 16;
|
|
131
|
+
// swapInAmountToken1 [16]
|
|
132
|
+
offset += 16;
|
|
133
|
+
// swapOutAmountToken0 [16]
|
|
134
|
+
offset += 16;
|
|
135
|
+
// status [1 byte]
|
|
136
|
+
offset += 1;
|
|
137
|
+
// padding [7 bytes]
|
|
138
|
+
offset += 7;
|
|
139
|
+
// rewardInfos [3 * 169 bytes = 507 bytes]
|
|
140
|
+
offset += 507;
|
|
141
|
+
// tickArrayBitmap [16 * u64 = 16 * 8 bytes = 128 bytes]
|
|
142
|
+
// Note: This is [u64; 16], NOT [u128; 16]. Each element is 8 bytes.
|
|
143
|
+
const tickArrayBitmap = [];
|
|
144
|
+
for (let i = 0; i < 16; i++) {
|
|
145
|
+
tickArrayBitmap.push(new bn_js_1.default(data.subarray(offset, offset + 8), "le"));
|
|
146
|
+
offset += 8;
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
ammConfig,
|
|
150
|
+
tokenMint0,
|
|
151
|
+
tokenMint1,
|
|
152
|
+
tokenVault0,
|
|
153
|
+
tokenVault1,
|
|
154
|
+
observationKey,
|
|
155
|
+
mintDecimals0,
|
|
156
|
+
mintDecimals1,
|
|
157
|
+
tickSpacing,
|
|
158
|
+
sqrtPriceX64,
|
|
159
|
+
tickCurrent,
|
|
160
|
+
liquidity,
|
|
161
|
+
tickArrayBitmap,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
async function fetchClmmPoolState(connection, poolId, programId) {
|
|
165
|
+
const accountInfo = await connection.getAccountInfo(poolId);
|
|
166
|
+
if (!accountInfo) {
|
|
167
|
+
throw new Error(`CLMM pool not found: ${poolId.toBase58()}`);
|
|
168
|
+
}
|
|
169
|
+
if (accountInfo.owner.toBase58() !== programId.toBase58()) {
|
|
170
|
+
throw new Error(`Pool ${poolId.toBase58()} owner ${accountInfo.owner.toBase58()} does not match expected program ${programId.toBase58()}`);
|
|
171
|
+
}
|
|
172
|
+
return decodePoolState(accountInfo.data);
|
|
173
|
+
}
|
|
174
|
+
async function getTickArrayBitmapExtension(programId, poolId, connection) {
|
|
175
|
+
const exBitmapAddress = deriveTickArrayBitmapExtension(programId, poolId);
|
|
176
|
+
const accountInfo = await connection.getAccountInfo(exBitmapAddress);
|
|
177
|
+
if (!accountInfo) {
|
|
178
|
+
// Graceful fallback — return empty bitmaps (pancakeswap pattern)
|
|
179
|
+
const emptyBitmaps = [];
|
|
180
|
+
for (let i = 0; i < EXTENSION_TICKARRAY_BITMAP_SIZE; i++) {
|
|
181
|
+
emptyBitmaps.push(Array(8).fill(new bn_js_1.default(0)));
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
poolId,
|
|
185
|
+
exBitmapAddress,
|
|
186
|
+
exists: false,
|
|
187
|
+
positiveTickArrayBitmap: emptyBitmaps,
|
|
188
|
+
negativeTickArrayBitmap: emptyBitmaps,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const data = accountInfo.data;
|
|
192
|
+
let offset = 8 + 32; // discriminator + poolId
|
|
193
|
+
// positiveTickArrayBitmap [14 * 8 * 8 = 896 bytes]
|
|
194
|
+
const positiveTickArrayBitmap = [];
|
|
195
|
+
for (let i = 0; i < EXTENSION_TICKARRAY_BITMAP_SIZE; i++) {
|
|
196
|
+
const row = [];
|
|
197
|
+
for (let j = 0; j < 8; j++) {
|
|
198
|
+
row.push(new bn_js_1.default(data.subarray(offset, offset + 8), "le"));
|
|
199
|
+
offset += 8;
|
|
200
|
+
}
|
|
201
|
+
positiveTickArrayBitmap.push(row);
|
|
202
|
+
}
|
|
203
|
+
// negativeTickArrayBitmap [14 * 8 * 8 = 896 bytes]
|
|
204
|
+
const negativeTickArrayBitmap = [];
|
|
205
|
+
for (let i = 0; i < EXTENSION_TICKARRAY_BITMAP_SIZE; i++) {
|
|
206
|
+
const row = [];
|
|
207
|
+
for (let j = 0; j < 8; j++) {
|
|
208
|
+
row.push(new bn_js_1.default(data.subarray(offset, offset + 8), "le"));
|
|
209
|
+
offset += 8;
|
|
210
|
+
}
|
|
211
|
+
negativeTickArrayBitmap.push(row);
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
poolId,
|
|
215
|
+
exBitmapAddress,
|
|
216
|
+
exists: true,
|
|
217
|
+
positiveTickArrayBitmap,
|
|
218
|
+
negativeTickArrayBitmap,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Tick array bitmap search
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
function getNextTickArrayStartIndex(currentStartIndex, tickSpacing, zeroForOne) {
|
|
225
|
+
const ticksPerArray = tickSpacing * TICK_ARRAY_SIZE;
|
|
226
|
+
return zeroForOne
|
|
227
|
+
? currentStartIndex - ticksPerArray
|
|
228
|
+
: currentStartIndex + ticksPerArray;
|
|
229
|
+
}
|
|
230
|
+
function getTickArrayStartIndexForTick(tick, tickSpacing) {
|
|
231
|
+
const ticksPerArray = tickSpacing * TICK_ARRAY_SIZE;
|
|
232
|
+
let startIndex = Math.floor(tick / ticksPerArray) * ticksPerArray;
|
|
233
|
+
if (tick < 0 && tick % ticksPerArray !== 0) {
|
|
234
|
+
startIndex -= ticksPerArray;
|
|
235
|
+
}
|
|
236
|
+
return startIndex;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Merge an array of u64 BNs into a single large BN.
|
|
240
|
+
* Matches the reference SDK: b = sum(bns[i] << (64 * i))
|
|
241
|
+
*/
|
|
242
|
+
function mergeTickArrayBitmapToSingleBN(bns) {
|
|
243
|
+
let b = new bn_js_1.default(0);
|
|
244
|
+
for (let i = 0; i < bns.length; i++) {
|
|
245
|
+
b = b.add(bns[i].shln(64 * i));
|
|
246
|
+
}
|
|
247
|
+
return b;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Check if a tick array is initialized using the pool's default bitmap.
|
|
251
|
+
* The default bitmap covers tick arrays within ±(tickSpacing * 60 * 512) of 0.
|
|
252
|
+
*
|
|
253
|
+
* Matches reference: compressed = floor(tick / multiplier) + 512
|
|
254
|
+
* bitPos = abs(compressed)
|
|
255
|
+
* isInit = bitmap.testn(bitPos)
|
|
256
|
+
*/
|
|
257
|
+
function checkTickArrayIsInitialized(bitmap, tick, tickSpacing) {
|
|
258
|
+
const multiplier = tickSpacing * TICK_ARRAY_SIZE;
|
|
259
|
+
const compressed = Math.floor(tick / multiplier) + 512;
|
|
260
|
+
const bitPos = Math.abs(compressed);
|
|
261
|
+
return {
|
|
262
|
+
isInitialized: bitmap.testn(bitPos),
|
|
263
|
+
startIndex: (bitPos - 512) * multiplier,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Check if tick indices overflow the default bitmap range.
|
|
268
|
+
* If so, the extension bitmap must be used.
|
|
269
|
+
*/
|
|
270
|
+
function isOverflowDefaultTickarrayBitmap(tickSpacing, tickIndices) {
|
|
271
|
+
const ticksInOneBitmap = tickSpacing * TICK_ARRAY_SIZE * TICK_ARRAY_BITMAP_SIZE;
|
|
272
|
+
const maxTickBoundary = ticksInOneBitmap;
|
|
273
|
+
const minTickBoundary = -maxTickBoundary;
|
|
274
|
+
for (const tickIndex of tickIndices) {
|
|
275
|
+
const tickArrayStartIndex = getTickArrayStartIndexForTick(tickIndex, tickSpacing);
|
|
276
|
+
if (tickArrayStartIndex >= maxTickBoundary || tickArrayStartIndex < minTickBoundary) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get the offset index into the extension bitmap array for a given tick index.
|
|
284
|
+
*/
|
|
285
|
+
function getBitmapOffset(tickIndex, tickSpacing) {
|
|
286
|
+
const ticksInOneBitmap = tickSpacing * TICK_ARRAY_SIZE * TICK_ARRAY_BITMAP_SIZE;
|
|
287
|
+
let offset = Math.floor(Math.abs(tickIndex) / ticksInOneBitmap) - 1;
|
|
288
|
+
if (tickIndex < 0 && Math.abs(tickIndex) % ticksInOneBitmap === 0)
|
|
289
|
+
offset--;
|
|
290
|
+
return offset;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Check if a tick array is initialized using the extension bitmap.
|
|
294
|
+
* Used for tick arrays outside the default bitmap range.
|
|
295
|
+
*/
|
|
296
|
+
function checkTickArrayIsInitInExtension(tickArrayStartIndex, tickSpacing, exBitmapInfo) {
|
|
297
|
+
const offset = getBitmapOffset(tickArrayStartIndex, tickSpacing);
|
|
298
|
+
const tickarrayBitmap = tickArrayStartIndex < 0
|
|
299
|
+
? exBitmapInfo.negativeTickArrayBitmap[offset]
|
|
300
|
+
: exBitmapInfo.positiveTickArrayBitmap[offset];
|
|
301
|
+
if (!tickarrayBitmap) {
|
|
302
|
+
return { isInitialized: false, startIndex: tickArrayStartIndex };
|
|
303
|
+
}
|
|
304
|
+
const ticksInOneBitmap = tickSpacing * TICK_ARRAY_SIZE * TICK_ARRAY_BITMAP_SIZE;
|
|
305
|
+
const tickArrayOffsetInBitmap = Math.floor((Math.abs(tickArrayStartIndex) % ticksInOneBitmap) / (tickSpacing * TICK_ARRAY_SIZE));
|
|
306
|
+
const merged = mergeTickArrayBitmapToSingleBN(tickarrayBitmap);
|
|
307
|
+
return {
|
|
308
|
+
isInitialized: merged.testn(tickArrayOffsetInBitmap),
|
|
309
|
+
startIndex: tickArrayStartIndex,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Check if a specific tick array start index is initialized, using
|
|
314
|
+
* either the default bitmap or extension bitmap depending on range.
|
|
315
|
+
*/
|
|
316
|
+
function isTickArrayInitialized(tickArrayStartIndex, tickSpacing, poolBitmap, exBitmapInfo) {
|
|
317
|
+
const isOverflow = isOverflowDefaultTickarrayBitmap(tickSpacing, [tickArrayStartIndex]);
|
|
318
|
+
if (isOverflow) {
|
|
319
|
+
const result = checkTickArrayIsInitInExtension(tickArrayStartIndex, tickSpacing, exBitmapInfo);
|
|
320
|
+
return result.isInitialized;
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
const merged = mergeTickArrayBitmapToSingleBN(poolBitmap);
|
|
324
|
+
const result = checkTickArrayIsInitialized(merged, tickArrayStartIndex, tickSpacing);
|
|
325
|
+
return result.isInitialized;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Find the first initialized tick array, starting from the current tick's array.
|
|
330
|
+
*
|
|
331
|
+
* Algorithm (matches reference tick-array-sdk.ts):
|
|
332
|
+
* 1. Check if the current tick's tick array is initialized in the bitmap.
|
|
333
|
+
* 2. If yes, verify it exists on-chain and return it.
|
|
334
|
+
* 3. If no, walk in the swap direction to find the next initialized one.
|
|
335
|
+
*/
|
|
336
|
+
async function findFirstInitializedTickArrayFromBitmap(connection, programId, poolId, poolState, zeroForOne) {
|
|
337
|
+
const currentStartIndex = getTickArrayStartIndexForTick(poolState.tickCurrent, poolState.tickSpacing);
|
|
338
|
+
// Step 1: Check if the current tick's array is initialized
|
|
339
|
+
const isOverflow = isOverflowDefaultTickarrayBitmap(poolState.tickSpacing, [poolState.tickCurrent]);
|
|
340
|
+
let isInit = false;
|
|
341
|
+
let startIndex = currentStartIndex;
|
|
342
|
+
if (isOverflow) {
|
|
343
|
+
const result = checkTickArrayIsInitInExtension(currentStartIndex, poolState.tickSpacing, poolState.exBitmapInfo);
|
|
344
|
+
isInit = result.isInitialized;
|
|
345
|
+
startIndex = result.startIndex;
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
const merged = mergeTickArrayBitmapToSingleBN(poolState.tickArrayBitmap);
|
|
349
|
+
const result = checkTickArrayIsInitialized(merged, poolState.tickCurrent, poolState.tickSpacing);
|
|
350
|
+
isInit = result.isInitialized;
|
|
351
|
+
startIndex = result.startIndex;
|
|
352
|
+
}
|
|
353
|
+
if (isInit) {
|
|
354
|
+
// Verify on-chain before returning
|
|
355
|
+
const tickArrayAddr = deriveTickArray(programId, poolId, startIndex);
|
|
356
|
+
const info = await connection.getAccountInfo(tickArrayAddr);
|
|
357
|
+
if (info && info.owner.equals(programId)) {
|
|
358
|
+
return {
|
|
359
|
+
isExist: true,
|
|
360
|
+
startIndex,
|
|
361
|
+
nextAccountMeta: tickArrayAddr,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// Step 2: Walk in the swap direction to find the next initialized tick array
|
|
366
|
+
const ticksPerArray = poolState.tickSpacing * TICK_ARRAY_SIZE;
|
|
367
|
+
const maxSearchDistance = 20;
|
|
368
|
+
let searchIndex = currentStartIndex;
|
|
369
|
+
for (let i = 0; i < maxSearchDistance; i++) {
|
|
370
|
+
searchIndex = zeroForOne
|
|
371
|
+
? searchIndex - ticksPerArray
|
|
372
|
+
: searchIndex + ticksPerArray;
|
|
373
|
+
if (searchIndex < MIN_TICK || searchIndex > MAX_TICK)
|
|
374
|
+
break;
|
|
375
|
+
const initCheck = isTickArrayInitialized(searchIndex, poolState.tickSpacing, poolState.tickArrayBitmap, poolState.exBitmapInfo);
|
|
376
|
+
if (initCheck) {
|
|
377
|
+
// Verify on-chain
|
|
378
|
+
const tickArrayAddr = deriveTickArray(programId, poolId, searchIndex);
|
|
379
|
+
const info = await connection.getAccountInfo(tickArrayAddr);
|
|
380
|
+
if (info && info.owner.equals(programId)) {
|
|
381
|
+
return {
|
|
382
|
+
isExist: true,
|
|
383
|
+
startIndex: searchIndex,
|
|
384
|
+
nextAccountMeta: tickArrayAddr,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return { isExist: false, startIndex: currentStartIndex };
|
|
390
|
+
}
|
|
391
|
+
// Brute-force fallback: check account existence directly
|
|
392
|
+
async function findFirstInitializedTickArrayBruteForce(connection, programId, poolId, tickCurrent, tickSpacing, zeroForOne) {
|
|
393
|
+
const currentStartIndex = getTickArrayStartIndexForTick(tickCurrent, tickSpacing);
|
|
394
|
+
const maxSearch = 10;
|
|
395
|
+
// Search in primary direction
|
|
396
|
+
let searchIndex = currentStartIndex;
|
|
397
|
+
for (let i = 0; i < maxSearch; i++) {
|
|
398
|
+
const addr = deriveTickArray(programId, poolId, searchIndex);
|
|
399
|
+
const info = await connection.getAccountInfo(addr);
|
|
400
|
+
if (info && info.owner.equals(programId)) {
|
|
401
|
+
return { isExist: true, startIndex: searchIndex, nextAccountMeta: addr };
|
|
402
|
+
}
|
|
403
|
+
searchIndex = getNextTickArrayStartIndex(searchIndex, tickSpacing, zeroForOne);
|
|
404
|
+
}
|
|
405
|
+
// Search in opposite direction
|
|
406
|
+
searchIndex = getNextTickArrayStartIndex(currentStartIndex, tickSpacing, !zeroForOne);
|
|
407
|
+
for (let i = 0; i < maxSearch; i++) {
|
|
408
|
+
const addr = deriveTickArray(programId, poolId, searchIndex);
|
|
409
|
+
const info = await connection.getAccountInfo(addr);
|
|
410
|
+
if (info && info.owner.equals(programId)) {
|
|
411
|
+
return { isExist: true, startIndex: searchIndex, nextAccountMeta: addr };
|
|
412
|
+
}
|
|
413
|
+
searchIndex = getNextTickArrayStartIndex(searchIndex, tickSpacing, !zeroForOne);
|
|
414
|
+
}
|
|
415
|
+
return { isExist: false, startIndex: currentStartIndex };
|
|
416
|
+
}
|
|
417
|
+
async function deriveClmmPoolAccounts(connection, poolId, programId) {
|
|
418
|
+
const poolState = await fetchClmmPoolState(connection, poolId, programId);
|
|
419
|
+
return {
|
|
420
|
+
poolId,
|
|
421
|
+
ammConfig: poolState.ammConfig,
|
|
422
|
+
poolState: poolId,
|
|
423
|
+
observationState: poolState.observationKey,
|
|
424
|
+
tokenVault0: poolState.tokenVault0,
|
|
425
|
+
tokenVault1: poolState.tokenVault1,
|
|
426
|
+
tokenMint0: poolState.tokenMint0,
|
|
427
|
+
tokenMint1: poolState.tokenMint1,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
// ---------------------------------------------------------------------------
|
|
431
|
+
// Swap IX builder
|
|
432
|
+
// ---------------------------------------------------------------------------
|
|
433
|
+
/**
|
|
434
|
+
* Create a CLMM swap_v2 instruction.
|
|
435
|
+
*
|
|
436
|
+
* @param zeroForOne - Swap direction: true = token0→token1, false = token1→token0.
|
|
437
|
+
* Determines which vault is input/output.
|
|
438
|
+
* @param isExactInput - true = `amount` is the exact input (typical case).
|
|
439
|
+
* false = `amount` is the exact output desired.
|
|
440
|
+
* For isExactInput=true, otherAmountThreshold = min output (0 = unlimited).
|
|
441
|
+
* For isExactInput=false, otherAmountThreshold = max input (u64::MAX = unlimited).
|
|
442
|
+
*/
|
|
443
|
+
function createClmmSwapIx(programId, poolAccounts, payer, inputTokenAccount, outputTokenAccount, amount, otherAmountThreshold, sqrtPriceLimitX64, zeroForOne, isExactInput, tickArrays, inputVaultMint, outputVaultMint, tickArrayBitmapExtension) {
|
|
444
|
+
const inputVault = zeroForOne ? poolAccounts.tokenVault0 : poolAccounts.tokenVault1;
|
|
445
|
+
const outputVault = zeroForOne ? poolAccounts.tokenVault1 : poolAccounts.tokenVault0;
|
|
446
|
+
const accounts = [
|
|
447
|
+
{ pubkey: payer, isSigner: true, isWritable: true },
|
|
448
|
+
{ pubkey: poolAccounts.ammConfig, isSigner: false, isWritable: false },
|
|
449
|
+
{ pubkey: poolAccounts.poolState, isSigner: false, isWritable: true },
|
|
450
|
+
{ pubkey: inputTokenAccount, isSigner: false, isWritable: true },
|
|
451
|
+
{ pubkey: outputTokenAccount, isSigner: false, isWritable: true },
|
|
452
|
+
{ pubkey: inputVault, isSigner: false, isWritable: true },
|
|
453
|
+
{ pubkey: outputVault, isSigner: false, isWritable: true },
|
|
454
|
+
{ pubkey: poolAccounts.observationState, isSigner: false, isWritable: true },
|
|
455
|
+
{ pubkey: spl_token_1.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
456
|
+
{ pubkey: spl_token_1.TOKEN_2022_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
457
|
+
{ pubkey: MEMO_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
458
|
+
{ pubkey: inputVaultMint, isSigner: false, isWritable: false },
|
|
459
|
+
{ pubkey: outputVaultMint, isSigner: false, isWritable: false },
|
|
460
|
+
];
|
|
461
|
+
// Remaining accounts: bitmap extension (only if exists on-chain) + tick arrays
|
|
462
|
+
const remainingAccounts = [];
|
|
463
|
+
if (tickArrayBitmapExtension) {
|
|
464
|
+
remainingAccounts.push({
|
|
465
|
+
pubkey: tickArrayBitmapExtension,
|
|
466
|
+
isSigner: false,
|
|
467
|
+
isWritable: true,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
for (const ta of tickArrays) {
|
|
471
|
+
remainingAccounts.push({
|
|
472
|
+
pubkey: ta,
|
|
473
|
+
isSigner: false,
|
|
474
|
+
isWritable: true,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
// Build instruction data (41 bytes)
|
|
478
|
+
const data = Buffer.alloc(41);
|
|
479
|
+
let off = 0;
|
|
480
|
+
SWAP_V2_DISCRIMINATOR.copy(data, off);
|
|
481
|
+
off += 8;
|
|
482
|
+
data.writeBigUInt64LE(amount, off);
|
|
483
|
+
off += 8;
|
|
484
|
+
data.writeBigUInt64LE(otherAmountThreshold, off);
|
|
485
|
+
off += 8;
|
|
486
|
+
const sqrtPriceBytes = sqrtPriceLimitX64.toArrayLike(Buffer, "le", 16);
|
|
487
|
+
sqrtPriceBytes.copy(data, off);
|
|
488
|
+
off += 16;
|
|
489
|
+
data.writeUInt8(isExactInput ? 1 : 0, off);
|
|
490
|
+
return new web3_js_1.TransactionInstruction({
|
|
491
|
+
keys: [...accounts, ...remainingAccounts],
|
|
492
|
+
programId,
|
|
493
|
+
data,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
// ---------------------------------------------------------------------------
|
|
497
|
+
// Token program detection
|
|
498
|
+
// ---------------------------------------------------------------------------
|
|
499
|
+
async function getTokenProgramForMint(connection, mint) {
|
|
500
|
+
const info = await connection.getAccountInfo(mint);
|
|
501
|
+
if (!info)
|
|
502
|
+
throw new Error(`Mint account not found: ${mint.toBase58()}`);
|
|
503
|
+
return info.owner.equals(spl_token_1.TOKEN_2022_PROGRAM_ID)
|
|
504
|
+
? spl_token_1.TOKEN_2022_PROGRAM_ID
|
|
505
|
+
: spl_token_1.TOKEN_PROGRAM_ID;
|
|
506
|
+
}
|
|
507
|
+
// ---------------------------------------------------------------------------
|
|
508
|
+
// Price calculation
|
|
509
|
+
// ---------------------------------------------------------------------------
|
|
510
|
+
function sqrtPriceX64ToPrice(sqrtPriceX64, decimalsA, decimalsB) {
|
|
511
|
+
return raydium_sdk_v2_1.MathUtil.x64ToDecimal(sqrtPriceX64)
|
|
512
|
+
.pow(2)
|
|
513
|
+
.mul(decimal_js_1.default.pow(10, decimalsA - decimalsB));
|
|
514
|
+
}
|
|
515
|
+
// ---------------------------------------------------------------------------
|
|
516
|
+
// Helpers
|
|
517
|
+
// ---------------------------------------------------------------------------
|
|
518
|
+
/**
|
|
519
|
+
* Compute the sqrt price limit for a swap.
|
|
520
|
+
* zeroForOne=true (price decreasing): limit = MIN_SQRT_PRICE_X64 + 1
|
|
521
|
+
* zeroForOne=false (price increasing): limit = MAX_SQRT_PRICE_X64 - 1
|
|
522
|
+
*/
|
|
523
|
+
function computeSqrtPriceLimit(currentSqrtPrice, zeroForOne) {
|
|
524
|
+
if (zeroForOne) {
|
|
525
|
+
const minPlusOne = MIN_SQRT_PRICE_X64.add(new bn_js_1.default(1));
|
|
526
|
+
if (minPlusOne.lt(currentSqrtPrice)) {
|
|
527
|
+
return minPlusOne;
|
|
528
|
+
}
|
|
529
|
+
const fallback = currentSqrtPrice.sub(new bn_js_1.default(1));
|
|
530
|
+
if (fallback.lte(MIN_SQRT_PRICE_X64)) {
|
|
531
|
+
return MIN_SQRT_PRICE_X64.add(new bn_js_1.default(1));
|
|
532
|
+
}
|
|
533
|
+
return fallback;
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
const maxMinusOne = MAX_SQRT_PRICE_X64.sub(new bn_js_1.default(1));
|
|
537
|
+
if (maxMinusOne.gt(currentSqrtPrice)) {
|
|
538
|
+
return maxMinusOne;
|
|
539
|
+
}
|
|
540
|
+
const fallback = currentSqrtPrice.add(new bn_js_1.default(1));
|
|
541
|
+
if (fallback.gte(MAX_SQRT_PRICE_X64)) {
|
|
542
|
+
return MAX_SQRT_PRICE_X64.sub(new bn_js_1.default(1));
|
|
543
|
+
}
|
|
544
|
+
return fallback;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function quoteDecimals(quoteMintStr) {
|
|
548
|
+
return SIX_DECIMAL_MINTS.has(quoteMintStr) ? 6 : 9;
|
|
549
|
+
}
|
|
550
|
+
function amountToSmallestUnit(amount, quoteMintStr) {
|
|
551
|
+
const decimals = quoteDecimals(quoteMintStr);
|
|
552
|
+
return BigInt(Math.floor(amount * Math.pow(10, decimals)));
|
|
553
|
+
}
|
|
554
|
+
class ClmmBaseAdapter {
|
|
555
|
+
name;
|
|
556
|
+
protocol;
|
|
557
|
+
capabilities = (0, types_1.defaultCapabilities)({
|
|
558
|
+
canBuy: true,
|
|
559
|
+
canSell: true,
|
|
560
|
+
canSnipe: true,
|
|
561
|
+
canFindPool: false, // Requires off-chain indexing or Raydium API
|
|
562
|
+
canGetPrice: true,
|
|
563
|
+
});
|
|
564
|
+
programId;
|
|
565
|
+
constructor(config) {
|
|
566
|
+
this.name = config.name;
|
|
567
|
+
this.protocol = config.protocol;
|
|
568
|
+
this.programId = config.programId;
|
|
569
|
+
}
|
|
570
|
+
// ---- Core: buy ----
|
|
571
|
+
async buy(params) {
|
|
572
|
+
const tokenMint = (0, types_1.requireTokenMint)(params, this.name);
|
|
573
|
+
const { amountSol, quoteMint: quoteMintParam, poolAddress, opts } = params;
|
|
574
|
+
const connection = (0, config_1.getConnection)();
|
|
575
|
+
const wallet = (0, config_1.getWallet)();
|
|
576
|
+
const quoteMintStr = quoteMintParam ?? types_1.WSOL_MINT;
|
|
577
|
+
if (!poolAddress) {
|
|
578
|
+
throw new Error(`${this.name}: poolAddress is required (auto-discovery not yet supported)`);
|
|
579
|
+
}
|
|
580
|
+
const allIxs = await this.buildFullSwapTx(tokenMint, amountSol, quoteMintStr, poolAddress, wallet, opts);
|
|
581
|
+
// Submit via RPC send+confirm
|
|
582
|
+
const result = await (0, send_rpc_1.sendAndConfirmVtx)(connection, allIxs, wallet);
|
|
583
|
+
return {
|
|
584
|
+
txSignature: result.txSignature,
|
|
585
|
+
confirmed: result.confirmed,
|
|
586
|
+
amountIn: amountSol,
|
|
587
|
+
amountInToken: quoteMintStr,
|
|
588
|
+
dex: this.name,
|
|
589
|
+
poolAddress,
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
// ---- Core: sell ----
|
|
593
|
+
async sell(params) {
|
|
594
|
+
const tokenMint = (0, types_1.requireTokenMint)(params, this.name);
|
|
595
|
+
const { percentage, quoteMint: quoteMintParam, poolAddress, opts } = params;
|
|
596
|
+
const connection = (0, config_1.getConnection)();
|
|
597
|
+
const wallet = (0, config_1.getWallet)();
|
|
598
|
+
const quoteMintStr = quoteMintParam ?? types_1.WSOL_MINT;
|
|
599
|
+
if (!poolAddress) {
|
|
600
|
+
throw new Error(`${this.name}: poolAddress is required (auto-discovery not yet supported)`);
|
|
601
|
+
}
|
|
602
|
+
// 1. Get balance of the token being sold
|
|
603
|
+
const baseMintPk = new web3_js_1.PublicKey(tokenMint);
|
|
604
|
+
const baseMintTokenProgram = await getTokenProgramForMint(connection, baseMintPk);
|
|
605
|
+
const baseAta = await (0, spl_token_1.getAssociatedTokenAddress)(baseMintPk, wallet.publicKey, false, baseMintTokenProgram);
|
|
606
|
+
let balance;
|
|
607
|
+
try {
|
|
608
|
+
const res = await connection.getTokenAccountBalance(baseAta);
|
|
609
|
+
balance = { amount: BigInt(res.value.amount), decimals: res.value.decimals };
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
balance = { amount: 0n, decimals: 0 };
|
|
613
|
+
}
|
|
614
|
+
// 2. Calculate sell amount from percentage
|
|
615
|
+
const sellAmount = (balance.amount * BigInt(Math.floor(percentage))) / 100n;
|
|
616
|
+
if (sellAmount === 0n) {
|
|
617
|
+
return {
|
|
618
|
+
txSignature: "",
|
|
619
|
+
confirmed: false,
|
|
620
|
+
amountIn: 0,
|
|
621
|
+
amountInToken: tokenMint,
|
|
622
|
+
dex: this.name,
|
|
623
|
+
poolAddress,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
// 3. Build swap IXs with reversed direction: tokenMint is now the input
|
|
627
|
+
const allIxs = await this.buildFullSellTx(tokenMint, sellAmount, quoteMintStr, poolAddress, wallet, opts);
|
|
628
|
+
// 4. Submit via RPC send+confirm
|
|
629
|
+
const result = await (0, send_rpc_1.sendAndConfirmVtx)(connection, allIxs, wallet);
|
|
630
|
+
const humanSellAmount = Number(sellAmount) / 10 ** balance.decimals;
|
|
631
|
+
return {
|
|
632
|
+
txSignature: result.txSignature,
|
|
633
|
+
confirmed: result.confirmed,
|
|
634
|
+
amountIn: humanSellAmount,
|
|
635
|
+
amountInToken: tokenMint,
|
|
636
|
+
dex: this.name,
|
|
637
|
+
poolAddress,
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
// ---- Snipe ----
|
|
641
|
+
async snipe(params) {
|
|
642
|
+
const { tokenMint, amountSol, poolAddress, quoteMint: quoteMintParam, tipSol, opts } = params;
|
|
643
|
+
const connection = (0, config_1.getConnection)();
|
|
644
|
+
const wallet = (0, config_1.getWallet)();
|
|
645
|
+
const quoteMintStr = quoteMintParam ?? types_1.WSOL_MINT;
|
|
646
|
+
const allIxs = await this.buildFullSwapTx(tokenMint, amountSol, quoteMintStr, poolAddress, wallet, opts);
|
|
647
|
+
const blockhash = await connection.getLatestBlockhash();
|
|
648
|
+
const results = await (0, landing_1.landTransaction)(allIxs, wallet, blockhash, {
|
|
649
|
+
dex: this.name,
|
|
650
|
+
operation: "snipe",
|
|
651
|
+
tipSol,
|
|
652
|
+
});
|
|
653
|
+
const accepted = results.find((r) => r.accepted);
|
|
654
|
+
return {
|
|
655
|
+
txSignature: accepted?.signature ?? "",
|
|
656
|
+
confirmed: !!accepted,
|
|
657
|
+
amountIn: amountSol,
|
|
658
|
+
amountInToken: quoteMintStr,
|
|
659
|
+
dex: this.name,
|
|
660
|
+
poolAddress,
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
// ---- buildSwapIxs ----
|
|
664
|
+
async buildSwapIxs(params) {
|
|
665
|
+
const tokenMint = (0, types_1.requireTokenMint)(params, this.name);
|
|
666
|
+
if ("percentage" in params) {
|
|
667
|
+
// Sell path
|
|
668
|
+
const { percentage, quoteMint: quoteMintParam, poolAddress } = params;
|
|
669
|
+
const quoteMintStr = quoteMintParam ?? types_1.WSOL_MINT;
|
|
670
|
+
if (!poolAddress) {
|
|
671
|
+
throw new Error(`${this.name}: poolAddress is required for buildSwapIxs`);
|
|
672
|
+
}
|
|
673
|
+
const connection = (0, config_1.getConnection)();
|
|
674
|
+
const wallet = (0, config_1.getWallet)();
|
|
675
|
+
const baseMintPk = new web3_js_1.PublicKey(tokenMint);
|
|
676
|
+
const baseMintTokenProgram = await getTokenProgramForMint(connection, baseMintPk);
|
|
677
|
+
const baseAta = await (0, spl_token_1.getAssociatedTokenAddress)(baseMintPk, wallet.publicKey, false, baseMintTokenProgram);
|
|
678
|
+
let balance;
|
|
679
|
+
try {
|
|
680
|
+
const res = await connection.getTokenAccountBalance(baseAta);
|
|
681
|
+
balance = { amount: BigInt(res.value.amount), decimals: res.value.decimals };
|
|
682
|
+
}
|
|
683
|
+
catch {
|
|
684
|
+
balance = { amount: 0n, decimals: 0 };
|
|
685
|
+
}
|
|
686
|
+
const sellAmount = (balance.amount * BigInt(Math.floor(percentage))) / 100n;
|
|
687
|
+
const sellAmountHuman = Number(sellAmount) / 10 ** balance.decimals;
|
|
688
|
+
// For sell: tokenMint is input, quoteMint is output
|
|
689
|
+
// Pass tokenMint as "quoteMint" (input) and quoteMintStr as "tokenMint" (output)
|
|
690
|
+
return this.doBuildSwapIxs(quoteMintStr, sellAmountHuman, tokenMint, poolAddress);
|
|
691
|
+
}
|
|
692
|
+
const { amountSol, quoteMint: quoteMintParam, poolAddress, opts } = params;
|
|
693
|
+
const quoteMintStr = quoteMintParam ?? types_1.WSOL_MINT;
|
|
694
|
+
if (!poolAddress) {
|
|
695
|
+
throw new Error(`${this.name}: poolAddress is required for buildSwapIxs`);
|
|
696
|
+
}
|
|
697
|
+
return this.doBuildSwapIxs(tokenMint, amountSol, quoteMintStr, poolAddress);
|
|
698
|
+
}
|
|
699
|
+
// ---- findPool (stub) ----
|
|
700
|
+
async findPool(baseMint, _quoteMint) {
|
|
701
|
+
// CLMM pool discovery requires off-chain indexing or Raydium API.
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
// ---- getPrice ----
|
|
705
|
+
async getPrice(poolAddress) {
|
|
706
|
+
const connection = (0, config_1.getConnection)();
|
|
707
|
+
const poolId = new web3_js_1.PublicKey(poolAddress);
|
|
708
|
+
const poolState = await fetchClmmPoolState(connection, poolId, this.programId);
|
|
709
|
+
const price = sqrtPriceX64ToPrice(poolState.sqrtPriceX64, poolState.mintDecimals0, poolState.mintDecimals1);
|
|
710
|
+
let realPrice = Number(price.toString());
|
|
711
|
+
if (realPrice > 1)
|
|
712
|
+
realPrice = 1 / realPrice;
|
|
713
|
+
return {
|
|
714
|
+
price: realPrice,
|
|
715
|
+
baseMint: poolState.tokenMint0.toBase58(),
|
|
716
|
+
quoteMint: poolState.tokenMint1.toBase58(),
|
|
717
|
+
source: "on-chain",
|
|
718
|
+
poolAddress,
|
|
719
|
+
timestamp: Date.now(),
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
// ---- Internal: resolve tick arrays for swap ----
|
|
723
|
+
async resolveTickArrays(connection, poolId, poolState, exBitmapInfo, zeroForOne) {
|
|
724
|
+
let firstResult = await findFirstInitializedTickArrayFromBitmap(connection, this.programId, poolId, {
|
|
725
|
+
tickCurrent: poolState.tickCurrent,
|
|
726
|
+
tickSpacing: poolState.tickSpacing,
|
|
727
|
+
tickArrayBitmap: poolState.tickArrayBitmap,
|
|
728
|
+
exBitmapInfo: {
|
|
729
|
+
positiveTickArrayBitmap: exBitmapInfo.positiveTickArrayBitmap,
|
|
730
|
+
negativeTickArrayBitmap: exBitmapInfo.negativeTickArrayBitmap,
|
|
731
|
+
},
|
|
732
|
+
}, zeroForOne);
|
|
733
|
+
// Brute-force fallback if bitmap search fails
|
|
734
|
+
if (!firstResult.isExist) {
|
|
735
|
+
firstResult = await findFirstInitializedTickArrayBruteForce(connection, this.programId, poolId, poolState.tickCurrent, poolState.tickSpacing, zeroForOne);
|
|
736
|
+
}
|
|
737
|
+
if (!firstResult.isExist || !firstResult.nextAccountMeta) {
|
|
738
|
+
throw new Error(`No initialized tick array found for pool ${poolId.toBase58()}. ` +
|
|
739
|
+
`Current tick: ${poolState.tickCurrent}, Tick spacing: ${poolState.tickSpacing}`);
|
|
740
|
+
}
|
|
741
|
+
// Build tick array list (up to 4)
|
|
742
|
+
const tickArrays = [firstResult.nextAccountMeta];
|
|
743
|
+
let currentStartIndex = firstResult.startIndex;
|
|
744
|
+
for (let i = 1; i < 4; i++) {
|
|
745
|
+
currentStartIndex = getNextTickArrayStartIndex(currentStartIndex, poolState.tickSpacing, zeroForOne);
|
|
746
|
+
const addr = deriveTickArray(this.programId, poolId, currentStartIndex);
|
|
747
|
+
const info = await connection.getAccountInfo(addr);
|
|
748
|
+
if (info && info.owner.equals(this.programId)) {
|
|
749
|
+
tickArrays.push(addr);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return { tickArrays, firstResult };
|
|
756
|
+
}
|
|
757
|
+
// ---- Internal: build raw swap instructions ----
|
|
758
|
+
async doBuildSwapIxs(tokenMint, amountSol, quoteMintStr, poolAddress) {
|
|
759
|
+
const connection = (0, config_1.getConnection)();
|
|
760
|
+
const wallet = (0, config_1.getWallet)();
|
|
761
|
+
const poolId = new web3_js_1.PublicKey(poolAddress);
|
|
762
|
+
const baseMint = new web3_js_1.PublicKey(tokenMint);
|
|
763
|
+
const quoteMint = new web3_js_1.PublicKey(quoteMintStr);
|
|
764
|
+
// 1. Derive pool accounts
|
|
765
|
+
const poolAccounts = await deriveClmmPoolAccounts(connection, poolId, this.programId);
|
|
766
|
+
// 2. Fetch pool state
|
|
767
|
+
const poolState = await fetchClmmPoolState(connection, poolId, this.programId);
|
|
768
|
+
// 3. Determine swap direction
|
|
769
|
+
// inputMint is what we're spending (quoteMint), outputMint is what we're receiving (baseMint)
|
|
770
|
+
const inputMint = quoteMint;
|
|
771
|
+
const outputMint = baseMint;
|
|
772
|
+
// zeroForOne = true means token0→token1 (price decreasing)
|
|
773
|
+
// zeroForOne = false means token1→token0 (price increasing)
|
|
774
|
+
const zeroForOne = poolState.tokenMint0.equals(inputMint);
|
|
775
|
+
// 4. Calculate amount
|
|
776
|
+
const amountIn = amountToSmallestUnit(amountSol, quoteMintStr);
|
|
777
|
+
// 5. Sqrt price limit
|
|
778
|
+
const sqrtPriceLimitX64 = computeSqrtPriceLimit(poolState.sqrtPriceX64, zeroForOne);
|
|
779
|
+
// 6. Get bitmap extension
|
|
780
|
+
const exBitmapInfo = await getTickArrayBitmapExtension(this.programId, poolId, connection);
|
|
781
|
+
// 7. Find initialized tick arrays
|
|
782
|
+
const { tickArrays } = await this.resolveTickArrays(connection, poolId, poolState, exBitmapInfo, zeroForOne);
|
|
783
|
+
// 8. Get user ATAs
|
|
784
|
+
const inputMintTokenProgram = await getTokenProgramForMint(connection, inputMint);
|
|
785
|
+
const outputMintTokenProgram = await getTokenProgramForMint(connection, outputMint);
|
|
786
|
+
const inputAta = await (0, spl_token_1.getAssociatedTokenAddress)(inputMint, wallet.publicKey, false, inputMintTokenProgram);
|
|
787
|
+
const outputAta = await (0, spl_token_1.getAssociatedTokenAddress)(outputMint, wallet.publicKey, false, outputMintTokenProgram);
|
|
788
|
+
// 9. Build swap IX (always exact-input mode: is_base_input=true, threshold=0)
|
|
789
|
+
const inputVaultMint = zeroForOne ? poolState.tokenMint0 : poolState.tokenMint1;
|
|
790
|
+
const outputVaultMint = zeroForOne ? poolState.tokenMint1 : poolState.tokenMint0;
|
|
791
|
+
const swapIx = createClmmSwapIx(this.programId, poolAccounts, wallet.publicKey, inputAta, outputAta, amountIn, BigInt(0), // min output = 0 (unlimited slippage)
|
|
792
|
+
sqrtPriceLimitX64, zeroForOne, true, // isExactInput = true (amount is the input amount)
|
|
793
|
+
tickArrays, inputVaultMint, outputVaultMint, exBitmapInfo.exists ? exBitmapInfo.exBitmapAddress : null);
|
|
794
|
+
// 10. Create ATA instructions
|
|
795
|
+
const createInputAtaIx = (0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(wallet.publicKey, inputAta, wallet.publicKey, inputMint, inputMintTokenProgram);
|
|
796
|
+
const createOutputAtaIx = (0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(wallet.publicKey, outputAta, wallet.publicKey, outputMint, outputMintTokenProgram);
|
|
797
|
+
return {
|
|
798
|
+
instructions: [createInputAtaIx, createOutputAtaIx, swapIx],
|
|
799
|
+
signers: [],
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
// ---- Internal: full transaction with WSOL wrapping + compute budget ----
|
|
803
|
+
async buildFullSwapTx(tokenMint, amountSol, quoteMintStr, poolAddress, wallet, opts) {
|
|
804
|
+
const { instructions: swapIxs } = await this.doBuildSwapIxs(tokenMint, amountSol, quoteMintStr, poolAddress);
|
|
805
|
+
const cuPrice = opts?.priorityFeeMicroLamports ?? types_1.DEFAULT_PRIORITY_FEE_MICRO_LAMPORTS;
|
|
806
|
+
const cuLimit = opts?.computeUnitLimit ?? types_1.DEFAULT_COMPUTE_UNIT_LIMIT;
|
|
807
|
+
const isWSol = quoteMintStr === types_1.WSOL_MINT;
|
|
808
|
+
const preIxs = [
|
|
809
|
+
web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: cuLimit }),
|
|
810
|
+
web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: cuPrice }),
|
|
811
|
+
];
|
|
812
|
+
if (isWSol) {
|
|
813
|
+
const quoteMintPk = new web3_js_1.PublicKey(quoteMintStr);
|
|
814
|
+
const inputAta = await (0, spl_token_1.getAssociatedTokenAddress)(quoteMintPk, wallet.publicKey, false, spl_token_1.TOKEN_PROGRAM_ID);
|
|
815
|
+
const amountLamports = Math.floor(amountSol * web3_js_1.LAMPORTS_PER_SOL);
|
|
816
|
+
// swapIxs = [createInputATA, createOutputATA, swapIx]
|
|
817
|
+
// Insert ATA creates first, then transfer+sync, then swap
|
|
818
|
+
const createAtaIxs = swapIxs.slice(0, -1); // ATA creation instructions
|
|
819
|
+
const swapOnlyIx = swapIxs[swapIxs.length - 1]; // the actual swap
|
|
820
|
+
const closeIx = (0, spl_token_1.createCloseAccountInstruction)(inputAta, wallet.publicKey, wallet.publicKey, [], spl_token_1.TOKEN_PROGRAM_ID);
|
|
821
|
+
return [
|
|
822
|
+
...preIxs,
|
|
823
|
+
...createAtaIxs,
|
|
824
|
+
web3_js_1.SystemProgram.transfer({
|
|
825
|
+
fromPubkey: wallet.publicKey,
|
|
826
|
+
toPubkey: inputAta,
|
|
827
|
+
lamports: amountLamports,
|
|
828
|
+
}),
|
|
829
|
+
(0, spl_token_1.createSyncNativeInstruction)(inputAta, spl_token_1.TOKEN_PROGRAM_ID),
|
|
830
|
+
swapOnlyIx,
|
|
831
|
+
closeIx,
|
|
832
|
+
];
|
|
833
|
+
}
|
|
834
|
+
return [...preIxs, ...swapIxs];
|
|
835
|
+
}
|
|
836
|
+
// ---- Internal: full sell transaction with WSOL output handling + compute budget ----
|
|
837
|
+
async buildFullSellTx(tokenMint, sellAmount, quoteMintStr, poolAddress, wallet, opts) {
|
|
838
|
+
// For sell, the input is tokenMint and the output is quoteMint.
|
|
839
|
+
// We build swap IXs directly using the raw sell amount (bigint) to avoid
|
|
840
|
+
// decimal rounding issues with quoteDecimals().
|
|
841
|
+
const connection = (0, config_1.getConnection)();
|
|
842
|
+
const poolId = new web3_js_1.PublicKey(poolAddress);
|
|
843
|
+
const baseMint = new web3_js_1.PublicKey(tokenMint);
|
|
844
|
+
const quoteMint = new web3_js_1.PublicKey(quoteMintStr);
|
|
845
|
+
// 1. Derive pool accounts
|
|
846
|
+
const poolAccounts = await deriveClmmPoolAccounts(connection, poolId, this.programId);
|
|
847
|
+
// 2. Fetch pool state
|
|
848
|
+
const poolState = await fetchClmmPoolState(connection, poolId, this.programId);
|
|
849
|
+
// 3. For sell: input is baseMint (token being sold), output is quoteMint
|
|
850
|
+
const inputMint = baseMint;
|
|
851
|
+
const outputMint = quoteMint;
|
|
852
|
+
// zeroForOne = true means token0→token1. For sell, input is the token.
|
|
853
|
+
const zeroForOne = poolState.tokenMint0.equals(inputMint);
|
|
854
|
+
// 4. Sqrt price limit
|
|
855
|
+
const sqrtPriceLimitX64 = computeSqrtPriceLimit(poolState.sqrtPriceX64, zeroForOne);
|
|
856
|
+
// 5. Get bitmap extension
|
|
857
|
+
const exBitmapInfo = await getTickArrayBitmapExtension(this.programId, poolId, connection);
|
|
858
|
+
// 6. Find initialized tick arrays
|
|
859
|
+
const { tickArrays } = await this.resolveTickArrays(connection, poolId, poolState, exBitmapInfo, zeroForOne);
|
|
860
|
+
// 7. Get user ATAs
|
|
861
|
+
const inputMintTokenProgram = await getTokenProgramForMint(connection, inputMint);
|
|
862
|
+
const outputMintTokenProgram = await getTokenProgramForMint(connection, outputMint);
|
|
863
|
+
const inputAta = await (0, spl_token_1.getAssociatedTokenAddress)(inputMint, wallet.publicKey, false, inputMintTokenProgram);
|
|
864
|
+
const outputAta = await (0, spl_token_1.getAssociatedTokenAddress)(outputMint, wallet.publicKey, false, outputMintTokenProgram);
|
|
865
|
+
// 8. Build swap IX (always exact-input mode: is_base_input=true, threshold=0)
|
|
866
|
+
const inputVaultMint = zeroForOne ? poolState.tokenMint0 : poolState.tokenMint1;
|
|
867
|
+
const outputVaultMint = zeroForOne ? poolState.tokenMint1 : poolState.tokenMint0;
|
|
868
|
+
const swapIx = createClmmSwapIx(this.programId, poolAccounts, wallet.publicKey, inputAta, outputAta, BigInt(sellAmount.toString()), BigInt(0), // min output = 0 (unlimited slippage)
|
|
869
|
+
sqrtPriceLimitX64, zeroForOne, true, // isExactInput = true (amount is the input amount)
|
|
870
|
+
tickArrays, inputVaultMint, outputVaultMint, exBitmapInfo.exists ? exBitmapInfo.exBitmapAddress : null);
|
|
871
|
+
// 9. Compose full transaction
|
|
872
|
+
const cuPrice = opts?.priorityFeeMicroLamports ?? types_1.DEFAULT_PRIORITY_FEE_MICRO_LAMPORTS;
|
|
873
|
+
const cuLimit = opts?.computeUnitLimit ?? types_1.DEFAULT_COMPUTE_UNIT_LIMIT;
|
|
874
|
+
const isWsolOutput = quoteMintStr === types_1.WSOL_MINT;
|
|
875
|
+
const preIxs = [
|
|
876
|
+
web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: cuLimit }),
|
|
877
|
+
web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: cuPrice }),
|
|
878
|
+
];
|
|
879
|
+
// Create ATAs idempotently
|
|
880
|
+
const createInputAtaIx = (0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(wallet.publicKey, inputAta, wallet.publicKey, inputMint, inputMintTokenProgram);
|
|
881
|
+
const createOutputAtaIx = (0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(wallet.publicKey, outputAta, wallet.publicKey, outputMint, outputMintTokenProgram);
|
|
882
|
+
if (isWsolOutput) {
|
|
883
|
+
// For sell to WSOL: create output WSOL ATA, swap, then close to unwrap SOL
|
|
884
|
+
const closeIx = (0, spl_token_1.createCloseAccountInstruction)(outputAta, wallet.publicKey, wallet.publicKey, [], spl_token_1.TOKEN_PROGRAM_ID);
|
|
885
|
+
return [...preIxs, createInputAtaIx, createOutputAtaIx, swapIx, closeIx];
|
|
886
|
+
}
|
|
887
|
+
return [...preIxs, createInputAtaIx, createOutputAtaIx, swapIx];
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
exports.ClmmBaseAdapter = ClmmBaseAdapter;
|
|
891
|
+
//# sourceMappingURL=clmm-base.js.map
|