From e1e15dd539b1d03147373c3e9327554b251d915f Mon Sep 17 00:00:00 2001 From: smypmsa Date: Fri, 29 Aug 2025 15:21:36 +0000 Subject: [PATCH] feat(pumpfun): add fee_config and fee_program, close #132 --- learning-examples/manual_buy.py | 21 +++++++++++++ learning-examples/manual_buy_geyser.py | 21 +++++++++++++ learning-examples/manual_sell.py | 21 +++++++++++++ learning-examples/mint_and_buy.py | 23 ++++++++++++++ .../pumpswap/manual_buy_pumpswap.py | 18 +++++++++++ .../pumpswap/manual_sell_pumpswap.py | 18 +++++++++++ src/platforms/pumpfun/address_provider.py | 30 +++++++++++++++++++ src/platforms/pumpfun/instruction_builder.py | 28 +++++++++++++++++ 8 files changed, 180 insertions(+) diff --git a/learning-examples/manual_buy.py b/learning-examples/manual_buy.py index cddaf49..cf6b261 100644 --- a/learning-examples/manual_buy.py +++ b/learning-examples/manual_buy.py @@ -33,6 +33,7 @@ PUMP_EVENT_AUTHORITY = Pubkey.from_string( "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1" ) PUMP_FEE = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM") +PUMP_FEE_PROGRAM = Pubkey.from_string("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ") SYSTEM_PROGRAM = Pubkey.from_string("11111111111111111111111111111111") SYSTEM_TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string( @@ -117,6 +118,14 @@ def _find_user_volume_accumulator(user: Pubkey) -> Pubkey: return derived_address +def _find_fee_config() -> Pubkey: + derived_address, _ = Pubkey.find_program_address( + [b"fee_config", bytes(PUMP_PROGRAM)], + PUMP_FEE_PROGRAM, + ) + return derived_address + + async def buy_token( mint: Pubkey, bonding_curve: Pubkey, @@ -176,6 +185,18 @@ async def buy_token( is_signer=False, is_writable=True, ), + # Index 14: fee_config (readonly) + AccountMeta( + pubkey=_find_fee_config(), + is_signer=False, + is_writable=False, + ), + # Index 15: fee_program (readonly) + AccountMeta( + pubkey=PUMP_FEE_PROGRAM, + is_signer=False, + is_writable=False, + ), ] discriminator = struct.pack(" Pubkey: return derived_address +def _find_fee_config() -> Pubkey: + derived_address, _ = Pubkey.find_program_address( + [b"fee_config", bytes(PUMP_PROGRAM)], + PUMP_FEE_PROGRAM, + ) + return derived_address + + async def create_geyser_connection(): """Establish a secure connection to the Geyser endpoint using the configured auth type.""" if AUTH_TYPE == "x-token": @@ -301,6 +310,18 @@ async def buy_token( is_signer=False, is_writable=True, ), + # Index 14: fee_config (readonly) + AccountMeta( + pubkey=_find_fee_config(), + is_signer=False, + is_writable=False, + ), + # Index 15: fee_program (readonly) + AccountMeta( + pubkey=PUMP_FEE_PROGRAM, + is_signer=False, + is_writable=False, + ), ] discriminator = struct.pack(" Pubkey: return derived_address +def _find_fee_config() -> Pubkey: + derived_address, _ = Pubkey.find_program_address( + [b"fee_config", bytes(PUMP_PROGRAM)], + PUMP_FEE_PROGRAM, + ) + return derived_address + + def calculate_pump_curve_price(curve_state: BondingCurveState) -> float: if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0: raise ValueError("Invalid reserve state") @@ -184,6 +193,18 @@ async def sell_token( pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False ), AccountMeta(pubkey=PUMP_PROGRAM, is_signer=False, is_writable=False), + # Index 12: fee_config (readonly) + AccountMeta( + pubkey=_find_fee_config(), + is_signer=False, + is_writable=False, + ), + # Index 13: fee_program (readonly) + AccountMeta( + pubkey=PUMP_FEE_PROGRAM, + is_signer=False, + is_writable=False, + ), ] discriminator = struct.pack(" Pubkey: return derived_address +def _find_fee_config() -> Pubkey: + derived_address, _ = Pubkey.find_program_address( + [b"fee_config", bytes(PUMP_PROGRAM)], + PUMP_FEE_PROGRAM, + ) + return derived_address + + def create_pump_create_instruction( mint: Pubkey, mint_authority: Pubkey, @@ -217,6 +228,18 @@ def create_buy_instruction( is_signer=False, is_writable=True, ), + # Index 14: fee_config (readonly) + AccountMeta( + pubkey=_find_fee_config(), + is_signer=False, + is_writable=False, + ), + # Index 15: fee_program (readonly) + AccountMeta( + pubkey=PUMP_FEE_PROGRAM, + is_signer=False, + is_writable=False, + ), ] data = ( diff --git a/learning-examples/pumpswap/manual_buy_pumpswap.py b/learning-examples/pumpswap/manual_buy_pumpswap.py index 0853a8a..ed73745 100644 --- a/learning-examples/pumpswap/manual_buy_pumpswap.py +++ b/learning-examples/pumpswap/manual_buy_pumpswap.py @@ -66,6 +66,7 @@ SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string( PUMP_SWAP_EVENT_AUTHORITY = Pubkey.from_string( "GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR" ) +PUMP_FEE_PROGRAM = Pubkey.from_string("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ") LAMPORTS_PER_SOL = 1_000_000_000 COMPUTE_UNIT_PRICE = 10_000 # Price in micro-lamports per compute unit COMPUTE_UNIT_BUDGET = 200_000 # Maximum compute units to use @@ -216,6 +217,19 @@ def find_user_volume_accumulator(user: Pubkey) -> Pubkey: return derived_address +def find_fee_config() -> Pubkey: + """Derive the Program Derived Address (PDA) for the fee config. + + Returns: + Pubkey of the derived fee config account + """ + derived_address, _ = Pubkey.find_program_address( + [b"fee_config", bytes(PUMP_AMM_PROGRAM_ID)], + PUMP_FEE_PROGRAM, + ) + return derived_address + + async def calculate_token_pool_price( client: AsyncClient, pool_base_token_account: Pubkey, @@ -341,6 +355,10 @@ async def buy_pump_swap( pubkey=global_volume_accumulator, is_signer=False, is_writable=True ), AccountMeta(pubkey=user_volume_accumulator, is_signer=False, is_writable=True), + # Index 21: fee_config (readonly) + AccountMeta(pubkey=find_fee_config(), is_signer=False, is_writable=False), + # Index 22: fee_program (readonly) + AccountMeta(pubkey=PUMP_FEE_PROGRAM, is_signer=False, is_writable=False), ] data = ( diff --git a/learning-examples/pumpswap/manual_sell_pumpswap.py b/learning-examples/pumpswap/manual_sell_pumpswap.py index 9f14d14..756a10d 100644 --- a/learning-examples/pumpswap/manual_sell_pumpswap.py +++ b/learning-examples/pumpswap/manual_sell_pumpswap.py @@ -58,6 +58,7 @@ SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string( PUMP_SWAP_EVENT_AUTHORITY = Pubkey.from_string( "GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR" ) +PUMP_FEE_PROGRAM = Pubkey.from_string("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ") LAMPORTS_PER_SOL = 1_000_000_000 COMPUTE_UNIT_PRICE = 10_000 # Price in micro-lamports per compute unit COMPUTE_UNIT_BUDGET = 100_000 # Maximum compute units to use @@ -171,6 +172,19 @@ def find_coin_creator_vault(coin_creator: Pubkey) -> Pubkey: return derived_address +def find_fee_config() -> Pubkey: + """Derive the Program Derived Address (PDA) for the fee config. + + Returns: + Pubkey of the derived fee config account + """ + derived_address, _ = Pubkey.find_program_address( + [b"fee_config", bytes(PUMP_AMM_PROGRAM_ID)], + PUMP_FEE_PROGRAM, + ) + return derived_address + + async def calculate_token_pool_price( client: AsyncClient, pool_base_token_account: Pubkey, @@ -327,6 +341,10 @@ async def sell_pump_swap( AccountMeta( pubkey=coin_creator_vault_authority, is_signer=False, is_writable=False ), + # Index 19: fee_config (readonly) + AccountMeta(pubkey=find_fee_config(), is_signer=False, is_writable=False), + # Index 20: fee_program (readonly) + AccountMeta(pubkey=PUMP_FEE_PROGRAM, is_signer=False, is_writable=False), ] data = ( diff --git a/src/platforms/pumpfun/address_provider.py b/src/platforms/pumpfun/address_provider.py index c17e522..771023b 100644 --- a/src/platforms/pumpfun/address_provider.py +++ b/src/platforms/pumpfun/address_provider.py @@ -34,6 +34,9 @@ class PumpFunAddresses: LIQUIDITY_MIGRATOR: Final[Pubkey] = Pubkey.from_string( "39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg" ) + FEE_PROGRAM: Final[Pubkey] = Pubkey.from_string( + "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" + ) @staticmethod def find_global_volume_accumulator() -> Pubkey: @@ -66,6 +69,20 @@ class PumpFunAddresses: ) return derived_address + @staticmethod + def find_fee_config() -> Pubkey: + """ + Derive the Program Derived Address (PDA) for the fee config. + + Returns: + Pubkey of the derived fee config account + """ + derived_address, _ = Pubkey.find_program_address( + [b"fee_config", bytes(PumpFunAddresses.PROGRAM)], + PumpFunAddresses.FEE_PROGRAM, + ) + return derived_address + class PumpFunAddressProvider(AddressProvider): """Pump.Fun implementation of AddressProvider interface.""" @@ -97,6 +114,7 @@ class PumpFunAddressProvider(AddressProvider): "event_authority": PumpFunAddresses.EVENT_AUTHORITY, "fee": PumpFunAddresses.FEE, "liquidity_migrator": PumpFunAddresses.LIQUIDITY_MIGRATOR, + "fee_program": PumpFunAddresses.FEE_PROGRAM, } # Combine system and platform-specific addresses @@ -223,6 +241,14 @@ class PumpFunAddressProvider(AddressProvider): """ return PumpFunAddresses.find_user_volume_accumulator(user) + def derive_fee_config(self) -> Pubkey: + """Derive the fee config PDA. + + Returns: + Fee config address + """ + return PumpFunAddresses.find_fee_config() + def get_buy_instruction_accounts( self, token_info: TokenInfo, user: Pubkey ) -> dict[str, Pubkey]: @@ -258,6 +284,8 @@ class PumpFunAddressProvider(AddressProvider): "program": PumpFunAddresses.PROGRAM, "global_volume_accumulator": self.derive_global_volume_accumulator(), "user_volume_accumulator": self.derive_user_volume_accumulator(user), + "fee_config": self.derive_fee_config(), + "fee_program": PumpFunAddresses.FEE_PROGRAM, } def get_sell_instruction_accounts( @@ -293,4 +321,6 @@ class PumpFunAddressProvider(AddressProvider): "token_program": SystemAddresses.TOKEN_PROGRAM, "event_authority": PumpFunAddresses.EVENT_AUTHORITY, "program": PumpFunAddresses.PROGRAM, + "fee_config": self.derive_fee_config(), + "fee_program": PumpFunAddresses.FEE_PROGRAM, } diff --git a/src/platforms/pumpfun/instruction_builder.py b/src/platforms/pumpfun/instruction_builder.py index 4540c5c..7aa24af 100644 --- a/src/platforms/pumpfun/instruction_builder.py +++ b/src/platforms/pumpfun/instruction_builder.py @@ -130,6 +130,18 @@ class PumpFunInstructionBuilder(InstructionBuilder): is_signer=False, is_writable=True, ), + # Index 14: fee_config (readonly) + AccountMeta( + pubkey=accounts_info["fee_config"], + is_signer=False, + is_writable=False, + ), + # Index 15: fee_program (readonly) + AccountMeta( + pubkey=accounts_info["fee_program"], + is_signer=False, + is_writable=False, + ), ] # Build instruction data: discriminator + token_amount + max_sol_cost @@ -217,6 +229,18 @@ class PumpFunInstructionBuilder(InstructionBuilder): AccountMeta( pubkey=accounts_info["program"], is_signer=False, is_writable=False ), + # Index 12: fee_config (readonly) + AccountMeta( + pubkey=accounts_info["fee_config"], + is_signer=False, + is_writable=False, + ), + # Index 13: fee_program (readonly) + AccountMeta( + pubkey=accounts_info["fee_program"], + is_signer=False, + is_writable=False, + ), ] # Build instruction data: discriminator + token_amount + min_sol_output @@ -260,6 +284,8 @@ class PumpFunInstructionBuilder(InstructionBuilder): accounts_info["global_volume_accumulator"], accounts_info["user_volume_accumulator"], accounts_info["program"], + accounts_info["fee_config"], + accounts_info["fee_program"], ] def get_required_accounts_for_sell( @@ -285,6 +311,8 @@ class PumpFunInstructionBuilder(InstructionBuilder): accounts_info["fee"], accounts_info["creator_vault"], accounts_info["program"], + accounts_info["fee_config"], + accounts_info["fee_program"], ] def calculate_token_amount_raw(self, token_amount_decimal: float) -> int: