import { createAssociatedTokenAccountInstruction } from "@solana/spl-token"; import { PublicKey, SystemProgram } from "@solana/web3.js"; import { FormatFarmKeyOut } from "@/api/type"; import { AddInstructionParam, jsonInfo2PoolKeys } from "@/common"; import { parseBigNumberish, BN_ZERO } from "@/common/bignumber"; import { SOLMint, WSOLMint } from "@/common/pubKey"; import { MakeTxData, MakeMultiTxData } from "@/common/txTool/txTool"; import { InstructionType, TxVersion } from "@/common/txTool/txType"; import { getATAAddress } from "@/common/pda"; import { FARM_PROGRAM_ID_V6 } from "@/common/programId"; import { generatePubKey } from "../account/util"; import { createWSolAccountInstructions } from "../account/instruction"; import ModuleBase from "../moduleBase"; import { TOKEN_WSOL } from "../token/constant"; import { ComputeBudgetConfig } from "@/raydium/type"; import { FARM_LOCK_MINT, FARM_LOCK_VAULT, isValidFarmVersion, poolTypeV6, validateFarmRewards, FARM_PROGRAM_TO_VERSION, } from "./config"; import { createAssociatedLedgerAccountInstruction, makeCreateFarmInstruction, makeCreatorWithdrawFarmRewardInstruction, makeRestartRewardInstruction, makeAddNewRewardInstruction, makeWithdrawInstructionV3, makeWithdrawInstructionV5, makeWithdrawInstructionV6, makeDepositInstructionV3, makeDepositInstructionV5, makeDepositInstructionV6, } from "./instruction"; import { farmStateV6Layout, FarmLedger } from "./layout"; import { CreateFarm, FarmDWParam, FarmRewardInfo, FarmRewardInfoConfig, RewardInfoKey, UpdateFarmReward, UpdateFarmRewards, CreateFarmExtInfo, } from "./type"; import { calFarmRewardAmount, farmRewardInfoToConfig, getAssociatedAuthority, getAssociatedLedgerAccount, getAssociatedLedgerPoolAccount, getFarmLedgerLayout, } from "./util"; import { FormatFarmInfoOut, FormatFarmKeyOutV6 } from "@/api/type"; import Decimal from "decimal.js"; export default class Farm extends ModuleBase { // token account needed private async _getUserRewardInfo({ payer, rewardInfo }: { payer: PublicKey; rewardInfo: FarmRewardInfo }): Promise<{ rewardPubKey?: PublicKey; newInstruction?: AddInstructionParam; }> { if (rewardInfo.mint.equals(SOLMint)) { const txInstructions = await createWSolAccountInstructions({ connection: this.scope.connection, owner: this.scope.ownerPubKey, payer, amount: calFarmRewardAmount(rewardInfo), }); return { rewardPubKey: txInstructions.addresses.newAccount, newInstruction: txInstructions, }; } return { rewardPubKey: await this.scope.account.getCreatedTokenAccount({ mint: rewardInfo.mint, associatedOnly: false, })!, }; } // token account needed public async create({ poolInfo: propPoolInfo, rewardInfos, payer, programId = FARM_PROGRAM_ID_V6, txVersion, }: CreateFarm): Promise> { this.checkDisabled(); this.scope.checkOwner(); const lpMint = new PublicKey(propPoolInfo.lpMint.address); const poolInfo = { lpMint, lockInfo: { lockMint: FARM_LOCK_MINT, lockVault: FARM_LOCK_VAULT }, version: 6, rewardInfos, programId, }; const txBuilder = this.createTxBuilder(); const payerPubKey = payer ?? this.scope.ownerPubKey; const farmKeyPair = generatePubKey({ fromPublicKey: payerPubKey, programId: poolInfo.programId }); const lamports = await this.scope.connection.getMinimumBalanceForRentExemption(farmStateV6Layout.span); txBuilder.addInstruction({ instructions: [ SystemProgram.createAccountWithSeed({ fromPubkey: payerPubKey, basePubkey: payerPubKey, seed: farmKeyPair.seed, newAccountPubkey: farmKeyPair.publicKey, lamports, space: farmStateV6Layout.span, programId: poolInfo.programId, }), ], }); const { publicKey: authority, nonce } = getAssociatedAuthority({ programId: new PublicKey(poolInfo.programId), poolId: farmKeyPair.publicKey, }); const lpVault = getAssociatedLedgerPoolAccount({ programId: poolInfo.programId, poolId: farmKeyPair.publicKey, mint: poolInfo.lpMint, type: "lpVault", }); const rewardInfoConfig: FarmRewardInfoConfig[] = []; const rewardInfoKey: RewardInfoKey[] = []; for (const rewardInfo of poolInfo.rewardInfos) { if (rewardInfo.openTime >= rewardInfo.endTime) this.logAndCreateError("start time error", "rewardInfo.rewardOpenTime", rewardInfo.openTime.toString()); if (isNaN(poolTypeV6[rewardInfo.rewardType])) this.logAndCreateError("rewardType error", rewardInfo.rewardType); if (Number(rewardInfo.perSecond) <= 0) this.logAndCreateError("rewardPerSecond error", rewardInfo.perSecond); rewardInfoConfig.push(farmRewardInfoToConfig(rewardInfo)); const { rewardPubKey, newInstruction } = await this._getUserRewardInfo({ rewardInfo, payer: payerPubKey, }); if (newInstruction) txBuilder.addInstruction(newInstruction); if (!rewardPubKey) this.logAndCreateError("cannot found target token accounts", this.scope.account.tokenAccounts); const rewardMint = rewardInfo.mint.equals(SOLMint) ? new PublicKey(TOKEN_WSOL.address) : rewardInfo.mint; rewardInfoKey.push({ rewardMint, rewardVault: getAssociatedLedgerPoolAccount({ programId: poolInfo.programId, poolId: farmKeyPair.publicKey, mint: rewardMint, type: "rewardVault", }), userRewardToken: rewardPubKey!, }); } const lockUserAccount = await this.scope.account.getCreatedTokenAccount({ mint: poolInfo.lockInfo.lockMint, }); if (!lockUserAccount) this.logAndCreateError("cannot found lock vault", "tokenAccounts", this.scope.account.tokenAccounts); const { instruction, instructionType } = makeCreateFarmInstruction({ farmId: farmKeyPair.publicKey, owner: this.scope.ownerPubKey, farmAuthority: authority, lpVault, lpMint: poolInfo.lpMint, lockVault: poolInfo.lockInfo.lockVault, lockMint: poolInfo.lockInfo.lockMint, lockUserAccount, programId: poolInfo.programId, rewardInfo: rewardInfoKey, rewardInfoConfig, nonce, }); return txBuilder .addInstruction({ instructions: [instruction], instructionTypes: [instructionType], }) .versionBuild({ txVersion, extInfo: { farmId: farmKeyPair.publicKey, farmAuthority: authority, lpVault, lockUserAccount: lockUserAccount!, nonce, }, }) as Promise>; } public async restartReward({ farmInfo, payer, newRewardInfo, txVersion, }: UpdateFarmReward): Promise> { const version = FARM_PROGRAM_TO_VERSION[farmInfo.programId]; if (version !== 6) this.logAndCreateError("invalid farm version ", version); const farmInfoKeys = jsonInfo2PoolKeys((await this.scope.api.fetchFarmKeysById({ ids: farmInfo.id }))[0]); const farmKeys = { id: farmInfoKeys.id, rewardInfos: farmInfo.rewardInfos, lpVault: farmInfoKeys.lpVault, programId: farmInfoKeys.programId, }; if (newRewardInfo.openTime >= newRewardInfo.endTime) this.logAndCreateError("start time error", "newRewardInfo", newRewardInfo); const payerPubKey = payer || this.scope.ownerPubKey; const rewardMint = newRewardInfo.mint.equals(SOLMint) ? new PublicKey(TOKEN_WSOL.address) : newRewardInfo.mint; const rewardInfoIndex = farmKeys.rewardInfos.findIndex((item) => new PublicKey(item.mint.address).equals(rewardMint), ); const rewardInfo = farmInfoKeys.rewardInfos[rewardInfoIndex]; if (!rewardInfo) this.logAndCreateError("configuration does not exist", "rewardMint", rewardMint); const rewardVault = rewardInfo!.vault ?? SOLMint; const txBuilder = this.createTxBuilder(); const { rewardPubKey: userRewardTokenPub, newInstruction } = await this._getUserRewardInfo({ rewardInfo: newRewardInfo, payer: payerPubKey, }); if (newInstruction) txBuilder.addInstruction(newInstruction); if (!userRewardTokenPub) this.logAndCreateError("cannot found target token accounts", this.scope.account.tokenAccounts); return txBuilder .addInstruction({ instructions: [ makeRestartRewardInstruction({ payer: this.scope.ownerPubKey, rewardVault, userRewardTokenPub: userRewardTokenPub!, farmKeys, rewardInfo: newRewardInfo, }), ], instructionTypes: [InstructionType.FarmV6Restart], }) .versionBuild({ txVersion }) as Promise>; } public async restartRewards({ farmInfo, payer, newRewardInfos, txVersion, }: UpdateFarmRewards): Promise> { const version = FARM_PROGRAM_TO_VERSION[farmInfo.programId]; if (version !== 6) this.logAndCreateError("invalid farm version ", version); const farmInfoKeys = jsonInfo2PoolKeys((await this.scope.api.fetchFarmKeysById({ ids: farmInfo.id }))[0]); const farmKeys = { id: farmInfoKeys.id, rewardInfos: farmInfo.rewardInfos, lpVault: farmInfoKeys.lpVault, programId: farmInfoKeys.programId, }; newRewardInfos.forEach((reward) => { if (reward.openTime >= reward.endTime) this.logAndCreateError("start time error", "newRewardInfo", reward); }); const payerPubKey = payer || this.scope.ownerPubKey; const txBuilder = this.createTxBuilder(); for (const itemReward of newRewardInfos) { const rewardMint = itemReward.mint.equals(SOLMint) ? new PublicKey(TOKEN_WSOL.address) : itemReward.mint; const rewardInfoIndex = farmKeys.rewardInfos.findIndex((item) => new PublicKey(item.mint.address).equals(rewardMint), ); const rewardInfo = farmInfoKeys.rewardInfos[rewardInfoIndex]; if (!rewardInfo) this.logAndCreateError("configuration does not exist", "rewardMint", rewardMint); const rewardVault = rewardInfo!.vault ?? SOLMint; const { rewardPubKey: userRewardTokenPub, newInstruction } = await this._getUserRewardInfo({ rewardInfo: itemReward, payer: payerPubKey, }); if (newInstruction) txBuilder.addInstruction(newInstruction); if (!userRewardTokenPub) this.logAndCreateError("cannot found target token accounts", this.scope.account.tokenAccounts); const ins = makeRestartRewardInstruction({ payer: this.scope.ownerPubKey, rewardVault, userRewardTokenPub: userRewardTokenPub!, farmKeys, rewardInfo: itemReward, }); txBuilder.addInstruction({ instructions: [ins], instructionTypes: [InstructionType.FarmV6Restart], }); } return txBuilder.versionBuild({ txVersion }) as Promise>; } public async addNewRewardToken(params: UpdateFarmReward): Promise> { const { txVersion, farmInfo, newRewardInfo, payer } = params; const version = FARM_PROGRAM_TO_VERSION[farmInfo.programId]; if (version !== 6) this.logAndCreateError("invalid farm version ", version); const farmKeys = jsonInfo2PoolKeys((await this.scope.api.fetchFarmKeysById({ ids: farmInfo.id }))[0]); const payerPubKey = payer ?? this.scope.ownerPubKey; const txBuilder = this.createTxBuilder(); const rewardMint = newRewardInfo.mint.equals(SOLMint) ? new PublicKey(TOKEN_WSOL.address) : newRewardInfo.mint; const rewardVault = getAssociatedLedgerPoolAccount({ programId: new PublicKey(farmInfo.programId), poolId: new PublicKey(farmInfo.id), mint: rewardMint, type: "rewardVault", }); const { rewardPubKey: userRewardTokenPub, newInstruction } = await this._getUserRewardInfo({ rewardInfo: newRewardInfo, payer: payerPubKey, }); if (newInstruction) txBuilder.addInstruction(newInstruction); if (!userRewardTokenPub) this.logAndCreateError("annot found target token accounts", this.scope.account.tokenAccounts); newRewardInfo.mint = rewardMint; return txBuilder .addInstruction({ instructions: [ makeAddNewRewardInstruction({ payer: this.scope.ownerPubKey, userRewardTokenPub: userRewardTokenPub!, farmKeys, rewardVault, rewardInfo: newRewardInfo, }), ], instructionTypes: [InstructionType.FarmV6CreatorAddReward], }) .versionBuild({ txVersion }) as Promise>; } public async addNewRewardsToken(params: UpdateFarmRewards): Promise> { const { txVersion, farmInfo, newRewardInfos, payer } = params; const version = FARM_PROGRAM_TO_VERSION[farmInfo.programId]; if (version !== 6) this.logAndCreateError("invalid farm version ", version); const farmKeys = jsonInfo2PoolKeys((await this.scope.api.fetchFarmKeysById({ ids: farmInfo.id }))[0]); const payerPubKey = payer ?? this.scope.ownerPubKey; const txBuilder = this.createTxBuilder(); for (const itemReward of newRewardInfos) { const rewardMint = itemReward.mint.equals(SOLMint) ? new PublicKey(TOKEN_WSOL.address) : itemReward.mint; const rewardVault = getAssociatedLedgerPoolAccount({ programId: new PublicKey(farmInfo.programId), poolId: new PublicKey(farmInfo.id), mint: rewardMint, type: "rewardVault", }); const { rewardPubKey: userRewardTokenPub, newInstruction } = await this._getUserRewardInfo({ rewardInfo: itemReward, payer: payerPubKey, }); if (newInstruction) txBuilder.addInstruction(newInstruction); if (!userRewardTokenPub) this.logAndCreateError("cannot found target token accounts", this.scope.account.tokenAccounts); const ins = makeAddNewRewardInstruction({ payer: this.scope.ownerPubKey, userRewardTokenPub: userRewardTokenPub!, farmKeys, rewardVault, rewardInfo: { ...itemReward, mint: rewardMint }, }); txBuilder.addInstruction({ instructions: [ins], instructionTypes: [InstructionType.FarmV6CreatorAddReward], }); } return txBuilder.versionBuild({ txVersion }) as Promise>; } public async deposit(params: FarmDWParam): Promise> { const { txVersion, farmInfo, amount, feePayer, useSOLBalance, associatedOnly = true, checkCreateATAOwner = false, userAuxiliaryLedgers, computeBudgetConfig, } = params; if (this.scope.availability.addFarm === false) this.logAndCreateError("farm deposit feature disabled in your region"); const { rewardInfos, programId } = farmInfo; const version = FARM_PROGRAM_TO_VERSION[programId]; if (!isValidFarmVersion(version)) this.logAndCreateError("invalid farm program:", farmInfo.programId); const [farmProgramId, farmId] = [new PublicKey(farmInfo.programId), new PublicKey(farmInfo.id)]; const farmKeys = (await this.scope.api.fetchFarmKeysById({ ids: farmInfo.id }))[0]; const ledger = getAssociatedLedgerAccount({ programId: farmProgramId, poolId: farmId, owner: this.scope.ownerPubKey, version, }); const txBuilder = this.createTxBuilder(); txBuilder.addCustomComputeBudget(computeBudgetConfig); const ownerMintToAccount: { [mint: string]: PublicKey } = {}; for (const item of this.scope.account.tokenAccounts) { if (associatedOnly) { const ata = getATAAddress(this.scope.ownerPubKey, item.mint, item.programId).publicKey; if (item.publicKey && ata.equals(item.publicKey)) ownerMintToAccount[item.mint.toString()] = item.publicKey; } else { ownerMintToAccount[item.mint.toString()] = item.publicKey!; } } const lpMint = farmKeys.lpMint; const ownerLpTokenAccount = ownerMintToAccount[lpMint.address]; if (!ownerLpTokenAccount) this.logAndCreateError("you don't have any lp", "lp zero", ownerMintToAccount); const rewardAccounts: PublicKey[] = []; for (const itemReward of rewardInfos) { const rewardUseSOLBalance = useSOLBalance && itemReward.mint.address === WSOLMint.toString(); let ownerRewardAccount = ownerMintToAccount[itemReward.mint.address]; if (!ownerRewardAccount) { const { account: _ownerRewardAccount, instructionParams } = await this.scope.account.getOrCreateTokenAccount({ mint: new PublicKey(itemReward.mint.address), notUseTokenAccount: rewardUseSOLBalance, createInfo: { payer: feePayer || this.scope.ownerPubKey, amount: 0, }, owner: this.scope.ownerPubKey, skipCloseAccount: !rewardUseSOLBalance, associatedOnly: rewardUseSOLBalance ? false : associatedOnly, checkCreateATAOwner, }); ownerRewardAccount = _ownerRewardAccount!; instructionParams && txBuilder.addInstruction(instructionParams); } ownerMintToAccount[itemReward.mint.address] = ownerRewardAccount; rewardAccounts.push(ownerRewardAccount); } let ledgerInfo: FarmLedger | undefined = undefined; const ledgerData = await this.scope.connection.getAccountInfo(ledger); if (ledgerData) { const ledgerLayout = getFarmLedgerLayout(version)!; ledgerInfo = ledgerLayout.decode(ledgerData.data); } if (farmInfo.programId !== FARM_PROGRAM_ID_V6.toString() && !ledgerInfo) { const { instruction, instructionType } = createAssociatedLedgerAccountInstruction({ id: farmId, programId: farmProgramId, version, ledger, owner: this.scope.ownerPubKey, }); txBuilder.addInstruction({ instructions: [instruction], instructionTypes: [instructionType] }); } const errorMsg = validateFarmRewards({ version, rewardInfos, rewardTokenAccountsPublicKeys: rewardAccounts, }); if (errorMsg) this.logAndCreateError(errorMsg); const insParams = { amount: parseBigNumberish(amount), owner: this.scope.ownerPubKey, farmInfo, farmKeys, lpAccount: ownerLpTokenAccount, rewardAccounts, userAuxiliaryLedgers: userAuxiliaryLedgers?.map((key) => new PublicKey(key)), }; const newInstruction = version === 6 ? makeDepositInstructionV6(insParams) : version === 5 ? makeDepositInstructionV5(insParams) : makeDepositInstructionV3(insParams); const insType = { 3: InstructionType.FarmV3Deposit, 5: InstructionType.FarmV5Deposit, 6: InstructionType.FarmV6Deposit, }; return txBuilder .addInstruction({ instructions: [newInstruction], instructionTypes: [insType[version]], }) .versionBuild({ txVersion }) as Promise>; } public async withdraw(params: FarmDWParam): Promise> { const { txVersion, farmInfo, amount, deposited, useSOLBalance, feePayer, associatedOnly = true, checkCreateATAOwner = false, userAuxiliaryLedgers, computeBudgetConfig, } = params; const { rewardInfos } = farmInfo; if (this.scope.availability.removeFarm === false) this.logAndCreateError("farm withdraw feature disabled in your region"); const version = FARM_PROGRAM_TO_VERSION[farmInfo.programId]; if (!isValidFarmVersion(version)) this.logAndCreateError("invalid farm program:", farmInfo.programId); const farmKeys = (await this.scope.api.fetchFarmKeysById({ ids: farmInfo.id }))[0]; const txBuilder = this.createTxBuilder(); txBuilder.addCustomComputeBudget(computeBudgetConfig); const ownerMintToAccount: { [mint: string]: PublicKey } = {}; for (const item of this.scope.account.tokenAccounts) { if (associatedOnly) { const ata = getATAAddress(this.scope.ownerPubKey, item.mint).publicKey; if (item.publicKey && ata.equals(item.publicKey)) ownerMintToAccount[item.mint.toString()] = item.publicKey; } else { ownerMintToAccount[item.mint.toString()] = item.publicKey!; } } if (!deposited) { const ledger = getAssociatedLedgerAccount({ programId: new PublicKey(farmInfo.programId), poolId: new PublicKey(farmInfo.id), owner: this.scope.ownerPubKey, version, }); const ledgerData = await this.scope.connection.getAccountInfo(ledger); if (!ledgerData) this.logAndCreateError("no lp data", { farmId: farmInfo.id, version, ledgerData }); const ledgerLayout = getFarmLedgerLayout(version)!; const ledgerInfo = ledgerLayout.decode(ledgerData!.data); if (ledgerInfo.deposited.isZero()) this.logAndCreateError("no deposited lp", { farmId: farmInfo.id }); } else { if (deposited.isZero()) this.logAndCreateError("no deposited lp", { farmId: farmInfo.id }); } const lpMint = farmKeys.lpMint.address; const lpMintUseSOLBalance = useSOLBalance && lpMint === WSOLMint.toString(); let ownerLpTokenAccount = ownerMintToAccount[lpMint.toString()]; if (!ownerLpTokenAccount) { const { account: _ownerRewardAccount, instructionParams } = await this.scope.account.getOrCreateTokenAccount({ mint: new PublicKey(lpMint), notUseTokenAccount: lpMintUseSOLBalance, createInfo: { payer: feePayer || this.scope.ownerPubKey, amount: 0, }, owner: this.scope.ownerPubKey, skipCloseAccount: true, associatedOnly: lpMintUseSOLBalance ? false : associatedOnly, checkCreateATAOwner, }); ownerLpTokenAccount = _ownerRewardAccount!; instructionParams && txBuilder.addInstruction(instructionParams); } ownerMintToAccount[lpMint.toString()] = ownerLpTokenAccount; const rewardAccounts: PublicKey[] = []; for (const itemReward of rewardInfos) { const rewardUseSOLBalance = useSOLBalance && itemReward.mint.address === WSOLMint.toString(); let ownerRewardAccount = ownerMintToAccount[itemReward.mint.address]; if (!ownerRewardAccount) { const { account: _ownerRewardAccount, instructionParams } = await this.scope.account.getOrCreateTokenAccount({ mint: new PublicKey(itemReward.mint.address), notUseTokenAccount: rewardUseSOLBalance, createInfo: { payer: feePayer || this.scope.ownerPubKey, amount: 0, }, owner: this.scope.ownerPubKey, skipCloseAccount: !rewardUseSOLBalance, associatedOnly: rewardUseSOLBalance ? false : associatedOnly, checkCreateATAOwner, }); ownerRewardAccount = _ownerRewardAccount!; instructionParams && txBuilder.addInstruction(instructionParams); } ownerMintToAccount[itemReward.mint.address] = ownerRewardAccount; rewardAccounts.push(ownerRewardAccount); } const errorMsg = validateFarmRewards({ version, rewardInfos, rewardTokenAccountsPublicKeys: rewardAccounts, }); if (errorMsg) this.logAndCreateError(errorMsg); const insParams = { amount: parseBigNumberish(amount), owner: this.scope.ownerPubKey, farmInfo, farmKeys, lpAccount: ownerLpTokenAccount, rewardAccounts, userAuxiliaryLedgers: userAuxiliaryLedgers?.map((key) => new PublicKey(key)), }; const newInstruction = version === 6 ? makeWithdrawInstructionV6(insParams) : version === 5 ? makeWithdrawInstructionV5(insParams) : makeWithdrawInstructionV3(insParams); const insType = { 3: InstructionType.FarmV3Withdraw, 5: InstructionType.FarmV5Withdraw, 6: InstructionType.FarmV6Withdraw, }; return txBuilder .addInstruction({ instructions: [newInstruction], instructionTypes: [insType[version]], }) .versionBuild({ txVersion }) as Promise>; } // token account needed public async withdrawFarmReward({ farmInfo, withdrawMint, txVersion, }: { farmInfo: FormatFarmInfoOut; withdrawMint: PublicKey; payer?: PublicKey; txVersion?: T; }): Promise> { this.scope.checkOwner(); const farmKeys = jsonInfo2PoolKeys( (await this.scope.api.fetchFarmKeysById({ ids: farmInfo.id }))[0] as FormatFarmKeyOutV6, ); const version = FARM_PROGRAM_TO_VERSION[farmInfo.programId]; if (version !== 6) this.logAndCreateError("invalid farm version", version); const rewardInfoIdx = farmInfo.rewardInfos.findIndex((item) => item.mint.address === SOLMint.toString() ? new PublicKey(TOKEN_WSOL.address) : withdrawMint, ); const rewardInfo = farmKeys.rewardInfos[rewardInfoIdx]; if (!rewardInfo) this.logAndCreateError("withdraw mint error", "rewardInfos", farmInfo); const rewardVault = rewardInfo?.vault ?? SOLMint; const txBuilder = this.createTxBuilder(); let userRewardToken: PublicKey; if (withdrawMint.equals(SOLMint)) { const txInstruction = await createWSolAccountInstructions({ connection: this.scope.connection, owner: this.scope.ownerPubKey, payer: this.scope.ownerPubKey, amount: calFarmRewardAmount({ ...rewardInfo, perSecond: new Decimal(rewardInfo.perSecond).mul(10 ** rewardInfo.mint.decimals).toString(), }), }); userRewardToken = txInstruction.addresses.newAccount; txBuilder.addInstruction(txInstruction); } else { const selectUserRewardToken = await this.scope.account.getCreatedTokenAccount({ mint: withdrawMint, }); if (selectUserRewardToken === null) { userRewardToken = await this.scope.account.getAssociatedTokenAccount(withdrawMint); txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountInstruction( this.scope.ownerPubKey, userRewardToken, this.scope.ownerPubKey, withdrawMint, ), ], instructionTypes: [InstructionType.CreateATA], }); } else { userRewardToken = selectUserRewardToken!; } } const { instruction, instructionType } = makeCreatorWithdrawFarmRewardInstruction({ programId: farmKeys.programId, id: farmKeys.id, authority: farmKeys.authority, lpVault: farmKeys.lpVault, rewardVault, userRewardToken, owner: this.scope.ownerPubKey, }); return txBuilder .addInstruction({ instructions: [instruction], instructionTypes: [instructionType], }) .versionBuild({ txVersion }) as Promise>; } public async harvestAllRewards(params: { farmInfoList: Record; feePayer?: PublicKey; useSOLBalance?: boolean; associatedOnly?: boolean; checkCreateATAOwner?: boolean; userAuxiliaryLedgers?: string[]; txVersion?: T; computeBudgetConfig?: ComputeBudgetConfig; }): Promise> { const { farmInfoList, useSOLBalance, feePayer, associatedOnly = true, checkCreateATAOwner = false, userAuxiliaryLedgers, txVersion, computeBudgetConfig, } = params; const txBuilder = this.createTxBuilder(); txBuilder.addCustomComputeBudget(computeBudgetConfig); const ownerMintToAccount: { [mint: string]: PublicKey } = {}; for (const item of this.scope.account.tokenAccounts) { if (associatedOnly) { const ata = getATAAddress(this.scope.ownerPubKey, item.mint).publicKey; if (item.publicKey && ata.equals(item.publicKey)) ownerMintToAccount[item.mint.toString()] = item.publicKey; } else { ownerMintToAccount[item.mint.toString()] = item.publicKey!; } } const allFarmKeys = await this.scope.api.fetchFarmKeysById({ ids: Object.values(farmInfoList) .map((f) => f.id) .join(","), }); const farmKeyMap: { [key: string]: FormatFarmKeyOut } = allFarmKeys.reduce( (acc, cur) => ({ ...acc, [cur.id]: cur }), {}, ); for (const farmInfo of Object.values(farmInfoList)) { const { programId, lpMint: farmLpMint, rewardInfos, id } = farmInfo; const version = FARM_PROGRAM_TO_VERSION[programId]; const lpMint = farmLpMint.address; const lpMintUseSOLBalance = useSOLBalance && lpMint === WSOLMint.toString(); let ownerLpTokenAccount = ownerMintToAccount[lpMint]; if (!ownerLpTokenAccount) { const { account: _ownerLpAccount, instructionParams } = await this.scope.account.getOrCreateTokenAccount({ mint: new PublicKey(lpMint), notUseTokenAccount: lpMintUseSOLBalance, createInfo: { payer: feePayer || this.scope.ownerPubKey, amount: 0, }, owner: this.scope.ownerPubKey, skipCloseAccount: true, associatedOnly: lpMintUseSOLBalance ? false : associatedOnly, checkCreateATAOwner, }); ownerLpTokenAccount = _ownerLpAccount!; instructionParams && txBuilder.addInstruction(instructionParams); } ownerMintToAccount[lpMint.toString()] = ownerLpTokenAccount; const rewardAccounts: PublicKey[] = []; for (const itemReward of rewardInfos) { const rewardUseSOLBalance = useSOLBalance && itemReward.mint.address === WSOLMint.toString(); let ownerRewardAccount = ownerMintToAccount[itemReward.mint.address]; if (!ownerRewardAccount) { const { account: _ownerRewardAccount, instructionParams } = await this.scope.account.getOrCreateTokenAccount({ mint: new PublicKey(itemReward.mint.address), notUseTokenAccount: rewardUseSOLBalance, createInfo: { payer: feePayer || this.scope.ownerPubKey, amount: 0, }, owner: this.scope.ownerPubKey, skipCloseAccount: !rewardUseSOLBalance, associatedOnly: rewardUseSOLBalance ? false : associatedOnly, checkCreateATAOwner, }); ownerRewardAccount = _ownerRewardAccount!; instructionParams && txBuilder.addInstruction(instructionParams); } ownerMintToAccount[itemReward.mint.address] = ownerRewardAccount; rewardAccounts.push(ownerRewardAccount); } const farmKeys = farmKeyMap[id]; const insParams = { amount: BN_ZERO, owner: this.scope.ownerPubKey, farmInfo, farmKeys, lpAccount: ownerLpTokenAccount, rewardAccounts, userAuxiliaryLedgers: userAuxiliaryLedgers?.map((key) => new PublicKey(key)), }; const withdrawInstruction = version === 6 ? makeWithdrawInstructionV6(insParams) : version === 5 ? makeWithdrawInstructionV5(insParams) : makeWithdrawInstructionV3(insParams); const insType = { 3: InstructionType.FarmV3Withdraw, 5: InstructionType.FarmV5Withdraw, 6: InstructionType.FarmV6Withdraw, }; txBuilder.addInstruction({ instructions: [withdrawInstruction], instructionTypes: [insType[version]], }); } if (txVersion === TxVersion.LEGACY) return txBuilder.sizeCheckBuild() as Promise>; return txBuilder.sizeCheckBuildV0() as Promise>; } }