UNPKG

@raydium-io/raydium-sdk-v2

Version:

An SDK for building applications on top of Raydium.

989 lines (882 loc) 36.5 kB
import { PublicKey, SystemProgram } from "@solana/web3.js"; import { createAssociatedTokenAccountIdempotentInstruction } from "@solana/spl-token"; import { parseBigNumberish } from "@/common"; import { FormatFarmKeyOut } from "../../api/type"; import { AddInstructionParam, jsonInfo2PoolKeys } from "@/common"; import { BN_ZERO } from "@/common/bignumber"; import { getATAAddress } from "@/common/pda"; import { FARM_PROGRAM_ID_V6, DEVNET_PROGRAM_ID } from "@/common/programId"; import { SOLMint, solToWSol, WSOLMint } from "@/common/pubKey"; import { MakeMultiTxData, MakeTxData } from "@/common/txTool/txTool"; import { InstructionType, TxVersion } from "@/common/txTool/txType"; import { generatePubKey } from "../account/util"; import Decimal from "decimal.js"; import { FormatFarmInfoOut, FormatFarmKeyOutV6 } from "../../api/type"; import { ComputeBudgetConfig, TxTipConfig } from "../../raydium/type"; import { createWSolAccountInstructions } from "../account/instruction"; import ModuleBase from "../moduleBase"; import { TOKEN_WSOL } from "../token/constant"; import { FARM_LOCK_MINT, FARM_LOCK_VAULT, FARM_PROGRAM_TO_VERSION, isValidFarmVersion, poolTypeV6, validateFarmRewards, } from "./config"; import { createAssociatedLedgerAccountInstruction, makeAddNewRewardInstruction, makeCreateFarmInstruction, makeCreatorWithdrawFarmRewardInstruction, makeDepositInstructionV3, makeDepositInstructionV5, makeDepositInstructionV6, makeRestartRewardInstruction, makeWithdrawInstructionV3, makeWithdrawInstructionV4, makeWithdrawInstructionV5, makeWithdrawInstructionV6, } from "./instruction"; import { FarmLedger, farmStateV6Layout } from "./layout"; import { CreateFarm, CreateFarmExtInfo, FarmDWParam, FarmRewardInfo, FarmRewardInfoConfig, RewardInfoKey, UpdateFarmReward, UpdateFarmRewards, } from "./type"; import { calFarmRewardAmount, farmRewardInfoToConfig, getAssociatedAuthority, getAssociatedLedgerAccount, getAssociatedLedgerPoolAccount, getFarmLedgerLayout, } from "./util"; 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, openTime: rewardInfo.openTime.toString(), endTime: rewardInfo.endTime.toString(), }), }); 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<T extends TxVersion>({ poolInfo: propPoolInfo, rewardInfos, payer, programId = FARM_PROGRAM_ID_V6, txVersion, feePayer, lockProgram, }: CreateFarm<T>): Promise<MakeTxData<T, CreateFarmExtInfo>> { this.checkDisabled(); this.scope.checkOwner(); const lpMint = new PublicKey(propPoolInfo.lpMint.address); const poolInfo = { lpMint, lockInfo: { lockMint: lockProgram?.mint ?? FARM_LOCK_MINT, lockVault: lockProgram?.vault ?? FARM_LOCK_VAULT }, version: 6, rewardInfos, programId, }; const txBuilder = this.createTxBuilder(feePayer); 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 { account: lockUserAccount, instructionParams } = await this.scope.account.getOrCreateTokenAccount({ mint: new PublicKey(poolInfo.lockInfo.lockMint), owner: this.scope.ownerPubKey, skipCloseAccount: false, createInfo: { payer: this.scope.ownerPubKey, amount: 0, }, associatedOnly: false, }); instructionParams && txBuilder.addInstruction(instructionParams); 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<CreateFarmExtInfo>({ txVersion, extInfo: { farmId: farmKeyPair.publicKey, farmAuthority: authority, lpVault, lockUserAccount: lockUserAccount!, nonce, }, }) as Promise<MakeTxData<T, CreateFarmExtInfo>>; } public async restartReward<T extends TxVersion>({ farmInfo, payer, newRewardInfo, txVersion, feePayer, }: UpdateFarmReward): Promise<MakeTxData<T>> { 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(feePayer); 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<MakeTxData<T>>; } public async restartRewards<T extends TxVersion>({ farmInfo, payer, newRewardInfos, txVersion, feePayer, }: UpdateFarmRewards<T>): Promise<MakeTxData<T>> { 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(feePayer); 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<MakeTxData<T>>; } public async addNewRewardToken<T extends TxVersion>(params: UpdateFarmReward): Promise<MakeTxData<T>> { const { txVersion, farmInfo, newRewardInfo, payer, feePayer } = 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(feePayer); 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<MakeTxData<T>>; } public async addNewRewardsToken<T extends TxVersion>(params: UpdateFarmRewards<T>): Promise<MakeTxData<T>> { const { txVersion, farmInfo, newRewardInfos, payer, feePayer } = 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(feePayer); 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<MakeTxData<T>>; } public async deposit<T extends TxVersion>(params: FarmDWParam<T>): Promise<MakeTxData<T>> { const { txVersion, farmInfo, amount, feePayer, useSOLBalance, associatedOnly = true, checkCreateATAOwner = false, userAuxiliaryLedgers, computeBudgetConfig, txTipConfig, } = 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 (version === 4) this.logAndCreateError("V4 has suspended deposits:", farmInfo.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: version as 3 | 5 | 6, }); const txBuilder = this.createTxBuilder(feePayer); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); 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({ tokenProgram: itemReward.mint.programId, 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() && farmInfo.programId !== DEVNET_PROGRAM_ID.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<MakeTxData<T>>; } public async withdraw<T extends TxVersion>(params: FarmDWParam<T>): Promise<MakeTxData<T>> { const { txVersion, farmInfo, amount, deposited, useSOLBalance, feePayer, associatedOnly = true, checkCreateATAOwner = false, userAuxiliaryLedgers, computeBudgetConfig, txTipConfig, } = 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(feePayer); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); 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 (version !== 4) { 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) { // user has old none ata farm vault and don't have ata vault if (version !== 6) { const { instruction, instructionType } = createAssociatedLedgerAccountInstruction({ id: new PublicKey(farmKeys.id), programId: new PublicKey(farmKeys.programId), version, ledger, owner: this.scope.ownerPubKey, }); txBuilder.addInstruction({ instructions: [instruction], instructionTypes: [instructionType] }); } } else { const ledgerLayout = getFarmLedgerLayout(version)!; const ledgerInfo = ledgerLayout.decode(ledgerData!.data); if (ledgerInfo.deposited.isZero()) this.logAndCreateError("no deposited lp", { farmId: farmInfo.id }); } } if (deposited && deposited.isZero() && !(userAuxiliaryLedgers || []).length) this.logAndCreateError("no deposited lp", { farmId: farmInfo.id }); // if (!deposited && version !== 4) { // 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) { // user has old not ata farm vault and don't have ata vault // if (version !== 6 && (userAuxiliaryLedgers || []).length > 0) { // const { instruction, instructionType } = createAssociatedLedgerAccountInstruction({ // id: new PublicKey(farmKeys.id), // programId: new PublicKey(farmKeys.programId), // version, // ledger, // owner: this.scope.ownerPubKey, // }); // txBuilder.addInstruction({ instructions: [instruction], instructionTypes: [instructionType] }); // } else { // this.logAndCreateError("no lp data", { farmId: farmInfo.id, version, ledgerData }); // } // } else { // 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) { // if (deposited.isZero() && !(userAuxiliaryLedgers || []).length) // 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({ tokenProgram: farmKeys.lpMint.programId, 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({ tokenProgram: itemReward.mint.programId, 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) : version === 4 ? makeWithdrawInstructionV4(insParams) : makeWithdrawInstructionV3(insParams); const insType = { 3: InstructionType.FarmV3Withdraw, 4: InstructionType.FarmV4Withdraw, 5: InstructionType.FarmV5Withdraw, 6: InstructionType.FarmV6Withdraw, }; return txBuilder .addInstruction({ instructions: [newInstruction], instructionTypes: [insType[version]], }) .versionBuild({ txVersion }) as Promise<MakeTxData<T>>; } // token account needed public async withdrawFarmReward<T extends TxVersion>({ farmInfo, withdrawMint, txVersion, computeBudgetConfig, txTipConfig, feePayer, }: { farmInfo: FormatFarmInfoOut; withdrawMint: PublicKey; payer?: PublicKey; computeBudgetConfig?: ComputeBudgetConfig; txTipConfig?: TxTipConfig; txVersion?: T; feePayer?: PublicKey; }): Promise<MakeTxData<T>> { 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 rewardInfo = farmKeys.rewardInfos.find((r) => solToWSol(r.mint.address).equals(solToWSol(withdrawMint))); if (!rewardInfo) this.logAndCreateError("withdraw mint error", "rewardInfos", farmInfo); const rewardVault = rewardInfo?.vault ?? SOLMint; const txBuilder = this.createTxBuilder(feePayer); let userRewardToken: PublicKey; if (withdrawMint.equals(SOLMint) || withdrawMint.equals(PublicKey.default)) { const txInstruction = await createWSolAccountInstructions({ connection: this.scope.connection, owner: this.scope.ownerPubKey, payer: this.scope.ownerPubKey, amount: calFarmRewardAmount({ ...rewardInfo, openTime: rewardInfo!.openTime as unknown as string, endTime: rewardInfo!.endTime as unknown as string, 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) { userRewardToken = await this.scope.account.getAssociatedTokenAccount(withdrawMint); txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction( 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, }); txBuilder.addCustomComputeBudget(computeBudgetConfig); txBuilder.addTipInstruction(txTipConfig); return txBuilder .addInstruction({ instructions: [instruction], instructionTypes: [instructionType], }) .versionBuild({ txVersion }) as Promise<MakeTxData<T>>; } public async harvestAllRewards<T extends TxVersion = TxVersion.LEGACY>(params: { farmInfoList: Record<string, FormatFarmInfoOut>; feePayer?: PublicKey; useSOLBalance?: boolean; associatedOnly?: boolean; checkCreateATAOwner?: boolean; userAuxiliaryLedgers?: string[]; txVersion?: T; computeBudgetConfig?: ComputeBudgetConfig; }): Promise<MakeMultiTxData<T>> { const { farmInfoList, useSOLBalance, feePayer, associatedOnly = true, checkCreateATAOwner = false, userAuxiliaryLedgers, txVersion, computeBudgetConfig, } = params; const txBuilder = this.createTxBuilder(feePayer); 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({ tokenProgram: farmLpMint.programId, 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) { if (rewardUseSOLBalance) { const { account: _ownerRewardAccount, instructionParams } = await this.scope.account.getOrCreateTokenAccount({ tokenProgram: itemReward.mint.programId, 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); } else { const mint = new PublicKey(itemReward.mint.address); ownerRewardAccount = this.scope.account.getAssociatedTokenAccount(mint); txBuilder.addInstruction({ instructions: [ createAssociatedTokenAccountIdempotentInstruction( this.scope.ownerPubKey, ownerRewardAccount, this.scope.ownerPubKey, mint, ), ], }); } } 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({ computeBudgetConfig }) as Promise<MakeMultiTxData<T>>; return txBuilder.sizeCheckBuildV0({ computeBudgetConfig }) as Promise<MakeMultiTxData<T>>; } }