naracli 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 +155 -0
- package/bin/nara-cli.ts +32 -0
- package/dist/nara-cli.mjs +2631 -0
- package/dist/quest/nara_quest.json +534 -0
- package/dist/zk/answer_proof.wasm +0 -0
- package/dist/zk/answer_proof_final.zkey +0 -0
- package/index.ts +76 -0
- package/package.json +54 -0
- package/src/cli/commands/config.ts +125 -0
- package/src/cli/commands/migrate.ts +270 -0
- package/src/cli/commands/pool.ts +364 -0
- package/src/cli/commands/quest.ts +312 -0
- package/src/cli/commands/swap.ts +349 -0
- package/src/cli/commands/wallet.ts +719 -0
- package/src/cli/index.ts +25 -0
- package/src/cli/quest/nara_quest.json +534 -0
- package/src/cli/quest/nara_quest_types.ts +540 -0
- package/src/cli/types.ts +207 -0
- package/src/cli/utils/output.ts +110 -0
- package/src/cli/utils/transaction.ts +146 -0
- package/src/cli/utils/validation.ts +120 -0
- package/src/cli/utils/wallet.ts +72 -0
- package/src/cli/zk/answer_proof.wasm +0 -0
- package/src/cli/zk/answer_proof_final.zkey +0 -0
- package/src/client.ts +96 -0
- package/src/config.ts +132 -0
- package/src/constants.ts +29 -0
- package/src/migrate.ts +222 -0
- package/src/pool.ts +259 -0
- package/src/quest.ts +379 -0
- package/src/swap.ts +608 -0
- package/src/types/snarkjs.d.ts +9 -0
package/src/swap.ts
ADDED
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
import { PublicKey, Transaction, VersionedTransaction } from "@solana/web3.js";
|
|
2
|
+
import BN from "bn.js";
|
|
3
|
+
import { NaraSDK } from "./client";
|
|
4
|
+
import {
|
|
5
|
+
deriveDammV2PoolAddress,
|
|
6
|
+
DAMM_V2_MIGRATION_FEE_ADDRESS,
|
|
7
|
+
} from "@meteora-ag/dynamic-bonding-curve-sdk";
|
|
8
|
+
import { NATIVE_MINT, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
|
9
|
+
import {
|
|
10
|
+
CpAmm,
|
|
11
|
+
SwapMode as CpAmmSwapMode,
|
|
12
|
+
getCurrentPoint,
|
|
13
|
+
ActivationType,
|
|
14
|
+
} from "@meteora-ag/cp-amm-sdk";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Swap mode enum
|
|
18
|
+
* - ExactIn: Exact input mode
|
|
19
|
+
* - PartialFill: Partial fill mode (recommended, can precisely fill the pool)
|
|
20
|
+
* - ExactOut: Exact output mode
|
|
21
|
+
*/
|
|
22
|
+
export enum SwapMode {
|
|
23
|
+
ExactIn = 0,
|
|
24
|
+
PartialFill = 1,
|
|
25
|
+
ExactOut = 2,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SwapQuoteResponse {
|
|
29
|
+
amountIn: string;
|
|
30
|
+
outputAmount: string;
|
|
31
|
+
minimumAmountOut: string;
|
|
32
|
+
nextSqrtPrice: string;
|
|
33
|
+
tradingFee: string;
|
|
34
|
+
protocolFee: string;
|
|
35
|
+
referralFee: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if pool has migrated to DAMM V2 and return relevant information
|
|
40
|
+
*/
|
|
41
|
+
async function checkPoolMigration(
|
|
42
|
+
sdk: NaraSDK,
|
|
43
|
+
tokenAddress: string
|
|
44
|
+
): Promise<{
|
|
45
|
+
isMigrated: boolean;
|
|
46
|
+
dammV2Pool?: PublicKey;
|
|
47
|
+
dammConfig?: PublicKey;
|
|
48
|
+
}> {
|
|
49
|
+
const client = sdk.getClient();
|
|
50
|
+
const tokenPubkey = new PublicKey(tokenAddress);
|
|
51
|
+
|
|
52
|
+
const poolAccount = await client.state.getPoolByBaseMint(tokenPubkey);
|
|
53
|
+
if (!poolAccount) {
|
|
54
|
+
throw new Error(`Pool not found for token: ${tokenAddress}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check if already migrated
|
|
58
|
+
if (poolAccount.account.isMigrated) {
|
|
59
|
+
// Get DAMM config
|
|
60
|
+
const virtualPool = poolAccount.account;
|
|
61
|
+
const poolConfig = await client.state.getPoolConfig(virtualPool.config);
|
|
62
|
+
const migrationFeeOption = (poolConfig as any).migrationFeeOption || 0;
|
|
63
|
+
|
|
64
|
+
// Get correct config address from array
|
|
65
|
+
const dammConfig = DAMM_V2_MIGRATION_FEE_ADDRESS[migrationFeeOption];
|
|
66
|
+
|
|
67
|
+
if (!dammConfig) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Invalid migration fee option: ${migrationFeeOption}. Cannot determine DAMM V2 config address.`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Derive DAMM V2 pool address
|
|
74
|
+
const dammV2Pool = deriveDammV2PoolAddress(
|
|
75
|
+
dammConfig,
|
|
76
|
+
NATIVE_MINT, // tokenA = SOL
|
|
77
|
+
tokenPubkey // tokenB = token
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// Verify pool account actually exists
|
|
81
|
+
const connection = sdk.getConnection();
|
|
82
|
+
const poolAccountInfo = await connection.getAccountInfo(dammV2Pool);
|
|
83
|
+
|
|
84
|
+
if (!poolAccountInfo) {
|
|
85
|
+
// Pool marked as migrated but account doesn't exist, migration not yet complete
|
|
86
|
+
return { isMigrated: false };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
isMigrated: true,
|
|
91
|
+
dammV2Pool,
|
|
92
|
+
dammConfig,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { isMigrated: false };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get swap quote
|
|
101
|
+
* @param sdk NaraSDK SDK instance
|
|
102
|
+
* @param tokenAddress Token address (baseMint)
|
|
103
|
+
* @param amountIn Input amount
|
|
104
|
+
* @param swapBaseForQuote true=sell token for SOL, false=buy token with SOL
|
|
105
|
+
* @param slippageBps Slippage in basis points (default 100 = 1%)
|
|
106
|
+
* @returns Swap quote information
|
|
107
|
+
*/
|
|
108
|
+
export async function getSwapQuote(
|
|
109
|
+
sdk: NaraSDK,
|
|
110
|
+
tokenAddress: string,
|
|
111
|
+
amountIn: BN,
|
|
112
|
+
swapBaseForQuote: boolean,
|
|
113
|
+
slippageBps: number = 100
|
|
114
|
+
): Promise<SwapQuoteResponse> {
|
|
115
|
+
const client = sdk.getClient();
|
|
116
|
+
const tokenPubkey = new PublicKey(tokenAddress);
|
|
117
|
+
|
|
118
|
+
// Get pool by token (baseMint) address
|
|
119
|
+
const poolAccount = await client.state.getPoolByBaseMint(tokenPubkey);
|
|
120
|
+
if (!poolAccount) {
|
|
121
|
+
throw new Error(`Pool not found for token: ${tokenAddress}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const virtualPool = poolAccount.account;
|
|
125
|
+
const poolConfig = await client.state.getPoolConfig(virtualPool.config);
|
|
126
|
+
|
|
127
|
+
const quote = client.pool.swapQuote({
|
|
128
|
+
virtualPool,
|
|
129
|
+
config: poolConfig,
|
|
130
|
+
swapBaseForQuote,
|
|
131
|
+
amountIn,
|
|
132
|
+
slippageBps,
|
|
133
|
+
hasReferral: false,
|
|
134
|
+
eligibleForFirstSwapWithMinFee: false,
|
|
135
|
+
currentPoint: new BN(0),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Cast to any to access IDL-derived properties that exist at runtime
|
|
139
|
+
const quoteResult = quote as any;
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
amountIn: amountIn.toString(),
|
|
143
|
+
outputAmount: quoteResult.outputAmount.toString(),
|
|
144
|
+
minimumAmountOut: quote.minimumAmountOut.toString(),
|
|
145
|
+
nextSqrtPrice: quoteResult.nextSqrtPrice.toString(),
|
|
146
|
+
tradingFee: quoteResult.tradingFee.toString(),
|
|
147
|
+
protocolFee: quoteResult.protocolFee.toString(),
|
|
148
|
+
referralFee: quoteResult.referralFee.toString(),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface BuyTokenParams {
|
|
153
|
+
tokenAddress: string;
|
|
154
|
+
amountInSOL: number;
|
|
155
|
+
owner: PublicKey;
|
|
156
|
+
slippageBps?: number;
|
|
157
|
+
/** Swap mode, defaults to PartialFill (recommended) */
|
|
158
|
+
swapMode?: SwapMode;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface BuyTokenResult {
|
|
162
|
+
/** Unsigned transaction (returns VersionedTransaction if ALT is configured) */
|
|
163
|
+
transaction: Transaction | VersionedTransaction;
|
|
164
|
+
/** Input amount in lamports */
|
|
165
|
+
amountIn: string;
|
|
166
|
+
/** Expected output amount */
|
|
167
|
+
expectedAmountOut: string;
|
|
168
|
+
/** Minimum output amount */
|
|
169
|
+
minimumAmountOut: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Create buy token transaction (returns unsigned transaction)
|
|
174
|
+
* @param sdk NaraSDK SDK instance
|
|
175
|
+
* @param params Buy parameters
|
|
176
|
+
* @returns Unsigned transaction and related information
|
|
177
|
+
*/
|
|
178
|
+
export async function buyToken(
|
|
179
|
+
sdk: NaraSDK,
|
|
180
|
+
params: BuyTokenParams
|
|
181
|
+
): Promise<BuyTokenResult> {
|
|
182
|
+
const client = sdk.getClient();
|
|
183
|
+
const connection = sdk.getConnection();
|
|
184
|
+
|
|
185
|
+
const tokenPubkey = new PublicKey(params.tokenAddress);
|
|
186
|
+
const amountIn = new BN(params.amountInSOL * 1e9); // Convert SOL to lamports
|
|
187
|
+
const slippageBps = params.slippageBps ?? 100;
|
|
188
|
+
const swapMode = params.swapMode ?? SwapMode.PartialFill; // Default to PartialFill mode
|
|
189
|
+
|
|
190
|
+
// Check if pool has migrated to DAMM V2
|
|
191
|
+
const migrationInfo = await checkPoolMigration(sdk, params.tokenAddress);
|
|
192
|
+
|
|
193
|
+
if (migrationInfo.isMigrated && migrationInfo.dammV2Pool) {
|
|
194
|
+
console.log("🚀 Pool launched to DAMM V2, using CP-AMM for swap");
|
|
195
|
+
|
|
196
|
+
// Use CP-AMM SDK for swap
|
|
197
|
+
const cpAmm = new CpAmm(connection);
|
|
198
|
+
|
|
199
|
+
// Get pool state
|
|
200
|
+
const poolState = await cpAmm.fetchPoolState(migrationInfo.dammV2Pool);
|
|
201
|
+
|
|
202
|
+
// Get current point (based on pool's activation type)
|
|
203
|
+
const currentPoint = await getCurrentPoint(
|
|
204
|
+
connection,
|
|
205
|
+
poolState.activationType as ActivationType
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Determine input/output tokens (SOL = tokenA, Token = tokenB)
|
|
209
|
+
const isAToB = poolState.tokenAMint.equals(NATIVE_MINT);
|
|
210
|
+
const inputTokenMint = isAToB ? poolState.tokenAMint : poolState.tokenBMint;
|
|
211
|
+
|
|
212
|
+
// Convert SwapMode
|
|
213
|
+
const cpAmmSwapMode =
|
|
214
|
+
swapMode === SwapMode.PartialFill
|
|
215
|
+
? CpAmmSwapMode.PartialFill
|
|
216
|
+
: swapMode === SwapMode.ExactOut
|
|
217
|
+
? CpAmmSwapMode.ExactOut
|
|
218
|
+
: CpAmmSwapMode.ExactIn;
|
|
219
|
+
|
|
220
|
+
// Build quote parameters
|
|
221
|
+
const quoteBaseParams = {
|
|
222
|
+
inputTokenMint,
|
|
223
|
+
slippage: slippageBps / 10000,
|
|
224
|
+
currentPoint,
|
|
225
|
+
poolState,
|
|
226
|
+
tokenADecimal: 9, // SOL decimals
|
|
227
|
+
tokenBDecimal: 6, // Token decimals (assumed)
|
|
228
|
+
hasReferral: false,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// Calculate quote
|
|
232
|
+
let quote: any;
|
|
233
|
+
if (cpAmmSwapMode === CpAmmSwapMode.ExactOut) {
|
|
234
|
+
quote = cpAmm.getQuote2({
|
|
235
|
+
...quoteBaseParams,
|
|
236
|
+
swapMode: cpAmmSwapMode,
|
|
237
|
+
amountOut: amountIn, // ExactOut: desired output amount
|
|
238
|
+
});
|
|
239
|
+
} else {
|
|
240
|
+
quote = cpAmm.getQuote2({
|
|
241
|
+
...quoteBaseParams,
|
|
242
|
+
swapMode: cpAmmSwapMode,
|
|
243
|
+
amountIn,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Build swap parameters
|
|
248
|
+
const swapBaseParams = {
|
|
249
|
+
payer: params.owner,
|
|
250
|
+
pool: migrationInfo.dammV2Pool,
|
|
251
|
+
inputTokenMint,
|
|
252
|
+
outputTokenMint: isAToB ? poolState.tokenBMint : poolState.tokenAMint,
|
|
253
|
+
tokenAMint: poolState.tokenAMint,
|
|
254
|
+
tokenBMint: poolState.tokenBMint,
|
|
255
|
+
tokenAVault: poolState.tokenAVault,
|
|
256
|
+
tokenBVault: poolState.tokenBVault,
|
|
257
|
+
tokenAProgram: TOKEN_PROGRAM_ID,
|
|
258
|
+
tokenBProgram: TOKEN_PROGRAM_ID,
|
|
259
|
+
referralTokenAccount: null,
|
|
260
|
+
poolState,
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Create transaction (TxBuilder returns Promise<Transaction>)
|
|
264
|
+
let transaction: Transaction;
|
|
265
|
+
if (cpAmmSwapMode === CpAmmSwapMode.ExactOut) {
|
|
266
|
+
transaction = await cpAmm.swap2({
|
|
267
|
+
...swapBaseParams,
|
|
268
|
+
swapMode: cpAmmSwapMode,
|
|
269
|
+
amountOut: amountIn,
|
|
270
|
+
maximumAmountIn: quote.maxSwapInAmount || amountIn.muln(2), // 2x as max
|
|
271
|
+
});
|
|
272
|
+
} else {
|
|
273
|
+
transaction = await cpAmm.swap2({
|
|
274
|
+
...swapBaseParams,
|
|
275
|
+
swapMode: cpAmmSwapMode,
|
|
276
|
+
amountIn,
|
|
277
|
+
minimumAmountOut: quote.minSwapOutAmount || new BN(0),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Compile transaction with ALT if configured
|
|
282
|
+
const compiledTx = await sdk.compileTransactionWithALT(
|
|
283
|
+
transaction,
|
|
284
|
+
params.owner
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
transaction: compiledTx,
|
|
289
|
+
amountIn: quote.swapInAmount?.toString() || amountIn.toString(),
|
|
290
|
+
expectedAmountOut: quote.swapOutAmount?.toString() || "0",
|
|
291
|
+
minimumAmountOut: quote.minSwapOutAmount?.toString() || "0",
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Not migrated, use DBC swap
|
|
296
|
+
const poolAccount = await client.state.getPoolByBaseMint(tokenPubkey);
|
|
297
|
+
if (!poolAccount) {
|
|
298
|
+
throw new Error(`Pool not found for token: ${params.tokenAddress}`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Get quote first for minimumAmountOut
|
|
302
|
+
// In PartialFill mode, continue even if insufficient liquidity
|
|
303
|
+
let quote: SwapQuoteResponse;
|
|
304
|
+
try {
|
|
305
|
+
quote = await getSwapQuote(
|
|
306
|
+
sdk,
|
|
307
|
+
params.tokenAddress,
|
|
308
|
+
amountIn,
|
|
309
|
+
false,
|
|
310
|
+
slippageBps
|
|
311
|
+
);
|
|
312
|
+
} catch (err: any) {
|
|
313
|
+
if (swapMode === SwapMode.PartialFill && err.message?.includes("Insufficient Liquidity")) {
|
|
314
|
+
// PartialFill mode: set minimumAmountOut to 0 when insufficient liquidity
|
|
315
|
+
console.warn("⚠️ Insufficient liquidity, using PartialFill mode to accept any available amount");
|
|
316
|
+
quote = {
|
|
317
|
+
amountIn: amountIn.toString(),
|
|
318
|
+
outputAmount: "0",
|
|
319
|
+
minimumAmountOut: "0",
|
|
320
|
+
nextSqrtPrice: "0",
|
|
321
|
+
tradingFee: "0",
|
|
322
|
+
protocolFee: "0",
|
|
323
|
+
referralFee: "0",
|
|
324
|
+
};
|
|
325
|
+
} else {
|
|
326
|
+
throw err;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Use swap2 method to support different swap modes
|
|
331
|
+
let transaction: Transaction;
|
|
332
|
+
|
|
333
|
+
if (swapMode === SwapMode.PartialFill) {
|
|
334
|
+
transaction = await client.pool.swap2({
|
|
335
|
+
owner: params.owner,
|
|
336
|
+
pool: poolAccount.publicKey,
|
|
337
|
+
swapBaseForQuote: false,
|
|
338
|
+
referralTokenAccount: null,
|
|
339
|
+
swapMode: SwapMode.PartialFill,
|
|
340
|
+
amountIn,
|
|
341
|
+
minimumAmountOut: new BN(quote.minimumAmountOut),
|
|
342
|
+
});
|
|
343
|
+
} else if (swapMode === SwapMode.ExactOut) {
|
|
344
|
+
transaction = await client.pool.swap2({
|
|
345
|
+
owner: params.owner,
|
|
346
|
+
pool: poolAccount.publicKey,
|
|
347
|
+
swapBaseForQuote: false,
|
|
348
|
+
referralTokenAccount: null,
|
|
349
|
+
swapMode: SwapMode.ExactOut,
|
|
350
|
+
amountOut: new BN(quote.outputAmount),
|
|
351
|
+
maximumAmountIn: amountIn,
|
|
352
|
+
});
|
|
353
|
+
} else {
|
|
354
|
+
// SwapMode.ExactIn
|
|
355
|
+
transaction = await client.pool.swap2({
|
|
356
|
+
owner: params.owner,
|
|
357
|
+
pool: poolAccount.publicKey,
|
|
358
|
+
swapBaseForQuote: false,
|
|
359
|
+
referralTokenAccount: null,
|
|
360
|
+
swapMode: SwapMode.ExactIn,
|
|
361
|
+
amountIn,
|
|
362
|
+
minimumAmountOut: new BN(quote.minimumAmountOut),
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Compile transaction with ALT if configured
|
|
367
|
+
const compiledTx = await sdk.compileTransactionWithALT(
|
|
368
|
+
transaction,
|
|
369
|
+
params.owner
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
transaction: compiledTx,
|
|
374
|
+
amountIn: amountIn.toString(),
|
|
375
|
+
expectedAmountOut: quote.outputAmount,
|
|
376
|
+
minimumAmountOut: quote.minimumAmountOut,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export interface SellTokenParams {
|
|
381
|
+
tokenAddress: string;
|
|
382
|
+
amountInToken: number;
|
|
383
|
+
owner: PublicKey;
|
|
384
|
+
tokenDecimals?: number;
|
|
385
|
+
slippageBps?: number;
|
|
386
|
+
/** Swap mode, defaults to PartialFill (recommended) */
|
|
387
|
+
swapMode?: SwapMode;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export interface SellTokenResult {
|
|
391
|
+
/** Unsigned transaction (returns VersionedTransaction if ALT is configured) */
|
|
392
|
+
transaction: Transaction | VersionedTransaction;
|
|
393
|
+
/** Input amount in token's smallest unit */
|
|
394
|
+
amountIn: string;
|
|
395
|
+
/** Expected output amount in lamports */
|
|
396
|
+
expectedAmountOut: string;
|
|
397
|
+
/** Minimum output amount in lamports */
|
|
398
|
+
minimumAmountOut: string;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Create sell token transaction (returns unsigned transaction)
|
|
403
|
+
* @param sdk NaraSDK SDK instance
|
|
404
|
+
* @param params Sell parameters
|
|
405
|
+
* @returns Unsigned transaction and related information
|
|
406
|
+
*/
|
|
407
|
+
export async function sellToken(
|
|
408
|
+
sdk: NaraSDK,
|
|
409
|
+
params: SellTokenParams
|
|
410
|
+
): Promise<SellTokenResult> {
|
|
411
|
+
const client = sdk.getClient();
|
|
412
|
+
const connection = sdk.getConnection();
|
|
413
|
+
|
|
414
|
+
const tokenPubkey = new PublicKey(params.tokenAddress);
|
|
415
|
+
const tokenDecimals = params.tokenDecimals ?? 6;
|
|
416
|
+
const amountIn = new BN(params.amountInToken * 10 ** tokenDecimals);
|
|
417
|
+
const slippageBps = params.slippageBps ?? 100;
|
|
418
|
+
const swapMode = params.swapMode ?? SwapMode.PartialFill; // Default to PartialFill mode
|
|
419
|
+
|
|
420
|
+
// Check if pool has migrated to DAMM V2
|
|
421
|
+
const migrationInfo = await checkPoolMigration(sdk, params.tokenAddress);
|
|
422
|
+
|
|
423
|
+
if (migrationInfo.isMigrated && migrationInfo.dammV2Pool) {
|
|
424
|
+
console.log("🚀 Pool launched to DAMM V2, using CP-AMM for swap");
|
|
425
|
+
|
|
426
|
+
// Use CP-AMM SDK for swap
|
|
427
|
+
const cpAmm = new CpAmm(connection);
|
|
428
|
+
|
|
429
|
+
// Get pool state
|
|
430
|
+
const poolState = await cpAmm.fetchPoolState(migrationInfo.dammV2Pool);
|
|
431
|
+
|
|
432
|
+
// Get current point (based on pool's activation type)
|
|
433
|
+
const currentPoint = await getCurrentPoint(
|
|
434
|
+
connection,
|
|
435
|
+
poolState.activationType as ActivationType
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
// Determine input/output tokens (Token = tokenB, SOL = tokenA)
|
|
439
|
+
const isAToB = poolState.tokenAMint.equals(tokenPubkey); // If token is A, then A->B (Token->SOL)
|
|
440
|
+
const inputTokenMint = isAToB ? poolState.tokenAMint : poolState.tokenBMint;
|
|
441
|
+
|
|
442
|
+
// Convert SwapMode
|
|
443
|
+
const cpAmmSwapMode =
|
|
444
|
+
swapMode === SwapMode.PartialFill
|
|
445
|
+
? CpAmmSwapMode.PartialFill
|
|
446
|
+
: swapMode === SwapMode.ExactOut
|
|
447
|
+
? CpAmmSwapMode.ExactOut
|
|
448
|
+
: CpAmmSwapMode.ExactIn;
|
|
449
|
+
|
|
450
|
+
// 构建报价参数
|
|
451
|
+
const quoteBaseParams = {
|
|
452
|
+
inputTokenMint,
|
|
453
|
+
slippage: slippageBps / 10000,
|
|
454
|
+
currentPoint,
|
|
455
|
+
poolState,
|
|
456
|
+
tokenADecimal: 9, // SOL decimals
|
|
457
|
+
tokenBDecimal: tokenDecimals,
|
|
458
|
+
hasReferral: false,
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// Calculate quote
|
|
462
|
+
let quote: any;
|
|
463
|
+
if (cpAmmSwapMode === CpAmmSwapMode.ExactOut) {
|
|
464
|
+
quote = cpAmm.getQuote2({
|
|
465
|
+
...quoteBaseParams,
|
|
466
|
+
swapMode: cpAmmSwapMode,
|
|
467
|
+
amountOut: amountIn, // ExactOut: desired output amount
|
|
468
|
+
});
|
|
469
|
+
} else {
|
|
470
|
+
quote = cpAmm.getQuote2({
|
|
471
|
+
...quoteBaseParams,
|
|
472
|
+
swapMode: cpAmmSwapMode,
|
|
473
|
+
amountIn,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Build swap parameters
|
|
478
|
+
const swapBaseParams = {
|
|
479
|
+
payer: params.owner,
|
|
480
|
+
pool: migrationInfo.dammV2Pool,
|
|
481
|
+
inputTokenMint,
|
|
482
|
+
outputTokenMint: isAToB ? poolState.tokenBMint : poolState.tokenAMint,
|
|
483
|
+
tokenAMint: poolState.tokenAMint,
|
|
484
|
+
tokenBMint: poolState.tokenBMint,
|
|
485
|
+
tokenAVault: poolState.tokenAVault,
|
|
486
|
+
tokenBVault: poolState.tokenBVault,
|
|
487
|
+
tokenAProgram: TOKEN_PROGRAM_ID,
|
|
488
|
+
tokenBProgram: TOKEN_PROGRAM_ID,
|
|
489
|
+
referralTokenAccount: null,
|
|
490
|
+
poolState,
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// Create transaction
|
|
494
|
+
let transaction: Transaction;
|
|
495
|
+
if (cpAmmSwapMode === CpAmmSwapMode.ExactOut) {
|
|
496
|
+
transaction = await cpAmm.swap2({
|
|
497
|
+
...swapBaseParams,
|
|
498
|
+
swapMode: cpAmmSwapMode,
|
|
499
|
+
amountOut: amountIn,
|
|
500
|
+
maximumAmountIn: quote.maxSwapInAmount || amountIn.muln(2),
|
|
501
|
+
});
|
|
502
|
+
} else {
|
|
503
|
+
transaction = await cpAmm.swap2({
|
|
504
|
+
...swapBaseParams,
|
|
505
|
+
swapMode: cpAmmSwapMode,
|
|
506
|
+
amountIn,
|
|
507
|
+
minimumAmountOut: quote.minSwapOutAmount || new BN(0),
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Compile transaction with ALT if configured
|
|
512
|
+
const compiledTx = await sdk.compileTransactionWithALT(
|
|
513
|
+
transaction,
|
|
514
|
+
params.owner
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
return {
|
|
518
|
+
transaction: compiledTx,
|
|
519
|
+
amountIn: quote.swapInAmount?.toString() || amountIn.toString(),
|
|
520
|
+
expectedAmountOut: quote.swapOutAmount?.toString() || "0",
|
|
521
|
+
minimumAmountOut: quote.minSwapOutAmount?.toString() || "0",
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Not migrated, use DBC swap
|
|
526
|
+
const poolAccount = await client.state.getPoolByBaseMint(tokenPubkey);
|
|
527
|
+
if (!poolAccount) {
|
|
528
|
+
throw new Error(`Pool not found for token: ${params.tokenAddress}`);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Get quote first for minimumAmountOut
|
|
532
|
+
// In PartialFill mode, continue even if insufficient liquidity
|
|
533
|
+
let quote: SwapQuoteResponse;
|
|
534
|
+
try {
|
|
535
|
+
quote = await getSwapQuote(
|
|
536
|
+
sdk,
|
|
537
|
+
params.tokenAddress,
|
|
538
|
+
amountIn,
|
|
539
|
+
true,
|
|
540
|
+
slippageBps
|
|
541
|
+
);
|
|
542
|
+
} catch (err: any) {
|
|
543
|
+
if (swapMode === SwapMode.PartialFill && err.message?.includes("Insufficient Liquidity")) {
|
|
544
|
+
// PartialFill mode: set minimumAmountOut to 0 when insufficient liquidity
|
|
545
|
+
console.warn("⚠️ Insufficient liquidity, using PartialFill mode to accept any available amount");
|
|
546
|
+
quote = {
|
|
547
|
+
amountIn: amountIn.toString(),
|
|
548
|
+
outputAmount: "0",
|
|
549
|
+
minimumAmountOut: "0",
|
|
550
|
+
nextSqrtPrice: "0",
|
|
551
|
+
tradingFee: "0",
|
|
552
|
+
protocolFee: "0",
|
|
553
|
+
referralFee: "0",
|
|
554
|
+
};
|
|
555
|
+
} else {
|
|
556
|
+
throw err;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Use swap2 method to support different swap modes
|
|
561
|
+
let transaction: Transaction;
|
|
562
|
+
|
|
563
|
+
if (swapMode === SwapMode.PartialFill) {
|
|
564
|
+
transaction = await client.pool.swap2({
|
|
565
|
+
owner: params.owner,
|
|
566
|
+
pool: poolAccount.publicKey,
|
|
567
|
+
swapBaseForQuote: true, // Token -> SOL (sell)
|
|
568
|
+
referralTokenAccount: null,
|
|
569
|
+
swapMode: SwapMode.PartialFill,
|
|
570
|
+
amountIn,
|
|
571
|
+
minimumAmountOut: new BN(quote.minimumAmountOut),
|
|
572
|
+
});
|
|
573
|
+
} else if (swapMode === SwapMode.ExactOut) {
|
|
574
|
+
transaction = await client.pool.swap2({
|
|
575
|
+
owner: params.owner,
|
|
576
|
+
pool: poolAccount.publicKey,
|
|
577
|
+
swapBaseForQuote: true,
|
|
578
|
+
referralTokenAccount: null,
|
|
579
|
+
swapMode: SwapMode.ExactOut,
|
|
580
|
+
amountOut: new BN(quote.outputAmount),
|
|
581
|
+
maximumAmountIn: amountIn,
|
|
582
|
+
});
|
|
583
|
+
} else {
|
|
584
|
+
// SwapMode.ExactIn
|
|
585
|
+
transaction = await client.pool.swap2({
|
|
586
|
+
owner: params.owner,
|
|
587
|
+
pool: poolAccount.publicKey,
|
|
588
|
+
swapBaseForQuote: true,
|
|
589
|
+
referralTokenAccount: null,
|
|
590
|
+
swapMode: SwapMode.ExactIn,
|
|
591
|
+
amountIn,
|
|
592
|
+
minimumAmountOut: new BN(quote.minimumAmountOut),
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Compile transaction with ALT if configured
|
|
597
|
+
const compiledTx = await sdk.compileTransactionWithALT(
|
|
598
|
+
transaction,
|
|
599
|
+
params.owner
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
return {
|
|
603
|
+
transaction: compiledTx,
|
|
604
|
+
amountIn: amountIn.toString(),
|
|
605
|
+
expectedAmountOut: quote.outputAmount,
|
|
606
|
+
minimumAmountOut: quote.minimumAmountOut,
|
|
607
|
+
};
|
|
608
|
+
}
|