@pump-fun/pump-sdk
v4.1.0
Published
Pump Bonding Curve SDK
Readme
Pump SDK
Official Pump program SDK
Updates
v3 bonding-curve trades, fee buckets and sweeps
Tracks pump-programs-monorepo PR #60. The v1 / v2 trades keep working unchanged.
Also tracks PR #61 (stacked on #55): the same v3 / v2 changes plus pump coins as quote mints, the post-completion buy and multi-hop routes.
Pump coins as
create_v2quotes. A pump coin listed on neitherGlobalnor quote control can quote a new coin.OnlinePumpSdk.resolveQuoteMintresolves it (source: "pumpCoin"). Pass itspumpQuote.accountstocreateV2Instruction/createV2AndBuyV2InstructionsaspumpQuote, and itspumpQuote.curveto the quote functions. The new coin's reserves are derived from what a standard launch's raise buys up the quote coin's whole quote chain, every coin priced live (programs #64; a raise past a trading coin's remaining supply completes it and buys on from its pool-to-be, asbuy_v3would);fetchPumpQuotewalks the chain andpumpQuote.accounts.chaincarries one account group per coin below the quote coin. Its depth is the quote coin's plus one, capped byGlobal.maxCurveDepth(setMaxCurveDepthInstruction; 0 disables the path) and the program'sMAX_CURVE_DEPTH(5). A deep chain of migrated coins needs a v0 transaction with a lookup table. Refusals map to typed errors (CurveDepthExceededError,QuoteBondingCurveNotEligibleError,QuoteCurveAwaitingMigrationError,QuoteReservesOutOfRangeError).Post-completion buy. A
buy_v3/buy_exact_quote_in_v3past the remaining supply completes the curve and buys the rest from the pool the migration will create. Quote it withgetBuyV3QuoteAmountFromTokenAmount/getBuyV3TokenAmountFromQuoteAmount, which need the curve's base token balance (fetchBuyStatereturns it ascurveBaseTokenBalance, read under the coin's real token program). On a complete curve they return 0.partialFillnow only matters on mayhem curves, where there is no post-completion leg. NewPostCompleteBuyEvent(decodePostCompleteBuyEvent).Multi-hop routes (pump-amm
multi_hop_swap):OnlinePumpSdk.resolveMultiHopRoute(path, side)picks each hop's venue,simulateMultiHopSwapreturns the amount out, andPumpSdk.multiHopSwapInstructionsbuilds the swap (wrapping SOL in, closing the WSOL account after, creating the buyback ATA). SOL-quoted curves can be hops too, at the route's SOL end (pump-programs-monorepo #63): SOL moves as lamports between the wallet and the curve, so the user's WSOL account there is only a placeholder (created, not funded) and a buy needsamountInplus rent in the wallet; mayhem curves are refused. Routes are all buys or all sells. The buyback ATA is in the user's currency. More than three hops needs a v0 transaction with a lookup table; pass it tosimulateMultiHopSwapasaddressLookupTablestoo.Layouts.
BondingCurvegainsdepth,initialVirtualQuoteReserves,postCompleteBaseOutandpostCompleteQuoteIn(BONDING_CURVE_SIZE/BONDING_CURVE_NEW_SIZE166).GlobalgainsmaxCurveDepth(GLOBAL_SIZE1088; runextend_accountonGlobalafter the upgrade).CreateEventgainsdepth. Older layouts still decode, with zeros.Fix: pump-amm's GlobalConfig is now the right PDA (
global_config) inadminCtoInstructionandsweepPoolCreatorFeeInstruction. The exportedAMM_GLOBAL_PDAnow derives it too and is deprecated in favour of pump-swap-sdk'sGLOBAL_CONFIG_PDA.New trades:
buy_v3,buy_exact_quote_in_v3,sell_v3. UsebuyV3Instructions,buyExactQuoteInV3InstructionsandsellV3Instructions, or theirget*V3InstructionRawversions. They take 17 accounts. Within the remaining supply prices and fees are the same as v2; quote v3 buys withgetBuyV3QuoteAmountFromTokenAmount/getBuyV3TokenAmountFromQuoteAmount, which also price a buy past it (see the post-completion buy above). Only the buyback slice of the protocol fee is paid in the trade. The rest of the protocol fee and the creator fee stay on the curve (BondingCurve.protocolFees/creatorFee) until a sweep pays them out. Cashback coins are refused (6094,CashbackCoinNotSupportedError); trade them with v2. See Trading with v3.One buyback account. On a SOL curve it is a listed
Global.buybackFeeRecipientswallet (random by default). On a token quote it is the first listed wallet's quote ATA,5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD(override withbuybackFeeRecipient). The program refuses a missing ATA and never creates it, so the v3 builders andmultiHopSwapInstructionscreate it idempotently first; the raw builders do not.Sweeps.
sweepProtocolFeeInstructionandsweepCreatorFeeInstructionare permissionless payouts. So issweepPoolCreatorFeeInstruction(pump-amm), for creator fees that the pump-amm v2 pool trades leave in the pool. An un-swept creator bucket blocksdistribute_creator_fees(_v2),admin_cto, fee-sharing creation andupdate_fee_shares(_v2)(pump 6095 / pump-amm 6081CreatorFeesNotSwept, pump-fees 6033PoolCreatorFeesNotSweptfor a pool bucket onupdate_fee_shares(_v2)). Put the sweep first in the same transaction.OnlinePumpSdk.adminCtoInstructionsandbuildDistributeCreatorFeesInstructionsdo it for you whenever a bucket is nonzero. The latter needs apayer(even for a SOL coin) only when the curve bucket is nonzero, leaves the optional pool sweep out without one, and reports the sweeps assweepCount; a list too large for one transaction goes in a v0 transaction with a lookup table.getMinimumDistributableFeecounts a SOL curve's un-swept bucket as distributable. The pool sweep pays the vault of the pool's owncoin_creator. Sweeps refuse the zero key as a destination, andgetFeeRecipientskips unset slots.update_fee_shares(_v2)on a complete curve takes the coin's canonical pool as an extra account after the shareholders (and after their quote ATAs on v2) to check the pool's creator bucket.updateFeeShares/updateFeeSharesV2append it unlessbondingCurveComplete: false(a live curve ignores it; leaving it out saves 33 bytes): the WSOL pool on v1, the pool ofquoteMinton v2 (it must be the curve's quote). It doesn't have to exist yet. Instructions built by older SDK versions leave it out and fail on a complete curve.Accounts and events.
BondingCurvegainscreatorFee/protocolFees(BONDING_CURVE_SIZE125 → 141, 166 with #61; shorter curves decode with zeros).QuoteControlgainsreservesAdmin, carved out of its reserved area. New decoders:decodeSweepBondingCurveFeeEventanddecodeSweepPoolFeeEventAmm.TradeEventBc,BuyEventAmmandSellEventAmmgaincreatorFeeUnclaimed: the curve's or pool's creator bucket after the trade, i.e. creator fees not swept yet. Events emitted before the field existed decode it as zero. The legacybuy,buy_v2andbuy_exact_*instructions gained a trailingpartial_fillargument, which the builders send as unset.Not included in this SDK: pump-amm's
buy_v2/sell_v2pool trades and the pool protocol sweep, which live in@pump-fun/pump-swap-sdk. Creator-vault balances and collects don't see fees that are still in a bucket; sweep the coin first. They also cover only listed quote mints: pass pump-coin quotes asextraQuoteMints.
Holder-reward coins, one CTO instruction, cashback retired (create_v2)
Trading instructions are unchanged and do not require an upgrade. What changed on create_v2:
- Holder-reward coins.
create_v2takes a trailingis_holder_rewardargument (OptionBool, EOF-tolerant: older instruction data still decodes). PassholderReward: truetocreateV2Instruction/createV2AndBuyV2Instructions/createV2AndBuyInstructions: the program ignorescreatorand makes the coin'sholderRewardsPda(mint)the creator, so every creator fee accrues to that PDA's vault and is paid out to the coin's holders instead of a wallet. Permanent. See Holder-reward coins. - Cashback is deprecated.
create_v2rejectsis_cashback_enabled = true(6082CashbackDeprecated); the create-and-buy builders throwCashbackDeprecatedErrorup front. Existing cashback coins are unchanged: trades still route the creator fee to the buyer andclaimCashbackInstruction/claimCashbackV2Instructionstill claim it. - One CTO instruction.
admin_set_creator,admin_set_all_coin_creators,admin_set_creator_fee_editableandset_creator_fee_bps(and pump-feesreset_fee_sharing_config*) are gone;admin_ctoreplaces them all and drives the pump-amm pool and the pump-fees sharing config itself. A configured creator fee changes only through it. See Changing the rate later: only through a CTO. - Accounts and events.
BondingCurvegainsisHolderReward(BONDING_CURVE_SIZE124 → 125),GlobalgainsholderRewardClaimAuthority/isHolderRewardEnabled(GLOBAL_SIZE1054 → 1087);CreateEventgainsisHolderReward,TradeEvent(and pump-ammBuyEvent/SellEvent) gainholderRewardsBps/holderRewardsalongside the unchangedcreatorFee*. Removed builders:setCreatorFeeBpsInstruction,adminSetCreatorFeeEditableInstruction,OnlinePumpSdk.adminSetCoinCreatorInstructions/adminSetCreatorFeeEditableInstructions/setCreatorFeeBpsInstructions; removed decoders and types:AdminSetCreatorEventBc,AdminSetCoinCreatorEventAmm,SetCreatorFeeBpsEvent,AdminSetCreatorFeeEditableEvent.
One-Time Creator Reward Distribution Policy
This update only affects fee-sharing configuration. Trading instructions are unchanged and do not require an upgrade.
TLDR: After the first configuration, reward distribution is locked and cannot be changed.
What changed:
- Reward distribution can be configured only once.
- You can configure it using either
updateFeeSharesorupdateSharingConfigWithSocialRecipients. - After it is configured, it is locked and cannot be changed again.
What did not change:
- Buy and sell instructions are unchanged.
Migration/compatibility notes:
- Reward distributions created before this policy are treated as final (locked).
RevokeFeeSharingAuthorityandTransferFeeSharingAuthorityare no longer supported.- If you are unsure whether a reward distribution is still editable, call
isSharingConfigEditable.
import { isSharingConfigEditable } from "@pump-fun/pump-sdk";Usage
const connection = new Connection(
"https://api.devnet.solana.com",
"confirmed",
);
const sdk = new PumpSdk(connection);Coin creation
const mint = PublicKey.unique();
const creator = PublicKey.unique();
const user = PublicKey.unique();
const instruction = await sdk.createInstruction({
mint,
name: "name",
symbol: "symbol",
uri: "uri",
creator,
user,
});
// or creating and buying instructions in the same tx
const global = await sdk.fetchGlobal();
const solAmount = new BN(0.1 * 10 ** 9); // 0.1 SOL
const instructions = await sdk.createAndBuyInstructions({
global,
mint,
name: "name",
symbol: "symbol",
uri: "uri",
creator,
user,
solAmount,
amount: getBuyTokenAmountFromSolAmount(global, null, solAmount),
});Buying coins
const mint = PublicKey.unique();
const user = PublicKey.unique();
const global = await sdk.fetchGlobal();
const { bondingCurveAccountInfo, bondingCurve, associatedUserAccountInfo } =
await sdk.fetchBuyState(mint, user);
const solAmount = new BN(0.1 * 10 ** 9); // 0.1 SOL
const instructions = await sdk.buyInstructions({
global,
bondingCurveAccountInfo,
bondingCurve,
associatedUserAccountInfo,
mint,
user,
solAmount,
amount: getBuyTokenAmountFromSolAmount(global, bondingCurve, solAmount),
slippage: 1,
});Selling coins
const mint = PublicKey.unique();
const user = PublicKey.unique();
const global = await sdk.fetchGlobal();
const { bondingCurveAccountInfo, bondingCurve } = await sdk.fetchSellState(mint, user);
const amount = new BN(15_828);
const instructions = await sdk.sellInstructions({
global,
bondingCurveAccountInfo,
bondingCurve,
mint,
user,
amount,
solAmount: getSellSolAmountFromTokenAmount(global, bondingCurve, amount),
slippage: 1,
});Trading with v3
The state from fetchBuyState / fetchSellState works as is, given the coin's token program: they default to SPL
Token while the v3 builders default to Token-2022, so pass it to both (TOKEN_2022_PROGRAM_ID for create_v2
coins). Quote buys with the v3 functions, which also price
the post-completion leg when the buy crosses the remaining supply:
const [global, feeConfig] = await Promise.all([
onlineSdk.fetchGlobal(),
onlineSdk.fetchFeeConfig(),
]);
const { bondingCurve, associatedUserAccountInfo, quoteTokenProgram, curveBaseTokenBalance } =
await onlineSdk.fetchBuyState(mint, user, TOKEN_2022_PROGRAM_ID);
const quoteAmount = new BN(0.1 * 10 ** 9); // spend at most 0.1 SOL, fees included
const instructions = await PUMP_SDK.buyExactQuoteInV3Instructions({
bondingCurve,
associatedUserAccountInfo,
mint,
user,
quoteAmount,
amount: getBuyV3TokenAmountFromQuoteAmount({
global,
feeConfig,
mintSupply: bondingCurve.tokenTotalSupply,
bondingCurve,
amount: quoteAmount,
curveBaseTokenBalance,
}),
slippage: 1,
quoteTokenProgram,
});
// sellV3Instructions({ bondingCurve, mint, user, amount, quoteAmount, slippage, quoteTokenProgram })
// buyV3Instructions({ bondingCurve, ..., amount, quoteAmount: getBuyV3QuoteAmountFromTokenAmount(...), slippage })The fees accrue on the curve. Anyone can pay them out:
const sweepCreator = await PUMP_SDK.sweepCreatorFeeInstruction({
payer,
mint,
creator: bondingCurve.creator,
quoteMint: bondingCurve.quoteMint,
quoteTokenProgram,
});
const sweepProtocol = await PUMP_SDK.sweepProtocolFeeInstruction({
payer,
mint,
recipient: getFeeRecipient(global, bondingCurve.isMayhemMode),
quoteMint: bondingCurve.quoteMint,
quoteTokenProgram,
});Quote mints
A bonding curve is quoted in SOL or in a token. The quote mints create_v2 accepts are not a static list: they are SOL, plus the mints whitelisted on Global (USDC on mainnet), plus the mints listed in the on-chain QuoteControl PDA (QUOTE_CONTROL_PDA, managed by an admin, possibly not initialized yet). Read the set from chain instead of hardcoding it:
import { OnlinePumpSdk } from "@pump-fun/pump-sdk";
const onlineSdk = new OnlinePumpSdk(connection);
// SOL first (reported as NATIVE_MINT), then the Global mints, then the
// QuoteControl mints. One RPC round-trip.
const supported = await onlineSdk.fetchSupportedQuoteMints();
// [{ mint, source: "sol" | "global" | "quoteControl", initialVirtualQuoteReserves }]
// (pump coins are not enumerated: any pump coin may quote once
// Global.maxCurveDepth allows it)
// One mint, with what the builders need for it. A pump coin listed nowhere
// resolves as `source: "pumpCoin"` (see the Updates section); a mint that is
// neither listed nor a pump coin throws UnsupportedQuoteMintError, and a
// refused pump coin throws the matching typed error (CurveDepthExceededError,
// ...).
const usdc = await onlineSdk.resolveQuoteMint(USDC_MINT);
usdc.quoteTokenProgram; // the program that owns the mint: TOKEN_PROGRAM_ID or TOKEN_2022_PROGRAM_ID
usdc.decimals;
usdc.initialVirtualQuoteReserves; // what a new curve quoted in it starts from
// Or just the token program (no RPC call for SOL).
const quoteTokenProgram = await onlineSdk.fetchQuoteTokenProgram(quoteMint);The quote token program
Every quote-side account a trade touches (the fee recipients', the bonding curve's, the user's, the creator vault's and the cashback accumulator's token accounts) is an ATA derived with the program that owns the quote mint, and that program may be SPL Token or Token-2022. Every builder that takes a quoteMint therefore also takes a quoteTokenProgram. The default, TOKEN_PROGRAM_ID, is right for SOL and USDC only; for anything else pass the mint's owner, from fetchQuoteTokenProgram / resolveQuoteMint, or from fetchBuyState / fetchSellState, which return the curve's quoteMint and quoteTokenProgram alongside the curve (pass the quote mint as the optional last argument when you already know it to save a round-trip).
Creating a coin quoted in a token
createV2AndBuyV2Instructions builds the create, the user's token account and the first buy, all agreeing on the quote. Token-quoted creates and first buys are heavy (roughly 200-240k CU each), so set an explicit compute budget: about 500k for create + buy, 250-300k for a standalone buy_v2 / sell_v2 on a Token-2022 quote.
import { ComputeBudgetProgram } from "@solana/web3.js";
import { getBuyTokenAmountFromSolAmount, PUMP_SDK } from "@pump-fun/pump-sdk";
const global = await onlineSdk.fetchGlobal();
const feeConfig = await onlineSdk.fetchFeeConfig();
const quoteControl = await onlineSdk.fetchQuoteControl(); // null until initialized
// USDC: whitelisted on Global, SPL Token.
const quoteAmount = new BN(10 * 10 ** 6); // 10 USDC
const usdcInstructions = [
ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 }),
...(await PUMP_SDK.createV2AndBuyV2Instructions({
global,
mint,
name,
symbol,
uri,
creator,
user,
quoteMint: USDC_MINT,
quoteTokenProgram: TOKEN_PROGRAM_ID,
quoteAmount,
amount: getBuyTokenAmountFromSolAmount({
global,
feeConfig,
mintSupply: null,
bondingCurve: null,
amount: quoteAmount,
quoteMint: USDC_MINT,
quoteControl,
}),
mayhemMode: false,
})),
];
// A Token-2022 quote admitted through QuoteControl.
const quote = await onlineSdk.resolveQuoteMint(XSTOCK_MINT);
const token2022Instructions = [
ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 }),
...(await PUMP_SDK.createV2AndBuyV2Instructions({
global,
mint,
name,
symbol,
uri,
creator,
user,
quoteMint: quote.mint,
quoteTokenProgram: quote.quoteTokenProgram, // TOKEN_2022_PROGRAM_ID
quoteAmount,
amount: getBuyTokenAmountFromSolAmount({
global,
feeConfig,
mintSupply: null,
bondingCurve: null,
amount: quoteAmount,
quoteMint: quote.mint,
quoteControl, // seeds the new curve from the QuoteControl entry
}),
mayhemMode: false, // a quote-control-only mint cannot be a mayhem coin (6071)
})),
];Under the hood a non-SOL create_v2 carries four remaining accounts, in this order: the quote mint, the bonding curve's quote ATA (writable; the program creates it), the quote token program, and the QuoteControl PDA. The program reads the PDA only for a mint Global does not whitelist, so the SDK always appends it; the program deployed before quote control ignores a fourth account.
Fee schedule by quote mint
pump-fees selects a bonding curve's (and its canonical pool's) fee schedule from the quote mint, and so does the SDK's quote math (computeFeesBps, getBuyTokenAmountFromSolAmount, ... via selectCurveFeeSchedule):
| Quote mint | Schedule |
| ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| SOL-like: the zero key, WSOL So111…112, the Token-2022 native mint 9pan9… (SOL_LIKE_QUOTE_MINTS) | market-cap tier from FeeConfig.feeTiers |
| A listed stable: mainnet USDC EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v (STABLE_QUOTE_MINTS, hardcoded on chain) | market-cap tier from FeeConfig.stableFeeTiers |
| Anything else (devnet USDC included) | FeeConfig.exoticFlatFees, or flatFees while that schedule is unset (all zero) |
Token-2022 quote accounts
ATAs of a Token-2022 quote mint carry extensions, so they are 170-182 bytes (175 for the xStock extension profile), never the 165 bytes of an SPL Token account. Do not size or parse quote token accounts assuming 165 bytes; unpackAccount from @solana/spl-token handles both.
Errors
create_v2 checks the quote in this order, so the first mismatch is what you see:
- 6064
InvalidQuoteTokenProgram: remaining account [2] is not SPL Token or Token-2022. - 6063
UnsupportedQuoteMint: the Token-2022 native mint; or, whenGlobaldoes not whitelist the mint, a missing remaining account [3] (a quote-control mint sent with only three remaining accounts) or a mintQuoteControldoes not list. - 6075
InvalidQuoteControl:Globaldoes not whitelist the mint and remaining account [3] is present but is not the canonical, pump-owned, well-formedQuoteControlPDA. - 6063
UnsupportedQuoteMintagain, after the list lookup: the quote token program is not the mint's owner, or a Token-2022 mint with an extension outside the xStock operable set. - 6065
InvalidAssociatedQuoteBondingCurve: the program is right but remaining account [1] is not the bonding curve's ATA under it. - 6071
MayhemModeQuoteMintNotAllowed:mayhemModewith a mint admitted only throughQuoteControl.
Managing the quote-control list (admin)
// Permissionless, once: anyone can create the PDA (no admin, empty list).
await PUMP_SDK.initializeQuoteControlInstruction({ user });
// Global.authority assigns the admin (the zero key revokes it).
await onlineSdk.adminSetQuoteControlAdminInstruction({ newAdmin }); // authority defaults to Global.authority
// QuoteControl.admin or Global.authority lists / de-lists mints. The reserves
// are what a new curve quoted in the mint starts from, in the mint's base units.
await onlineSdk.adminAddQuoteControlMintInstruction({
quoteMint,
initialVirtualQuoteReserves: new BN(4_292_000_000),
authority: quoteControlAdmin, // optional; defaults to Global.authority
});
await onlineSdk.adminRemoveQuoteControlMintInstruction({ quoteMint });
// FeeConfig.admin sets the flat schedule exotic quotes pay.
await onlineSdk.adminSetExoticFlatFeesInstruction({
exoticFlatFees: { lpFeeBps: new BN(0), protocolFeeBps: new BN(300), creatorFeeBps: new BN(25) },
});The offline PumpSdk has the same builders with explicit signers (setQuoteControlAdminInstruction, addQuoteControlMintInstruction, removeQuoteControlMintInstruction, setExoticFlatFeesInstruction).
Configurable creator fee
A coin can carry its own creator fee rate instead of the pump-fees schedule's. BondingCurve.creatorFeeBps is that rate in basis points, and zero means "not configured": the schedule's creator rate for the coin's quote mint applies (see Fee schedule by quote mint). While Global.creatorFeeConfigurable is on, a nonzero rate replaces the schedule's creator rate on every trade of that coin; the protocol rate is untouched. With the gate off, configured rates are neither read nor accepted (a kill switch). A rate must be within 1..=Global.maxConfigurableCreatorFeeBps, and only a coin quoted through QuoteControl stores one: on a SOL or USDC quote the create-time value is ignored and a CTO refuses it (6091). The SDK's quote math applies the stored rate by itself: computeFeesBps, getFee, getBuyTokenAmountFromSolAmount, getBuySolAmountFromTokenAmount and getSellSolAmountFromTokenAmount read bondingCurve.creatorFeeBps and global.creatorFeeConfigurable, so an unconfigured coin quotes exactly as before.
Creating a coin with its own creator fee
Pass creatorFeeBps to createV2Instruction, createV2AndBuyV2Instructions or createV2AndBuyInstructions. It becomes create_v2's creator_fee_bps argument, which the SDK always sends (zero when unset) right before the trailing is_holder_reward byte. Quote the first buy with the same rate so it covers the fees the program charges:
const creatorFeeBps = new BN(250); // 2.5%
const quoteAmount = new BN(0.1 * 10 ** 9); // 0.1 SOL
const instructions = await PUMP_SDK.createV2AndBuyV2Instructions({
global,
mint,
name,
symbol,
uri,
creator,
user,
quoteAmount,
amount: getBuyTokenAmountFromSolAmount({
global,
feeConfig,
mintSupply: null,
bondingCurve: null,
amount: quoteAmount,
quoteMint: NATIVE_MINT,
creatorFeeBps, // the first buy pays the configured rate
}),
mayhemMode: false,
creatorFeeBps,
});createV2Instruction encodes whatever it is given; the program rejects a nonzero rate while the gate is off (6077 CreatorFeeNotConfigurable) or outside 1..=max (6078 CreatorFeeBpsOutOfRange), and any cashback coin before that (6082 CashbackDeprecated). createV2AndBuyV2Instructions and createV2AndBuyInstructions hold global, so they throw CashbackDeprecatedError, CreatorFeeNotConfigurableError or CreatorFeeBpsOutOfRangeError up front for what the program would reject (a zero or omitted rate always passes).
Changing the rate later: only through a CTO
BondingCurve.canEditCreatorFee is retired: nothing sets or reads it any more. A configured rate is fixed at creation and changes only through a community takeover, admin_cto, signed by Global.adminSetCreatorAuthority. One instruction covers the bonding curve, the coin's canonical pump-amm pool (through admin_cto_pool, when one exists) and its pump-fees SharingConfig (through admin_cto_sharing_config, when the coin is fee-shared); pump signs the two CPIs with its ["pool-authority", mint] PDA, which is the only caller those instructions accept. It replaces admin_set_creator, admin_set_all_coin_creators, admin_set_creator_fee_editable, set_creator_fee_bps and pump-fees reset_fee_sharing_config*.
Two paths, chosen by the arguments:
- New creator:
newCreatorset. The outgoing wallet creator is first paid from both vaults; a fee-shared coin keeps itsSharingConfigas creator and the config is rewritten tonewCreatorat 100%. - Holder rewards:
isHolderReward: true, nonewCreator. The coin becomes a holder-reward coin: the creator becomesholderRewardsPda(mint), cashback is cleared, a fee-shared coin's vaults are swept into the PDA's creator vault and itsSharingConfigis terminated. Permanent: a later CTO on the coin may only repeat this path to change the rate (6083HolderRewardCreatorImmutableotherwise), and it needsGlobal.isHolderRewardEnabled(6084).
creatorFeeBps may be set on either path, only on a quote admitted through QuoteControl (6091 CreatorFeeNotConfigurableForQuote on SOL or USDC), within 1..=Global.maxConfigurableCreatorFeeBps with the gate on, and not on a coin that stays cashback (6080). Mayhem coins cannot be taken over (6088). The instruction needs about ADMIN_CTO_COMPUTE_UNIT_LIMIT (600k) compute units.
// Online: Global and the curve in one round-trip, the signer defaulting to
// Global.adminSetCreatorAuthority, every rule above checked with typed errors
// first. Returns [setComputeUnitLimit(600_000), admin_cto].
const handOver = await onlineSdk.adminCtoInstructions(mint, { newCreator });
const toHolders = await onlineSdk.adminCtoInstructions(mint, {
isHolderReward: true,
creatorFeeBps: new BN(250), // exotic quotes only
});
// Offline, with explicit accounts: the creator as the curve stores it (the
// zero key on a legacy curve) and the curve's quote (NATIVE_MINT for SOL).
const instruction = await PUMP_SDK.adminCtoInstruction({
adminSetCreatorAuthority,
mint,
currentCreator: bondingCurve.creator,
quoteMint: normalizeQuoteMint(bondingCurve.quoteMint),
quoteTokenProgram, // the program that owns the quote mint
newCreator,
});The offline builder marks currentCreator writable unless it is the zero key: the program requires a wallet creator to be passed writable so it can be paid (6092), and the IDL cannot declare it so (a legacy curve's zero-key creator is the system program). AdminCtoEvent (decodeAdminCtoEvent) records both paths: old and new creator, whether the coin is now holder-reward or cashback, old and new rate, whether the sharing config was reset, how much was swept, and whether a pool was updated; pump-amm emits AdminCtoPoolEvent (decodeAdminCtoPoolEventAmm) for the pool side.
Admin: the gate and the ceiling
// Global.authority signs (fetched when omitted).
await onlineSdk.adminUpdateCreatorFeeConfigInstruction({
creatorFeeConfigurable: true,
maxConfigurableCreatorFeeBps: new BN(5_000),
});The program puts no bound on the ceiling. Every sell path subtracts fees with checked arithmetic, so a rate that pushes protocol + creator fees past 100% makes a coin buy-only.
Deploy order and pre-upgrade accounts
The upgrades append fields to BondingCurve (115 → 124 bytes with configurable creator fees, → 125 with holder-reward coins, BONDING_CURVE_SIZE; extend_account still grows a curve to BONDING_CURVE_NEW_SIZE = 151) and to Global (1045 → 1054 → 1087 bytes, GLOBAL_SIZE). The program reads every historical curve length and so does PumpSdk.decodeBondingCurve (a shorter account reads creatorFeeBps = 0, canEditCreatorFee = false, isHolderReward = false). Global is a plain Anchor account: extend_account must run on it right after each program upgrade, and until then decodeGlobal / fetchGlobal read the shorter account with the new fields at their defaults (gate off, holder rewards disabled, the zero key as claim authority). Pre-upgrade curves keep trading, but every instruction that rewrites the whole curve (set_creator, ...) fails on an account that is still 115 bytes until the permissionless extend_account grows it, so prepend PUMP_SDK.extendAccountInstruction({ account, user }) when the account is shorter than the size the instruction writes. admin_cto grows the curve (and, through pump-amm, the pool) itself, the authority paying the rent. update_creator_fee_config and update_holder_reward_config fail on a Global shorter than GLOBAL_SIZE until it is extended.
Events: CreateEvent gains creatorFeeBps and isHolderReward (zero / false from legacy create and in events emitted before the fields existed); decodeCreateEventBc reads every layout: the pre-creator-fee one, the configurable-creator-fee one and the holder-reward one. TradeEvent gains holderRewardsBps / holderRewards, which decodeTradeEventBc reads as zero from older logs. UpdateCreatorFeeConfigEvent, AdminCtoEvent and DistributeFeeToHoldersEvent have decoders on PumpSdk. pump-amm's CreatePoolEvent gains creatorFeeBps, canEditCreatorFee and isHolderReward; decodeCreatePoolEventAmm reads the one- and ten-byte-shorter historical layouts with the missing fields at their defaults, and BuyEvent / SellEvent gain holderRewardsBps / holderRewards (decodeBuyEventAmm / decodeSellEventAmm read zero from older logs); AdminCtoPoolEvent has decodeAdminCtoPoolEventAmm.
Holder-reward coins
A holder-reward coin pays its creator fee to its holders instead of a creator. On chain that is a coin whose creator is the per-mint holderRewardsPda(mint) (["holder-rewards", mint]): BondingCurve.isHolderReward (and Pool.isHolderReward after graduation) is true, and every creator fee accrues to that PDA's creator vault (creatorVaultPda(holderRewardsPda(mint))) exactly as it would for a wallet creator, so the trade paths, the fee math and the creator_vault derivation are unchanged: keep reading bondingCurve.creator. Trade events report the fee twice, in the unchanged creatorFee / creatorFeeBasisPoints and in the new holderRewards / holderRewardsBps (zero on any other coin). A coin becomes holder-reward at creation or through a CTO, and stays so.
const global = await onlineSdk.fetchGlobal();
if (!global.isHolderRewardEnabled) throw new Error("holder-reward coins are off");
// The first buy pays into the PDA's vault: the builders derive it from the
// mint, whatever `creator` says (the program ignores that argument).
const instructions = await PUMP_SDK.createV2AndBuyV2Instructions({
global,
mint,
name,
symbol,
uri,
creator, // ignored by the program on a holder-reward coin
user,
quoteAmount,
amount: getBuyTokenAmountFromSolAmount({
global,
feeConfig,
mintSupply: null,
bondingCurve: null,
amount: quoteAmount,
quoteMint: NATIVE_MINT,
}),
mayhemMode: false,
holderReward: true,
});
// A standalone create.
await PUMP_SDK.createV2Instruction({ mint, name, symbol, uri, creator, user, mayhemMode: false, holderReward: true });create_v2 needs Global.isHolderRewardEnabled (6084 HolderRewardDisabled); the create-and-buy builders throw HolderRewardDisabledError up front. newBondingCurve(global, quoteMint, quoteControl, creatorFeeBps, isHolderReward) carries the flag for quoting a new curve.
Collecting and paying out
Nothing new is needed to collect: the permissionless collect_creator_fee / collect_creator_fee_v2 (and, after graduation, pump-amm's collect_coin_creator_fee or transfer_creator_fees_to_pump) called with the PDA as creator move the vault onto the PDA (lamports on a SOL quote, the PDA's quote ATA on a token quote). collectCoinCreatorFeeInstructions(holderRewardsPda(mint)) and the V2 / all-quotes variants build them. distribute_fee_to_holders, signed by Global.holderRewardClaimAuthority, then pays amounts[i] from the PDA to holder i:
// Online: the signer from Global, the quote from the curve, the PDA's quote
// ATA passed when it exists (the payout source on a token quote; on a SOL
// quote a parked WSOL account the program sweeps into the PDA first).
const [distribute] = await onlineSdk.distributeFeeToHoldersInstructions(mint, [
{ owner: holderA, amount: new BN(1_000_000) },
{ owner: holderB, amount: new BN(2_500_000) },
]);
// Offline.
await PUMP_SDK.distributeFeeToHoldersInstruction({
holderRewardClaimAuthority,
mint,
quoteMint, // NATIVE_MINT for a SOL coin
quoteTokenProgram,
recipients,
holderRewardsTokenAccount, // omit on a SOL quote with nothing parked
});Remaining accounts are [owner, owner's quote ATA] per recipient; on a token quote a missing ATA is created (rent paid by the authority). The program refuses a count or ATA mismatch (6085), a SOL payout that would leave the PDA between zero and its rent-exempt minimum (6086; draining it fully is fine) and a token payout without a source account (6087). DistributeFeeToHoldersEvent (decodeDistributeFeeToHoldersEvent) records the mint, the quote (the zero key for SOL), the recipient count and the total.
Admin: the gate and the claim authority
// Global.authority signs (fetched when omitted).
await onlineSdk.adminUpdateHolderRewardConfigInstruction({
isHolderRewardEnabled: true,
holderRewardClaimAuthority,
});Creator fees
const user = PublicKey.unique();
// Getting total accumulated SOL creator fees for both Pump and PumpSwap programs
console.log((await sdk.getCreatorVaultBalanceBothPrograms(user)).toString());
// Collecting SOL creator fees instructions
const instructions = await sdk.collectCoinCreatorFeeInstructions(user);Creator fees accrue in the coin's quote mint. For coins quoted in a token, collect per mint with collectCoinCreatorFeeV2Instructions(creator, quoteMint, quoteTokenProgram), or sweep every quote mint currently listed on Global or in QuoteControl at once:
// What is waiting, per listed quote mint, in the Pump creator vault and the PumpSwap vault.
const balances = await sdk.getCreatorVaultQuoteBalances(user);
// SOL, then each listed token quote whose vault token account exists. Creates
// the creator's missing quote token accounts (neither program does).
const instructions = await sdk.collectCoinCreatorFeeAllQuotesInstructions(user, feePayer);
// De-listing a mint stops new creates only: curves quoted in it keep accruing
// fees, and the sweep does not know about them. Pass them explicitly.
const all = await sdk.collectCoinCreatorFeeAllQuotesInstructions(user, feePayer, [delistedMint]);Each quote's instructions are contiguous, so split the result per quote when it does not fit one transaction.
Fee Sharing
Fee sharing allows token creators to set up fee distribution to multiple shareholders. The OnlinePumpSdk provides methods to check distributable fees and distribute them.
Important: Reward split can be setup once and once only by calling either
updateFeeSharesorupdateSharingConfigWithSocialRecipients. Double-check final recipients andshareBpsbefore submitting.
Quote mints:
updateFeeSharesanddistributeCreatorFees(v1), and the v1transfer_creator_fees_to_pumpconsolidation the online helpers build, only handle SOL-quoted coins. A coin quoted in a token must useupdateFeeSharesV2,distributeCreatorFeesV2andtransferCreatorFeesToPumpV2, passing the coin'squoteMintand the program that owns it asquoteTokenProgram(see Quote mints); the online helpers below take the same asoptions.
import { OnlinePumpSdk } from "@pump-fun/pump-sdk";
const connection = new Connection(
"https://api.devnet.solana.com",
"confirmed",
);
const onlineSdk = new OnlinePumpSdk(connection);
const mint = new PublicKey("...");Check if Creator Has Migrated to Fee Sharing
Before checking or distributing fees, verify that the coin creator has set up fee sharing:
const usingSharingConfig = isCreatorUsingSharingConfig({ mint, creator });
if (!usingSharingConfig) {
console.log("Creator has not set up fee sharing");
return;
}
// Creator has migrated, proceed with fee distributionGet Minimum Distributable Fee
Check whether a coin's fee sharing configuration balance and distributable fees
const result = await onlineSdk.getMinimumDistributableFee(mint);
console.log("Minimum required:", result.minimumRequired.toString());
console.log("Distributable fees:", result.distributableFees.toString());
console.log("Can distribute:", result.canDistribute);
console.log("Is graduated:", result.isGraduated);This method handles both graduated (AMM) and non-graduated (bonding curve) tokens. For graduated tokens, it automatically consolidates fees from the AMM vault before calculating.
For a coin quoted in a token, pass the quote so the canonical pool (and therefore isGraduated) and the consolidation instruction are right:
const result = await onlineSdk.getMinimumDistributableFee(mint, simulationSigner, {
quoteMint: bondingCurve.quoteMint, // normalized: the zero key means SOL
quoteTokenProgram, // optional; fetched from the mint when omitted
payer, // optional; signer for the V2 consolidation, defaults to simulationSigner
});Note that the on-chain get_minimum_distributable_fee view reads the lamport creator vault only, so for a token quote minimumRequired, distributableFees and canDistribute describe SOL fees, not the quote-token fees; result.feesQuoteMint (always NATIVE_MINT today) says which mint the figures are in, so compare it with the coin's quote mint before gating on canDistribute. getCreatorVaultQuoteBalances reports token-quote amounts.
Distribute Creator Fees
Build instructions to distribute accumulated creator fees to shareholders:
// `payer` signs the creator-fee sweeps, needed once v3 / pool v2 trades left creator fees behind.
const { instructions, isGraduated, sweepCount } = await onlineSdk.buildDistributeCreatorFeesInstructions(mint, {
payer,
});
// instructions contains:
// - sweep_creator_fee (curve and/or pool) first, when v3 / pool v2 trades left creator fees there
// (`sweepCount` of them; send them in a transaction of their own if the whole list is too large)
// - For graduated tokens: transferCreatorFeesToPump + distributeCreatorFees
// - For non-graduated tokens: distributeCreatorFees only
// Add instructions to your transaction
const tx = new Transaction().add(...instructions);This method automatically handles graduated tokens by including the transferCreatorFeesToPump instruction to consolidate fees from the AMM vault before distributing.
For a coin quoted in a token pass the quote and a payer (the V2 instructions may create token accounts); the quote token program is fetched from the mint when omitted:
const { instructions } = await onlineSdk.buildDistributeCreatorFeesInstructions(mint, {
quoteMint: bondingCurve.quoteMint,
payer,
});GitHub Recipient and Social Fee PDA Requirements
If you are adding a GitHub recipient as a fee recipient in sharing config, make sure to initialize the social fee pda before adding it as a recipient. Use one of these methods:
import { Platform, PUMP_SDK } from "@pump-fun/pump-sdk";
// 1) Update an existing sharing config
await PUMP_SDK.updateSharingConfigWithSocialRecipients({
authority,
mint,
currentShareholders,
newShareholders: [
{ address: authority, shareBps: 7000 },
{ userId: "1234567", platform: Platform.GitHub, shareBps: 3000 },
],
});
// 2) Create sharing config + set social recipients in one flow
// - Use pool for graduated coins or null for ungraduated
await PUMP_SDK.createSharingConfigWithSocialRecipients({
creator,
mint,
pool,
newShareholders: [
{ address: creator, shareBps: 7000 },
{ userId: "1234567", platform: Platform.GitHub, shareBps: 3000 },
],
});Method selection:
updateSharingConfigWithSocialRecipients: use when sharing config already exists.createSharingConfigWithSocialRecipients: use for first-time setup (creates config, then updates shares).
Important:
- Social fee PDA creation is a one-time initialization per
(userId, platform)and can be reused across coins once created. - Reward split can be setup once and once only by calling either
updateFeeSharesorupdateSharingConfigWithSocialRecipients. Double-check final recipients andshareBpsbefore submitting.
✅ Checklist
- [ ] The GitHub user must be able to log in to claim fees. GitHub organizations are not supported for social fee recipients; adding an organization account can result in fees being permanently lost.
- [ ] Only
Platform.GitHubis supported. Any attempt to use a different platform value can result in the coin being banned or fees lost. - [ ] Fees in a GitHub vault can only be claimed by the linked GitHub user, and only through Pump.fun (web or mobile). You are responsible for directing users to claim there; we do not support any claim flow outside our apps.
- [ ] You have initialized the social fee recipient pda by using one of the above helper or
createSocialFeePda
