@pump-fun/pump-swap-sdk
Version:
Official SDK for interacting with Pump Swap AMM protocol on Solana
199 lines (179 loc) • 6.16 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 {
BuyBaseInputResult,
BuyQuoteInputResult,
FeeConfig,
GlobalConfig,
} from "../types/sdk";
export function buyBaseInput({
base,
slippage,
baseReserve,
quoteReserve,
globalConfig,
baseMintAccount,
baseMint,
coinCreator,
creator,
feeConfig,
}: {
base: BN;
slippage: number; // 1 => 1%
baseReserve: BN;
quoteReserve: BN;
globalConfig: GlobalConfig;
baseMintAccount: RawMint;
baseMint: PublicKey;
coinCreator: PublicKey;
creator: PublicKey;
feeConfig: FeeConfig | null;
}): 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 {
lpFeeBps,
protocolFeeBps,
creatorFeeBps: coinCreatorFeeBps,
} = computeFeesBps({
globalConfig,
feeConfig,
creator,
baseMintSupply: new BN(baseMintAccount.supply.toString()),
baseMint,
baseReserve,
quoteReserve,
tradeSize: quoteAmountIn,
});
const lpFee = fee(quoteAmountIn, lpFeeBps);
const protocolFee = fee(quoteAmountIn, protocolFeeBps);
const coinCreatorFee = PublicKey.default.equals(coinCreator)
? new BN(0)
: fee(quoteAmountIn, coinCreatorFeeBps);
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 buyQuoteInput({
quote,
slippage,
baseReserve,
quoteReserve,
globalConfig,
baseMintAccount,
baseMint,
coinCreator,
creator,
feeConfig,
}: {
quote: BN;
slippage: number; // 1 => 1%
baseReserve: BN;
quoteReserve: BN;
globalConfig: GlobalConfig;
baseMintAccount: RawMint;
baseMint: PublicKey;
coinCreator: PublicKey;
creator: PublicKey;
feeConfig: FeeConfig | null;
}): BuyQuoteInputResult {
// -----------------------------------------------------
// 1) Basic validations
// -----------------------------------------------------
if (baseReserve.isZero() || quoteReserve.isZero()) {
throw new Error(
"Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.",
);
}
// -----------------------------------------------------
// 2) Calculate fees total fee basis points and denominator
// -----------------------------------------------------
const {
lpFeeBps,
protocolFeeBps,
creatorFeeBps: coinCreatorFeeBps,
} = computeFeesBps({
globalConfig,
feeConfig,
creator,
baseMintSupply: new BN(baseMintAccount.supply.toString()),
baseMint,
baseReserve,
quoteReserve,
tradeSize: quote,
});
const totalFeeBps = lpFeeBps
.add(protocolFeeBps)
.add(PublicKey.default.equals(coinCreator) ? new BN(0) : coinCreatorFeeBps);
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)
};
}