sol-trade-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +390 -0
- package/dist/chunk-MMQAMIKR.mjs +3735 -0
- package/dist/chunk-NEZDFAYA.mjs +7744 -0
- package/dist/clients-VITWK7B6.mjs +1370 -0
- package/dist/index-1BK_FXsW.d.mts +2327 -0
- package/dist/index-1BK_FXsW.d.ts +2327 -0
- package/dist/index.d.mts +2659 -0
- package/dist/index.d.ts +2659 -0
- package/dist/index.js +13265 -0
- package/dist/index.mjs +562 -0
- package/dist/perf/index.d.mts +2 -0
- package/dist/perf/index.d.ts +2 -0
- package/dist/perf/index.js +3742 -0
- package/dist/perf/index.mjs +214 -0
- package/package.json +101 -0
- package/src/__tests__/complete_sdk.test.ts +354 -0
- package/src/__tests__/hotpath.test.ts +486 -0
- package/src/__tests__/nonce.test.ts +45 -0
- package/src/__tests__/sdk.test.ts +425 -0
- package/src/address-lookup/index.ts +197 -0
- package/src/cache/cache.ts +308 -0
- package/src/calc/index.ts +1058 -0
- package/src/calc/pumpfun.ts +124 -0
- package/src/common/bonding_curve.ts +272 -0
- package/src/common/compute-budget.ts +148 -0
- package/src/common/confirm-any-signature.ts +184 -0
- package/src/common/fast-timing.ts +481 -0
- package/src/common/fast_fn.ts +150 -0
- package/src/common/gas-fee-strategy.ts +253 -0
- package/src/common/map-pool.ts +23 -0
- package/src/common/nonce.ts +40 -0
- package/src/common/sdk-log.ts +460 -0
- package/src/common/seed.ts +381 -0
- package/src/common/spl-token.ts +578 -0
- package/src/common/subscription-handle.ts +644 -0
- package/src/common/trading-utils.ts +239 -0
- package/src/common/wsol-manager.ts +325 -0
- package/src/compute/compute_budget_manager.ts +187 -0
- package/src/compute/index.ts +21 -0
- package/src/constants/index.ts +96 -0
- package/src/execution/execution.ts +532 -0
- package/src/execution/index.ts +42 -0
- package/src/hotpath/executor.ts +464 -0
- package/src/hotpath/index.ts +64 -0
- package/src/hotpath/state.ts +435 -0
- package/src/index.ts +2117 -0
- package/src/instruction/bonk_builder.ts +730 -0
- package/src/instruction/index.ts +24 -0
- package/src/instruction/meteora_damm_v2_builder.ts +509 -0
- package/src/instruction/pumpfun_builder.ts +1183 -0
- package/src/instruction/pumpswap.ts +1123 -0
- package/src/instruction/raydium_amm_v4_builder.ts +692 -0
- package/src/instruction/raydium_cpmm_builder.ts +795 -0
- package/src/middleware/traits.ts +407 -0
- package/src/params/index.ts +483 -0
- package/src/perf/compiler-optimization.ts +529 -0
- package/src/perf/hardware.ts +631 -0
- package/src/perf/index.ts +9 -0
- package/src/perf/kernel-bypass.ts +656 -0
- package/src/perf/protocol.ts +682 -0
- package/src/perf/realtime.ts +592 -0
- package/src/perf/simd.ts +668 -0
- package/src/perf/syscall-bypass.ts +331 -0
- package/src/perf/ultra-low-latency.ts +505 -0
- package/src/perf/zero-copy.ts +589 -0
- package/src/pool/pool.ts +294 -0
- package/src/rpc/client.ts +345 -0
- package/src/sdk-errors.ts +13 -0
- package/src/security/index.ts +26 -0
- package/src/security/secure-key.ts +303 -0
- package/src/security/validators.ts +281 -0
- package/src/seed/pda.ts +262 -0
- package/src/serialization/index.ts +28 -0
- package/src/serialization/serialization.ts +288 -0
- package/src/swqos/clients.ts +1754 -0
- package/src/swqos/index.ts +50 -0
- package/src/swqos/providers.ts +1707 -0
- package/src/trading/core/async-executor.ts +702 -0
- package/src/trading/core/confirmation-monitor.ts +711 -0
- package/src/trading/core/index.ts +82 -0
- package/src/trading/core/retry-handler.ts +683 -0
- package/src/trading/core/transaction-pool.ts +780 -0
- package/src/trading/executor.ts +385 -0
- package/src/trading/factory.ts +282 -0
- package/src/trading/index.ts +30 -0
- package/src/types.ts +8 -0
- package/src/utils/index.ts +155 -0
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raydium AMM V4 Protocol Instruction Builder
|
|
3
|
+
*
|
|
4
|
+
* Production-grade instruction builder for Raydium AMM V4 protocol.
|
|
5
|
+
* 100% port of Rust implementation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
PublicKey,
|
|
10
|
+
Keypair,
|
|
11
|
+
AccountMeta,
|
|
12
|
+
TransactionInstruction,
|
|
13
|
+
SystemProgram,
|
|
14
|
+
} from "@solana/web3.js";
|
|
15
|
+
import {
|
|
16
|
+
getAssociatedTokenAddressSync,
|
|
17
|
+
createAssociatedTokenAccountInstruction,
|
|
18
|
+
TOKEN_PROGRAM_ID,
|
|
19
|
+
createCloseAccountInstruction,
|
|
20
|
+
NATIVE_MINT,
|
|
21
|
+
createSyncNativeInstruction,
|
|
22
|
+
} from "@solana/spl-token";
|
|
23
|
+
|
|
24
|
+
// ============================================
|
|
25
|
+
// Program IDs and Constants
|
|
26
|
+
// ============================================
|
|
27
|
+
|
|
28
|
+
/** Raydium AMM V4 program ID */
|
|
29
|
+
export const RAYDIUM_AMM_V4_PROGRAM_ID = new PublicKey(
|
|
30
|
+
"675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
/** Authority */
|
|
34
|
+
export const RAYDIUM_AMM_V4_AUTHORITY = new PublicKey(
|
|
35
|
+
"5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1"
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
/** Fee rates */
|
|
39
|
+
export const RAYDIUM_AMM_V4_TRADE_FEE_NUMERATOR = BigInt(25);
|
|
40
|
+
export const RAYDIUM_AMM_V4_TRADE_FEE_DENOMINATOR = BigInt(10000);
|
|
41
|
+
export const RAYDIUM_AMM_V4_SWAP_FEE_NUMERATOR = BigInt(25);
|
|
42
|
+
export const RAYDIUM_AMM_V4_SWAP_FEE_DENOMINATOR = BigInt(10000);
|
|
43
|
+
|
|
44
|
+
// ============================================
|
|
45
|
+
// Discriminators
|
|
46
|
+
// ============================================
|
|
47
|
+
|
|
48
|
+
/** Swap base in instruction discriminator (single byte) */
|
|
49
|
+
export const RAYDIUM_AMM_V4_SWAP_BASE_IN_DISCRIMINATOR: Buffer = Buffer.from([9]);
|
|
50
|
+
|
|
51
|
+
/** Swap base out instruction discriminator (single byte) */
|
|
52
|
+
export const RAYDIUM_AMM_V4_SWAP_BASE_OUT_DISCRIMINATOR: Buffer = Buffer.from([11]);
|
|
53
|
+
|
|
54
|
+
// ============================================
|
|
55
|
+
// Seeds
|
|
56
|
+
// ============================================
|
|
57
|
+
|
|
58
|
+
export const RAYDIUM_AMM_V4_POOL_SEED = Buffer.from("pool");
|
|
59
|
+
|
|
60
|
+
// ============================================
|
|
61
|
+
// Helper Functions
|
|
62
|
+
// ============================================
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Compute swap amount for AMM V4
|
|
66
|
+
*/
|
|
67
|
+
export function computeRaydiumAmmV4SwapAmount(
|
|
68
|
+
coinReserve: bigint,
|
|
69
|
+
pcReserve: bigint,
|
|
70
|
+
isCoinIn: boolean,
|
|
71
|
+
amountIn: bigint,
|
|
72
|
+
slippageBasisPoints: bigint
|
|
73
|
+
): { amountOut: bigint; minAmountOut: bigint } {
|
|
74
|
+
// Apply trade fee (0.25%)
|
|
75
|
+
const amountInAfterFee = amountIn - (amountIn * RAYDIUM_AMM_V4_TRADE_FEE_NUMERATOR) / RAYDIUM_AMM_V4_TRADE_FEE_DENOMINATOR;
|
|
76
|
+
|
|
77
|
+
// Calculate output using constant product formula
|
|
78
|
+
let amountOut: bigint;
|
|
79
|
+
if (isCoinIn) {
|
|
80
|
+
// Selling coin for pc: output = (pcReserve * amountIn) / (coinReserve + amountIn)
|
|
81
|
+
const denominator = coinReserve + amountInAfterFee;
|
|
82
|
+
amountOut = (pcReserve * amountInAfterFee) / denominator;
|
|
83
|
+
} else {
|
|
84
|
+
// Selling pc for coin: output = (coinReserve * amountIn) / (pcReserve + amountIn)
|
|
85
|
+
const denominator = pcReserve + amountInAfterFee;
|
|
86
|
+
amountOut = (coinReserve * amountInAfterFee) / denominator;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Apply slippage
|
|
90
|
+
const minAmountOut = amountOut - (amountOut * slippageBasisPoints) / BigInt(10000);
|
|
91
|
+
|
|
92
|
+
return { amountOut, minAmountOut };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================
|
|
96
|
+
// Types
|
|
97
|
+
// ============================================
|
|
98
|
+
|
|
99
|
+
export interface RaydiumAmmV4Params {
|
|
100
|
+
amm: PublicKey;
|
|
101
|
+
coinMint: PublicKey;
|
|
102
|
+
pcMint: PublicKey;
|
|
103
|
+
tokenCoin: PublicKey;
|
|
104
|
+
tokenPc: PublicKey;
|
|
105
|
+
coinReserve: bigint;
|
|
106
|
+
pcReserve: bigint;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface BuildRaydiumAmmV4BuyInstructionsParams {
|
|
110
|
+
payer: Keypair | PublicKey;
|
|
111
|
+
outputMint: PublicKey;
|
|
112
|
+
inputAmount: bigint;
|
|
113
|
+
slippageBasisPoints?: bigint;
|
|
114
|
+
fixedOutputAmount?: bigint;
|
|
115
|
+
createInputMintAta?: boolean;
|
|
116
|
+
createOutputMintAta?: boolean;
|
|
117
|
+
closeInputMintAta?: boolean;
|
|
118
|
+
protocolParams: RaydiumAmmV4Params;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface BuildRaydiumAmmV4SellInstructionsParams {
|
|
122
|
+
payer: Keypair | PublicKey;
|
|
123
|
+
inputMint: PublicKey;
|
|
124
|
+
inputAmount: bigint;
|
|
125
|
+
slippageBasisPoints?: bigint;
|
|
126
|
+
fixedOutputAmount?: bigint;
|
|
127
|
+
createOutputMintAta?: boolean;
|
|
128
|
+
closeOutputMintAta?: boolean;
|
|
129
|
+
closeInputMintAta?: boolean;
|
|
130
|
+
protocolParams: RaydiumAmmV4Params;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ============================================
|
|
134
|
+
// Instruction Builders
|
|
135
|
+
// ============================================
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Build buy instructions for Raydium AMM V4 protocol
|
|
139
|
+
*/
|
|
140
|
+
export function buildRaydiumAmmV4BuyInstructions(
|
|
141
|
+
params: BuildRaydiumAmmV4BuyInstructionsParams
|
|
142
|
+
): TransactionInstruction[] {
|
|
143
|
+
const {
|
|
144
|
+
payer,
|
|
145
|
+
outputMint,
|
|
146
|
+
inputAmount,
|
|
147
|
+
slippageBasisPoints = BigInt(1000),
|
|
148
|
+
fixedOutputAmount,
|
|
149
|
+
createInputMintAta = true,
|
|
150
|
+
createOutputMintAta = true,
|
|
151
|
+
closeInputMintAta = false,
|
|
152
|
+
protocolParams,
|
|
153
|
+
} = params;
|
|
154
|
+
|
|
155
|
+
if (inputAmount === BigInt(0)) {
|
|
156
|
+
throw new Error("Amount cannot be zero");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
|
|
160
|
+
const instructions: TransactionInstruction[] = [];
|
|
161
|
+
|
|
162
|
+
const WSOL_TOKEN_ACCOUNT = new PublicKey("So11111111111111111111111111111111111111112");
|
|
163
|
+
const USDC_TOKEN_ACCOUNT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
164
|
+
|
|
165
|
+
const {
|
|
166
|
+
amm,
|
|
167
|
+
coinMint,
|
|
168
|
+
pcMint,
|
|
169
|
+
tokenCoin,
|
|
170
|
+
tokenPc,
|
|
171
|
+
coinReserve,
|
|
172
|
+
pcReserve,
|
|
173
|
+
} = protocolParams;
|
|
174
|
+
|
|
175
|
+
// Check pool type
|
|
176
|
+
const isWsol = coinMint.equals(WSOL_TOKEN_ACCOUNT) || pcMint.equals(WSOL_TOKEN_ACCOUNT);
|
|
177
|
+
const isUsdc = coinMint.equals(USDC_TOKEN_ACCOUNT) || pcMint.equals(USDC_TOKEN_ACCOUNT);
|
|
178
|
+
|
|
179
|
+
if (!isWsol && !isUsdc) {
|
|
180
|
+
throw new Error("Pool must contain WSOL or USDC");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Determine swap direction
|
|
184
|
+
const isBaseIn = coinMint.equals(WSOL_TOKEN_ACCOUNT) || coinMint.equals(USDC_TOKEN_ACCOUNT);
|
|
185
|
+
|
|
186
|
+
// Calculate output
|
|
187
|
+
const swapResult = computeRaydiumAmmV4SwapAmount(
|
|
188
|
+
coinReserve,
|
|
189
|
+
pcReserve,
|
|
190
|
+
isBaseIn,
|
|
191
|
+
inputAmount,
|
|
192
|
+
slippageBasisPoints
|
|
193
|
+
);
|
|
194
|
+
const minimumAmountOut = fixedOutputAmount || swapResult.minAmountOut;
|
|
195
|
+
|
|
196
|
+
// Determine input/output mints
|
|
197
|
+
const inputMint = isWsol ? WSOL_TOKEN_ACCOUNT : USDC_TOKEN_ACCOUNT;
|
|
198
|
+
|
|
199
|
+
// Derive user token accounts
|
|
200
|
+
const userSourceTokenAccount = getAssociatedTokenAddressSync(
|
|
201
|
+
inputMint,
|
|
202
|
+
payerPubkey,
|
|
203
|
+
true,
|
|
204
|
+
TOKEN_PROGRAM_ID
|
|
205
|
+
);
|
|
206
|
+
const userDestinationTokenAccount = getAssociatedTokenAddressSync(
|
|
207
|
+
outputMint,
|
|
208
|
+
payerPubkey,
|
|
209
|
+
true,
|
|
210
|
+
TOKEN_PROGRAM_ID
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Handle WSOL wrapping
|
|
214
|
+
if (createInputMintAta && isWsol) {
|
|
215
|
+
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
|
|
216
|
+
instructions.push(
|
|
217
|
+
createAssociatedTokenAccountInstruction(
|
|
218
|
+
payerPubkey,
|
|
219
|
+
wsolAta,
|
|
220
|
+
payerPubkey,
|
|
221
|
+
NATIVE_MINT,
|
|
222
|
+
TOKEN_PROGRAM_ID
|
|
223
|
+
)
|
|
224
|
+
);
|
|
225
|
+
instructions.push(createSyncNativeInstruction(wsolAta));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Create output mint ATA if needed
|
|
229
|
+
if (createOutputMintAta) {
|
|
230
|
+
instructions.push(
|
|
231
|
+
createAssociatedTokenAccountInstruction(
|
|
232
|
+
payerPubkey,
|
|
233
|
+
userDestinationTokenAccount,
|
|
234
|
+
payerPubkey,
|
|
235
|
+
outputMint,
|
|
236
|
+
TOKEN_PROGRAM_ID
|
|
237
|
+
)
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Build instruction data (1 byte discriminator + 8 bytes amountIn + 8 bytes minAmountOut)
|
|
242
|
+
const data = Buffer.alloc(17);
|
|
243
|
+
RAYDIUM_AMM_V4_SWAP_BASE_IN_DISCRIMINATOR.copy(data, 0);
|
|
244
|
+
data.writeBigUInt64LE(inputAmount, 1);
|
|
245
|
+
data.writeBigUInt64LE(minimumAmountOut, 9);
|
|
246
|
+
|
|
247
|
+
// Build accounts (Raydium AMM V4 has a specific account order - 17 accounts)
|
|
248
|
+
const accounts: AccountMeta[] = [
|
|
249
|
+
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
250
|
+
{ pubkey: amm, isSigner: false, isWritable: true },
|
|
251
|
+
{ pubkey: RAYDIUM_AMM_V4_AUTHORITY, isSigner: false, isWritable: false },
|
|
252
|
+
{ pubkey: amm, isSigner: false, isWritable: true }, // Amm Open Orders (same as amm for simplicity)
|
|
253
|
+
{ pubkey: tokenCoin, isSigner: false, isWritable: true }, // Pool Coin Token Account
|
|
254
|
+
{ pubkey: tokenPc, isSigner: false, isWritable: true }, // Pool Pc Token Account
|
|
255
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Program (placeholder)
|
|
256
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Market (placeholder)
|
|
257
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Bids (placeholder)
|
|
258
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Asks (placeholder)
|
|
259
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Event Queue (placeholder)
|
|
260
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Coin Vault Account (placeholder)
|
|
261
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Pc Vault Account (placeholder)
|
|
262
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Vault Signer (placeholder)
|
|
263
|
+
{ pubkey: userSourceTokenAccount, isSigner: false, isWritable: true },
|
|
264
|
+
{ pubkey: userDestinationTokenAccount, isSigner: false, isWritable: true },
|
|
265
|
+
{ pubkey: payerPubkey, isSigner: true, isWritable: true },
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
instructions.push(
|
|
269
|
+
new TransactionInstruction({
|
|
270
|
+
keys: accounts,
|
|
271
|
+
programId: RAYDIUM_AMM_V4_PROGRAM_ID,
|
|
272
|
+
data,
|
|
273
|
+
})
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// Close WSOL ATA if requested
|
|
277
|
+
if (closeInputMintAta && isWsol) {
|
|
278
|
+
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
|
|
279
|
+
instructions.push(
|
|
280
|
+
createCloseAccountInstruction(wsolAta, payerPubkey, payerPubkey, [], TOKEN_PROGRAM_ID)
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return instructions;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Build sell instructions for Raydium AMM V4 protocol
|
|
289
|
+
*/
|
|
290
|
+
export function buildRaydiumAmmV4SellInstructions(
|
|
291
|
+
params: BuildRaydiumAmmV4SellInstructionsParams
|
|
292
|
+
): TransactionInstruction[] {
|
|
293
|
+
const {
|
|
294
|
+
payer,
|
|
295
|
+
inputMint,
|
|
296
|
+
inputAmount,
|
|
297
|
+
slippageBasisPoints = BigInt(1000),
|
|
298
|
+
fixedOutputAmount,
|
|
299
|
+
createOutputMintAta = true,
|
|
300
|
+
closeOutputMintAta = false,
|
|
301
|
+
closeInputMintAta = false,
|
|
302
|
+
protocolParams,
|
|
303
|
+
} = params;
|
|
304
|
+
|
|
305
|
+
if (inputAmount === BigInt(0)) {
|
|
306
|
+
throw new Error("Amount cannot be zero");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
|
|
310
|
+
const instructions: TransactionInstruction[] = [];
|
|
311
|
+
|
|
312
|
+
const WSOL_TOKEN_ACCOUNT = new PublicKey("So11111111111111111111111111111111111111112");
|
|
313
|
+
const USDC_TOKEN_ACCOUNT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
314
|
+
|
|
315
|
+
const {
|
|
316
|
+
amm,
|
|
317
|
+
coinMint,
|
|
318
|
+
pcMint,
|
|
319
|
+
tokenCoin,
|
|
320
|
+
tokenPc,
|
|
321
|
+
coinReserve,
|
|
322
|
+
pcReserve,
|
|
323
|
+
} = protocolParams;
|
|
324
|
+
|
|
325
|
+
// Check pool type
|
|
326
|
+
const isWsol = coinMint.equals(WSOL_TOKEN_ACCOUNT) || pcMint.equals(WSOL_TOKEN_ACCOUNT);
|
|
327
|
+
const isUsdc = coinMint.equals(USDC_TOKEN_ACCOUNT) || pcMint.equals(USDC_TOKEN_ACCOUNT);
|
|
328
|
+
|
|
329
|
+
if (!isWsol && !isUsdc) {
|
|
330
|
+
throw new Error("Pool must contain WSOL or USDC");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Determine swap direction (selling token for WSOL/USDC means pc is output)
|
|
334
|
+
const isBaseIn = pcMint.equals(WSOL_TOKEN_ACCOUNT) || pcMint.equals(USDC_TOKEN_ACCOUNT);
|
|
335
|
+
|
|
336
|
+
// Calculate output
|
|
337
|
+
const swapResult = computeRaydiumAmmV4SwapAmount(
|
|
338
|
+
coinReserve,
|
|
339
|
+
pcReserve,
|
|
340
|
+
isBaseIn,
|
|
341
|
+
inputAmount,
|
|
342
|
+
slippageBasisPoints
|
|
343
|
+
);
|
|
344
|
+
const minimumAmountOut = fixedOutputAmount || swapResult.minAmountOut;
|
|
345
|
+
|
|
346
|
+
// Determine output mint
|
|
347
|
+
const outputMint = isWsol ? WSOL_TOKEN_ACCOUNT : USDC_TOKEN_ACCOUNT;
|
|
348
|
+
|
|
349
|
+
// Derive user token accounts
|
|
350
|
+
const userSourceTokenAccount = getAssociatedTokenAddressSync(
|
|
351
|
+
inputMint,
|
|
352
|
+
payerPubkey,
|
|
353
|
+
true,
|
|
354
|
+
TOKEN_PROGRAM_ID
|
|
355
|
+
);
|
|
356
|
+
const userDestinationTokenAccount = getAssociatedTokenAddressSync(
|
|
357
|
+
outputMint,
|
|
358
|
+
payerPubkey,
|
|
359
|
+
true,
|
|
360
|
+
TOKEN_PROGRAM_ID
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
// Create WSOL ATA for receiving if needed
|
|
364
|
+
if (createOutputMintAta && isWsol) {
|
|
365
|
+
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
|
|
366
|
+
instructions.push(
|
|
367
|
+
createAssociatedTokenAccountInstruction(
|
|
368
|
+
payerPubkey,
|
|
369
|
+
wsolAta,
|
|
370
|
+
payerPubkey,
|
|
371
|
+
NATIVE_MINT,
|
|
372
|
+
TOKEN_PROGRAM_ID
|
|
373
|
+
)
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Build instruction data
|
|
378
|
+
const data = Buffer.alloc(17);
|
|
379
|
+
RAYDIUM_AMM_V4_SWAP_BASE_IN_DISCRIMINATOR.copy(data, 0);
|
|
380
|
+
data.writeBigUInt64LE(inputAmount, 1);
|
|
381
|
+
data.writeBigUInt64LE(minimumAmountOut, 9);
|
|
382
|
+
|
|
383
|
+
// Build accounts
|
|
384
|
+
const accounts: AccountMeta[] = [
|
|
385
|
+
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
386
|
+
{ pubkey: amm, isSigner: false, isWritable: true },
|
|
387
|
+
{ pubkey: RAYDIUM_AMM_V4_AUTHORITY, isSigner: false, isWritable: false },
|
|
388
|
+
{ pubkey: amm, isSigner: false, isWritable: true }, // Amm Open Orders
|
|
389
|
+
{ pubkey: tokenCoin, isSigner: false, isWritable: true }, // Pool Coin Token Account
|
|
390
|
+
{ pubkey: tokenPc, isSigner: false, isWritable: true }, // Pool Pc Token Account
|
|
391
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Program
|
|
392
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Market
|
|
393
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Bids
|
|
394
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Asks
|
|
395
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Event Queue
|
|
396
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Coin Vault Account
|
|
397
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Pc Vault Account
|
|
398
|
+
{ pubkey: amm, isSigner: false, isWritable: false }, // Serum Vault Signer
|
|
399
|
+
{ pubkey: userSourceTokenAccount, isSigner: false, isWritable: true },
|
|
400
|
+
{ pubkey: userDestinationTokenAccount, isSigner: false, isWritable: true },
|
|
401
|
+
{ pubkey: payerPubkey, isSigner: true, isWritable: true },
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
instructions.push(
|
|
405
|
+
new TransactionInstruction({
|
|
406
|
+
keys: accounts,
|
|
407
|
+
programId: RAYDIUM_AMM_V4_PROGRAM_ID,
|
|
408
|
+
data,
|
|
409
|
+
})
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
// Close WSOL ATA if requested
|
|
413
|
+
if (closeOutputMintAta && isWsol) {
|
|
414
|
+
const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
|
|
415
|
+
instructions.push(
|
|
416
|
+
createCloseAccountInstruction(wsolAta, payerPubkey, payerPubkey, [], TOKEN_PROGRAM_ID)
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Close input token ATA if requested
|
|
421
|
+
if (closeInputMintAta) {
|
|
422
|
+
instructions.push(
|
|
423
|
+
createCloseAccountInstruction(
|
|
424
|
+
userSourceTokenAccount,
|
|
425
|
+
payerPubkey,
|
|
426
|
+
payerPubkey,
|
|
427
|
+
[],
|
|
428
|
+
TOKEN_PROGRAM_ID
|
|
429
|
+
)
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return instructions;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ===== AMM Info Decoder - from Rust: src/instruction/utils/raydium_amm_v4_types.rs =====
|
|
437
|
+
|
|
438
|
+
export const AMM_INFO_SIZE = 752;
|
|
439
|
+
|
|
440
|
+
export interface RaydiumAmmFees {
|
|
441
|
+
minSeparateNumerator: bigint;
|
|
442
|
+
minSeparateDenominator: bigint;
|
|
443
|
+
tradeFeeNumerator: bigint;
|
|
444
|
+
tradeFeeDenominator: bigint;
|
|
445
|
+
pnlNumerator: bigint;
|
|
446
|
+
pnlDenominator: bigint;
|
|
447
|
+
swapFeeNumerator: bigint;
|
|
448
|
+
swapFeeDenominator: bigint;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export interface RaydiumAmmOutputData {
|
|
452
|
+
needTakePnlCoin: bigint;
|
|
453
|
+
needTakePnlPc: bigint;
|
|
454
|
+
totalPnlPc: bigint;
|
|
455
|
+
totalPnlCoin: bigint;
|
|
456
|
+
poolOpenTime: bigint;
|
|
457
|
+
punishPcAmount: bigint;
|
|
458
|
+
punishCoinAmount: bigint;
|
|
459
|
+
orderbookToInitTime: bigint;
|
|
460
|
+
swapCoinInAmount: bigint;
|
|
461
|
+
swapPcOutAmount: bigint;
|
|
462
|
+
swapTakePcFee: bigint;
|
|
463
|
+
swapPcInAmount: bigint;
|
|
464
|
+
swapCoinOutAmount: bigint;
|
|
465
|
+
swapTakeCoinFee: bigint;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export interface RaydiumAmmInfo {
|
|
469
|
+
status: bigint;
|
|
470
|
+
nonce: bigint;
|
|
471
|
+
orderNum: bigint;
|
|
472
|
+
depth: bigint;
|
|
473
|
+
coinDecimals: bigint;
|
|
474
|
+
pcDecimals: bigint;
|
|
475
|
+
state: bigint;
|
|
476
|
+
resetFlag: bigint;
|
|
477
|
+
minSize: bigint;
|
|
478
|
+
volMaxCutRatio: bigint;
|
|
479
|
+
amountWave: bigint;
|
|
480
|
+
coinLotSize: bigint;
|
|
481
|
+
pcLotSize: bigint;
|
|
482
|
+
minPriceMultiplier: bigint;
|
|
483
|
+
maxPriceMultiplier: bigint;
|
|
484
|
+
sysDecimalValue: bigint;
|
|
485
|
+
fees: RaydiumAmmFees;
|
|
486
|
+
output: RaydiumAmmOutputData;
|
|
487
|
+
tokenCoin: PublicKey;
|
|
488
|
+
tokenPc: PublicKey;
|
|
489
|
+
coinMint: PublicKey;
|
|
490
|
+
pcMint: PublicKey;
|
|
491
|
+
lpMint: PublicKey;
|
|
492
|
+
openOrders: PublicKey;
|
|
493
|
+
market: PublicKey;
|
|
494
|
+
serumDex: PublicKey;
|
|
495
|
+
targetOrders: PublicKey;
|
|
496
|
+
withdrawQueue: PublicKey;
|
|
497
|
+
tokenTempLp: PublicKey;
|
|
498
|
+
ammOwner: PublicKey;
|
|
499
|
+
lpAmount: bigint;
|
|
500
|
+
clientOrderId: bigint;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Decode Raydium AMM v4 info from account data.
|
|
505
|
+
* 100% from Rust: src/instruction/utils/raydium_amm_v4_types.rs amm_info_decode
|
|
506
|
+
*/
|
|
507
|
+
export function decodeAmmInfo(data: Buffer): RaydiumAmmInfo | null {
|
|
508
|
+
if (data.length < AMM_INFO_SIZE) {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
try {
|
|
513
|
+
let offset = 0;
|
|
514
|
+
|
|
515
|
+
const readU64 = () => {
|
|
516
|
+
const val = data.readBigUInt64LE(offset);
|
|
517
|
+
offset += 8;
|
|
518
|
+
return val;
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
// status: u64
|
|
522
|
+
const status = readU64();
|
|
523
|
+
// nonce: u64
|
|
524
|
+
const nonce = readU64();
|
|
525
|
+
// order_num: u64
|
|
526
|
+
const orderNum = readU64();
|
|
527
|
+
// depth: u64
|
|
528
|
+
const depth = readU64();
|
|
529
|
+
// coin_decimals: u64
|
|
530
|
+
const coinDecimals = readU64();
|
|
531
|
+
// pc_decimals: u64
|
|
532
|
+
const pcDecimals = readU64();
|
|
533
|
+
// state: u64
|
|
534
|
+
const state = readU64();
|
|
535
|
+
// reset_flag: u64
|
|
536
|
+
const resetFlag = readU64();
|
|
537
|
+
// min_size: u64
|
|
538
|
+
const minSize = readU64();
|
|
539
|
+
// vol_max_cut_ratio: u64
|
|
540
|
+
const volMaxCutRatio = readU64();
|
|
541
|
+
// amount_wave: u64
|
|
542
|
+
const amountWave = readU64();
|
|
543
|
+
// coin_lot_size: u64
|
|
544
|
+
const coinLotSize = readU64();
|
|
545
|
+
// pc_lot_size: u64
|
|
546
|
+
const pcLotSize = readU64();
|
|
547
|
+
// min_price_multiplier: u64
|
|
548
|
+
const minPriceMultiplier = readU64();
|
|
549
|
+
// max_price_multiplier: u64
|
|
550
|
+
const maxPriceMultiplier = readU64();
|
|
551
|
+
// sys_decimal_value: u64
|
|
552
|
+
const sysDecimalValue = readU64();
|
|
553
|
+
|
|
554
|
+
// fees: Fees (8 * u64)
|
|
555
|
+
const fees: RaydiumAmmFees = {
|
|
556
|
+
minSeparateNumerator: readU64(),
|
|
557
|
+
minSeparateDenominator: readU64(),
|
|
558
|
+
tradeFeeNumerator: readU64(),
|
|
559
|
+
tradeFeeDenominator: readU64(),
|
|
560
|
+
pnlNumerator: readU64(),
|
|
561
|
+
pnlDenominator: readU64(),
|
|
562
|
+
swapFeeNumerator: readU64(),
|
|
563
|
+
swapFeeDenominator: readU64(),
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
// output: OutPutData
|
|
567
|
+
const output: RaydiumAmmOutputData = {
|
|
568
|
+
needTakePnlCoin: readU64(),
|
|
569
|
+
needTakePnlPc: readU64(),
|
|
570
|
+
totalPnlPc: readU64(),
|
|
571
|
+
totalPnlCoin: readU64(),
|
|
572
|
+
poolOpenTime: readU64(),
|
|
573
|
+
punishPcAmount: readU64(),
|
|
574
|
+
punishCoinAmount: readU64(),
|
|
575
|
+
orderbookToInitTime: readU64(),
|
|
576
|
+
swapCoinInAmount: readU64(),
|
|
577
|
+
swapPcOutAmount: readU64(),
|
|
578
|
+
swapTakePcFee: readU64(),
|
|
579
|
+
swapPcInAmount: readU64(),
|
|
580
|
+
swapCoinOutAmount: readU64(),
|
|
581
|
+
swapTakeCoinFee: readU64(),
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
// token_coin: Pubkey
|
|
585
|
+
const tokenCoin = new PublicKey(data.subarray(offset, offset + 32));
|
|
586
|
+
offset += 32;
|
|
587
|
+
|
|
588
|
+
// token_pc: Pubkey
|
|
589
|
+
const tokenPc = new PublicKey(data.subarray(offset, offset + 32));
|
|
590
|
+
offset += 32;
|
|
591
|
+
|
|
592
|
+
// coin_mint: Pubkey
|
|
593
|
+
const coinMint = new PublicKey(data.subarray(offset, offset + 32));
|
|
594
|
+
offset += 32;
|
|
595
|
+
|
|
596
|
+
// pc_mint: Pubkey
|
|
597
|
+
const pcMint = new PublicKey(data.subarray(offset, offset + 32));
|
|
598
|
+
offset += 32;
|
|
599
|
+
|
|
600
|
+
// lp_mint: Pubkey
|
|
601
|
+
const lpMint = new PublicKey(data.subarray(offset, offset + 32));
|
|
602
|
+
offset += 32;
|
|
603
|
+
|
|
604
|
+
// open_orders: Pubkey
|
|
605
|
+
const openOrders = new PublicKey(data.subarray(offset, offset + 32));
|
|
606
|
+
offset += 32;
|
|
607
|
+
|
|
608
|
+
// market: Pubkey
|
|
609
|
+
const market = new PublicKey(data.subarray(offset, offset + 32));
|
|
610
|
+
offset += 32;
|
|
611
|
+
|
|
612
|
+
// serum_dex: Pubkey
|
|
613
|
+
const serumDex = new PublicKey(data.subarray(offset, offset + 32));
|
|
614
|
+
offset += 32;
|
|
615
|
+
|
|
616
|
+
// target_orders: Pubkey
|
|
617
|
+
const targetOrders = new PublicKey(data.subarray(offset, offset + 32));
|
|
618
|
+
offset += 32;
|
|
619
|
+
|
|
620
|
+
// withdraw_queue: Pubkey
|
|
621
|
+
const withdrawQueue = new PublicKey(data.subarray(offset, offset + 32));
|
|
622
|
+
offset += 32;
|
|
623
|
+
|
|
624
|
+
// token_temp_lp: Pubkey
|
|
625
|
+
const tokenTempLp = new PublicKey(data.subarray(offset, offset + 32));
|
|
626
|
+
offset += 32;
|
|
627
|
+
|
|
628
|
+
// amm_owner: Pubkey
|
|
629
|
+
const ammOwner = new PublicKey(data.subarray(offset, offset + 32));
|
|
630
|
+
offset += 32;
|
|
631
|
+
|
|
632
|
+
// lp_amount: u64
|
|
633
|
+
const lpAmount = readU64();
|
|
634
|
+
|
|
635
|
+
// client_order_id: u64
|
|
636
|
+
const clientOrderId = readU64();
|
|
637
|
+
|
|
638
|
+
return {
|
|
639
|
+
status,
|
|
640
|
+
nonce,
|
|
641
|
+
orderNum,
|
|
642
|
+
depth,
|
|
643
|
+
coinDecimals,
|
|
644
|
+
pcDecimals,
|
|
645
|
+
state,
|
|
646
|
+
resetFlag,
|
|
647
|
+
minSize,
|
|
648
|
+
volMaxCutRatio,
|
|
649
|
+
amountWave,
|
|
650
|
+
coinLotSize,
|
|
651
|
+
pcLotSize,
|
|
652
|
+
minPriceMultiplier,
|
|
653
|
+
maxPriceMultiplier,
|
|
654
|
+
sysDecimalValue,
|
|
655
|
+
fees,
|
|
656
|
+
output,
|
|
657
|
+
tokenCoin,
|
|
658
|
+
tokenPc,
|
|
659
|
+
coinMint,
|
|
660
|
+
pcMint,
|
|
661
|
+
lpMint,
|
|
662
|
+
openOrders,
|
|
663
|
+
market,
|
|
664
|
+
serumDex,
|
|
665
|
+
targetOrders,
|
|
666
|
+
withdrawQueue,
|
|
667
|
+
tokenTempLp,
|
|
668
|
+
ammOwner,
|
|
669
|
+
lpAmount,
|
|
670
|
+
clientOrderId,
|
|
671
|
+
};
|
|
672
|
+
} catch {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// ===== Async Fetch Functions - from Rust: src/instruction/utils/raydium_amm_v4.rs =====
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Fetch AMM info from RPC.
|
|
681
|
+
* 100% from Rust: src/instruction/utils/raydium_amm_v4.rs fetch_amm_info
|
|
682
|
+
*/
|
|
683
|
+
export async function fetchAmmInfo(
|
|
684
|
+
connection: { getAccountInfo: (pubkey: PublicKey) => Promise<{ value?: { data: Buffer } }> },
|
|
685
|
+
amm: PublicKey
|
|
686
|
+
): Promise<RaydiumAmmInfo | null> {
|
|
687
|
+
const account = await connection.getAccountInfo(amm);
|
|
688
|
+
if (!account?.value?.data) {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
return decodeAmmInfo(account.value.data);
|
|
692
|
+
}
|