UNPKG

@raydium-io/raydium-sdk-v2

Version:

An SDK for building applications on top of Raydium.

1,392 lines (1,241 loc) 55.9 kB
import { PublicKey } from "@solana/web3.js"; import { ApiV3PoolInfoStandardItemCpmm, CpmmKeys } from "../../api/type"; import { AccountLayout, NATIVE_MINT, TOKEN_PROGRAM_ID, createAssociatedTokenAccountIdempotentInstruction, } from "@solana/spl-token"; import { BN_ZERO } from "@/common/bignumber"; import { getATAAddress } from "@/common/pda"; import { WSOLMint } from "@/common/pubKey"; import { MakeMultiTxData, MakeTxData } from "@/common/txTool/txTool"; import { InstructionType, TxVersion } from "@/common/txTool/txType"; import { Percent } from "../../module"; import { CurveCalculator } from "./curve/calculator"; import BN from "bn.js"; import Decimal from "decimal.js"; import { CREATE_CPMM_POOL_PROGRAM, fetchMultipleMintInfos, getMultipleAccountsInfoWithCustomFlags, getTransferAmountFeeV2, LOCK_CPMM_AUTH, LOCK_CPMM_PROGRAM, } from "@/common"; import { GetTransferAmountFee, ReturnTypeFetchMultipleMintInfos } from "../../raydium/type"; import ModuleBase, { ModuleBaseProps } from "../moduleBase"; import { toApiV3Token, toFeeConfig } from "../token"; import { makeCreateCpmmPoolInInstruction, makeDepositCpmmInInstruction, makeSwapCpmmBaseInInstruction, makeSwapCpmmBaseOutInstruction, makeWithdrawCpmmInInstruction, makeCpmmLockInstruction, collectCpFeeInstruction, initializeWithPermission, makeCollectCreatorFeeInstruction, } from "./instruction"; import { CpmmConfigInfoLayout, CpmmPoolInfoLayout } from "./layout"; import { getCreatePoolKeys, getPdaObservationId, getPdaPermissionId, getPdaPoolAuthority } from "./pda"; import { AddCpmmLiquidityParams, ComputePairAmountParams, CpmmComputeData, CpmmLockExtInfo, CpmmParsedRpcData, CpmmSwapParams, CreateCpmmPoolAddress, CreateCpmmPoolParam, LockCpmmLpParams, HarvestLockCpmmLpParams, WithdrawCpmmLiquidityParams, HarvestMultiLockCpmmLpParams, FeeOn, CreateCpmmPoolPermissionParam, CollectCreatorFees, CollectMultiCreatorFees, } from "./type"; import { getCpLockPda } 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<CpmmParsedRpcData> { return (await this.getRpcPoolInfos([poolId], fetchConfigInfo))[poolId]; } public async getRpcPoolInfos( poolIds: string[], fetchConfigInfo?: boolean, ): Promise<{ [poolId: string]: CpmmParsedRpcData; }> { 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]: CpmmParsedRpcData } = {}; for (const [id, info] of Object.entries(poolInfos)) { const baseReserve = vaultInfo[info.vaultA.toString()] .sub(info.protocolFeesMintA) .sub(info.fundFeesMintA) .sub(info.creatorFeesMintA); const quoteReserve = vaultInfo[info.vaultB.toString()] .sub(info.protocolFeesMintB) .sub(info.fundFeesMintB) .sub(info.creatorFeesMintB); 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, CpmmParsedRpcData>; 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: CpmmParsedRpcData; }> { 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, burnPercent: 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, observationId: getPdaObservationId(rpcData.programId, new PublicKey(poolId)).publicKey.toBase58(), }, rpcData, }; } public async createPool<T extends TxVersion>({ poolId, programId, poolFeeAccount, startTime, ownerInfo, associatedOnly = false, checkCreateATAOwner = false, txVersion, feeConfig, computeBudgetConfig, txTipConfig, feePayer, ...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(feePayer); 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({ poolId, 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); txBuilder.addTipInstruction(txTipConfig); 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, txTipConfig, config, txVersion, feePayer, } = 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(feePayer); 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); txBuilder.addTipInstruction(txTipConfig); 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, txTipConfig, txVersion, feePayer, closeWsol = true, } = 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(feePayer); 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 && closeWsol), 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 && closeWsol), 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); txBuilder.addTipInstruction(txTipConfig); 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, txTipConfig, txVersion, feePayer, } = params; const { bypassAssociatedCheck, checkCreateATAOwner, associatedOnly } = { // default ...{ bypassAssociatedCheck: false, checkCreateATAOwner: false, associatedOnly: true }, // custom ...config, }; const txBuilder = this.createTxBuilder(feePayer); const [mintA, mintB] = [new PublicKey(poolInfo.mintA.address), new PublicKey(poolInfo.mintB.address)]; if (!fixedOut) { swapResult.outputAmount = swapResult.outputAmount.mul(new BN((1 - slippage) * 10000)).div(new BN(10000)); } else { swapResult.inputAmount = swapResult.inputAmount.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.inputAmount : 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.inputAmount, } : 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 ? makeSwapCpmmBaseInInstruction( 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.outputAmount, ) : makeSwapCpmmBaseOutInstruction( 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.inputAmount, swapResult.outputAmount, ), ], instructionTypes: [fixedOut ? InstructionType.CpmmSwapBaseOut : InstructionType.ClmmSwapBaseIn], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild({ txVersion }) as Promise<MakeTxData<T>>; } public async lockLp<T extends TxVersion>(params: LockCpmmLpParams<T>): Promise<MakeTxData<CpmmLockExtInfo>> { const { poolInfo, lpAmount, computeBudgetConfig, txTipConfig, txVersion, feePayer, feeNftOwner } = params; if (lpAmount.isZero()) this.logAndCreateError("lpAmount must greater than zero", { lpAmount: lpAmount.toString(), }); const txBuilder = this.createTxBuilder(feePayer); const poolKeys = params.poolKeys ?? (await this.getCpmmPoolKeys(poolInfo.id)); const insData = await makeCpmmLockInstruction({ poolInfo, poolKeys, ownerInfo: { wallet: this.scope.ownerPubKey, feePayer: params.feePayer ?? this.scope.ownerPubKey, }, feeNftOwner: feeNftOwner ?? this.scope.ownerPubKey, lockProgram: params.programId ?? LOCK_CPMM_PROGRAM, lockAuthProgram: params.authProgram ?? LOCK_CPMM_AUTH, lpAmount, withMetadata: params.withMetadata ?? true, getEphemeralSigners: params.getEphemeralSigners, }); txBuilder.addInstruction(insData); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild({ txVersion, extInfo: insData.address }) as Promise<MakeTxData<CpmmLockExtInfo>>; } public async harvestLockLp<T extends TxVersion>(params: HarvestLockCpmmLpParams<T>): Promise<MakeTxData> { const { poolInfo, lpFeeAmount, nftMint, programId = LOCK_CPMM_PROGRAM, authProgram = LOCK_CPMM_AUTH, cpmmProgram, computeBudgetConfig, txTipConfig, txVersion, closeWsol = true, } = params; if (lpFeeAmount.isZero()) this.logAndCreateError("lpFeeAmount must greater than zero", { lpAmount: lpFeeAmount.toString(), }); const feePayer = params.feePayer || this.scope.ownerPubKey; const txBuilder = this.createTxBuilder(feePayer); 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 && closeWsol), 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 && closeWsol), associatedOnly: mintBUseSOLBalance ? false : true, checkCreateATAOwner: false, }); tokenAccountB = _ownerTokenAccountB; accountBInstructions && txBuilder.addInstruction(accountBInstructions); if (!tokenAccountA || !tokenAccountB) this.logAndCreateError("cannot found target token accounts", { tokenAccountA, tokenAccountB }); const poolKeys = params.poolKeys ?? (await this.getCpmmPoolKeys(poolInfo.id)); const { publicKey: nftAccount } = getATAAddress(feePayer, nftMint, TOKEN_PROGRAM_ID); const { publicKey: lockPda } = getCpLockPda(programId, nftMint); const { publicKey: lockLpVault } = getATAAddress( authProgram, new PublicKey(poolInfo.lpMint.address), TOKEN_PROGRAM_ID, ); txBuilder.addInstruction({ instructions: [ collectCpFeeInstruction({ programId, nftOwner: this.scope.ownerPubKey, auth: authProgram, nftMint, nftAccount, lockPda, poolId: new PublicKey(poolInfo.id), mintLp: new PublicKey(poolKeys.mintLp.address), userVaultA: tokenAccountA!, userVaultB: tokenAccountB!, poolVaultA: new PublicKey(poolKeys.vault.A), poolVaultB: new PublicKey(poolKeys.vault.B), mintA, mintB, lockLpVault, lpFeeAmount, cpmmProgram: cpmmProgram?.programId, cpmmAuthProgram: cpmmProgram?.authProgram, }), ], instructionTypes: [InstructionType.CpmmCollectLockFee], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild({ txVersion }) as Promise<MakeTxData>; } public async harvestMultiLockLp<T extends TxVersion>( params: HarvestMultiLockCpmmLpParams<T>, ): Promise<MakeMultiTxData<T>> { const { lockInfo, programId = LOCK_CPMM_PROGRAM, authProgram = LOCK_CPMM_AUTH, cpmmProgram, computeBudgetConfig, txVersion, closeWsol = true, } = params; const feePayer = params.feePayer || this.scope.ownerPubKey; const txBuilder = this.createTxBuilder(feePayer); const tokenAccRecord: Record<string, PublicKey | undefined> = {}; for (const lockData of lockInfo) { const { poolInfo, lpFeeAmount, nftMint } = lockData; if (lpFeeAmount.isZero()) continue; 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 = tokenAccRecord[poolInfo.mintA.address]; let tokenAccountB: PublicKey | undefined = tokenAccRecord[poolInfo.mintB.address]; if (!tokenAccountA) { if (mintAUseSOLBalance) { const { account: _ownerTokenAccountA, instructionParams: accountAInstructions } = await this.scope.account.getOrCreateTokenAccount({ tokenProgram: poolInfo.mintA.programId, mint: new PublicKey(poolInfo.mintA.address), notUseTokenAccount: true, owner: this.scope.ownerPubKey, createInfo: { payer: this.scope.ownerPubKey, amount: 0, }, skipCloseAccount: !closeWsol, associatedOnly: false, checkCreateATAOwner: false, }); tokenAccountA = _ownerTokenAccountA; accountAInstructions && txBuilder.addInstruction(accountAInstructions); tokenAccRecord[poolInfo.mintA.address] = _ownerTokenAccountA; } else { const mint = new PublicKey(poolInfo.mintA.address); tokenAccountA = this.scope.account.getAssociatedTokenAccount(mint, new PublicKey(poolInfo.mintA.programId)); txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, tokenAccountA, this.scope.ownerPubKey, mint, new PublicKey(poolInfo.mintA.programId), ), ], }); tokenAccRecord[poolInfo.mintA.address] = tokenAccountA; } } if (!tokenAccountB) { if (mintBUseSOLBalance) { const { account: _ownerTokenAccountB, instructionParams: accountBInstructions } = await this.scope.account.getOrCreateTokenAccount({ tokenProgram: poolInfo.mintB.programId, mint: new PublicKey(poolInfo.mintB.address), notUseTokenAccount: true, owner: this.scope.ownerPubKey, createInfo: { payer: this.scope.ownerPubKey, amount: 0, }, skipCloseAccount: !closeWsol, associatedOnly: false, checkCreateATAOwner: false, }); tokenAccountB = _ownerTokenAccountB; accountBInstructions && txBuilder.addInstruction(accountBInstructions); tokenAccRecord[poolInfo.mintB.address] = _ownerTokenAccountB; } else { const mint = new PublicKey(poolInfo.mintB.address); tokenAccountB = this.scope.account.getAssociatedTokenAccount(mint, new PublicKey(poolInfo.mintB.programId)); txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, tokenAccountB, this.scope.ownerPubKey, mint, new PublicKey(poolInfo.mintB.programId), ), ], }); tokenAccRecord[poolInfo.mintB.address] = tokenAccountB; } } if (!tokenAccountA || !tokenAccountB) this.logAndCreateError("cannot found target token accounts", { tokenAccountA, tokenAccountB }); const poolKeys = lockData.poolKeys ?? (await this.getCpmmPoolKeys(poolInfo.id)); const { publicKey: nftAccount } = getATAAddress(feePayer, nftMint, TOKEN_PROGRAM_ID); const { publicKey: lockPda } = getCpLockPda(programId, nftMint); const { publicKey: lockLpVault } = getATAAddress( authProgram, new PublicKey(poolInfo.lpMint.address), TOKEN_PROGRAM_ID, ); txBuilder.addInstruction({ instructions: [ collectCpFeeInstruction({ programId, nftOwner: this.scope.ownerPubKey, auth: authProgram, nftMint, nftAccount, lockPda, poolId: new PublicKey(poolInfo.id), mintLp: new PublicKey(poolKeys.mintLp.address), userVaultA: tokenAccountA!, userVaultB: tokenAccountB!, poolVaultA: new PublicKey(poolKeys.vault.A), poolVaultB: new PublicKey(poolKeys.vault.B), mintA, mintB, lockLpVault, lpFeeAmount, cpmmProgram: cpmmProgram?.programId, cpmmAuthProgram: cpmmProgram?.authProgram, }), ], instructionTypes: [InstructionType.CpmmCollectLockFee], }); } if (txVersion === TxVersion.V0) return txBuilder.sizeCheckBuildV0({ computeBudgetConfig }) as Promise<MakeMultiTxData<T>>; return txBuilder.sizeCheckBuild({ computeBudgetConfig }) as Promise<MakeMultiTxData<T>>; } public async createPoolWithPermission<T extends TxVersion>({ poolId, programId, poolFeeAccount, startTime, ownerInfo, associatedOnly = false, checkCreateATAOwner = false, txVersion, feeConfig, computeBudgetConfig, txTipConfig, feePayer, feeOn, ...params }: CreateCpmmPoolPermissionParam<T>): Promise<MakeTxData<T, { address: CreateCpmmPoolAddress }>> { console.log("***this method only available for wallet with permissions***"); 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(feePayer); 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({ poolId, programId, configId: new PublicKey(feeConfig.id), mintA: mintAPubkey, mintB: mintBPubkey, }); txBuilder.addInstruction({ instructions: [ initializeWithPermission( programId, this.scope.ownerPubKey, 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, getPdaPermissionId(programId, this.scope.ownerPubKey).publicKey, mintAAmount, mintBAmount, startTime, feeOn, ), ], instructionTypes: [InstructionType.CpmmCreatePool], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild({ txVersion, extInfo: { address: { ...poolKeys, mintA, mintB, programId, poolFeeAccount, feeConfig }, }, }) as Promise<MakeTxData<T, { address: CreateCpmmPoolAddress }>>; } public async collectCreatorFees<T extends TxVersion>({ poolInfo, poolKeys: propPoolKeys, programId = CREATE_CPMM_POOL_PROGRAM, txVersion, computeBudgetConfig, txTipConfig, feePayer, }: CollectCreatorFees<T>): Promise<MakeTxData<T>> { const payer = feePayer || this.scope.ownerPubKey; const txBuilder = this.createTxBuilder(payer); const poolKeys = propPoolKeys ?? (await this.getCpmmPoolKeys(poolInfo.id)); const [mintA, mintB, mintAProgram, mintBProgram] = [ new PublicKey(poolInfo.mintA.address), new PublicKey(poolInfo.mintB.address), new PublicKey(poolInfo.mintA.programId), new PublicKey(poolInfo.mintB.programId), ]; const tokenAccountA = this.scope.account.getAssociatedTokenAccount(mintA, mintAProgram); const tokenAccountB = this.scope.account.getAssociatedTokenAccount(mintB, mintBProgram); txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, tokenAccountA, this.scope.ownerPubKey, mintA, new PublicKey(poolInfo.mintA.programId), ), createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, tokenAccountB, this.scope.ownerPubKey, mintB, new PublicKey(poolInfo.mintB.programId), ), ], }); txBuilder.addInstruction({ instructions: [ makeCollectCreatorFeeInstruction( programId, this.scope.ownerPubKey, new PublicKey(poolKeys.authority), new PublicKey(poolKeys.id), new PublicKey(poolKeys.config.id), new PublicKey(poolKeys.vault.A), new PublicKey(poolKeys.vault.B), mintA, mintB, tokenAccountA, tokenAccountB, mintAProgram, mintBProgram, ), ], instructionTypes: [], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild({ txVersion, }) as Promise<MakeTxData<T>>; } public async collectMultiCreatorFees<T extends TxVersion>({ poolInfoList, programId = CREATE_CPMM_POOL_PROGRAM, txVersion, computeBudgetConfig, feePayer, }: CollectMultiCreatorFees<T>): Promise<MakeMultiTxData<T>> { const payer = feePayer || this.scope.ownerPubKey; const txBuilder = this.createTxBuilder(payer); const tokenAccRecord: Record<string, PublicKey> = {}; const poolKeyList = await this.scope.api.fetchPoolKeysById({ idList: poolInfoList.map((p) => p.id) }); for (const poolInfo of poolInfoList) { const poolKeys = (poolKeyList.find((p) => p.id === poolInfo.id) || (await this.getCpmmPoolKeys(poolInfo.id))) as CpmmKeys; const [mintA, mintB, mintAProgram, mintBProgram] = [ new PublicKey(poolInfo.mintA.address), new PublicKey(poolInfo.mintB.address), new PublicKey(poolInfo.mintA.programId), new PublicKey(poolInfo.mintB.programId), ]; const tokenAccountA = tokenAccRecord[poolInfo.mintA.address] || this.scope.account.getAssociatedTokenAccount(mintA, mintAProgram); const tokenAccountB = tokenAccRecord[poolInfo.mintB.address] || this.scope.account.getAssociatedTokenAccount(mintB, mintBProgram); if (!tokenAccRecord[poolInfo.mintA.address]) txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, tokenAccountA, this.scope.ownerPubKey, mintA, mintAProgram, ), ], }); if (!tokenAccRecord[poolInfo.mintB.address]) txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, tokenAccountB, this.scope.ownerPubKey, mintB, mintBProgram, ), ], }); tokenAccRecord[poolInfo.mintA.address] = tokenAccountA; tokenAccRecord[poolInfo.mintB.address] = tokenAccountB; txBuilder.addInstruction({ instructions: [ makeCollectCreatorFeeInstruction( programId, this.scope.ownerPubKey, new PublicKey(poolKeys.authority), new PublicKey(poolKeys.id), new PublicKey(poolKeys.config.id), new PublicKey(poolKeys.vault.A), new PublicKey(poolKeys.vault.B), mintA, mintB, tokenAccountA, tokenAccountB, mintAProgram, mintBProgram, ), ], instructionTypes: [], }); } if (txVersion === TxVersion.V0) return txBuilder.sizeCheckBuildV0({ computeBudgetConfig }) as Promise<MakeMultiTxData<T>>; return