@pump-fun/pump-swap-sdk
Version:
Official SDK for interacting with Pump Swap AMM protocol on Solana
140 lines (122 loc) • 5.14 kB
text/typescript
import BN from "bn.js";
import { ceilDiv, fee } from "./util";
import {
BuyBaseInputResult,
BuyQuoteInputResult,
GlobalConfig,
} from "../types/sdk";
import { PublicKey } from "@solana/web3.js";
export function buyBaseInputInternal(
base: BN,
slippage: number, // 1 => 1%
baseReserve: BN,
quoteReserve: BN,
globalConfig: GlobalConfig,
coinCreator: PublicKey,
): BuyBaseInputResult {
// -----------------------------------------------------
// 1) Basic validations
// -----------------------------------------------------
if (baseReserve.isZero() || quoteReserve.isZero()) {
throw new Error(
"Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.",
);
}
if (base.gt(baseReserve)) {
throw new Error("Cannot buy more base tokens than the pool reserves.");
}
// -----------------------------------------------------
// 2) Calculate the raw quote needed (Raydium-like formula)
// quote_amount_in = ceil_div(quote_reserve * base, base_reserve - base)
// -----------------------------------------------------
const numerator = quoteReserve.mul(base);
const denominator = baseReserve.sub(base);
if (denominator.isZero()) {
throw new Error("Pool would be depleted; denominator is zero.");
}
const quoteAmountIn = ceilDiv(numerator, denominator);
// -----------------------------------------------------
// 3) Calculate fees
// - LP Fee = floor((quoteAmountIn * lpFeeBps) / 10000)
// - Protocol Fee = floor((quoteAmountIn * protocolFeeBps) / 10000)
// -----------------------------------------------------
const lpFee = fee(quoteAmountIn, globalConfig.lpFeeBasisPoints);
const protocolFee = fee(quoteAmountIn, globalConfig.protocolFeeBasisPoints);
const coinCreatorFee = PublicKey.default.equals(coinCreator)
? new BN(0)
: fee(quoteAmountIn, globalConfig.coinCreatorFeeBasisPoints);
const totalQuote = quoteAmountIn
.add(lpFee)
.add(protocolFee)
.add(coinCreatorFee);
// -----------------------------------------------------
// 4) Calculate maxQuote with slippage
// If slippage=1 => factor = (1 + 1/100) = 1.01
// -----------------------------------------------------
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));
// maxQuote = totalQuote * slippageFactor / 1e9
const maxQuote = totalQuote.mul(slippageFactor).div(precision);
return {
internalQuoteAmount: quoteAmountIn,
uiQuote: totalQuote, // Final total quote after fees
maxQuote,
};
}
export function buyQuoteInputInternal(
quote: BN,
slippage: number, // 1 => 1%
baseReserve: BN,
quoteReserve: BN,
globalConfig: GlobalConfig,
coinCreator: PublicKey,
): BuyQuoteInputResult {
// -----------------------------------------------------
// 1) Basic validations
// -----------------------------------------------------
if (baseReserve.isZero() || quoteReserve.isZero()) {
throw new Error(
"Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.",
);
}
// -----------------------------------------------------
// 2) Calculate total fee basis points and denominator
// -----------------------------------------------------
const totalFeeBps = globalConfig.lpFeeBasisPoints
.add(globalConfig.protocolFeeBasisPoints)
.add(
PublicKey.default.equals(coinCreator)
? new BN(0)
: globalConfig.coinCreatorFeeBasisPoints,
);
const denominator = new BN(10_000).add(totalFeeBps);
// -----------------------------------------------------
// 3) Calculate effective quote amount
// -----------------------------------------------------
const effectiveQuote = quote.mul(new BN(10_000)).div(denominator);
// -----------------------------------------------------
// 4) Calculate the base tokens received using effectiveQuote
// base_amount_out = floor(base_reserve * effectiveQuote / (quote_reserve + effectiveQuote))
// -----------------------------------------------------
const numerator = baseReserve.mul(effectiveQuote);
const denominatorEffective = quoteReserve.add(effectiveQuote);
if (denominatorEffective.isZero()) {
throw new Error("Pool would be depleted; denominator is zero.");
}
const baseAmountOut = numerator.div(denominatorEffective);
// -----------------------------------------------------
// 5) Calculate maxQuote with slippage
// If slippage=1 => factor = (1 + 1/100) = 1.01
// -----------------------------------------------------
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));
// maxQuote = quote * slippageFactor / 1e9
const maxQuote = quote.mul(slippageFactor).div(precision);
return {
base: baseAmountOut, // Base tokens received after fees
internalQuoteWithoutFees: effectiveQuote,
maxQuote, // Maximum quote tokens to pay (with slippage)
};
}