UNPKG

@raydium-io/raydium-sdk-v2

Version:

An SDK for building applications on top of Raydium.

1,533 lines (1,360 loc) 63.9 kB
import ModuleBase, { ModuleBaseProps } from "../moduleBase"; import { TxVersion, MakeTxData, LAUNCHPAD_PROGRAM, getMultipleAccountsInfoWithCustomFlags, getATAAddress, MakeMultiTxData, } from "@/common"; import { BuyToken, BuyTokenExactOut, ClaimAllPlatformFee, ClaimCreatorFee, ClaimMultiCreatorFee, ClaimMultipleVaultPlatformFee, ClaimMultiVesting, ClaimPlatformFee, ClaimVaultPlatformFee, ClaimVesting, CpmmCreatorFeeOn, CreateLaunchPad, CreateMultipleVesting, CreatePlatform, CreatePlatformVestingAccount, CreateVesting, LaunchpadConfigInfo, LaunchpadPoolInfo, SellToken, SellTokenExactOut, UpdatePlatform, } from "./type"; import { getPdaCreatorFeeVaultAuth, getPdaCreatorVault, getPdaLaunchpadAuth, getPdaLaunchpadPoolId, getPdaLaunchpadVaultId, getPdaPlatformConfigAccess, getPdaPlatformFeeVaultAuth, getPdaPlatformId, getPdaPlatformVault, getPdaVestId, } from "./pda"; import { buyExactInInstruction, sellExactInInstruction, createPlatformConfig, updatePlatformConfig, claimPlatformFee, createVestingAccount, claimVestedToken, buyExactOutInstruction, initializeWithToken2022, sellExactOut, claimPlatformFeeFromVault, claimCreatorFee, initializeV2, createPlatformVestingAccountIns, } from "./instrument"; import { NATIVE_MINT, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, TransferFeeConfig, createAssociatedTokenAccountIdempotentInstruction, createSyncNativeInstruction, getTransferFeeConfig, unpackMint, } from "@solana/spl-token"; import BN from "bn.js"; import { PublicKey, SystemProgram } from "@solana/web3.js"; import { getPdaMetadataKey } from "../clmm"; import { LaunchpadConfig, LaunchpadPool, PlatformConfig } from "./layout"; import { Curve, SwapInfoReturn } from "./curve/curve"; import Decimal from "decimal.js"; import { ApiV3Token } from "@/api"; export const LaunchpadPoolInitParam = { initPriceX64: new BN("515752397214619"), supply: new BN(1_000_000_000_000_000), totalSellA: new BN(793_100_000_000_000), totalFundRaisingB: new BN(85_000_000_000), totalFundRaisingBUSD: new BN(12_500_000_000), totalLockedAmount: new BN("0"), cliffPeriod: new BN("0"), unlockPeriod: new BN("0"), decimals: 6, virtualA: new BN("1073471847374405"), virtualB: new BN("30050573465"), realA: new BN(0), realB: new BN(0), protocolFee: new BN(0), platformId: new PublicKey("4Bu96XjU84XjPDSpveTVf6LYGCkfW5FK7SNkREWcEfV4"), vestingSchedule: { totalLockedAmount: new BN(0), cliffPeriod: new BN(0), unlockPeriod: new BN(0), startTime: new BN(0), totalAllocatedShare: new BN(0), }, }; const SLIPPAGE_UNIT = new BN(10000); export const usdMintBSet = new Set([ "USDCoctVLVnvTXBEuP9s8hntucdJokbo17RwHuNXemT", "USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB", ]); export interface SwapInfoReturnExt extends SwapInfoReturn { decimalOutAmount: Decimal; minDecimalOutAmount: Decimal; } export default class LaunchpadModule extends ModuleBase { constructor(params: ModuleBaseProps) { super(params); } public async createLaunchpad<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, authProgramId, platformId = LaunchpadPoolInitParam.platformId, mintA, decimals = 6, mintBDecimals = 9, name, symbol, uri, migrateType, configId, configInfo: propConfigInfo, txVersion, computeBudgetConfig, txTipConfig, feePayer, buyAmount, minMintAAmount, slippage, associatedOnly = true, checkCreateATAOwner = false, extraSigners, token2022, transferFeeExtensionParams, creatorFeeOn = CpmmCreatorFeeOn.OnlyTokenB, platformConfigAccess, ...extraConfigs }: CreateLaunchPad<T>): Promise< MakeMultiTxData<T, { address: LaunchpadPoolInfo & { poolId: PublicKey }; swapInfo: SwapInfoReturnExt }> > { const txBuilder = this.createTxBuilder(feePayer); authProgramId = authProgramId ?? getPdaLaunchpadAuth(programId).publicKey; token2022 = !!transferFeeExtensionParams; if (token2022) migrateType = "cpmm"; let configInfo = propConfigInfo; if (!configInfo && configId) { const r = await this.scope.connection.getAccountInfo(configId); if (r) configInfo = LaunchpadConfig.decode(r.data); } if (!configInfo) this.logAndCreateError("config not found"); const mintB = configInfo!.mintB; const curType = configInfo!.curveType; // const { publicKey: configId } = getPdaLaunchpadConfigId(programId, mintB, curType, configIndex); const { publicKey: poolId } = getPdaLaunchpadPoolId(programId, mintA, mintB); const { publicKey: vaultA } = getPdaLaunchpadVaultId(programId, poolId, mintA); const { publicKey: vaultB } = getPdaLaunchpadVaultId(programId, poolId, mintB); const { publicKey: metaId } = getPdaMetadataKey(mintA); this.logDebug( `create token: ${mintA.toBase58()}, mintB: ${mintB.toBase58()}, decimals A:${decimals}/B:${mintBDecimals}, config:${configId.toBase58()}`, ); if (symbol.length > 10) this.logAndCreateError("Symbol length should shorter than 11"); if (!uri) this.logAndCreateError("uri should not empty"); const configs = await this.scope.api.fetchLaunchConfigs(); const apiConfig = configs.find((c) => c.key.pubKey === configId.toBase58())!; const supply = extraConfigs?.supply ?? new BN(apiConfig.defaultParams.supplyInit); const totalSellA = extraConfigs?.totalSellA ?? new BN(apiConfig.defaultParams.totalSellA); const totalFundRaisingB = extraConfigs?.totalFundRaisingB ?? new BN(apiConfig.defaultParams.totalFundRaisingB); const totalLockedAmount = extraConfigs?.totalLockedAmount ?? new BN(0); // let defaultPlatformFeeRate = platformFeeRate; // let defaultPlatformVestingScale = platformVestingScale; // if (!platformFeeRate) { // const platformData = await this.scope.connection.getAccountInfo(platformId); // if (!platformData) this.logAndCreateError("platform id not found:", platformId.toString()); // const platform = PlatformConfig.decode(platformData!.data); // defaultPlatformVestingScale = platform.platformVestingScale; // defaultPlatformFeeRate = platform.feeRate; // } const platformData = await this.scope.connection.getAccountInfo(platformId); if (!platformData) this.logAndCreateError("platform id not found:", platformId.toString()); const platform = PlatformConfig.decode(platformData!.data); const defaultPlatformVestingScale = platform.platformVestingScale; const defaultPlatformFeeRate = platform.feeRate; const curve = Curve.getCurve(configInfo!.curveType); const initParam = curve.getInitParam({ supply, totalFundRaising: totalFundRaisingB, totalSell: totalSellA, totalLockedAmount, migrateFee: configInfo!.migrateFee, }); let mintBInfo: ApiV3Token | undefined; try { mintBInfo = await this.scope.token.getTokenInfo(mintB); } catch { this.logDebug("can not get mintB info from getTokenInfo"); } const poolInfo: LaunchpadPoolInfo = { epoch: new BN(896), bump: 254, status: 0, mintDecimalsA: decimals, mintDecimalsB: mintBInfo?.decimals ?? mintBDecimals, supply, totalSellA, mintA: new PublicKey(mintA), mintB, virtualA: initParam.a, virtualB: initParam.b, realA: LaunchpadPoolInitParam.realA, realB: LaunchpadPoolInitParam.realB, migrateFee: configInfo!.migrateFee, migrateType: migrateType === "amm" ? 0 : 1, protocolFee: LaunchpadPoolInitParam.protocolFee, platformFee: defaultPlatformFeeRate!, platformId, configId, vaultA, vaultB, creator: this.scope.ownerPubKey, totalFundRaisingB, vestingSchedule: { totalLockedAmount, cliffPeriod: new BN(0), unlockPeriod: new BN(0), startTime: new BN(0), totalAllocatedShare: new BN(0), }, mintProgramFlag: token2022 ? 1 : 0, cpmmCreatorFeeOn: creatorFeeOn, platformVestingShare: defaultPlatformVestingScale ?? new BN(0), }; const initCurve = Curve.getCurve(configInfo!.curveType); const { c } = initCurve.getInitParam({ supply: poolInfo.supply, totalFundRaising: poolInfo.totalFundRaisingB, totalLockedAmount, totalSell: configInfo!.curveType === 0 ? poolInfo.totalSellA : new BN(0), migrateFee: configInfo!.migrateFee, }); try { Curve.checkParam({ supply: poolInfo.supply, totalFundRaising: poolInfo.totalFundRaisingB, totalSell: c, totalLockedAmount, decimals: poolInfo.mintDecimalsA, config: configInfo!, migrateType, }); this.logDebug("check init params success"); } catch (e: any) { this.logAndCreateError(`check create mint params failed, ${e.message}`); } txBuilder.addInstruction({ instructions: [ token2022 ? initializeWithToken2022( programId, feePayer ?? this.scope.ownerPubKey, this.scope.ownerPubKey, configId, platformId, authProgramId, poolId, mintA, mintB, vaultA, vaultB, decimals, name, symbol, uri || "https://", { type: curType === 0 ? "ConstantCurve" : curType === 1 ? "FixedCurve" : curType === 2 ? "LinearCurve" : "ConstantCurve", totalSellA, migrateType, supply, totalFundRaisingB, }, totalLockedAmount, extraConfigs?.cliffPeriod ?? new BN(0), extraConfigs?.unlockPeriod ?? new BN(0), creatorFeeOn, transferFeeExtensionParams, ) : initializeV2( programId, feePayer ?? this.scope.ownerPubKey, this.scope.ownerPubKey, configId, platformId, authProgramId, poolId, mintA, mintB, vaultA, vaultB, metaId, decimals, name, symbol, uri || "https://", { type: curType === 0 ? "ConstantCurve" : curType === 1 ? "FixedCurve" : curType === 2 ? "LinearCurve" : "ConstantCurve", totalSellA, migrateType, supply, totalFundRaisingB, }, totalLockedAmount, extraConfigs?.cliffPeriod ?? new BN(0), extraConfigs?.unlockPeriod ?? new BN(0), creatorFeeOn, platformConfigAccess ? getPdaPlatformConfigAccess(programId, platformId, configId).publicKey : undefined, ), ], }); const epoch = token2022 ? await this.scope.connection.getEpochInfo() : undefined; const fee = transferFeeExtensionParams ? { epoch: BigInt(epoch?.epoch || 0), maximumFee: BigInt(transferFeeExtensionParams?.maxinumFee.toString() ?? 0), transferFeeBasisPoints: transferFeeExtensionParams?.transferFeeBasePoints ?? 0, } : undefined; let swapInfo: SwapInfoReturn = { amountA: { amount: new BN(0), fee: undefined, expirationTime: undefined, }, amountB: new BN(0), splitFee: { platformFee: new BN(0), shareFee: new BN(0), protocolFee: new BN(0), creatorFee: new BN(0), }, }; let splitIns; if (extraSigners?.length) txBuilder.addInstruction({ signers: extraSigners }); if (!extraConfigs.createOnly) { const { builder, extInfo } = await this.buyToken({ programId, authProgramId, mintAProgram: token2022 ? TOKEN_2022_PROGRAM_ID : undefined, mintA, mintB, poolInfo, buyAmount, minMintAAmount, shareFeeRate: extraConfigs.shareFeeRate, shareFeeReceiver: extraConfigs.shareFeeReceiver, configInfo, platformFeeRate: defaultPlatformFeeRate, slippage, associatedOnly, checkCreateATAOwner, skipCheckMintA: !fee, transferFeeConfigA: fee ? { transferFeeConfigAuthority: authProgramId, withdrawWithheldAuthority: authProgramId, withheldAmount: BigInt(0), olderTransferFee: fee, newerTransferFee: fee, } : undefined, fromCreate: true, }); txBuilder.addInstruction({ ...builder.AllTxData }); swapInfo = { ...extInfo }; splitIns = (this.scope.cluster === "devnet" || txVersion === TxVersion.LEGACY) && extraConfigs.shareFeeReceiver ? [builder.allInstructions[0]] : undefined; } txBuilder.addTipInstruction(txTipConfig); if (txVersion === TxVersion.V0) return txBuilder.sizeCheckBuildV0({ computeBudgetConfig, swapInfo, splitIns, address: { ...poolInfo, poolId, }, }) as Promise< MakeMultiTxData<T, { address: LaunchpadPoolInfo & { poolId: PublicKey }; swapInfo: SwapInfoReturnExt }> >; return txBuilder.sizeCheckBuild({ computeBudgetConfig, swapInfo, splitIns, address: { ...poolInfo, poolId, }, }) as Promise< MakeMultiTxData<T, { address: LaunchpadPoolInfo & { poolId: PublicKey }; swapInfo: SwapInfoReturnExt }> >; } public async buyToken<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, authProgramId, mintA, mintAProgram = TOKEN_PROGRAM_ID, mintB = NATIVE_MINT, poolInfo: propPoolInfo, configInfo: propConfigInfo, platformFeeRate, txVersion, computeBudgetConfig, txTipConfig, feePayer, buyAmount, minMintAAmount: propMinMintAAmount, slippage, shareFeeRate = new BN(0), shareFeeReceiver, associatedOnly = true, checkCreateATAOwner = false, fromCreate = false, transferFeeConfigA: propsTransferFeeConfigA, skipCheckMintA = false, }: BuyToken<T>): Promise<MakeTxData<T, SwapInfoReturnExt>> { if (buyAmount.lte(new BN(0))) this.logAndCreateError("buy amount should gt 0:", buyAmount.toString()); const txBuilder = this.createTxBuilder(feePayer); const { publicKey: poolId } = getPdaLaunchpadPoolId(programId, mintA, mintB); authProgramId = authProgramId ?? getPdaLaunchpadAuth(programId).publicKey; let transferFeeConfigA = propsTransferFeeConfigA; if (!skipCheckMintA) { if (!transferFeeConfigA) { const mintInfo = await this.scope.connection.getAccountInfo(mintA); if (mintInfo && mintInfo.owner.equals(TOKEN_2022_PROGRAM_ID)) { mintAProgram = mintInfo.owner; const onlineData = unpackMint(mintA, mintInfo, mintAProgram); transferFeeConfigA = getTransferFeeConfig(onlineData) || undefined; } } else { mintAProgram = TOKEN_2022_PROGRAM_ID; } } const userTokenAccountA = this.scope.account.getAssociatedTokenAccount(mintA, mintAProgram); const isMintBSol = mintB.equals(NATIVE_MINT); const useAta = fromCreate && isMintBSol; let userTokenAccountB: PublicKey | null = useAta ? this.scope.account.getAssociatedTokenAccount(mintB, TOKEN_PROGRAM_ID) : null; const mintBUseSOLBalance = isMintBSol; txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, userTokenAccountA, this.scope.ownerPubKey, mintA, mintAProgram, ), ...(useAta ? [ createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, userTokenAccountB!, this.scope.ownerPubKey, mintB, TOKEN_PROGRAM_ID, ), SystemProgram.transfer({ fromPubkey: this.scope.ownerPubKey, toPubkey: userTokenAccountB!, lamports: BigInt(buyAmount.toString()), }), createSyncNativeInstruction(userTokenAccountB!), ] : []), ], }); if (!useAta) { const { account: _ownerTokenAccountB, instructionParams: _tokenAccountBInstruction } = await this.scope.account.getOrCreateTokenAccount({ mint: mintB, owner: this.scope.ownerPubKey, createInfo: mintBUseSOLBalance ? { payer: this.scope.ownerPubKey!, amount: buyAmount, } : undefined, skipCloseAccount: !mintBUseSOLBalance, notUseTokenAccount: mintBUseSOLBalance, associatedOnly: mintBUseSOLBalance ? false : associatedOnly, checkCreateATAOwner, }); if (_ownerTokenAccountB) userTokenAccountB = _ownerTokenAccountB; txBuilder.addInstruction(_tokenAccountBInstruction || {}); } if (!userTokenAccountB) this.logAndCreateError( `cannot found mintB(${mintB.toBase58()}) buy token accounts`, "tokenAccounts", this.scope.account.tokenAccounts, ); let poolInfo = propPoolInfo; if (!poolInfo) { const poolData = await this.scope.connection.getAccountInfo(poolId, { commitment: "processed" }); if (!poolData) this.logAndCreateError("cannot found pool:", poolId.toBase58()); poolInfo = LaunchpadPool.decode(poolData!.data); } let configInfo = propConfigInfo; const allData = await getMultipleAccountsInfoWithCustomFlags( this.scope.connection, [configInfo ? undefined : poolInfo.configId, poolInfo.platformId] .filter(Boolean) .map((key) => ({ pubkey: key! })), ); if (!configInfo) { const data = allData.find((d) => d.pubkey.equals(poolInfo!.configId)); if (!data || !data.accountInfo) this.logAndCreateError("config not found: ", poolInfo.configId.toBase58()); configInfo = LaunchpadConfig.decode(data!.accountInfo!.data); } const platformData = allData.find((d) => d.pubkey.equals(poolInfo!.platformId)); if (!platformData || !platformData.accountInfo) this.logAndCreateError("platform info not found: ", poolInfo.configId.toBase58()); const platformInfo = PlatformConfig.decode(platformData!.accountInfo!.data); platformFeeRate = platformFeeRate || platformInfo.feeRate; const calculatedAmount = Curve.buyExactIn({ poolInfo, amountB: buyAmount, protocolFeeRate: configInfo.tradeFeeRate, platformFeeRate, curveType: configInfo.curveType, shareFeeRate, creatorFeeRate: platformInfo.creatorFeeRate, transferFeeConfigA, slot: await this.scope.connection.getSlot(), }); const decimalAmountA = new Decimal(calculatedAmount.amountA.amount.toString()).sub( calculatedAmount.amountA.fee?.toString() ?? 0, ); const multiplier = slippage ? new Decimal(SLIPPAGE_UNIT.sub(slippage).toNumber() / SLIPPAGE_UNIT.toNumber()).clampedTo(0, 1) : new Decimal(1); const minMintAAmount = propMinMintAAmount ?? (slippage ? new BN(decimalAmountA.mul(multiplier).toFixed(0)) : calculatedAmount.amountA.amount.sub(calculatedAmount.amountA.fee ?? new BN(0))); if (calculatedAmount.amountB.lt(buyAmount)) { console.log( `maximum ${mintA.toBase58()} amount can buy is ${calculatedAmount.amountA.toString()}, input ${mintB.toBase58()} amount: ${calculatedAmount.amountB.toString()}`, ); } const shareATA = shareFeeReceiver ? getATAAddress(shareFeeReceiver, mintB, TOKEN_PROGRAM_ID).publicKey : undefined; if (shareATA) { txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction(this.scope.ownerPubKey, shareATA, shareFeeReceiver!, mintB), ], }); } txBuilder.addInstruction({ instructions: [ buyExactInInstruction( programId, this.scope.ownerPubKey, authProgramId, poolInfo.configId, poolInfo.platformId, poolId, userTokenAccountA!, userTokenAccountB!, poolInfo.vaultA, poolInfo.vaultB, mintA, mintB, mintAProgram, TOKEN_PROGRAM_ID, getPdaPlatformVault(programId, poolInfo.platformId, mintB).publicKey, getPdaCreatorVault(programId, poolInfo.creator, mintB).publicKey, calculatedAmount.amountB.lt(buyAmount) ? calculatedAmount.amountB : buyAmount, minMintAAmount, shareFeeRate, shareATA, ), ], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild<SwapInfoReturnExt>({ txVersion, extInfo: { ...calculatedAmount, decimalOutAmount: decimalAmountA, minDecimalOutAmount: new Decimal(minMintAAmount.toString()), }, }) as Promise<MakeTxData<T, SwapInfoReturnExt>>; } public async buyTokenExactOut<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, authProgramId, mintA, mintAProgram = TOKEN_PROGRAM_ID, mintB = NATIVE_MINT, poolInfo: propPoolInfo, configInfo: propConfigInfo, transferFeeConfigA: propsTransferFeeConfigA, platformFeeRate, txVersion, computeBudgetConfig, txTipConfig, feePayer, maxBuyAmount, outAmount, slippage, shareFeeRate = new BN(0), shareFeeReceiver, associatedOnly = true, checkCreateATAOwner = false, skipCheckMintA = false, }: BuyTokenExactOut<T>): Promise<MakeTxData<T, { outAmount: BN; maxSpentAmount: BN }>> { if (outAmount.lte(new BN(0))) this.logAndCreateError("out amount should gt 0:", outAmount.toString()); const txBuilder = this.createTxBuilder(feePayer); const { publicKey: poolId } = getPdaLaunchpadPoolId(programId, mintA, mintB); authProgramId = authProgramId ?? getPdaLaunchpadAuth(programId).publicKey; let poolInfo = propPoolInfo; if (!poolInfo) { const poolData = await this.scope.connection.getAccountInfo(poolId, { commitment: "processed" }); if (!poolData) this.logAndCreateError("cannot found pool:", poolId.toBase58()); poolInfo = LaunchpadPool.decode(poolData!.data); } let configInfo = propConfigInfo; const allData = await getMultipleAccountsInfoWithCustomFlags( this.scope.connection, [configInfo ? undefined : poolInfo.configId, poolInfo.platformId] .filter(Boolean) .map((key) => ({ pubkey: key! })), ); if (!configInfo) { const data = allData.find((d) => d.pubkey.equals(poolInfo!.configId)); if (!data || !data.accountInfo) this.logAndCreateError("config not found: ", poolInfo.configId.toBase58()); configInfo = LaunchpadConfig.decode(data!.accountInfo!.data); } const platformData = allData.find((d) => d.pubkey.equals(poolInfo!.platformId)); if (!platformData || !platformData.accountInfo) this.logAndCreateError("platform info not found: ", poolInfo.configId.toBase58()); const platformInfo = PlatformConfig.decode(platformData!.accountInfo!.data); platformFeeRate = platformFeeRate || platformInfo.feeRate; let transferFeeConfigA = propsTransferFeeConfigA; if (!skipCheckMintA) { if (!transferFeeConfigA) { const mintInfo = await this.scope.connection.getAccountInfo(mintA); if (mintInfo && mintInfo.owner.equals(TOKEN_2022_PROGRAM_ID)) { mintAProgram = mintInfo.owner; const onlineData = unpackMint(mintA, mintInfo, mintAProgram); transferFeeConfigA = getTransferFeeConfig(onlineData) || undefined; } } else { mintAProgram = TOKEN_2022_PROGRAM_ID; } } const calculatedAmount = Curve.buyExactOut({ poolInfo, amountA: outAmount, protocolFeeRate: configInfo.tradeFeeRate, platformFeeRate, curveType: configInfo.curveType, shareFeeRate, creatorFeeRate: platformInfo.creatorFeeRate, transferFeeConfigA, slot: await this.scope.connection.getSlot(), }); const decimalAmountB = new Decimal(calculatedAmount.amountB.toString()); const multiplier = slippage ? new Decimal(SLIPPAGE_UNIT.add(slippage).toNumber() / SLIPPAGE_UNIT.toNumber()).clampedTo( 0, Number.MIN_SAFE_INTEGER, ) : new Decimal(1); const maxAmountB = maxBuyAmount ?? slippage ? new BN(decimalAmountB.mul(multiplier).toFixed(0)) : calculatedAmount.amountB; const userTokenAccountA = this.scope.account.getAssociatedTokenAccount(mintA, mintAProgram); let userTokenAccountB: PublicKey | null = null; const mintBUseSOLBalance = mintB.equals(NATIVE_MINT); txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, userTokenAccountA, this.scope.ownerPubKey, mintA, mintAProgram, ), ], }); const { account: _ownerTokenAccountB, instructionParams: _tokenAccountBInstruction } = await this.scope.account.getOrCreateTokenAccount({ mint: mintB, owner: this.scope.ownerPubKey, createInfo: mintBUseSOLBalance ? { payer: this.scope.ownerPubKey!, amount: calculatedAmount.amountB, } : undefined, skipCloseAccount: !mintBUseSOLBalance, notUseTokenAccount: mintBUseSOLBalance, associatedOnly: mintBUseSOLBalance ? false : associatedOnly, checkCreateATAOwner, }); if (_ownerTokenAccountB) userTokenAccountB = _ownerTokenAccountB; txBuilder.addInstruction(_tokenAccountBInstruction || {}); if (userTokenAccountB === undefined) this.logAndCreateError( `cannot found mintB(${mintB.toBase58()}) token accounts`, "tokenAccounts", this.scope.account.tokenAccounts, ); const shareATA = shareFeeReceiver ? getATAAddress(shareFeeReceiver, mintB, TOKEN_PROGRAM_ID).publicKey : undefined; if (shareATA) { txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction(this.scope.ownerPubKey, shareATA, shareFeeReceiver!, mintB), ], }); } txBuilder.addInstruction({ instructions: [ buyExactOutInstruction( programId, this.scope.ownerPubKey, authProgramId, poolInfo.configId, poolInfo.platformId, poolId, userTokenAccountA!, userTokenAccountB!, poolInfo.vaultA, poolInfo.vaultB, mintA, mintB, mintAProgram, TOKEN_PROGRAM_ID, getPdaPlatformVault(programId, poolInfo.platformId, mintB).publicKey, getPdaCreatorVault(programId, poolInfo.creator, mintB).publicKey, outAmount, maxAmountB, shareFeeRate, shareATA, ), ], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild<{ outAmount: BN; maxSpentAmount: BN }>({ txVersion, extInfo: { maxSpentAmount: maxAmountB, outAmount, }, }) as Promise<MakeTxData<T, { outAmount: BN; maxSpentAmount: BN }>>; } public async sellToken<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, authProgramId, mintAProgram = TOKEN_PROGRAM_ID, mintA, mintB = NATIVE_MINT, poolInfo: propPoolInfo, configInfo: propConfigInfo, platformFeeRate, txVersion, computeBudgetConfig, txTipConfig, feePayer, sellAmount, minAmountB: propMinAmountB, slippage, shareFeeRate = new BN(0), shareFeeReceiver, associatedOnly = true, checkCreateATAOwner = false, skipCheckMintA = false, }: SellToken<T>): Promise<MakeTxData<T, { outAmount: BN }>> { authProgramId = authProgramId ?? getPdaLaunchpadAuth(programId).publicKey; const txBuilder = this.createTxBuilder(feePayer); if (sellAmount.lte(new BN(0))) this.logAndCreateError("sell amount should be gt 0"); const { publicKey: poolId } = getPdaLaunchpadPoolId(programId, mintA, mintB); let transferFeeConfigA: TransferFeeConfig | undefined; if (!skipCheckMintA) { const mintInfo = await this.scope.connection.getAccountInfo(mintA); if (mintInfo && mintInfo.owner.equals(TOKEN_2022_PROGRAM_ID)) { mintAProgram = mintInfo.owner; const onlineData = unpackMint(mintA, mintInfo, mintAProgram); transferFeeConfigA = getTransferFeeConfig(onlineData) || undefined; } } let userTokenAccountA: PublicKey | null = null; let userTokenAccountB: PublicKey | null = null; const mintBUseSOLBalance = mintB.equals(NATIVE_MINT); const { account: _ownerTokenAccountA, instructionParams: _tokenAccountAInstruction } = await this.scope.account.getOrCreateTokenAccount({ tokenProgram: mintAProgram, mint: mintA, owner: this.scope.ownerPubKey, createInfo: undefined, skipCloseAccount: true, notUseTokenAccount: false, associatedOnly, checkCreateATAOwner, }); if (_ownerTokenAccountA) userTokenAccountA = _ownerTokenAccountA; txBuilder.addInstruction(_tokenAccountAInstruction || {}); if (userTokenAccountA === undefined) this.logAndCreateError("cannot found mintA token accounts", "tokenAccounts", this.scope.account.tokenAccounts); const { account: _ownerTokenAccountB, instructionParams: _tokenAccountBInstruction } = await this.scope.account.getOrCreateTokenAccount({ mint: mintB, owner: this.scope.ownerPubKey, createInfo: mintBUseSOLBalance ? { payer: this.scope.ownerPubKey!, amount: 0, } : undefined, skipCloseAccount: !mintBUseSOLBalance, notUseTokenAccount: mintBUseSOLBalance, associatedOnly: mintBUseSOLBalance ? false : associatedOnly, checkCreateATAOwner, }); if (_ownerTokenAccountB) userTokenAccountB = _ownerTokenAccountB; txBuilder.addInstruction(_tokenAccountBInstruction || {}); if (userTokenAccountB === undefined) this.logAndCreateError("cannot found mintB token accounts", "tokenAccounts", this.scope.account.tokenAccounts); let poolInfo = propPoolInfo; if (!poolInfo) { const poolData = await this.scope.connection.getAccountInfo(poolId, { commitment: "processed" }); if (!poolData) this.logAndCreateError("cannot found pool", poolId.toBase58()); poolInfo = LaunchpadPool.decode(poolData!.data); } let configInfo = propConfigInfo; const allData = await getMultipleAccountsInfoWithCustomFlags( this.scope.connection, [configInfo ? undefined : poolInfo.configId, poolInfo.platformId] .filter(Boolean) .map((key) => ({ pubkey: key! })), ); if (!configInfo) { const data = allData.find((d) => d.pubkey.equals(poolInfo!.configId)); if (!data || !data.accountInfo) this.logAndCreateError("config not found: ", poolInfo.configId.toBase58()); configInfo = LaunchpadConfig.decode(data!.accountInfo!.data); } const platformData = allData.find((d) => d.pubkey.equals(poolInfo!.platformId)); if (!platformData || !platformData.accountInfo) this.logAndCreateError("platform info not found: ", poolInfo.configId.toBase58()); const platformInfo = PlatformConfig.decode(platformData!.accountInfo!.data); platformFeeRate = platformFeeRate || platformInfo.feeRate; const calculatedAmount = Curve.sellExactIn({ poolInfo, amountA: sellAmount, protocolFeeRate: configInfo.tradeFeeRate, platformFeeRate, curveType: configInfo.curveType, shareFeeRate, creatorFeeRate: platformInfo.creatorFeeRate, transferFeeConfigA, slot: await this.scope.connection.getSlot(), }); const decimalAmountB = new Decimal(calculatedAmount.amountB.toString()); const multiplier = slippage ? new Decimal(SLIPPAGE_UNIT.sub(slippage).toNumber() / SLIPPAGE_UNIT.toNumber()).clampedTo(0, 1) : new Decimal(1); const minAmountB = propMinAmountB ?? (slippage ? new BN(decimalAmountB.mul(multiplier).toFixed(0)) : calculatedAmount.amountB); if (minAmountB.lte(new BN(0))) this.logAndCreateError(`out ${mintB.toBase58()} amount should be gt 0`); const shareATA = shareFeeReceiver ? getATAAddress(shareFeeReceiver, mintB, TOKEN_PROGRAM_ID).publicKey : undefined; if (shareATA) { txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction(this.scope.ownerPubKey, shareATA, shareFeeReceiver!, mintB), ], }); } txBuilder.addInstruction({ instructions: [ sellExactInInstruction( programId, this.scope.ownerPubKey, authProgramId, poolInfo.configId, poolInfo.platformId, poolId, userTokenAccountA!, userTokenAccountB!, poolInfo.vaultA, poolInfo.vaultB, mintA, mintB, mintAProgram, TOKEN_PROGRAM_ID, getPdaPlatformVault(programId, poolInfo.platformId, mintB).publicKey, getPdaCreatorVault(programId, poolInfo.creator, mintB).publicKey, calculatedAmount.amountA.amount.lt(sellAmount) ? calculatedAmount.amountA.amount : sellAmount, minAmountB, shareFeeRate, shareATA, ), ], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild<{ outAmount: BN }>({ txVersion, extInfo: { outAmount: minAmountB, }, }) as Promise<MakeTxData<T, { outAmount: BN }>>; } public async sellTokenExactOut<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, authProgramId, mintAProgram = TOKEN_PROGRAM_ID, mintA, mintB = NATIVE_MINT, poolInfo: propPoolInfo, configInfo: propConfigInfo, platformFeeRate, txVersion, computeBudgetConfig, txTipConfig, feePayer, inAmount, maxSellAmount, slippage, shareFeeRate = new BN(0), shareFeeReceiver, associatedOnly = true, checkCreateATAOwner = false, skipCheckMintA = false, }: SellTokenExactOut<T>): Promise<MakeTxData<T, { maxSellAmount: BN }>> { authProgramId = authProgramId ?? getPdaLaunchpadAuth(programId).publicKey; const txBuilder = this.createTxBuilder(feePayer); if (maxSellAmount?.lte(new BN(0))) this.logAndCreateError("max sell amount should be gt 0"); const { publicKey: poolId } = getPdaLaunchpadPoolId(programId, mintA, mintB); let transferFeeConfigA: TransferFeeConfig | undefined; if (!skipCheckMintA) { const mintInfo = await this.scope.connection.getAccountInfo(mintA); if (mintInfo && mintInfo.owner.equals(TOKEN_2022_PROGRAM_ID)) { mintAProgram = mintInfo.owner; const onlineData = unpackMint(mintA, mintInfo, mintAProgram); transferFeeConfigA = getTransferFeeConfig(onlineData) || undefined; } } let userTokenAccountA: PublicKey | null = null; let userTokenAccountB: PublicKey | null = null; const mintBUseSOLBalance = mintB.equals(NATIVE_MINT); const { account: _ownerTokenAccountA, instructionParams: _tokenAccountAInstruction } = await this.scope.account.getOrCreateTokenAccount({ tokenProgram: mintAProgram, mint: mintA, owner: this.scope.ownerPubKey, createInfo: undefined, skipCloseAccount: true, notUseTokenAccount: false, associatedOnly, checkCreateATAOwner, }); if (_ownerTokenAccountA) userTokenAccountA = _ownerTokenAccountA; txBuilder.addInstruction(_tokenAccountAInstruction || {}); if (userTokenAccountA === undefined) this.logAndCreateError("cannot found mintA token accounts", "tokenAccounts", this.scope.account.tokenAccounts); const { account: _ownerTokenAccountB, instructionParams: _tokenAccountBInstruction } = await this.scope.account.getOrCreateTokenAccount({ mint: mintB, owner: this.scope.ownerPubKey, createInfo: mintBUseSOLBalance ? { payer: this.scope.ownerPubKey!, amount: 0, } : undefined, skipCloseAccount: !mintBUseSOLBalance, notUseTokenAccount: mintBUseSOLBalance, associatedOnly: mintBUseSOLBalance ? false : associatedOnly, checkCreateATAOwner, }); if (_ownerTokenAccountB) userTokenAccountB = _ownerTokenAccountB; txBuilder.addInstruction(_tokenAccountBInstruction || {}); if (userTokenAccountB === undefined) this.logAndCreateError("cannot found mintB token accounts", "tokenAccounts", this.scope.account.tokenAccounts); let poolInfo = propPoolInfo; if (!poolInfo) { const poolData = await this.scope.connection.getAccountInfo(poolId, { commitment: "processed" }); if (!poolData) this.logAndCreateError("cannot found pool", poolId.toBase58()); poolInfo = LaunchpadPool.decode(poolData!.data); } let configInfo = propConfigInfo; const allData = await getMultipleAccountsInfoWithCustomFlags( this.scope.connection, [configInfo ? undefined : poolInfo.configId, poolInfo.platformId] .filter(Boolean) .map((key) => ({ pubkey: key! })), ); if (!configInfo) { const data = allData.find((d) => d.pubkey.equals(poolInfo!.configId)); if (!data || !data.accountInfo) this.logAndCreateError("config not found: ", poolInfo.configId.toBase58()); configInfo = LaunchpadConfig.decode(data!.accountInfo!.data); } const platformData = allData.find((d) => d.pubkey.equals(poolInfo!.platformId)); if (!platformData || !platformData.accountInfo) this.logAndCreateError("platform info not found: ", poolInfo.configId.toBase58()); const platformInfo = PlatformConfig.decode(platformData!.accountInfo!.data); platformFeeRate = platformFeeRate || platformInfo.feeRate; const calculatedAmount = Curve.sellExactOut({ poolInfo, amountB: inAmount, protocolFeeRate: configInfo.tradeFeeRate, platformFeeRate, curveType: configInfo.curveType, shareFeeRate, creatorFeeRate: platformInfo.creatorFeeRate, transferFeeConfigA, slot: await this.scope.connection.getSlot(), }); const decimalAmountA = new Decimal(calculatedAmount.amountA.amount.toString()); const multiplier = slippage ? new Decimal(SLIPPAGE_UNIT.add(slippage).toNumber() / SLIPPAGE_UNIT.toNumber()).clampedTo( 0, Number.MAX_SAFE_INTEGER, ) : new Decimal(1); const maxSellAmountA = maxSellAmount ?? slippage ? new BN(decimalAmountA.mul(multiplier).toFixed(0)) : calculatedAmount.amountA.amount; const shareATA = shareFeeReceiver ? getATAAddress(shareFeeReceiver, mintB, TOKEN_PROGRAM_ID).publicKey : undefined; if (shareATA) { txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction(this.scope.ownerPubKey, shareATA, shareFeeReceiver!, mintB), ], }); } txBuilder.addInstruction({ instructions: [ sellExactOut( programId, this.scope.ownerPubKey, authProgramId, poolInfo.configId, poolInfo.platformId, poolId, userTokenAccountA!, userTokenAccountB!, poolInfo.vaultA, poolInfo.vaultB, mintA, mintB, mintAProgram, TOKEN_PROGRAM_ID, getPdaPlatformVault(programId, poolInfo.platformId, mintB).publicKey, getPdaCreatorVault(programId, poolInfo.creator, mintB).publicKey, inAmount, maxSellAmountA, shareFeeRate, shareATA, ), ], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild<{ maxSellAmount: BN }>({ txVersion, extInfo: { maxSellAmount: maxSellAmountA, }, }) as Promise<MakeTxData<T, { maxSellAmount: BN }>>; } public async createPlatformConfig<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, platformAdmin, platformClaimFeeWallet, platformLockNftWallet, platformVestingWallet, cpConfigId, migrateCpLockNftScale, transferFeeExtensionAuth, creatorFeeRate, feeRate, name, web, img, platformVestingScale = new BN(0), // max: 1_000_000 = 100% txVersion, computeBudgetConfig, txTipConfig, feePayer, }: CreatePlatform<T>): Promise<MakeTxData<T, { platformId: PublicKey }>> { const txBuilder = this.createTxBuilder(feePayer); const { publicKey: platformId } = getPdaPlatformId(programId, platformAdmin); txBuilder.addInstruction({ instructions: [ createPlatformConfig( programId, platformAdmin, platformClaimFeeWallet, platformLockNftWallet, platformVestingWallet, platformId, cpConfigId, transferFeeExtensionAuth, migrateCpLockNftScale, feeRate, creatorFeeRate, name, web, img, platformVestingScale, ), ], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild({ txVersion, extInfo: { platformId, }, }) as Promise<MakeTxData<T, { platformId: PublicKey }>>; } public async updatePlatformConfig<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, platformAdmin, platformId: propsPlatformId, updateInfo, txVersion, computeBudgetConfig, txTipConfig, feePayer, }: UpdatePlatform<T>): Promise<MakeTxData> { const txBuilder = this.createTxBuilder(feePayer); const platformId = propsPlatformId ?? getPdaPlatformId(programId, platformAdmin).publicKey; txBuilder.addInstruction({ instructions: [updatePlatformConfig(programId, platformAdmin, platformId, updateInfo)], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild({ txVersion, }) as Promise<MakeTxData>; } public async createPlatformVestingAccount<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, platformVestingWallet, beneficiary, platformId, poolId, vestingRecord: propsVestingRecord, txVersion, computeBudgetConfig, txTipConfig, feePayer, }: CreatePlatformVestingAccount<T>): Promise<MakeTxData<T>> { const txBuilder = this.createTxBuilder(feePayer); const vestingRecord = propsVestingRecord ?? getPdaVestId(programId, poolId, beneficiary).publicKey; txBuilder.addInstruction({ instructions: [ createPlatformVestingAccountIns( programId, platformVestingWallet, beneficiary, platformId, poolId, vestingRecord, ), ], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild({ txVersion, }) as Promise<MakeTxData<T>>; } public async claimPlatformFee<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, authProgramId, platformId, poolId, platformClaimFeeWallet, mintB: propsMintB, vaultB: propsVaultB, mintBProgram = TOKEN_PROGRAM_ID, txVersion, computeBudgetConfig, txTipConfig, feePayer, }: ClaimPlatformFee<T>): Promise<MakeTxData> { const txBuilder = this.createTxBuilder(feePayer); authProgramId = authProgramId ?? getPdaLaunchpadAuth(programId).publicKey; let mintB = propsMintB; let vaultB = propsVaultB; if (!mintB) { const poolData = await this.scope.connection.getAccountInfo(poolId, { commitment: "processed" }); if (!poolData) this.logAndCreateError("cannot found pool:", poolId.toBase58()); const poolInfo = LaunchpadPool.decode(poolData!.data); const configData = await this.scope.connection.getAccountInfo(poolInfo.configId, { commitment: "processed" }); if (!configData) this.logAndCreateError("cannot found config:", poolInfo.configId.toBase58()); const configInfo = LaunchpadConfig.decode(configData!.data); mintB = configInfo.mintB; vaultB = vaultB ?? poolInfo.vaultB; } if (!mintB || !vaultB) { this.logAndCreateError( "cannot found mint info, mintB: ", mintB.toBase58(), ", vaultB: ", vaultB?.toBase58() ?? "", ); } const userTokenAccountB = getATAAddress(this.scope.ownerPubKey, mintB, TOKEN_PROGRAM_ID).publicKey; txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, userTokenAccountB, this.scope.ownerPubKey, mintB, ), ], }); txBuilder.addInstruction({ instructions: [ claimPlatformFee( programId, platformClaimFeeWallet, authProgramId, poolId, platformId, vaultB!, userTokenAccountB!, mintB, mintBProgram, ), ], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild({ txVersion, }) as Promise<MakeTxData>; } public async claimAllPlatformFee<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, authProgramId, platformId, platformClaimFeeWallet, txVersion, computeBudgetConfig, txTipConfig, feePayer, }: ClaimAllPlatformFee<T>): Promise<MakeMultiTxData<T>> { const txBuilder = this.createTxBuilder(feePayer); authProgramId = authProgramId ?? getPdaLaunchpadAuth(programId).publicKey; const allPlatformPool = await this.scope.connection.getProgramAccounts(programId, { filters: [ { dataSize: LaunchpadPool.span }, { memcmp: { offset: LaunchpadPool.offsetOf("platformId"), bytes: platformId.toString() } }, ], }); allPlatformPool.forEach((data) => { const pool = LaunchpadPool.decode(data.account.data); if (pool.platformFee.lte(new BN(0))) return; const userTokenAccountB = getATAAddress(this.scope.ownerPubKey, pool.mintB, TOKEN_PROGRAM_ID).publicKey; txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, userTokenAccountB, this.scope.ownerPubKey, pool.mintB, ), ], }); txBuilder.addInstruction({ instructions: [ claimPlatformFee( programId, platformClaimFeeWallet, authProgramId!, data.pubkey, platformId, pool.vaultB, userTokenAccountB!, pool.mintB, TOKEN_PROGRAM_ID, ), ], }); }); txBuilder.addTipInstruction(txTipConfig); if (txVersion === TxVersion.V0) return txBuilder.sizeCheckBuildV0({ computeBudgetConfig }) as Promise<MakeMultiTxData<T>>; return txBuilder.sizeCheckBuild({ computeBudgetConfig, }) as Promise<MakeMultiTxData<T>>; } public async createVesting<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, poolId, beneficiary, shareAmount, txVersion, computeBudgetConfig, txTipConfig, feePayer, }: CreateVesting<T>): Promise<MakeTxData> { const txBuilder = this.createTxBuilder(feePayer); const poolInfo = await this.getRpcPoolInfo({ poolId }); if (shareAmount.add(poolInfo.vestingSchedule.totalAllocatedShare).gt(poolInfo.vestingSchedule.totalLockedAmount)) this.logAndCreateError("share amount exceed total locked amount"); const vestingRecord = getPdaVestId(programId, poolId, beneficiary).publicKey; txBuilder.addInstruction({ instructions: [ createVestingAccount(programId, this.scope.ownerPubKey, beneficiary, poolId, vestingRecord, shareAmount), ], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild({ txVersion, }) as Promise<MakeTxData>; } public async createMultipleVesting<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, poolId, beneficiaryList, txVersion, computeBudgetConfig, feePayer, }: CreateMultipleVesting<T>): Promise<MakeMultiTxData<T>> { const txBuilder = this.createTxBuilder(feePayer); if (beneficiaryList.length === 0) this.logAndCreateError("beneficiaryList is null"); const poolInfo = await this.getRpcPoolInfo({ pool