@raydium-io/raydium-sdk-v2
Version:
An SDK for building applications on top of Raydium.
927 lines (827 loc) • 33.4 kB
text/typescript
import { PublicKey } from "@solana/web3.js";
import { NATIVE_MINT, TOKEN_PROGRAM_ID, AccountLayout } from "@solana/spl-token";
import { ApiV3PoolInfoConcentratedItem, ApiV3PoolInfoStandardItemCpmm, CpmmKeys } from "@/api/type";
import { Percent } from "@/module";
import { BN_ZERO } from "@/common/bignumber";
import { getATAAddress } from "@/common/pda";
import { WSOLMint } from "@/common/pubKey";
import { InstructionType, TxVersion } from "@/common/txTool/txType";
import { MakeTxData } from "@/common/txTool/txTool";
import { CurveCalculator } from "./curve/calculator";
import ModuleBase, { ModuleBaseProps } from "../moduleBase";
import {
CreateCpmmPoolParam,
CreateCpmmPoolAddress,
AddCpmmLiquidityParams,
WithdrawCpmmLiquidityParams,
CpmmSwapParams,
ComputePairAmountParams,
CpmmRpcData,
CpmmComputeData,
} from "./type";
import { getCreatePoolKeys, getPdaObservationId } from "./pda";
import {
makeCreateCpmmPoolInInstruction,
makeDepositCpmmInInstruction,
makeWithdrawCpmmInInstruction,
makeSwapCpmmBaseInInInstruction,
makeSwapCpmmBaseOutInInstruction,
} from "./instruction";
import BN from "bn.js";
import { CpmmPoolInfoLayout, CpmmConfigInfoLayout } from "./layout";
import Decimal from "decimal.js";
import { fetchMultipleMintInfos, getMultipleAccountsInfoWithCustomFlags, getTransferAmountFeeV2 } from "@/common";
import { GetTransferAmountFee, ReturnTypeFetchMultipleMintInfos } from "@/raydium/type";
import { toApiV3Token, toFeeConfig } from "../token";
import { getPdaPoolAuthority } from "./pda";
export default class CpmmModule extends ModuleBase {
constructor(params: ModuleBaseProps) {
super(params);
}
public async load(): Promise<void> {
this.checkDisabled();
}
public async getCpmmPoolKeys(poolId: string): Promise<CpmmKeys> {
return ((await this.scope.api.fetchPoolKeysById({ idList: [poolId] })) as CpmmKeys[])[0];
}
public async getRpcPoolInfo(poolId: string, fetchConfigInfo?: boolean): Promise<CpmmRpcData> {
return (await this.getRpcPoolInfos([poolId], fetchConfigInfo))[poolId];
}
public async getRpcPoolInfos(
poolIds: string[],
fetchConfigInfo?: boolean,
): Promise<{
[poolId: string]: CpmmRpcData;
}> {
const accounts = await getMultipleAccountsInfoWithCustomFlags(
this.scope.connection,
poolIds.map((i) => ({ pubkey: new PublicKey(i) })),
);
const poolInfos: { [poolId: string]: ReturnType<typeof CpmmPoolInfoLayout.decode> & { programId: PublicKey } } = {};
const needFetchConfigId = new Set<string>();
const needFetchVaults: PublicKey[] = [];
for (let i = 0; i < poolIds.length; i++) {
const item = accounts[i];
if (item.accountInfo === null) throw Error("fetch pool info error: " + String(poolIds[i]));
const rpc = CpmmPoolInfoLayout.decode(item.accountInfo.data);
poolInfos[String(poolIds[i])] = {
...rpc,
programId: item.accountInfo.owner,
};
needFetchConfigId.add(String(rpc.configId));
needFetchVaults.push(rpc.vaultA, rpc.vaultB);
}
const configInfo: { [configId: string]: ReturnType<typeof CpmmConfigInfoLayout.decode> } = {};
if (fetchConfigInfo) {
const configIds = [...needFetchConfigId];
const configState = await getMultipleAccountsInfoWithCustomFlags(
this.scope.connection,
configIds.map((i) => ({ pubkey: new PublicKey(i) })),
);
for (let i = 0; i < configIds.length; i++) {
const configItemInfo = configState[i].accountInfo;
if (configItemInfo === null) throw Error("fetch pool config error: " + configIds[i]);
configInfo[configIds[i]] = CpmmConfigInfoLayout.decode(configItemInfo.data);
}
}
const vaultInfo: { [vaultId: string]: BN } = {};
const vaultAccountInfo = await getMultipleAccountsInfoWithCustomFlags(
this.scope.connection,
needFetchVaults.map((i) => ({ pubkey: new PublicKey(i) })),
);
for (let i = 0; i < needFetchVaults.length; i++) {
const vaultItemInfo = vaultAccountInfo[i].accountInfo;
if (vaultItemInfo === null) throw Error("fetch vault info error: " + needFetchVaults[i]);
vaultInfo[String(needFetchVaults[i])] = new BN(AccountLayout.decode(vaultItemInfo.data).amount.toString());
}
const returnData: { [poolId: string]: CpmmRpcData } = {};
for (const [id, info] of Object.entries(poolInfos)) {
const baseReserve = vaultInfo[info.vaultA.toString()].sub(info.protocolFeesMintA).sub(info.fundFeesMintA);
const quoteReserve = vaultInfo[info.vaultB.toString()].sub(info.protocolFeesMintB).sub(info.fundFeesMintB);
returnData[id] = {
...info,
baseReserve,
quoteReserve,
vaultAAmount: vaultInfo[info.vaultA.toString()],
vaultBAmount: vaultInfo[info.vaultB.toString()],
configInfo: configInfo[info.configId.toString()],
poolPrice: new Decimal(quoteReserve.toString())
.div(new Decimal(10).pow(info.mintDecimalB))
.div(new Decimal(baseReserve.toString()).div(new Decimal(10).pow(info.mintDecimalA))),
};
}
return returnData;
}
public toComputePoolInfos({
pools,
mintInfos,
}: {
pools: Record<string, CpmmRpcData>;
mintInfos: ReturnTypeFetchMultipleMintInfos;
}): Record<string, CpmmComputeData> {
return Object.keys(pools).reduce((acc, cur) => {
const pool = pools[cur];
const [mintA, mintB] = [pool.mintA.toBase58(), pool.mintB.toBase58()];
return {
...acc,
[cur]: {
...pool,
id: new PublicKey(cur),
configInfo: pool.configInfo!,
version: 7 as const,
authority: getPdaPoolAuthority(pool.programId).publicKey,
mintA: toApiV3Token({
address: mintA,
decimals: pool.mintDecimalA,
programId: pool.mintProgramA.toBase58(),
extensions: {
feeConfig: mintInfos[mintA]?.feeConfig ? toFeeConfig(mintInfos[mintA]?.feeConfig) : undefined,
},
}),
mintB: toApiV3Token({
address: mintB,
decimals: pool.mintDecimalB,
programId: pool.mintProgramB.toBase58(),
extensions: {
feeConfig: mintInfos[mintB]?.feeConfig ? toFeeConfig(mintInfos[mintB]?.feeConfig) : undefined,
},
}),
},
};
}, {} as Record<string, CpmmComputeData>);
}
public async getPoolInfoFromRpc(poolId: string): Promise<{
poolInfo: ApiV3PoolInfoStandardItemCpmm;
poolKeys: CpmmKeys;
rpcData: CpmmRpcData;
}> {
const rpcData = await this.getRpcPoolInfo(poolId, true);
const mintInfos = await fetchMultipleMintInfos({
connection: this.scope.connection,
mints: [rpcData.mintA, rpcData.mintB],
});
const mintA = toApiV3Token({
address: rpcData.mintA.toBase58(),
decimals: rpcData.mintDecimalA,
programId: rpcData.mintProgramA.toBase58(),
extensions: {
feeConfig: mintInfos[rpcData.mintA.toBase58()].feeConfig
? toFeeConfig(mintInfos[rpcData.mintA.toBase58()].feeConfig)
: undefined,
},
});
const mintB = toApiV3Token({
address: rpcData.mintB.toBase58(),
decimals: rpcData.mintDecimalB,
programId: rpcData.mintProgramB.toBase58(),
extensions: {
feeConfig: mintInfos[rpcData.mintB.toBase58()].feeConfig
? toFeeConfig(mintInfos[rpcData.mintB.toBase58()].feeConfig)
: undefined,
},
});
const lpMint = toApiV3Token({
address: rpcData.mintLp.toBase58(),
decimals: rpcData.lpDecimals,
programId: TOKEN_PROGRAM_ID.toBase58(),
});
const configInfo = {
id: rpcData.configId.toBase58(),
index: rpcData.configInfo!.index,
protocolFeeRate: rpcData.configInfo!.protocolFeeRate.toNumber(),
tradeFeeRate: rpcData.configInfo!.tradeFeeRate.toNumber(),
fundFeeRate: rpcData.configInfo!.fundFeeRate.toNumber(),
createPoolFee: rpcData.configInfo!.createPoolFee.toString(),
};
const mockRewardData = {
volume: 0,
volumeQuote: 0,
volumeFee: 0,
apr: 0,
feeApr: 0,
priceMin: 0,
priceMax: 0,
rewardApr: [],
};
return {
poolInfo: {
programId: rpcData.programId.toBase58(),
id: poolId,
type: "Standard",
lpMint,
lpPrice: 0,
lpAmount: rpcData.lpAmount.toNumber(),
config: configInfo,
mintA,
mintB,
rewardDefaultInfos: [],
rewardDefaultPoolInfos: "Ecosystem",
price: rpcData.poolPrice.toNumber(),
mintAmountA: new Decimal(rpcData.vaultAAmount.toString()).div(10 ** mintA.decimals).toNumber(),
mintAmountB: new Decimal(rpcData.vaultBAmount.toString()).div(10 ** mintB.decimals).toNumber(),
feeRate: rpcData.configInfo!.tradeFeeRate.toNumber(),
openTime: rpcData.openTime.toString(),
tvl: 0,
day: mockRewardData,
week: mockRewardData,
month: mockRewardData,
pooltype: [],
farmUpcomingCount: 0,
farmOngoingCount: 0,
farmFinishedCount: 0,
},
poolKeys: {
programId: rpcData.programId.toBase58(),
id: poolId,
mintA,
mintB,
openTime: rpcData.openTime.toString(),
vault: { A: rpcData.vaultA.toBase58(), B: rpcData.vaultB.toBase58() },
authority: getPdaPoolAuthority(rpcData.programId).publicKey.toBase58(),
mintLp: lpMint,
config: configInfo,
},
rpcData,
};
}
public async createPool<T extends TxVersion>({
programId,
poolFeeAccount,
startTime,
ownerInfo,
associatedOnly = false,
checkCreateATAOwner = false,
txVersion,
feeConfig,
computeBudgetConfig,
...params
}: CreateCpmmPoolParam<T>): Promise<MakeTxData<T, { address: CreateCpmmPoolAddress }>> {
const payer = ownerInfo.feePayer || this.scope.owner?.publicKey;
const isFront = new BN(new PublicKey(params.mintA.address).toBuffer()).lte(
new BN(new PublicKey(params.mintB.address).toBuffer()),
);
const [mintA, mintB] = isFront ? [params.mintA, params.mintB] : [params.mintB, params.mintA];
const [mintAAmount, mintBAmount] = isFront
? [params.mintAAmount, params.mintBAmount]
: [params.mintBAmount, params.mintAAmount];
const mintAUseSOLBalance = ownerInfo.useSOLBalance && mintA.address === NATIVE_MINT.toBase58();
const mintBUseSOLBalance = ownerInfo.useSOLBalance && mintB.address === NATIVE_MINT.toBase58();
const [mintAPubkey, mintBPubkey] = [new PublicKey(mintA.address), new PublicKey(mintB.address)];
const txBuilder = this.createTxBuilder();
const { account: userVaultA, instructionParams: userVaultAInstruction } =
await this.scope.account.getOrCreateTokenAccount({
mint: mintAPubkey,
tokenProgram: mintA.programId,
owner: this.scope.ownerPubKey,
createInfo: mintAUseSOLBalance
? {
payer: payer!,
amount: mintAAmount,
}
: undefined,
notUseTokenAccount: mintAUseSOLBalance,
skipCloseAccount: !mintAUseSOLBalance,
associatedOnly: mintAUseSOLBalance ? false : associatedOnly,
checkCreateATAOwner,
});
txBuilder.addInstruction(userVaultAInstruction || {});
const { account: userVaultB, instructionParams: userVaultBInstruction } =
await this.scope.account.getOrCreateTokenAccount({
mint: new PublicKey(mintB.address),
tokenProgram: mintB.programId,
owner: this.scope.ownerPubKey,
createInfo: mintBUseSOLBalance
? {
payer: payer!,
amount: mintBAmount,
}
: undefined,
notUseTokenAccount: mintBUseSOLBalance,
skipCloseAccount: !mintBUseSOLBalance,
associatedOnly: mintBUseSOLBalance ? false : associatedOnly,
checkCreateATAOwner,
});
txBuilder.addInstruction(userVaultBInstruction || {});
if (userVaultA === undefined || userVaultB === undefined) throw Error("you don't has some token account");
const poolKeys = getCreatePoolKeys({
programId,
configId: new PublicKey(feeConfig.id),
mintA: mintAPubkey,
mintB: mintBPubkey,
});
txBuilder.addInstruction({
instructions: [
makeCreateCpmmPoolInInstruction(
programId,
this.scope.ownerPubKey,
new PublicKey(feeConfig.id),
poolKeys.authority,
poolKeys.poolId,
mintAPubkey,
mintBPubkey,
poolKeys.lpMint,
userVaultA,
userVaultB,
getATAAddress(this.scope.ownerPubKey, poolKeys.lpMint).publicKey,
poolKeys.vaultA,
poolKeys.vaultB,
poolFeeAccount,
new PublicKey(mintA.programId ?? TOKEN_PROGRAM_ID),
new PublicKey(mintB.programId ?? TOKEN_PROGRAM_ID),
poolKeys.observationId,
mintAAmount,
mintBAmount,
startTime,
),
],
instructionTypes: [InstructionType.CpmmCreatePool],
});
txBuilder.addCustomComputeBudget(computeBudgetConfig);
return txBuilder.versionBuild({
txVersion,
extInfo: {
address: { ...poolKeys, mintA, mintB, programId, poolFeeAccount, feeConfig },
},
}) as Promise<MakeTxData<T, { address: CreateCpmmPoolAddress }>>;
}
public async addLiquidity<T extends TxVersion>(params: AddCpmmLiquidityParams<T>): Promise<MakeTxData<T>> {
const {
poolInfo,
poolKeys: propPoolKeys,
inputAmount,
baseIn,
slippage,
computeResult,
computeBudgetConfig,
config,
txVersion,
} = params;
if (this.scope.availability.addStandardPosition === false)
this.logAndCreateError("add liquidity feature disabled in your region");
if (inputAmount.isZero())
this.logAndCreateError("amounts must greater than zero", "amountInA", {
amountInA: inputAmount.toString(),
});
const { account } = this.scope;
const { bypassAssociatedCheck, checkCreateATAOwner } = {
// default
...{ bypassAssociatedCheck: false, checkCreateATAOwner: false },
// custom
...config,
};
const rpcPoolData = computeResult ? undefined : await this.getRpcPoolInfo(poolInfo.id);
const {
liquidity,
inputAmountFee,
anotherAmount: _anotherAmount,
} = computeResult ||
this.computePairAmount({
poolInfo: {
...poolInfo,
lpAmount: new Decimal(rpcPoolData!.lpAmount.toString()).div(10 ** poolInfo.lpMint.decimals).toNumber(),
},
baseReserve: rpcPoolData!.baseReserve,
quoteReserve: rpcPoolData!.quoteReserve,
slippage: new Percent(0),
baseIn,
epochInfo: await this.scope.fetchEpochInfo(),
amount: new Decimal(inputAmount.toString()).div(
10 ** (baseIn ? poolInfo.mintA.decimals : poolInfo.mintB.decimals),
),
});
const anotherAmount = _anotherAmount.amount;
const mintAUseSOLBalance = poolInfo.mintA.address === NATIVE_MINT.toString();
const mintBUseSOLBalance = poolInfo.mintB.address === NATIVE_MINT.toString();
const txBuilder = this.createTxBuilder();
const [mintA, mintB] = [new PublicKey(poolInfo.mintA.address), new PublicKey(poolInfo.mintB.address)];
const { account: tokenAccountA, instructionParams: _tokenAccountAInstruction } =
await this.scope.account.getOrCreateTokenAccount({
tokenProgram: poolInfo.mintA.programId,
mint: new PublicKey(poolInfo.mintA.address),
owner: this.scope.ownerPubKey,
createInfo:
mintAUseSOLBalance || (baseIn ? inputAmount : anotherAmount).isZero()
? {
payer: this.scope.ownerPubKey,
amount: baseIn ? inputAmount : anotherAmount,
}
: undefined,
skipCloseAccount: !mintAUseSOLBalance,
notUseTokenAccount: mintAUseSOLBalance,
associatedOnly: false,
checkCreateATAOwner,
});
txBuilder.addInstruction(_tokenAccountAInstruction || {});
const { account: tokenAccountB, instructionParams: _tokenAccountBInstruction } =
await this.scope.account.getOrCreateTokenAccount({
tokenProgram: poolInfo.mintB.programId,
mint: new PublicKey(poolInfo.mintB.address),
owner: this.scope.ownerPubKey,
createInfo:
mintBUseSOLBalance || (baseIn ? anotherAmount : inputAmount).isZero()
? {
payer: this.scope.ownerPubKey,
amount: baseIn ? anotherAmount : inputAmount,
}
: undefined,
skipCloseAccount: !mintBUseSOLBalance,
notUseTokenAccount: mintBUseSOLBalance,
associatedOnly: false,
checkCreateATAOwner,
});
txBuilder.addInstruction(_tokenAccountBInstruction || {});
if (!tokenAccountA && !tokenAccountB)
this.logAndCreateError("cannot found target token accounts", "tokenAccounts", account.tokenAccounts);
const lpTokenAccount = await account.getCreatedTokenAccount({
mint: new PublicKey(poolInfo.lpMint.address),
});
const { tokenAccount: _lpTokenAccount, ...lpInstruction } = await account.handleTokenAccount({
side: "out",
amount: 0,
mint: new PublicKey(poolInfo.lpMint.address),
tokenAccount: lpTokenAccount,
bypassAssociatedCheck,
checkCreateATAOwner,
});
txBuilder.addInstruction(lpInstruction);
const poolKeys = propPoolKeys ?? (await this.getCpmmPoolKeys(poolInfo.id));
const _slippage = new Percent(new BN(1)).sub(slippage);
txBuilder.addInstruction({
instructions: [
makeDepositCpmmInInstruction(
new PublicKey(poolInfo.programId),
this.scope.ownerPubKey,
new PublicKey(poolKeys.authority),
new PublicKey(poolInfo.id),
_lpTokenAccount!,
tokenAccountA!,
tokenAccountB!,
new PublicKey(poolKeys.vault.A),
new PublicKey(poolKeys.vault.B),
mintA,
mintB,
new PublicKey(poolInfo.lpMint.address),
computeResult ? computeResult?.liquidity : _slippage.mul(liquidity).quotient,
baseIn ? inputAmountFee.amount : anotherAmount,
baseIn ? anotherAmount : inputAmountFee.amount,
),
],
instructionTypes: [InstructionType.CpmmAddLiquidity],
lookupTableAddress: poolKeys.lookupTableAccount ? [poolKeys.lookupTableAccount] : [],
});
txBuilder.addCustomComputeBudget(computeBudgetConfig);
return txBuilder.versionBuild({ txVersion }) as Promise<MakeTxData<T>>;
}
public async withdrawLiquidity<T extends TxVersion>(params: WithdrawCpmmLiquidityParams<T>): Promise<MakeTxData<T>> {
const { poolInfo, poolKeys: propPoolKeys, lpAmount, slippage, computeBudgetConfig, txVersion } = params;
if (this.scope.availability.addStandardPosition === false)
this.logAndCreateError("add liquidity feature disabled in your region");
const _slippage = new Percent(new BN(1)).sub(slippage);
const rpcPoolData = await this.getRpcPoolInfo(poolInfo.id);
const [amountMintA, amountMintB] = [
_slippage.mul(lpAmount.mul(rpcPoolData.baseReserve).div(rpcPoolData.lpAmount)).quotient,
_slippage.mul(lpAmount.mul(rpcPoolData.quoteReserve).div(rpcPoolData.lpAmount)).quotient,
];
const epochInfo = await this.scope.fetchEpochInfo();
const [mintAAmountFee, mintBAmountFee] = [
getTransferAmountFeeV2(amountMintA, poolInfo.mintA.extensions.feeConfig, epochInfo, false),
getTransferAmountFeeV2(amountMintB, poolInfo.mintB.extensions.feeConfig, epochInfo, false),
];
const { account } = this.scope;
const txBuilder = this.createTxBuilder();
const [mintA, mintB] = [new PublicKey(poolInfo.mintA.address), new PublicKey(poolInfo.mintB.address)];
const mintAUseSOLBalance = mintA.equals(WSOLMint);
const mintBUseSOLBalance = mintB.equals(WSOLMint);
let tokenAccountA: PublicKey | undefined = undefined;
let tokenAccountB: PublicKey | undefined = undefined;
const { account: _ownerTokenAccountA, instructionParams: accountAInstructions } =
await this.scope.account.getOrCreateTokenAccount({
tokenProgram: poolInfo.mintA.programId,
mint: new PublicKey(poolInfo.mintA.address),
notUseTokenAccount: mintAUseSOLBalance,
owner: this.scope.ownerPubKey,
createInfo: {
payer: this.scope.ownerPubKey,
amount: 0,
},
skipCloseAccount: !mintAUseSOLBalance,
associatedOnly: mintAUseSOLBalance ? false : true,
checkCreateATAOwner: false,
});
tokenAccountA = _ownerTokenAccountA;
accountAInstructions && txBuilder.addInstruction(accountAInstructions);
const { account: _ownerTokenAccountB, instructionParams: accountBInstructions } =
await this.scope.account.getOrCreateTokenAccount({
tokenProgram: poolInfo.mintB.programId,
mint: new PublicKey(poolInfo.mintB.address),
notUseTokenAccount: mintBUseSOLBalance,
owner: this.scope.ownerPubKey,
createInfo: {
payer: this.scope.ownerPubKey,
amount: 0,
},
skipCloseAccount: !mintBUseSOLBalance,
associatedOnly: mintBUseSOLBalance ? false : true,
checkCreateATAOwner: false,
});
tokenAccountB = _ownerTokenAccountB;
accountBInstructions && txBuilder.addInstruction(accountBInstructions);
if (!tokenAccountA || !tokenAccountB)
this.logAndCreateError("cannot found target token accounts", "tokenAccounts", account.tokenAccounts);
const lpTokenAccount = await account.getCreatedTokenAccount({
mint: new PublicKey(poolInfo.lpMint.address),
});
if (!lpTokenAccount)
this.logAndCreateError("cannot found lp token account", "tokenAccounts", account.tokenAccounts);
const poolKeys = propPoolKeys ?? (await this.getCpmmPoolKeys(poolInfo.id));
txBuilder.addInstruction({
instructions: [
makeWithdrawCpmmInInstruction(
new PublicKey(poolInfo.programId),
this.scope.ownerPubKey,
new PublicKey(poolKeys.authority),
new PublicKey(poolInfo.id),
lpTokenAccount!,
tokenAccountA!,
tokenAccountB!,
new PublicKey(poolKeys.vault.A),
new PublicKey(poolKeys.vault.B),
mintA,
mintB,
new PublicKey(poolInfo.lpMint.address),
lpAmount,
amountMintA.sub(mintAAmountFee.fee ?? new BN(0)),
amountMintB.sub(mintBAmountFee.fee ?? new BN(0)),
),
],
instructionTypes: [InstructionType.CpmmWithdrawLiquidity],
lookupTableAddress: poolKeys.lookupTableAccount ? [poolKeys.lookupTableAccount] : [],
});
txBuilder.addCustomComputeBudget(computeBudgetConfig);
return txBuilder.versionBuild({ txVersion }) as Promise<MakeTxData<T>>;
}
public async swap<T extends TxVersion>(params: CpmmSwapParams<T>): Promise<MakeTxData<T>> {
const {
poolInfo,
poolKeys: propPoolKeys,
baseIn,
fixedOut,
inputAmount,
swapResult,
slippage = 0,
config,
computeBudgetConfig,
txVersion,
} = params;
const { bypassAssociatedCheck, checkCreateATAOwner, associatedOnly } = {
// default
...{ bypassAssociatedCheck: false, checkCreateATAOwner: false, associatedOnly: true },
// custom
...config,
};
const txBuilder = this.createTxBuilder();
const [mintA, mintB] = [new PublicKey(poolInfo.mintA.address), new PublicKey(poolInfo.mintB.address)];
if (!fixedOut) {
swapResult.destinationAmountSwapped = swapResult.destinationAmountSwapped
.mul(new BN((1 - slippage) * 10000))
.div(new BN(10000));
} else {
swapResult.sourceAmountSwapped = swapResult.sourceAmountSwapped
.mul(new BN((1 + slippage) * 10000))
.div(new BN(10000));
}
const mintAUseSOLBalance = poolInfo.mintA.address === WSOLMint.toBase58();
const mintBUseSOLBalance = poolInfo.mintB.address === WSOLMint.toBase58();
const { account: mintATokenAcc, instructionParams: mintATokenAccInstruction } =
await this.scope.account.getOrCreateTokenAccount({
mint: mintA,
tokenProgram: new PublicKey(poolInfo.mintA.programId ?? TOKEN_PROGRAM_ID),
owner: this.scope.ownerPubKey,
createInfo:
mintAUseSOLBalance || !baseIn
? {
payer: this.scope.ownerPubKey,
amount: baseIn ? swapResult.sourceAmountSwapped : 0,
}
: undefined,
notUseTokenAccount: mintAUseSOLBalance,
skipCloseAccount: !mintAUseSOLBalance,
associatedOnly: mintAUseSOLBalance ? false : associatedOnly,
checkCreateATAOwner,
});
mintATokenAccInstruction && txBuilder.addInstruction(mintATokenAccInstruction);
const { account: mintBTokenAcc, instructionParams: mintBTokenAccInstruction } =
await this.scope.account.getOrCreateTokenAccount({
mint: mintB,
tokenProgram: new PublicKey(poolInfo.mintB.programId ?? TOKEN_PROGRAM_ID),
owner: this.scope.ownerPubKey,
createInfo:
mintBUseSOLBalance || baseIn
? {
payer: this.scope.ownerPubKey,
amount: baseIn ? 0 : swapResult.sourceAmountSwapped,
}
: undefined,
notUseTokenAccount: mintBUseSOLBalance,
skipCloseAccount: !mintBUseSOLBalance,
associatedOnly: mintBUseSOLBalance ? false : associatedOnly,
checkCreateATAOwner,
});
mintBTokenAccInstruction && txBuilder.addInstruction(mintBTokenAccInstruction);
if (!mintATokenAcc || !mintBTokenAcc)
this.logAndCreateError("user do not have token account", {
mintA: poolInfo.mintA.symbol || poolInfo.mintA.address,
mintB: poolInfo.mintB.symbol || poolInfo.mintB.address,
mintATokenAcc,
mintBTokenAcc,
mintAUseSOLBalance,
mintBUseSOLBalance,
associatedOnly,
});
const poolKeys = propPoolKeys ?? (await this.getCpmmPoolKeys(poolInfo.id));
txBuilder.addInstruction({
instructions: [
!fixedOut
? makeSwapCpmmBaseInInInstruction(
new PublicKey(poolInfo.programId),
this.scope.ownerPubKey,
new PublicKey(poolKeys.authority),
new PublicKey(poolKeys.config.id),
new PublicKey(poolInfo.id),
baseIn ? mintATokenAcc! : mintBTokenAcc!,
baseIn ? mintBTokenAcc! : mintATokenAcc!,
new PublicKey(poolKeys.vault[baseIn ? "A" : "B"]),
new PublicKey(poolKeys.vault[baseIn ? "B" : "A"]),
new PublicKey(poolInfo[baseIn ? "mintA" : "mintB"].programId ?? TOKEN_PROGRAM_ID),
new PublicKey(poolInfo[baseIn ? "mintB" : "mintA"].programId ?? TOKEN_PROGRAM_ID),
baseIn ? mintA : mintB,
baseIn ? mintB : mintA,
getPdaObservationId(new PublicKey(poolInfo.programId), new PublicKey(poolInfo.id)).publicKey,
inputAmount,
swapResult.destinationAmountSwapped,
)
: makeSwapCpmmBaseOutInInstruction(
new PublicKey(poolInfo.programId),
this.scope.ownerPubKey,
new PublicKey(poolKeys.authority),
new PublicKey(poolKeys.config.id),
new PublicKey(poolInfo.id),
baseIn ? mintATokenAcc! : mintBTokenAcc!,
baseIn ? mintBTokenAcc! : mintATokenAcc!,
new PublicKey(poolKeys.vault[baseIn ? "A" : "B"]),
new PublicKey(poolKeys.vault[baseIn ? "B" : "A"]),
new PublicKey(poolInfo[baseIn ? "mintA" : "mintB"].programId ?? TOKEN_PROGRAM_ID),
new PublicKey(poolInfo[baseIn ? "mintB" : "mintA"].programId ?? TOKEN_PROGRAM_ID),
baseIn ? mintA : mintB,
baseIn ? mintB : mintA,
getPdaObservationId(new PublicKey(poolInfo.programId), new PublicKey(poolInfo.id)).publicKey,
swapResult.sourceAmountSwapped,
swapResult.destinationAmountSwapped,
),
],
instructionTypes: [fixedOut ? InstructionType.CpmmSwapBaseOut : InstructionType.ClmmSwapBaseIn],
});
txBuilder.addCustomComputeBudget(computeBudgetConfig);
return txBuilder.versionBuild({ txVersion }) as Promise<MakeTxData<T>>;
}
public computeSwapAmount({
pool,
amountIn,
outputMint,
slippage,
}: {
pool: CpmmComputeData;
amountIn: BN;
outputMint: string | PublicKey;
slippage: number;
}): {
allTrade: boolean;
amountIn: BN;
amountOut: BN;
minAmountOut: BN;
fee: BN;
executionPrice: Decimal;
priceImpact: any;
} {
const isBaseIn = outputMint.toString() === pool.mintB.address;
const swapResult = CurveCalculator.swap(
amountIn,
isBaseIn ? pool.baseReserve : pool.quoteReserve,
isBaseIn ? pool.quoteReserve : pool.baseReserve,
pool.configInfo.tradeFeeRate,
);
const executionPrice = new Decimal(swapResult.destinationAmountSwapped.toString()).div(
swapResult.sourceAmountSwapped.toString(),
);
const minAmountOut = swapResult.destinationAmountSwapped.mul(new BN((1 - slippage) * 10000)).div(new BN(10000));
return {
allTrade: swapResult.sourceAmountSwapped.eq(amountIn),
amountIn,
amountOut: swapResult.destinationAmountSwapped,
minAmountOut,
executionPrice,
fee: swapResult.tradeFee,
priceImpact: pool.poolPrice.sub(executionPrice).div(pool.poolPrice),
};
}
public computePairAmount({
poolInfo,
baseReserve,
quoteReserve,
amount,
slippage,
epochInfo,
baseIn,
}: ComputePairAmountParams): {
inputAmountFee: GetTransferAmountFee;
anotherAmount: GetTransferAmountFee;
maxAnotherAmount: GetTransferAmountFee;
liquidity: BN;
} {
const coefficient = 1 - Number(slippage.toSignificant()) / 100;
const inputAmount = new BN(
new Decimal(amount)
.mul(10 ** poolInfo[baseIn ? "mintA" : "mintB"].decimals)
.mul(coefficient)
.toFixed(0),
);
const inputAmountFee = getTransferAmountFeeV2(
inputAmount,
poolInfo[baseIn ? "mintA" : "mintB"].extensions.feeConfig,
epochInfo,
false,
);
const _inputAmountWithoutFee = inputAmount.sub(inputAmountFee.fee ?? new BN(0));
const lpAmount = new BN(
new Decimal(poolInfo.lpAmount).mul(10 ** poolInfo.lpMint.decimals).toFixed(0, Decimal.ROUND_DOWN),
);
this.logDebug("baseReserve:", baseReserve.toString(), "quoteReserve:", quoteReserve.toString());
this.logDebug(
"tokenIn:",
baseIn ? poolInfo.mintA.symbol : poolInfo.mintB.symbol,
"amountIn:",
inputAmount.toString(),
"amountInFee:",
inputAmountFee.fee?.toString() ?? 0,
"anotherToken:",
baseIn ? poolInfo.mintB.symbol : poolInfo.mintA.symbol,
"slippage:",
`${slippage.toSignificant()}%`,
);
// input is fixed
const input = baseIn ? "base" : "quote";
this.logDebug("input side:", input);
const liquidity = _inputAmountWithoutFee.mul(lpAmount).div(input === "base" ? baseReserve : quoteReserve);
let anotherAmountFee: GetTransferAmountFee = {
amount: BN_ZERO,
fee: undefined,
expirationTime: undefined,
};
if (!_inputAmountWithoutFee.isZero()) {
const lpAmountData = lpToAmount(liquidity, baseReserve, quoteReserve, lpAmount);
this.logDebug("lpAmountData:", {
amountA: lpAmountData.amountA.toString(),
amountB: lpAmountData.amountB.toString(),
});
anotherAmountFee = getTransferAmountFeeV2(
lpAmountData[baseIn ? "amountB" : "amountA"],
poolInfo[baseIn ? "mintB" : "mintA"].extensions.feeConfig,
epochInfo,
true,
);
}
const _slippage = new Percent(new BN(1)).add(slippage);
const slippageAdjustedAmount = getTransferAmountFeeV2(
_slippage.mul(anotherAmountFee.amount.sub(anotherAmountFee.fee ?? new BN(0))).quotient,
poolInfo[baseIn ? "mintB" : "mintA"].extensions.feeConfig,
epochInfo,
true,
);
this.logDebug(
"anotherAmount:",
anotherAmountFee.amount.toString(),
"anotherAmountFee:",
anotherAmountFee.fee?.toString() ?? 0,
"maxAnotherAmount:",
slippageAdjustedAmount.amount.toString(),
"maxAnotherAmountFee:",
slippageAdjustedAmount.fee?.toString() ?? 0,
);
return {
inputAmountFee,
anotherAmount: anotherAmountFee,
maxAnotherAmount: slippageAdjustedAmount,
liquidity,
};
}
}
function lpToAmount(lp: BN, poolAmountA: BN, poolAmountB: BN, supply: BN): { amountA: BN; amountB: BN } {
let amountA = lp.mul(poolAmountA).div(supply);
if (!amountA.isZero() && !lp.mul(poolAmountA).mod(supply).isZero()) amountA = amountA.add(new BN(1));
let amountB = lp.mul(poolAmountB).div(supply);
if (!amountB.isZero() && !lp.mul(poolAmountB).mod(supply).isZero()) amountB = amountB.add(new BN(1));
return {
amountA,
amountB,
};
}