UNPKG

@hubbleprotocol/farms-sdk

Version:
643 lines (556 loc) 14.8 kB
import { ConnectionConfig, Keypair, PublicKey, TransactionInstruction, } from "@solana/web3.js"; import * as anchor from "@coral-xyz/anchor"; import { collToLamportsDecimal, createMint, getFarmAuthorityPDA, getFarmsProgramId, getFarmVaultPDA, getRewardVaultPDA, getTreasuryAuthorityPDA, getTreasuryVaultPDA, getUserStatePDA, GlobalConfigAccounts, mintTo, setupAta, solAirdrop, createGlobalConfigPublicKeyRentExempt, sleep, } from "./utils"; import Decimal from "decimal.js"; import { getMintDecimals } from "@project-serum/serum/lib/market"; import { Cluster, FarmsIdl, UserAccounts, FarmAccounts, parseKeypairFile, } from "./utils"; import { FarmState } from "../rpc_client/accounts"; import { Farms } from "../Farms"; import { FarmConfigOption } from "../rpc_client/types"; import { Chain, Web3Client } from "./sendTransactionsUtils"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; export const SIZE_GLOBAL_CONFIG = 2136; export const SIZE_FARM_STATE = 8336; const MAINNET_BETA_CHAIN_ID = 101; const LOCALNET_CHAIN_ID = 102; export type Env = { provider: anchor.AnchorProvider; program: anchor.Program; initialOwner: Keypair; cluster: Cluster; web3Client: Web3Client; }; export type Config = { strategyInfos: FarmInfo[]; }; export type FarmInfo = { address: PublicKey; owner: PublicKey; baseVaultAuthority: PublicKey; pool: PublicKey; globalConfig: PublicKey; tokenAMint: PublicKey; tokenAVault: PublicKey; tokenAVaultBump: number; tokenBMint: PublicKey; tokenBVault: PublicKey; tokenBVaultBump: number; sharesMint: PublicKey; sharesMintAuthority: PublicKey; sharesMintAuthorityBump: number; poolProgramId: PublicKey; yvaultsProgramId: PublicKey; }; export function setUpProgram(args: { clusterOverride?: string; adminFilePath?: string; programOverride?: PublicKey; }): Env { // Cluster & admin if (!args.clusterOverride) { throw new Error("Cluster is required"); } if (!args.adminFilePath) { throw new Error("Admin is required"); } const cluster = args.clusterOverride; let chain: Chain; if (cluster === "localnet") { chain = { name: "localnet", endpoint: "http://127.0.0.1:8899", chainID: LOCALNET_CHAIN_ID, displayName: "Localnet", }; } else { chain = { name: "mainnet-beta", endpoint: cluster, wsEndpoint: cluster?.replace("https:", "wss:") + "/whirligig", chainID: MAINNET_BETA_CHAIN_ID, displayName: "Mainnet Beta (Triton)", }; } const client = new Web3Client(chain); const connection = client.sendConnection; const payer = parseKeypairFile(args.adminFilePath); // @ts-ignore const wallet = new anchor.Wallet(payer); const provider = new anchor.AnchorProvider( connection, wallet, anchor.AnchorProvider.defaultOptions(), ); const initialOwner = payer; anchor.setProvider(provider); // Programs const farmsProgramId = args.programOverride || getFarmsProgramId(cluster); const program = new anchor.Program(FarmsIdl, farmsProgramId); return { initialOwner, provider, program, cluster: cluster as Cluster, web3Client: client, }; } export async function createGlobalAccountsWithAirdrop( env: Env, owner: Keypair = env.initialOwner, rewardTokens: Array<PublicKey>, ): Promise<GlobalConfigAccounts> { const globalConfig: Keypair = Keypair.generate(); let rewardAtas = new Array<PublicKey>(); let treasuryVaults = new Array<PublicKey>(); for (let index = 0; index < rewardTokens.length; index++) { rewardAtas.push(await setupAta(env.provider, rewardTokens[index], owner)); treasuryVaults.push( await getTreasuryVaultPDA( env.program.programId, globalConfig.publicKey, rewardTokens[index], ), ); } let treasuryVaultAuthority = await getTreasuryAuthorityPDA( env.program.programId, globalConfig.publicKey, ); const globalAccounts: GlobalConfigAccounts = { globalAdmin: owner, globalConfig, treasuryVaults, globalAdminRewardAtas: rewardAtas, treasuryVaultAuthority, }; return globalAccounts; } export async function createGlobalAccounts( env: Env, owner: Keypair = env.initialOwner, ): Promise<GlobalConfigAccounts> { let bufferSpaceZeroAccount = 0; const globalConfig: Keypair = await createGlobalConfigPublicKeyRentExempt( env.provider, env.program.programId, ); let rewardAtas = new Array<PublicKey>(); let treasuryVaults = new Array<PublicKey>(); let treasuryVaultAuthority = await getTreasuryAuthorityPDA( env.program.programId, globalConfig.publicKey, ); const globalAccounts: GlobalConfigAccounts = { globalAdmin: owner, globalConfig, treasuryVaults, globalAdminRewardAtas: rewardAtas, treasuryVaultAuthority, }; return globalAccounts; } export async function setUpGlobalConfigWithAirdrop( env: Env, owner: Keypair = env.initialOwner, rewardTokens: Array<PublicKey>, ): Promise<GlobalConfigAccounts> { const globalAccounts = await createGlobalAccountsWithAirdrop( env, owner, rewardTokens, ); const farmClient = new Farms(env.provider.connection); await farmClient.createGlobalConfig( globalAccounts.globalAdmin, globalAccounts.globalConfig, 0, ); return globalAccounts; } export async function setUpGlobalConfig( env: Env, owner: Keypair = env.initialOwner, ): Promise<GlobalConfigAccounts> { const globalAccounts = await createGlobalAccounts(env, owner); const farmClient = new Farms(env.provider.connection); await farmClient.createGlobalConfig( globalAccounts.globalAdmin, globalAccounts.globalConfig, 0, ); return globalAccounts; } export async function createUser( env: Env, farmAccounts: FarmAccounts, solAirdropAmount: Decimal, tokenAirdropAmount: Decimal, rewardAirdropAmounts: Array<Decimal>, owner?: Keypair, ): Promise<UserAccounts> { if (!owner) { owner = new anchor.web3.Keypair(); } const userState = await getUserStatePDA( env.program.programId, farmAccounts.farmState.publicKey, owner.publicKey, ); if (solAirdropAmount.gt(0)) { await solAirdrop(env.provider, owner.publicKey, solAirdropAmount); await sleep(1000); } let farmState = await FarmState.fetch( env.provider.connection, farmAccounts.farmState.publicKey, ); if (!farmState) { throw new Error("Farm state not found"); } const tokenAta = await setupAta(env.provider, farmState.token.mint, owner); let rewardAtas = new Array<PublicKey>(); for (let index = 0; index < farmState.numRewardTokens.toNumber(); index++) { rewardAtas.push( await setupAta( env.provider, farmState.rewardInfos[index].token.mint, owner, ), ); } await sleep(2000); if (tokenAirdropAmount.gt(0)) { await mintTo( env.provider, farmState.token.mint, tokenAta, collToLamportsDecimal( tokenAirdropAmount, farmState.token.decimals.toNumber(), ).toNumber(), ); } const testingUser: UserAccounts = { owner, userState: userState, tokenAta, rewardAtas, }; return testingUser; } export async function setUpUser( env: Env, farmAccounts: FarmAccounts, rewardTokens: Array<PublicKey>, owner?: Keypair, ): Promise<UserAccounts> { if (!owner) { owner = new anchor.web3.Keypair(); } const solAirdropAmount = new Decimal(5); const tokenAirdropAmount = new Decimal(2000000); let rewardAirdropAmounts = new Array<Decimal>(rewardTokens.length); rewardAirdropAmounts.fill(new Decimal(20000000)); const userAccounts = await createUser( env, farmAccounts, solAirdropAmount, tokenAirdropAmount, rewardAirdropAmounts, owner, ); const farmClient = new Farms(env.provider.connection); await farmClient.createNewUser( userAccounts.owner, farmAccounts.farmState.publicKey, 0, ); return userAccounts; } export async function createFarmAccountsWithAirdrops( env: Env, rewardTokens: Array<PublicKey>, tokenMint: PublicKey, solAirdropAmount: Decimal, rewardAirdropAmounts: Array<Decimal>, farmAdmin?: Keypair, ): Promise<FarmAccounts> { if (!farmAdmin) { farmAdmin = new anchor.web3.Keypair(); } const farmState: Keypair = Keypair.generate(); if (solAirdropAmount.gt(0)) { await solAirdrop(env.provider, farmAdmin.publicKey, solAirdropAmount); await sleep(1000); } const farmVault = await getFarmVaultPDA( env.program.programId, farmState.publicKey, tokenMint, ); const farmVaultAuthority = await getFarmAuthorityPDA( env.program.programId, farmState.publicKey, ); let rewardVaults = new Array<PublicKey>(); let adminRewardAtas = new Array<PublicKey>(); for (let index = 0; index < rewardTokens.length; index++) { rewardVaults.push( await getRewardVaultPDA( env.program.programId, farmState.publicKey, rewardTokens[index], ), ); adminRewardAtas.push( await setupAta(env.provider, rewardTokens[index], farmAdmin), ); } await sleep(2000); for (let index = 0; index < rewardTokens.length; index++) { let tokenDecimals = await getMintDecimals( env.provider.connection, rewardTokens[index], ); if (rewardAirdropAmounts[index].gt(0)) { await mintTo( env.provider, rewardTokens[index], adminRewardAtas[index], collToLamportsDecimal( rewardAirdropAmounts[index], tokenDecimals, ).toNumber(), ); } } let farmAccounts: FarmAccounts = { farmAdmin: farmAdmin, farmState: farmState, tokenMint, farmVault, rewardVaults, farmVaultAuthority, rewardMints: rewardTokens, adminRewardAtas, }; return farmAccounts; } export async function createFarmAccounts( env: Env, rewardTokens: Array<PublicKey>, tokenMint: PublicKey, farmAdmin: Keypair, ): Promise<FarmAccounts> { const farmState: Keypair = Keypair.generate(); const farmVault = await getFarmVaultPDA( env.program.programId, farmState.publicKey, tokenMint, ); const farmVaultAuthority = await getFarmAuthorityPDA( env.program.programId, farmState.publicKey, ); let rewardVaults = new Array<PublicKey>(); let adminRewardAtas = new Array<PublicKey>(); let farmAccounts: FarmAccounts = { farmAdmin: farmAdmin, farmState: farmState, tokenMint, farmVault, rewardVaults, farmVaultAuthority, rewardMints: rewardTokens, adminRewardAtas, }; return farmAccounts; } export async function setUpFarmWithAirdrops( env: Env, globalAccounts: GlobalConfigAccounts, rewardTokens: Array<PublicKey>, farmAdmin?: Keypair, tokenMint?: PublicKey, ): Promise<FarmAccounts> { if (!farmAdmin) { farmAdmin = new anchor.web3.Keypair(); } const solAirdropAmount = new Decimal(5); let rewardAirdropAmounts = new Array<Decimal>(rewardTokens.length); rewardAirdropAmounts.fill(new Decimal(10000)); if (!tokenMint) { tokenMint = await createMint(env.provider, env.initialOwner.publicKey); } const farmAccounts = await createFarmAccountsWithAirdrops( env, rewardTokens, tokenMint, solAirdropAmount, rewardAirdropAmounts, ); const farmClient = new Farms(env.provider.connection); await farmClient.createFarm( farmAccounts.farmAdmin, globalAccounts.globalConfig.publicKey, farmAccounts.farmState, tokenMint, "execute", 0, ); return farmAccounts; } export async function setUpFarm( env: Env, globalConfig: PublicKey, tokenMint: PublicKey, farmAdmin: Keypair, mode: string, priorityFeeMultiplier: number, ): Promise<FarmAccounts> { let rewardTokens = new Array<PublicKey>(); const farmAccounts = await createFarmAccounts( env, rewardTokens, tokenMint, farmAdmin, ); const farmClient = new Farms(env.provider.connection); await farmClient.createFarm( farmAccounts.farmAdmin, globalConfig, farmAccounts.farmState, tokenMint, mode, priorityFeeMultiplier, env.web3Client, ); return farmAccounts; } export async function setUpFarmIx( env: Env, globalConfig: PublicKey, tokenMint: PublicKey, farmAdmin: Keypair, mode: string, ): Promise<[TransactionInstruction[], FarmAccounts]> { let rewardTokens = new Array<PublicKey>(); const farmAccounts = await createFarmAccounts( env, rewardTokens, tokenMint, farmAdmin, ); const farmClient = new Farms(env.provider.connection); return [ await farmClient.createFarmIx( mode === "multisig" ? new PublicKey(process.env.MULTISIG!) : farmAccounts.farmAdmin.publicKey, farmAccounts.farmState, globalConfig, tokenMint, ), farmAccounts, ]; } export async function setupFarmsEnvironment( env: Env, numRewardTokens: number, minClaimDurationSeconds: number = 60, rewardRps: number = 50, ) { let rewardTokens = Array<PublicKey>(); for (let i = 0; i < numRewardTokens; i++) { rewardTokens.push( await createMint(env.provider, env.initialOwner.publicKey), ); } const globalAccounts = await setUpGlobalConfigWithAirdrop( env, env.initialOwner, rewardTokens, ); const farmAccounts = await setUpFarmWithAirdrops( env, globalAccounts, rewardTokens, ); const farmsClient = new Farms(env.provider.connection); for (let i = 0; i < numRewardTokens; i++) { await farmsClient.addRewardToFarm( farmAccounts.farmAdmin, globalAccounts.globalConfig.publicKey, farmAccounts.farmState.publicKey, rewardTokens[i], TOKEN_PROGRAM_ID, "execute", 0, ); } for (let i = 0; i < numRewardTokens; i++) { await farmsClient.addRewardAmountToFarm( farmAccounts.farmAdmin, farmAccounts.farmState.publicKey, rewardTokens[i], new Decimal(100), "execute", 0, ); } for (let i = 0; i < numRewardTokens; i++) { await farmsClient.updateFarmConfig( farmAccounts.farmAdmin, farmAccounts.farmState.publicKey, rewardTokens[i], new FarmConfigOption.UpdateRewardRps(), rewardRps, "execute", 0, ); await farmsClient.updateFarmConfig( farmAccounts.farmAdmin, farmAccounts.farmState.publicKey, rewardTokens[i], new FarmConfigOption.UpdateRewardMinClaimDuration(), minClaimDurationSeconds, "execute", 0, ); } const userAccounts = await setUpUser(env, farmAccounts, rewardTokens); return { farmsClient, farmAccounts, globalAccounts, userAccounts, rewardTokens, }; }