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
+89
View File
@@ -0,0 +1,89 @@
---
description: Package management and command execution guidelines using uv. Apply when installing dependencies, running scripts, or managing the Python environment.
globs: "**/*"
alwaysApply: true
---
# Package Management with UV
## Overview
This project exclusively uses `uv` as the package manager for all Python operations. UV is a fast Python package installer and resolver, written in Rust, that serves as a drop-in replacement for pip and other Python package managers.
## Core Principles
1. **Use `uv` for all package operations** - Never use pip, pipenv, poetry, or conda
2. **Use `uv run` for script execution** - Ensures proper virtual environment activation
3. **Maintain consistency** - All documentation and examples should reference uv commands
## Package Installation
### Installing Dependencies
```bash
# Install all project dependencies
uv sync --extra dev
# Install additional packages
uv add package-name
# Install from requirements file
uv add -r requirements.txt
```
### Managing Virtual Environments
```bash
# Create virtual environment
uv venv
# Activate virtual environment (if needed manually)
source .venv/bin/activate
```
## Script Execution
### Running Bot Scripts
```bash
# Primary method: Use the installed command
pump_bot
# Alternative: Run bot_runner.py directly
uv run src/bot_runner.py
```
### Running Test Scripts
```bash
# Run listener performance tests
uv run tests/test_geyser_listener.py
uv run tests/test_logs_listener.py
uv run tests/test_block_listener.py
# Run comparative analysis
uv run tests/compare_listeners.py
uv run tests/compare_listeners.py 60
```
### Development and Testing
```bash
# Run linting
uv run ruff check src/
# Run formatting
uv run ruff format src/
```
## Project Structure Integration
The project is configured to work seamlessly with uv:
- `pyproject.toml` - Defines project dependencies and the `pump_bot` command
- `uv.lock` - Locks dependency versions for reproducible builds
- `.venv/` - Virtual environment managed by uv
## Environment Configuration
UV automatically detects and uses the virtual environment when running commands.
+8 -2
View File
@@ -20,6 +20,12 @@ The main codebase is organized as follows:
- @src/bot_runner.py: Entry point for bot execution
- @src/config_loader.py: Configuration loading and validation
### Package Command
The project provides a convenient command-line interface:
- `pump_bot`: Installed command that runs all enabled bots (defined in @pyproject.toml)
- Alternative: `uv run src/bot_runner.py` for direct script execution
#### Core Components
- @src/core/: Blockchain interaction and protocol abstractions
@@ -90,7 +96,7 @@ The main codebase is organized as follows:
## Development Flow
1. Configure bot parameters in @bots/*.yaml
2. Launch bot using @src/bot_runner.py
2. Launch bot using `uv run pump_bot` or `uv run src/bot_runner.py`
3. Monitor execution in @logs/
4. Review trade outcomes in @trades/trades.log
@@ -106,4 +112,4 @@ The main codebase is organized as follows:
- @README.md: Project overview and setup instructions
- @MAINTAINERS.md: Maintenance guidelines and procedures
- @CLAUDE.md: Integration with Claude AI for strategy development
- @CLAUDE.md: Development rules for Claude Code
+8 -8
View File
@@ -23,7 +23,7 @@ Each listener has a dedicated test script for standalone performance analysis:
```bash
# Run Geyser listener test for 30 seconds
python tests/test_geyser_listener.py
uv run tests/test_geyser_listener.py
```
### Logs Listener Test
@@ -35,7 +35,7 @@ python tests/test_geyser_listener.py
```bash
# Run logs listener test
python tests/test_logs_listener.py
uv run tests/test_logs_listener.py
```
### Block Listener Test
@@ -47,7 +47,7 @@ python tests/test_logs_listener.py
```bash
# Run block listener test
python tests/test_block_listener.py
uv run tests/test_block_listener.py
```
## Comparative Performance Analysis
@@ -62,10 +62,10 @@ python tests/test_block_listener.py
```bash
# Compare all listeners for 60 seconds
python tests/compare_listeners.py 60
uv run tests/compare_listeners.py 60
# Default 60-second comparison
python tests/compare_listeners.py
uv run tests/compare_listeners.py
```
### Performance Metrics
@@ -123,19 +123,19 @@ export GEYSER_API_TOKEN="your-api-token"
### Quick Performance Check
```bash
# 30-second comparison of all listeners
python tests/compare_listeners.py 30
uv run tests/compare_listeners.py 30
```
### Extended Performance Analysis
```bash
# 10-minute deep analysis
python tests/compare_listeners.py 600
uv run tests/compare_listeners.py 600
```
### Individual Listener Validation
```bash
# Test specific listener implementation
python tests/test_geyser_listener.py
uv run tests/test_geyser_listener.py
```
## Use Cases
+42 -37
View File
@@ -16,94 +16,99 @@ from utils.logger import setup_file_logging
def setup_logging(bot_name: str):
"""
Set up logging to file for a specific bot instance.
Args:
bot_name: Name of the bot for the log file
"""
log_dir = Path("logs")
log_dir.mkdir(exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_filename = log_dir / f"{bot_name}_{timestamp}.log"
setup_file_logging(str(log_filename))
async def start_bot(config_path: str):
"""
Start a trading bot with the configuration from the specified path.
Args:
config_path: Path to the YAML configuration file
"""
cfg = load_bot_config(config_path)
setup_logging(cfg["name"])
print_config_summary(cfg)
trader = PumpTrader(
# Connection settings
rpc_endpoint=cfg["rpc_endpoint"],
wss_endpoint=cfg["wss_endpoint"],
private_key=cfg["private_key"],
# Trade parameters
buy_amount=cfg["trade"]["buy_amount"],
buy_slippage=cfg["trade"]["buy_slippage"],
sell_slippage=cfg["trade"]["sell_slippage"],
# Extreme fast mode settings
extreme_fast_mode=cfg["trade"].get("extreme_fast_mode", False),
extreme_fast_token_amount=cfg["trade"].get("extreme_fast_token_amount", 30),
# Exit strategy configuration
exit_strategy=cfg["trade"].get("exit_strategy", "time_based"),
take_profit_percentage=cfg["trade"].get("take_profit_percentage"),
stop_loss_percentage=cfg["trade"].get("stop_loss_percentage"),
max_hold_time=cfg["trade"].get("max_hold_time"),
price_check_interval=cfg["trade"].get("price_check_interval", 10),
# Listener configuration
listener_type=cfg["filters"]["listener_type"],
# Geyser configuration (if applicable)
geyser_endpoint=cfg.get("geyser", {}).get("endpoint"),
geyser_api_token=cfg.get("geyser", {}).get("api_token"),
geyser_auth_type=cfg.get("geyser", {}).get("auth_type"),
# PumpPortal configuration (if applicable)
pumpportal_url=cfg.get("pumpportal", {}).get("url", "wss://pumpportal.fun/api/data"),
pumpportal_url=cfg.get("pumpportal", {}).get(
"url", "wss://pumpportal.fun/api/data"
),
# Priority fee configuration
enable_dynamic_priority_fee=cfg.get("priority_fees", {}).get("enable_dynamic", False),
enable_fixed_priority_fee=cfg.get("priority_fees", {}).get("enable_fixed", True),
enable_dynamic_priority_fee=cfg.get("priority_fees", {}).get(
"enable_dynamic", False
),
enable_fixed_priority_fee=cfg.get("priority_fees", {}).get(
"enable_fixed", True
),
fixed_priority_fee=cfg.get("priority_fees", {}).get("fixed_amount", 500000),
extra_priority_fee=cfg.get("priority_fees", {}).get("extra_percentage", 0.0),
hard_cap_prior_fee=cfg.get("priority_fees", {}).get("hard_cap", 500000),
# Retry and timeout settings
max_retries=cfg.get("retries", {}).get("max_attempts", 10),
wait_time_after_creation=cfg.get("retries", {}).get("wait_after_creation", 15),
wait_time_after_buy=cfg.get("retries", {}).get("wait_after_buy", 15),
wait_time_before_new_token=cfg.get("retries", {}).get("wait_before_new_token", 15),
wait_time_before_new_token=cfg.get("retries", {}).get(
"wait_before_new_token", 15
),
max_token_age=cfg.get("timing", {}).get("max_token_age", 0.001),
token_wait_timeout=cfg.get("timing", {}).get("token_wait_timeout", 30),
# Cleanup settings
cleanup_mode=cfg.get("cleanup", {}).get("mode", "disabled"),
cleanup_force_close_with_burn=cfg.get("cleanup", {}).get("force_close_with_burn", False),
cleanup_with_priority_fee=cfg.get("cleanup", {}).get("with_priority_fee", False),
cleanup_force_close_with_burn=cfg.get("cleanup", {}).get(
"force_close_with_burn", False
),
cleanup_with_priority_fee=cfg.get("cleanup", {}).get(
"with_priority_fee", False
),
# Trading filters
match_string=cfg["filters"].get("match_string"),
bro_address=cfg["filters"].get("bro_address"),
marry_mode=cfg["filters"].get("marry_mode", False),
yolo_mode=cfg["filters"].get("yolo_mode", False),
)
await trader.start()
def run_bot_process(config_path):
asyncio.run(start_bot(config_path))
def run_all_bots():
"""
Run all bots defined in YAML files in the 'bots' directory.
@@ -114,22 +119,22 @@ def run_all_bots():
if not bot_dir.exists():
logging.error(f"Bot directory '{bot_dir}' not found")
return
bot_files = list(bot_dir.glob("*.yaml"))
if not bot_files:
logging.error(f"No bot configuration files found in '{bot_dir}'")
return
logging.info(f"Found {len(bot_files)} bot configuration files")
processes = []
skipped_bots = 0
for file in bot_files:
try:
cfg = load_bot_config(str(file))
bot_name = cfg.get("name", file.stem)
# Skip bots with enabled=False
if not cfg.get("enabled", True):
logging.info(f"Skipping disabled bot '{bot_name}'")
@@ -139,20 +144,20 @@ def run_all_bots():
if cfg.get("separate_process", False):
logging.info(f"Starting bot '{bot_name}' in separate process")
p = multiprocessing.Process(
target=run_bot_process,
args=(str(file),),
name=f"bot-{bot_name}"
target=run_bot_process, args=(str(file),), name=f"bot-{bot_name}"
)
p.start()
processes.append(p)
else:
logging.info(f"Starting bot '{bot_name}' in main process")
asyncio.run(start_bot(str(file)))
asyncio.run(start_bot(str(file)))
except Exception as e:
logging.exception(f"Failed to start bot from {file}: {e}")
logging.info(f"Started {len(bot_files) - skipped_bots} bots, skipped {skipped_bots} disabled bots")
logging.info(
f"Started {len(bot_files) - skipped_bots} bots, skipped {skipped_bots} disabled bots"
)
for p in processes:
p.join()
logging.info(f"Process {p.name} completed")
@@ -161,11 +166,11 @@ def run_all_bots():
def main() -> None:
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
run_all_bots()
if __name__ == "__main__":
main()
main()
+4 -1
View File
@@ -14,6 +14,7 @@ logger = get_logger(__name__)
class AccountCleanupManager:
"""Handles safe cleanup of token accounts (ATA) after trading sessions."""
def __init__(
self,
client: SolanaClient,
@@ -60,7 +61,9 @@ class AccountCleanupManager:
instructions = []
if balance > 0 and self.close_with_force_burn:
logger.info(f"Burning {balance} tokens from ATA {ata} (mint: {mint})...")
logger.info(
f"Burning {balance} tokens from ATA {ata} (mint: {mint})..."
)
burn_ix = burn(
BurnParams(
account=ata,
+35 -9
View File
@@ -17,26 +17,52 @@ def should_cleanup_post_session(cleanup_mode) -> bool:
async def handle_cleanup_after_failure(
client, wallet, mint, priority_fee_manager, cleanup_mode, cleanup_with_prior_fee, force_burn
):
client,
wallet,
mint,
priority_fee_manager,
cleanup_mode,
cleanup_with_prior_fee,
force_burn,
):
if should_cleanup_after_failure(cleanup_mode):
logger.info("[Cleanup] Triggered by failed buy transaction.")
manager = AccountCleanupManager(client, wallet, priority_fee_manager, cleanup_with_prior_fee, force_burn)
manager = AccountCleanupManager(
client, wallet, priority_fee_manager, cleanup_with_prior_fee, force_burn
)
await manager.cleanup_ata(mint)
async def handle_cleanup_after_sell(
client, wallet, mint, priority_fee_manager, cleanup_mode, cleanup_with_prior_fee, force_burn
):
client,
wallet,
mint,
priority_fee_manager,
cleanup_mode,
cleanup_with_prior_fee,
force_burn,
):
if should_cleanup_after_sell(cleanup_mode):
logger.info("[Cleanup] Triggered after token sell.")
manager = AccountCleanupManager(client, wallet, priority_fee_manager, cleanup_with_prior_fee, force_burn)
manager = AccountCleanupManager(
client, wallet, priority_fee_manager, cleanup_with_prior_fee, force_burn
)
await manager.cleanup_ata(mint)
async def handle_cleanup_post_session(
client, wallet, mints, priority_fee_manager, cleanup_mode, cleanup_with_prior_fee, force_burn
):
client,
wallet,
mints,
priority_fee_manager,
cleanup_mode,
cleanup_with_prior_fee,
force_burn,
):
if should_cleanup_post_session(cleanup_mode):
logger.info("[Cleanup] Triggered post trading session.")
manager = AccountCleanupManager(client, wallet, priority_fee_manager, cleanup_with_prior_fee, force_burn)
manager = AccountCleanupManager(
client, wallet, priority_fee_manager, cleanup_with_prior_fee, force_burn
)
for mint in mints:
await manager.cleanup_ata(mint)
+98 -43
View File
@@ -5,48 +5,90 @@ import yaml
from dotenv import load_dotenv
REQUIRED_FIELDS = [
"name", "rpc_endpoint", "wss_endpoint", "private_key",
"trade.buy_amount", "trade.buy_slippage", "trade.sell_slippage",
"filters.listener_type", "filters.max_token_age"
"name",
"rpc_endpoint",
"wss_endpoint",
"private_key",
"trade.buy_amount",
"trade.buy_slippage",
"trade.sell_slippage",
"filters.listener_type",
"filters.max_token_age",
]
CONFIG_VALIDATION_RULES = [
# (path, type, min_value, max_value, error_message)
("trade.buy_amount", (int, float), 0, float('inf'), "trade.buy_amount must be a positive number"),
(
"trade.buy_amount",
(int, float),
0,
float("inf"),
"trade.buy_amount must be a positive number",
),
("trade.buy_slippage", float, 0, 1, "trade.buy_slippage must be between 0 and 1"),
("trade.sell_slippage", float, 0, 1, "trade.sell_slippage must be between 0 and 1"),
("priority_fees.fixed_amount", int, 0, float('inf'), "priority_fees.fixed_amount must be a non-negative integer"),
("priority_fees.extra_percentage", float, 0, 1, "priority_fees.extra_percentage must be between 0 and 1"),
("priority_fees.hard_cap", int, 0, float('inf'), "priority_fees.hard_cap must be a non-negative integer"),
("retries.max_attempts", int, 0, 100, "retries.max_attempts must be between 0 and 100"),
("filters.max_token_age", (int, float), 0, float('inf'), "filters.max_token_age must be a non-negative number")
(
"priority_fees.fixed_amount",
int,
0,
float("inf"),
"priority_fees.fixed_amount must be a non-negative integer",
),
(
"priority_fees.extra_percentage",
float,
0,
1,
"priority_fees.extra_percentage must be between 0 and 1",
),
(
"priority_fees.hard_cap",
int,
0,
float("inf"),
"priority_fees.hard_cap must be a non-negative integer",
),
(
"retries.max_attempts",
int,
0,
100,
"retries.max_attempts must be between 0 and 100",
),
(
"filters.max_token_age",
(int, float),
0,
float("inf"),
"filters.max_token_age must be a non-negative number",
),
]
# Valid values for enum-like fields
VALID_VALUES = {
"filters.listener_type": ["logs", "blocks", "geyser", "pumpportal"],
"cleanup.mode": ["disabled", "on_fail", "after_sell", "post_session"],
"trade.exit_strategy": ["time_based", "tp_sl", "manual"]
"trade.exit_strategy": ["time_based", "tp_sl", "manual"],
}
def load_bot_config(path: str) -> dict:
"""
Load and validate a bot configuration from a YAML file.
Args:
path: Path to the YAML configuration file (relative or absolute)
Returns:
Validated configuration dictionary
Raises:
FileNotFoundError: If the configuration file doesn't exist
ValueError: If the configuration is invalid
"""
with open(path) as f:
config = yaml.safe_load(f)
env_file = config.get("env_file")
if env_file:
env_path = os.path.join(os.path.dirname(path), env_file)
@@ -55,19 +97,21 @@ def load_bot_config(path: str) -> dict:
else:
# If not found relative to config, try relative to current working directory
load_dotenv(env_file, override=True)
resolve_env_vars(config)
validate_config(config)
return config
def resolve_env_vars(config: dict) -> None:
"""
Recursively resolve environment variables in the configuration.
Args:
config: Configuration dictionary to process
"""
def resolve_env(value):
if isinstance(value, str) and value.startswith("${") and value.endswith("}"):
env_var = value[2:-1]
@@ -76,27 +120,28 @@ def resolve_env_vars(config: dict) -> None:
raise ValueError(f"Environment variable '{env_var}' not found")
return env_value
return value
def resolve_all(d):
for k, v in d.items():
if isinstance(v, dict):
resolve_all(v)
else:
d[k] = resolve_env(v)
resolve_all(config)
def get_nested_value(config: dict, path: str) -> Any:
"""
Get a nested value from the configuration using dot notation.
Args:
config: Configuration dictionary
path: Path to the value using dot notation (e.g., "trade.buy_amount")
Returns:
The value at the specified path
Raises:
ValueError: If the path doesn't exist in the configuration
"""
@@ -108,36 +153,37 @@ def get_nested_value(config: dict, path: str) -> Any:
value = value[key]
return value
def validate_config(config: dict) -> None:
"""
Validate the configuration against defined rules.
Args:
config: Configuration dictionary to validate
Raises:
ValueError: If the configuration is invalid
"""
for field in REQUIRED_FIELDS:
get_nested_value(config, field)
for path, expected_type, min_val, max_val, error_msg in CONFIG_VALIDATION_RULES:
try:
value = get_nested_value(config, path)
if not isinstance(value, expected_type):
raise ValueError(f"Type error: {error_msg}")
if isinstance(value, (int, float)) and not (min_val <= value <= max_val):
raise ValueError(f"Range error: {error_msg}")
except ValueError as e:
# Re-raise if it's our own error
if str(e).startswith(("Type error:", "Range error:")):
raise
# Otherwise, the field might be missing
continue
# Validate enum-like fields
for path, valid_values in VALID_VALUES.items():
try:
@@ -147,43 +193,52 @@ def validate_config(config: dict) -> None:
except ValueError:
# Skip if the field is missing
continue
# Cannot enable both dynamic and fixed priority fees
try:
dynamic = get_nested_value(config, "priority_fees.enable_dynamic")
fixed = get_nested_value(config, "priority_fees.enable_fixed")
if dynamic and fixed:
raise ValueError("Cannot enable both dynamic and fixed priority fees simultaneously")
raise ValueError(
"Cannot enable both dynamic and fixed priority fees simultaneously"
)
except ValueError:
# Skip if one of the fields is missing
pass
def print_config_summary(config: dict) -> None:
"""
Print a summary of the loaded configuration.
Args:
config: Configuration dictionary
"""
print(f"Bot name: {config.get('name', 'unnamed')}")
print(f"Listener type: {config.get('filters', {}).get('listener_type', 'not configured')}")
trade = config.get('trade', {})
print(
f"Listener type: {config.get('filters', {}).get('listener_type', 'not configured')}"
)
trade = config.get("trade", {})
print("Trade settings:")
print(f" - Buy amount: {trade.get('buy_amount', 'not configured')} SOL")
print(f" - Buy slippage: {trade.get('buy_slippage', 'not configured') * 100}%")
print(f" - Extreme fast mode: {'enabled' if trade.get('extreme_fast_mode') else 'disabled'}")
fees = config.get('priority_fees', {})
print(
f" - Extreme fast mode: {'enabled' if trade.get('extreme_fast_mode') else 'disabled'}"
)
fees = config.get("priority_fees", {})
print("Priority fees:")
if fees.get('enable_dynamic'):
if fees.get("enable_dynamic"):
print(" - Dynamic fees enabled")
elif fees.get('enable_fixed'):
print(f" - Fixed fee: {fees.get('fixed_amount', 'not configured')} microlamports")
elif fees.get("enable_fixed"):
print(
f" - Fixed fee: {fees.get('fixed_amount', 'not configured')} microlamports"
)
print("Configuration loaded successfully!")
if __name__ == "__main__":
config = load_bot_config("bots/bot-sniper.yaml")
print_config_summary(config)
print_config_summary(config)
+9 -3
View File
@@ -36,7 +36,9 @@ class SolanaClient:
self._client = None
self._cached_blockhash: Hash | None = None
self._blockhash_lock = asyncio.Lock()
self._blockhash_updater_task = asyncio.create_task(self.start_blockhash_updater())
self._blockhash_updater_task = asyncio.create_task(
self.start_blockhash_updater()
)
async def start_blockhash_updater(self, interval: float = 5.0):
"""Start background task to update recent blockhash."""
@@ -104,7 +106,9 @@ class SolanaClient:
ValueError: If account doesn't exist or has no data
"""
client = await self.get_client()
response = await client.get_account_info(pubkey, encoding="base64") # base64 encoding for account data by default
response = await client.get_account_info(
pubkey, encoding="base64"
) # base64 encoding for account data by default
if not response.value:
raise ValueError(f"Account {pubkey} not found")
return response.value
@@ -207,7 +211,9 @@ class SolanaClient:
"""
client = await self.get_client()
try:
await client.confirm_transaction(signature, commitment=commitment, sleep_seconds=1)
await client.confirm_transaction(
signature, commitment=commitment, sleep_seconds=1
)
return True
except Exception as e:
logger.error(f"Failed to confirm transaction {signature}: {e!s}")
+2 -2
View File
@@ -45,9 +45,9 @@ class BondingCurveState:
parsed = self._STRUCT.parse(data[8:])
self.__dict__.update(parsed)
# Convert raw bytes to Pubkey for creator field
if hasattr(self, 'creator') and isinstance(self.creator, bytes):
if hasattr(self, "creator") and isinstance(self.creator, bytes):
self.creator = Pubkey.from_bytes(self.creator)
def calculate_price(self) -> float:
File diff suppressed because one or more lines are too long
+348 -42
View File
@@ -3,7 +3,13 @@ from google.protobuf.internal import containers as _containers
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
from typing import (
ClassVar as _ClassVar,
Iterable as _Iterable,
Mapping as _Mapping,
Optional as _Optional,
Union as _Union,
)
from solana_storage_pb2 import ConfirmedBlock as ConfirmedBlock
from solana_storage_pb2 import ConfirmedTransaction as ConfirmedTransaction
from solana_storage_pb2 import Transaction as Transaction
@@ -41,6 +47,7 @@ class CommitmentLevel(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
COMPLETED: _ClassVar[CommitmentLevel]
CREATED_BANK: _ClassVar[CommitmentLevel]
DEAD: _ClassVar[CommitmentLevel]
PROCESSED: CommitmentLevel
CONFIRMED: CommitmentLevel
FINALIZED: CommitmentLevel
@@ -50,56 +57,106 @@ CREATED_BANK: CommitmentLevel
DEAD: CommitmentLevel
class SubscribeRequest(_message.Message):
__slots__ = ("accounts", "slots", "transactions", "transactions_status", "blocks", "blocks_meta", "entry", "commitment", "accounts_data_slice", "ping")
__slots__ = (
"accounts",
"slots",
"transactions",
"transactions_status",
"blocks",
"blocks_meta",
"entry",
"commitment",
"accounts_data_slice",
"ping",
)
class AccountsEntry(_message.Message):
__slots__ = ("key", "value")
KEY_FIELD_NUMBER: _ClassVar[int]
VALUE_FIELD_NUMBER: _ClassVar[int]
key: str
value: SubscribeRequestFilterAccounts
def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[SubscribeRequestFilterAccounts, _Mapping]] = ...) -> None: ...
def __init__(
self,
key: _Optional[str] = ...,
value: _Optional[_Union[SubscribeRequestFilterAccounts, _Mapping]] = ...,
) -> None: ...
class SlotsEntry(_message.Message):
__slots__ = ("key", "value")
KEY_FIELD_NUMBER: _ClassVar[int]
VALUE_FIELD_NUMBER: _ClassVar[int]
key: str
value: SubscribeRequestFilterSlots
def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[SubscribeRequestFilterSlots, _Mapping]] = ...) -> None: ...
def __init__(
self,
key: _Optional[str] = ...,
value: _Optional[_Union[SubscribeRequestFilterSlots, _Mapping]] = ...,
) -> None: ...
class TransactionsEntry(_message.Message):
__slots__ = ("key", "value")
KEY_FIELD_NUMBER: _ClassVar[int]
VALUE_FIELD_NUMBER: _ClassVar[int]
key: str
value: SubscribeRequestFilterTransactions
def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[SubscribeRequestFilterTransactions, _Mapping]] = ...) -> None: ...
def __init__(
self,
key: _Optional[str] = ...,
value: _Optional[
_Union[SubscribeRequestFilterTransactions, _Mapping]
] = ...,
) -> None: ...
class TransactionsStatusEntry(_message.Message):
__slots__ = ("key", "value")
KEY_FIELD_NUMBER: _ClassVar[int]
VALUE_FIELD_NUMBER: _ClassVar[int]
key: str
value: SubscribeRequestFilterTransactions
def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[SubscribeRequestFilterTransactions, _Mapping]] = ...) -> None: ...
def __init__(
self,
key: _Optional[str] = ...,
value: _Optional[
_Union[SubscribeRequestFilterTransactions, _Mapping]
] = ...,
) -> None: ...
class BlocksEntry(_message.Message):
__slots__ = ("key", "value")
KEY_FIELD_NUMBER: _ClassVar[int]
VALUE_FIELD_NUMBER: _ClassVar[int]
key: str
value: SubscribeRequestFilterBlocks
def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[SubscribeRequestFilterBlocks, _Mapping]] = ...) -> None: ...
def __init__(
self,
key: _Optional[str] = ...,
value: _Optional[_Union[SubscribeRequestFilterBlocks, _Mapping]] = ...,
) -> None: ...
class BlocksMetaEntry(_message.Message):
__slots__ = ("key", "value")
KEY_FIELD_NUMBER: _ClassVar[int]
VALUE_FIELD_NUMBER: _ClassVar[int]
key: str
value: SubscribeRequestFilterBlocksMeta
def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[SubscribeRequestFilterBlocksMeta, _Mapping]] = ...) -> None: ...
def __init__(
self,
key: _Optional[str] = ...,
value: _Optional[_Union[SubscribeRequestFilterBlocksMeta, _Mapping]] = ...,
) -> None: ...
class EntryEntry(_message.Message):
__slots__ = ("key", "value")
KEY_FIELD_NUMBER: _ClassVar[int]
VALUE_FIELD_NUMBER: _ClassVar[int]
key: str
value: SubscribeRequestFilterEntry
def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[SubscribeRequestFilterEntry, _Mapping]] = ...) -> None: ...
def __init__(
self,
key: _Optional[str] = ...,
value: _Optional[_Union[SubscribeRequestFilterEntry, _Mapping]] = ...,
) -> None: ...
ACCOUNTS_FIELD_NUMBER: _ClassVar[int]
SLOTS_FIELD_NUMBER: _ClassVar[int]
TRANSACTIONS_FIELD_NUMBER: _ClassVar[int]
@@ -118,9 +175,29 @@ class SubscribeRequest(_message.Message):
blocks_meta: _containers.MessageMap[str, SubscribeRequestFilterBlocksMeta]
entry: _containers.MessageMap[str, SubscribeRequestFilterEntry]
commitment: CommitmentLevel
accounts_data_slice: _containers.RepeatedCompositeFieldContainer[SubscribeRequestAccountsDataSlice]
accounts_data_slice: _containers.RepeatedCompositeFieldContainer[
SubscribeRequestAccountsDataSlice
]
ping: SubscribeRequestPing
def __init__(self, accounts: _Optional[_Mapping[str, SubscribeRequestFilterAccounts]] = ..., slots: _Optional[_Mapping[str, SubscribeRequestFilterSlots]] = ..., transactions: _Optional[_Mapping[str, SubscribeRequestFilterTransactions]] = ..., transactions_status: _Optional[_Mapping[str, SubscribeRequestFilterTransactions]] = ..., blocks: _Optional[_Mapping[str, SubscribeRequestFilterBlocks]] = ..., blocks_meta: _Optional[_Mapping[str, SubscribeRequestFilterBlocksMeta]] = ..., entry: _Optional[_Mapping[str, SubscribeRequestFilterEntry]] = ..., commitment: _Optional[_Union[CommitmentLevel, str]] = ..., accounts_data_slice: _Optional[_Iterable[_Union[SubscribeRequestAccountsDataSlice, _Mapping]]] = ..., ping: _Optional[_Union[SubscribeRequestPing, _Mapping]] = ...) -> None: ...
def __init__(
self,
accounts: _Optional[_Mapping[str, SubscribeRequestFilterAccounts]] = ...,
slots: _Optional[_Mapping[str, SubscribeRequestFilterSlots]] = ...,
transactions: _Optional[
_Mapping[str, SubscribeRequestFilterTransactions]
] = ...,
transactions_status: _Optional[
_Mapping[str, SubscribeRequestFilterTransactions]
] = ...,
blocks: _Optional[_Mapping[str, SubscribeRequestFilterBlocks]] = ...,
blocks_meta: _Optional[_Mapping[str, SubscribeRequestFilterBlocksMeta]] = ...,
entry: _Optional[_Mapping[str, SubscribeRequestFilterEntry]] = ...,
commitment: _Optional[_Union[CommitmentLevel, str]] = ...,
accounts_data_slice: _Optional[
_Iterable[_Union[SubscribeRequestAccountsDataSlice, _Mapping]]
] = ...,
ping: _Optional[_Union[SubscribeRequestPing, _Mapping]] = ...,
) -> None: ...
class SubscribeRequestFilterAccounts(_message.Message):
__slots__ = ("account", "owner", "filters", "nonempty_txn_signature")
@@ -130,9 +207,19 @@ class SubscribeRequestFilterAccounts(_message.Message):
NONEMPTY_TXN_SIGNATURE_FIELD_NUMBER: _ClassVar[int]
account: _containers.RepeatedScalarFieldContainer[str]
owner: _containers.RepeatedScalarFieldContainer[str]
filters: _containers.RepeatedCompositeFieldContainer[SubscribeRequestFilterAccountsFilter]
filters: _containers.RepeatedCompositeFieldContainer[
SubscribeRequestFilterAccountsFilter
]
nonempty_txn_signature: bool
def __init__(self, account: _Optional[_Iterable[str]] = ..., owner: _Optional[_Iterable[str]] = ..., filters: _Optional[_Iterable[_Union[SubscribeRequestFilterAccountsFilter, _Mapping]]] = ..., nonempty_txn_signature: bool = ...) -> None: ...
def __init__(
self,
account: _Optional[_Iterable[str]] = ...,
owner: _Optional[_Iterable[str]] = ...,
filters: _Optional[
_Iterable[_Union[SubscribeRequestFilterAccountsFilter, _Mapping]]
] = ...,
nonempty_txn_signature: bool = ...,
) -> None: ...
class SubscribeRequestFilterAccountsFilter(_message.Message):
__slots__ = ("memcmp", "datasize", "token_account_state", "lamports")
@@ -144,7 +231,17 @@ class SubscribeRequestFilterAccountsFilter(_message.Message):
datasize: int
token_account_state: bool
lamports: SubscribeRequestFilterAccountsFilterLamports
def __init__(self, memcmp: _Optional[_Union[SubscribeRequestFilterAccountsFilterMemcmp, _Mapping]] = ..., datasize: _Optional[int] = ..., token_account_state: bool = ..., lamports: _Optional[_Union[SubscribeRequestFilterAccountsFilterLamports, _Mapping]] = ...) -> None: ...
def __init__(
self,
memcmp: _Optional[
_Union[SubscribeRequestFilterAccountsFilterMemcmp, _Mapping]
] = ...,
datasize: _Optional[int] = ...,
token_account_state: bool = ...,
lamports: _Optional[
_Union[SubscribeRequestFilterAccountsFilterLamports, _Mapping]
] = ...,
) -> None: ...
class SubscribeRequestFilterAccountsFilterMemcmp(_message.Message):
__slots__ = ("offset", "bytes", "base58", "base64")
@@ -156,7 +253,13 @@ class SubscribeRequestFilterAccountsFilterMemcmp(_message.Message):
bytes: bytes
base58: str
base64: str
def __init__(self, offset: _Optional[int] = ..., bytes: _Optional[bytes] = ..., base58: _Optional[str] = ..., base64: _Optional[str] = ...) -> None: ...
def __init__(
self,
offset: _Optional[int] = ...,
bytes: _Optional[bytes] = ...,
base58: _Optional[str] = ...,
base64: _Optional[str] = ...,
) -> None: ...
class SubscribeRequestFilterAccountsFilterLamports(_message.Message):
__slots__ = ("eq", "ne", "lt", "gt")
@@ -168,7 +271,13 @@ class SubscribeRequestFilterAccountsFilterLamports(_message.Message):
ne: int
lt: int
gt: int
def __init__(self, eq: _Optional[int] = ..., ne: _Optional[int] = ..., lt: _Optional[int] = ..., gt: _Optional[int] = ...) -> None: ...
def __init__(
self,
eq: _Optional[int] = ...,
ne: _Optional[int] = ...,
lt: _Optional[int] = ...,
gt: _Optional[int] = ...,
) -> None: ...
class SubscribeRequestFilterSlots(_message.Message):
__slots__ = ("filter_by_commitment",)
@@ -177,7 +286,14 @@ class SubscribeRequestFilterSlots(_message.Message):
def __init__(self, filter_by_commitment: bool = ...) -> None: ...
class SubscribeRequestFilterTransactions(_message.Message):
__slots__ = ("vote", "failed", "signature", "account_include", "account_exclude", "account_required")
__slots__ = (
"vote",
"failed",
"signature",
"account_include",
"account_exclude",
"account_required",
)
VOTE_FIELD_NUMBER: _ClassVar[int]
FAILED_FIELD_NUMBER: _ClassVar[int]
SIGNATURE_FIELD_NUMBER: _ClassVar[int]
@@ -190,10 +306,23 @@ class SubscribeRequestFilterTransactions(_message.Message):
account_include: _containers.RepeatedScalarFieldContainer[str]
account_exclude: _containers.RepeatedScalarFieldContainer[str]
account_required: _containers.RepeatedScalarFieldContainer[str]
def __init__(self, vote: bool = ..., failed: bool = ..., signature: _Optional[str] = ..., account_include: _Optional[_Iterable[str]] = ..., account_exclude: _Optional[_Iterable[str]] = ..., account_required: _Optional[_Iterable[str]] = ...) -> None: ...
def __init__(
self,
vote: bool = ...,
failed: bool = ...,
signature: _Optional[str] = ...,
account_include: _Optional[_Iterable[str]] = ...,
account_exclude: _Optional[_Iterable[str]] = ...,
account_required: _Optional[_Iterable[str]] = ...,
) -> None: ...
class SubscribeRequestFilterBlocks(_message.Message):
__slots__ = ("account_include", "include_transactions", "include_accounts", "include_entries")
__slots__ = (
"account_include",
"include_transactions",
"include_accounts",
"include_entries",
)
ACCOUNT_INCLUDE_FIELD_NUMBER: _ClassVar[int]
INCLUDE_TRANSACTIONS_FIELD_NUMBER: _ClassVar[int]
INCLUDE_ACCOUNTS_FIELD_NUMBER: _ClassVar[int]
@@ -202,7 +331,13 @@ class SubscribeRequestFilterBlocks(_message.Message):
include_transactions: bool
include_accounts: bool
include_entries: bool
def __init__(self, account_include: _Optional[_Iterable[str]] = ..., include_transactions: bool = ..., include_accounts: bool = ..., include_entries: bool = ...) -> None: ...
def __init__(
self,
account_include: _Optional[_Iterable[str]] = ...,
include_transactions: bool = ...,
include_accounts: bool = ...,
include_entries: bool = ...,
) -> None: ...
class SubscribeRequestFilterBlocksMeta(_message.Message):
__slots__ = ()
@@ -218,7 +353,9 @@ class SubscribeRequestAccountsDataSlice(_message.Message):
LENGTH_FIELD_NUMBER: _ClassVar[int]
offset: int
length: int
def __init__(self, offset: _Optional[int] = ..., length: _Optional[int] = ...) -> None: ...
def __init__(
self, offset: _Optional[int] = ..., length: _Optional[int] = ...
) -> None: ...
class SubscribeRequestPing(_message.Message):
__slots__ = ("id",)
@@ -227,7 +364,18 @@ class SubscribeRequestPing(_message.Message):
def __init__(self, id: _Optional[int] = ...) -> None: ...
class SubscribeUpdate(_message.Message):
__slots__ = ("filters", "account", "slot", "transaction", "transaction_status", "block", "ping", "pong", "block_meta", "entry")
__slots__ = (
"filters",
"account",
"slot",
"transaction",
"transaction_status",
"block",
"ping",
"pong",
"block_meta",
"entry",
)
FILTERS_FIELD_NUMBER: _ClassVar[int]
ACCOUNT_FIELD_NUMBER: _ClassVar[int]
SLOT_FIELD_NUMBER: _ClassVar[int]
@@ -248,7 +396,21 @@ class SubscribeUpdate(_message.Message):
pong: SubscribeUpdatePong
block_meta: SubscribeUpdateBlockMeta
entry: SubscribeUpdateEntry
def __init__(self, filters: _Optional[_Iterable[str]] = ..., account: _Optional[_Union[SubscribeUpdateAccount, _Mapping]] = ..., slot: _Optional[_Union[SubscribeUpdateSlot, _Mapping]] = ..., transaction: _Optional[_Union[SubscribeUpdateTransaction, _Mapping]] = ..., transaction_status: _Optional[_Union[SubscribeUpdateTransactionStatus, _Mapping]] = ..., block: _Optional[_Union[SubscribeUpdateBlock, _Mapping]] = ..., ping: _Optional[_Union[SubscribeUpdatePing, _Mapping]] = ..., pong: _Optional[_Union[SubscribeUpdatePong, _Mapping]] = ..., block_meta: _Optional[_Union[SubscribeUpdateBlockMeta, _Mapping]] = ..., entry: _Optional[_Union[SubscribeUpdateEntry, _Mapping]] = ...) -> None: ...
def __init__(
self,
filters: _Optional[_Iterable[str]] = ...,
account: _Optional[_Union[SubscribeUpdateAccount, _Mapping]] = ...,
slot: _Optional[_Union[SubscribeUpdateSlot, _Mapping]] = ...,
transaction: _Optional[_Union[SubscribeUpdateTransaction, _Mapping]] = ...,
transaction_status: _Optional[
_Union[SubscribeUpdateTransactionStatus, _Mapping]
] = ...,
block: _Optional[_Union[SubscribeUpdateBlock, _Mapping]] = ...,
ping: _Optional[_Union[SubscribeUpdatePing, _Mapping]] = ...,
pong: _Optional[_Union[SubscribeUpdatePong, _Mapping]] = ...,
block_meta: _Optional[_Union[SubscribeUpdateBlockMeta, _Mapping]] = ...,
entry: _Optional[_Union[SubscribeUpdateEntry, _Mapping]] = ...,
) -> None: ...
class SubscribeUpdateAccount(_message.Message):
__slots__ = ("account", "slot", "is_startup")
@@ -258,10 +420,24 @@ class SubscribeUpdateAccount(_message.Message):
account: SubscribeUpdateAccountInfo
slot: int
is_startup: bool
def __init__(self, account: _Optional[_Union[SubscribeUpdateAccountInfo, _Mapping]] = ..., slot: _Optional[int] = ..., is_startup: bool = ...) -> None: ...
def __init__(
self,
account: _Optional[_Union[SubscribeUpdateAccountInfo, _Mapping]] = ...,
slot: _Optional[int] = ...,
is_startup: bool = ...,
) -> None: ...
class SubscribeUpdateAccountInfo(_message.Message):
__slots__ = ("pubkey", "lamports", "owner", "executable", "rent_epoch", "data", "write_version", "txn_signature")
__slots__ = (
"pubkey",
"lamports",
"owner",
"executable",
"rent_epoch",
"data",
"write_version",
"txn_signature",
)
PUBKEY_FIELD_NUMBER: _ClassVar[int]
LAMPORTS_FIELD_NUMBER: _ClassVar[int]
OWNER_FIELD_NUMBER: _ClassVar[int]
@@ -278,7 +454,17 @@ class SubscribeUpdateAccountInfo(_message.Message):
data: bytes
write_version: int
txn_signature: bytes
def __init__(self, pubkey: _Optional[bytes] = ..., lamports: _Optional[int] = ..., owner: _Optional[bytes] = ..., executable: bool = ..., rent_epoch: _Optional[int] = ..., data: _Optional[bytes] = ..., write_version: _Optional[int] = ..., txn_signature: _Optional[bytes] = ...) -> None: ...
def __init__(
self,
pubkey: _Optional[bytes] = ...,
lamports: _Optional[int] = ...,
owner: _Optional[bytes] = ...,
executable: bool = ...,
rent_epoch: _Optional[int] = ...,
data: _Optional[bytes] = ...,
write_version: _Optional[int] = ...,
txn_signature: _Optional[bytes] = ...,
) -> None: ...
class SubscribeUpdateSlot(_message.Message):
__slots__ = ("slot", "parent", "status", "dead_error")
@@ -290,7 +476,13 @@ class SubscribeUpdateSlot(_message.Message):
parent: int
status: CommitmentLevel
dead_error: str
def __init__(self, slot: _Optional[int] = ..., parent: _Optional[int] = ..., status: _Optional[_Union[CommitmentLevel, str]] = ..., dead_error: _Optional[str] = ...) -> None: ...
def __init__(
self,
slot: _Optional[int] = ...,
parent: _Optional[int] = ...,
status: _Optional[_Union[CommitmentLevel, str]] = ...,
dead_error: _Optional[str] = ...,
) -> None: ...
class SubscribeUpdateTransaction(_message.Message):
__slots__ = ("transaction", "slot")
@@ -298,7 +490,11 @@ class SubscribeUpdateTransaction(_message.Message):
SLOT_FIELD_NUMBER: _ClassVar[int]
transaction: SubscribeUpdateTransactionInfo
slot: int
def __init__(self, transaction: _Optional[_Union[SubscribeUpdateTransactionInfo, _Mapping]] = ..., slot: _Optional[int] = ...) -> None: ...
def __init__(
self,
transaction: _Optional[_Union[SubscribeUpdateTransactionInfo, _Mapping]] = ...,
slot: _Optional[int] = ...,
) -> None: ...
class SubscribeUpdateTransactionInfo(_message.Message):
__slots__ = ("signature", "is_vote", "transaction", "meta", "index")
@@ -312,7 +508,16 @@ class SubscribeUpdateTransactionInfo(_message.Message):
transaction: _solana_storage_pb2.Transaction
meta: _solana_storage_pb2.TransactionStatusMeta
index: int
def __init__(self, signature: _Optional[bytes] = ..., is_vote: bool = ..., transaction: _Optional[_Union[_solana_storage_pb2.Transaction, _Mapping]] = ..., meta: _Optional[_Union[_solana_storage_pb2.TransactionStatusMeta, _Mapping]] = ..., index: _Optional[int] = ...) -> None: ...
def __init__(
self,
signature: _Optional[bytes] = ...,
is_vote: bool = ...,
transaction: _Optional[_Union[_solana_storage_pb2.Transaction, _Mapping]] = ...,
meta: _Optional[
_Union[_solana_storage_pb2.TransactionStatusMeta, _Mapping]
] = ...,
index: _Optional[int] = ...,
) -> None: ...
class SubscribeUpdateTransactionStatus(_message.Message):
__slots__ = ("slot", "signature", "is_vote", "index", "err")
@@ -326,10 +531,31 @@ class SubscribeUpdateTransactionStatus(_message.Message):
is_vote: bool
index: int
err: _solana_storage_pb2.TransactionError
def __init__(self, slot: _Optional[int] = ..., signature: _Optional[bytes] = ..., is_vote: bool = ..., index: _Optional[int] = ..., err: _Optional[_Union[_solana_storage_pb2.TransactionError, _Mapping]] = ...) -> None: ...
def __init__(
self,
slot: _Optional[int] = ...,
signature: _Optional[bytes] = ...,
is_vote: bool = ...,
index: _Optional[int] = ...,
err: _Optional[_Union[_solana_storage_pb2.TransactionError, _Mapping]] = ...,
) -> None: ...
class SubscribeUpdateBlock(_message.Message):
__slots__ = ("slot", "blockhash", "rewards", "block_time", "block_height", "parent_slot", "parent_blockhash", "executed_transaction_count", "transactions", "updated_account_count", "accounts", "entries_count", "entries")
__slots__ = (
"slot",
"blockhash",
"rewards",
"block_time",
"block_height",
"parent_slot",
"parent_blockhash",
"executed_transaction_count",
"transactions",
"updated_account_count",
"accounts",
"entries_count",
"entries",
)
SLOT_FIELD_NUMBER: _ClassVar[int]
BLOCKHASH_FIELD_NUMBER: _ClassVar[int]
REWARDS_FIELD_NUMBER: _ClassVar[int]
@@ -351,15 +577,50 @@ class SubscribeUpdateBlock(_message.Message):
parent_slot: int
parent_blockhash: str
executed_transaction_count: int
transactions: _containers.RepeatedCompositeFieldContainer[SubscribeUpdateTransactionInfo]
transactions: _containers.RepeatedCompositeFieldContainer[
SubscribeUpdateTransactionInfo
]
updated_account_count: int
accounts: _containers.RepeatedCompositeFieldContainer[SubscribeUpdateAccountInfo]
entries_count: int
entries: _containers.RepeatedCompositeFieldContainer[SubscribeUpdateEntry]
def __init__(self, slot: _Optional[int] = ..., blockhash: _Optional[str] = ..., rewards: _Optional[_Union[_solana_storage_pb2.Rewards, _Mapping]] = ..., block_time: _Optional[_Union[_solana_storage_pb2.UnixTimestamp, _Mapping]] = ..., block_height: _Optional[_Union[_solana_storage_pb2.BlockHeight, _Mapping]] = ..., parent_slot: _Optional[int] = ..., parent_blockhash: _Optional[str] = ..., executed_transaction_count: _Optional[int] = ..., transactions: _Optional[_Iterable[_Union[SubscribeUpdateTransactionInfo, _Mapping]]] = ..., updated_account_count: _Optional[int] = ..., accounts: _Optional[_Iterable[_Union[SubscribeUpdateAccountInfo, _Mapping]]] = ..., entries_count: _Optional[int] = ..., entries: _Optional[_Iterable[_Union[SubscribeUpdateEntry, _Mapping]]] = ...) -> None: ...
def __init__(
self,
slot: _Optional[int] = ...,
blockhash: _Optional[str] = ...,
rewards: _Optional[_Union[_solana_storage_pb2.Rewards, _Mapping]] = ...,
block_time: _Optional[
_Union[_solana_storage_pb2.UnixTimestamp, _Mapping]
] = ...,
block_height: _Optional[
_Union[_solana_storage_pb2.BlockHeight, _Mapping]
] = ...,
parent_slot: _Optional[int] = ...,
parent_blockhash: _Optional[str] = ...,
executed_transaction_count: _Optional[int] = ...,
transactions: _Optional[
_Iterable[_Union[SubscribeUpdateTransactionInfo, _Mapping]]
] = ...,
updated_account_count: _Optional[int] = ...,
accounts: _Optional[
_Iterable[_Union[SubscribeUpdateAccountInfo, _Mapping]]
] = ...,
entries_count: _Optional[int] = ...,
entries: _Optional[_Iterable[_Union[SubscribeUpdateEntry, _Mapping]]] = ...,
) -> None: ...
class SubscribeUpdateBlockMeta(_message.Message):
__slots__ = ("slot", "blockhash", "rewards", "block_time", "block_height", "parent_slot", "parent_blockhash", "executed_transaction_count", "entries_count")
__slots__ = (
"slot",
"blockhash",
"rewards",
"block_time",
"block_height",
"parent_slot",
"parent_blockhash",
"executed_transaction_count",
"entries_count",
)
SLOT_FIELD_NUMBER: _ClassVar[int]
BLOCKHASH_FIELD_NUMBER: _ClassVar[int]
REWARDS_FIELD_NUMBER: _ClassVar[int]
@@ -378,10 +639,32 @@ class SubscribeUpdateBlockMeta(_message.Message):
parent_blockhash: str
executed_transaction_count: int
entries_count: int
def __init__(self, slot: _Optional[int] = ..., blockhash: _Optional[str] = ..., rewards: _Optional[_Union[_solana_storage_pb2.Rewards, _Mapping]] = ..., block_time: _Optional[_Union[_solana_storage_pb2.UnixTimestamp, _Mapping]] = ..., block_height: _Optional[_Union[_solana_storage_pb2.BlockHeight, _Mapping]] = ..., parent_slot: _Optional[int] = ..., parent_blockhash: _Optional[str] = ..., executed_transaction_count: _Optional[int] = ..., entries_count: _Optional[int] = ...) -> None: ...
def __init__(
self,
slot: _Optional[int] = ...,
blockhash: _Optional[str] = ...,
rewards: _Optional[_Union[_solana_storage_pb2.Rewards, _Mapping]] = ...,
block_time: _Optional[
_Union[_solana_storage_pb2.UnixTimestamp, _Mapping]
] = ...,
block_height: _Optional[
_Union[_solana_storage_pb2.BlockHeight, _Mapping]
] = ...,
parent_slot: _Optional[int] = ...,
parent_blockhash: _Optional[str] = ...,
executed_transaction_count: _Optional[int] = ...,
entries_count: _Optional[int] = ...,
) -> None: ...
class SubscribeUpdateEntry(_message.Message):
__slots__ = ("slot", "index", "num_hashes", "hash", "executed_transaction_count", "starting_transaction_index")
__slots__ = (
"slot",
"index",
"num_hashes",
"hash",
"executed_transaction_count",
"starting_transaction_index",
)
SLOT_FIELD_NUMBER: _ClassVar[int]
INDEX_FIELD_NUMBER: _ClassVar[int]
NUM_HASHES_FIELD_NUMBER: _ClassVar[int]
@@ -394,7 +677,15 @@ class SubscribeUpdateEntry(_message.Message):
hash: bytes
executed_transaction_count: int
starting_transaction_index: int
def __init__(self, slot: _Optional[int] = ..., index: _Optional[int] = ..., num_hashes: _Optional[int] = ..., hash: _Optional[bytes] = ..., executed_transaction_count: _Optional[int] = ..., starting_transaction_index: _Optional[int] = ...) -> None: ...
def __init__(
self,
slot: _Optional[int] = ...,
index: _Optional[int] = ...,
num_hashes: _Optional[int] = ...,
hash: _Optional[bytes] = ...,
executed_transaction_count: _Optional[int] = ...,
starting_transaction_index: _Optional[int] = ...,
) -> None: ...
class SubscribeUpdatePing(_message.Message):
__slots__ = ()
@@ -422,7 +713,9 @@ class GetLatestBlockhashRequest(_message.Message):
__slots__ = ("commitment",)
COMMITMENT_FIELD_NUMBER: _ClassVar[int]
commitment: CommitmentLevel
def __init__(self, commitment: _Optional[_Union[CommitmentLevel, str]] = ...) -> None: ...
def __init__(
self, commitment: _Optional[_Union[CommitmentLevel, str]] = ...
) -> None: ...
class GetLatestBlockhashResponse(_message.Message):
__slots__ = ("slot", "blockhash", "last_valid_block_height")
@@ -432,13 +725,20 @@ class GetLatestBlockhashResponse(_message.Message):
slot: int
blockhash: str
last_valid_block_height: int
def __init__(self, slot: _Optional[int] = ..., blockhash: _Optional[str] = ..., last_valid_block_height: _Optional[int] = ...) -> None: ...
def __init__(
self,
slot: _Optional[int] = ...,
blockhash: _Optional[str] = ...,
last_valid_block_height: _Optional[int] = ...,
) -> None: ...
class GetBlockHeightRequest(_message.Message):
__slots__ = ("commitment",)
COMMITMENT_FIELD_NUMBER: _ClassVar[int]
commitment: CommitmentLevel
def __init__(self, commitment: _Optional[_Union[CommitmentLevel, str]] = ...) -> None: ...
def __init__(
self, commitment: _Optional[_Union[CommitmentLevel, str]] = ...
) -> None: ...
class GetBlockHeightResponse(_message.Message):
__slots__ = ("block_height",)
@@ -450,7 +750,9 @@ class GetSlotRequest(_message.Message):
__slots__ = ("commitment",)
COMMITMENT_FIELD_NUMBER: _ClassVar[int]
commitment: CommitmentLevel
def __init__(self, commitment: _Optional[_Union[CommitmentLevel, str]] = ...) -> None: ...
def __init__(
self, commitment: _Optional[_Union[CommitmentLevel, str]] = ...
) -> None: ...
class GetSlotResponse(_message.Message):
__slots__ = ("slot",)
@@ -474,7 +776,11 @@ class IsBlockhashValidRequest(_message.Message):
COMMITMENT_FIELD_NUMBER: _ClassVar[int]
blockhash: str
commitment: CommitmentLevel
def __init__(self, blockhash: _Optional[str] = ..., commitment: _Optional[_Union[CommitmentLevel, str]] = ...) -> None: ...
def __init__(
self,
blockhash: _Optional[str] = ...,
commitment: _Optional[_Union[CommitmentLevel, str]] = ...,
) -> None: ...
class IsBlockhashValidResponse(_message.Message):
__slots__ = ("slot", "valid")
+203 -171
View File
@@ -5,23 +5,26 @@ import grpc
import geyser.generated.geyser_pb2 as geyser__pb2
GRPC_GENERATED_VERSION = '1.71.0'
GRPC_GENERATED_VERSION = "1.71.0"
GRPC_VERSION = grpc.__version__
_version_not_supported = False
try:
from grpc._utilities import first_version_is_lower
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
_version_not_supported = first_version_is_lower(
GRPC_VERSION, GRPC_GENERATED_VERSION
)
except ImportError:
_version_not_supported = True
if _version_not_supported:
raise RuntimeError(
f'The grpc package installed is at version {GRPC_VERSION},'
+ ' but the generated code in geyser_pb2_grpc.py depends on'
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
f"The grpc package installed is at version {GRPC_VERSION},"
+ " but the generated code in geyser_pb2_grpc.py depends on"
+ f" grpcio>={GRPC_GENERATED_VERSION}."
+ f" Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}"
+ f" or downgrade your generated code using grpcio-tools<={GRPC_VERSION}."
)
@@ -35,40 +38,47 @@ class GeyserStub:
channel: A grpc.Channel.
"""
self.Subscribe = channel.stream_stream(
'/geyser.Geyser/Subscribe',
request_serializer=geyser__pb2.SubscribeRequest.SerializeToString,
response_deserializer=geyser__pb2.SubscribeUpdate.FromString,
_registered_method=True)
"/geyser.Geyser/Subscribe",
request_serializer=geyser__pb2.SubscribeRequest.SerializeToString,
response_deserializer=geyser__pb2.SubscribeUpdate.FromString,
_registered_method=True,
)
self.Ping = channel.unary_unary(
'/geyser.Geyser/Ping',
request_serializer=geyser__pb2.PingRequest.SerializeToString,
response_deserializer=geyser__pb2.PongResponse.FromString,
_registered_method=True)
"/geyser.Geyser/Ping",
request_serializer=geyser__pb2.PingRequest.SerializeToString,
response_deserializer=geyser__pb2.PongResponse.FromString,
_registered_method=True,
)
self.GetLatestBlockhash = channel.unary_unary(
'/geyser.Geyser/GetLatestBlockhash',
request_serializer=geyser__pb2.GetLatestBlockhashRequest.SerializeToString,
response_deserializer=geyser__pb2.GetLatestBlockhashResponse.FromString,
_registered_method=True)
"/geyser.Geyser/GetLatestBlockhash",
request_serializer=geyser__pb2.GetLatestBlockhashRequest.SerializeToString,
response_deserializer=geyser__pb2.GetLatestBlockhashResponse.FromString,
_registered_method=True,
)
self.GetBlockHeight = channel.unary_unary(
'/geyser.Geyser/GetBlockHeight',
request_serializer=geyser__pb2.GetBlockHeightRequest.SerializeToString,
response_deserializer=geyser__pb2.GetBlockHeightResponse.FromString,
_registered_method=True)
"/geyser.Geyser/GetBlockHeight",
request_serializer=geyser__pb2.GetBlockHeightRequest.SerializeToString,
response_deserializer=geyser__pb2.GetBlockHeightResponse.FromString,
_registered_method=True,
)
self.GetSlot = channel.unary_unary(
'/geyser.Geyser/GetSlot',
request_serializer=geyser__pb2.GetSlotRequest.SerializeToString,
response_deserializer=geyser__pb2.GetSlotResponse.FromString,
_registered_method=True)
"/geyser.Geyser/GetSlot",
request_serializer=geyser__pb2.GetSlotRequest.SerializeToString,
response_deserializer=geyser__pb2.GetSlotResponse.FromString,
_registered_method=True,
)
self.IsBlockhashValid = channel.unary_unary(
'/geyser.Geyser/IsBlockhashValid',
request_serializer=geyser__pb2.IsBlockhashValidRequest.SerializeToString,
response_deserializer=geyser__pb2.IsBlockhashValidResponse.FromString,
_registered_method=True)
"/geyser.Geyser/IsBlockhashValid",
request_serializer=geyser__pb2.IsBlockhashValidRequest.SerializeToString,
response_deserializer=geyser__pb2.IsBlockhashValidResponse.FromString,
_registered_method=True,
)
self.GetVersion = channel.unary_unary(
'/geyser.Geyser/GetVersion',
request_serializer=geyser__pb2.GetVersionRequest.SerializeToString,
response_deserializer=geyser__pb2.GetVersionResponse.FromString,
_registered_method=True)
"/geyser.Geyser/GetVersion",
request_serializer=geyser__pb2.GetVersionRequest.SerializeToString,
response_deserializer=geyser__pb2.GetVersionResponse.FromString,
_registered_method=True,
)
class GeyserServicer:
@@ -77,109 +87,112 @@ class GeyserServicer:
def Subscribe(self, request_iterator, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
context.set_details("Method not implemented!")
raise NotImplementedError("Method not implemented!")
def Ping(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
context.set_details("Method not implemented!")
raise NotImplementedError("Method not implemented!")
def GetLatestBlockhash(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
context.set_details("Method not implemented!")
raise NotImplementedError("Method not implemented!")
def GetBlockHeight(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
context.set_details("Method not implemented!")
raise NotImplementedError("Method not implemented!")
def GetSlot(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
context.set_details("Method not implemented!")
raise NotImplementedError("Method not implemented!")
def IsBlockhashValid(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
context.set_details("Method not implemented!")
raise NotImplementedError("Method not implemented!")
def GetVersion(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
context.set_details("Method not implemented!")
raise NotImplementedError("Method not implemented!")
def add_GeyserServicer_to_server(servicer, server):
rpc_method_handlers = {
'Subscribe': grpc.stream_stream_rpc_method_handler(
servicer.Subscribe,
request_deserializer=geyser__pb2.SubscribeRequest.FromString,
response_serializer=geyser__pb2.SubscribeUpdate.SerializeToString,
),
'Ping': grpc.unary_unary_rpc_method_handler(
servicer.Ping,
request_deserializer=geyser__pb2.PingRequest.FromString,
response_serializer=geyser__pb2.PongResponse.SerializeToString,
),
'GetLatestBlockhash': grpc.unary_unary_rpc_method_handler(
servicer.GetLatestBlockhash,
request_deserializer=geyser__pb2.GetLatestBlockhashRequest.FromString,
response_serializer=geyser__pb2.GetLatestBlockhashResponse.SerializeToString,
),
'GetBlockHeight': grpc.unary_unary_rpc_method_handler(
servicer.GetBlockHeight,
request_deserializer=geyser__pb2.GetBlockHeightRequest.FromString,
response_serializer=geyser__pb2.GetBlockHeightResponse.SerializeToString,
),
'GetSlot': grpc.unary_unary_rpc_method_handler(
servicer.GetSlot,
request_deserializer=geyser__pb2.GetSlotRequest.FromString,
response_serializer=geyser__pb2.GetSlotResponse.SerializeToString,
),
'IsBlockhashValid': grpc.unary_unary_rpc_method_handler(
servicer.IsBlockhashValid,
request_deserializer=geyser__pb2.IsBlockhashValidRequest.FromString,
response_serializer=geyser__pb2.IsBlockhashValidResponse.SerializeToString,
),
'GetVersion': grpc.unary_unary_rpc_method_handler(
servicer.GetVersion,
request_deserializer=geyser__pb2.GetVersionRequest.FromString,
response_serializer=geyser__pb2.GetVersionResponse.SerializeToString,
),
"Subscribe": grpc.stream_stream_rpc_method_handler(
servicer.Subscribe,
request_deserializer=geyser__pb2.SubscribeRequest.FromString,
response_serializer=geyser__pb2.SubscribeUpdate.SerializeToString,
),
"Ping": grpc.unary_unary_rpc_method_handler(
servicer.Ping,
request_deserializer=geyser__pb2.PingRequest.FromString,
response_serializer=geyser__pb2.PongResponse.SerializeToString,
),
"GetLatestBlockhash": grpc.unary_unary_rpc_method_handler(
servicer.GetLatestBlockhash,
request_deserializer=geyser__pb2.GetLatestBlockhashRequest.FromString,
response_serializer=geyser__pb2.GetLatestBlockhashResponse.SerializeToString,
),
"GetBlockHeight": grpc.unary_unary_rpc_method_handler(
servicer.GetBlockHeight,
request_deserializer=geyser__pb2.GetBlockHeightRequest.FromString,
response_serializer=geyser__pb2.GetBlockHeightResponse.SerializeToString,
),
"GetSlot": grpc.unary_unary_rpc_method_handler(
servicer.GetSlot,
request_deserializer=geyser__pb2.GetSlotRequest.FromString,
response_serializer=geyser__pb2.GetSlotResponse.SerializeToString,
),
"IsBlockhashValid": grpc.unary_unary_rpc_method_handler(
servicer.IsBlockhashValid,
request_deserializer=geyser__pb2.IsBlockhashValidRequest.FromString,
response_serializer=geyser__pb2.IsBlockhashValidResponse.SerializeToString,
),
"GetVersion": grpc.unary_unary_rpc_method_handler(
servicer.GetVersion,
request_deserializer=geyser__pb2.GetVersionRequest.FromString,
response_serializer=geyser__pb2.GetVersionResponse.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'geyser.Geyser', rpc_method_handlers)
"geyser.Geyser", rpc_method_handlers
)
server.add_generic_rpc_handlers((generic_handler,))
server.add_registered_method_handlers('geyser.Geyser', rpc_method_handlers)
server.add_registered_method_handlers("geyser.Geyser", rpc_method_handlers)
# This class is part of an EXPERIMENTAL API.
# This class is part of an EXPERIMENTAL API.
class Geyser:
"""Missing associated documentation comment in .proto file."""
@staticmethod
def Subscribe(request_iterator,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
def Subscribe(
request_iterator,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None,
):
return grpc.experimental.stream_stream(
request_iterator,
target,
'/geyser.Geyser/Subscribe',
"/geyser.Geyser/Subscribe",
geyser__pb2.SubscribeRequest.SerializeToString,
geyser__pb2.SubscribeUpdate.FromString,
options,
@@ -190,23 +203,26 @@ class Geyser:
wait_for_ready,
timeout,
metadata,
_registered_method=True)
_registered_method=True,
)
@staticmethod
def Ping(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
def Ping(
request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None,
):
return grpc.experimental.unary_unary(
request,
target,
'/geyser.Geyser/Ping',
"/geyser.Geyser/Ping",
geyser__pb2.PingRequest.SerializeToString,
geyser__pb2.PongResponse.FromString,
options,
@@ -217,23 +233,26 @@ class Geyser:
wait_for_ready,
timeout,
metadata,
_registered_method=True)
_registered_method=True,
)
@staticmethod
def GetLatestBlockhash(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
def GetLatestBlockhash(
request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None,
):
return grpc.experimental.unary_unary(
request,
target,
'/geyser.Geyser/GetLatestBlockhash',
"/geyser.Geyser/GetLatestBlockhash",
geyser__pb2.GetLatestBlockhashRequest.SerializeToString,
geyser__pb2.GetLatestBlockhashResponse.FromString,
options,
@@ -244,23 +263,26 @@ class Geyser:
wait_for_ready,
timeout,
metadata,
_registered_method=True)
_registered_method=True,
)
@staticmethod
def GetBlockHeight(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
def GetBlockHeight(
request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None,
):
return grpc.experimental.unary_unary(
request,
target,
'/geyser.Geyser/GetBlockHeight',
"/geyser.Geyser/GetBlockHeight",
geyser__pb2.GetBlockHeightRequest.SerializeToString,
geyser__pb2.GetBlockHeightResponse.FromString,
options,
@@ -271,23 +293,26 @@ class Geyser:
wait_for_ready,
timeout,
metadata,
_registered_method=True)
_registered_method=True,
)
@staticmethod
def GetSlot(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
def GetSlot(
request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None,
):
return grpc.experimental.unary_unary(
request,
target,
'/geyser.Geyser/GetSlot',
"/geyser.Geyser/GetSlot",
geyser__pb2.GetSlotRequest.SerializeToString,
geyser__pb2.GetSlotResponse.FromString,
options,
@@ -298,23 +323,26 @@ class Geyser:
wait_for_ready,
timeout,
metadata,
_registered_method=True)
_registered_method=True,
)
@staticmethod
def IsBlockhashValid(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
def IsBlockhashValid(
request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None,
):
return grpc.experimental.unary_unary(
request,
target,
'/geyser.Geyser/IsBlockhashValid',
"/geyser.Geyser/IsBlockhashValid",
geyser__pb2.IsBlockhashValidRequest.SerializeToString,
geyser__pb2.IsBlockhashValidResponse.FromString,
options,
@@ -325,23 +353,26 @@ class Geyser:
wait_for_ready,
timeout,
metadata,
_registered_method=True)
_registered_method=True,
)
@staticmethod
def GetVersion(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
def GetVersion(
request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None,
):
return grpc.experimental.unary_unary(
request,
target,
'/geyser.Geyser/GetVersion',
"/geyser.Geyser/GetVersion",
geyser__pb2.GetVersionRequest.SerializeToString,
geyser__pb2.GetVersionResponse.FromString,
options,
@@ -352,4 +383,5 @@ class Geyser:
wait_for_ready,
timeout,
metadata,
_registered_method=True)
_registered_method=True,
)
File diff suppressed because one or more lines are too long
+168 -21
View File
@@ -2,7 +2,13 @@ from google.protobuf.internal import containers as _containers
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
from typing import (
ClassVar as _ClassVar,
Iterable as _Iterable,
Mapping as _Mapping,
Optional as _Optional,
Union as _Union,
)
DESCRIPTOR: _descriptor.FileDescriptor
@@ -13,6 +19,7 @@ class RewardType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
Rent: _ClassVar[RewardType]
Staking: _ClassVar[RewardType]
Voting: _ClassVar[RewardType]
Unspecified: RewardType
Fee: RewardType
Rent: RewardType
@@ -20,7 +27,16 @@ Staking: RewardType
Voting: RewardType
class ConfirmedBlock(_message.Message):
__slots__ = ("previous_blockhash", "blockhash", "parent_slot", "transactions", "rewards", "block_time", "block_height", "num_partitions")
__slots__ = (
"previous_blockhash",
"blockhash",
"parent_slot",
"transactions",
"rewards",
"block_time",
"block_height",
"num_partitions",
)
PREVIOUS_BLOCKHASH_FIELD_NUMBER: _ClassVar[int]
BLOCKHASH_FIELD_NUMBER: _ClassVar[int]
PARENT_SLOT_FIELD_NUMBER: _ClassVar[int]
@@ -37,7 +53,19 @@ class ConfirmedBlock(_message.Message):
block_time: UnixTimestamp
block_height: BlockHeight
num_partitions: NumPartitions
def __init__(self, previous_blockhash: _Optional[str] = ..., blockhash: _Optional[str] = ..., parent_slot: _Optional[int] = ..., transactions: _Optional[_Iterable[_Union[ConfirmedTransaction, _Mapping]]] = ..., rewards: _Optional[_Iterable[_Union[Reward, _Mapping]]] = ..., block_time: _Optional[_Union[UnixTimestamp, _Mapping]] = ..., block_height: _Optional[_Union[BlockHeight, _Mapping]] = ..., num_partitions: _Optional[_Union[NumPartitions, _Mapping]] = ...) -> None: ...
def __init__(
self,
previous_blockhash: _Optional[str] = ...,
blockhash: _Optional[str] = ...,
parent_slot: _Optional[int] = ...,
transactions: _Optional[
_Iterable[_Union[ConfirmedTransaction, _Mapping]]
] = ...,
rewards: _Optional[_Iterable[_Union[Reward, _Mapping]]] = ...,
block_time: _Optional[_Union[UnixTimestamp, _Mapping]] = ...,
block_height: _Optional[_Union[BlockHeight, _Mapping]] = ...,
num_partitions: _Optional[_Union[NumPartitions, _Mapping]] = ...,
) -> None: ...
class ConfirmedTransaction(_message.Message):
__slots__ = ("transaction", "meta")
@@ -45,7 +73,11 @@ class ConfirmedTransaction(_message.Message):
META_FIELD_NUMBER: _ClassVar[int]
transaction: Transaction
meta: TransactionStatusMeta
def __init__(self, transaction: _Optional[_Union[Transaction, _Mapping]] = ..., meta: _Optional[_Union[TransactionStatusMeta, _Mapping]] = ...) -> None: ...
def __init__(
self,
transaction: _Optional[_Union[Transaction, _Mapping]] = ...,
meta: _Optional[_Union[TransactionStatusMeta, _Mapping]] = ...,
) -> None: ...
class Transaction(_message.Message):
__slots__ = ("signatures", "message")
@@ -53,10 +85,21 @@ class Transaction(_message.Message):
MESSAGE_FIELD_NUMBER: _ClassVar[int]
signatures: _containers.RepeatedScalarFieldContainer[bytes]
message: Message
def __init__(self, signatures: _Optional[_Iterable[bytes]] = ..., message: _Optional[_Union[Message, _Mapping]] = ...) -> None: ...
def __init__(
self,
signatures: _Optional[_Iterable[bytes]] = ...,
message: _Optional[_Union[Message, _Mapping]] = ...,
) -> None: ...
class Message(_message.Message):
__slots__ = ("header", "account_keys", "recent_blockhash", "instructions", "versioned", "address_table_lookups")
__slots__ = (
"header",
"account_keys",
"recent_blockhash",
"instructions",
"versioned",
"address_table_lookups",
)
HEADER_FIELD_NUMBER: _ClassVar[int]
ACCOUNT_KEYS_FIELD_NUMBER: _ClassVar[int]
RECENT_BLOCKHASH_FIELD_NUMBER: _ClassVar[int]
@@ -68,18 +111,39 @@ class Message(_message.Message):
recent_blockhash: bytes
instructions: _containers.RepeatedCompositeFieldContainer[CompiledInstruction]
versioned: bool
address_table_lookups: _containers.RepeatedCompositeFieldContainer[MessageAddressTableLookup]
def __init__(self, header: _Optional[_Union[MessageHeader, _Mapping]] = ..., account_keys: _Optional[_Iterable[bytes]] = ..., recent_blockhash: _Optional[bytes] = ..., instructions: _Optional[_Iterable[_Union[CompiledInstruction, _Mapping]]] = ..., versioned: bool = ..., address_table_lookups: _Optional[_Iterable[_Union[MessageAddressTableLookup, _Mapping]]] = ...) -> None: ...
address_table_lookups: _containers.RepeatedCompositeFieldContainer[
MessageAddressTableLookup
]
def __init__(
self,
header: _Optional[_Union[MessageHeader, _Mapping]] = ...,
account_keys: _Optional[_Iterable[bytes]] = ...,
recent_blockhash: _Optional[bytes] = ...,
instructions: _Optional[_Iterable[_Union[CompiledInstruction, _Mapping]]] = ...,
versioned: bool = ...,
address_table_lookups: _Optional[
_Iterable[_Union[MessageAddressTableLookup, _Mapping]]
] = ...,
) -> None: ...
class MessageHeader(_message.Message):
__slots__ = ("num_required_signatures", "num_readonly_signed_accounts", "num_readonly_unsigned_accounts")
__slots__ = (
"num_required_signatures",
"num_readonly_signed_accounts",
"num_readonly_unsigned_accounts",
)
NUM_REQUIRED_SIGNATURES_FIELD_NUMBER: _ClassVar[int]
NUM_READONLY_SIGNED_ACCOUNTS_FIELD_NUMBER: _ClassVar[int]
NUM_READONLY_UNSIGNED_ACCOUNTS_FIELD_NUMBER: _ClassVar[int]
num_required_signatures: int
num_readonly_signed_accounts: int
num_readonly_unsigned_accounts: int
def __init__(self, num_required_signatures: _Optional[int] = ..., num_readonly_signed_accounts: _Optional[int] = ..., num_readonly_unsigned_accounts: _Optional[int] = ...) -> None: ...
def __init__(
self,
num_required_signatures: _Optional[int] = ...,
num_readonly_signed_accounts: _Optional[int] = ...,
num_readonly_unsigned_accounts: _Optional[int] = ...,
) -> None: ...
class MessageAddressTableLookup(_message.Message):
__slots__ = ("account_key", "writable_indexes", "readonly_indexes")
@@ -89,10 +153,32 @@ class MessageAddressTableLookup(_message.Message):
account_key: bytes
writable_indexes: bytes
readonly_indexes: bytes
def __init__(self, account_key: _Optional[bytes] = ..., writable_indexes: _Optional[bytes] = ..., readonly_indexes: _Optional[bytes] = ...) -> None: ...
def __init__(
self,
account_key: _Optional[bytes] = ...,
writable_indexes: _Optional[bytes] = ...,
readonly_indexes: _Optional[bytes] = ...,
) -> None: ...
class TransactionStatusMeta(_message.Message):
__slots__ = ("err", "fee", "pre_balances", "post_balances", "inner_instructions", "inner_instructions_none", "log_messages", "log_messages_none", "pre_token_balances", "post_token_balances", "rewards", "loaded_writable_addresses", "loaded_readonly_addresses", "return_data", "return_data_none", "compute_units_consumed")
__slots__ = (
"err",
"fee",
"pre_balances",
"post_balances",
"inner_instructions",
"inner_instructions_none",
"log_messages",
"log_messages_none",
"pre_token_balances",
"post_token_balances",
"rewards",
"loaded_writable_addresses",
"loaded_readonly_addresses",
"return_data",
"return_data_none",
"compute_units_consumed",
)
ERR_FIELD_NUMBER: _ClassVar[int]
FEE_FIELD_NUMBER: _ClassVar[int]
PRE_BALANCES_FIELD_NUMBER: _ClassVar[int]
@@ -125,7 +211,27 @@ class TransactionStatusMeta(_message.Message):
return_data: ReturnData
return_data_none: bool
compute_units_consumed: int
def __init__(self, err: _Optional[_Union[TransactionError, _Mapping]] = ..., fee: _Optional[int] = ..., pre_balances: _Optional[_Iterable[int]] = ..., post_balances: _Optional[_Iterable[int]] = ..., inner_instructions: _Optional[_Iterable[_Union[InnerInstructions, _Mapping]]] = ..., inner_instructions_none: bool = ..., log_messages: _Optional[_Iterable[str]] = ..., log_messages_none: bool = ..., pre_token_balances: _Optional[_Iterable[_Union[TokenBalance, _Mapping]]] = ..., post_token_balances: _Optional[_Iterable[_Union[TokenBalance, _Mapping]]] = ..., rewards: _Optional[_Iterable[_Union[Reward, _Mapping]]] = ..., loaded_writable_addresses: _Optional[_Iterable[bytes]] = ..., loaded_readonly_addresses: _Optional[_Iterable[bytes]] = ..., return_data: _Optional[_Union[ReturnData, _Mapping]] = ..., return_data_none: bool = ..., compute_units_consumed: _Optional[int] = ...) -> None: ...
def __init__(
self,
err: _Optional[_Union[TransactionError, _Mapping]] = ...,
fee: _Optional[int] = ...,
pre_balances: _Optional[_Iterable[int]] = ...,
post_balances: _Optional[_Iterable[int]] = ...,
inner_instructions: _Optional[
_Iterable[_Union[InnerInstructions, _Mapping]]
] = ...,
inner_instructions_none: bool = ...,
log_messages: _Optional[_Iterable[str]] = ...,
log_messages_none: bool = ...,
pre_token_balances: _Optional[_Iterable[_Union[TokenBalance, _Mapping]]] = ...,
post_token_balances: _Optional[_Iterable[_Union[TokenBalance, _Mapping]]] = ...,
rewards: _Optional[_Iterable[_Union[Reward, _Mapping]]] = ...,
loaded_writable_addresses: _Optional[_Iterable[bytes]] = ...,
loaded_readonly_addresses: _Optional[_Iterable[bytes]] = ...,
return_data: _Optional[_Union[ReturnData, _Mapping]] = ...,
return_data_none: bool = ...,
compute_units_consumed: _Optional[int] = ...,
) -> None: ...
class TransactionError(_message.Message):
__slots__ = ("err",)
@@ -139,7 +245,11 @@ class InnerInstructions(_message.Message):
INSTRUCTIONS_FIELD_NUMBER: _ClassVar[int]
index: int
instructions: _containers.RepeatedCompositeFieldContainer[InnerInstruction]
def __init__(self, index: _Optional[int] = ..., instructions: _Optional[_Iterable[_Union[InnerInstruction, _Mapping]]] = ...) -> None: ...
def __init__(
self,
index: _Optional[int] = ...,
instructions: _Optional[_Iterable[_Union[InnerInstruction, _Mapping]]] = ...,
) -> None: ...
class InnerInstruction(_message.Message):
__slots__ = ("program_id_index", "accounts", "data", "stack_height")
@@ -151,7 +261,13 @@ class InnerInstruction(_message.Message):
accounts: bytes
data: bytes
stack_height: int
def __init__(self, program_id_index: _Optional[int] = ..., accounts: _Optional[bytes] = ..., data: _Optional[bytes] = ..., stack_height: _Optional[int] = ...) -> None: ...
def __init__(
self,
program_id_index: _Optional[int] = ...,
accounts: _Optional[bytes] = ...,
data: _Optional[bytes] = ...,
stack_height: _Optional[int] = ...,
) -> None: ...
class CompiledInstruction(_message.Message):
__slots__ = ("program_id_index", "accounts", "data")
@@ -161,7 +277,12 @@ class CompiledInstruction(_message.Message):
program_id_index: int
accounts: bytes
data: bytes
def __init__(self, program_id_index: _Optional[int] = ..., accounts: _Optional[bytes] = ..., data: _Optional[bytes] = ...) -> None: ...
def __init__(
self,
program_id_index: _Optional[int] = ...,
accounts: _Optional[bytes] = ...,
data: _Optional[bytes] = ...,
) -> None: ...
class TokenBalance(_message.Message):
__slots__ = ("account_index", "mint", "ui_token_amount", "owner", "program_id")
@@ -175,7 +296,14 @@ class TokenBalance(_message.Message):
ui_token_amount: UiTokenAmount
owner: str
program_id: str
def __init__(self, account_index: _Optional[int] = ..., mint: _Optional[str] = ..., ui_token_amount: _Optional[_Union[UiTokenAmount, _Mapping]] = ..., owner: _Optional[str] = ..., program_id: _Optional[str] = ...) -> None: ...
def __init__(
self,
account_index: _Optional[int] = ...,
mint: _Optional[str] = ...,
ui_token_amount: _Optional[_Union[UiTokenAmount, _Mapping]] = ...,
owner: _Optional[str] = ...,
program_id: _Optional[str] = ...,
) -> None: ...
class UiTokenAmount(_message.Message):
__slots__ = ("ui_amount", "decimals", "amount", "ui_amount_string")
@@ -187,7 +315,13 @@ class UiTokenAmount(_message.Message):
decimals: int
amount: str
ui_amount_string: str
def __init__(self, ui_amount: _Optional[float] = ..., decimals: _Optional[int] = ..., amount: _Optional[str] = ..., ui_amount_string: _Optional[str] = ...) -> None: ...
def __init__(
self,
ui_amount: _Optional[float] = ...,
decimals: _Optional[int] = ...,
amount: _Optional[str] = ...,
ui_amount_string: _Optional[str] = ...,
) -> None: ...
class ReturnData(_message.Message):
__slots__ = ("program_id", "data")
@@ -195,7 +329,9 @@ class ReturnData(_message.Message):
DATA_FIELD_NUMBER: _ClassVar[int]
program_id: bytes
data: bytes
def __init__(self, program_id: _Optional[bytes] = ..., data: _Optional[bytes] = ...) -> None: ...
def __init__(
self, program_id: _Optional[bytes] = ..., data: _Optional[bytes] = ...
) -> None: ...
class Reward(_message.Message):
__slots__ = ("pubkey", "lamports", "post_balance", "reward_type", "commission")
@@ -209,7 +345,14 @@ class Reward(_message.Message):
post_balance: int
reward_type: RewardType
commission: str
def __init__(self, pubkey: _Optional[str] = ..., lamports: _Optional[int] = ..., post_balance: _Optional[int] = ..., reward_type: _Optional[_Union[RewardType, str]] = ..., commission: _Optional[str] = ...) -> None: ...
def __init__(
self,
pubkey: _Optional[str] = ...,
lamports: _Optional[int] = ...,
post_balance: _Optional[int] = ...,
reward_type: _Optional[_Union[RewardType, str]] = ...,
commission: _Optional[str] = ...,
) -> None: ...
class Rewards(_message.Message):
__slots__ = ("rewards", "num_partitions")
@@ -217,7 +360,11 @@ class Rewards(_message.Message):
NUM_PARTITIONS_FIELD_NUMBER: _ClassVar[int]
rewards: _containers.RepeatedCompositeFieldContainer[Reward]
num_partitions: NumPartitions
def __init__(self, rewards: _Optional[_Iterable[_Union[Reward, _Mapping]]] = ..., num_partitions: _Optional[_Union[NumPartitions, _Mapping]] = ...) -> None: ...
def __init__(
self,
rewards: _Optional[_Iterable[_Union[Reward, _Mapping]]] = ...,
num_partitions: _Optional[_Union[NumPartitions, _Mapping]] = ...,
) -> None: ...
class UnixTimestamp(_message.Message):
__slots__ = ("timestamp",)
@@ -1,24 +1,28 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import warnings
GRPC_GENERATED_VERSION = '1.71.0'
GRPC_GENERATED_VERSION = "1.71.0"
GRPC_VERSION = grpc.__version__
_version_not_supported = False
try:
from grpc._utilities import first_version_is_lower
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
_version_not_supported = first_version_is_lower(
GRPC_VERSION, GRPC_GENERATED_VERSION
)
except ImportError:
_version_not_supported = True
if _version_not_supported:
raise RuntimeError(
f'The grpc package installed is at version {GRPC_VERSION},'
+ f' but the generated code in solana_storage_pb2_grpc.py depends on'
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
f"The grpc package installed is at version {GRPC_VERSION},"
+ f" but the generated code in solana_storage_pb2_grpc.py depends on"
+ f" grpcio>={GRPC_GENERATED_VERSION}."
+ f" Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}"
+ f" or downgrade your generated code using grpcio-tools<={GRPC_VERSION}."
)
+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
+113 -58
View File
@@ -82,10 +82,12 @@ class TokenBuyer(Trader):
# Skip the wait and directly calculate the amount
token_amount = self.extreme_fast_token_amount
token_price_sol = self.amount / token_amount
#logger.info(f"EXTREME FAST Mode: Buying {token_amount} tokens.")
# logger.info(f"EXTREME FAST Mode: Buying {token_amount} tokens.")
else:
# Regular behavior with RPC call
curve_state = await self.curve_manager.get_curve_state(token_info.bonding_curve)
curve_state = await self.curve_manager.get_curve_state(
token_info.bonding_curve
)
token_price_sol = curve_state.calculate_price()
token_amount = self.amount / token_price_sol
@@ -114,16 +116,20 @@ class TokenBuyer(Trader):
if success:
# Get actual execution data from bonding curve balance changes
actual_price, actual_tokens = await self._get_actual_execution_price(tx_signature, token_info)
actual_price, actual_tokens = await self._get_actual_execution_price(
tx_signature, token_info
)
logger.info(f"Buy transaction confirmed: {tx_signature}")
logger.info(f"Actual price paid to bonding curve: {actual_price:.8f} SOL per token")
logger.info(
f"Actual price paid to bonding curve: {actual_price:.8f} SOL per token"
)
return TradeResult(
success=True,
tx_signature=tx_signature,
amount=actual_tokens, # Actual tokens received
price=actual_price, # Actual price based on bonding curve SOL flow
amount=actual_tokens, # Actual tokens received
price=actual_price, # Actual price based on bonding curve SOL flow
)
else:
return TradeResult(
@@ -196,7 +202,7 @@ class TokenBuyer(Trader):
self.wallet.pubkey,
self.wallet.pubkey,
token_info.mint,
SystemAddresses.TOKEN_PROGRAM
SystemAddresses.TOKEN_PROGRAM,
)
# Prepare buy instruction data
@@ -222,129 +228,178 @@ class TokenBuyer(Trader):
logger.error(f"Buy transaction failed: {e!s}")
raise
async def _get_actual_execution_price(self, tx_signature: str, token_info: TokenInfo) -> tuple[float, float]:
async def _get_actual_execution_price(
self, tx_signature: str, token_info: TokenInfo
) -> tuple[float, float]:
"""Get actual execution price from bonding curve SOL balance changes."""
try:
client = await self.client.get_client()
tx_response = await client.get_transaction(
tx_signature,
tx_signature,
encoding="jsonParsed",
commitment="confirmed",
max_supported_transaction_version=0
max_supported_transaction_version=0,
)
if not tx_response.value or not tx_response.value.transaction:
raise ValueError("Transaction not found")
meta = tx_response.value.transaction.meta
if not meta or not meta.pre_balances or not meta.post_balances:
raise ValueError("Transaction balance data not found")
# Get accounts - they're ParsedAccountTxStatus objects, need to extract pubkey
accounts = tx_response.value.transaction.transaction.message.account_keys
# Find bonding curve account index in the transaction
bonding_curve_index = None
for i, account in enumerate(accounts):
# Extract pubkey from ParsedAccountTxStatus object
account_pubkey = str(account.pubkey) if hasattr(account, 'pubkey') else str(account)
account_pubkey = (
str(account.pubkey) if hasattr(account, "pubkey") else str(account)
)
if account_pubkey == str(token_info.bonding_curve):
bonding_curve_index = i
break
if bonding_curve_index is None:
raise ValueError("Bonding curve not found in transaction accounts")
pre_balance_lamports = meta.pre_balances[bonding_curve_index]
post_balance_lamports = meta.post_balances[bonding_curve_index]
sol_sent_to_curve = (post_balance_lamports - pre_balance_lamports) / LAMPORTS_PER_SOL
sol_sent_to_curve = (
post_balance_lamports - pre_balance_lamports
) / LAMPORTS_PER_SOL
if sol_sent_to_curve <= 0:
raise ValueError(f"No SOL sent to bonding curve: {sol_sent_to_curve}")
tokens_received = await self._get_tokens_received_from_tx(tx_response, token_info)
tokens_received = await self._get_tokens_received_from_tx(
tx_response, token_info
)
if tokens_received == 0:
raise ValueError("Cannot compute execution price: zero tokens received")
actual_price = sol_sent_to_curve / tokens_received
logger.info(f"Bonding curve received: {sol_sent_to_curve:.6f} SOL")
logger.info(f"We received: {tokens_received:.6f} tokens")
logger.info(f"Actual execution price: {actual_price:.8f} SOL per token")
return actual_price, tokens_received
except Exception as e:
logger.warning(f"Failed to get actual execution price from bonding curve: {e}")
logger.warning(
f"Failed to get actual execution price from bonding curve: {e}"
)
# Fallback to EXTREME_FAST estimate
tokens_received = self.extreme_fast_token_amount if self.extreme_fast_mode else self.amount / await self.curve_manager.calculate_price(token_info.bonding_curve)
tokens_received = (
self.extreme_fast_token_amount
if self.extreme_fast_mode
else self.amount
/ await self.curve_manager.calculate_price(token_info.bonding_curve)
)
if tokens_received == 0:
logger.error("Fallback failed unable to determine tokens received")
return 0.0, 0.0
logger.error("Fallback failed unable to determine tokens received")
return 0.0, 0.0
return self.amount / tokens_received, tokens_received
async def _get_tokens_received_from_tx(self, tx_response, token_info: TokenInfo) -> float:
async def _get_tokens_received_from_tx(
self, tx_response, token_info: TokenInfo
) -> float:
"""Extract tokens received from transaction token balance changes."""
meta = tx_response.value.transaction.meta
pre_token_balance = 0
post_token_balance = 0
wallet_str = str(self.wallet.pubkey)
mint_str = str(token_info.mint)
if meta.pre_token_balances:
for balance in meta.pre_token_balances:
# Convert to string for comparison
balance_owner = str(balance.owner) if hasattr(balance, 'owner') else str(getattr(balance, 'owner', ''))
balance_mint = str(balance.mint) if hasattr(balance, 'mint') else str(getattr(balance, 'mint', ''))
balance_owner = (
str(balance.owner)
if hasattr(balance, "owner")
else str(getattr(balance, "owner", ""))
)
balance_mint = (
str(balance.mint)
if hasattr(balance, "mint")
else str(getattr(balance, "mint", ""))
)
if balance_owner == wallet_str and balance_mint == mint_str:
try:
# Try multiple ways to get the amount
if hasattr(balance, 'ui_token_amount'):
if hasattr(balance, "ui_token_amount"):
amount_obj = balance.ui_token_amount
if hasattr(amount_obj, 'amount') and amount_obj.amount is not None:
if (
hasattr(amount_obj, "amount")
and amount_obj.amount is not None
):
pre_token_balance = int(amount_obj.amount)
elif hasattr(amount_obj, 'ui_amount') and amount_obj.ui_amount is not None:
pre_token_balance = int(float(amount_obj.ui_amount) * (10**TOKEN_DECIMALS))
elif (
hasattr(amount_obj, "ui_amount")
and amount_obj.ui_amount is not None
):
pre_token_balance = int(
float(amount_obj.ui_amount) * (10**TOKEN_DECIMALS)
)
except (ValueError, TypeError) as e:
logger.warning(f"Error parsing pre-token balance: {e}")
break
# Check post-token balances
# Check post-token balances
if meta.post_token_balances:
for balance in meta.post_token_balances:
# Convert to string for comparison
balance_owner = str(balance.owner) if hasattr(balance, 'owner') else str(getattr(balance, 'owner', ''))
balance_mint = str(balance.mint) if hasattr(balance, 'mint') else str(getattr(balance, 'mint', ''))
balance_owner = (
str(balance.owner)
if hasattr(balance, "owner")
else str(getattr(balance, "owner", ""))
)
balance_mint = (
str(balance.mint)
if hasattr(balance, "mint")
else str(getattr(balance, "mint", ""))
)
if balance_owner == wallet_str and balance_mint == mint_str:
try:
# Try multiple ways to get the amount
if hasattr(balance, 'ui_token_amount'):
if hasattr(balance, "ui_token_amount"):
amount_obj = balance.ui_token_amount
if hasattr(amount_obj, 'amount') and amount_obj.amount is not None:
if (
hasattr(amount_obj, "amount")
and amount_obj.amount is not None
):
post_token_balance = int(amount_obj.amount)
elif hasattr(amount_obj, 'ui_amount') and amount_obj.ui_amount is not None:
post_token_balance = int(float(amount_obj.ui_amount) * (10**TOKEN_DECIMALS))
elif (
hasattr(amount_obj, "ui_amount")
and amount_obj.ui_amount is not None
):
post_token_balance = int(
float(amount_obj.ui_amount) * (10**TOKEN_DECIMALS)
)
except (ValueError, TypeError) as e:
logger.warning(f"Error parsing post-token balance: {e}")
break
# Calculate tokens received
if pre_token_balance == 0 and post_token_balance > 0:
tokens_received_raw = post_token_balance
else:
tokens_received_raw = post_token_balance - pre_token_balance
if tokens_received_raw <= 0:
logger.warning("Token balance search failed. Using fallback from EXTREME_FAST estimate.")
logger.warning(
"Token balance search failed. Using fallback from EXTREME_FAST estimate."
)
# Fallback: use the amount we know we bought
if self.extreme_fast_mode and self.extreme_fast_token_amount > 0:
return self.extreme_fast_token_amount
@@ -352,4 +407,4 @@ class TokenBuyer(Trader):
logger.error("Cannot determine tokens received from transaction")
return 0.0
return tokens_received_raw / 10**TOKEN_DECIMALS
return tokens_received_raw / 10**TOKEN_DECIMALS
+27 -26
View File
@@ -11,6 +11,7 @@ from solders.pubkey import Pubkey
class ExitReason(Enum):
"""Reasons for position exit."""
TAKE_PROFIT = "take_profit"
STOP_LOSS = "stop_loss"
MAX_HOLD_TIME = "max_hold_time"
@@ -20,27 +21,27 @@ class ExitReason(Enum):
@dataclass
class Position:
"""Represents an active trading position."""
# Token information
mint: Pubkey
symbol: str
# Position details
entry_price: float
quantity: float
entry_time: datetime
# Exit conditions
take_profit_price: float | None = None
stop_loss_price: float | None = None
max_hold_time: int | None = None # seconds
# Status
is_active: bool = True
exit_reason: ExitReason | None = None
exit_price: float | None = None
exit_time: datetime | None = None
@classmethod
def create_from_buy_result(
cls,
@@ -53,7 +54,7 @@ class Position:
max_hold_time: int | None = None,
) -> "Position":
"""Create a position from a successful buy transaction.
Args:
mint: Token mint address
symbol: Token symbol
@@ -62,18 +63,18 @@ class Position:
take_profit_percentage: Take profit percentage (0.5 = 50% profit)
stop_loss_percentage: Stop loss percentage (0.2 = 20% loss)
max_hold_time: Maximum hold time in seconds
Returns:
Position instance
"""
take_profit_price = None
if take_profit_percentage is not None:
take_profit_price = entry_price * (1 + take_profit_percentage)
stop_loss_price = None
if stop_loss_percentage is not None:
stop_loss_price = entry_price * (1 - stop_loss_percentage)
return cls(
mint=mint,
symbol=symbol,
@@ -84,38 +85,38 @@ class Position:
stop_loss_price=stop_loss_price,
max_hold_time=max_hold_time,
)
def should_exit(self, current_price: float) -> tuple[bool, ExitReason | None]:
"""Check if position should be exited based on current conditions.
Args:
current_price: Current token price
Returns:
Tuple of (should_exit, exit_reason)
"""
if not self.is_active:
return False, None
# Check take profit
if self.take_profit_price and current_price >= self.take_profit_price:
return True, ExitReason.TAKE_PROFIT
# Check stop loss
if self.stop_loss_price and current_price <= self.stop_loss_price:
return True, ExitReason.STOP_LOSS
# Check max hold time
if self.max_hold_time:
elapsed_time = (datetime.utcnow() - self.entry_time).total_seconds()
if elapsed_time >= self.max_hold_time:
return True, ExitReason.MAX_HOLD_TIME
return False, None
def close_position(self, exit_price: float, exit_reason: ExitReason) -> None:
"""Close the position with exit details.
Args:
exit_price: Price at which position was exited
exit_reason: Reason for exit
@@ -124,27 +125,27 @@ class Position:
self.exit_price = exit_price
self.exit_reason = exit_reason
self.exit_time = datetime.utcnow()
def get_pnl(self, current_price: float | None = None) -> dict:
"""Calculate profit/loss for the position.
Args:
current_price: Current price (uses exit_price if position is closed)
Returns:
Dictionary with PnL information
"""
if self.is_active and current_price is None:
raise ValueError("current_price required for active position")
price_to_use = self.exit_price if not self.is_active else current_price
if price_to_use is None:
raise ValueError("No price available for PnL calculation")
price_change = price_to_use - self.entry_price
price_change_pct = (price_change / self.entry_price) * 100
unrealized_pnl = price_change * self.quantity
return {
"entry_price": self.entry_price,
"current_price": price_to_use,
@@ -153,7 +154,7 @@ class Position:
"unrealized_pnl_sol": unrealized_pnl,
"quantity": self.quantity,
}
def __str__(self) -> str:
"""String representation of position."""
if self.is_active:
@@ -162,4 +163,4 @@ class Position:
status = f"CLOSED ({self.exit_reason.value})"
else:
status = "CLOSED (UNKNOWN)"
return f"Position({self.symbol}: {self.quantity:.6f} @ {self.entry_price:.8f} SOL - {status})"
return f"Position({self.symbol}: {self.quantity:.6f} @ {self.entry_price:.8f} SOL - {status})"
+3 -1
View File
@@ -175,7 +175,9 @@ class TokenSeller(Trader):
pubkey=SystemAddresses.PROGRAM, is_signer=False, is_writable=False
),
AccountMeta(
pubkey=token_info.creator_vault, is_signer=False, is_writable=True,
pubkey=token_info.creator_vault,
is_signer=False,
is_writable=True,
),
AccountMeta(
pubkey=SystemAddresses.TOKEN_PROGRAM, is_signer=False, is_writable=False
+134 -107
View File
@@ -39,6 +39,7 @@ logger = get_logger(__name__)
class PumpTrader:
"""Coordinates trading operations for pump.fun tokens with focus on freshness."""
def __init__(
self,
rpc_endpoint: str,
@@ -52,37 +53,31 @@ class PumpTrader:
geyser_api_token: str | None = None,
geyser_auth_type: str = "x-token",
pumpportal_url: str = "wss://pumpportal.fun/api/data",
extreme_fast_mode: bool = False,
extreme_fast_token_amount: int = 30,
# Exit strategy configuration
exit_strategy: str = "time_based",
take_profit_percentage: float | None = None,
stop_loss_percentage: float | None = None,
max_hold_time: int | None = None,
price_check_interval: int = 10,
# Priority fee configuration
enable_dynamic_priority_fee: bool = False,
enable_fixed_priority_fee: bool = True,
fixed_priority_fee: int = 200_000,
extra_priority_fee: float = 0.0,
hard_cap_prior_fee: int = 200_000,
# Retry and timeout settings
max_retries: int = 3,
wait_time_after_creation: int = 15, # here and further - seconds
wait_time_after_creation: int = 15, # here and further - seconds
wait_time_after_buy: int = 15,
wait_time_before_new_token: int = 15,
max_token_age: int | float = 0.001,
token_wait_timeout: int = 30,
# Cleanup settings
cleanup_mode: str = "disabled",
cleanup_force_close_with_burn: bool = False,
cleanup_with_priority_fee: bool = False,
# Trading filters
match_string: str | None = None,
bro_address: str | None = None,
@@ -129,7 +124,7 @@ class PumpTrader:
cleanup_mode: Cleanup mode ("disabled", "auto", or "manual")
cleanup_force_close_with_burn: Whether to force close with burn during cleanup
cleanup_with_priority_fee: Whether to use priority fees during cleanup
match_string: Optional string to match in token name/symbol
bro_address: Optional creator address to filter by
marry_mode: If True, only buy tokens and skip selling
@@ -155,7 +150,7 @@ class PumpTrader:
buy_slippage,
max_retries,
extreme_fast_token_amount,
extreme_fast_mode
extreme_fast_mode,
)
self.seller = TokenSeller(
self.solana_client,
@@ -165,30 +160,34 @@ class PumpTrader:
sell_slippage,
max_retries,
)
# Initialize the appropriate listener type
listener_type = listener_type.lower()
if listener_type == "geyser":
if not geyser_endpoint or not geyser_api_token:
raise ValueError("Geyser endpoint and API token are required for geyser listener")
raise ValueError(
"Geyser endpoint and API token are required for geyser listener"
)
self.token_listener = GeyserListener(
geyser_endpoint,
geyser_endpoint,
geyser_api_token,
geyser_auth_type,
PumpAddresses.PROGRAM
geyser_auth_type,
PumpAddresses.PROGRAM,
)
logger.info("Using Geyser listener for token monitoring")
elif listener_type == "logs":
self.token_listener = LogsListener(wss_endpoint, PumpAddresses.PROGRAM)
logger.info("Using logsSubscribe listener for token monitoring")
elif listener_type == "pumpportal":
self.token_listener = PumpPortalListener(PumpAddresses.PROGRAM, pumpportal_url)
self.token_listener = PumpPortalListener(
PumpAddresses.PROGRAM, pumpportal_url
)
logger.info("Using PumpPortal listener for token monitoring")
else:
self.token_listener = BlockListener(wss_endpoint, PumpAddresses.PROGRAM)
logger.info("Using blockSubscribe listener for token monitoring")
# Trading parameters
self.buy_amount = buy_amount
self.buy_slippage = buy_slippage
@@ -196,21 +195,21 @@ class PumpTrader:
self.max_retries = max_retries
self.extreme_fast_mode = extreme_fast_mode
self.extreme_fast_token_amount = extreme_fast_token_amount
# Exit strategy parameters
self.exit_strategy = exit_strategy.lower()
self.take_profit_percentage = take_profit_percentage
self.stop_loss_percentage = stop_loss_percentage
self.max_hold_time = max_hold_time
self.price_check_interval = price_check_interval
# Timing parameters
self.wait_time_after_creation = wait_time_after_creation
self.wait_time_after_buy = wait_time_after_buy
self.wait_time_before_new_token = wait_time_before_new_token
self.max_token_age = max_token_age
self.token_wait_timeout = token_wait_timeout
# Cleanup parameters
self.cleanup_mode = cleanup_mode
self.cleanup_force_close_with_burn = cleanup_force_close_with_burn
@@ -221,26 +220,36 @@ class PumpTrader:
self.bro_address = bro_address
self.marry_mode = marry_mode
self.yolo_mode = yolo_mode
# State tracking
self.traded_mints: set[Pubkey] = set()
self.token_queue: asyncio.Queue = asyncio.Queue()
self.processing: bool = False
self.processed_tokens: set[str] = set()
self.token_timestamps: dict[str, float] = {}
async def start(self) -> None:
"""Start the trading bot and listen for new tokens."""
logger.info("Starting pump.fun trader")
logger.info(f"Match filter: {self.match_string if self.match_string else 'None'}")
logger.info(f"Creator filter: {self.bro_address if self.bro_address else 'None'}")
logger.info(
f"Match filter: {self.match_string if self.match_string else 'None'}"
)
logger.info(
f"Creator filter: {self.bro_address if self.bro_address else 'None'}"
)
logger.info(f"Marry mode: {self.marry_mode}")
logger.info(f"YOLO mode: {self.yolo_mode}")
logger.info(f"Exit strategy: {self.exit_strategy}")
if self.exit_strategy == "tp_sl":
logger.info(f"Take profit: {self.take_profit_percentage * 100 if self.take_profit_percentage else 'None'}%")
logger.info(f"Stop loss: {self.stop_loss_percentage * 100 if self.stop_loss_percentage else 'None'}%")
logger.info(f"Max hold time: {self.max_hold_time if self.max_hold_time else 'None'} seconds")
logger.info(
f"Take profit: {self.take_profit_percentage * 100 if self.take_profit_percentage else 'None'}%"
)
logger.info(
f"Stop loss: {self.stop_loss_percentage * 100 if self.stop_loss_percentage else 'None'}%"
)
logger.info(
f"Max hold time: {self.max_hold_time if self.max_hold_time else 'None'} seconds"
)
logger.info(f"Max token age: {self.max_token_age} seconds")
try:
@@ -253,19 +262,23 @@ class PumpTrader:
# Choose operating mode based on yolo_mode
if not self.yolo_mode:
# Single token mode: process one token and exit
logger.info("Running in single token mode - will process one token and exit")
logger.info(
"Running in single token mode - will process one token and exit"
)
token_info = await self._wait_for_token()
if token_info:
await self._handle_token(token_info)
logger.info("Finished processing single token. Exiting...")
else:
logger.info(f"No suitable token found within timeout period ({self.token_wait_timeout}s). Exiting...")
logger.info(
f"No suitable token found within timeout period ({self.token_wait_timeout}s). Exiting..."
)
else:
# Continuous mode: process tokens until interrupted
logger.info("Running in continuous mode - will process tokens until interrupted")
processor_task = asyncio.create_task(
self._process_token_queue()
logger.info(
"Running in continuous mode - will process tokens until interrupted"
)
processor_task = asyncio.create_task(self._process_token_queue())
try:
await self.token_listener.listen_for_tokens(
@@ -281,28 +294,28 @@ class PumpTrader:
await processor_task
except asyncio.CancelledError:
pass
except Exception as e:
logger.error(f"Trading stopped due to error: {e!s}")
finally:
await self._cleanup_resources()
logger.info("Pump trader has shut down")
async def _wait_for_token(self) -> TokenInfo | None:
"""Wait for a single token to be detected.
Returns:
TokenInfo or None if timeout occurs
"""
# Create a one-time event to signal when a token is found
token_found = asyncio.Event()
found_token = None
async def token_callback(token: TokenInfo) -> None:
nonlocal found_token
token_key = str(token.mint)
# Only process if not already processed and fresh
if token_key not in self.processed_tokens:
# Record when the token was discovered
@@ -310,7 +323,7 @@ class PumpTrader:
found_token = token
self.processed_tokens.add(token_key)
token_found.set()
listener_task = asyncio.create_task(
self.token_listener.listen_for_tokens(
token_callback,
@@ -318,15 +331,19 @@ class PumpTrader:
self.bro_address,
)
)
# Wait for a token with a timeout
try:
logger.info(f"Waiting for a suitable token (timeout: {self.token_wait_timeout}s)...")
logger.info(
f"Waiting for a suitable token (timeout: {self.token_wait_timeout}s)..."
)
await asyncio.wait_for(token_found.wait(), timeout=self.token_wait_timeout)
logger.info(f"Found token: {found_token.symbol} ({found_token.mint})")
return found_token
except TimeoutError:
logger.info(f"Timed out after waiting {self.token_wait_timeout}s for a token")
logger.info(
f"Timed out after waiting {self.token_wait_timeout}s for a token"
)
return None
finally:
listener_task.cancel()
@@ -341,28 +358,26 @@ class PumpTrader:
try:
logger.info(f"Cleaning up {len(self.traded_mints)} traded token(s)...")
await handle_cleanup_post_session(
self.solana_client,
self.wallet,
list(self.traded_mints),
self.solana_client,
self.wallet,
list(self.traded_mints),
self.priority_fee_manager,
self.cleanup_mode,
self.cleanup_with_priority_fee,
self.cleanup_force_close_with_burn
self.cleanup_force_close_with_burn,
)
except Exception as e:
logger.error(f"Error during cleanup: {e!s}")
old_keys = {k for k in self.token_timestamps if k not in self.processed_tokens}
for key in old_keys:
self.token_timestamps.pop(key, None)
await self.solana_client.close()
async def _queue_token(
self, token_info: TokenInfo
) -> None:
async def _queue_token(self, token_info: TokenInfo) -> None:
"""Queue a token for processing if not already processed.
Args:
token_info: Token information to queue
"""
@@ -413,9 +428,7 @@ class PumpTrader:
finally:
self.token_queue.task_done()
async def _handle_token(
self, token_info: TokenInfo
) -> None:
async def _handle_token(self, token_info: TokenInfo) -> None:
"""Handle a new token creation event.
Args:
@@ -456,7 +469,7 @@ class PumpTrader:
self, token_info: TokenInfo, buy_result: TradeResult
) -> None:
"""Handle successful token purchase.
Args:
token_info: Token information
buy_result: The result of the buy operation
@@ -470,7 +483,7 @@ class PumpTrader:
buy_result.tx_signature,
)
self.traded_mints.add(token_info.mint)
# Choose exit strategy
if not self.marry_mode:
if self.exit_strategy == "tp_sl":
@@ -486,28 +499,28 @@ class PumpTrader:
self, token_info: TokenInfo, buy_result: TradeResult
) -> None:
"""Handle failed token purchase.
Args:
token_info: Token information
buy_result: The result of the buy operation
"""
logger.error(
f"Failed to buy {token_info.symbol}: {buy_result.error_message}"
)
logger.error(f"Failed to buy {token_info.symbol}: {buy_result.error_message}")
# Close ATA if enabled
await handle_cleanup_after_failure(
self.solana_client,
self.wallet,
token_info.mint,
self.solana_client,
self.wallet,
token_info.mint,
self.priority_fee_manager,
self.cleanup_mode,
self.cleanup_with_priority_fee,
self.cleanup_force_close_with_burn
self.cleanup_force_close_with_burn,
)
async def _handle_tp_sl_exit(self, token_info: TokenInfo, buy_result: TradeResult) -> None:
async def _handle_tp_sl_exit(
self, token_info: TokenInfo, buy_result: TradeResult
) -> None:
"""Handle take profit/stop loss exit strategy.
Args:
token_info: Token information
buy_result: Result from the buy operation
@@ -517,30 +530,28 @@ class PumpTrader:
mint=token_info.mint,
symbol=token_info.symbol,
entry_price=buy_result.price, # type: ignore
quantity=buy_result.amount, # type: ignore
quantity=buy_result.amount, # type: ignore
take_profit_percentage=self.take_profit_percentage,
stop_loss_percentage=self.stop_loss_percentage,
max_hold_time=self.max_hold_time,
)
logger.info(f"Created position: {position}")
if position.take_profit_price:
logger.info(f"Take profit target: {position.take_profit_price:.8f} SOL")
if position.stop_loss_price:
logger.info(f"Stop loss target: {position.stop_loss_price:.8f} SOL")
# Monitor position until exit condition is met
await self._monitor_position_until_exit(token_info, position)
async def _handle_time_based_exit(self, token_info: TokenInfo) -> None:
"""Handle legacy time-based exit strategy.
Args:
token_info: Token information
"""
logger.info(
f"Waiting for {self.wait_time_after_buy} seconds before selling..."
)
logger.info(f"Waiting for {self.wait_time_after_buy} seconds before selling...")
await asyncio.sleep(self.wait_time_after_buy)
logger.info(f"Selling {token_info.symbol}...")
@@ -557,52 +568,62 @@ class PumpTrader:
)
# Close ATA if enabled
await handle_cleanup_after_sell(
self.solana_client,
self.wallet,
token_info.mint,
self.solana_client,
self.wallet,
token_info.mint,
self.priority_fee_manager,
self.cleanup_mode,
self.cleanup_with_priority_fee,
self.cleanup_force_close_with_burn
self.cleanup_force_close_with_burn,
)
else:
logger.error(
f"Failed to sell {token_info.symbol}: {sell_result.error_message}"
)
async def _monitor_position_until_exit(self, token_info: TokenInfo, position: Position) -> None:
async def _monitor_position_until_exit(
self, token_info: TokenInfo, position: Position
) -> None:
"""Monitor a position until exit conditions are met.
Args:
token_info: Token information
position: Position to monitor
"""
logger.info(f"Starting position monitoring (check interval: {self.price_check_interval}s)")
logger.info(
f"Starting position monitoring (check interval: {self.price_check_interval}s)"
)
while position.is_active:
try:
# Get current price from bonding curve
current_price = await self.curve_manager.calculate_price(token_info.bonding_curve)
current_price = await self.curve_manager.calculate_price(
token_info.bonding_curve
)
# Check if position should be exited
should_exit, exit_reason = position.should_exit(current_price)
if should_exit and exit_reason:
logger.info(f"Exit condition met: {exit_reason.value}")
logger.info(f"Current price: {current_price:.8f} SOL")
# Log PnL before exit
pnl = position.get_pnl(current_price)
logger.info(f"Position PnL: {pnl['price_change_pct']:.2f}% ({pnl['unrealized_pnl_sol']:.6f} SOL)")
logger.info(
f"Position PnL: {pnl['price_change_pct']:.2f}% ({pnl['unrealized_pnl_sol']:.6f} SOL)"
)
# Execute sell
sell_result = await self.seller.execute(token_info)
if sell_result.success:
# Close position with actual exit price
position.close_position(sell_result.price, exit_reason) # type: ignore
logger.info(f"Successfully exited position: {exit_reason.value}")
logger.info(
f"Successfully exited position: {exit_reason.value}"
)
self._log_trade(
"sell",
token_info,
@@ -610,41 +631,47 @@ class PumpTrader:
sell_result.amount, # type: ignore
sell_result.tx_signature,
)
# Log final PnL
final_pnl = position.get_pnl()
logger.info(f"Final PnL: {final_pnl['price_change_pct']:.2f}% ({final_pnl['unrealized_pnl_sol']:.6f} SOL)")
logger.info(
f"Final PnL: {final_pnl['price_change_pct']:.2f}% ({final_pnl['unrealized_pnl_sol']:.6f} SOL)"
)
# Close ATA if enabled
await handle_cleanup_after_sell(
self.solana_client,
self.wallet,
token_info.mint,
self.solana_client,
self.wallet,
token_info.mint,
self.priority_fee_manager,
self.cleanup_mode,
self.cleanup_with_priority_fee,
self.cleanup_force_close_with_burn
self.cleanup_force_close_with_burn,
)
else:
logger.error(f"Failed to exit position: {sell_result.error_message}")
logger.error(
f"Failed to exit position: {sell_result.error_message}"
)
# Keep monitoring in case sell can be retried
break
else:
# Log current status
pnl = position.get_pnl(current_price)
logger.debug(f"Position status: {current_price:.8f} SOL ({pnl['price_change_pct']:+.2f}%)")
logger.debug(
f"Position status: {current_price:.8f} SOL ({pnl['price_change_pct']:+.2f}%)"
)
# Wait before next price check
await asyncio.sleep(self.price_check_interval)
except Exception as e:
logger.error(f"Error monitoring position: {e}")
await asyncio.sleep(self.price_check_interval) # Continue monitoring despite errors
await asyncio.sleep(
self.price_check_interval
) # Continue monitoring despite errors
async def _save_token_info(
self, token_info: TokenInfo
) -> None:
async def _save_token_info(self, token_info: TokenInfo) -> None:
"""Save token information to a file.
Args:
@@ -694,4 +721,4 @@ class PumpTrader:
with open("trades/trades.log", "a") as log_file:
log_file.write(json.dumps(log_entry) + "\n")
except Exception as e:
logger.error(f"Failed to log trade information: {e!s}")
logger.error(f"Failed to log trade information: {e!s}")
+4 -1
View File
@@ -43,7 +43,10 @@ def setup_file_logging(
# Check if file handler with same filename already exists
for handler in root_logger.handlers:
if isinstance(handler, logging.FileHandler) and handler.baseFilename == filename:
if (
isinstance(handler, logging.FileHandler)
and handler.baseFilename == filename
):
return # File handler already added
formatter = logging.Formatter(
Generated
+1186
View File
File diff suppressed because it is too large Load Diff