@pump-fun/pump-swap-sdk
Version:
Official SDK for interacting with Pump Swap AMM protocol on Solana
265 lines (229 loc) • 8.37 kB
text/typescript
import { expect } from "chai";
import BN from "bn.js";
import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js";
import { getAccount, getMint, MintLayout, RawMint } from "@solana/spl-token";
import { buyBaseInput, buyQuoteInput } from "../sdk/buy";
import { createFeeConfigFromGlobalConfig } from "./utils";
import { GlobalConfig, Pool } from "../types/sdk";
import { OnlinePumpAmmSdk } from "../sdk/onlinePumpAmm";
import { PUMP_AMM_SDK } from "../sdk/offlinePumpAmm";
describe("buyBaseInput with fees", () => {
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
const sdk = new OnlinePumpAmmSdk(connection);
const user = new PublicKey("4kBH5H5p9oRkZPGLSx8R4WKoDsmXnEpmzsgkebkKvzSg");
const baseMintAccount: RawMint = {
mintAuthorityOption: 0,
mintAuthority: PublicKey.unique(),
supply: BigInt(1),
decimals: 9,
isInitialized: false,
freezeAuthorityOption: 0,
freezeAuthority: PublicKey.unique(),
};
const pool: Pool = {
poolBump: 1,
index: 0,
creator: PublicKey.unique(),
baseMint: PublicKey.unique(),
quoteMint: PublicKey.unique(),
lpMint: PublicKey.unique(),
poolBaseTokenAccount: PublicKey.unique(),
poolQuoteTokenAccount: PublicKey.unique(),
lpSupply: new BN(0),
coinCreator: PublicKey.default,
isMayhemMode: false,
isCashbackCoin: false,
};
const globalConfig: GlobalConfig = {
admin: PublicKey.unique(),
lpFeeBasisPoints: new BN(30),
protocolFeeBasisPoints: new BN(20),
disableFlags: 0,
protocolFeeRecipients: [],
coinCreatorFeeBasisPoints: new BN(0),
adminSetCoinCreatorAuthority: PublicKey.unique(),
whitelistPda: PublicKey.unique(),
reservedFeeRecipient: PublicKey.unique(),
mayhemModeEnabled: false,
reservedFeeRecipients: [],
};
const feeConfig = createFeeConfigFromGlobalConfig(globalConfig);
it("should compute quote + fees + slippage correctly", () => {
// Example pool reserves
const baseReserve = new BN(1_000_000);
const quoteReserve = new BN(2_000_000);
// Request to buy 10,000 base tokens
const base = new BN(10_000);
// Slippage = 1% (slippage=1 => 1%)
const slippage = 1;
const result = buyBaseInput({
base,
slippage,
baseReserve,
quoteReserve,
globalConfig,
baseMintAccount,
baseMint: pool.baseMint,
coinCreator: pool.coinCreator,
creator: pool.creator,
feeConfig,
});
console.log("quote =", result.uiQuote.toString());
console.log("maxQuote =", result.maxQuote.toString());
// You can calculate offline and replace these with your
// actual expected values:
const expectedQuote = new BN(20305); // Example only
const expectedMaxQuote = new BN(20508); // Example only
expect(result.uiQuote.toString()).eq(expectedQuote.toString());
expect(result.maxQuote.toString()).eq(expectedMaxQuote.toString());
});
describe("debug quote errors", () => {
// https://solscan.io/tx/2RvDKD7vfd5bGZ6TLBu4Xm1zhyjUxe1gmFLmHGoFSskssqTuGQk1hD7cvR6UWkqU96CBu5eYnpXodhz4PjVNThcX?cluster=devnet
const poolKey = new PublicKey(
"Eo7kU23fKzYbZux6tKcaosFyr7AfJucijzRpNwPrL9G6",
);
const baseReserve = new BN(1_000_000_000_000_000);
const quoteReserve = new BN(1_381_503_388);
const base = new BN(306_127_676_981_862);
const quote = new BN(617_120_563);
const slippage = 0;
it("buyBaseInput should compute quote + fees + slippage correctly", async () => {
const pool = await sdk.fetchPool(poolKey);
const result = buyBaseInput({
base,
slippage,
baseReserve,
quoteReserve,
globalConfig,
baseMintAccount,
baseMint: pool.baseMint,
coinCreator: pool.coinCreator,
creator: pool.creator,
feeConfig: await sdk.fetchFeeConfigAccount(),
});
const expectedQuote = quote.addn(1).toString();
expect(result.uiQuote.toString()).eq(expectedQuote);
expect(result.maxQuote.toString()).eq(expectedQuote);
});
it("buyQuoteInput should compute quote + fees + slippage correctly", async () => {
const pool = await sdk.fetchPool(poolKey);
const result = buyQuoteInput({
quote,
slippage,
baseReserve,
quoteReserve,
globalConfig,
baseMintAccount,
baseMint: pool.baseMint,
coinCreator: pool.coinCreator,
creator: pool.creator,
feeConfig: await sdk.fetchFeeConfigAccount(),
});
expect(result.base.toString()).eq(base.toString());
});
});
describe("debug quote errors2", () => {
// https://solscan.io/tx/39xg5JpDUAhxPEQf1iyab5hRKLQeg3TAsQ5Tis1z2P4hzzS3b9vhbuTdiLBr4PAjn6XscUfj1CcQ6cmbRca6pCsd?cluster=devnet
const poolKey = new PublicKey(
"Eo7kU23fKzYbZux6tKcaosFyr7AfJucijzRpNwPrL9G6",
);
const baseReserve = new BN(1_000_000_000_000_000);
const quoteReserve = new BN(1_381_908_988);
const base = new BN(66_703_004_162_116);
const quote = new BN(100_000_000);
const slippage = 0;
// AZhWzPYTxCgb6QcSRCSN8kBA57n3jvAneJoUbGmZpump
function parseMint(data: Buffer): RawMint | null {
try {
return MintLayout.decode(new Uint8Array(data)) as RawMint;
} catch (e) {
console.warn("Failed to parse mint account", e);
return null;
}
}
// https://solscan.io/tx/5rYHFqLR5znTecBzPcYCErPEqv1gGT36URE81avB1WJSocvp9s7ADxH2wemQ7z26wvfHmBQmHwuQde7BtukakbKC?cluster=devnet#tokenBalanceChange
it("buyBaseInput should compute quote + fees + slippage correctly", async () => {
const pool = await sdk.fetchPool(poolKey);
const baseMintAccount = parseMint(
(await connection.getAccountInfo(pool.baseMint))!.data,
)!;
const result = buyBaseInput({
base,
slippage,
baseReserve,
quoteReserve,
globalConfig: await sdk.fetchGlobalConfigAccount(),
baseMintAccount,
baseMint: pool.baseMint,
coinCreator: pool.coinCreator,
creator: pool.creator,
feeConfig: await sdk.fetchFeeConfigAccount(),
});
const expectedQuote = quote.addn(2).toString();
expect(result.uiQuote.toString()).eq(expectedQuote);
expect(result.maxQuote.toString()).eq(expectedQuote);
});
it("buyQuoteInput should compute quote + fees + slippage correctly", async () => {
const pool = await sdk.fetchPool(poolKey);
const result = buyQuoteInput({
quote,
slippage,
baseReserve,
quoteReserve,
globalConfig,
baseMintAccount,
baseMint: pool.baseMint,
coinCreator: pool.coinCreator,
creator: pool.creator,
feeConfig: await sdk.fetchFeeConfigAccount(),
});
const expectedBase = base;
expect(result.base.toString()).eq(expectedBase.toString());
const result2 = buyBaseInput({
base: expectedBase,
slippage,
baseReserve,
quoteReserve,
globalConfig,
baseMintAccount,
baseMint: pool.baseMint,
coinCreator: pool.coinCreator,
creator: pool.creator,
feeConfig: await sdk.fetchFeeConfigAccount(),
});
const expectedQuote = quote.addn(2).toString();
expect(result2.uiQuote.toString()).eq(expectedQuote);
expect(result2.maxQuote.toString()).eq(expectedQuote);
});
});
it("should fail if base > baseReserve", () => {
const baseReserve = new BN(1_000_000);
const quoteReserve = new BN(2_000_000);
const base = new BN(2_000_000); // more than pool
const slippage = 1;
expect(() =>
buyBaseInput({
base,
slippage,
baseReserve,
quoteReserve,
globalConfig,
baseMintAccount,
baseMint: pool.baseMint,
coinCreator: pool.coinCreator,
creator: pool.creator,
feeConfig,
}),
).to.throw("Cannot buy more base tokens than the pool reserves.");
});
it("should build the instruction successfully", async () => {
const pool = new PublicKey("Fzrac7XDX29dYBfMeoPBG18zB2BYFxR5v9fV9zFH7fnV");
expect(async () => {
return await PUMP_AMM_SDK.buyBaseInput(
await sdk.swapSolanaState(pool, user),
new BN(10),
10,
);
}).to.not.throw();
});
});