feat(core): pump portal fully integrated

This commit is contained in:
smypmsa
2025-08-04 05:40:02 +00:00
parent bf7fbaa8ab
commit ea1d6b469a
8 changed files with 328 additions and 126 deletions
+1 -1
View File
@@ -45,7 +45,7 @@ VALID_VALUES = {
# Platform-specific listener compatibility # Platform-specific listener compatibility
PLATFORM_LISTENER_COMPATIBILITY = { PLATFORM_LISTENER_COMPATIBILITY = {
Platform.PUMP_FUN: ["logs", "blocks", "geyser", "pumpportal"], Platform.PUMP_FUN: ["logs", "blocks", "geyser", "pumpportal"],
Platform.LETS_BONK: ["blocks", "geyser"], # LetsBonk only supports geyser and blocks Platform.LETS_BONK: ["blocks", "geyser", "pumpportal"],
} }
+33 -15
View File
@@ -87,21 +87,30 @@ class ListenerFactory:
return listener return listener
elif listener_type == "pumpportal": elif listener_type == "pumpportal":
# PumpPortal is pump.fun specific, so filter platforms # Import the new universal PumpPortal listener
pumpfun_platforms = [Platform.PUMP_FUN] from monitoring.universal_pumpportal_listener import (
if platforms: UniversalPumpPortalListener,
pumpfun_platforms = [p for p in platforms if p == Platform.PUMP_FUN]
if not pumpfun_platforms:
raise ValueError("PumpPortal listener only supports pump.fun platform")
from monitoring.pumpportal_listener import PumpPortalListener
listener = PumpPortalListener(
pump_program=None, # Will be determined from platform
pumpportal_url=pumpportal_url,
) )
logger.info("Created PumpPortal listener for token monitoring")
# Validate that requested platforms support PumpPortal
supported_pumpportal_platforms = [Platform.PUMP_FUN, Platform.LETS_BONK]
if platforms:
unsupported = [p for p in platforms if p not in supported_pumpportal_platforms]
if unsupported:
logger.warning(f"Platforms {[p.value for p in unsupported]} do not support PumpPortal")
# Filter to only supported platforms
filtered_platforms = [p for p in platforms if p in supported_pumpportal_platforms]
if not filtered_platforms:
raise ValueError("No supported platforms specified for PumpPortal listener")
platforms = filtered_platforms
listener = UniversalPumpPortalListener(
pumpportal_url=pumpportal_url,
platforms=platforms,
)
logger.info(f"Created Universal PumpPortal listener for platforms: {[p.value for p in (platforms or supported_pumpportal_platforms)]}")
return listener return listener
else: else:
@@ -132,6 +141,15 @@ class ListenerFactory:
if platform == Platform.PUMP_FUN: if platform == Platform.PUMP_FUN:
return ["logs", "blocks", "geyser", "pumpportal"] return ["logs", "blocks", "geyser", "pumpportal"]
elif platform == Platform.LETS_BONK: elif platform == Platform.LETS_BONK:
return ["blocks", "geyser"] # LetsBonk only supports geyser and blocks return ["blocks", "geyser", "pumpportal"] # Added pumpportal support
else: else:
return ["blocks", "geyser"] # Default universal listeners return ["blocks", "geyser"] # Default universal listeners
@staticmethod
def get_pumpportal_supported_platforms() -> list[Platform]:
"""Get list of platforms that support PumpPortal listener.
Returns:
List of platforms with PumpPortal support
"""
return [Platform.PUMP_FUN, Platform.LETS_BONK]
@@ -1,5 +1,5 @@
""" """
PumpPortal monitoring for pump.fun tokens. Universal PumpPortal listener that works with multiple platforms.
""" """
import asyncio import asyncio
@@ -7,37 +7,59 @@ import json
from collections.abc import Awaitable, Callable from collections.abc import Awaitable, Callable
import websockets import websockets
from solders.pubkey import Pubkey
from interfaces.core import TokenInfo from interfaces.core import Platform, TokenInfo
from monitoring.base_listener import BaseTokenListener from monitoring.base_listener import BaseTokenListener
from monitoring.pumpportal_event_processor import PumpPortalEventProcessor
from platforms.pumpfun.address_provider import PumpFunAddresses
from utils.logger import get_logger from utils.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
class PumpPortalListener(BaseTokenListener): class UniversalPumpPortalListener(BaseTokenListener):
"""PumpPortal listener for pump.fun token creation events.""" """Universal PumpPortal listener that works with multiple platforms."""
def __init__( def __init__(
self, self,
pump_program: Pubkey | None = None,
pumpportal_url: str = "wss://pumpportal.fun/api/data", pumpportal_url: str = "wss://pumpportal.fun/api/data",
platforms: list[Platform] | None = None,
): ):
"""Initialize token listener. """Initialize universal PumpPortal listener.
Args: Args:
pump_program: Pump.fun program address (optional, will use default if None)
pumpportal_url: PumpPortal WebSocket URL pumpportal_url: PumpPortal WebSocket URL
platforms: List of platforms to monitor (if None, monitor all supported platforms)
""" """
super().__init__() super().__init__()
self.pump_program = pump_program or PumpFunAddresses.PROGRAM
self.pumpportal_url = pumpportal_url self.pumpportal_url = pumpportal_url
self.event_processor = PumpPortalEventProcessor(self.pump_program)
self.ping_interval = 20 # seconds self.ping_interval = 20 # seconds
# Get platform-specific processors
from platforms.letsbonk.pumpportal_processor import LetsBonkPumpPortalProcessor
from platforms.pumpfun.pumpportal_processor import PumpFunPumpPortalProcessor
# Create processor instances
all_processors = [
PumpFunPumpPortalProcessor(),
LetsBonkPumpPortalProcessor(),
]
# Filter processors based on requested platforms
if platforms is None:
self.processors = all_processors
else:
self.processors = [p for p in all_processors if p.platform in platforms]
# Build mapping of pool names to processors for quick lookup
self.pool_to_processors: dict[str, list] = {}
for processor in self.processors:
for pool_name in processor.supported_pool_names:
if pool_name not in self.pool_to_processors:
self.pool_to_processors[pool_name] = []
self.pool_to_processors[pool_name].append(processor)
logger.info(f"Initialized Universal PumpPortal listener for platforms: {[p.platform.value for p in self.processors]}")
logger.info(f"Monitoring pools: {list(self.pool_to_processors.keys())}")
async def listen_for_tokens( async def listen_for_tokens(
self, self,
token_callback: Callable[[TokenInfo], Awaitable[None]], token_callback: Callable[[TokenInfo], Awaitable[None]],
@@ -64,9 +86,10 @@ class PumpPortalListener(BaseTokenListener):
continue continue
logger.info( logger.info(
f"New token detected: {token_info.name} ({token_info.symbol})" f"New token detected: {token_info.name} ({token_info.symbol}) on {token_info.platform.value}"
) )
# Apply filters
if match_string and not ( if match_string and not (
match_string.lower() in token_info.name.lower() match_string.lower() in token_info.name.lower()
or match_string.lower() in token_info.symbol.lower() or match_string.lower() in token_info.symbol.lower()
@@ -76,14 +99,14 @@ class PumpPortalListener(BaseTokenListener):
) )
continue continue
if ( if creator_address:
creator_address creator_str = str(token_info.creator) if token_info.creator else ""
and str(token_info.user) != creator_address user_str = str(token_info.user) if token_info.user else ""
): if creator_address not in [creator_str, user_str]:
logger.info( logger.info(
f"Token not created by {creator_address}. Skipping..." f"Token not created by {creator_address}. Skipping..."
) )
continue continue
await token_callback(token_info) await token_callback(token_info)
@@ -150,18 +173,35 @@ class PumpPortalListener(BaseTokenListener):
data = json.loads(response) data = json.loads(response)
# Handle different message formats from PumpPortal # Handle different message formats from PumpPortal
token_info = None token_data = None
if "method" in data and data["method"] == "newToken": if "method" in data and data["method"] == "newToken":
# Standard newToken method format # Standard newToken method format
params = data.get("params", []) params = data.get("params", [])
if params and len(params) > 0: if params and len(params) > 0:
token_data = params[0] token_data = params[0]
token_info = self.event_processor.process_token_data(token_data) elif "signature" in data and "mint" in data and "pool" in data:
elif "signature" in data and "mint" in data:
# Direct token data format # Direct token data format
token_info = self.event_processor.process_token_data(data) token_data = data
return token_info if not token_data:
return None
# Get pool name to determine which processor to use
pool_name = token_data.get("pool", "").lower()
if pool_name not in self.pool_to_processors:
logger.debug(f"Ignoring token from unsupported pool: {pool_name}")
return None
# Try each processor that supports this pool
for processor in self.pool_to_processors[pool_name]:
if processor.can_process(token_data):
token_info = processor.process_token_data(token_data)
if token_info:
logger.debug(f"Successfully processed token using {processor.platform.value} processor")
return token_info
logger.debug(f"No processor could handle token data from pool {pool_name}")
return None
except TimeoutError: except TimeoutError:
logger.debug("No data received from PumpPortal for 30 seconds") logger.debug("No data received from PumpPortal for 30 seconds")
+3 -1
View File
@@ -9,11 +9,13 @@ from .address_provider import LetsBonkAddressProvider
from .curve_manager import LetsBonkCurveManager from .curve_manager import LetsBonkCurveManager
from .event_parser import LetsBonkEventParser from .event_parser import LetsBonkEventParser
from .instruction_builder import LetsBonkInstructionBuilder from .instruction_builder import LetsBonkInstructionBuilder
from .pumpportal_processor import LetsBonkPumpPortalProcessor
# Export implementations for direct use if needed # Export implementations for direct use if needed
__all__ = [ __all__ = [
'LetsBonkAddressProvider', 'LetsBonkAddressProvider',
'LetsBonkCurveManager', 'LetsBonkCurveManager',
'LetsBonkEventParser', 'LetsBonkEventParser',
'LetsBonkInstructionBuilder' 'LetsBonkInstructionBuilder',
'LetsBonkPumpPortalProcessor'
] ]
+61 -18
View File
@@ -79,6 +79,52 @@ class LetsBonkAddressProvider(AddressProvider):
) )
return pool_state return pool_state
def derive_base_vault(self, base_mint: Pubkey, quote_mint: Pubkey | None = None) -> Pubkey:
"""Derive the base vault address for a token pair.
Args:
base_mint: Base token mint address
quote_mint: Quote token mint (defaults to WSOL)
Returns:
Base vault address
"""
if quote_mint is None:
quote_mint = SystemAddresses.SOL_MINT
# First derive the pool state address
pool_state = self.derive_pool_address(base_mint, quote_mint)
# Then derive the base vault using pool_vault seed
base_vault, _ = Pubkey.find_program_address(
[b"pool_vault", bytes(pool_state), bytes(base_mint)],
LetsBonkAddresses.PROGRAM
)
return base_vault
def derive_quote_vault(self, base_mint: Pubkey, quote_mint: Pubkey | None = None) -> Pubkey:
"""Derive the quote vault address for a token pair.
Args:
base_mint: Base token mint address
quote_mint: Quote token mint (defaults to WSOL)
Returns:
Quote vault address
"""
if quote_mint is None:
quote_mint = SystemAddresses.SOL_MINT
# First derive the pool state address
pool_state = self.derive_pool_address(base_mint, quote_mint)
# Then derive the quote vault using pool_vault seed
quote_vault, _ = Pubkey.find_program_address(
[b"pool_vault", bytes(pool_state), bytes(quote_mint)],
LetsBonkAddresses.PROGRAM
)
return quote_vault
def derive_user_token_account(self, user: Pubkey, mint: Pubkey) -> Pubkey: def derive_user_token_account(self, user: Pubkey, mint: Pubkey) -> Pubkey:
"""Derive user's associated token account address. """Derive user's associated token account address.
@@ -102,15 +148,22 @@ class LetsBonkAddressProvider(AddressProvider):
""" """
accounts = {} accounts = {}
# Add pool state - must be present in token_info # Add pool state - derive if not present or use existing
if token_info.pool_state: if token_info.pool_state:
accounts["pool_state"] = token_info.pool_state accounts["pool_state"] = token_info.pool_state
else:
accounts["pool_state"] = self.derive_pool_address(token_info.mint)
# Add vault addresses - must be present in token_info # Add vault addresses - derive if not present or use existing
if token_info.base_vault: if token_info.base_vault:
accounts["base_vault"] = token_info.base_vault accounts["base_vault"] = token_info.base_vault
else:
accounts["base_vault"] = self.derive_base_vault(token_info.mint)
if token_info.quote_vault: if token_info.quote_vault:
accounts["quote_vault"] = token_info.quote_vault accounts["quote_vault"] = token_info.quote_vault
else:
accounts["quote_vault"] = self.derive_quote_vault(token_info.mint)
# Derive authority PDA # Derive authority PDA
accounts["authority"] = self.derive_authority_pda() accounts["authority"] = self.derive_authority_pda()
@@ -174,20 +227,15 @@ class LetsBonkAddressProvider(AddressProvider):
""" """
additional_accounts = self.get_additional_accounts(token_info) additional_accounts = self.get_additional_accounts(token_info)
# Vault addresses must be present in token_info
if not token_info.base_vault or not token_info.quote_vault:
raise ValueError(f"Missing required vault addresses for token {token_info.mint}. "
f"base_vault: {token_info.base_vault}, quote_vault: {token_info.quote_vault}")
return { return {
"payer": user, "payer": user,
"authority": additional_accounts["authority"], "authority": additional_accounts["authority"],
"global_config": LetsBonkAddresses.GLOBAL_CONFIG, "global_config": LetsBonkAddresses.GLOBAL_CONFIG,
"platform_config": LetsBonkAddresses.PLATFORM_CONFIG, "platform_config": LetsBonkAddresses.PLATFORM_CONFIG,
"pool_state": token_info.pool_state, "pool_state": additional_accounts["pool_state"],
"user_base_token": self.derive_user_token_account(user, token_info.mint), "user_base_token": self.derive_user_token_account(user, token_info.mint),
"base_vault": token_info.base_vault, "base_vault": additional_accounts["base_vault"],
"quote_vault": token_info.quote_vault, "quote_vault": additional_accounts["quote_vault"],
"base_token_mint": token_info.mint, "base_token_mint": token_info.mint,
"quote_token_mint": SystemAddresses.SOL_MINT, "quote_token_mint": SystemAddresses.SOL_MINT,
"base_token_program": SystemAddresses.TOKEN_PROGRAM, "base_token_program": SystemAddresses.TOKEN_PROGRAM,
@@ -208,20 +256,15 @@ class LetsBonkAddressProvider(AddressProvider):
""" """
additional_accounts = self.get_additional_accounts(token_info) additional_accounts = self.get_additional_accounts(token_info)
# Vault addresses must be present in token_info
if not token_info.base_vault or not token_info.quote_vault:
raise ValueError(f"Missing required vault addresses for token {token_info.mint}. "
f"base_vault: {token_info.base_vault}, quote_vault: {token_info.quote_vault}")
return { return {
"payer": user, "payer": user,
"authority": additional_accounts["authority"], "authority": additional_accounts["authority"],
"global_config": LetsBonkAddresses.GLOBAL_CONFIG, "global_config": LetsBonkAddresses.GLOBAL_CONFIG,
"platform_config": LetsBonkAddresses.PLATFORM_CONFIG, "platform_config": LetsBonkAddresses.PLATFORM_CONFIG,
"pool_state": token_info.pool_state, "pool_state": additional_accounts["pool_state"],
"user_base_token": self.derive_user_token_account(user, token_info.mint), "user_base_token": self.derive_user_token_account(user, token_info.mint),
"base_vault": token_info.base_vault, "base_vault": additional_accounts["base_vault"],
"quote_vault": token_info.quote_vault, "quote_vault": additional_accounts["quote_vault"],
"base_token_mint": token_info.mint, "base_token_mint": token_info.mint,
"quote_token_mint": SystemAddresses.SOL_MINT, "quote_token_mint": SystemAddresses.SOL_MINT,
"base_token_program": SystemAddresses.TOKEN_PROGRAM, "base_token_program": SystemAddresses.TOKEN_PROGRAM,
@@ -0,0 +1,118 @@
"""
LetsBonk-specific PumpPortal event processor.
File: src/platforms/letsbonk/pumpportal_processor.py
"""
from solders.pubkey import Pubkey
from interfaces.core import Platform, TokenInfo
from platforms.letsbonk.address_provider import LetsBonkAddressProvider
from utils.logger import get_logger
logger = get_logger(__name__)
class LetsBonkPumpPortalProcessor:
"""PumpPortal processor for LetsBonk tokens."""
def __init__(self):
"""Initialize the processor with address provider."""
self.address_provider = LetsBonkAddressProvider()
@property
def platform(self) -> Platform:
"""Get the platform this processor handles."""
return Platform.LETS_BONK
@property
def supported_pool_names(self) -> list[str]:
"""Get the pool names this processor supports from PumpPortal."""
return ["bonk"] # PumpPortal pool name for LetsBonk/bonk pools
def can_process(self, token_data: dict) -> bool:
"""Check if this processor can handle the given token data.
Args:
token_data: Token data from PumpPortal
Returns:
True if this processor can handle the token data
"""
pool = token_data.get("pool", "").lower()
return pool in self.supported_pool_names
def process_token_data(self, token_data: dict) -> TokenInfo | None:
"""Process LetsBonk token data from PumpPortal.
Args:
token_data: Token data from PumpPortal WebSocket
Returns:
TokenInfo if token creation found, None otherwise
"""
try:
# Extract required fields for LetsBonk
name = token_data.get("name", "")
symbol = token_data.get("symbol", "")
mint_str = token_data.get("mint")
creator_str = token_data.get("traderPublicKey")
uri = token_data.get("uri", "")
# Note: LetsBonk tokens from PumpPortal might have different field mappings
# This would need to be adjusted based on actual PumpPortal data for LetsBonk tokens
if not all([name, symbol, mint_str, creator_str]):
logger.warning("Missing required fields in PumpPortal LetsBonk token data")
return None
# Convert string addresses to Pubkey objects
mint = Pubkey.from_string(mint_str)
user = Pubkey.from_string(creator_str)
creator = user
# Derive LetsBonk-specific addresses
pool_state = self.address_provider.derive_pool_address(mint)
# For LetsBonk, vault addresses might need to be derived differently
# or provided in the PumpPortal data. For now, we'll derive them
# using the standard pattern, but this might need adjustment
additional_accounts = self.address_provider.get_additional_accounts(
# Create a minimal TokenInfo to get additional accounts
TokenInfo(
name=name,
symbol=symbol,
uri=uri,
mint=mint,
platform=Platform.LETS_BONK,
pool_state=pool_state,
user=user,
creator=creator,
base_vault=None, # Will be filled from additional_accounts
quote_vault=None, # Will be filled from additional_accounts
)
)
# Extract vault addresses if available
base_vault = additional_accounts.get("base_vault")
quote_vault = additional_accounts.get("quote_vault")
# If vaults aren't available from additional_accounts,
# we might need to derive them or leave them None
# and let the trading logic handle the derivation
return TokenInfo(
name=name,
symbol=symbol,
uri=uri,
mint=mint,
platform=Platform.LETS_BONK,
pool_state=pool_state,
base_vault=base_vault,
quote_vault=quote_vault,
user=user,
creator=creator,
)
except Exception as e:
logger.error(f"Failed to process PumpPortal LetsBonk token data: {e}")
return None
+3 -1
View File
@@ -9,11 +9,13 @@ from .address_provider import PumpFunAddressProvider
from .curve_manager import PumpFunCurveManager from .curve_manager import PumpFunCurveManager
from .event_parser import PumpFunEventParser from .event_parser import PumpFunEventParser
from .instruction_builder import PumpFunInstructionBuilder from .instruction_builder import PumpFunInstructionBuilder
from .pumpportal_processor import PumpFunPumpPortalProcessor
# Export implementations for direct use if needed # Export implementations for direct use if needed
__all__ = [ __all__ = [
'PumpFunAddressProvider', 'PumpFunAddressProvider',
'PumpFunCurveManager', 'PumpFunCurveManager',
'PumpFunEventParser', 'PumpFunEventParser',
'PumpFunInstructionBuilder' 'PumpFunInstructionBuilder',
'PumpFunPumpPortalProcessor'
] ]
@@ -1,30 +1,48 @@
""" """
Event processing for pump.fun tokens using PumpPortal data. PumpFun-specific PumpPortal event processor.
File: src/platforms/pumpfun/pumpportal_processor.py
""" """
from solders.pubkey import Pubkey from solders.pubkey import Pubkey
from core.pubkeys import SystemAddresses
from interfaces.core import Platform, TokenInfo from interfaces.core import Platform, TokenInfo
from platforms.pumpfun.address_provider import PumpFunAddresses from platforms.pumpfun.address_provider import PumpFunAddressProvider
from utils.logger import get_logger from utils.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
class PumpPortalEventProcessor: class PumpFunPumpPortalProcessor:
"""Processes token creation events from PumpPortal WebSocket.""" """PumpPortal processor for pump.fun tokens."""
def __init__(self, pump_program: Pubkey | None = None): def __init__(self):
"""Initialize event processor. """Initialize the processor with address provider."""
self.address_provider = PumpFunAddressProvider()
@property
def platform(self) -> Platform:
"""Get the platform this processor handles."""
return Platform.PUMP_FUN
@property
def supported_pool_names(self) -> list[str]:
"""Get the pool names this processor supports from PumpPortal."""
return ["pump"] # PumpPortal pool name for pump.fun
def can_process(self, token_data: dict) -> bool:
"""Check if this processor can handle the given token data.
Args: Args:
pump_program: Pump.fun program address (optional, will use default if None) token_data: Token data from PumpPortal
Returns:
True if this processor can handle the token data
""" """
self.pump_program = pump_program or PumpFunAddresses.PROGRAM pool = token_data.get("pool", "").lower()
return pool in self.supported_pool_names
def process_token_data(self, token_data: dict) -> TokenInfo | None: def process_token_data(self, token_data: dict) -> TokenInfo | None:
"""Process token data from PumpPortal and extract token creation info. """Process pump.fun token data from PumpPortal.
Args: Args:
token_data: Token data from PumpPortal WebSocket token_data: Token data from PumpPortal WebSocket
@@ -41,11 +59,12 @@ class PumpPortalEventProcessor:
creator_str = token_data.get("traderPublicKey") # Maps to user field creator_str = token_data.get("traderPublicKey") # Maps to user field
uri = token_data.get("uri", "") uri = token_data.get("uri", "")
# Additional fields available from PumpPortal but not used: # Additional fields available from PumpPortal but not currently used:
# - initialBuy: Initial buy amount in SOL # - initialBuy: Initial buy amount in tokens
# - marketCapSol: Market cap in SOL # - solAmount: SOL amount spent on initial buy
# - vSolInBondingCurve: Virtual SOL in bonding curve # - vSolInBondingCurve: Virtual SOL in bonding curve
# - vTokensInBondingCurve: Virtual tokens in bonding curve # - vTokensInBondingCurve: Virtual tokens in bonding curve
# - marketCapSol: Market cap in SOL
# - signature: Transaction signature # - signature: Transaction signature
if not all([name, symbol, mint_str, bonding_curve_str, creator_str]): if not all([name, symbol, mint_str, bonding_curve_str, creator_str]):
@@ -61,11 +80,11 @@ class PumpPortalEventProcessor:
# since PumpPortal doesn't distinguish between them # since PumpPortal doesn't distinguish between them
creator = user creator = user
# Calculate derived addresses # Derive additional addresses using platform provider
associated_bonding_curve = self._find_associated_bonding_curve( associated_bonding_curve = self.address_provider.derive_associated_bonding_curve(
mint, bonding_curve mint, bonding_curve
) )
creator_vault = self._find_creator_vault(creator) creator_vault = self.address_provider.derive_creator_vault(creator)
return TokenInfo( return TokenInfo(
name=name, name=name,
@@ -83,43 +102,3 @@ class PumpPortalEventProcessor:
except Exception as e: except Exception as e:
logger.error(f"Failed to process PumpPortal token data: {e}") logger.error(f"Failed to process PumpPortal token data: {e}")
return None return None
def _find_associated_bonding_curve(
self, mint: Pubkey, bonding_curve: Pubkey
) -> Pubkey:
"""
Find the associated bonding curve for a given mint and bonding curve.
This uses the standard ATA derivation.
Args:
mint: Token mint address
bonding_curve: Bonding curve address
Returns:
Associated bonding curve address
"""
derived_address, _ = Pubkey.find_program_address(
[
bytes(bonding_curve),
bytes(SystemAddresses.TOKEN_PROGRAM),
bytes(mint),
],
SystemAddresses.ASSOCIATED_TOKEN_PROGRAM,
)
return derived_address
def _find_creator_vault(self, creator: Pubkey) -> Pubkey:
"""
Find the creator vault for a creator.
Args:
creator: Creator address
Returns:
Creator vault address
"""
derived_address, _ = Pubkey.find_program_address(
[b"creator-vault", bytes(creator)],
PumpFunAddresses.PROGRAM,
)
return derived_address