docs(cursor): add uv rules, formatting

This commit is contained in:
smypmsa
2025-07-18 12:03:00 +00:00
parent a5a92fbf4b
commit 536a5dd6a5
28 changed files with 2746 additions and 784 deletions
+2 -5
View File
@@ -172,7 +172,7 @@ class PumpEventProcessor:
args["user"] = str(accounts[7])
return args
def _find_creator_vault(self, creator: Pubkey) -> Pubkey:
"""
Find the creator vault for a creator.
@@ -184,10 +184,7 @@ class PumpEventProcessor:
Creator vault address
"""
derived_address, _ = Pubkey.find_program_address(
[
b"creator-vault",
bytes(creator)
],
[b"creator-vault", bytes(creator)],
PumpAddresses.PROGRAM,
)
return derived_address
+1 -1
View File
@@ -105,7 +105,7 @@ class BlockListener(BaseTokenListener):
{"mentionsAccountOrProgram": str(self.pump_program)},
{
"commitment": "confirmed",
"encoding": "base64", # base64 is faster than other encoding options
"encoding": "base64", # base64 is faster than other encoding options
"showRewards": False,
"transactionDetails": "full",
"maxSupportedTransactionVersion": 0,
+17 -16
View File
@@ -28,7 +28,9 @@ class GeyserEventProcessor:
"""
self.pump_program = pump_program
def process_transaction_data(self, instruction_data: bytes, accounts: list, keys: list) -> TokenInfo | None:
def process_transaction_data(
self, instruction_data: bytes, accounts: list, keys: list
) -> TokenInfo | None:
"""Process transaction data and extract token creation info.
Args:
@@ -45,7 +47,7 @@ class GeyserEventProcessor:
try:
# Skip past the 8-byte discriminator
offset = 8
# Helper to read strings (prefixed with length)
def read_string():
nonlocal offset
@@ -53,16 +55,18 @@ class GeyserEventProcessor:
length = struct.unpack_from("<I", instruction_data, offset)[0]
offset += 4
# Extract and decode the string
value = instruction_data[offset:offset + length].decode("utf-8")
value = instruction_data[offset : offset + length].decode("utf-8")
offset += length
return value
def read_pubkey():
nonlocal offset
value = base58.b58encode(instruction_data[offset : offset + 32]).decode("utf-8")
value = base58.b58encode(instruction_data[offset : offset + 32]).decode(
"utf-8"
)
offset += 32
return Pubkey.from_string(value)
# Helper to get account key
def get_account_key(index):
if index >= len(accounts):
@@ -71,7 +75,7 @@ class GeyserEventProcessor:
if account_index >= len(keys):
return None
return Pubkey.from_bytes(keys[account_index])
name = read_string()
symbol = read_string()
uri = read_string()
@@ -83,11 +87,11 @@ class GeyserEventProcessor:
user = get_account_key(7)
creator_vault = self._find_creator_vault(creator)
if not all([mint, bonding_curve, associated_bonding_curve, user]):
logger.warning("Missing required account keys in token creation")
return None
return TokenInfo(
name=name,
symbol=symbol,
@@ -99,11 +103,11 @@ class GeyserEventProcessor:
creator=creator,
creator_vault=creator_vault,
)
except Exception as e:
logger.error(f"Failed to process transaction data: {e}")
return None
def _find_creator_vault(self, creator: Pubkey) -> Pubkey:
"""
Find the creator vault for a creator.
@@ -115,10 +119,7 @@ class GeyserEventProcessor:
Creator vault address
"""
derived_address, _ = Pubkey.find_program_address(
[
b"creator-vault",
bytes(creator)
],
[b"creator-vault", bytes(creator)],
PumpAddresses.PROGRAM,
)
return derived_address
return derived_address
+41 -32
View File
@@ -20,9 +20,15 @@ logger = get_logger(__name__)
class GeyserListener(BaseTokenListener):
"""Geyser listener for pump.fun token creation events."""
def __init__(self, geyser_endpoint: str, geyser_api_token: str, geyser_auth_type: str, pump_program: Pubkey):
def __init__(
self,
geyser_endpoint: str,
geyser_api_token: str,
geyser_auth_type: str,
pump_program: Pubkey,
):
"""Initialize token listener.
Args:
geyser_endpoint: Geyser gRPC endpoint URL
geyser_api_token: API token for authentication
@@ -40,27 +46,31 @@ class GeyserListener(BaseTokenListener):
)
self.pump_program = pump_program
self.event_processor = GeyserEventProcessor(pump_program)
async def _create_geyser_connection(self):
"""Establish a secure connection to the Geyser endpoint."""
if self.auth_type == "x-token":
auth = grpc.metadata_call_credentials(
lambda _, callback: callback((("x-token", self.geyser_api_token),), None)
lambda _, callback: callback(
(("x-token", self.geyser_api_token),), None
)
)
else: # Default to basic auth
auth = grpc.metadata_call_credentials(
lambda _, callback: callback((("authorization", f"Basic {self.geyser_api_token}"),), None)
lambda _, callback: callback(
(("authorization", f"Basic {self.geyser_api_token}"),), None
)
)
creds = grpc.composite_channel_credentials(
grpc.ssl_channel_credentials(), auth
)
creds = grpc.composite_channel_credentials(grpc.ssl_channel_credentials(), auth)
channel = grpc.aio.secure_channel(self.geyser_endpoint, creds)
return geyser_pb2_grpc.GeyserStub(channel), channel
def _create_subscription_request(self):
"""Create a subscription request for Pump.fun transactions."""
request = geyser_pb2.SubscribeRequest()
request.transactions["pump_filter"].account_include.append(str(self.pump_program))
request.transactions["pump_filter"].account_include.append(
str(self.pump_program)
)
request.transactions["pump_filter"].failed = False
request.commitment = geyser_pb2.CommitmentLevel.PROCESSED
return request
@@ -72,7 +82,7 @@ class GeyserListener(BaseTokenListener):
creator_address: str | None = None,
) -> None:
"""Listen for new token creations using Geyser subscription.
Args:
token_callback: Callback function for new tokens
match_string: Optional string to match in token name/symbol
@@ -82,20 +92,22 @@ class GeyserListener(BaseTokenListener):
try:
stub, channel = await self._create_geyser_connection()
request = self._create_subscription_request()
logger.info(f"Connected to Geyser endpoint: {self.geyser_endpoint}")
logger.info(f"Monitoring for transactions involving program: {self.pump_program}")
logger.info(
f"Monitoring for transactions involving program: {self.pump_program}"
)
try:
async for update in stub.Subscribe(iter([request])):
token_info = await self._process_update(update)
if not token_info:
continue
logger.info(
f"New token detected: {token_info.name} ({token_info.symbol})"
)
if match_string and not (
match_string.lower() in token_info.name.lower()
or match_string.lower() in token_info.symbol.lower()
@@ -104,43 +116,40 @@ class GeyserListener(BaseTokenListener):
f"Token does not match filter '{match_string}'. Skipping..."
)
continue
if (
creator_address
and str(token_info.user) != creator_address
):
if creator_address and str(token_info.user) != creator_address:
logger.info(
f"Token not created by {creator_address}. Skipping..."
)
continue
await token_callback(token_info)
except grpc.aio.AioRpcError as e:
logger.error(f"gRPC error: {e.details()}")
await asyncio.sleep(5)
finally:
await channel.close()
except Exception as e:
logger.error(f"Geyser connection error: {e}")
logger.info("Reconnecting in 10 seconds...")
await asyncio.sleep(10)
async def _process_update(self, update) -> TokenInfo | None:
"""Process a Geyser update and extract token creation info.
Args:
update: Geyser update from the subscription
Returns:
TokenInfo if a token creation is found, None otherwise
"""
try:
if not update.HasField("transaction"):
return None
tx = update.transaction.transaction.transaction
msg = getattr(tx, "message", None)
if msg is None:
@@ -151,20 +160,20 @@ class GeyserListener(BaseTokenListener):
program_idx = ix.program_id_index
if program_idx >= len(msg.account_keys):
continue
program_id = msg.account_keys[program_idx]
if bytes(program_id) != bytes(self.pump_program):
continue
# Process instruction data
token_info = self.event_processor.process_transaction_data(
ix.data, ix.accounts, msg.account_keys
)
if token_info:
return token_info
return None
except Exception as e:
logger.error(f"Error processing Geyser update: {e}")
return None
+10 -11
View File
@@ -43,7 +43,7 @@ class LogsEventProcessor:
# Check if this is a token creation
if not any("Program log: Instruction: Create" in log for log in logs):
return None
# Skip swaps as the first condition may pass them
if any("Program log: Instruction: CreateTokenAccount" in log for log in logs):
return None
@@ -55,7 +55,7 @@ class LogsEventProcessor:
encoded_data = log.split(": ")[1]
decoded_data = base64.b64decode(encoded_data)
parsed_data = self._parse_create_instruction(decoded_data)
if parsed_data and "name" in parsed_data:
mint = Pubkey.from_string(parsed_data["mint"])
bonding_curve = Pubkey.from_string(parsed_data["bondingCurve"])
@@ -64,7 +64,7 @@ class LogsEventProcessor:
)
creator = Pubkey.from_string(parsed_data["creator"])
creator_vault = self._find_creator_vault(creator)
return TokenInfo(
name=parsed_data["name"],
symbol=parsed_data["symbol"],
@@ -78,7 +78,7 @@ class LogsEventProcessor:
)
except Exception as e:
logger.error(f"Failed to process log data: {e}")
return None
def _parse_create_instruction(self, data: bytes) -> dict | None:
@@ -92,11 +92,13 @@ class LogsEventProcessor:
"""
if len(data) < 8:
return None
# Check for the correct instruction discriminator
discriminator = struct.unpack("<Q", data[:8])[0]
if discriminator != self.CREATE_DISCRIMINATOR:
logger.info(f"Skipping non-Create instruction with discriminator: {discriminator}")
logger.info(
f"Skipping non-Create instruction with discriminator: {discriminator}"
)
return None
offset = 8
@@ -154,7 +156,7 @@ class LogsEventProcessor:
SystemAddresses.ASSOCIATED_TOKEN_PROGRAM,
)
return derived_address
def _find_creator_vault(self, creator: Pubkey) -> Pubkey:
"""
Find the creator vault for a creator.
@@ -166,10 +168,7 @@ class LogsEventProcessor:
Creator vault address
"""
derived_address, _ = Pubkey.find_program_address(
[
b"creator-vault",
bytes(creator)
],
[b"creator-vault", bytes(creator)],
PumpAddresses.PROGRAM,
)
return derived_address
+3 -6
View File
@@ -55,7 +55,7 @@ class PumpPortalEventProcessor:
mint = Pubkey.from_string(mint_str)
bonding_curve = Pubkey.from_string(bonding_curve_str)
user = Pubkey.from_string(creator_str)
# For PumpPortal, we assume the creator is the same as the user
# since PumpPortal doesn't distinguish between them
creator = user
@@ -117,10 +117,7 @@ class PumpPortalEventProcessor:
Creator vault address
"""
derived_address, _ = Pubkey.find_program_address(
[
b"creator-vault",
bytes(creator)
],
[b"creator-vault", bytes(creator)],
PumpAddresses.PROGRAM,
)
return derived_address
return derived_address
+11 -7
View File
@@ -20,7 +20,11 @@ logger = get_logger(__name__)
class PumpPortalListener(BaseTokenListener):
"""PumpPortal listener for pump.fun token creation events."""
def __init__(self, pump_program: Pubkey, pumpportal_url: str = "wss://pumpportal.fun/api/data"):
def __init__(
self,
pump_program: Pubkey,
pumpportal_url: str = "wss://pumpportal.fun/api/data",
):
"""Initialize token listener.
Args:
@@ -82,7 +86,9 @@ class PumpPortalListener(BaseTokenListener):
await token_callback(token_info)
except websockets.exceptions.ConnectionClosed:
logger.warning("PumpPortal WebSocket connection closed. Reconnecting...")
logger.warning(
"PumpPortal WebSocket connection closed. Reconnecting..."
)
finally:
ping_task.cancel()
try:
@@ -94,16 +100,14 @@ class PumpPortalListener(BaseTokenListener):
logger.exception("PumpPortal WebSocket connection error")
logger.info("Reconnecting in 5 seconds...")
await asyncio.sleep(5)
async def _subscribe_to_new_tokens(self, websocket) -> None:
"""Subscribe to new token events from PumpPortal.
Args:
websocket: Active WebSocket connection
"""
subscription_message = json.dumps({
"method": "subscribeNewToken",
"params": []
})
subscription_message = json.dumps({"method": "subscribeNewToken", "params": []})
await websocket.send(subscription_message)
logger.info("Subscribed to PumpPortal new token events")
@@ -167,4 +171,4 @@ class PumpPortalListener(BaseTokenListener):
except Exception as e:
logger.error(f"Error processing PumpPortal WebSocket message: {e}")
return None
return None