@pump-fun/pump-swap-sdk
Version:
Official SDK for interacting with Pump Swap AMM protocol on Solana
218 lines (199 loc) • 6.63 kB
text/typescript
import BN from "bn.js";
import { PublicKey } from "@solana/web3.js";
import { RawMint } from "@solana/spl-token";
import { ceilDiv, fee } from "./util";
import { computeFeesBps } from "./fees";
import {
GlobalConfig,
FeeConfig,
SellBaseInputResult,
SellQuoteInputResult,
} from "../types/sdk";
export function sellBaseInput({
base,
slippage,
baseReserve,
quoteReserve,
globalConfig,
baseMintAccount,
baseMint,
coinCreator,
creator,
feeConfig,
}: {
base: BN;
slippage: number; // e.g. 1 => 1% slippage tolerance
baseReserve: BN;
quoteReserve: BN;
globalConfig: GlobalConfig;
baseMintAccount: RawMint;
baseMint: PublicKey;
coinCreator: PublicKey;
creator: PublicKey;
feeConfig: FeeConfig | null;
}): SellBaseInputResult {
// -----------------------------------------
// 1) Basic validations
// -----------------------------------------
if (baseReserve.isZero() || quoteReserve.isZero()) {
throw new Error(
"Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.",
);
}
// -----------------------------------------
// 2) Calculate the raw quote output (no fees)
// This matches a typical constant-product formula for selling base to get quote:
// quote_amount_out = floor( (quoteReserve * base) / (baseReserve + base) )
// -----------------------------------------
const quoteAmountOut = quoteReserve.mul(base).div(baseReserve.add(base)); // floor by BN.div
// -----------------------------------------
// 3) Calculate fees
// LP fee and protocol fee are both taken from 'quoteAmountOut'
// -----------------------------------------
const {
lpFeeBps,
protocolFeeBps,
creatorFeeBps: coinCreatorFeeBps,
} = computeFeesBps({
globalConfig,
feeConfig,
creator,
baseMintSupply: new BN(baseMintAccount.supply.toString()),
baseMint,
baseReserve,
quoteReserve,
tradeSize: quoteAmountOut,
});
const lpFee = fee(quoteAmountOut, lpFeeBps);
const protocolFee = fee(quoteAmountOut, protocolFeeBps);
const coinCreatorFee = PublicKey.default.equals(coinCreator)
? new BN(0)
: fee(quoteAmountOut, coinCreatorFeeBps);
// Subtract fees to get the actual user receive
const finalQuote = quoteAmountOut
.sub(lpFee)
.sub(protocolFee)
.sub(coinCreatorFee);
if (finalQuote.isNeg()) {
// Theoretically shouldn't happen unless fees exceed quoteAmountOut
throw new Error("Fees exceed total output; final quote is negative.");
}
// -----------------------------------------
// 4) Calculate minQuote with slippage
// - If slippage=1 => 1%, we allow receiving as low as 99% of finalQuote
// -----------------------------------------
const precision = new BN(1_000_000_000); // For safe integer math
// (1 - slippage/100) => e.g. slippage=1 => factor= 0.99
const slippageFactorFloat = (1 - slippage / 100) * 1_000_000_000;
const slippageFactor = new BN(Math.floor(slippageFactorFloat));
// minQuote = finalQuote * (1 - slippage/100)
const minQuote = finalQuote.mul(slippageFactor).div(precision);
return {
uiQuote: finalQuote, // actual tokens user receives after fees
minQuote, // minimum acceptable tokens after applying slippage
internalQuoteAmountOut: quoteAmountOut,
};
}
const MAX_FEE_BASIS_POINTS = new BN(10_000); // Assuming MAX_FEE_BASIS_POINTS is 10,000 (100%)
function calculateQuoteAmountOut(
userQuoteAmountOut: BN,
lpFeeBasisPoints: BN,
protocolFeeBasisPoints: BN,
coinCreatorFeeBasisPoints: BN,
): BN {
// Calculate the total fee basis points
const totalFeeBasisPoints = lpFeeBasisPoints
.add(protocolFeeBasisPoints)
.add(coinCreatorFeeBasisPoints);
// Calculate the denominator
const denominator = MAX_FEE_BASIS_POINTS.sub(totalFeeBasisPoints);
// Calculate the quote_amount_out
return ceilDiv(userQuoteAmountOut.mul(MAX_FEE_BASIS_POINTS), denominator);
}
export function sellQuoteInput({
quote,
slippage,
baseReserve,
quoteReserve,
globalConfig,
baseMintAccount,
baseMint,
coinCreator,
creator,
feeConfig,
}: {
quote: BN;
slippage: number; // e.g. 1 => 1% slippage tolerance
baseReserve: BN;
quoteReserve: BN;
globalConfig: GlobalConfig;
baseMintAccount: RawMint;
baseMint: PublicKey;
coinCreator: PublicKey;
creator: PublicKey;
feeConfig: FeeConfig | null;
}): SellQuoteInputResult {
// -----------------------------------------
// 1) Basic validations
// -----------------------------------------
if (baseReserve.isZero() || quoteReserve.isZero()) {
throw new Error(
"Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.",
);
}
if (quote.gt(quoteReserve)) {
throw new Error(
"Cannot receive more quote tokens than the pool quote reserves.",
);
}
// -----------------------------------------
// 2) Calculate the fees included in the quote
// -----------------------------------------
const {
lpFeeBps,
protocolFeeBps,
creatorFeeBps: coinCreatorFeeBps,
} = computeFeesBps({
globalConfig,
feeConfig,
creator,
baseMintSupply: new BN(baseMintAccount.supply.toString()),
baseMint,
baseReserve,
quoteReserve,
tradeSize: quote,
});
const rawQuote = calculateQuoteAmountOut(
quote,
lpFeeBps,
protocolFeeBps,
PublicKey.default.equals(coinCreator) ? new BN(0) : coinCreatorFeeBps,
);
// -----------------------------------------
// 3) Calculate the base amount needed for the raw quote output
// Invert the constant product formula:
// base_amount_in = ceil((baseReserve * rawQuote) / (quoteReserve - rawQuote))
// -----------------------------------------
if (rawQuote.gte(quoteReserve)) {
throw new Error(
"Invalid input: Desired quote amount exceeds available reserve.",
);
}
const baseAmountIn = ceilDiv(
baseReserve.mul(rawQuote),
quoteReserve.sub(rawQuote),
);
// -----------------------------------------
// 4) Calculate minQuote with slippage
// - If slippage=1 => 1%, we allow receiving as low as 99% of the desired quote
// -----------------------------------------
const precision = new BN(1_000_000_000); // For slippage calculations
const slippageFactorFloat = (1 - slippage / 100) * 1_000_000_000;
const slippageFactor = new BN(Math.floor(slippageFactorFloat));
const minQuote = quote.mul(slippageFactor).div(precision);
return {
internalRawQuote: rawQuote,
base: baseAmountIn, // amount of base tokens required to get the desired quote
minQuote, // minimum acceptable tokens after applying slippage
};
}