Merge pull request #86 from chainstacklabs/feat/geyser-listener-for-bot

Add Geyser listener support to bot
This commit is contained in:
Anton
2025-04-15 06:52:43 +00:00
committed by GitHub
19 changed files with 2261 additions and 64 deletions
+2
View File
@@ -1,3 +1,5 @@
SOLANA_NODE_RPC_ENDPOINT=...
SOLANA_NODE_WSS_ENDPOINT=...
GEYSER_ENDPOINT=...
GEYSER_API_TOKEN=...
SOLANA_PRIVATE_KEY=...
+13
View File
@@ -65,6 +65,9 @@ async def main() -> None:
rpc_endpoint: str | None = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
wss_endpoint: str | None = os.environ.get("SOLANA_NODE_WSS_ENDPOINT")
private_key: str | None = os.environ.get("SOLANA_PRIVATE_KEY")
geyser_endpoint: str | None = os.environ.get("GEYSER_ENDPOINT")
geyser_api_token: str | None = os.environ.get("GEYSER_API_TOKEN")
# Validate configuration values
if not rpc_endpoint or not rpc_endpoint.startswith(("http://", "https://")):
@@ -79,6 +82,14 @@ async def main() -> None:
logger.error("Invalid private key. Key appears to be missing or too short")
sys.exit(1)
if config.LISTENER_TYPE.lower() == "geyser":
if not geyser_endpoint:
logger.error("GEYSER_ENDPOINT environment variable is not set")
sys.exit(1)
if not geyser_api_token:
logger.error("GEYSER_API_TOKEN environment variable is not set")
sys.exit(1)
# Get trading parameters
buy_amount: float = args.amount if args.amount is not None else config.BUY_AMOUNT
buy_slippage: float = (
@@ -97,6 +108,8 @@ async def main() -> None:
sell_slippage=sell_slippage,
max_retries=config.MAX_RETRIES,
listener_type=config.LISTENER_TYPE,
geyser_endpoint=geyser_endpoint,
geyser_api_token=geyser_api_token,
)
try:
+6 -5
View File
@@ -15,7 +15,7 @@ SELL_SLIPPAGE: float = 0.4 # Consistent slippage tolerance to maintain trading
# EXTREME FAST Mode configuration
# When enabled, skips waiting for the bonding curve to stabilize and RPC price check.
# The bot buys the specified number of tokens directly, making the process faster but less precise.
EXTREME_FAST_MODE: bool = False
EXTREME_FAST_MODE: bool = True
# Amount of tokens to buy in EXTREME FAST mode. No price calculation is done; the bot buys exactly this amount.
EXTREME_FAST_TOKEN_AMOUNT: int = 30
@@ -24,15 +24,16 @@ EXTREME_FAST_TOKEN_AMOUNT: int = 30
# Manage transaction speed and cost on the Solana network
ENABLE_DYNAMIC_PRIORITY_FEE: bool = False # Adaptive fee calculation
ENABLE_FIXED_PRIORITY_FEE: bool = True # Use consistent, predictable fee
FIXED_PRIORITY_FEE: int = 2_000 # Base fee in microlamports
FIXED_PRIORITY_FEE: int = 500_000 # Base fee in microlamports
EXTRA_PRIORITY_FEE: float = 0.0 # Percentage increase on priority fee (0.1 = 10%)
HARD_CAP_PRIOR_FEE: int = 200_000 # Maximum allowable fee to prevent excessive spending in microlamports
HARD_CAP_PRIOR_FEE: int = 500_000 # Maximum allowable fee to prevent excessive spending in microlamports
# Listener configuration
# Choose method for detecting new tokens on the network
# "logs": Recommended for more stable token detection
# "blocks": Unstable method, potentially less reliable
# "geyser": The fastest way for getting updates, requires Geyser endpoint
LISTENER_TYPE = "logs"
@@ -48,7 +49,7 @@ WAIT_TIME_BEFORE_NEW_TOKEN: int | float = 15 # Pause between token trades
# Token and account management
# Control token processing and account cleanup strategies
MAX_TOKEN_AGE: int | float = 0.1 # Maximum token age in seconds for processing
MAX_TOKEN_AGE: int | float = 0.001 # Maximum token age in seconds for processing
# Cleanup mode determines when to manage token accounts. Options:
# "disabled": No cleanup will occur.
@@ -98,7 +99,7 @@ def validate_configuration() -> None:
raise ValueError("Cannot enable both dynamic and fixed priority fees simultaneously")
# Validate listener type
if LISTENER_TYPE not in ["logs", "blocks"]:
if LISTENER_TYPE not in ["logs", "blocks", "geyser"]:
raise ValueError("LISTENER_TYPE must be either 'logs' or 'blocks'")
# Validate cleanup mode
+2 -2
View File
@@ -8,7 +8,7 @@ from typing import Any
import aiohttp
from solana.rpc.async_api import AsyncClient
from solana.rpc.commitment import Confirmed
from solana.rpc.commitment import Processed
from solana.rpc.types import TxOpts
from solders.compute_budget import set_compute_unit_limit, set_compute_unit_price
from solders.hash import Hash
@@ -163,7 +163,7 @@ class SolanaClient:
for attempt in range(max_retries):
try:
tx_opts = TxOpts(
skip_preflight=skip_preflight, preflight_commitment=Confirmed
skip_preflight=skip_preflight, preflight_commitment=Processed
)
response = await client.send_transaction(transaction, tx_opts)
return response.value
File diff suppressed because one or more lines are too long
+485
View File
@@ -0,0 +1,485 @@
import solana_storage_pb2 as _solana_storage_pb2
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 solana_storage_pb2 import ConfirmedBlock as ConfirmedBlock
from solana_storage_pb2 import ConfirmedTransaction as ConfirmedTransaction
from solana_storage_pb2 import Transaction as Transaction
from solana_storage_pb2 import Message as Message
from solana_storage_pb2 import MessageHeader as MessageHeader
from solana_storage_pb2 import MessageAddressTableLookup as MessageAddressTableLookup
from solana_storage_pb2 import TransactionStatusMeta as TransactionStatusMeta
from solana_storage_pb2 import TransactionError as TransactionError
from solana_storage_pb2 import InnerInstructions as InnerInstructions
from solana_storage_pb2 import InnerInstruction as InnerInstruction
from solana_storage_pb2 import CompiledInstruction as CompiledInstruction
from solana_storage_pb2 import TokenBalance as TokenBalance
from solana_storage_pb2 import UiTokenAmount as UiTokenAmount
from solana_storage_pb2 import ReturnData as ReturnData
from solana_storage_pb2 import Reward as Reward
from solana_storage_pb2 import Rewards as Rewards
from solana_storage_pb2 import UnixTimestamp as UnixTimestamp
from solana_storage_pb2 import BlockHeight as BlockHeight
from solana_storage_pb2 import NumPartitions as NumPartitions
from solana_storage_pb2 import RewardType as RewardType
DESCRIPTOR: _descriptor.FileDescriptor
Unspecified: _solana_storage_pb2.RewardType
Fee: _solana_storage_pb2.RewardType
Rent: _solana_storage_pb2.RewardType
Staking: _solana_storage_pb2.RewardType
Voting: _solana_storage_pb2.RewardType
class CommitmentLevel(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
PROCESSED: _ClassVar[CommitmentLevel]
CONFIRMED: _ClassVar[CommitmentLevel]
FINALIZED: _ClassVar[CommitmentLevel]
FIRST_SHRED_RECEIVED: _ClassVar[CommitmentLevel]
COMPLETED: _ClassVar[CommitmentLevel]
CREATED_BANK: _ClassVar[CommitmentLevel]
DEAD: _ClassVar[CommitmentLevel]
PROCESSED: CommitmentLevel
CONFIRMED: CommitmentLevel
FINALIZED: CommitmentLevel
FIRST_SHRED_RECEIVED: CommitmentLevel
COMPLETED: CommitmentLevel
CREATED_BANK: CommitmentLevel
DEAD: CommitmentLevel
class SubscribeRequest(_message.Message):
__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: ...
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: ...
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: ...
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: ...
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: ...
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: ...
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: ...
ACCOUNTS_FIELD_NUMBER: _ClassVar[int]
SLOTS_FIELD_NUMBER: _ClassVar[int]
TRANSACTIONS_FIELD_NUMBER: _ClassVar[int]
TRANSACTIONS_STATUS_FIELD_NUMBER: _ClassVar[int]
BLOCKS_FIELD_NUMBER: _ClassVar[int]
BLOCKS_META_FIELD_NUMBER: _ClassVar[int]
ENTRY_FIELD_NUMBER: _ClassVar[int]
COMMITMENT_FIELD_NUMBER: _ClassVar[int]
ACCOUNTS_DATA_SLICE_FIELD_NUMBER: _ClassVar[int]
PING_FIELD_NUMBER: _ClassVar[int]
accounts: _containers.MessageMap[str, SubscribeRequestFilterAccounts]
slots: _containers.MessageMap[str, SubscribeRequestFilterSlots]
transactions: _containers.MessageMap[str, SubscribeRequestFilterTransactions]
transactions_status: _containers.MessageMap[str, SubscribeRequestFilterTransactions]
blocks: _containers.MessageMap[str, SubscribeRequestFilterBlocks]
blocks_meta: _containers.MessageMap[str, SubscribeRequestFilterBlocksMeta]
entry: _containers.MessageMap[str, SubscribeRequestFilterEntry]
commitment: CommitmentLevel
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: ...
class SubscribeRequestFilterAccounts(_message.Message):
__slots__ = ("account", "owner", "filters", "nonempty_txn_signature")
ACCOUNT_FIELD_NUMBER: _ClassVar[int]
OWNER_FIELD_NUMBER: _ClassVar[int]
FILTERS_FIELD_NUMBER: _ClassVar[int]
NONEMPTY_TXN_SIGNATURE_FIELD_NUMBER: _ClassVar[int]
account: _containers.RepeatedScalarFieldContainer[str]
owner: _containers.RepeatedScalarFieldContainer[str]
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: ...
class SubscribeRequestFilterAccountsFilter(_message.Message):
__slots__ = ("memcmp", "datasize", "token_account_state", "lamports")
MEMCMP_FIELD_NUMBER: _ClassVar[int]
DATASIZE_FIELD_NUMBER: _ClassVar[int]
TOKEN_ACCOUNT_STATE_FIELD_NUMBER: _ClassVar[int]
LAMPORTS_FIELD_NUMBER: _ClassVar[int]
memcmp: SubscribeRequestFilterAccountsFilterMemcmp
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: ...
class SubscribeRequestFilterAccountsFilterMemcmp(_message.Message):
__slots__ = ("offset", "bytes", "base58", "base64")
OFFSET_FIELD_NUMBER: _ClassVar[int]
BYTES_FIELD_NUMBER: _ClassVar[int]
BASE58_FIELD_NUMBER: _ClassVar[int]
BASE64_FIELD_NUMBER: _ClassVar[int]
offset: int
bytes: bytes
base58: str
base64: str
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")
EQ_FIELD_NUMBER: _ClassVar[int]
NE_FIELD_NUMBER: _ClassVar[int]
LT_FIELD_NUMBER: _ClassVar[int]
GT_FIELD_NUMBER: _ClassVar[int]
eq: int
ne: int
lt: int
gt: int
def __init__(self, eq: _Optional[int] = ..., ne: _Optional[int] = ..., lt: _Optional[int] = ..., gt: _Optional[int] = ...) -> None: ...
class SubscribeRequestFilterSlots(_message.Message):
__slots__ = ("filter_by_commitment",)
FILTER_BY_COMMITMENT_FIELD_NUMBER: _ClassVar[int]
filter_by_commitment: bool
def __init__(self, filter_by_commitment: bool = ...) -> None: ...
class SubscribeRequestFilterTransactions(_message.Message):
__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]
ACCOUNT_INCLUDE_FIELD_NUMBER: _ClassVar[int]
ACCOUNT_EXCLUDE_FIELD_NUMBER: _ClassVar[int]
ACCOUNT_REQUIRED_FIELD_NUMBER: _ClassVar[int]
vote: bool
failed: bool
signature: str
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: ...
class SubscribeRequestFilterBlocks(_message.Message):
__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]
INCLUDE_ENTRIES_FIELD_NUMBER: _ClassVar[int]
account_include: _containers.RepeatedScalarFieldContainer[str]
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: ...
class SubscribeRequestFilterBlocksMeta(_message.Message):
__slots__ = ()
def __init__(self) -> None: ...
class SubscribeRequestFilterEntry(_message.Message):
__slots__ = ()
def __init__(self) -> None: ...
class SubscribeRequestAccountsDataSlice(_message.Message):
__slots__ = ("offset", "length")
OFFSET_FIELD_NUMBER: _ClassVar[int]
LENGTH_FIELD_NUMBER: _ClassVar[int]
offset: int
length: int
def __init__(self, offset: _Optional[int] = ..., length: _Optional[int] = ...) -> None: ...
class SubscribeRequestPing(_message.Message):
__slots__ = ("id",)
ID_FIELD_NUMBER: _ClassVar[int]
id: int
def __init__(self, id: _Optional[int] = ...) -> None: ...
class SubscribeUpdate(_message.Message):
__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]
TRANSACTION_FIELD_NUMBER: _ClassVar[int]
TRANSACTION_STATUS_FIELD_NUMBER: _ClassVar[int]
BLOCK_FIELD_NUMBER: _ClassVar[int]
PING_FIELD_NUMBER: _ClassVar[int]
PONG_FIELD_NUMBER: _ClassVar[int]
BLOCK_META_FIELD_NUMBER: _ClassVar[int]
ENTRY_FIELD_NUMBER: _ClassVar[int]
filters: _containers.RepeatedScalarFieldContainer[str]
account: SubscribeUpdateAccount
slot: SubscribeUpdateSlot
transaction: SubscribeUpdateTransaction
transaction_status: SubscribeUpdateTransactionStatus
block: SubscribeUpdateBlock
ping: SubscribeUpdatePing
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: ...
class SubscribeUpdateAccount(_message.Message):
__slots__ = ("account", "slot", "is_startup")
ACCOUNT_FIELD_NUMBER: _ClassVar[int]
SLOT_FIELD_NUMBER: _ClassVar[int]
IS_STARTUP_FIELD_NUMBER: _ClassVar[int]
account: SubscribeUpdateAccountInfo
slot: int
is_startup: bool
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")
PUBKEY_FIELD_NUMBER: _ClassVar[int]
LAMPORTS_FIELD_NUMBER: _ClassVar[int]
OWNER_FIELD_NUMBER: _ClassVar[int]
EXECUTABLE_FIELD_NUMBER: _ClassVar[int]
RENT_EPOCH_FIELD_NUMBER: _ClassVar[int]
DATA_FIELD_NUMBER: _ClassVar[int]
WRITE_VERSION_FIELD_NUMBER: _ClassVar[int]
TXN_SIGNATURE_FIELD_NUMBER: _ClassVar[int]
pubkey: bytes
lamports: int
owner: bytes
executable: bool
rent_epoch: int
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: ...
class SubscribeUpdateSlot(_message.Message):
__slots__ = ("slot", "parent", "status", "dead_error")
SLOT_FIELD_NUMBER: _ClassVar[int]
PARENT_FIELD_NUMBER: _ClassVar[int]
STATUS_FIELD_NUMBER: _ClassVar[int]
DEAD_ERROR_FIELD_NUMBER: _ClassVar[int]
slot: int
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: ...
class SubscribeUpdateTransaction(_message.Message):
__slots__ = ("transaction", "slot")
TRANSACTION_FIELD_NUMBER: _ClassVar[int]
SLOT_FIELD_NUMBER: _ClassVar[int]
transaction: SubscribeUpdateTransactionInfo
slot: int
def __init__(self, transaction: _Optional[_Union[SubscribeUpdateTransactionInfo, _Mapping]] = ..., slot: _Optional[int] = ...) -> None: ...
class SubscribeUpdateTransactionInfo(_message.Message):
__slots__ = ("signature", "is_vote", "transaction", "meta", "index")
SIGNATURE_FIELD_NUMBER: _ClassVar[int]
IS_VOTE_FIELD_NUMBER: _ClassVar[int]
TRANSACTION_FIELD_NUMBER: _ClassVar[int]
META_FIELD_NUMBER: _ClassVar[int]
INDEX_FIELD_NUMBER: _ClassVar[int]
signature: bytes
is_vote: bool
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: ...
class SubscribeUpdateTransactionStatus(_message.Message):
__slots__ = ("slot", "signature", "is_vote", "index", "err")
SLOT_FIELD_NUMBER: _ClassVar[int]
SIGNATURE_FIELD_NUMBER: _ClassVar[int]
IS_VOTE_FIELD_NUMBER: _ClassVar[int]
INDEX_FIELD_NUMBER: _ClassVar[int]
ERR_FIELD_NUMBER: _ClassVar[int]
slot: int
signature: bytes
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: ...
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")
SLOT_FIELD_NUMBER: _ClassVar[int]
BLOCKHASH_FIELD_NUMBER: _ClassVar[int]
REWARDS_FIELD_NUMBER: _ClassVar[int]
BLOCK_TIME_FIELD_NUMBER: _ClassVar[int]
BLOCK_HEIGHT_FIELD_NUMBER: _ClassVar[int]
PARENT_SLOT_FIELD_NUMBER: _ClassVar[int]
PARENT_BLOCKHASH_FIELD_NUMBER: _ClassVar[int]
EXECUTED_TRANSACTION_COUNT_FIELD_NUMBER: _ClassVar[int]
TRANSACTIONS_FIELD_NUMBER: _ClassVar[int]
UPDATED_ACCOUNT_COUNT_FIELD_NUMBER: _ClassVar[int]
ACCOUNTS_FIELD_NUMBER: _ClassVar[int]
ENTRIES_COUNT_FIELD_NUMBER: _ClassVar[int]
ENTRIES_FIELD_NUMBER: _ClassVar[int]
slot: int
blockhash: str
rewards: _solana_storage_pb2.Rewards
block_time: _solana_storage_pb2.UnixTimestamp
block_height: _solana_storage_pb2.BlockHeight
parent_slot: int
parent_blockhash: str
executed_transaction_count: int
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: ...
class SubscribeUpdateBlockMeta(_message.Message):
__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]
BLOCK_TIME_FIELD_NUMBER: _ClassVar[int]
BLOCK_HEIGHT_FIELD_NUMBER: _ClassVar[int]
PARENT_SLOT_FIELD_NUMBER: _ClassVar[int]
PARENT_BLOCKHASH_FIELD_NUMBER: _ClassVar[int]
EXECUTED_TRANSACTION_COUNT_FIELD_NUMBER: _ClassVar[int]
ENTRIES_COUNT_FIELD_NUMBER: _ClassVar[int]
slot: int
blockhash: str
rewards: _solana_storage_pb2.Rewards
block_time: _solana_storage_pb2.UnixTimestamp
block_height: _solana_storage_pb2.BlockHeight
parent_slot: int
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: ...
class SubscribeUpdateEntry(_message.Message):
__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]
HASH_FIELD_NUMBER: _ClassVar[int]
EXECUTED_TRANSACTION_COUNT_FIELD_NUMBER: _ClassVar[int]
STARTING_TRANSACTION_INDEX_FIELD_NUMBER: _ClassVar[int]
slot: int
index: int
num_hashes: int
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: ...
class SubscribeUpdatePing(_message.Message):
__slots__ = ()
def __init__(self) -> None: ...
class SubscribeUpdatePong(_message.Message):
__slots__ = ("id",)
ID_FIELD_NUMBER: _ClassVar[int]
id: int
def __init__(self, id: _Optional[int] = ...) -> None: ...
class PingRequest(_message.Message):
__slots__ = ("count",)
COUNT_FIELD_NUMBER: _ClassVar[int]
count: int
def __init__(self, count: _Optional[int] = ...) -> None: ...
class PongResponse(_message.Message):
__slots__ = ("count",)
COUNT_FIELD_NUMBER: _ClassVar[int]
count: int
def __init__(self, count: _Optional[int] = ...) -> None: ...
class GetLatestBlockhashRequest(_message.Message):
__slots__ = ("commitment",)
COMMITMENT_FIELD_NUMBER: _ClassVar[int]
commitment: CommitmentLevel
def __init__(self, commitment: _Optional[_Union[CommitmentLevel, str]] = ...) -> None: ...
class GetLatestBlockhashResponse(_message.Message):
__slots__ = ("slot", "blockhash", "last_valid_block_height")
SLOT_FIELD_NUMBER: _ClassVar[int]
BLOCKHASH_FIELD_NUMBER: _ClassVar[int]
LAST_VALID_BLOCK_HEIGHT_FIELD_NUMBER: _ClassVar[int]
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: ...
class GetBlockHeightRequest(_message.Message):
__slots__ = ("commitment",)
COMMITMENT_FIELD_NUMBER: _ClassVar[int]
commitment: CommitmentLevel
def __init__(self, commitment: _Optional[_Union[CommitmentLevel, str]] = ...) -> None: ...
class GetBlockHeightResponse(_message.Message):
__slots__ = ("block_height",)
BLOCK_HEIGHT_FIELD_NUMBER: _ClassVar[int]
block_height: int
def __init__(self, block_height: _Optional[int] = ...) -> None: ...
class GetSlotRequest(_message.Message):
__slots__ = ("commitment",)
COMMITMENT_FIELD_NUMBER: _ClassVar[int]
commitment: CommitmentLevel
def __init__(self, commitment: _Optional[_Union[CommitmentLevel, str]] = ...) -> None: ...
class GetSlotResponse(_message.Message):
__slots__ = ("slot",)
SLOT_FIELD_NUMBER: _ClassVar[int]
slot: int
def __init__(self, slot: _Optional[int] = ...) -> None: ...
class GetVersionRequest(_message.Message):
__slots__ = ()
def __init__(self) -> None: ...
class GetVersionResponse(_message.Message):
__slots__ = ("version",)
VERSION_FIELD_NUMBER: _ClassVar[int]
version: str
def __init__(self, version: _Optional[str] = ...) -> None: ...
class IsBlockhashValidRequest(_message.Message):
__slots__ = ("blockhash", "commitment")
BLOCKHASH_FIELD_NUMBER: _ClassVar[int]
COMMITMENT_FIELD_NUMBER: _ClassVar[int]
blockhash: str
commitment: CommitmentLevel
def __init__(self, blockhash: _Optional[str] = ..., commitment: _Optional[_Union[CommitmentLevel, str]] = ...) -> None: ...
class IsBlockhashValidResponse(_message.Message):
__slots__ = ("slot", "valid")
SLOT_FIELD_NUMBER: _ClassVar[int]
VALID_FIELD_NUMBER: _ClassVar[int]
slot: int
valid: bool
def __init__(self, slot: _Optional[int] = ..., valid: bool = ...) -> None: ...
+355
View File
@@ -0,0 +1,355 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import geyser.generated.geyser_pb2 as geyser__pb2
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)
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}.'
)
class GeyserStub:
"""Missing associated documentation comment in .proto file."""
def __init__(self, channel):
"""Constructor.
Args:
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)
self.Ping = channel.unary_unary(
'/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)
self.GetBlockHeight = channel.unary_unary(
'/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)
self.IsBlockhashValid = channel.unary_unary(
'/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)
class GeyserServicer:
"""Missing associated documentation comment in .proto file."""
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!')
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!')
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!')
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!')
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!')
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!')
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!')
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,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'geyser.Geyser', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
server.add_registered_method_handlers('geyser.Geyser', rpc_method_handlers)
# 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):
return grpc.experimental.stream_stream(
request_iterator,
target,
'/geyser.Geyser/Subscribe',
geyser__pb2.SubscribeRequest.SerializeToString,
geyser__pb2.SubscribeUpdate.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_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):
return grpc.experimental.unary_unary(
request,
target,
'/geyser.Geyser/Ping',
geyser__pb2.PingRequest.SerializeToString,
geyser__pb2.PongResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_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):
return grpc.experimental.unary_unary(
request,
target,
'/geyser.Geyser/GetLatestBlockhash',
geyser__pb2.GetLatestBlockhashRequest.SerializeToString,
geyser__pb2.GetLatestBlockhashResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_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):
return grpc.experimental.unary_unary(
request,
target,
'/geyser.Geyser/GetBlockHeight',
geyser__pb2.GetBlockHeightRequest.SerializeToString,
geyser__pb2.GetBlockHeightResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_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):
return grpc.experimental.unary_unary(
request,
target,
'/geyser.Geyser/GetSlot',
geyser__pb2.GetSlotRequest.SerializeToString,
geyser__pb2.GetSlotResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_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):
return grpc.experimental.unary_unary(
request,
target,
'/geyser.Geyser/IsBlockhashValid',
geyser__pb2.IsBlockhashValidRequest.SerializeToString,
geyser__pb2.IsBlockhashValidResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_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):
return grpc.experimental.unary_unary(
request,
target,
'/geyser.Geyser/GetVersion',
geyser__pb2.GetVersionRequest.SerializeToString,
geyser__pb2.GetVersionResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
File diff suppressed because one or more lines are too long
+238
View File
@@ -0,0 +1,238 @@
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
DESCRIPTOR: _descriptor.FileDescriptor
class RewardType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
Unspecified: _ClassVar[RewardType]
Fee: _ClassVar[RewardType]
Rent: _ClassVar[RewardType]
Staking: _ClassVar[RewardType]
Voting: _ClassVar[RewardType]
Unspecified: RewardType
Fee: RewardType
Rent: RewardType
Staking: RewardType
Voting: RewardType
class ConfirmedBlock(_message.Message):
__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]
TRANSACTIONS_FIELD_NUMBER: _ClassVar[int]
REWARDS_FIELD_NUMBER: _ClassVar[int]
BLOCK_TIME_FIELD_NUMBER: _ClassVar[int]
BLOCK_HEIGHT_FIELD_NUMBER: _ClassVar[int]
NUM_PARTITIONS_FIELD_NUMBER: _ClassVar[int]
previous_blockhash: str
blockhash: str
parent_slot: int
transactions: _containers.RepeatedCompositeFieldContainer[ConfirmedTransaction]
rewards: _containers.RepeatedCompositeFieldContainer[Reward]
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: ...
class ConfirmedTransaction(_message.Message):
__slots__ = ("transaction", "meta")
TRANSACTION_FIELD_NUMBER: _ClassVar[int]
META_FIELD_NUMBER: _ClassVar[int]
transaction: Transaction
meta: TransactionStatusMeta
def __init__(self, transaction: _Optional[_Union[Transaction, _Mapping]] = ..., meta: _Optional[_Union[TransactionStatusMeta, _Mapping]] = ...) -> None: ...
class Transaction(_message.Message):
__slots__ = ("signatures", "message")
SIGNATURES_FIELD_NUMBER: _ClassVar[int]
MESSAGE_FIELD_NUMBER: _ClassVar[int]
signatures: _containers.RepeatedScalarFieldContainer[bytes]
message: Message
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")
HEADER_FIELD_NUMBER: _ClassVar[int]
ACCOUNT_KEYS_FIELD_NUMBER: _ClassVar[int]
RECENT_BLOCKHASH_FIELD_NUMBER: _ClassVar[int]
INSTRUCTIONS_FIELD_NUMBER: _ClassVar[int]
VERSIONED_FIELD_NUMBER: _ClassVar[int]
ADDRESS_TABLE_LOOKUPS_FIELD_NUMBER: _ClassVar[int]
header: MessageHeader
account_keys: _containers.RepeatedScalarFieldContainer[bytes]
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: ...
class MessageHeader(_message.Message):
__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: ...
class MessageAddressTableLookup(_message.Message):
__slots__ = ("account_key", "writable_indexes", "readonly_indexes")
ACCOUNT_KEY_FIELD_NUMBER: _ClassVar[int]
WRITABLE_INDEXES_FIELD_NUMBER: _ClassVar[int]
READONLY_INDEXES_FIELD_NUMBER: _ClassVar[int]
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: ...
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")
ERR_FIELD_NUMBER: _ClassVar[int]
FEE_FIELD_NUMBER: _ClassVar[int]
PRE_BALANCES_FIELD_NUMBER: _ClassVar[int]
POST_BALANCES_FIELD_NUMBER: _ClassVar[int]
INNER_INSTRUCTIONS_FIELD_NUMBER: _ClassVar[int]
INNER_INSTRUCTIONS_NONE_FIELD_NUMBER: _ClassVar[int]
LOG_MESSAGES_FIELD_NUMBER: _ClassVar[int]
LOG_MESSAGES_NONE_FIELD_NUMBER: _ClassVar[int]
PRE_TOKEN_BALANCES_FIELD_NUMBER: _ClassVar[int]
POST_TOKEN_BALANCES_FIELD_NUMBER: _ClassVar[int]
REWARDS_FIELD_NUMBER: _ClassVar[int]
LOADED_WRITABLE_ADDRESSES_FIELD_NUMBER: _ClassVar[int]
LOADED_READONLY_ADDRESSES_FIELD_NUMBER: _ClassVar[int]
RETURN_DATA_FIELD_NUMBER: _ClassVar[int]
RETURN_DATA_NONE_FIELD_NUMBER: _ClassVar[int]
COMPUTE_UNITS_CONSUMED_FIELD_NUMBER: _ClassVar[int]
err: TransactionError
fee: int
pre_balances: _containers.RepeatedScalarFieldContainer[int]
post_balances: _containers.RepeatedScalarFieldContainer[int]
inner_instructions: _containers.RepeatedCompositeFieldContainer[InnerInstructions]
inner_instructions_none: bool
log_messages: _containers.RepeatedScalarFieldContainer[str]
log_messages_none: bool
pre_token_balances: _containers.RepeatedCompositeFieldContainer[TokenBalance]
post_token_balances: _containers.RepeatedCompositeFieldContainer[TokenBalance]
rewards: _containers.RepeatedCompositeFieldContainer[Reward]
loaded_writable_addresses: _containers.RepeatedScalarFieldContainer[bytes]
loaded_readonly_addresses: _containers.RepeatedScalarFieldContainer[bytes]
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: ...
class TransactionError(_message.Message):
__slots__ = ("err",)
ERR_FIELD_NUMBER: _ClassVar[int]
err: bytes
def __init__(self, err: _Optional[bytes] = ...) -> None: ...
class InnerInstructions(_message.Message):
__slots__ = ("index", "instructions")
INDEX_FIELD_NUMBER: _ClassVar[int]
INSTRUCTIONS_FIELD_NUMBER: _ClassVar[int]
index: int
instructions: _containers.RepeatedCompositeFieldContainer[InnerInstruction]
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")
PROGRAM_ID_INDEX_FIELD_NUMBER: _ClassVar[int]
ACCOUNTS_FIELD_NUMBER: _ClassVar[int]
DATA_FIELD_NUMBER: _ClassVar[int]
STACK_HEIGHT_FIELD_NUMBER: _ClassVar[int]
program_id_index: int
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: ...
class CompiledInstruction(_message.Message):
__slots__ = ("program_id_index", "accounts", "data")
PROGRAM_ID_INDEX_FIELD_NUMBER: _ClassVar[int]
ACCOUNTS_FIELD_NUMBER: _ClassVar[int]
DATA_FIELD_NUMBER: _ClassVar[int]
program_id_index: int
accounts: bytes
data: bytes
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")
ACCOUNT_INDEX_FIELD_NUMBER: _ClassVar[int]
MINT_FIELD_NUMBER: _ClassVar[int]
UI_TOKEN_AMOUNT_FIELD_NUMBER: _ClassVar[int]
OWNER_FIELD_NUMBER: _ClassVar[int]
PROGRAM_ID_FIELD_NUMBER: _ClassVar[int]
account_index: int
mint: str
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: ...
class UiTokenAmount(_message.Message):
__slots__ = ("ui_amount", "decimals", "amount", "ui_amount_string")
UI_AMOUNT_FIELD_NUMBER: _ClassVar[int]
DECIMALS_FIELD_NUMBER: _ClassVar[int]
AMOUNT_FIELD_NUMBER: _ClassVar[int]
UI_AMOUNT_STRING_FIELD_NUMBER: _ClassVar[int]
ui_amount: float
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: ...
class ReturnData(_message.Message):
__slots__ = ("program_id", "data")
PROGRAM_ID_FIELD_NUMBER: _ClassVar[int]
DATA_FIELD_NUMBER: _ClassVar[int]
program_id: bytes
data: bytes
def __init__(self, program_id: _Optional[bytes] = ..., data: _Optional[bytes] = ...) -> None: ...
class Reward(_message.Message):
__slots__ = ("pubkey", "lamports", "post_balance", "reward_type", "commission")
PUBKEY_FIELD_NUMBER: _ClassVar[int]
LAMPORTS_FIELD_NUMBER: _ClassVar[int]
POST_BALANCE_FIELD_NUMBER: _ClassVar[int]
REWARD_TYPE_FIELD_NUMBER: _ClassVar[int]
COMMISSION_FIELD_NUMBER: _ClassVar[int]
pubkey: str
lamports: int
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: ...
class Rewards(_message.Message):
__slots__ = ("rewards", "num_partitions")
REWARDS_FIELD_NUMBER: _ClassVar[int]
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: ...
class UnixTimestamp(_message.Message):
__slots__ = ("timestamp",)
TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
timestamp: int
def __init__(self, timestamp: _Optional[int] = ...) -> None: ...
class BlockHeight(_message.Message):
__slots__ = ("block_height",)
BLOCK_HEIGHT_FIELD_NUMBER: _ClassVar[int]
block_height: int
def __init__(self, block_height: _Optional[int] = ...) -> None: ...
class NumPartitions(_message.Message):
__slots__ = ("num_partitions",)
NUM_PARTITIONS_FIELD_NUMBER: _ClassVar[int]
num_partitions: int
def __init__(self, num_partitions: _Optional[int] = ...) -> None: ...
@@ -0,0 +1,24 @@
# 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_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)
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}.'
)
+262
View File
@@ -0,0 +1,262 @@
syntax = "proto3";
import public "solana-storage.proto";
option go_package = "github.com/rpcpool/yellowstone-grpc/examples/golang/proto";
package geyser;
service Geyser {
rpc Subscribe(stream SubscribeRequest) returns (stream SubscribeUpdate) {}
rpc Ping(PingRequest) returns (PongResponse) {}
rpc GetLatestBlockhash(GetLatestBlockhashRequest) returns (GetLatestBlockhashResponse) {}
rpc GetBlockHeight(GetBlockHeightRequest) returns (GetBlockHeightResponse) {}
rpc GetSlot(GetSlotRequest) returns (GetSlotResponse) {}
rpc IsBlockhashValid(IsBlockhashValidRequest) returns (IsBlockhashValidResponse) {}
rpc GetVersion(GetVersionRequest) returns (GetVersionResponse) {}
}
enum CommitmentLevel {
PROCESSED = 0;
CONFIRMED = 1;
FINALIZED = 2;
FIRST_SHRED_RECEIVED = 3;
COMPLETED = 4;
CREATED_BANK = 5;
DEAD = 6;
}
message SubscribeRequest {
map<string, SubscribeRequestFilterAccounts> accounts = 1;
map<string, SubscribeRequestFilterSlots> slots = 2;
map<string, SubscribeRequestFilterTransactions> transactions = 3;
map<string, SubscribeRequestFilterTransactions> transactions_status = 10;
map<string, SubscribeRequestFilterBlocks> blocks = 4;
map<string, SubscribeRequestFilterBlocksMeta> blocks_meta = 5;
map<string, SubscribeRequestFilterEntry> entry = 8;
optional CommitmentLevel commitment = 6;
repeated SubscribeRequestAccountsDataSlice accounts_data_slice = 7;
optional SubscribeRequestPing ping = 9;
}
message SubscribeRequestFilterAccounts {
repeated string account = 2;
repeated string owner = 3;
repeated SubscribeRequestFilterAccountsFilter filters = 4;
optional bool nonempty_txn_signature = 5;
}
message SubscribeRequestFilterAccountsFilter {
oneof filter {
SubscribeRequestFilterAccountsFilterMemcmp memcmp = 1;
uint64 datasize = 2;
bool token_account_state = 3;
SubscribeRequestFilterAccountsFilterLamports lamports = 4;
}
}
message SubscribeRequestFilterAccountsFilterMemcmp {
uint64 offset = 1;
oneof data {
bytes bytes = 2;
string base58 = 3;
string base64 = 4;
}
}
message SubscribeRequestFilterAccountsFilterLamports {
oneof cmp {
uint64 eq = 1;
uint64 ne = 2;
uint64 lt = 3;
uint64 gt = 4;
}
}
message SubscribeRequestFilterSlots {
optional bool filter_by_commitment = 1;
}
message SubscribeRequestFilterTransactions {
optional bool vote = 1;
optional bool failed = 2;
optional string signature = 5;
repeated string account_include = 3;
repeated string account_exclude = 4;
repeated string account_required = 6;
}
message SubscribeRequestFilterBlocks {
repeated string account_include = 1;
optional bool include_transactions = 2;
optional bool include_accounts = 3;
optional bool include_entries = 4;
}
message SubscribeRequestFilterBlocksMeta {}
message SubscribeRequestFilterEntry {}
message SubscribeRequestAccountsDataSlice {
uint64 offset = 1;
uint64 length = 2;
}
message SubscribeRequestPing {
int32 id = 1;
}
message SubscribeUpdate {
repeated string filters = 1;
oneof update_oneof {
SubscribeUpdateAccount account = 2;
SubscribeUpdateSlot slot = 3;
SubscribeUpdateTransaction transaction = 4;
SubscribeUpdateTransactionStatus transaction_status = 10;
SubscribeUpdateBlock block = 5;
SubscribeUpdatePing ping = 6;
SubscribeUpdatePong pong = 9;
SubscribeUpdateBlockMeta block_meta = 7;
SubscribeUpdateEntry entry = 8;
}
}
message SubscribeUpdateAccount {
SubscribeUpdateAccountInfo account = 1;
uint64 slot = 2;
bool is_startup = 3;
}
message SubscribeUpdateAccountInfo {
bytes pubkey = 1;
uint64 lamports = 2;
bytes owner = 3;
bool executable = 4;
uint64 rent_epoch = 5;
bytes data = 6;
uint64 write_version = 7;
optional bytes txn_signature = 8;
}
message SubscribeUpdateSlot {
uint64 slot = 1;
optional uint64 parent = 2;
CommitmentLevel status = 3;
optional string dead_error = 4;
}
message SubscribeUpdateTransaction {
SubscribeUpdateTransactionInfo transaction = 1;
uint64 slot = 2;
}
message SubscribeUpdateTransactionInfo {
bytes signature = 1;
bool is_vote = 2;
solana.storage.ConfirmedBlock.Transaction transaction = 3;
solana.storage.ConfirmedBlock.TransactionStatusMeta meta = 4;
uint64 index = 5;
}
message SubscribeUpdateTransactionStatus {
uint64 slot = 1;
bytes signature = 2;
bool is_vote = 3;
uint64 index = 4;
solana.storage.ConfirmedBlock.TransactionError err = 5;
}
message SubscribeUpdateBlock {
uint64 slot = 1;
string blockhash = 2;
solana.storage.ConfirmedBlock.Rewards rewards = 3;
solana.storage.ConfirmedBlock.UnixTimestamp block_time = 4;
solana.storage.ConfirmedBlock.BlockHeight block_height = 5;
uint64 parent_slot = 7;
string parent_blockhash = 8;
uint64 executed_transaction_count = 9;
repeated SubscribeUpdateTransactionInfo transactions = 6;
uint64 updated_account_count = 10;
repeated SubscribeUpdateAccountInfo accounts = 11;
uint64 entries_count = 12;
repeated SubscribeUpdateEntry entries = 13;
}
message SubscribeUpdateBlockMeta {
uint64 slot = 1;
string blockhash = 2;
solana.storage.ConfirmedBlock.Rewards rewards = 3;
solana.storage.ConfirmedBlock.UnixTimestamp block_time = 4;
solana.storage.ConfirmedBlock.BlockHeight block_height = 5;
uint64 parent_slot = 6;
string parent_blockhash = 7;
uint64 executed_transaction_count = 8;
uint64 entries_count = 9;
}
message SubscribeUpdateEntry {
uint64 slot = 1;
uint64 index = 2;
uint64 num_hashes = 3;
bytes hash = 4;
uint64 executed_transaction_count = 5;
uint64 starting_transaction_index = 6; // added in v1.18, for solana 1.17 value is always 0
}
message SubscribeUpdatePing {}
message SubscribeUpdatePong {
int32 id = 1;
}
// non-streaming methods
message PingRequest {
int32 count = 1;
}
message PongResponse {
int32 count = 1;
}
message GetLatestBlockhashRequest {
optional CommitmentLevel commitment = 1;
}
message GetLatestBlockhashResponse {
uint64 slot = 1;
string blockhash = 2;
uint64 last_valid_block_height = 3;
}
message GetBlockHeightRequest {
optional CommitmentLevel commitment = 1;
}
message GetBlockHeightResponse {
uint64 block_height = 1;
}
message GetSlotRequest {
optional CommitmentLevel commitment = 1;
}
message GetSlotResponse {
uint64 slot = 1;
}
message GetVersionRequest {}
message GetVersionResponse {
string version = 1;
}
message IsBlockhashValidRequest {
string blockhash = 1;
optional CommitmentLevel commitment = 2;
}
message IsBlockhashValidResponse {
uint64 slot = 1;
bool valid = 2;
}
+149
View File
@@ -0,0 +1,149 @@
syntax = "proto3";
package solana.storage.ConfirmedBlock;
option go_package = "github.com/rpcpool/yellowstone-grpc/examples/golang/proto";
message ConfirmedBlock {
string previous_blockhash = 1;
string blockhash = 2;
uint64 parent_slot = 3;
repeated ConfirmedTransaction transactions = 4;
repeated Reward rewards = 5;
UnixTimestamp block_time = 6;
BlockHeight block_height = 7;
NumPartitions num_partitions = 8;
}
message ConfirmedTransaction {
Transaction transaction = 1;
TransactionStatusMeta meta = 2;
}
message Transaction {
repeated bytes signatures = 1;
Message message = 2;
}
message Message {
MessageHeader header = 1;
repeated bytes account_keys = 2;
bytes recent_blockhash = 3;
repeated CompiledInstruction instructions = 4;
bool versioned = 5;
repeated MessageAddressTableLookup address_table_lookups = 6;
}
message MessageHeader {
uint32 num_required_signatures = 1;
uint32 num_readonly_signed_accounts = 2;
uint32 num_readonly_unsigned_accounts = 3;
}
message MessageAddressTableLookup {
bytes account_key = 1;
bytes writable_indexes = 2;
bytes readonly_indexes = 3;
}
message TransactionStatusMeta {
TransactionError err = 1;
uint64 fee = 2;
repeated uint64 pre_balances = 3;
repeated uint64 post_balances = 4;
repeated InnerInstructions inner_instructions = 5;
bool inner_instructions_none = 10;
repeated string log_messages = 6;
bool log_messages_none = 11;
repeated TokenBalance pre_token_balances = 7;
repeated TokenBalance post_token_balances = 8;
repeated Reward rewards = 9;
repeated bytes loaded_writable_addresses = 12;
repeated bytes loaded_readonly_addresses = 13;
ReturnData return_data = 14;
bool return_data_none = 15;
// Sum of compute units consumed by all instructions.
// Available since Solana v1.10.35 / v1.11.6.
// Set to `None` for txs executed on earlier versions.
optional uint64 compute_units_consumed = 16;
}
message TransactionError {
bytes err = 1;
}
message InnerInstructions {
uint32 index = 1;
repeated InnerInstruction instructions = 2;
}
message InnerInstruction {
uint32 program_id_index = 1;
bytes accounts = 2;
bytes data = 3;
// Invocation stack height of an inner instruction.
// Available since Solana v1.14.6
// Set to `None` for txs executed on earlier versions.
optional uint32 stack_height = 4;
}
message CompiledInstruction {
uint32 program_id_index = 1;
bytes accounts = 2;
bytes data = 3;
}
message TokenBalance {
uint32 account_index = 1;
string mint = 2;
UiTokenAmount ui_token_amount = 3;
string owner = 4;
string program_id = 5;
}
message UiTokenAmount {
double ui_amount = 1;
uint32 decimals = 2;
string amount = 3;
string ui_amount_string = 4;
}
message ReturnData {
bytes program_id = 1;
bytes data = 2;
}
enum RewardType {
Unspecified = 0;
Fee = 1;
Rent = 2;
Staking = 3;
Voting = 4;
}
message Reward {
string pubkey = 1;
int64 lamports = 2;
uint64 post_balance = 3;
RewardType reward_type = 4;
string commission = 5;
}
message Rewards {
repeated Reward rewards = 1;
NumPartitions num_partitions = 2;
}
message UnixTimestamp {
int64 timestamp = 1;
}
message BlockHeight {
uint64 block_height = 1;
}
message NumPartitions {
uint64 num_partitions = 1;
}
+92
View File
@@ -0,0 +1,92 @@
"""
Event processing for pump.fun tokens using Geyser data.
"""
import struct
from typing import Final
from solders.pubkey import Pubkey
from trading.base import TokenInfo
from utils.logger import get_logger
logger = get_logger(__name__)
class GeyserEventProcessor:
"""Processes token creation events from Geyser stream."""
CREATE_DISCRIMINATOR: Final[bytes] = struct.pack("<Q", 8576854823835016728)
def __init__(self, pump_program: Pubkey):
"""Initialize event processor.
Args:
pump_program: Pump.fun program address
"""
self.pump_program = pump_program
def process_transaction_data(self, instruction_data: bytes, accounts: list, keys: list) -> TokenInfo | None:
"""Process transaction data and extract token creation info.
Args:
instruction_data: Raw instruction data
accounts: List of account indices
keys: List of account public keys
Returns:
TokenInfo if token creation found, None otherwise
"""
if not instruction_data.startswith(self.CREATE_DISCRIMINATOR):
return None
try:
# Skip past the 8-byte discriminator
offset = 8
# Helper to read strings (prefixed with length)
def read_string():
nonlocal offset
# Get string length (4-byte uint)
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")
offset += length
return value
# Helper to get account key
def get_account_key(index):
if index >= len(accounts):
return None
account_index = accounts[index]
if account_index >= len(keys):
return None
return Pubkey.from_bytes(keys[account_index])
name = read_string()
symbol = read_string()
uri = read_string()
mint = get_account_key(0)
bonding_curve = get_account_key(2)
associated_bonding_curve = get_account_key(3)
user = get_account_key(7)
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,
uri=uri,
mint=mint,
bonding_curve=bonding_curve,
associated_bonding_curve=associated_bonding_curve,
user=user,
)
except Exception as e:
logger.error(f"Failed to process transaction data: {e}")
return None
+159
View File
@@ -0,0 +1,159 @@
"""
Geyser monitoring for pump.fun tokens.
"""
import asyncio
from collections.abc import Awaitable, Callable
import grpc
from solders.pubkey import Pubkey
from geyser.generated import geyser_pb2, geyser_pb2_grpc
from monitoring.base_listener import BaseTokenListener
from monitoring.geyser_event_processor import GeyserEventProcessor
from trading.base import TokenInfo
from utils.logger import get_logger
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, pump_program: Pubkey):
"""Initialize token listener.
Args:
geyser_endpoint: Geyser gRPC endpoint URL
geyser_api_token: API token for authentication
pump_program: Pump.fun program address
"""
self.geyser_endpoint = geyser_endpoint
self.geyser_api_token = geyser_api_token
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."""
auth = grpc.metadata_call_credentials(
lambda context, callback: callback(
(("authorization", f"Basic {self.geyser_api_token}"),), None
)
)
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"].failed = False
request.commitment = geyser_pb2.CommitmentLevel.PROCESSED
return request
async def listen_for_tokens(
self,
token_callback: Callable[[TokenInfo], Awaitable[None]],
match_string: str | None = None,
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
creator_address: Optional creator address to filter by
"""
while True:
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}")
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()
):
logger.info(
f"Token does not match filter '{match_string}'. Skipping..."
)
continue
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:
return None
for ix in msg.instructions:
# Skip non-Pump.fun program instructions
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
+19 -3
View File
@@ -22,6 +22,7 @@ from core.priority_fee.manager import PriorityFeeManager
from core.pubkeys import PumpAddresses
from core.wallet import Wallet
from monitoring.block_listener import BlockListener
from monitoring.geyser_listener import GeyserListener
from monitoring.logs_listener import LogsListener
from trading.base import TokenInfo, TradeResult
from trading.buyer import TokenBuyer
@@ -43,7 +44,9 @@ class PumpTrader:
buy_slippage: float,
sell_slippage: float,
max_retries: int = 5,
listener_type: str = "block", # Add this parameter
listener_type: str = "logs",
geyser_endpoint: str | None = None,
geyser_api_token: str | None = None,
):
"""Initialize the pump trader.
@@ -55,7 +58,9 @@ class PumpTrader:
buy_slippage: Slippage tolerance for buys
sell_slippage: Slippage tolerance for sells
max_retries: Maximum number of retry attempts
listener_type: Type of listener to use ('block' or 'logs')
listener_type: Type of listener to use ('logs', 'blocks', or 'geyser')
geyser_endpoint: Geyser endpoint URL (required for geyser listener)
geyser_api_token: Geyser API token (required for geyser listener)
"""
self.solana_client = SolanaClient(rpc_endpoint)
self.wallet = Wallet(private_key)
@@ -92,7 +97,18 @@ class PumpTrader:
)
# Initialize the appropriate listener type
if listener_type.lower() == "logs":
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")
self.token_listener = GeyserListener(
geyser_endpoint,
geyser_api_token,
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")
else:
+123 -52
View File
@@ -1,6 +1,6 @@
"""
Test script to compare BlockListener and LogsListener
Runs both listeners simultaneously to compare their performance
Test script to compare BlockListener, LogsListener, and GeyserListener
Runs all listeners simultaneously to compare their performance
"""
import asyncio
@@ -10,13 +10,18 @@ import sys
import time
from pathlib import Path
from dotenv import load_dotenv
sys.path.append(str(Path(__file__).parent.parent / "src"))
from core.pubkeys import PumpAddresses
from monitoring.block_listener import BlockListener
from monitoring.geyser_listener import GeyserListener
from monitoring.logs_listener import LogsListener
from trading.base import TokenInfo
load_dotenv()
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
@@ -46,9 +51,30 @@ class TimingTokenCallback:
print(f"{'=' * 50}\n")
async def listen_with_timeout(listener, callback, timeout):
"""Run a listener for a specified duration"""
try:
listen_task = asyncio.create_task(
listener.listen_for_tokens(callback.on_token_created)
)
await asyncio.sleep(timeout)
listen_task.cancel()
try:
await listen_task
except asyncio.CancelledError:
pass
except Exception as e:
logger.error(f"Error in listener {callback.name}: {e}")
async def run_comparison(test_duration: int = 300):
"""Run both listeners and compare their performance"""
"""Run all listeners and compare their performance"""
wss_endpoint = os.environ.get("SOLANA_NODE_WSS_ENDPOINT")
geyser_endpoint = os.environ.get("GEYSER_ENDPOINT")
geyser_api_token = os.environ.get("GEYSER_API_TOKEN")
if not wss_endpoint:
logger.error("SOLANA_NODE_WSS_ENDPOINT environment variable is not set")
return
@@ -57,70 +83,115 @@ async def run_comparison(test_duration: int = 300):
block_listener = BlockListener(wss_endpoint, PumpAddresses.PROGRAM)
logs_listener = LogsListener(wss_endpoint, PumpAddresses.PROGRAM)
block_callback = TimingTokenCallback("BlockListener")
logs_callback = TimingTokenCallback("LogsListener")
logger.info("Starting both listeners...")
block_task = asyncio.create_task(
block_listener.listen_for_tokens(block_callback.on_token_created)
)
logs_task = asyncio.create_task(
logs_listener.listen_for_tokens(logs_callback.on_token_created)
)
listener_tasks = [
listen_with_timeout(block_listener, block_callback, test_duration),
listen_with_timeout(logs_listener, logs_callback, test_duration)
]
callbacks = [block_callback, logs_callback]
listener_names = ["BlockListener", "LogsListener"]
# Initialize Geyser listener if credentials are available
if geyser_endpoint and geyser_api_token:
logger.info(f"Connecting to Geyser API: {geyser_endpoint}")
geyser_listener = GeyserListener(geyser_endpoint, geyser_api_token, PumpAddresses.PROGRAM)
geyser_callback = TimingTokenCallback("GeyserListener")
listener_tasks.append(
listen_with_timeout(geyser_listener, geyser_callback, test_duration)
)
callbacks.append(geyser_callback)
listener_names.append("GeyserListener")
else:
logger.warning("Geyser API credentials not found. Running without Geyser listener.")
logger.info("Starting all listeners simultaneously...")
logger.info(f"Comparison running for {test_duration} seconds...")
try:
await asyncio.sleep(test_duration)
# Start all listeners at the same time
start_time = time.time()
await asyncio.gather(*listener_tasks)
end_time = time.time()
logger.info(f"Test completed in {end_time - start_time:.2f} seconds")
except KeyboardInterrupt:
logger.info("Test interrupted by user")
finally:
block_task.cancel()
logs_task.cancel()
try:
await asyncio.gather(block_task, logs_task, return_exceptions=True)
except asyncio.CancelledError:
pass
# No need for explicit cancellation as gather() will be interrupted
logger.info(f"BlockListener detected {len(block_callback.detected_tokens)} tokens")
logger.info(f"LogsListener detected {len(logs_callback.detected_tokens)} tokens")
for i, callback in enumerate(callbacks):
logger.info(f"{listener_names[i]} detected {len(callback.detected_tokens)} tokens")
# Find tokens detected by both listeners
block_mints = {str(token.mint) for token in block_callback.detected_tokens}
logs_mints = {str(token.mint) for token in logs_callback.detected_tokens}
common_mints = block_mints.intersection(logs_mints)
# Find tokens detected by multiple listeners
all_mints = {}
for i, callback in enumerate(callbacks):
mints = {str(token.mint) for token in callback.detected_tokens}
all_mints[listener_names[i]] = mints
logger.info(f"Tokens detected by both listeners: {len(common_mints)}")
# Analyze common detections between all listeners
if len(callbacks) > 1:
logger.info("\nAnalyzing token detection across listeners:")
# Find tokens detected by all listeners
if len(callbacks) > 2: # If we have all 3 listeners
common_to_all = set.intersection(*all_mints.values())
logger.info(f"Tokens detected by all listeners: {len(common_to_all)}")
# Compare pairs of listeners
listeners = list(all_mints.keys())
for i in range(len(listeners)):
for j in range(i+1, len(listeners)):
listener1 = listeners[i]
listener2 = listeners[j]
common = all_mints[listener1].intersection(all_mints[listener2])
logger.info(f"Tokens detected by both {listener1} and {listener2}: {len(common)}")
unique1 = all_mints[listener1] - all_mints[listener2]
unique2 = all_mints[listener2] - all_mints[listener1]
logger.info(f"Tokens unique to {listener1}: {len(unique1)}")
logger.info(f"Tokens unique to {listener2}: {len(unique2)}")
# Find tokens detected by at least one listener
all_detected = set.union(*all_mints.values())
logger.info(f"Total unique tokens detected by any listener: {len(all_detected)}")
# Compare detection times for common tokens
if common_mints:
logger.info("\nPerformance comparison for tokens detected by both listeners:")
logger.info("Token Mint | BlockListener Time | LogsListener Time | Difference (ms)")
logger.info("\nDetection speed comparison:")
# Collect all tokens detected by at least two listeners
detection_comparisons = []
for mint in set.union(*all_mints.values()):
detections = {}
for i, callback in enumerate(callbacks):
if mint in callback.detection_times:
detections[listener_names[i]] = callback.detection_times[mint]
if len(detections) > 1: # Only consider tokens detected by multiple listeners
detection_comparisons.append((mint, detections))
if detection_comparisons:
logger.info("Token | " + " | ".join(listener_names) + " | Fastest")
logger.info("-" * 80)
for mint in common_mints:
block_time = block_callback.detection_times.get(mint)
logs_time = logs_callback.detection_times.get(mint)
for mint, detections in detection_comparisons:
# Create row with detection times or "N/A" if not detected
times = []
for name in listener_names:
time_str = f"{detections.get(name, 0):.6f}" if name in detections else "N/A"
times.append(time_str)
if block_time and logs_time:
diff_ms = abs(block_time - logs_time) * 1000 # Convert to milliseconds
faster = "BlockListener" if block_time < logs_time else "LogsListener"
logger.info(f"{mint[:10]}... | {block_time:.6f} | {logs_time:.6f} | {diff_ms:.2f}ms ({faster} faster)")
# Report tokens only detected by one listener
block_only = block_mints - logs_mints
logs_only = logs_mints - block_mints
if block_only:
logger.info(f"\nTokens only detected by BlockListener: {len(block_only)}")
for mint in block_only:
logger.info(f" - {mint}")
if logs_only:
logger.info(f"\nTokens only detected by LogsListener: {len(logs_only)}")
for mint in logs_only:
logger.info(f" - {mint}")
# Determine fastest listener
valid_times = {name: time for name, time in detections.items() if time > 0}
fastest = min(valid_times.items(), key=lambda x: x[1])[0] if valid_times else "N/A"
logger.info(f"{mint[:10]}... | " + " | ".join(times) + f" | {fastest}")
else:
logger.info("No tokens were detected by multiple listeners for timing comparison")
if __name__ == "__main__":
+5 -1
View File
@@ -9,12 +9,16 @@ import os
import sys
from pathlib import Path
from dotenv import load_dotenv
sys.path.append(str(Path(__file__).parent.parent / "src"))
from core.pubkeys import PumpAddresses
from monitoring.block_listener import BlockListener
from trading.base import TokenInfo
load_dotenv()
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
@@ -91,7 +95,7 @@ async def test_block_listener(
if __name__ == "__main__":
match_string = None # Update if you want to filter tokens by name/symbol
creator_address = None # Update if you want to filter tokens by creator address
test_duration = 15
test_duration = 30
logger.info("Starting block listener test (using blockSubscribe)")
asyncio.run(test_block_listener(match_string, creator_address, test_duration))
+107
View File
@@ -0,0 +1,107 @@
"""
Test script for GeyserListener
Tests gRPC monitoring for new pump.fun tokens using Geyser
"""
import asyncio
import logging
import os
import sys
from pathlib import Path
from dotenv import load_dotenv
sys.path.append(str(Path(__file__).parent.parent / "src"))
from core.pubkeys import PumpAddresses
from monitoring.geyser_listener import GeyserListener
from trading.base import TokenInfo
load_dotenv()
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger("geyser-listener-test")
class TestTokenCallback:
def __init__(self):
self.detected_tokens = []
async def on_token_created(self, token_info: TokenInfo) -> None:
"""Process detected token"""
logger.info(f"New token detected: {token_info.name} ({token_info.symbol})")
logger.info(f"Mint: {token_info.mint}")
self.detected_tokens.append(token_info)
print(f"\n{'=' * 50}")
print(f"NEW TOKEN: {token_info.name}")
print(f"Symbol: {token_info.symbol}")
print(f"Mint: {token_info.mint}")
print(f"URI: {token_info.uri}")
print(f"Creator: {token_info.user}")
print(f"Bonding Curve: {token_info.bonding_curve}")
print(f"Associated Bonding Curve: {token_info.associated_bonding_curve}")
print(f"{'=' * 50}\n")
async def test_geyser_listener(
match_string: str | None = None,
creator_address: str | None = None,
test_duration: int = 60,
):
"""Test the Geyser listener functionality"""
geyser_endpoint = os.environ.get("GEYSER_ENDPOINT")
geyser_api_token = os.environ.get("GEYSER_API_TOKEN")
if not geyser_endpoint:
logger.error("GEYSER_ENDPOINT environment variable is not set")
return []
if not geyser_api_token:
logger.error("GEYSER_API_TOKEN environment variable is not set")
return []
logger.info(f"Connecting to Geyser API: {geyser_endpoint}")
listener = GeyserListener(geyser_endpoint, geyser_api_token, PumpAddresses.PROGRAM)
callback = TestTokenCallback()
if match_string:
logger.info(f"Filtering tokens matching: {match_string}")
if creator_address:
logger.info(f"Filtering tokens by creator: {creator_address}")
listen_task = asyncio.create_task(
listener.listen_for_tokens(
callback.on_token_created,
match_string=match_string,
creator_address=creator_address,
)
)
logger.info(f"Listening for {test_duration} seconds...")
try:
await asyncio.sleep(test_duration)
except KeyboardInterrupt:
logger.info("Test interrupted by user")
finally:
listen_task.cancel()
try:
await listen_task
except asyncio.CancelledError:
pass
logger.info(f"Detected {len(callback.detected_tokens)} tokens")
for token in callback.detected_tokens:
logger.info(f" - {token.name} ({token.symbol}): {token.mint}")
return callback.detected_tokens
if __name__ == "__main__":
match_string = None # Update if you want to filter tokens by name/symbol
creator_address = None # Update if you want to filter tokens by creator address
test_duration = 30
logger.info("Starting Geyser listener test (using Geyser API)")
asyncio.run(test_geyser_listener(match_string, creator_address, test_duration))
+5 -1
View File
@@ -9,12 +9,16 @@ import os
import sys
from pathlib import Path
from dotenv import load_dotenv
sys.path.append(str(Path(__file__).parent.parent / "src"))
from core.pubkeys import PumpAddresses
from monitoring.logs_listener import LogsListener
from trading.base import TokenInfo
load_dotenv()
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
@@ -91,7 +95,7 @@ async def test_logs_listener(
if __name__ == "__main__":
match_string = None # Update if you want to filter tokens by name/symbol
creator_address = None # Update if you want to filter tokens by creator address
test_duration = 15
test_duration = 30
logger.info("Starting logs listener test (using logsSubscribe)")
asyncio.run(test_logs_listener(match_string, creator_address, test_duration))