UNPKG

@pump-fun/pump-swap-sdk

Version:

Official SDK for interacting with Pump Swap AMM protocol on Solana

1,671 lines (1,469 loc) 42.8 kB
import { Program } from "@coral-xyz/anchor"; import { PumpAmm } from "../types/pump_amm"; import { AccountInfo, Connection, PublicKey, SystemProgram, TransactionInstruction, } from "@solana/web3.js"; import { globalConfigPda, globalVolumeAccumulatorPda, lpMintPda, poolPda, PUMP_AMM_PROGRAM_ID, pumpAmmEventAuthorityPda, userVolumeAccumulatorPda, } from "./pda"; import { AccountLayout, ASSOCIATED_TOKEN_PROGRAM_ID, createAssociatedTokenAccountIdempotentInstruction, createCloseAccountInstruction, createSyncNativeInstruction, getAccount, getAssociatedTokenAddressSync, NATIVE_MINT, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, } from "@solana/spl-token"; import { depositToken0Internal } from "./deposit"; import { withdrawInternal } from "./withdraw"; import { buyBaseInputInternal, buyQuoteInputInternal } from "./buy"; import { sellBaseInputInternal, sellQuoteInputInternal } from "./sell"; import { CollectCoinCreatorFeeSolanaState, CommonSolanaState, CreatePoolSolanaState, DepositBaseResult, DepositQuoteResult, GlobalConfig, GlobalVolumeAccumulator, LiquidityAccounts, LiquiditySolanaState, Pool, SwapAccounts, SwapSolanaState, UserVolumeAccumulator, WithdrawResult, } from "../types/sdk"; import { getPumpAmmProgram } from "./util"; import BN from "bn.js"; import { currentDayTokens, totalUnclaimedTokens } from "./tokenIncentives"; export const POOL_ACCOUNT_NEW_SIZE = 300; export class PumpAmmInternalSdk { public readonly connection: Connection; private readonly program: Program<PumpAmm>; private readonly offlineProgram: Program<PumpAmm>; private readonly globalConfig: PublicKey; constructor(connection: Connection, programId: string = PUMP_AMM_PROGRAM_ID) { this.connection = connection; this.program = getPumpAmmProgram(connection, programId); this.offlineProgram = getPumpAmmProgram( null as any as Connection, programId, ); this.globalConfig = globalConfigPda(this.offlineProgram.programId)[0]; } programId(): PublicKey { return this.offlineProgram.programId; } globalConfigKey(): PublicKey { return this.globalConfig; } poolKey( index: number, creator: PublicKey, baseMint: PublicKey, quoteMint: PublicKey, ): [PublicKey, number] { return poolPda( index, creator, baseMint, quoteMint, this.offlineProgram.programId, ); } lpMintKey(pool: PublicKey): [PublicKey, number] { return lpMintPda(pool, this.offlineProgram.programId); } fetchGlobalConfigAccount(): Promise<GlobalConfig> { return this.program.account.globalConfig.fetch(this.globalConfig); } fetchPool(pool: PublicKey): Promise<Pool> { return this.program.account.pool.fetch(pool); } decodeGlobalConfig( globalConfigAccountInfo: AccountInfo<Buffer>, ): GlobalConfig { return this.offlineProgram.coder.accounts.decode<GlobalConfig>( "globalConfig", globalConfigAccountInfo.data, ); } decodePool(poolAccountInfo: AccountInfo<Buffer>) { return this.offlineProgram.coder.accounts.decode<Pool>( "pool", poolAccountInfo.data, ); } fetchGlobalVolumeAccumulator(): Promise<GlobalVolumeAccumulator> { return this.program.account.globalVolumeAccumulator.fetch( globalVolumeAccumulatorPda()[0], ); } decodeGlobalVolumeAccumulator( globalVolumeAccumulatorAccountInfo: AccountInfo<Buffer>, ): GlobalVolumeAccumulator { return this.offlineProgram.coder.accounts.decode<GlobalVolumeAccumulator>( "globalVolumeAccumulator", globalVolumeAccumulatorAccountInfo.data, ); } fetchUserVolumeAccumulator( user: PublicKey, ): Promise<UserVolumeAccumulator | null> { return this.program.account.userVolumeAccumulator.fetchNullable( userVolumeAccumulatorPda(user)[0], ); } decodeUserVolumeAccumulator( userVolumeAccumulatorAccountInfo: AccountInfo<Buffer>, ): UserVolumeAccumulator { return this.offlineProgram.coder.accounts.decode<UserVolumeAccumulator>( "userVolumeAccumulator", userVolumeAccumulatorAccountInfo.data, ); } async createPoolInstructionsInternal( createPoolSolanaState: CreatePoolSolanaState, baseIn: BN, quoteIn: BN, ): Promise<TransactionInstruction[]> { const { index, creator, baseMint, quoteMint, poolKey, baseTokenProgram, quoteTokenProgram, userBaseTokenAccount, userQuoteTokenAccount, poolBaseTokenAccount, poolQuoteTokenAccount, userBaseAccountInfo, userQuoteAccountInfo, poolBaseAccountInfo, poolQuoteAccountInfo, } = createPoolSolanaState; return await this.withWsolAccounts( creator, baseMint, userBaseTokenAccount, this.accountExists(userBaseAccountInfo, baseTokenProgram), baseIn, quoteMint, userQuoteTokenAccount, this.accountExists(userQuoteAccountInfo, quoteTokenProgram), quoteIn, async () => { const instructions: TransactionInstruction[] = []; if (!this.accountExists(poolBaseAccountInfo, baseTokenProgram)) { instructions.push( createAssociatedTokenAccountIdempotentInstruction( creator, poolBaseTokenAccount, poolKey, baseMint, baseTokenProgram, ), ); } if (!this.accountExists(poolQuoteAccountInfo, quoteTokenProgram)) { instructions.push( createAssociatedTokenAccountIdempotentInstruction( creator, poolQuoteTokenAccount, poolKey, quoteMint, quoteTokenProgram, ), ); } instructions.push( await this.offlineProgram.methods .createPool(index, baseIn, quoteIn, SystemProgram.programId) .accountsPartial({ globalConfig: this.globalConfig, baseMint, quoteMint, creator, userBaseTokenAccount, userQuoteTokenAccount, baseTokenProgram, quoteTokenProgram, }) .instruction(), ); return instructions; }, ); } async depositInstructionsInternal( liquiditySolanaState: LiquiditySolanaState, lpToken: BN, maxBase: BN, maxQuote: BN, ): Promise<TransactionInstruction[]> { const { pool, user, userPoolAccountInfo, userBaseTokenAccount, userQuoteTokenAccount, userPoolTokenAccount, userBaseAccountInfo, userQuoteAccountInfo, baseTokenProgram, quoteTokenProgram, } = liquiditySolanaState; const { baseMint, quoteMint, lpMint } = pool; const liquidityAccounts = this.liquidityAccounts(liquiditySolanaState); return await this.withFixPoolInstructions( liquiditySolanaState, async () => { return await this.withWsolAccounts( user, baseMint, userBaseTokenAccount, this.accountExists(userBaseAccountInfo, baseTokenProgram), maxBase, quoteMint, userQuoteTokenAccount, this.accountExists(userQuoteAccountInfo, quoteTokenProgram), maxQuote, async () => { const instructions: TransactionInstruction[] = []; if ( !this.accountExists(userPoolAccountInfo, TOKEN_2022_PROGRAM_ID) ) { instructions.push( createAssociatedTokenAccountIdempotentInstruction( user, userPoolTokenAccount, user, lpMint, TOKEN_2022_PROGRAM_ID, ), ); } instructions.push( await this.offlineProgram.methods .deposit(lpToken, maxBase, maxQuote) .accounts(liquidityAccounts) .instruction(), ); return instructions; }, ); }, ); } private async withWsolAccounts( user: PublicKey, baseMint: PublicKey, userBaseAta: PublicKey, userBaseAtaExists: boolean, baseAmount: BN, quoteMint: PublicKey, userQuoteAta: PublicKey, userQuoteAtaExists: boolean, quoteAmount: BN, block: () => Promise<TransactionInstruction[]>, ) { return await this.withWsolAccount( user, user, baseMint, userBaseAta, userBaseAtaExists, baseAmount, async () => this.withWsolAccount( user, user, quoteMint, userQuoteAta, userQuoteAtaExists, quoteAmount, block, ), ); } private async withWsolAccount( payer: PublicKey, user: PublicKey, mint: PublicKey, ata: PublicKey, ataExists: boolean, amount: BN, block: () => Promise<TransactionInstruction[]>, closeWsolAccount: boolean = true, ): Promise<TransactionInstruction[]> { const instructions: TransactionInstruction[] = []; if (mint.equals(NATIVE_MINT)) { if (!ataExists) { instructions.push( createAssociatedTokenAccountIdempotentInstruction( payer, ata, user, NATIVE_MINT, ), ); } if (amount.gtn(0)) { instructions.push( SystemProgram.transfer({ fromPubkey: user, toPubkey: ata, lamports: BigInt(amount.toString()), }), createSyncNativeInstruction(ata), ); } } const blockInstructions = await block(); instructions.push(...blockInstructions); if (mint.equals(NATIVE_MINT) && closeWsolAccount) { instructions.push( createCloseAccountInstruction( ata, user, user, undefined, TOKEN_PROGRAM_ID, ), ); } return instructions; } private accountExists( accountInfo: AccountInfo<Buffer> | null, owner: PublicKey, ): boolean { return accountInfo !== null && accountInfo.owner.equals(owner); } depositBaseInputInternal( liquiditySolanaState: LiquiditySolanaState, base: BN, slippage: number, ): DepositBaseResult { const { pool, poolBaseTokenAccount, poolQuoteTokenAccount } = liquiditySolanaState; const { token1, lpToken, maxToken0, maxToken1 } = depositToken0Internal( base, slippage, new BN(poolBaseTokenAccount.amount.toString()), new BN(poolQuoteTokenAccount.amount.toString()), pool.lpSupply, ); return { quote: token1, lpToken, maxBase: maxToken0, maxQuote: maxToken1, }; } depositQuoteInputInternal( liquiditySolanaState: LiquiditySolanaState, quote: BN, slippage: number, ): DepositQuoteResult { const { pool, poolBaseTokenAccount, poolQuoteTokenAccount } = liquiditySolanaState; const { token1, lpToken, maxToken0, maxToken1 } = depositToken0Internal( quote, slippage, new BN(poolQuoteTokenAccount.amount.toString()), new BN(poolBaseTokenAccount.amount.toString()), pool.lpSupply, ); return { base: token1, lpToken, maxBase: maxToken1, maxQuote: maxToken0, }; } async withdrawInstructionsInternal( liquiditySolanaState: LiquiditySolanaState, lpTokenAmountIn: BN, minBaseAmountOut: BN, minQuoteAmountOut: BN, ): Promise<TransactionInstruction[]> { const { pool, baseTokenProgram, quoteTokenProgram, user, userBaseAccountInfo, userQuoteAccountInfo, userBaseTokenAccount, userQuoteTokenAccount, } = liquiditySolanaState; const { baseMint, quoteMint } = pool; const liquidityAccounts = this.liquidityAccounts(liquiditySolanaState); return await this.withFixPoolInstructions( liquiditySolanaState, async () => { const instructions: TransactionInstruction[] = []; let baseWsolAtaCreated = false; if (!this.accountExists(userBaseAccountInfo, baseTokenProgram)) { instructions.push( createAssociatedTokenAccountIdempotentInstruction( user, userBaseTokenAccount, user, baseMint, baseTokenProgram, ), ); if (baseMint.equals(NATIVE_MINT)) { baseWsolAtaCreated = true; } } let quoteWsolAtaCreated = false; if (!this.accountExists(userQuoteAccountInfo, quoteTokenProgram)) { instructions.push( createAssociatedTokenAccountIdempotentInstruction( user, userQuoteTokenAccount, user, quoteMint, quoteTokenProgram, ), ); if (quoteMint.equals(NATIVE_MINT)) { quoteWsolAtaCreated = true; } } instructions.push( await this.offlineProgram.methods .withdraw(lpTokenAmountIn, minBaseAmountOut, minQuoteAmountOut) .accounts(liquidityAccounts) .instruction(), ); if (baseWsolAtaCreated) { instructions.push( createCloseAccountInstruction( userBaseTokenAccount, user, user, undefined, TOKEN_PROGRAM_ID, ), ); } if (quoteWsolAtaCreated) { instructions.push( createCloseAccountInstruction( userQuoteTokenAccount, user, user, undefined, TOKEN_PROGRAM_ID, ), ); } return instructions; }, ); } withdrawInputsInternal( liquiditySolanaState: LiquiditySolanaState, lpAmount: BN, slippage: number, ): WithdrawResult { const { pool, poolBaseTokenAccount, poolQuoteTokenAccount } = liquiditySolanaState; return withdrawInternal( lpAmount, slippage, new BN(poolBaseTokenAccount.amount.toString()), new BN(poolQuoteTokenAccount.amount.toString()), pool.lpSupply, ); } private liquidityAccounts( liquiditySolanaState: LiquiditySolanaState, ): LiquidityAccounts { const { poolKey, pool, user, userBaseTokenAccount, userQuoteTokenAccount, userPoolTokenAccount, } = liquiditySolanaState; const { baseMint, quoteMint, lpMint, poolBaseTokenAccount, poolQuoteTokenAccount, } = pool; let program = this.programId(); let [eventAuthority] = pumpAmmEventAuthorityPda(program); return { pool: poolKey, globalConfig: this.globalConfig, user, baseMint, quoteMint, lpMint, userBaseTokenAccount, userQuoteTokenAccount, userPoolTokenAccount, poolBaseTokenAccount, poolQuoteTokenAccount, tokenProgram: TOKEN_PROGRAM_ID, token2022Program: TOKEN_2022_PROGRAM_ID, eventAuthority, program, }; } async buyInstructionsInternal( swapSolanaState: SwapSolanaState, baseOut: BN, maxQuoteIn: BN, ): Promise<TransactionInstruction[]> { return await this.withFixPoolInstructions(swapSolanaState, async () => { return await this.buyInstructionsInternalNoPool( swapSolanaState, baseOut, maxQuoteIn, ); }); } async createPoolSolanaState( index: number, creator: PublicKey, baseMint: PublicKey, quoteMint: PublicKey, userBaseTokenAccount: PublicKey | undefined = undefined, userQuoteTokenAccount: PublicKey | undefined = undefined, ): Promise<CreatePoolSolanaState> { const [globalConfigAccountInfo, baseMintAccountInfo, quoteMintAccountInfo] = await this.connection.getMultipleAccountsInfo([ this.globalConfig, baseMint, quoteMint, ]); if (globalConfigAccountInfo === null) { throw new Error("Global config account not found"); } if (baseMintAccountInfo === null) { throw new Error(`baseMint=${baseMint.toString()} not found`); } if (quoteMintAccountInfo === null) { throw new Error(`quoteMint=${quoteMint.toString()} not found`); } const globalConfig = this.decodeGlobalConfig(globalConfigAccountInfo); const [baseTokenProgram, quoteTokenProgram] = [ baseMintAccountInfo.owner, quoteMintAccountInfo.owner, ]; const [poolKey] = poolPda( index, creator, baseMint, quoteMint, this.offlineProgram.programId, ); const poolBaseTokenAccount = getAssociatedTokenAddressSync( baseMint, poolKey, true, baseTokenProgram, ); const poolQuoteTokenAccount = getAssociatedTokenAddressSync( quoteMint, poolKey, true, quoteTokenProgram, ); const [poolBaseAccountInfo, poolQuoteAccountInfo] = await this.connection.getMultipleAccountsInfo([ poolBaseTokenAccount, poolQuoteTokenAccount, ]); if (userBaseTokenAccount === undefined) { userBaseTokenAccount = getAssociatedTokenAddressSync( baseMint, creator, true, baseTokenProgram, ); } if (userQuoteTokenAccount === undefined) { userQuoteTokenAccount = getAssociatedTokenAddressSync( quoteMint, creator, true, quoteTokenProgram, ); } const [userBaseAccountInfo, userQuoteAccountInfo] = await this.connection.getMultipleAccountsInfo([ userBaseTokenAccount, userQuoteTokenAccount, ]); return { index, creator, baseMint, quoteMint, globalConfig, poolKey, poolBaseTokenAccount, poolQuoteTokenAccount, baseTokenProgram, quoteTokenProgram, userBaseTokenAccount, userQuoteTokenAccount, userBaseAccountInfo, userQuoteAccountInfo, poolBaseAccountInfo, poolQuoteAccountInfo, }; } async swapSolanaState( poolKey: PublicKey, user: PublicKey, userBaseTokenAccount: PublicKey | undefined = undefined, userQuoteTokenAccount: PublicKey | undefined = undefined, ): Promise<SwapSolanaState> { const [globalConfigAccountInfo, poolAccountInfo] = await this.connection.getMultipleAccountsInfo([ this.globalConfig, poolKey, ]); if (globalConfigAccountInfo === null) { throw new Error("Global config account not found"); } if (poolAccountInfo === null) { throw new Error("Pool account not found"); } const globalConfig = this.decodeGlobalConfig(globalConfigAccountInfo); const pool = this.decodePool(poolAccountInfo); const { baseMint, quoteMint, poolBaseTokenAccount, poolQuoteTokenAccount } = pool; const [ baseMintAccountInfo, quoteMintAccountInfo, poolBaseAccountInfo, poolQuoteAccountInfo, ] = await this.connection.getMultipleAccountsInfo([ baseMint, quoteMint, poolBaseTokenAccount, poolQuoteTokenAccount, ]); if (baseMintAccountInfo === null) { throw new Error(`baseMint=${baseMint.toString()} not found`); } if (quoteMintAccountInfo === null) { throw new Error(`quoteMint=${quoteMint.toString()} not found`); } if (poolBaseAccountInfo === null) { throw new Error( `Pool base token account ${poolBaseTokenAccount.toString()} not found`, ); } if (poolQuoteAccountInfo === null) { throw new Error( `Pool quote token account ${poolQuoteTokenAccount.toString()} not found`, ); } const [baseTokenProgram, quoteTokenProgram] = [ baseMintAccountInfo.owner, quoteMintAccountInfo.owner, ]; const decodedPoolBaseTokenAccount = AccountLayout.decode( poolBaseAccountInfo.data, ); const decodedPoolQuoteTokenAccount = AccountLayout.decode( poolQuoteAccountInfo.data, ); if (userBaseTokenAccount === undefined) { userBaseTokenAccount = getAssociatedTokenAddressSync( baseMint, user, true, baseTokenProgram, ); } if (userQuoteTokenAccount === undefined) { userQuoteTokenAccount = getAssociatedTokenAddressSync( quoteMint, user, true, quoteTokenProgram, ); } const [userBaseAccountInfo, userQuoteAccountInfo] = await this.connection.getMultipleAccountsInfo([ userBaseTokenAccount, userQuoteTokenAccount, ]); return { globalConfig, poolKey, poolAccountInfo, pool, poolBaseAmount: new BN(decodedPoolBaseTokenAccount.amount.toString()), poolQuoteAmount: new BN(decodedPoolQuoteTokenAccount.amount.toString()), baseTokenProgram, quoteTokenProgram, user, userBaseTokenAccount, userQuoteTokenAccount, userBaseAccountInfo, userQuoteAccountInfo, }; } async swapSolanaStateNoPool( poolKey: PublicKey, user: PublicKey, userBaseTokenAccount: PublicKey | undefined = undefined, userQuoteTokenAccount: PublicKey | undefined = undefined, ): Promise<SwapSolanaState> { const [globalConfigAccountInfo, poolAccountInfo] = await this.connection.getMultipleAccountsInfo([ this.globalConfig, poolKey, ]); if (globalConfigAccountInfo === null) { throw new Error("Global config account not found"); } if (poolAccountInfo === null) { throw new Error("Pool account not found"); } const globalConfig = this.decodeGlobalConfig(globalConfigAccountInfo); const pool = this.decodePool(poolAccountInfo); const { baseMint, quoteMint, poolBaseTokenAccount, poolQuoteTokenAccount } = pool; const [ baseMintAccountInfo, quoteMintAccountInfo, poolBaseAccountInfo, poolQuoteAccountInfo, ] = await this.connection.getMultipleAccountsInfo([ baseMint, quoteMint, poolBaseTokenAccount, poolQuoteTokenAccount, ]); if (baseMintAccountInfo === null) { throw new Error(`baseMint=${baseMint.toString()} not found`); } if (quoteMintAccountInfo === null) { throw new Error(`quoteMint=${quoteMint.toString()} not found`); } if (poolBaseAccountInfo === null) { throw new Error( `Pool base token account ${poolBaseTokenAccount.toString()} not found`, ); } if (poolQuoteAccountInfo === null) { throw new Error( `Pool quote token account ${poolQuoteTokenAccount.toString()} not found`, ); } const [baseTokenProgram, quoteTokenProgram] = [ baseMintAccountInfo.owner, quoteMintAccountInfo.owner, ]; const decodedPoolBaseTokenAccount = AccountLayout.decode( poolBaseAccountInfo.data, ); const decodedPoolQuoteTokenAccount = AccountLayout.decode( poolQuoteAccountInfo.data, ); if (userBaseTokenAccount === undefined) { userBaseTokenAccount = getAssociatedTokenAddressSync( baseMint, user, true, baseTokenProgram, ); } if (userQuoteTokenAccount === undefined) { userQuoteTokenAccount = getAssociatedTokenAddressSync( quoteMint, user, true, quoteTokenProgram, ); } const [userBaseAccountInfo, userQuoteAccountInfo] = await this.connection.getMultipleAccountsInfo([ userBaseTokenAccount, userQuoteTokenAccount, ]); return { globalConfig, poolKey, poolAccountInfo, pool, poolBaseAmount: new BN(decodedPoolBaseTokenAccount.amount.toString()), poolQuoteAmount: new BN(decodedPoolQuoteTokenAccount.amount.toString()), baseTokenProgram, quoteTokenProgram, user, userBaseTokenAccount, userQuoteTokenAccount, userBaseAccountInfo, userQuoteAccountInfo, }; } async liquiditySolanaState( poolKey: PublicKey, user: PublicKey, userBaseTokenAccount: PublicKey | undefined = undefined, userQuoteTokenAccount: PublicKey | undefined = undefined, userPoolTokenAccount: PublicKey | undefined = undefined, ): Promise<LiquiditySolanaState> { const [globalConfigAccountInfo, poolAccountInfo] = await this.connection.getMultipleAccountsInfo([ this.globalConfig, poolKey, ]); if (globalConfigAccountInfo === null) { throw new Error("Global config account not found"); } if (poolAccountInfo === null) { throw new Error("Pool account not found"); } const globalConfig = this.decodeGlobalConfig(globalConfigAccountInfo); const pool = this.decodePool(poolAccountInfo); const { baseMint, quoteMint, lpMint, poolBaseTokenAccount, poolQuoteTokenAccount, } = pool; const [ baseMintAccountInfo, quoteMintAccountInfo, poolBaseAccountInfo, poolQuoteAccountInfo, ] = await this.connection.getMultipleAccountsInfo([ baseMint, quoteMint, poolBaseTokenAccount, poolQuoteTokenAccount, ]); if (baseMintAccountInfo === null) { throw new Error(`baseMint=${baseMint.toString()} not found`); } if (quoteMintAccountInfo === null) { throw new Error(`quoteMint=${quoteMint.toString()} not found`); } if (poolBaseAccountInfo === null) { throw new Error( `Pool base token account ${poolBaseTokenAccount.toString()} not found`, ); } if (poolQuoteAccountInfo === null) { throw new Error( `Pool quote token account ${poolQuoteTokenAccount.toString()} not found`, ); } const [baseTokenProgram, quoteTokenProgram] = [ baseMintAccountInfo.owner, quoteMintAccountInfo.owner, ]; const decodedPoolBaseTokenAccount = AccountLayout.decode( poolBaseAccountInfo.data, ); const decodedPoolQuoteTokenAccount = AccountLayout.decode( poolQuoteAccountInfo.data, ); if (userBaseTokenAccount === undefined) { userBaseTokenAccount = getAssociatedTokenAddressSync( baseMint, user, true, baseTokenProgram, ); } if (userQuoteTokenAccount === undefined) { userQuoteTokenAccount = getAssociatedTokenAddressSync( quoteMint, user, true, quoteTokenProgram, ); } if (userPoolTokenAccount === undefined) { userPoolTokenAccount = getAssociatedTokenAddressSync( lpMint, user, true, TOKEN_2022_PROGRAM_ID, ); } const [userBaseAccountInfo, userQuoteAccountInfo, userPoolAccountInfo] = await this.connection.getMultipleAccountsInfo([ userBaseTokenAccount, userQuoteTokenAccount, userPoolTokenAccount, ]); return { globalConfig, poolKey, poolAccountInfo, pool, poolBaseTokenAccount: decodedPoolBaseTokenAccount, poolQuoteTokenAccount: decodedPoolQuoteTokenAccount, baseTokenProgram, quoteTokenProgram, user, userBaseTokenAccount, userQuoteTokenAccount, userPoolTokenAccount, userBaseAccountInfo, userQuoteAccountInfo, userPoolAccountInfo, }; } async buyInstructionsInternalNoPool( swapSolanaState: SwapSolanaState, baseOut: BN, maxQuoteIn: BN, ): Promise<TransactionInstruction[]> { const { userBaseAccountInfo, userQuoteAccountInfo } = swapSolanaState; const swapAccounts = this.swapAccounts(swapSolanaState); const { user, baseMint, quoteMint, userBaseTokenAccount, userQuoteTokenAccount, baseTokenProgram, quoteTokenProgram, } = swapAccounts; return this.withWsolAccount( user, user, quoteMint, userQuoteTokenAccount, this.accountExists(userQuoteAccountInfo, quoteTokenProgram), maxQuoteIn, async () => { const instructions = []; if (!this.accountExists(userBaseAccountInfo, baseTokenProgram)) { instructions.push( createAssociatedTokenAccountIdempotentInstruction( user, userBaseTokenAccount, user, baseMint, baseTokenProgram, ), ); } instructions.push( await this.offlineProgram.methods .buy(baseOut, maxQuoteIn, { 0: true }) .accounts(swapAccounts) .instruction(), ); if (baseMint.equals(NATIVE_MINT)) { instructions.push( createCloseAccountInstruction( userBaseTokenAccount, user, user, undefined, TOKEN_PROGRAM_ID, ), ); } return instructions; }, ); } async buyBaseInput( swapSolanaState: SwapSolanaState, base: BN, slippage: number, ): Promise<TransactionInstruction[]> { const { pool, globalConfig, poolBaseAmount, poolQuoteAmount } = swapSolanaState; const { maxQuote } = buyBaseInputInternal( base, slippage, poolBaseAmount, poolQuoteAmount, globalConfig, pool.coinCreator, ); return this.buyInstructionsInternal(swapSolanaState, base, maxQuote); } async buyQuoteInput( swapSolanaState: SwapSolanaState, quote: BN, slippage: number, ): Promise<TransactionInstruction[]> { const { globalConfig, pool, poolBaseAmount, poolQuoteAmount } = swapSolanaState; const { base, maxQuote } = buyQuoteInputInternal( quote, slippage, poolBaseAmount, poolQuoteAmount, globalConfig, pool.coinCreator, ); return this.buyInstructionsInternal(swapSolanaState, base, maxQuote); } async sellInstructionsInternal( swapSolanaState: SwapSolanaState, baseAmountIn: BN, minQuoteAmountOut: BN, ): Promise<TransactionInstruction[]> { return await this.withFixPoolInstructions(swapSolanaState, async () => { return await this.sellInstructionsInternalNoPool( swapSolanaState, baseAmountIn, minQuoteAmountOut, ); }); } private async withFixPoolInstructions( commonSolanaState: CommonSolanaState, block: () => Promise<TransactionInstruction[]>, ): Promise<TransactionInstruction[]> { const { poolAccountInfo, poolKey, user } = commonSolanaState; const instructions: TransactionInstruction[] = []; if ( poolAccountInfo === null || poolAccountInfo.data.length < POOL_ACCOUNT_NEW_SIZE ) { instructions.push( await this.offlineProgram.methods .extendAccount() .accountsPartial({ account: poolKey, user, }) .instruction(), ); } return [...instructions, ...(await block())]; } async sellInstructionsInternalNoPool( swapSolanaState: SwapSolanaState, baseAmountIn: BN, minQuoteAmountOut: BN, ): Promise<TransactionInstruction[]> { const { userBaseAccountInfo, userQuoteAccountInfo } = swapSolanaState; const swapAccounts = this.swapAccounts(swapSolanaState); const { user, baseMint, quoteMint, userBaseTokenAccount, userQuoteTokenAccount, baseTokenProgram, quoteTokenProgram, } = swapAccounts; return this.withWsolAccount( user, user, baseMint, userBaseTokenAccount, this.accountExists(userBaseAccountInfo, baseTokenProgram), baseAmountIn, async () => { const instructions = []; if (!this.accountExists(userQuoteAccountInfo, quoteTokenProgram)) { instructions.push( createAssociatedTokenAccountIdempotentInstruction( user, userQuoteTokenAccount, user, quoteMint, quoteTokenProgram, ), ); } instructions.push( await this.offlineProgram.methods .sell(baseAmountIn, minQuoteAmountOut) .accounts(swapAccounts) .instruction(), ); if (quoteMint.equals(NATIVE_MINT)) { instructions.push( createCloseAccountInstruction( userQuoteTokenAccount, user, user, undefined, TOKEN_PROGRAM_ID, ), ); } return instructions; }, ); } async sellBaseInput( swapSolanaState: SwapSolanaState, base: BN, slippage: number, ): Promise<TransactionInstruction[]> { const { globalConfig, pool, poolBaseAmount, poolQuoteAmount } = swapSolanaState; const { minQuote } = sellBaseInputInternal( base, slippage, poolBaseAmount, poolQuoteAmount, globalConfig, pool.coinCreator, ); return this.sellInstructionsInternal(swapSolanaState, base, minQuote); } async sellQuoteInput( swapSolanaState: SwapSolanaState, quote: BN, slippage: number, ): Promise<TransactionInstruction[]> { const { globalConfig, pool, poolBaseAmount, poolQuoteAmount } = swapSolanaState; const { base, minQuote } = sellQuoteInputInternal( quote, slippage, poolBaseAmount, poolQuoteAmount, globalConfig, pool.coinCreator, ); return this.sellInstructionsInternal(swapSolanaState, base, minQuote); } async extendAccount( account: PublicKey, user: PublicKey, ): Promise<TransactionInstruction> { return this.offlineProgram.methods .extendAccount() .accountsPartial({ account, user, }) .instruction(); } async collectCoinCreatorFeeSolanaState( coinCreator: PublicKey, coinCreatorTokenAccount: PublicKey | undefined = undefined, ): Promise<CollectCoinCreatorFeeSolanaState> { const quoteMint = NATIVE_MINT; const quoteTokenProgram = TOKEN_PROGRAM_ID; let coinCreatorVaultAuthority = this.coinCreatorVaultAuthorityPda(coinCreator); let coinCreatorVaultAta = this.coinCreatorVaultAta( coinCreatorVaultAuthority, quoteMint, quoteTokenProgram, ); if (coinCreatorTokenAccount === undefined) { coinCreatorTokenAccount = getAssociatedTokenAddressSync( quoteMint, coinCreator, true, quoteTokenProgram, ); } const [coinCreatorVaultAtaAccountInfo, coinCreatorTokenAccountInfo] = await this.connection.getMultipleAccountsInfo([ coinCreatorVaultAta, coinCreatorTokenAccount, ]); return { coinCreator, quoteMint, quoteTokenProgram, coinCreatorVaultAuthority, coinCreatorVaultAta, coinCreatorTokenAccount, coinCreatorVaultAtaAccountInfo, coinCreatorTokenAccountInfo, }; } async collectCoinCreatorFee( collectCoinCreatorFeeSolanaState: CollectCoinCreatorFeeSolanaState, ): Promise<TransactionInstruction[]> { const { coinCreator, quoteMint, quoteTokenProgram, coinCreatorVaultAuthority, coinCreatorVaultAta, coinCreatorTokenAccount, coinCreatorVaultAtaAccountInfo, coinCreatorTokenAccountInfo, } = collectCoinCreatorFeeSolanaState; return await this.withWsolAccount( coinCreator, coinCreatorVaultAuthority, quoteMint, coinCreatorVaultAta, this.accountExists(coinCreatorVaultAtaAccountInfo, quoteTokenProgram), new BN(0), async () => { return await this.withWsolAccount( coinCreator, coinCreator, quoteMint, coinCreatorTokenAccount, this.accountExists(coinCreatorTokenAccountInfo, quoteTokenProgram), new BN(0), async () => { return [ await this.offlineProgram.methods .collectCoinCreatorFee() .accountsPartial({ coinCreator, coinCreatorTokenAccount, quoteMint, quoteTokenProgram, }) .instruction(), ]; }, ); }, false, ); } async getCoinCreatorVaultBalance(coinCreator: PublicKey): Promise<BN> { const quoteMint = NATIVE_MINT; const quoteTokenProgram = TOKEN_PROGRAM_ID; const coinCreatorVaultAuthority = this.coinCreatorVaultAuthorityPda(coinCreator); const coinCreatorVaultAta = this.coinCreatorVaultAta( coinCreatorVaultAuthority, quoteMint, quoteTokenProgram, ); try { const tokenAccount = await getAccount( this.connection, coinCreatorVaultAta, undefined, quoteTokenProgram, ); return new BN(tokenAccount.amount.toString()); } catch (e) { console.error(`Error fetching token account ${coinCreatorVaultAta}:`, e); return new BN(0); } } async setCoinCreator(pool: PublicKey): Promise<TransactionInstruction> { return this.offlineProgram.methods .setCoinCreator() .accountsPartial({ pool, }) .instruction(); } private swapAccounts(swapSolanaState: SwapSolanaState): SwapAccounts { const { globalConfig, poolKey, pool, baseTokenProgram, quoteTokenProgram, user, userBaseTokenAccount, userQuoteTokenAccount, } = swapSolanaState; const { protocolFeeRecipients } = globalConfig; const protocolFeeRecipient = protocolFeeRecipients[ Math.floor(Math.random() * protocolFeeRecipients.length) ]; const { baseMint, quoteMint, poolBaseTokenAccount, poolQuoteTokenAccount, coinCreator, } = pool; const coinCreatorVaultAuthority = this.coinCreatorVaultAuthorityPda(coinCreator); let program = this.programId(); let [eventAuthority] = pumpAmmEventAuthorityPda(program); return { pool: poolKey, globalConfig: this.globalConfig, user, baseMint, quoteMint, userBaseTokenAccount, userQuoteTokenAccount, poolBaseTokenAccount, poolQuoteTokenAccount, protocolFeeRecipient, protocolFeeRecipientTokenAccount: getAssociatedTokenAddressSync( quoteMint, protocolFeeRecipient, true, quoteTokenProgram, ), baseTokenProgram, quoteTokenProgram, systemProgram: SystemProgram.programId, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, eventAuthority, program, coinCreatorVaultAta: this.coinCreatorVaultAta( coinCreatorVaultAuthority, quoteMint, quoteTokenProgram, ), coinCreatorVaultAuthority, }; } coinCreatorVaultAuthorityPda(coinCreator: PublicKey) { const [coinCreatorVaultAuthority] = PublicKey.findProgramAddressSync( [Buffer.from("creator_vault"), coinCreator.toBuffer()], this.programId(), ); return coinCreatorVaultAuthority; } coinCreatorVaultAta( coinCreatorVaultAuthority: PublicKey, quoteMint: PublicKey, quoteTokenProgram: PublicKey, ) { return getAssociatedTokenAddressSync( quoteMint, coinCreatorVaultAuthority, true, quoteTokenProgram, ); } async claimTokenIncentivesInternal( user: PublicKey, payer: PublicKey, ): Promise<TransactionInstruction[]> { const { mint } = await this.fetchGlobalVolumeAccumulator(); if (mint.equals(PublicKey.default)) { return []; } const [mintAccountInfo, userAccumulatorAccountInfo] = await this.connection.getMultipleAccountsInfo([ mint, userVolumeAccumulatorPda(user)[0], ]); if (!mintAccountInfo) { return []; } if (!userAccumulatorAccountInfo) { return []; } return [ await this.offlineProgram.methods .claimTokenIncentives() .accountsPartial({ user, payer, mint, tokenProgram: mintAccountInfo.owner, }) .instruction(), ]; } async getTotalUnclaimedTokens(user: PublicKey): Promise<BN> { const [ globalVolumeAccumulatorAccountInfo, userVolumeAccumulatorAccountInfo, ] = await this.connection.getMultipleAccountsInfo([ globalVolumeAccumulatorPda()[0], userVolumeAccumulatorPda(user)[0], ]); if ( !globalVolumeAccumulatorAccountInfo || !userVolumeAccumulatorAccountInfo ) { return new BN(0); } const globalVolumeAccumulator = this.decodeGlobalVolumeAccumulator( globalVolumeAccumulatorAccountInfo, ); const userVolumeAccumulator = this.decodeUserVolumeAccumulator( userVolumeAccumulatorAccountInfo, ); return totalUnclaimedTokens(globalVolumeAccumulator, userVolumeAccumulator); } async getCurrentDayTokens(user: PublicKey): Promise<BN> { const [ globalVolumeAccumulatorAccountInfo, userVolumeAccumulatorAccountInfo, ] = await this.connection.getMultipleAccountsInfo([ globalVolumeAccumulatorPda()[0], userVolumeAccumulatorPda(user)[0], ]); if ( !globalVolumeAccumulatorAccountInfo || !userVolumeAccumulatorAccountInfo ) { return new BN(0); } const globalVolumeAccumulator = this.decodeGlobalVolumeAccumulator( globalVolumeAccumulatorAccountInfo, ); const userVolumeAccumulator = this.decodeUserVolumeAccumulator( userVolumeAccumulatorAccountInfo, ); return currentDayTokens(globalVolumeAccumulator, userVolumeAccumulator); } async syncUserVolumeAccumulator( user: PublicKey, ): Promise<TransactionInstruction> { return await this.offlineProgram.methods .syncUserVolumeAccumulator() .accountsPartial({ user }) .instruction(); } async initUserVolumeAccumulator({ payer, user, }: { payer: PublicKey; user: PublicKey; }): Promise<TransactionInstruction> { return await this.offlineProgram.methods .initUserVolumeAccumulator() .accountsPartial({ payer, user }) .instruction(); } async closeUserVolumeAccumulator( user: PublicKey, ): Promise<TransactionInstruction> { return await this.offlineProgram.methods .closeUserVolumeAccumulator() .accountsPartial({ user }) .instruction(); } }