config
["config", mint]
The kit, the thresholds, the high-water mark, and the running totals. Initialized once. Nothing in the program edits the kit after that.
FeeKit is one program. It does not launch the pump coin. The creator does that on pump, then this program binds the creator fees to a vault and a kit. After the lock, the kit cannot be changed and the vault cannot be withdrawn. A crank only succeeds when the program's checks pass.
9EMWVqoVNW9armPPwiY7DtW7kgQ14LTgk8F3mCkMZ11C
["config", mint]
The kit, the thresholds, the high-water mark, and the running totals. Initialized once. Nothing in the program edits the kit after that.
["vault", mint]
A zero-data system account so pump can pay SOL into it and an allowed kit action can pay SOL out. The program signs for it. There is no arbitrary withdraw.
["treasury"]
Pump's 15% lands here. It is separate from every coin vault. The platform authority can withdraw above rent. That instruction cannot touch a kit vault.
//! The SOL vault is a zero-data system account so pump can transfer SOL out of it.
//! There is no arbitrary withdraw instruction.Two transactions. The first is pump's create, signed by the creator and the mint. The second is initialize_vault and then lock_fees, signed by the creator. If the lock fails, the coin already exists on pump. Finish it with initialize and lock. Do not create it again.
Initialize reads the bonding curve and refuses the launch unless the signer is still that curve's creator, the quote is SOL, and the coin is not holder-rewards. The parameters are checked before the vault exists.
match params.kit {
KIT_BURN => {
require!(
params.interval_slots == 0
&& params.drawdown_bps == 0
&& params.spend_bps == BPS_DENOMINATOR as u16
&& params.mark_delay_slots == 0,
FeeKitError::InvalidParams
);
}
KIT_FLOOR => {
require!(
params.interval_slots == 0
&& params.mark_delay_slots == 0
&& params.drawdown_bps >= 1
&& params.drawdown_bps <= MAX_DRAWDOWN_BPS
&& params.spend_bps == BPS_DENOMINATOR as u16,
FeeKitError::InvalidParams
);
}
KIT_GRADUATE => {
require!(
params.slippage_bps == GRADUATE_SLIPPAGE_BPS
&& params.min_execute_lamports == MIN_EXECUTE_LAMPORTS
&& params.max_spend_lamports == 0
&& params.interval_slots == 0
&& params.drawdown_bps == 0
&& params.spend_bps == BPS_DENOMINATOR as u16
&& params.mark_delay_slots == 0,
FeeKitError::InvalidParams
);
}
_ => return err!(FeeKitError::InvalidParams),
}Kit byte 0 is Burn, 3 is Floor, and 5 is Graduate. Bytes 1, 2, and 4 are rejected. Draw remains compatible with existing launches: it is a Burn config whose minimum spend is the maximum u64, so buy execution can never clear the threshold. Collect still runs, and pay_draw recognizes that sentinel and pays its hourly pot.
Lock can run once. It creates pump's sharing config if needed, pays any fees already waiting to the current shareholder, then sets two shareholders: the kit vault at 8,500 bps and the treasury at 1,500 bps. The sharing admin is revoked in that same update, so a later transaction cannot point the fees somewhere else. Every collect, buy, and release reads the sharing account again and stops unless that exact split is still there.
/// Borsh Vec<Shareholder> with the kit vault at 85% and the treasury at 15%.
pub fn update_fee_shares_v2_data(sol_vault: &Pubkey, treasury: &Pubkey) -> Vec<u8> {
let mut data = Vec::with_capacity(80);
data.extend_from_slice(&UPDATE_FEE_SHARES_V2);
data.extend_from_slice(&2u32.to_le_bytes());
data.extend_from_slice(sol_vault.as_ref());
data.extend_from_slice(&KIT_SHARE_BPS.to_le_bytes());
data.extend_from_slice(treasury.as_ref());
data.extend_from_slice(&PLATFORM_FEE_BPS.to_le_bytes());
data
}A coin that is already on pump can be locked the same way, if the signer is still the creator and the fees still pay that wallet. Fees earned before the lock are paid out during it. Fees after the lock follow the split. A graduated coin passes its canonical PumpSwap pool, writable, as the only extra account.
require!(shareholders == 2, FeeKitError::FeeRecipientMismatch);
require!(vault_bps == KIT_SHARE_BPS, FeeKitError::FeeRecipientMismatch);
require!(treasury_bps == PLATFORM_FEE_BPS, FeeKitError::FeeRecipientMismatch);After the lock, none of these require the creator. The signer is just the crank. The program signs whenever SOL or tokens leave the vault.
The pump creator signs. The program checks the bonding curve, rejects a non-SOL quote and a holder-rewards coin, and creates the config and the vault.
The same creator signs, once. Pending creator fees are paid out first. From then on the split is the vault and the treasury, and the sharing admin is revoked.
Anyone. Pulls the pump creator vault into the kit vault, then pays the signer 5% of that fee from the vault's share.
Platform authority only. Sets or rotates the dedicated online signer allowed to settle Draw payouts. It does not expose the deploy or treasury key.
Draw executor only. At most hourly, pays recorded, unpaid creator fees from a sentinel Draw vault to the selected wallet.
Anyone, after graduation. Moves wrapped SOL from the PumpSwap creator vault into the pump creator vault so the next collect can see it.
Anyone, and only useful for Floor. If spot is a new high, the stored mark moves up. The line under it moves with the mark.
Anyone, while the curve is open. Buys with the vault and burns the tokens in the same transaction.
Anyone, after graduation, for Burn and Floor. Buys the canonical PumpSwap pool and burns. Graduate is rejected here.
Anyone, and only a Graduate coin whose curve is complete. Sends the spendable vault balance to the creator stored at initialize.
Rent for an empty account, plus 3,000,000 lamports, stays in the vault. The rest has to clear min_execute_lamports, which cannot be under 50,000. A non-zero max spend is a cap. The caller passes a minimum number of tokens. The program computes its own floor from the constant-product quote, the coin's slippage, and a 5% fee haircut, and rejects a minimum below that floor. The buy and the burn are one transaction. The burn is the vault's whole token balance, not a promise to burn later.
require!(bought >= min_tokens_out, FeeKitError::SlippageExceeded);
require!(bought > 0, FeeKitError::ZeroOutput);
token_interface::burn(
CpiContext::new_with_signer(
token_program.clone(),
token_interface::Burn {
mint: mint.clone(),
from: from.clone(),
authority: authority.clone(),
},
signer,
),
balance,
)?;Floor adds two checks. Spot above the line under the high returns DrawdownNotMet. A spot under the line spends only the quote that reaches the line, and only if that amount still clears the minimum. The curve has no resting order, so the program does not see a sell. The next crank buys with whatever is spendable. The cranker page follows the same rules from the client side.
execute_swap returns GraduatePaysCreator for this kit. release_creator requires the curve to be complete, requires the recipient to be the creator stored on the config, and transfers the spendable balance. The 15% already paid to the treasury stays there.
require!(config.locked, FeeKitError::FeesNotLocked);
require!(config.is_graduate(), FeeKitError::InvalidParams);
require_keys_eq!(
ctx.accounts.creator.key(),
config.creator,
FeeKitError::NotCreator
);
let curve = decode_bonding_curve(&ctx.accounts.bonding_curve.try_borrow_data()?)?;
require!(curve.complete, FeeKitError::NotGraduated);
let payout = spendable_lamports(ctx.accounts.sol_vault.lamports(), rent);
require!(
payout >= config.min_execute_lamports,
FeeKitError::ThresholdNotMet
);It will not unlock a coin, change the shareholders, or expose an arbitrary vault withdrawal. SOL leaves a kit vault only for the crank's 5% collect reward, a buy the kit allows, a Graduate payout to the stored creator, or an hourly Draw payout signed by the separately configured Draw executor. Draw must pay exactly the full collected, unpaid pot.