@raydium-io/raydium-sdk-v2
Version:
An SDK for building applications on top of Raydium.
989 lines (882 loc) • 36.5 kB
text/typescript
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>>;
}
}