UNPKG

@powrldgr/raydium-sdk-v2

Version:

An SDK for building applications on top of Raydium.

1,154 lines (1,026 loc) 36.5 kB
import ModuleBase, { ModuleBaseProps } from "../moduleBase"; import { TxVersion, MakeTxData, LAUNCHPAD_PROGRAM, getMultipleAccountsInfoWithCustomFlags, getATAAddress, MakeMultiTxData, } from "@/common"; import { BuyToken, ClaimAllPlatformFee, ClaimMultiVesting, ClaimPlatformFee, ClaimVesting, CreateLaunchPad, CreateMultipleVesting, CreatePlatform, CreateVesting, LaunchpadConfigInfo, LaunchpadPoolInfo, SellToken, UpdatePlatform, } from "./type"; import { getPdaLaunchpadAuth, getPdaLaunchpadPoolId, getPdaLaunchpadVaultId, getPdaPlatformId, getPdaVestId, } from "./pda"; import { initialize, buyExactInInstruction, sellExactInInstruction, createPlatformConfig, updatePlatformConfig, claimPlatformFee, createVestingAccount, claimVestedToken, } from "./instrument"; import { NATIVE_MINT, TOKEN_PROGRAM_ID, createAssociatedTokenAccountIdempotentInstruction } from "@solana/spl-token"; import BN from "bn.js"; import { PublicKey } from "@solana/web3.js"; import { getPdaMetadataKey } from "../clmm"; import { LaunchpadConfig, LaunchpadPool, PlatformConfig } from "./layout"; import { Curve } from "./curve/curve"; import Decimal from "decimal.js"; 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), 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 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, platformFeeRate, txVersion, computeBudgetConfig, txTipConfig, feePayer, buyAmount, minMintAAmount, slippage, associatedOnly = true, checkCreateATAOwner = false, extraSigners, ...extraConfigs }: CreateLaunchPad<T>): Promise< MakeMultiTxData<T, { address: LaunchpadPoolInfo & { poolId: PublicKey }; outAmount: BN }> > { const txBuilder = this.createTxBuilder(feePayer); authProgramId = authProgramId ?? getPdaLaunchpadAuth(programId).publicKey; 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 supply = extraConfigs?.supply ?? LaunchpadPoolInitParam.supply; const totalSellA = extraConfigs?.totalSellA ?? LaunchpadPoolInitParam.totalSellA; const totalFundRaisingB = extraConfigs?.totalFundRaisingB ?? LaunchpadPoolInitParam.totalFundRaisingB; const totalLockedAmount = extraConfigs?.totalLockedAmount ?? new BN(0); let defaultPlatformFeeRate = platformFeeRate; if (!platformFeeRate) { const platformData = await this.scope.connection.getAccountInfo(platformId); if (!platformData) this.logAndCreateError("platform id not found:", platformId.toString()); defaultPlatformFeeRate = PlatformConfig.decode(platformData!.data).feeRate; } const curve = Curve.getCurve(configInfo!.curveType); const initParam = curve.getInitParam({ supply, totalFundRaising: totalFundRaisingB, totalSell: totalSellA, totalLockedAmount, migrateFee: configInfo!.migrateFee, }); const poolInfo: LaunchpadPoolInfo = { epoch: new BN(896), bump: 254, status: 0, mintDecimalsA: decimals, mintDecimalsB: 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), }, }; 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: [ initialize( programId, feePayer ?? this.scope.ownerPubKey, this.scope.ownerPubKey, configId, platformId, authProgramId, poolId, mintA, mintB, vaultA, vaultB, metaId, TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, 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), ), ], }); let outAmount = new BN(0); let splitIns; if (extraSigners?.length) txBuilder.addInstruction({ signers: extraSigners }); if (!extraConfigs.createOnly) { const { builder, extInfo } = await this.buyToken({ programId, authProgramId, mintA, mintB, poolInfo, buyAmount, minMintAAmount, shareFeeRate: extraConfigs.shareFeeRate, shareFeeReceiver: extraConfigs.shareFeeReceiver, configInfo, platformFeeRate: defaultPlatformFeeRate, slippage, associatedOnly, checkCreateATAOwner, }); txBuilder.addInstruction({ ...builder.AllTxData }); outAmount = extInfo.outAmount; 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, outAmount, splitIns, address: { ...poolInfo, poolId, }, }) as Promise<MakeMultiTxData<T, { address: LaunchpadPoolInfo & { poolId: PublicKey }; outAmount: BN }>>; return txBuilder.sizeCheckBuild({ computeBudgetConfig, outAmount, splitIns, address: { ...poolInfo, poolId, }, }) as Promise<MakeMultiTxData<T, { address: LaunchpadPoolInfo & { poolId: PublicKey }; outAmount: BN }>>; } public async buyToken<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, authProgramId, mintA, 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, }: BuyToken<T>): Promise<MakeTxData<T, { outAmount: BN }>> { 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 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({ mint: mintA, owner: this.scope.ownerPubKey, createInfo: { payer: this.scope.ownerPubKey, amount: 0, }, skipCloseAccount: true, notUseTokenAccount: false, associatedOnly, checkCreateATAOwner, }); if (_ownerTokenAccountA) userTokenAccountA = _ownerTokenAccountA; txBuilder.addInstruction(_tokenAccountAInstruction || {}); if (userTokenAccountA === undefined) this.logAndCreateError( `cannot found mintA(${mintA.toBase58()}) 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: buyAmount, } : 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, ); 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, platformFeeRate ? undefined : 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); } if (!platformFeeRate) { const data = allData.find((d) => d.pubkey.equals(poolInfo!.platformId)); if (!data || !data.accountInfo) this.logAndCreateError("platform info not found: ", poolInfo.configId.toBase58()); platformFeeRate = PlatformConfig.decode(data!.accountInfo!.data).feeRate; } const calculatedAmount = Curve.buyExactIn({ poolInfo, amountB: buyAmount, protocolFeeRate: configInfo.tradeFeeRate, platformFeeRate, curveType: configInfo.curveType, shareFeeRate, }); const decimalAmountA = new Decimal(calculatedAmount.amountA.toString()); 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); if (calculatedAmount.amountB.lt(buyAmount)) { console.log( `maximum ${mintA.toBase58()} amount can buy is ${calculatedAmount.amountA.toString()}, input ${mintB.toBase58()} amount: ${calculatedAmount.amountB.toString()}`, ); } // let shareATA: PublicKey | undefined; // if (shareFeeReceiver) { // if (mintB.equals(NATIVE_MINT)) { // const { addresses, ...txInstruction } = await createWSolAccountInstructions({ // connection: this.scope.connection, // owner: shareFeeReceiver, // payer: this.scope.ownerPubKey, // amount: 0, // skipCloseAccount: true, // }); // txBuilder.addInstruction(txInstruction); // shareATA = addresses.newAccount; // } else { // shareATA = getATAAddress(shareFeeReceiver, mintB, TOKEN_PROGRAM_ID).publicKey; // txBuilder.addInstruction({ // instructions: [ // createAssociatedTokenAccountIdempotentInstruction(this.scope.ownerPubKey, shareATA, shareFeeReceiver!, mintB), // ], // }); // // } // } 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, TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, calculatedAmount.amountB.lt(buyAmount) ? calculatedAmount.amountB : buyAmount, minMintAAmount, shareFeeRate, shareATA, ), ], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild<{ outAmount: BN }>({ txVersion, extInfo: { outAmount: minMintAAmount, }, }) as Promise<MakeTxData<T, { outAmount: BN }>>; } public async sellToken<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, authProgramId, 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, }: 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 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({ 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, platformFeeRate ? undefined : 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); } if (!platformFeeRate) { const data = allData.find((d) => d.pubkey.equals(poolInfo!.platformId)); if (!data || !data.accountInfo) this.logAndCreateError("platform info not found: ", poolInfo.configId.toBase58()); platformFeeRate = PlatformConfig.decode(data!.accountInfo!.data).feeRate; } const calculatedAmount = Curve.sellExactIn({ poolInfo, amountA: sellAmount, protocolFeeRate: configInfo.tradeFeeRate, platformFeeRate, curveType: configInfo.curveType, shareFeeRate, }); 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, TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, calculatedAmount.amountA.lt(sellAmount) ? calculatedAmount.amountA : 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 createPlatformConfig<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, platformAdmin, platformClaimFeeWallet, platformLockNftWallet, cpConfigId, migrateCpLockNftScale, feeRate, name, web, img, 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, platformId, cpConfigId, migrateCpLockNftScale, feeRate, name, web, img, ), ], }); 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 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({ poolId }); const allShareAmount = beneficiaryList.reduce( (acc, cur) => acc.add(cur.shareAmount), poolInfo.vestingSchedule.totalAllocatedShare, ); if (allShareAmount.gt(poolInfo.vestingSchedule.totalLockedAmount)) this.logAndCreateError("share amount exceed total locked amount"); beneficiaryList.forEach((beneficiary) => { const vestingRecord = getPdaVestId(programId, poolId, beneficiary.wallet).publicKey; txBuilder.addInstruction({ instructions: [ createVestingAccount( programId, this.scope.ownerPubKey, beneficiary.wallet, poolId, vestingRecord, beneficiary.shareAmount, ), ], }); }); if (txVersion === TxVersion.V0) return txBuilder.sizeCheckBuildV0({ computeBudgetConfig }) as Promise<MakeMultiTxData<T>>; return txBuilder.sizeCheckBuild({ computeBudgetConfig }) as Promise<MakeMultiTxData<T>>; } public async claimVesting<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, poolId, poolInfo: propsPoolInfo, vestingRecord: propsVestingRecord, txVersion, computeBudgetConfig, txTipConfig, feePayer, }: ClaimVesting<T>): Promise<MakeTxData> { const txBuilder = this.createTxBuilder(feePayer); const authProgramId = getPdaLaunchpadAuth(programId).publicKey; const vestingRecord = propsVestingRecord || getPdaVestId(programId, poolId, this.scope.ownerPubKey).publicKey; let poolInfo = propsPoolInfo; if (!poolInfo) { const r = await this.scope.connection.getAccountInfo(poolId); if (!r) this.logAndCreateError("pool not found"); poolInfo = LaunchpadPool.decode(r!.data); } const userTokenAccountA = getATAAddress(this.scope.ownerPubKey, poolInfo.mintA, TOKEN_PROGRAM_ID).publicKey; txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, userTokenAccountA, this.scope.ownerPubKey, poolInfo.mintA, ), ], }); txBuilder.addInstruction({ instructions: [ claimVestedToken( programId, this.scope.ownerPubKey, authProgramId, poolId, vestingRecord, userTokenAccountA!, poolInfo.vaultA, poolInfo.mintA, TOKEN_PROGRAM_ID, ), ], }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder.versionBuild({ txVersion, }) as Promise<MakeTxData>; } public async claimMultiVesting<T extends TxVersion>({ programId = LAUNCHPAD_PROGRAM, poolIdList, poolsInfo: propsPoolsInfo = {}, vestingRecords = {}, txVersion, computeBudgetConfig, feePayer, }: ClaimMultiVesting<T>): Promise<MakeMultiTxData<T>> { const txBuilder = this.createTxBuilder(feePayer); let poolsInfo = { ...propsPoolsInfo }; const authProgramId = getPdaLaunchpadAuth(programId).publicKey; const needFetchPools = poolIdList.filter((id) => !poolsInfo[id.toBase58()]); if (needFetchPools.length) { const fetchedPools = await this.getRpcPoolsInfo({ poolIdList: needFetchPools }); poolsInfo = { ...poolsInfo, ...fetchedPools.poolInfoMap, }; } poolIdList.forEach((poolId) => { const poolIdStr = poolId.toBase58(); const poolInfo = poolsInfo[poolIdStr]; if (!poolInfo) this.logAndCreateError(`pool info not found: ${poolIdStr}`); const vestingRecord = vestingRecords[poolIdStr] || getPdaVestId(programId, poolId, this.scope.ownerPubKey).publicKey; const userTokenAccountA = getATAAddress(this.scope.ownerPubKey, poolInfo.mintA, TOKEN_PROGRAM_ID).publicKey; txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, userTokenAccountA, this.scope.ownerPubKey, poolInfo.mintA, ), ], }); txBuilder.addInstruction({ instructions: [ claimVestedToken( programId, this.scope.ownerPubKey, authProgramId, poolId, vestingRecord, userTokenAccountA!, poolInfo.vaultA, poolInfo.mintA, TOKEN_PROGRAM_ID, ), ], }); }); if (txVersion === TxVersion.V0) return txBuilder.sizeCheckBuildV0({ computeBudgetConfig }) as Promise<MakeMultiTxData<T>>; return txBuilder.sizeCheckBuild({ computeBudgetConfig }) as Promise<MakeMultiTxData<T>>; } public async getRpcPoolInfo({ poolId, }: { poolId: PublicKey; }): Promise<LaunchpadPoolInfo & { configInfo: LaunchpadConfigInfo }> { const data = await this.getRpcPoolsInfo({ poolIdList: [poolId] }); return data.poolInfoMap[poolId.toBase58()]; } public async getRpcPoolsInfo({ poolIdList, config, }: { poolIdList: PublicKey[]; config?: { batchRequest?: boolean; chunkCount?: number }; }): Promise<{ poolInfoMap: Record< string, LaunchpadPoolInfo & { poolId: PublicKey; configInfo: LaunchpadConfigInfo; } >; }> { const accounts = await getMultipleAccountsInfoWithCustomFlags( this.scope.connection, poolIdList.map((i) => ({ pubkey: i })), config, ); const poolInfoMap: { [poolId: string]: LaunchpadPoolInfo & { poolId: PublicKey } } = {}; const configKeys: PublicKey[] = []; for (let i = 0; i < poolIdList.length; i++) { const item = accounts[i]; if (item === null || !item.accountInfo) throw Error("fetch pool info error: " + poolIdList[i].toBase58()); const poolInfo = LaunchpadPool.decode(item.accountInfo.data); poolInfoMap[poolIdList[i].toBase58()] = { ...poolInfo, poolId: item.accountInfo.owner, }; configKeys.push(poolInfo.configId); } const configAccounts = await getMultipleAccountsInfoWithCustomFlags( this.scope.connection, configKeys.map((i) => ({ pubkey: i })), config, ); const configInfoMap: { [poolId: string]: LaunchpadConfigInfo & { configId: PublicKey } } = {}; for (let i = 0; i < configKeys.length; i++) { const item = configAccounts[i]; if (item === null || !item.accountInfo) throw Error("fetch config info error: " + configKeys[i].toBase58()); const configInfo = LaunchpadConfig.decode(item.accountInfo.data); configInfoMap[configKeys[i].toBase58()] = { ...configInfo, configId: item.accountInfo.owner, }; } return { poolInfoMap: Object.keys(poolInfoMap).reduce( (acc, cur) => ({ ...acc, [cur]: { ...poolInfoMap[cur], configInfo: configInfoMap[poolInfoMap[cur].configId.toBase58()], }, }), {}, ), }; } }