@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
text/typescript
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();
}
}