mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-08-05 11:47:42 +00:00
docs(claude): add claude code rules
This commit is contained in:
@@ -25,14 +25,16 @@ RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
|
||||
TOKEN_MINT = "..."
|
||||
|
||||
# Constants
|
||||
PUMP_PROGRAM_ID: Final[Pubkey] = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
|
||||
PUMP_PROGRAM_ID: Final[Pubkey] = Pubkey.from_string(
|
||||
"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
|
||||
)
|
||||
EXPECTED_DISCRIMINATOR: Final[bytes] = struct.pack("<Q", 6966180631402821399)
|
||||
|
||||
|
||||
class BondingCurveState:
|
||||
"""
|
||||
Represents the state of a bonding curve account.
|
||||
|
||||
|
||||
Attributes:
|
||||
virtual_token_reserves: Virtual token reserves in the curve
|
||||
virtual_sol_reserves: Virtual SOL reserves in the curve
|
||||
@@ -41,6 +43,7 @@ class BondingCurveState:
|
||||
token_total_supply: Total token supply in the curve
|
||||
complete: Whether the curve has completed and liquidity migrated
|
||||
"""
|
||||
|
||||
_STRUCT_1 = Struct(
|
||||
"virtual_token_reserves" / Int64ul,
|
||||
"virtual_sol_reserves" / Int64ul,
|
||||
@@ -73,9 +76,9 @@ class BondingCurveState:
|
||||
|
||||
else:
|
||||
parsed = self._STRUCT_2.parse(data[8:])
|
||||
self.__dict__.update(parsed)
|
||||
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)
|
||||
|
||||
|
||||
@@ -84,11 +87,11 @@ def get_associated_bonding_curve_address(
|
||||
) -> tuple[Pubkey, int]:
|
||||
"""
|
||||
Derives the associated bonding curve address for a given mint.
|
||||
|
||||
|
||||
Args:
|
||||
mint: The token mint address
|
||||
program_id: The program ID for the bonding curve
|
||||
|
||||
|
||||
Returns:
|
||||
Tuple of (bonding curve address, bump seed)
|
||||
"""
|
||||
@@ -100,14 +103,14 @@ async def get_bonding_curve_state(
|
||||
) -> BondingCurveState:
|
||||
"""
|
||||
Fetches and validates the state of a bonding curve account.
|
||||
|
||||
|
||||
Args:
|
||||
conn: AsyncClient connection to Solana RPC
|
||||
curve_address: Address of the bonding curve account
|
||||
|
||||
|
||||
Returns:
|
||||
BondingCurveState object containing parsed account data
|
||||
|
||||
|
||||
Raises:
|
||||
ValueError: If account data is invalid or missing
|
||||
"""
|
||||
@@ -125,7 +128,7 @@ async def get_bonding_curve_state(
|
||||
async def check_token_status(mint_address: str) -> None:
|
||||
"""
|
||||
Checks and prints the status of a token and its bonding curve.
|
||||
|
||||
|
||||
Args:
|
||||
mint_address: The token mint address as a string
|
||||
"""
|
||||
@@ -174,9 +177,11 @@ async def check_token_status(mint_address: str) -> None:
|
||||
def main() -> None:
|
||||
"""Main entry point for the token status checker."""
|
||||
parser = argparse.ArgumentParser(description="Check token bonding curve status")
|
||||
parser.add_argument("mint_address", nargs='?', help="The token mint address", default=TOKEN_MINT)
|
||||
parser.add_argument(
|
||||
"mint_address", nargs="?", help="The token mint address", default=TOKEN_MINT
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
asyncio.run(check_token_status(args.mint_address))
|
||||
|
||||
|
||||
|
||||
@@ -20,8 +20,12 @@ load_dotenv()
|
||||
|
||||
# Constants
|
||||
RPC_ENDPOINT: Final[str] = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
|
||||
PUMP_PROGRAM_ID: Final[Pubkey] = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
|
||||
TOKEN_PROGRAM_ID: Final[Pubkey] = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
|
||||
PUMP_PROGRAM_ID: Final[Pubkey] = Pubkey.from_string(
|
||||
"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
|
||||
)
|
||||
TOKEN_PROGRAM_ID: Final[Pubkey] = Pubkey.from_string(
|
||||
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
||||
)
|
||||
|
||||
# The 8-byte discriminator for bonding curve accounts in Pump.fun
|
||||
BONDING_CURVE_DISCRIMINATOR_BYTES: Final[bytes] = bytes.fromhex("17b7f83760d8ac60")
|
||||
@@ -30,10 +34,10 @@ BONDING_CURVE_DISCRIMINATOR_BYTES: Final[bytes] = bytes.fromhex("17b7f83760d8ac6
|
||||
async def get_bonding_curves_by_reserves(client: AsyncClient | None = None) -> list:
|
||||
"""
|
||||
Fetch bonding curve accounts with real token reserves below a threshold.
|
||||
|
||||
|
||||
Args:
|
||||
client: Optional AsyncClient instance. If None, a new one will be created.
|
||||
|
||||
|
||||
Returns:
|
||||
List of bonding curve accounts matching the criteria
|
||||
"""
|
||||
@@ -47,19 +51,21 @@ async def get_bonding_curves_by_reserves(client: AsyncClient | None = None) -> l
|
||||
if should_close_client:
|
||||
client = AsyncClient(RPC_ENDPOINT, commitment="processed", timeout=180)
|
||||
await client.is_connected()
|
||||
|
||||
|
||||
# Define on-chain filters for getProgramAccounts
|
||||
filters = [
|
||||
MemcmpOpts(offset=0, bytes=BONDING_CURVE_DISCRIMINATOR_BYTES), # Match bonding curve accounts
|
||||
MemcmpOpts(offset=30, bytes=msb_prefix), # Pre-filter by real token reserves MSB
|
||||
MemcmpOpts(
|
||||
offset=0, bytes=BONDING_CURVE_DISCRIMINATOR_BYTES
|
||||
), # Match bonding curve accounts
|
||||
MemcmpOpts(
|
||||
offset=30, bytes=msb_prefix
|
||||
), # Pre-filter by real token reserves MSB
|
||||
MemcmpOpts(offset=48, bytes=b"\x00"), # Ensure complete flag is False
|
||||
]
|
||||
|
||||
# Query accounts matching filters
|
||||
response = await client.get_program_accounts(
|
||||
PUMP_PROGRAM_ID,
|
||||
encoding="base64",
|
||||
filters=filters
|
||||
PUMP_PROGRAM_ID, encoding="base64", filters=filters
|
||||
)
|
||||
|
||||
result = []
|
||||
@@ -68,7 +74,7 @@ async def get_bonding_curves_by_reserves(client: AsyncClient | None = None) -> l
|
||||
|
||||
# Extract real_token_reserves (u64 = 8 bytes, little-endian)
|
||||
offset: int = 24 # real_token_reserves field offset
|
||||
real_token_reserves: int = struct.unpack("<Q", raw[offset:offset + 8])[0]
|
||||
real_token_reserves: int = struct.unpack("<Q", raw[offset : offset + 8])[0]
|
||||
|
||||
# Post-filter: ensure value is below the threshold
|
||||
if real_token_reserves < threshold:
|
||||
@@ -88,11 +94,11 @@ async def find_associated_bonding_curve(
|
||||
) -> dict | None:
|
||||
"""
|
||||
Find the SPL token account owned by a bonding curve.
|
||||
|
||||
|
||||
Args:
|
||||
bonding_curve_address: The bonding curve public key (as a string)
|
||||
client: Optional AsyncClient instance. If None, a new one will be created.
|
||||
|
||||
|
||||
Returns:
|
||||
The associated SPL token account data or None if not found
|
||||
"""
|
||||
@@ -101,12 +107,12 @@ async def find_associated_bonding_curve(
|
||||
if should_close_client:
|
||||
client = AsyncClient(RPC_ENDPOINT)
|
||||
await client.is_connected()
|
||||
|
||||
|
||||
response = await client.get_token_accounts_by_owner(
|
||||
Pubkey.from_string(bonding_curve_address),
|
||||
TokenAccountOpts(program_id=TOKEN_PROGRAM_ID)
|
||||
TokenAccountOpts(program_id=TOKEN_PROGRAM_ID),
|
||||
)
|
||||
|
||||
|
||||
if response.value and len(response.value) > 0:
|
||||
return response.value[0].account
|
||||
else:
|
||||
@@ -123,10 +129,10 @@ async def find_associated_bonding_curve(
|
||||
def get_mint_address(data: bytes) -> str:
|
||||
"""
|
||||
Extract the mint address from SPL token account data.
|
||||
|
||||
|
||||
Args:
|
||||
data: The token account data as bytes
|
||||
|
||||
|
||||
Returns:
|
||||
The mint address as a base58-encoded string
|
||||
"""
|
||||
@@ -137,7 +143,7 @@ async def main() -> None:
|
||||
"""Main entry point for querying and processing bonding curves."""
|
||||
async with AsyncClient(RPC_ENDPOINT, commitment="processed", timeout=120) as client:
|
||||
await client.is_connected()
|
||||
|
||||
|
||||
bonding_curves = await get_bonding_curves_by_reserves(client)
|
||||
print(f"Total matches: {len(bonding_curves)}")
|
||||
print("=" * 50)
|
||||
@@ -147,13 +153,13 @@ async def main() -> None:
|
||||
associated_token_account = await find_associated_bonding_curve(
|
||||
str(bonding_curve.pubkey), client
|
||||
)
|
||||
|
||||
|
||||
if associated_token_account:
|
||||
mint_address = get_mint_address(associated_token_account.data)
|
||||
print(f"Bonding curve: {bonding_curve.pubkey}")
|
||||
print(f"Mint address: {mint_address}")
|
||||
print("=" * 50)
|
||||
|
||||
|
||||
# For demonstration, only process the first curve
|
||||
break
|
||||
|
||||
|
||||
@@ -17,21 +17,25 @@ load_dotenv()
|
||||
# Constants
|
||||
RPC_URL: Final[str] = os.getenv("SOLANA_NODE_RPC_ENDPOINT")
|
||||
TOKEN_MINT: Final[str] = "xWrzYY4c1LnbSkLrd2LDUg9vw7YtVyJhGmw7MABpump"
|
||||
PUMP_PROGRAM_ID: Final[Pubkey] = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
|
||||
PUMP_PROGRAM_ID: Final[Pubkey] = Pubkey.from_string(
|
||||
"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
|
||||
)
|
||||
LAMPORTS_PER_SOL: Final[int] = 1_000_000_000
|
||||
TOKEN_DECIMALS: Final[int] = 6
|
||||
EXPECTED_DISCRIMINATOR: Final[bytes] = struct.pack("<Q", 6966180631402821399) # Pump.fun bonding curve discriminator
|
||||
EXPECTED_DISCRIMINATOR: Final[bytes] = struct.pack(
|
||||
"<Q", 6966180631402821399
|
||||
) # Pump.fun bonding curve discriminator
|
||||
POLL_INTERVAL: Final[int] = 10 # Seconds between each status check
|
||||
|
||||
|
||||
def get_associated_bonding_curve_address(mint: Pubkey, program_id: Pubkey) -> Pubkey:
|
||||
"""
|
||||
Derive the bonding curve PDA address from a mint address.
|
||||
|
||||
|
||||
Args:
|
||||
mint: The token mint address
|
||||
program_id: The program ID for the bonding curve
|
||||
|
||||
|
||||
Returns:
|
||||
The bonding curve address
|
||||
"""
|
||||
@@ -41,14 +45,14 @@ def get_associated_bonding_curve_address(mint: Pubkey, program_id: Pubkey) -> Pu
|
||||
async def get_account_data(client: AsyncClient, pubkey: Pubkey) -> bytes:
|
||||
"""
|
||||
Fetch raw account data for a given public key.
|
||||
|
||||
|
||||
Args:
|
||||
client: AsyncClient connection to Solana RPC
|
||||
pubkey: The public key of the account to fetch
|
||||
|
||||
|
||||
Returns:
|
||||
The raw account data as bytes
|
||||
|
||||
|
||||
Raises:
|
||||
ValueError: If the account is not found or has no data
|
||||
"""
|
||||
@@ -62,13 +66,13 @@ async def get_account_data(client: AsyncClient, pubkey: Pubkey) -> bytes:
|
||||
def parse_curve_state(data: bytes) -> dict:
|
||||
"""
|
||||
Decode bonding curve account data into a readable format.
|
||||
|
||||
|
||||
Args:
|
||||
data: The raw bonding curve account data
|
||||
|
||||
|
||||
Returns:
|
||||
A dictionary containing parsed bonding curve fields
|
||||
|
||||
|
||||
Raises:
|
||||
ValueError: If the account discriminator is invalid
|
||||
"""
|
||||
@@ -89,7 +93,7 @@ def parse_curve_state(data: bytes) -> dict:
|
||||
def print_curve_status(state: dict) -> None:
|
||||
"""
|
||||
Print the current status of the bonding curve in a readable format.
|
||||
|
||||
|
||||
Args:
|
||||
state: The parsed bonding curve state dictionary
|
||||
"""
|
||||
@@ -98,11 +102,11 @@ def print_curve_status(state: dict) -> None:
|
||||
progress = 100.0
|
||||
else:
|
||||
# Pump.fun constants (already converted to human-readable format)
|
||||
TOTAL_SUPPLY = 1_000_000_000 # 1B tokens
|
||||
TOTAL_SUPPLY = 1_000_000_000 # 1B tokens
|
||||
RESERVED_TOKENS = 206_900_000 # 206.9M tokens reserved for migration
|
||||
|
||||
|
||||
initial_real_token_reserves = TOTAL_SUPPLY - RESERVED_TOKENS # 793.1M tokens
|
||||
|
||||
|
||||
if initial_real_token_reserves > 0:
|
||||
left_tokens = state["real_token_reserves"]
|
||||
progress = 100 - (left_tokens * 100) / initial_real_token_reserves
|
||||
@@ -124,7 +128,9 @@ async def track_curve() -> None:
|
||||
return
|
||||
|
||||
mint_pubkey: Pubkey = Pubkey.from_string(TOKEN_MINT)
|
||||
curve_pubkey: Pubkey = get_associated_bonding_curve_address(mint_pubkey, PUMP_PROGRAM_ID)
|
||||
curve_pubkey: Pubkey = get_associated_bonding_curve_address(
|
||||
mint_pubkey, PUMP_PROGRAM_ID
|
||||
)
|
||||
|
||||
print("Tracking bonding curve for:", mint_pubkey)
|
||||
print("Curve address:", curve_pubkey, "\n")
|
||||
|
||||
@@ -20,11 +20,15 @@ PRIVATE_KEY = os.getenv("SOLANA_PRIVATE_KEY")
|
||||
MINT_ADDRESS = Pubkey.from_string("9WHpYbqG6LJvfCYfMjvGbyo1wHXgroCrixPb33s2pump")
|
||||
|
||||
|
||||
async def close_account_if_exists(client: SolanaClient, wallet: Wallet, account: Pubkey, mint: Pubkey):
|
||||
async def close_account_if_exists(
|
||||
client: SolanaClient, wallet: Wallet, account: Pubkey, mint: Pubkey
|
||||
):
|
||||
"""Safely close a token account if it exists and reclaim rent."""
|
||||
try:
|
||||
solana_client = await client.get_client()
|
||||
info = await solana_client.get_account_info(account, encoding="base64") # base64 encoding for account data by deafult
|
||||
info = await solana_client.get_account_info(
|
||||
account, encoding="base64"
|
||||
) # base64 encoding for account data by deafult
|
||||
|
||||
# WARNING: This will permanently burn all tokens in the account before closing it
|
||||
# Closing account is impossible if balance is positive
|
||||
|
||||
@@ -7,6 +7,7 @@ SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string(
|
||||
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
|
||||
)
|
||||
|
||||
|
||||
def get_bonding_curve_address(mint: Pubkey, program_id: Pubkey) -> tuple[Pubkey, int]:
|
||||
"""
|
||||
Derives the bonding curve address for a given mint
|
||||
@@ -37,9 +38,7 @@ def main():
|
||||
try:
|
||||
mint = Pubkey.from_string(mint_address)
|
||||
|
||||
bonding_curve_address, bump = get_bonding_curve_address(
|
||||
mint, PUMP_PROGRAM
|
||||
)
|
||||
bonding_curve_address, bump = get_bonding_curve_address(mint, PUMP_PROGRAM)
|
||||
|
||||
# Calculate the associated bonding curve
|
||||
associated_bonding_curve = find_associated_bonding_curve(
|
||||
|
||||
@@ -77,7 +77,9 @@ def decode_transaction(tx_data, idl):
|
||||
print(f"\nInstruction {idx}:")
|
||||
print(f"Program ID: {program_id}")
|
||||
|
||||
if program_id == "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P": # Pump Fun Program
|
||||
if (
|
||||
program_id == "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
|
||||
): # Pump Fun Program
|
||||
ix_data = bytes(ix.data)
|
||||
discriminator = struct.unpack("<Q", ix_data[:8])[0]
|
||||
|
||||
|
||||
@@ -43,9 +43,9 @@ class BondingCurveState:
|
||||
|
||||
else:
|
||||
parsed = self._STRUCT_2.parse(data[8:])
|
||||
self.__dict__.update(parsed)
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -76,7 +76,9 @@ for ix in instructions:
|
||||
if "parsed" in ix:
|
||||
print(f"Parsed instruction: {ix['program']} - {ix['parsed']['type']}")
|
||||
print(f"Info: {json.dumps(ix['parsed']['info'], indent=2)}")
|
||||
elif program_id == "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P": # Pump Fun Program
|
||||
elif (
|
||||
program_id == "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
|
||||
): # Pump Fun Program
|
||||
matching_instruction = find_matching_instruction(accounts, data)
|
||||
if matching_instruction:
|
||||
decoded_data = decode_instruction_data(
|
||||
|
||||
@@ -24,44 +24,52 @@ from solders.pubkey import Pubkey
|
||||
load_dotenv()
|
||||
|
||||
RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
|
||||
MIGRATION_PROGRAM_ID = Pubkey.from_string("39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg")
|
||||
MIGRATION_PROGRAM_ID = Pubkey.from_string(
|
||||
"39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg"
|
||||
)
|
||||
PUMP_AMM_PROGRAM_ID = Pubkey.from_string("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
|
||||
QUOTE_MINT_SOL = base58.b58encode(bytes(Pubkey.from_string("So11111111111111111111111111111111111111112"))).decode()
|
||||
QUOTE_MINT_SOL = base58.b58encode(
|
||||
bytes(Pubkey.from_string("So11111111111111111111111111111111111111112"))
|
||||
).decode()
|
||||
|
||||
MARKET_DISCRIMINATOR = base58.b58encode(b'\xf1\x9am\x04\x11\xb1m\xbc').decode()
|
||||
MARKET_DISCRIMINATOR = base58.b58encode(b"\xf1\x9am\x04\x11\xb1m\xbc").decode()
|
||||
MARKET_ACCOUNT_LENGTH = 8 + 1 + 2 + 32 * 6 + 8 # total size of known market structure
|
||||
|
||||
|
||||
class DetectionTracker:
|
||||
"""Tracks and analyzes detection times for both methods across providers"""
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.migrations = {} # {base_mint: {provider: timestamp}}
|
||||
self.markets = {} # {base_mint: {provider: timestamp}}
|
||||
self.markets = {} # {base_mint: {provider: timestamp}}
|
||||
self.migration_messages = {} # {provider: count}
|
||||
self.market_messages = {} # {provider: count}
|
||||
self.market_messages = {} # {provider: count}
|
||||
self.start_time = time.time()
|
||||
|
||||
|
||||
def add_migration(self, base_mint, provider, timestamp):
|
||||
"""Record a migration detection event"""
|
||||
if base_mint not in self.migrations:
|
||||
self.migrations[base_mint] = {}
|
||||
self.migrations[base_mint][provider] = timestamp
|
||||
print(f"[MIGRATION] base_mint={base_mint} provider={provider} time={timestamp:.3f}")
|
||||
print(
|
||||
f"[MIGRATION] base_mint={base_mint} provider={provider} time={timestamp:.3f}"
|
||||
)
|
||||
|
||||
def add_market(self, base_mint, provider, timestamp):
|
||||
"""Record a market detection event"""
|
||||
if base_mint not in self.markets:
|
||||
self.markets[base_mint] = {}
|
||||
self.markets[base_mint][provider] = timestamp
|
||||
print(f"[MARKET] base_mint={base_mint} provider={provider} time={timestamp:.3f}")
|
||||
|
||||
print(
|
||||
f"[MARKET] base_mint={base_mint} provider={provider} time={timestamp:.3f}"
|
||||
)
|
||||
|
||||
def increment_migration_messages(self, provider):
|
||||
"""Count WebSocket messages received by migration listener"""
|
||||
if provider not in self.migration_messages:
|
||||
self.migration_messages[provider] = 0
|
||||
self.migration_messages[provider] += 1
|
||||
|
||||
|
||||
def increment_market_messages(self, provider):
|
||||
"""Count WebSocket messages received by market listener"""
|
||||
if provider not in self.market_messages:
|
||||
@@ -71,17 +79,19 @@ class DetectionTracker:
|
||||
def print_summary(self):
|
||||
"""Print detailed summary statistics of the comparison test"""
|
||||
test_duration = time.time() - self.start_time
|
||||
|
||||
|
||||
# Count total messages
|
||||
total_migration_messages = sum(self.migration_messages.values())
|
||||
total_market_messages = sum(self.market_messages.values())
|
||||
|
||||
|
||||
print("\n=== Test Summary ===")
|
||||
print(f"Test duration: {test_duration:.2f} seconds")
|
||||
print(f"WebSocket messages received: {total_migration_messages + total_market_messages}")
|
||||
print(
|
||||
f"WebSocket messages received: {total_migration_messages + total_market_messages}"
|
||||
)
|
||||
print(f" - Migration events: {total_migration_messages}")
|
||||
print(f" - Market events: {total_market_messages}")
|
||||
|
||||
|
||||
# Count unique tokens detected by each method
|
||||
unique_migrations = set(self.migrations.keys())
|
||||
unique_markets = set(self.markets.keys())
|
||||
@@ -91,16 +101,22 @@ class DetectionTracker:
|
||||
print(f" - Migration events: {len(unique_migrations)}")
|
||||
print(f" - Market events: {len(unique_markets)}")
|
||||
print(f" - Detected in both: {len(common_tokens)}\n")
|
||||
|
||||
|
||||
print("=== Provider Message Counts ===")
|
||||
print("Provider | Migration Messages | Market Messages | Total Messages")
|
||||
print(
|
||||
"Provider | Migration Messages | Market Messages | Total Messages"
|
||||
)
|
||||
print("-" * 80)
|
||||
all_providers = set(self.migration_messages.keys()) | set(self.market_messages.keys())
|
||||
all_providers = set(self.migration_messages.keys()) | set(
|
||||
self.market_messages.keys()
|
||||
)
|
||||
for provider in sorted(all_providers):
|
||||
migration_count = self.migration_messages.get(provider, 0)
|
||||
market_count = self.market_messages.get(provider, 0)
|
||||
total = migration_count + market_count
|
||||
print(f"{provider:<22} | {migration_count:<18} | {market_count:<14} | {total}")
|
||||
print(
|
||||
f"{provider:<22} | {migration_count:<18} | {market_count:<14} | {total}"
|
||||
)
|
||||
print()
|
||||
|
||||
print("=== Migration Event Provider Performance ===")
|
||||
@@ -108,34 +124,48 @@ class DetectionTracker:
|
||||
|
||||
print("\n=== Market Event Provider Performance ===")
|
||||
self._print_provider_performance(self.markets)
|
||||
|
||||
|
||||
# Compare detection methods for tokens detected by both
|
||||
if common_tokens:
|
||||
print("\n=== Detection Timing Comparison: Migration vs Market ===")
|
||||
print("Base Mint | First Detection Method | First Provider | Time Delta (ms)")
|
||||
print(
|
||||
"Base Mint | First Detection Method | First Provider | Time Delta (ms)"
|
||||
)
|
||||
print("-" * 100)
|
||||
|
||||
|
||||
migration_first = 0
|
||||
market_first = 0
|
||||
total_delta_ms = 0
|
||||
|
||||
|
||||
for base_mint in sorted(common_tokens):
|
||||
# Find earliest time for each method
|
||||
migration_time = min(self.migrations[base_mint].values()) if base_mint in self.migrations else float('inf')
|
||||
market_time = min(self.markets[base_mint].values()) if base_mint in self.markets else float('inf')
|
||||
|
||||
migration_time = (
|
||||
min(self.migrations[base_mint].values())
|
||||
if base_mint in self.migrations
|
||||
else float("inf")
|
||||
)
|
||||
market_time = (
|
||||
min(self.markets[base_mint].values())
|
||||
if base_mint in self.markets
|
||||
else float("inf")
|
||||
)
|
||||
|
||||
# Find provider with earliest time for each method
|
||||
migration_provider = None
|
||||
if base_mint in self.migrations:
|
||||
migration_provider = min(self.migrations[base_mint].items(), key=lambda x: x[1])[0]
|
||||
|
||||
migration_provider = min(
|
||||
self.migrations[base_mint].items(), key=lambda x: x[1]
|
||||
)[0]
|
||||
|
||||
market_provider = None
|
||||
if base_mint in self.markets:
|
||||
market_provider = min(self.markets[base_mint].items(), key=lambda x: x[1])[0]
|
||||
|
||||
market_provider = min(
|
||||
self.markets[base_mint].items(), key=lambda x: x[1]
|
||||
)[0]
|
||||
|
||||
delta_ms = abs(migration_time - market_time) * 1000
|
||||
total_delta_ms += delta_ms
|
||||
|
||||
|
||||
if migration_time < market_time:
|
||||
first_method = "Migration"
|
||||
first_provider = migration_provider
|
||||
@@ -144,15 +174,21 @@ class DetectionTracker:
|
||||
first_method = "Market"
|
||||
first_provider = market_provider
|
||||
market_first += 1
|
||||
|
||||
print(f"{base_mint} | {first_method:<21} | {first_provider:<14} | {delta_ms:8.1f}")
|
||||
|
||||
|
||||
print(
|
||||
f"{base_mint} | {first_method:<21} | {first_provider:<14} | {delta_ms:8.1f}"
|
||||
)
|
||||
|
||||
# Print statistics summary
|
||||
if common_tokens:
|
||||
avg_delta_ms = total_delta_ms / len(common_tokens)
|
||||
print("\nSummary statistics:")
|
||||
print(f" - Migration detected first: {migration_first}/{len(common_tokens)} ({migration_first/len(common_tokens)*100:.1f}%)")
|
||||
print(f" - Market detected first: {market_first}/{len(common_tokens)} ({market_first/len(common_tokens)*100:.1f}%)")
|
||||
print(
|
||||
f" - Migration detected first: {migration_first}/{len(common_tokens)} ({migration_first / len(common_tokens) * 100:.1f}%)"
|
||||
)
|
||||
print(
|
||||
f" - Market detected first: {market_first}/{len(common_tokens)} ({market_first / len(common_tokens) * 100:.1f}%)"
|
||||
)
|
||||
print(f" - Average timing difference: {avg_delta_ms:.1f} ms")
|
||||
|
||||
def _print_provider_performance(self, events_dict):
|
||||
@@ -160,45 +196,47 @@ class DetectionTracker:
|
||||
# Count how many times each provider was first
|
||||
first_count = {}
|
||||
total_events = 0
|
||||
|
||||
|
||||
for base_mint, providers in events_dict.items():
|
||||
total_events += 1
|
||||
if not providers:
|
||||
continue
|
||||
|
||||
|
||||
# Find the fastest provider for this event
|
||||
fastest_provider = min(providers.items(), key=lambda x: x[1])[0]
|
||||
if fastest_provider not in first_count:
|
||||
first_count[fastest_provider] = 0
|
||||
first_count[fastest_provider] += 1
|
||||
|
||||
|
||||
if not first_count:
|
||||
print("No events detected")
|
||||
return
|
||||
|
||||
|
||||
# Print rankings
|
||||
print("Provider | First Detections | Percentage")
|
||||
print("-" * 60)
|
||||
|
||||
for provider, count in sorted(first_count.items(), key=lambda x: x[1], reverse=True):
|
||||
|
||||
for provider, count in sorted(
|
||||
first_count.items(), key=lambda x: x[1], reverse=True
|
||||
):
|
||||
percentage = (count / total_events) * 100 if total_events > 0 else 0
|
||||
print(f"{provider:<22} | {count:<16} | {percentage:.1f}%")
|
||||
|
||||
|
||||
# Calculate average latency between providers
|
||||
self._print_provider_latency_matrix(events_dict)
|
||||
|
||||
|
||||
def _print_provider_latency_matrix(self, events_dict):
|
||||
"""Print a matrix of average latency between providers"""
|
||||
# Get unique providers
|
||||
all_providers = set()
|
||||
for providers_data in events_dict.values():
|
||||
all_providers.update(providers_data.keys())
|
||||
|
||||
|
||||
if len(all_providers) <= 1:
|
||||
return
|
||||
|
||||
|
||||
providers_list = sorted(all_providers)
|
||||
|
||||
|
||||
print("\nAverage Latency Matrix (ms):")
|
||||
# Print header
|
||||
header = " |"
|
||||
@@ -206,7 +244,7 @@ class DetectionTracker:
|
||||
header += f" {provider[:8]:>8} |"
|
||||
print(header)
|
||||
print("-" * len(header))
|
||||
|
||||
|
||||
# Calculate and print latency matrix
|
||||
for provider1 in providers_list:
|
||||
row = f"{provider1[:8]:>8} |"
|
||||
@@ -214,14 +252,16 @@ class DetectionTracker:
|
||||
if provider1 == provider2:
|
||||
row += " — |"
|
||||
continue
|
||||
|
||||
|
||||
# Calculate average latency
|
||||
latencies = []
|
||||
for base_mint, providers_data in events_dict.items():
|
||||
if provider1 in providers_data and provider2 in providers_data:
|
||||
latency_ms = (providers_data[provider2] - providers_data[provider1]) * 1000
|
||||
latency_ms = (
|
||||
providers_data[provider2] - providers_data[provider1]
|
||||
) * 1000
|
||||
latencies.append(latency_ms)
|
||||
|
||||
|
||||
if latencies:
|
||||
avg_latency = sum(latencies) / len(latencies)
|
||||
row += f" {avg_latency:>+7.1f} |"
|
||||
@@ -232,10 +272,11 @@ class DetectionTracker:
|
||||
|
||||
# ============ MARKET DETECTION METHODS ============
|
||||
|
||||
|
||||
async def fetch_existing_market_pubkeys():
|
||||
"""
|
||||
Fetch existing AMM market accounts from the blockchain
|
||||
|
||||
|
||||
Used to filter out already existing markets when detecting new ones
|
||||
"""
|
||||
headers = {"Content-Type": "application/json"}
|
||||
@@ -251,10 +292,10 @@ async def fetch_existing_market_pubkeys():
|
||||
"filters": [
|
||||
{"dataSize": MARKET_ACCOUNT_LENGTH},
|
||||
{"memcmp": {"offset": 0, "bytes": MARKET_DISCRIMINATOR}},
|
||||
{"memcmp": {"offset": 75, "bytes": QUOTE_MINT_SOL}}
|
||||
]
|
||||
}
|
||||
]
|
||||
{"memcmp": {"offset": 75, "bytes": QUOTE_MINT_SOL}},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
@@ -266,7 +307,7 @@ async def fetch_existing_market_pubkeys():
|
||||
def parse_market_account_data(data):
|
||||
"""
|
||||
Parse binary market account data into a structured format
|
||||
|
||||
|
||||
This function matches the parser from the market listener script
|
||||
"""
|
||||
parsed_data = {}
|
||||
@@ -287,15 +328,19 @@ def parse_market_account_data(data):
|
||||
try:
|
||||
for field_name, field_type in fields:
|
||||
if field_type == "pubkey":
|
||||
value = data[offset:offset + 32]
|
||||
value = data[offset : offset + 32]
|
||||
parsed_data[field_name] = base58.b58encode(value).decode("utf-8")
|
||||
offset += 32
|
||||
elif field_type in {"u64", "i64"}:
|
||||
value = struct.unpack("<Q", data[offset:offset + 8])[0] if field_type == "u64" else struct.unpack("<q", data[offset:offset + 8])[0]
|
||||
value = (
|
||||
struct.unpack("<Q", data[offset : offset + 8])[0]
|
||||
if field_type == "u64"
|
||||
else struct.unpack("<q", data[offset : offset + 8])[0]
|
||||
)
|
||||
parsed_data[field_name] = value
|
||||
offset += 8
|
||||
elif field_type == "u16":
|
||||
value = struct.unpack("<H", data[offset:offset + 2])[0]
|
||||
value = struct.unpack("<H", data[offset : offset + 2])[0]
|
||||
parsed_data[field_name] = value
|
||||
offset += 2
|
||||
elif field_type == "u8":
|
||||
@@ -311,7 +356,7 @@ def parse_market_account_data(data):
|
||||
def parse_migrate_instruction(data):
|
||||
"""
|
||||
Parse binary migration instruction data into a structured format
|
||||
|
||||
|
||||
This function matches the parser from the migration listener script
|
||||
"""
|
||||
if len(data) < 8:
|
||||
@@ -323,7 +368,7 @@ def parse_migrate_instruction(data):
|
||||
|
||||
fields = [
|
||||
("timestamp", "i64"),
|
||||
("index", "u16"),
|
||||
("index", "u16"),
|
||||
("creator", "pubkey"),
|
||||
("baseMint", "pubkey"),
|
||||
("quoteMint", "pubkey"),
|
||||
@@ -346,15 +391,19 @@ def parse_migrate_instruction(data):
|
||||
try:
|
||||
for field_name, field_type in fields:
|
||||
if field_type == "pubkey":
|
||||
value = data[offset:offset + 32]
|
||||
value = data[offset : offset + 32]
|
||||
parsed_data[field_name] = base58.b58encode(value).decode("utf-8")
|
||||
offset += 32
|
||||
elif field_type in {"u64", "i64"}:
|
||||
value = struct.unpack("<Q", data[offset:offset + 8])[0] if field_type == "u64" else struct.unpack("<q", data[offset:offset + 8])[0]
|
||||
value = (
|
||||
struct.unpack("<Q", data[offset : offset + 8])[0]
|
||||
if field_type == "u64"
|
||||
else struct.unpack("<q", data[offset : offset + 8])[0]
|
||||
)
|
||||
parsed_data[field_name] = value
|
||||
offset += 8
|
||||
elif field_type == "u16":
|
||||
value = struct.unpack("<H", data[offset:offset + 2])[0]
|
||||
value = struct.unpack("<H", data[offset : offset + 2])[0]
|
||||
parsed_data[field_name] = value
|
||||
offset += 2
|
||||
elif field_type == "u8":
|
||||
@@ -379,10 +428,11 @@ def is_transaction_successful(logs):
|
||||
|
||||
# ============ WEBSOCKET LISTENERS ============
|
||||
|
||||
|
||||
async def listen_for_migrations(wss_url, provider_name, tracker, known_events=None):
|
||||
"""
|
||||
Listen for migration instructions via WebSocket
|
||||
|
||||
|
||||
Args:
|
||||
wss_url: WebSocket URL to connect to
|
||||
provider_name: Name of the RPC provider
|
||||
@@ -391,21 +441,23 @@ async def listen_for_migrations(wss_url, provider_name, tracker, known_events=No
|
||||
"""
|
||||
if known_events is None:
|
||||
known_events = set()
|
||||
|
||||
|
||||
while True:
|
||||
try:
|
||||
print(f"[INFO] Connecting migration listener to {provider_name}...")
|
||||
async with websockets.connect(wss_url) as websocket:
|
||||
# Subscribe to logs mentioning the migration program
|
||||
subscription_message = json.dumps({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "logsSubscribe",
|
||||
"params": [
|
||||
{"mentions": [str(MIGRATION_PROGRAM_ID)]},
|
||||
{"commitment": "processed"},
|
||||
],
|
||||
})
|
||||
subscription_message = json.dumps(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "logsSubscribe",
|
||||
"params": [
|
||||
{"mentions": [str(MIGRATION_PROGRAM_ID)]},
|
||||
{"commitment": "processed"},
|
||||
],
|
||||
}
|
||||
)
|
||||
await websocket.send(subscription_message)
|
||||
await websocket.recv() # Get subscription confirmation
|
||||
print(f"[INFO] Migration listener active for {provider_name}")
|
||||
@@ -416,27 +468,27 @@ async def listen_for_migrations(wss_url, provider_name, tracker, known_events=No
|
||||
response = await websocket.recv()
|
||||
data = json.loads(response)
|
||||
tracker.increment_migration_messages(provider_name)
|
||||
|
||||
|
||||
# Check if it's a notification and not something else
|
||||
if data.get("method") != "logsNotification":
|
||||
continue
|
||||
|
||||
|
||||
# Get transaction logs
|
||||
log_data = data["params"]["result"]["value"]
|
||||
logs = log_data.get("logs", [])
|
||||
|
||||
|
||||
# Skip failed transactions
|
||||
if not is_transaction_successful(logs):
|
||||
continue
|
||||
|
||||
|
||||
# Skip if not a Migrate instruction
|
||||
if not any("Instruction: Migrate" in log for log in logs):
|
||||
continue
|
||||
|
||||
|
||||
# Skip already migrated curves
|
||||
if any("already migrated" in log for log in logs):
|
||||
continue
|
||||
|
||||
|
||||
# Search for Program data in logs
|
||||
for log in logs:
|
||||
if log.startswith("Program data:"):
|
||||
@@ -444,24 +496,31 @@ async def listen_for_migrations(wss_url, provider_name, tracker, known_events=No
|
||||
# Decode and parse the instruction data
|
||||
data = base64.b64decode(log.split(": ")[1])
|
||||
parsed = parse_migrate_instruction(data)
|
||||
|
||||
|
||||
if parsed and "baseMint" in parsed:
|
||||
base_mint = parsed["baseMint"]
|
||||
# Only track the timestamp for the first time we see this event
|
||||
# from this provider, but still count messages
|
||||
if (provider_name, base_mint) not in known_events:
|
||||
if (
|
||||
provider_name,
|
||||
base_mint,
|
||||
) not in known_events:
|
||||
ts = time.time()
|
||||
tracker.add_migration(base_mint, provider_name, ts)
|
||||
tracker.add_migration(
|
||||
base_mint, provider_name, ts
|
||||
)
|
||||
known_events.add((provider_name, base_mint))
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Failed to decode Program data: {e}")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Migration listener for {provider_name}: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Connection error in migration listener for {provider_name}: {e}")
|
||||
print(
|
||||
f"[ERROR] Connection error in migration listener for {provider_name}: {e}"
|
||||
)
|
||||
print("[INFO] Reconnecting in 5 seconds...")
|
||||
await asyncio.sleep(5)
|
||||
|
||||
@@ -469,7 +528,7 @@ async def listen_for_migrations(wss_url, provider_name, tracker, known_events=No
|
||||
async def listen_for_markets(wss_url, provider_name, tracker, known_markets):
|
||||
"""
|
||||
Listen for new market accounts via WebSocket
|
||||
|
||||
|
||||
Args:
|
||||
wss_url: WebSocket URL to connect to
|
||||
provider_name: Name of the RPC provider
|
||||
@@ -481,51 +540,60 @@ async def listen_for_markets(wss_url, provider_name, tracker, known_markets):
|
||||
print(f"[INFO] Connecting market listener to {provider_name}...")
|
||||
async with websockets.connect(wss_url) as websocket:
|
||||
# Subscribe to program account changes
|
||||
sub_msg = json.dumps({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "programSubscribe",
|
||||
"params": [
|
||||
str(PUMP_AMM_PROGRAM_ID),
|
||||
{
|
||||
"commitment": "processed",
|
||||
"encoding": "base64",
|
||||
"filters": [
|
||||
{"dataSize": MARKET_ACCOUNT_LENGTH},
|
||||
{"memcmp": {"offset": 0, "bytes": MARKET_DISCRIMINATOR}},
|
||||
{"memcmp": {"offset": 75, "bytes": QUOTE_MINT_SOL}}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
sub_msg = json.dumps(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "programSubscribe",
|
||||
"params": [
|
||||
str(PUMP_AMM_PROGRAM_ID),
|
||||
{
|
||||
"commitment": "processed",
|
||||
"encoding": "base64",
|
||||
"filters": [
|
||||
{"dataSize": MARKET_ACCOUNT_LENGTH},
|
||||
{
|
||||
"memcmp": {
|
||||
"offset": 0,
|
||||
"bytes": MARKET_DISCRIMINATOR,
|
||||
}
|
||||
},
|
||||
{"memcmp": {"offset": 75, "bytes": QUOTE_MINT_SOL}},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
await websocket.send(sub_msg)
|
||||
await websocket.recv() # Get subscription confirmation
|
||||
print(f"[INFO] Market listener active for {provider_name}")
|
||||
|
||||
|
||||
# Track events already seen by this provider
|
||||
provider_known = set()
|
||||
|
||||
|
||||
while True:
|
||||
try:
|
||||
# Receive WebSocket message
|
||||
msg = await websocket.recv()
|
||||
data = json.loads(msg)
|
||||
tracker.increment_market_messages(provider_name)
|
||||
|
||||
|
||||
# Check if it's a notification and not something else
|
||||
if data.get("method") != "programNotification":
|
||||
continue
|
||||
|
||||
|
||||
# Extract account information
|
||||
message_value = data["params"]["result"]["value"]
|
||||
pubkey = message_value["pubkey"]
|
||||
raw_account_data = message_value["account"].get("data", [None])[0]
|
||||
|
||||
raw_account_data = message_value["account"].get("data", [None])[
|
||||
0
|
||||
]
|
||||
|
||||
# Skip if we've already processed this market (either globally or for this provider)
|
||||
if pubkey in known_markets or pubkey in provider_known:
|
||||
continue
|
||||
provider_known.add(pubkey)
|
||||
|
||||
|
||||
# Skip if there's no data
|
||||
if not raw_account_data:
|
||||
print("[ERROR] Account data is empty")
|
||||
@@ -537,7 +605,12 @@ async def listen_for_markets(wss_url, provider_name, tracker, known_markets):
|
||||
parsed = parse_market_account_data(account_data)
|
||||
|
||||
# Skip user-created markets (they are on-curve)
|
||||
if parsed.get("creator") and Pubkey.from_string(parsed.get("creator")).is_on_curve():
|
||||
if (
|
||||
parsed.get("creator")
|
||||
and Pubkey.from_string(
|
||||
parsed.get("creator")
|
||||
).is_on_curve()
|
||||
):
|
||||
continue # skip user-created pool
|
||||
|
||||
# Record the market detection
|
||||
@@ -545,10 +618,10 @@ async def listen_for_markets(wss_url, provider_name, tracker, known_markets):
|
||||
if base_mint:
|
||||
ts = time.time()
|
||||
tracker.add_market(base_mint, provider_name, ts)
|
||||
|
||||
|
||||
# Add to the shared known markets to avoid duplicate processing
|
||||
known_markets.add(pubkey)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Failed to decode account: {e}")
|
||||
|
||||
@@ -556,17 +629,22 @@ async def listen_for_markets(wss_url, provider_name, tracker, known_markets):
|
||||
print(f"[ERROR] Market listener for {provider_name}: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Connection error in market listener for {provider_name}: {e}")
|
||||
print(
|
||||
f"[ERROR] Connection error in market listener for {provider_name}: {e}"
|
||||
)
|
||||
print("[INFO] Reconnecting in 5 seconds...")
|
||||
await asyncio.sleep(5)
|
||||
|
||||
|
||||
# ============ MAIN TEST RUNNER ============
|
||||
|
||||
async def run_comparison_test(migration_wss_endpoints, market_wss_endpoints, test_duration=600):
|
||||
|
||||
async def run_comparison_test(
|
||||
migration_wss_endpoints, market_wss_endpoints, test_duration=600
|
||||
):
|
||||
"""
|
||||
Run the comparison test with multiple WebSocket endpoints
|
||||
|
||||
|
||||
Args:
|
||||
migration_wss_endpoints: Dict of {provider_name: wss_url} for migration listeners
|
||||
market_wss_endpoints: Dict of {provider_name: wss_url} for market listeners
|
||||
@@ -577,17 +655,19 @@ async def run_comparison_test(migration_wss_endpoints, market_wss_endpoints, tes
|
||||
known_markets = await fetch_existing_market_pubkeys()
|
||||
print(f"[INFO] Loaded {len(known_markets)} existing markets")
|
||||
|
||||
known_migration_events = set()
|
||||
known_migration_events = set()
|
||||
tasks = []
|
||||
|
||||
|
||||
# Start migration listeners
|
||||
for provider_name, wss_url in migration_wss_endpoints.items():
|
||||
print(f"[INFO] Starting migration listener for {provider_name}")
|
||||
task = asyncio.create_task(
|
||||
listen_for_migrations(wss_url, provider_name, tracker, known_migration_events)
|
||||
listen_for_migrations(
|
||||
wss_url, provider_name, tracker, known_migration_events
|
||||
)
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
|
||||
# Start market listeners
|
||||
for provider_name, wss_url in market_wss_endpoints.items():
|
||||
print(f"[INFO] Starting market listener for {provider_name}")
|
||||
@@ -595,14 +675,14 @@ async def run_comparison_test(migration_wss_endpoints, market_wss_endpoints, tes
|
||||
listen_for_markets(wss_url, provider_name, tracker, known_markets)
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
|
||||
# Run for specified duration
|
||||
print(f"[INFO] Test running for {test_duration} seconds...")
|
||||
await asyncio.sleep(test_duration)
|
||||
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
|
||||
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
tracker.print_summary()
|
||||
|
||||
@@ -615,23 +695,31 @@ if __name__ == "__main__":
|
||||
"provider_2": os.environ.get("SOLANA_NODE_WSS_ENDPOINT_2"),
|
||||
# Add more providers to .env as needed
|
||||
}
|
||||
|
||||
|
||||
market_providers = {
|
||||
"chainstack": os.environ.get("SOLANA_NODE_WSS_ENDPOINT"),
|
||||
"provider_2": os.environ.get("SOLANA_NODE_WSS_ENDPOINT_2"),
|
||||
# Add more providers to .env as needed
|
||||
}
|
||||
|
||||
|
||||
# Filter out any providers with missing endpoints
|
||||
migration_providers = {name: url for name, url in migration_providers.items() if url}
|
||||
migration_providers = {
|
||||
name: url for name, url in migration_providers.items() if url
|
||||
}
|
||||
market_providers = {name: url for name, url in market_providers.items() if url}
|
||||
|
||||
|
||||
# Get test duration from environment or use default (10 minutes)
|
||||
TEST_DURATION = int(os.environ.get("TEST_DURATION", 600))
|
||||
|
||||
print(f"[INFO] Starting Solana detector comparison test for {TEST_DURATION} seconds")
|
||||
|
||||
print(
|
||||
f"[INFO] Starting Solana detector comparison test for {TEST_DURATION} seconds"
|
||||
)
|
||||
print(f"[INFO] Migration providers: {', '.join(migration_providers.keys())}")
|
||||
print(f"[INFO] Market providers: {', '.join(market_providers.keys())}")
|
||||
|
||||
|
||||
# Run the test
|
||||
asyncio.run(run_comparison_test(migration_providers, market_providers, test_duration=TEST_DURATION))
|
||||
asyncio.run(
|
||||
run_comparison_test(
|
||||
migration_providers, market_providers, test_duration=TEST_DURATION
|
||||
)
|
||||
)
|
||||
|
||||
@@ -46,11 +46,7 @@ async def listen_for_events():
|
||||
"id": 1,
|
||||
"method": "blockSubscribe",
|
||||
"params": [
|
||||
{
|
||||
"mentionsAccountOrProgram": str(
|
||||
PUMP_MIGRATOR_ID
|
||||
)
|
||||
},
|
||||
{"mentionsAccountOrProgram": str(PUMP_MIGRATOR_ID)},
|
||||
{
|
||||
"commitment": "confirmed",
|
||||
"encoding": "json",
|
||||
|
||||
@@ -20,7 +20,9 @@ from solders.pubkey import Pubkey
|
||||
load_dotenv()
|
||||
|
||||
WSS_ENDPOINT = os.environ.get("SOLANA_NODE_WSS_ENDPOINT")
|
||||
MIGRATION_PROGRAM_ID = Pubkey.from_string("39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg")
|
||||
MIGRATION_PROGRAM_ID = Pubkey.from_string(
|
||||
"39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg"
|
||||
)
|
||||
|
||||
|
||||
def parse_migrate_instruction(data):
|
||||
@@ -33,7 +35,7 @@ def parse_migrate_instruction(data):
|
||||
|
||||
fields = [
|
||||
("timestamp", "i64"),
|
||||
("index", "u16"),
|
||||
("index", "u16"),
|
||||
("creator", "publicKey"),
|
||||
("baseMint", "publicKey"),
|
||||
("quoteMint", "publicKey"),
|
||||
@@ -56,15 +58,19 @@ def parse_migrate_instruction(data):
|
||||
try:
|
||||
for field_name, field_type in fields:
|
||||
if field_type == "publicKey":
|
||||
value = data[offset:offset + 32]
|
||||
value = data[offset : offset + 32]
|
||||
parsed_data[field_name] = base58.b58encode(value).decode("utf-8")
|
||||
offset += 32
|
||||
elif field_type in {"u64", "i64"}:
|
||||
value = struct.unpack("<Q", data[offset:offset + 8])[0] if field_type == "u64" else struct.unpack("<q", data[offset:offset + 8])[0]
|
||||
value = (
|
||||
struct.unpack("<Q", data[offset : offset + 8])[0]
|
||||
if field_type == "u64"
|
||||
else struct.unpack("<q", data[offset : offset + 8])[0]
|
||||
)
|
||||
parsed_data[field_name] = value
|
||||
offset += 8
|
||||
elif field_type == "u16":
|
||||
value = struct.unpack("<H", data[offset:offset + 2])[0]
|
||||
value = struct.unpack("<H", data[offset : offset + 2])[0]
|
||||
parsed_data[field_name] = value
|
||||
offset += 2
|
||||
elif field_type == "u8":
|
||||
@@ -124,7 +130,9 @@ async def listen_for_migrations():
|
||||
}
|
||||
)
|
||||
await websocket.send(subscription_message)
|
||||
print(f"[INFO] Listening for migration instructions from program: {MIGRATION_PROGRAM_ID}")
|
||||
print(
|
||||
f"[INFO] Listening for migration instructions from program: {MIGRATION_PROGRAM_ID}"
|
||||
)
|
||||
|
||||
response = await websocket.recv()
|
||||
print(f"[INFO] Subscription response: {response}")
|
||||
@@ -138,16 +146,24 @@ async def listen_for_migrations():
|
||||
log_data = data["params"]["result"]["value"]
|
||||
logs = log_data.get("logs", [])
|
||||
|
||||
signature = log_data.get('signature', 'N/A')
|
||||
signature = log_data.get("signature", "N/A")
|
||||
print(f"\n[INFO] Transaction signature: {signature}")
|
||||
|
||||
if is_transaction_successful(logs):
|
||||
if not any("Program log: Instruction: Migrate" in log for log in logs):
|
||||
if not any(
|
||||
"Program log: Instruction: Migrate" in log
|
||||
for log in logs
|
||||
):
|
||||
print("[INFO] Skipping: no migrate instruction")
|
||||
continue
|
||||
|
||||
if any("Program log: Bonding curve already migrated" in log for log in logs):
|
||||
print("[INFO] Skipping: bonding curve already migrated")
|
||||
if any(
|
||||
"Program log: Bonding curve already migrated" in log
|
||||
for log in logs
|
||||
):
|
||||
print(
|
||||
"[INFO] Skipping: bonding curve already migrated"
|
||||
)
|
||||
continue
|
||||
|
||||
print("[INFO] Processing migration instruction...")
|
||||
@@ -155,7 +171,9 @@ async def listen_for_migrations():
|
||||
else:
|
||||
print("[INFO] Skipping failed transaction.")
|
||||
except TimeoutError:
|
||||
print("[INFO] Timeout waiting for WebSocket message, retrying...")
|
||||
print(
|
||||
"[INFO] Timeout waiting for WebSocket message, retrying..."
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[ERROR] An error occurred: {e}")
|
||||
break
|
||||
@@ -165,5 +183,6 @@ async def listen_for_migrations():
|
||||
print("[INFO] Reconnecting in 5 seconds...")
|
||||
await asyncio.sleep(5)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(listen_for_migrations())
|
||||
asyncio.run(listen_for_migrations())
|
||||
|
||||
@@ -25,8 +25,10 @@ RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT")
|
||||
PUMP_AMM_PROGRAM_ID = Pubkey.from_string("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
|
||||
|
||||
MARKET_ACCOUNT_LENGTH = 8 + 1 + 2 + 32 * 6 + 8 # total size of known market structure
|
||||
MARKET_DISCRIMINATOR = base58.b58encode(b'\xf1\x9am\x04\x11\xb1m\xbc').decode()
|
||||
QUOTE_MINT_SOL = base58.b58encode(bytes(Pubkey.from_string("So11111111111111111111111111111111111111112"))).decode()
|
||||
MARKET_DISCRIMINATOR = base58.b58encode(b"\xf1\x9am\x04\x11\xb1m\xbc").decode()
|
||||
QUOTE_MINT_SOL = base58.b58encode(
|
||||
bytes(Pubkey.from_string("So11111111111111111111111111111111111111112"))
|
||||
).decode()
|
||||
|
||||
|
||||
async def fetch_existing_market_pubkeys():
|
||||
@@ -43,10 +45,10 @@ async def fetch_existing_market_pubkeys():
|
||||
"filters": [
|
||||
{"dataSize": MARKET_ACCOUNT_LENGTH},
|
||||
{"memcmp": {"offset": 0, "bytes": MARKET_DISCRIMINATOR}},
|
||||
{"memcmp": {"offset": 75, "bytes": QUOTE_MINT_SOL}}
|
||||
]
|
||||
}
|
||||
]
|
||||
{"memcmp": {"offset": 75, "bytes": QUOTE_MINT_SOL}},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
@@ -69,21 +71,25 @@ def parse_market_account_data(data):
|
||||
("pool_base_token_account", "pubkey"),
|
||||
("pool_quote_token_account", "pubkey"),
|
||||
("lp_supply", "u64"),
|
||||
("coin_creator", "pubkey")
|
||||
("coin_creator", "pubkey"),
|
||||
]
|
||||
|
||||
try:
|
||||
for field_name, field_type in fields:
|
||||
if field_type == "pubkey":
|
||||
value = data[offset:offset + 32]
|
||||
value = data[offset : offset + 32]
|
||||
parsed_data[field_name] = base58.b58encode(value).decode("utf-8")
|
||||
offset += 32
|
||||
elif field_type in {"u64", "i64"}:
|
||||
value = struct.unpack("<Q", data[offset:offset + 8])[0] if field_type == "u64" else struct.unpack("<q", data[offset:offset + 8])[0]
|
||||
value = (
|
||||
struct.unpack("<Q", data[offset : offset + 8])[0]
|
||||
if field_type == "u64"
|
||||
else struct.unpack("<q", data[offset : offset + 8])[0]
|
||||
)
|
||||
parsed_data[field_name] = value
|
||||
offset += 8
|
||||
elif field_type == "u16":
|
||||
value = struct.unpack("<H", data[offset:offset + 2])[0]
|
||||
value = struct.unpack("<H", data[offset : offset + 2])[0]
|
||||
parsed_data[field_name] = value
|
||||
offset += 2
|
||||
elif field_type == "u8":
|
||||
@@ -104,23 +110,30 @@ async def listen_new_markets():
|
||||
try:
|
||||
print("[INFO] Connecting to WebSocket...")
|
||||
async with websockets.connect(WSS_ENDPOINT) as ws:
|
||||
sub_msg = json.dumps({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "programSubscribe",
|
||||
"params": [
|
||||
str(PUMP_AMM_PROGRAM_ID),
|
||||
{
|
||||
"commitment": "processed",
|
||||
"encoding": "base64",
|
||||
"filters": [
|
||||
{"dataSize": MARKET_ACCOUNT_LENGTH},
|
||||
{"memcmp": {"offset": 0, "bytes": MARKET_DISCRIMINATOR}},
|
||||
{"memcmp": {"offset": 75, "bytes": QUOTE_MINT_SOL}}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
sub_msg = json.dumps(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "programSubscribe",
|
||||
"params": [
|
||||
str(PUMP_AMM_PROGRAM_ID),
|
||||
{
|
||||
"commitment": "processed",
|
||||
"encoding": "base64",
|
||||
"filters": [
|
||||
{"dataSize": MARKET_ACCOUNT_LENGTH},
|
||||
{
|
||||
"memcmp": {
|
||||
"offset": 0,
|
||||
"bytes": MARKET_DISCRIMINATOR,
|
||||
}
|
||||
},
|
||||
{"memcmp": {"offset": 75, "bytes": QUOTE_MINT_SOL}},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
await ws.send(sub_msg)
|
||||
print(f"[INFO] Subscribed to: {PUMP_AMM_PROGRAM_ID}")
|
||||
|
||||
@@ -131,11 +144,13 @@ async def listen_new_markets():
|
||||
if "method" in data and data["method"] == "programNotification":
|
||||
message_value = data["params"]["result"]["value"]
|
||||
pubkey = message_value["pubkey"]
|
||||
raw_account_data = message_value["account"].get("data", [None])[0]
|
||||
raw_account_data = message_value["account"].get("data", [None])[
|
||||
0
|
||||
]
|
||||
slot = data["params"]["result"]["context"]["slot"]
|
||||
|
||||
if pubkey in known_pubkeys:
|
||||
#print("[INFO] Skipping already existed market...")
|
||||
# print("[INFO] Skipping already existed market...")
|
||||
continue
|
||||
|
||||
if not raw_account_data:
|
||||
@@ -146,7 +161,9 @@ async def listen_new_markets():
|
||||
account_data = base64.b64decode(raw_account_data)
|
||||
parsed = parse_market_account_data(account_data)
|
||||
|
||||
if Pubkey.from_string(parsed.get("creator", "")).is_on_curve():
|
||||
if Pubkey.from_string(
|
||||
parsed.get("creator", "")
|
||||
).is_on_curve():
|
||||
print("[INFO] Skipping user-created market...")
|
||||
continue # skip user-created pool
|
||||
|
||||
|
||||
@@ -30,25 +30,27 @@ load_dotenv(override=True)
|
||||
PUMP_PROGRAM_ID = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
|
||||
PUMP_CREATE_PREFIX = struct.pack("<Q", 8576854823835016728)
|
||||
PUMPPORTAL_WS_URL = "wss://pumpportal.fun/api/data"
|
||||
TEST_DURATION = 30 # seconds
|
||||
TEST_DURATION = 30 # seconds
|
||||
|
||||
GEYSER_AUTH_TYPE = "x-token" # or "basic"
|
||||
GEYSER_AUTH_TYPE = "x-token" # or "basic"
|
||||
|
||||
|
||||
class DetectionTracker:
|
||||
"""Tracks and analyzes detection times for both methods across providers"""
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.tokens = {} # {mint: {provider: timestamp}}
|
||||
self.messages = {} # {provider: count}
|
||||
self.start_time = time.time()
|
||||
|
||||
|
||||
def add_token(self, mint, name, symbol, provider, timestamp):
|
||||
"""Record a token detection event"""
|
||||
if mint not in self.tokens:
|
||||
self.tokens[mint] = {'name': name, 'symbol': symbol, 'detections': {}}
|
||||
self.tokens[mint]['detections'][provider] = timestamp
|
||||
print(f"[TOKEN] mint={mint} name={name} symbol={symbol} provider={provider} time={timestamp:.3f}")
|
||||
self.tokens[mint] = {"name": name, "symbol": symbol, "detections": {}}
|
||||
self.tokens[mint]["detections"][provider] = timestamp
|
||||
print(
|
||||
f"[TOKEN] mint={mint} name={name} symbol={symbol} provider={provider} time={timestamp:.3f}"
|
||||
)
|
||||
|
||||
def increment_messages(self, provider):
|
||||
"""Count WebSocket/gRPC messages received by listener"""
|
||||
@@ -59,29 +61,29 @@ class DetectionTracker:
|
||||
def print_summary(self):
|
||||
"""Print detailed summary statistics of the comparison test"""
|
||||
test_duration = time.time() - self.start_time
|
||||
|
||||
|
||||
# Count total messages
|
||||
total_messages = sum(self.messages.values())
|
||||
|
||||
|
||||
print("\n=== Test Summary ===")
|
||||
print(f"Test duration: {test_duration:.2f} seconds")
|
||||
print(f"Messages received: {total_messages}")
|
||||
|
||||
|
||||
# Count unique tokens detected by each provider
|
||||
provider_tokens = {}
|
||||
all_providers = set()
|
||||
for mint, token_data in self.tokens.items():
|
||||
providers = token_data['detections'].keys()
|
||||
providers = token_data["detections"].keys()
|
||||
all_providers.update(providers)
|
||||
for provider in providers:
|
||||
if provider not in provider_tokens:
|
||||
provider_tokens[provider] = 0
|
||||
provider_tokens[provider] += 1
|
||||
|
||||
|
||||
print(f"Tokens detected: {len(self.tokens)}")
|
||||
for provider, count in sorted(provider_tokens.items()):
|
||||
print(f" - {provider}: {count}")
|
||||
|
||||
|
||||
print("\n=== Provider Message Counts ===")
|
||||
print("Provider | Messages")
|
||||
print("-" * 40)
|
||||
@@ -92,81 +94,91 @@ class DetectionTracker:
|
||||
|
||||
print("=== Token Detection Provider Performance ===")
|
||||
self._print_provider_performance()
|
||||
|
||||
|
||||
# Print token details
|
||||
print("\n=== Detected Tokens ===")
|
||||
print("Mint | Name | Symbol | First Provider | Detected By")
|
||||
print(
|
||||
"Mint | Name | Symbol | First Provider | Detected By"
|
||||
)
|
||||
print("-" * 100)
|
||||
|
||||
for mint, token_data in sorted(self.tokens.items(), key=lambda x: min(x[1]['detections'].values())):
|
||||
name = token_data['name'][:15] # Truncate long names
|
||||
symbol = token_data['symbol'][:6] # Truncate long symbols
|
||||
|
||||
|
||||
for mint, token_data in sorted(
|
||||
self.tokens.items(), key=lambda x: min(x[1]["detections"].values())
|
||||
):
|
||||
name = token_data["name"][:15] # Truncate long names
|
||||
symbol = token_data["symbol"][:6] # Truncate long symbols
|
||||
|
||||
# Find first provider
|
||||
first_provider = min(token_data['detections'].items(), key=lambda x: x[1])[0]
|
||||
|
||||
first_provider = min(token_data["detections"].items(), key=lambda x: x[1])[
|
||||
0
|
||||
]
|
||||
|
||||
# Get list of providers that detected this token
|
||||
providers = ", ".join(sorted(token_data['detections'].keys()))
|
||||
|
||||
print(f"{mint} | {name:<16} | {symbol:<6} | {first_provider:<14} | {providers}")
|
||||
|
||||
providers = ", ".join(sorted(token_data["detections"].keys()))
|
||||
|
||||
print(
|
||||
f"{mint} | {name:<16} | {symbol:<6} | {first_provider:<14} | {providers}"
|
||||
)
|
||||
|
||||
def _print_provider_performance(self):
|
||||
"""Print performance metrics for providers"""
|
||||
# Count how many times each provider was first
|
||||
first_count = {}
|
||||
total_tokens = len(self.tokens)
|
||||
|
||||
|
||||
for mint, token_data in self.tokens.items():
|
||||
detections = token_data['detections']
|
||||
detections = token_data["detections"]
|
||||
if not detections:
|
||||
continue
|
||||
|
||||
|
||||
# Find the fastest provider for this token
|
||||
fastest_provider = min(detections.items(), key=lambda x: x[1])[0]
|
||||
if fastest_provider not in first_count:
|
||||
first_count[fastest_provider] = 0
|
||||
first_count[fastest_provider] += 1
|
||||
|
||||
|
||||
if not first_count:
|
||||
print("No tokens detected")
|
||||
return
|
||||
|
||||
|
||||
# Print rankings
|
||||
print("Provider | First Detections | Percentage")
|
||||
print("-" * 60)
|
||||
|
||||
for provider, count in sorted(first_count.items(), key=lambda x: x[1], reverse=True):
|
||||
|
||||
for provider, count in sorted(
|
||||
first_count.items(), key=lambda x: x[1], reverse=True
|
||||
):
|
||||
percentage = (count / total_tokens) * 100 if total_tokens > 0 else 0
|
||||
print(f"{provider:<22} | {count:<16} | {percentage:.1f}%")
|
||||
|
||||
|
||||
# Calculate average latency between providers
|
||||
self._print_provider_latency_matrix()
|
||||
|
||||
|
||||
def _print_provider_latency_matrix(self):
|
||||
"""Print a matrix of average latency between providers"""
|
||||
# Get unique providers
|
||||
all_providers = set()
|
||||
for token_data in self.tokens.values():
|
||||
all_providers.update(token_data['detections'].keys())
|
||||
|
||||
all_providers.update(token_data["detections"].keys())
|
||||
|
||||
if len(all_providers) <= 1:
|
||||
return
|
||||
|
||||
|
||||
providers_list = sorted(all_providers)
|
||||
|
||||
|
||||
# Calculate column width based on longest provider name
|
||||
max_provider_len = max(len(provider) for provider in providers_list)
|
||||
col_width = max(max_provider_len, 8) # Minimum 8 for latency values
|
||||
|
||||
|
||||
print("\nAverage Latency Matrix (ms):")
|
||||
|
||||
|
||||
# Print header
|
||||
header = f"{'':>{col_width}} |"
|
||||
for provider in providers_list:
|
||||
header += f" {provider:>{col_width}} |"
|
||||
print(header)
|
||||
print("-" * len(header))
|
||||
|
||||
|
||||
# Calculate and print latency matrix
|
||||
for provider1 in providers_list:
|
||||
row = f"{provider1:>{col_width}} |"
|
||||
@@ -174,15 +186,17 @@ class DetectionTracker:
|
||||
if provider1 == provider2:
|
||||
row += f" {'—':>{col_width}} |"
|
||||
continue
|
||||
|
||||
|
||||
# Calculate average latency
|
||||
latencies = []
|
||||
for token_data in self.tokens.values():
|
||||
detections = token_data['detections']
|
||||
detections = token_data["detections"]
|
||||
if provider1 in detections and provider2 in detections:
|
||||
latency_ms = (detections[provider2] - detections[provider1]) * 1000
|
||||
latency_ms = (
|
||||
detections[provider2] - detections[provider1]
|
||||
) * 1000
|
||||
latencies.append(latency_ms)
|
||||
|
||||
|
||||
if latencies:
|
||||
avg_latency = sum(latencies) / len(latencies)
|
||||
row += f" {avg_latency:>+{col_width}.1f} |"
|
||||
@@ -193,6 +207,7 @@ class DetectionTracker:
|
||||
|
||||
# ============ TOKEN DETECTION METHODS ============
|
||||
|
||||
|
||||
async def fetch_existing_token_mints():
|
||||
"""
|
||||
Fetch existing token mints to avoid duplicate detections
|
||||
@@ -208,31 +223,33 @@ def parse_create_instruction(data):
|
||||
"""
|
||||
if len(data) < 8:
|
||||
return None
|
||||
|
||||
|
||||
offset = 8 # Skip discriminator
|
||||
parsed_data = {}
|
||||
|
||||
try:
|
||||
# Parse name (string)
|
||||
length = struct.unpack("<I", data[offset:offset + 4])[0]
|
||||
length = struct.unpack("<I", data[offset : offset + 4])[0]
|
||||
offset += 4
|
||||
parsed_data["name"] = data[offset:offset + length].decode("utf-8")
|
||||
parsed_data["name"] = data[offset : offset + length].decode("utf-8")
|
||||
offset += length
|
||||
|
||||
# Parse symbol (string)
|
||||
length = struct.unpack("<I", data[offset:offset + 4])[0]
|
||||
length = struct.unpack("<I", data[offset : offset + 4])[0]
|
||||
offset += 4
|
||||
parsed_data["symbol"] = data[offset:offset + length].decode("utf-8")
|
||||
parsed_data["symbol"] = data[offset : offset + length].decode("utf-8")
|
||||
offset += length
|
||||
|
||||
# Parse uri (string)
|
||||
length = struct.unpack("<I", data[offset:offset + 4])[0]
|
||||
length = struct.unpack("<I", data[offset : offset + 4])[0]
|
||||
offset += 4
|
||||
parsed_data["uri"] = data[offset:offset + length].decode("utf-8")
|
||||
parsed_data["uri"] = data[offset : offset + length].decode("utf-8")
|
||||
offset += length
|
||||
|
||||
# Parse mint (pubkey)
|
||||
parsed_data["mint"] = base58.b58encode(data[offset : offset + 32]).decode("utf-8")
|
||||
parsed_data["mint"] = base58.b58encode(data[offset : offset + 32]).decode(
|
||||
"utf-8"
|
||||
)
|
||||
offset += 32
|
||||
|
||||
return parsed_data
|
||||
@@ -251,32 +268,35 @@ def is_transaction_successful(logs):
|
||||
|
||||
# ============ WEBSOCKET LISTENERS ============
|
||||
|
||||
|
||||
async def listen_block_subscription(wss_url, provider_name, tracker, known_tokens=None):
|
||||
"""
|
||||
Listen for new tokens via block subscription
|
||||
"""
|
||||
if known_tokens is None:
|
||||
known_tokens = set()
|
||||
|
||||
|
||||
while True:
|
||||
try:
|
||||
print(f"[INFO] Connecting block listener to {provider_name}...")
|
||||
async with websockets.connect(wss_url) as websocket:
|
||||
subscription_message = json.dumps({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "blockSubscribe",
|
||||
"params": [
|
||||
{"mentionsAccountOrProgram": str(PUMP_PROGRAM_ID)},
|
||||
{
|
||||
"commitment": "confirmed",
|
||||
"encoding": "base64",
|
||||
"showRewards": False,
|
||||
"transactionDetails": "full",
|
||||
"maxSupportedTransactionVersion": 0,
|
||||
},
|
||||
],
|
||||
})
|
||||
subscription_message = json.dumps(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "blockSubscribe",
|
||||
"params": [
|
||||
{"mentionsAccountOrProgram": str(PUMP_PROGRAM_ID)},
|
||||
{
|
||||
"commitment": "confirmed",
|
||||
"encoding": "base64",
|
||||
"showRewards": False,
|
||||
"transactionDetails": "full",
|
||||
"maxSupportedTransactionVersion": 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
await websocket.send(subscription_message)
|
||||
await websocket.recv()
|
||||
print(f"[INFO] Block listener active for {provider_name}")
|
||||
@@ -289,57 +309,79 @@ async def listen_block_subscription(wss_url, provider_name, tracker, known_token
|
||||
|
||||
if data.get("method") != "blockNotification":
|
||||
continue
|
||||
|
||||
|
||||
block_data = data["params"]["result"]
|
||||
if "value" not in block_data or "block" not in block_data["value"]:
|
||||
if (
|
||||
"value" not in block_data
|
||||
or "block" not in block_data["value"]
|
||||
):
|
||||
continue
|
||||
|
||||
|
||||
block = block_data["value"]["block"]
|
||||
if "transactions" not in block:
|
||||
continue
|
||||
|
||||
|
||||
for tx in block["transactions"]:
|
||||
if not isinstance(tx, dict) or "transaction" not in tx:
|
||||
continue
|
||||
|
||||
|
||||
tx_data_b64 = tx["transaction"][0]
|
||||
tx_data = base64.b64decode(tx_data_b64)
|
||||
|
||||
try:
|
||||
transaction = VersionedTransaction.from_bytes(tx_data)
|
||||
for ix in transaction.message.instructions:
|
||||
if transaction.message.account_keys[ix.program_id_index] == PUMP_PROGRAM_ID:
|
||||
if (
|
||||
transaction.message.account_keys[
|
||||
ix.program_id_index
|
||||
]
|
||||
== PUMP_PROGRAM_ID
|
||||
):
|
||||
data_bytes = bytes(ix.data)
|
||||
|
||||
|
||||
if not data_bytes.startswith(PUMP_CREATE_PREFIX):
|
||||
if not data_bytes.startswith(
|
||||
PUMP_CREATE_PREFIX
|
||||
):
|
||||
continue
|
||||
|
||||
parsed = parse_create_instruction(data_bytes)
|
||||
if not parsed:
|
||||
continue
|
||||
|
||||
|
||||
if len(ix.accounts) > 0:
|
||||
try:
|
||||
mint = str(transaction.message.account_keys[ix.accounts[0]]) # First account is usually the mint
|
||||
|
||||
mint = str(
|
||||
transaction.message.account_keys[
|
||||
ix.accounts[0]
|
||||
]
|
||||
) # First account is usually the mint
|
||||
|
||||
if mint in known_tokens:
|
||||
continue
|
||||
|
||||
ts = time.time()
|
||||
tracker.add_token(mint, parsed["name"], parsed["symbol"],
|
||||
f"{provider_name}_block", ts)
|
||||
tracker.add_token(
|
||||
mint,
|
||||
parsed["name"],
|
||||
parsed["symbol"],
|
||||
f"{provider_name}_block",
|
||||
ts,
|
||||
)
|
||||
known_tokens.add(mint)
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Failed to process block instruction: {e}")
|
||||
print(
|
||||
f"[ERROR] Failed to process block instruction: {e}"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Failed to process transaction: {e}")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Block listener for {provider_name}: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Connection error in block listener for {provider_name}: {e}")
|
||||
print(
|
||||
f"[ERROR] Connection error in block listener for {provider_name}: {e}"
|
||||
)
|
||||
print("[INFO] Reconnecting in 5 seconds...")
|
||||
await asyncio.sleep(5)
|
||||
|
||||
@@ -350,20 +392,22 @@ async def listen_logs_subscription(wss_url, provider_name, tracker, known_tokens
|
||||
"""
|
||||
if known_tokens is None:
|
||||
known_tokens = set()
|
||||
|
||||
|
||||
while True:
|
||||
try:
|
||||
print(f"[INFO] Connecting logs listener to {provider_name}...")
|
||||
async with websockets.connect(wss_url) as websocket:
|
||||
subscription_message = json.dumps({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "logsSubscribe",
|
||||
"params": [
|
||||
{"mentions": [str(PUMP_PROGRAM_ID)]},
|
||||
{"commitment": "processed"},
|
||||
],
|
||||
})
|
||||
subscription_message = json.dumps(
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "logsSubscribe",
|
||||
"params": [
|
||||
{"mentions": [str(PUMP_PROGRAM_ID)]},
|
||||
{"commitment": "processed"},
|
||||
],
|
||||
}
|
||||
)
|
||||
await websocket.send(subscription_message)
|
||||
await websocket.recv()
|
||||
print(f"[INFO] Logs listener active for {provider_name}")
|
||||
@@ -379,8 +423,10 @@ async def listen_logs_subscription(wss_url, provider_name, tracker, known_tokens
|
||||
|
||||
log_data = data["params"]["result"]["value"]
|
||||
logs = log_data.get("logs", [])
|
||||
|
||||
if not any("Program log: Instruction: Create" in log for log in logs):
|
||||
|
||||
if not any(
|
||||
"Program log: Instruction: Create" in log for log in logs
|
||||
):
|
||||
continue
|
||||
|
||||
for log in logs:
|
||||
@@ -401,17 +447,17 @@ async def listen_logs_subscription(wss_url, provider_name, tracker, known_tokens
|
||||
|
||||
ts = time.time()
|
||||
tracker.add_token(
|
||||
mint,
|
||||
parsed.get("name", "Unknown"),
|
||||
parsed.get("symbol", "UNK"),
|
||||
f"{provider_name}_logs",
|
||||
ts
|
||||
mint,
|
||||
parsed.get("name", "Unknown"),
|
||||
parsed.get("symbol", "UNK"),
|
||||
f"{provider_name}_logs",
|
||||
ts,
|
||||
)
|
||||
known_tokens.add(mint)
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Failed to decode Program data: {e}")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Logs listener for {provider_name}: {e}")
|
||||
break
|
||||
@@ -422,7 +468,9 @@ async def listen_logs_subscription(wss_url, provider_name, tracker, known_tokens
|
||||
await asyncio.sleep(5)
|
||||
|
||||
|
||||
async def listen_geyser_grpc(endpoint, api_token, provider_name, tracker, known_tokens=None):
|
||||
async def listen_geyser_grpc(
|
||||
endpoint, api_token, provider_name, tracker, known_tokens=None
|
||||
):
|
||||
"""
|
||||
Listen for new tokens via Geyser gRPC API
|
||||
"""
|
||||
@@ -430,12 +478,14 @@ async def listen_geyser_grpc(endpoint, api_token, provider_name, tracker, known_
|
||||
# Import the generated protobuf modules
|
||||
from generated import geyser_pb2, geyser_pb2_grpc
|
||||
except ImportError:
|
||||
print("[ERROR] Could not import geyser_pb2 or geyser_pb2_grpc. Make sure to generate from .proto files")
|
||||
print(
|
||||
"[ERROR] Could not import geyser_pb2 or geyser_pb2_grpc. Make sure to generate from .proto files"
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
if known_tokens is None:
|
||||
known_tokens = set()
|
||||
|
||||
|
||||
while True:
|
||||
try:
|
||||
print(f"[INFO] Connecting Geyser gRPC listener to {provider_name}...")
|
||||
@@ -446,55 +496,70 @@ async def listen_geyser_grpc(endpoint, api_token, provider_name, tracker, known_
|
||||
)
|
||||
else:
|
||||
auth = grpc.metadata_call_credentials(
|
||||
lambda context, callback: callback((("authorization", f"Basic {api_token}"),), None)
|
||||
lambda context, callback: callback(
|
||||
(("authorization", f"Basic {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(endpoint, creds)
|
||||
stub = geyser_pb2_grpc.GeyserStub(channel)
|
||||
|
||||
request = geyser_pb2.SubscribeRequest()
|
||||
request.transactions["pump_filter"].account_include.append(str(PUMP_PROGRAM_ID))
|
||||
request.transactions["pump_filter"].account_include.append(
|
||||
str(PUMP_PROGRAM_ID)
|
||||
)
|
||||
request.transactions["pump_filter"].failed = False
|
||||
request.commitment = geyser_pb2.CommitmentLevel.PROCESSED
|
||||
|
||||
|
||||
print(f"[INFO] Geyser gRPC listener active for {provider_name}")
|
||||
|
||||
|
||||
async for update in stub.Subscribe(iter([request])):
|
||||
tracker.increment_messages(provider_name)
|
||||
|
||||
|
||||
# Skip non-transaction updates
|
||||
if not update.HasField("transaction"):
|
||||
continue
|
||||
|
||||
|
||||
tx = update.transaction.transaction.transaction
|
||||
msg = getattr(tx, "message", None)
|
||||
if msg is None:
|
||||
continue
|
||||
|
||||
|
||||
for ix in msg.instructions:
|
||||
if not ix.data.startswith(PUMP_CREATE_PREFIX):
|
||||
continue
|
||||
|
||||
|
||||
parsed = parse_create_instruction(ix.data)
|
||||
if not parsed:
|
||||
continue
|
||||
|
||||
|
||||
if len(ix.accounts) == 0 or ix.accounts[0] >= len(msg.account_keys):
|
||||
continue
|
||||
|
||||
mint = base58.b58encode(bytes(msg.account_keys[ix.accounts[0]])).decode()
|
||||
mint = base58.b58encode(
|
||||
bytes(msg.account_keys[ix.accounts[0]])
|
||||
).decode()
|
||||
|
||||
if mint in known_tokens:
|
||||
continue
|
||||
|
||||
ts = time.time()
|
||||
tracker.add_token(mint, parsed["name"], parsed["symbol"],
|
||||
f"{provider_name}_geyser", ts)
|
||||
tracker.add_token(
|
||||
mint,
|
||||
parsed["name"],
|
||||
parsed["symbol"],
|
||||
f"{provider_name}_geyser",
|
||||
ts,
|
||||
)
|
||||
known_tokens.add(mint)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Connection error in Geyser gRPC listener for {provider_name}: {e}")
|
||||
print(
|
||||
f"[ERROR] Connection error in Geyser gRPC listener for {provider_name}: {e}"
|
||||
)
|
||||
print("[INFO] Reconnecting in 5 seconds...")
|
||||
await asyncio.sleep(5)
|
||||
|
||||
@@ -505,13 +570,15 @@ async def listen_pumpportal(provider_name, tracker, known_tokens=None):
|
||||
"""
|
||||
if known_tokens is None:
|
||||
known_tokens = set()
|
||||
|
||||
|
||||
while True:
|
||||
try:
|
||||
print("[INFO] Connecting to PumpPortal WebSocket...")
|
||||
async with websockets.connect(PUMPPORTAL_WS_URL) as websocket:
|
||||
# Subscribe to new token events
|
||||
await websocket.send(json.dumps({"method": "subscribeNewToken", "params": []}))
|
||||
await websocket.send(
|
||||
json.dumps({"method": "subscribeNewToken", "params": []})
|
||||
)
|
||||
print(f"[INFO] PumpPortal listener active for {provider_name}")
|
||||
|
||||
while True:
|
||||
@@ -520,51 +587,55 @@ async def listen_pumpportal(provider_name, tracker, known_tokens=None):
|
||||
message = await websocket.recv()
|
||||
data = json.loads(message)
|
||||
tracker.increment_messages(provider_name)
|
||||
|
||||
|
||||
# Extract token information
|
||||
token_info = None
|
||||
if "method" in data and data["method"] == "newToken":
|
||||
token_info = data.get("params", [{}])[0]
|
||||
elif "signature" in data and "mint" in data:
|
||||
token_info = data
|
||||
|
||||
|
||||
if not token_info:
|
||||
continue
|
||||
|
||||
|
||||
# Get token details
|
||||
mint = token_info.get("mint")
|
||||
name = token_info.get("name", "Unknown")
|
||||
symbol = token_info.get("symbol", "UNK")
|
||||
|
||||
|
||||
if not mint:
|
||||
continue
|
||||
|
||||
|
||||
# Skip known tokens
|
||||
if mint in known_tokens:
|
||||
continue
|
||||
|
||||
|
||||
# Record the token detection
|
||||
ts = time.time()
|
||||
tracker.add_token(mint, name, symbol,
|
||||
f"{provider_name}_pumpportal", ts)
|
||||
tracker.add_token(
|
||||
mint, name, symbol, f"{provider_name}_pumpportal", ts
|
||||
)
|
||||
known_tokens.add(mint)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] PumpPortal listener for {provider_name}: {e}")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Connection error in PumpPortal listener for {provider_name}: {e}")
|
||||
print(
|
||||
f"[ERROR] Connection error in PumpPortal listener for {provider_name}: {e}"
|
||||
)
|
||||
print("[INFO] Reconnecting in 5 seconds...")
|
||||
await asyncio.sleep(5)
|
||||
|
||||
|
||||
# ============ MAIN TEST RUNNER ============
|
||||
|
||||
|
||||
async def run_comparison_test(providers, test_duration=600):
|
||||
"""
|
||||
Run the comparison test with multiple WebSocket endpoints
|
||||
|
||||
|
||||
Args:
|
||||
providers: Dict of {provider_name: {'wss': wss_url, 'geyser': (endpoint, api_token)}}
|
||||
test_duration: How long to run the test in seconds (default: 10 minutes)
|
||||
@@ -575,32 +646,38 @@ async def run_comparison_test(providers, test_duration=600):
|
||||
print(f"[INFO] Loaded {len(known_tokens)} existing tokens")
|
||||
|
||||
tasks = []
|
||||
|
||||
|
||||
# Start all listeners for each provider
|
||||
for provider_name, urls in providers.items():
|
||||
if urls.get('wss'):
|
||||
if urls.get("wss"):
|
||||
print(f"[INFO] Starting block listener for {provider_name}")
|
||||
task = asyncio.create_task(
|
||||
listen_block_subscription(urls['wss'], provider_name, tracker, known_tokens.copy())
|
||||
listen_block_subscription(
|
||||
urls["wss"], provider_name, tracker, known_tokens.copy()
|
||||
)
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
if urls.get('wss'):
|
||||
if urls.get("wss"):
|
||||
print(f"[INFO] Starting logs listener for {provider_name}")
|
||||
task = asyncio.create_task(
|
||||
listen_logs_subscription(urls['wss'], provider_name, tracker, known_tokens.copy())
|
||||
listen_logs_subscription(
|
||||
urls["wss"], provider_name, tracker, known_tokens.copy()
|
||||
)
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
if urls.get('geyser'):
|
||||
endpoint, api_token = urls['geyser']
|
||||
|
||||
if urls.get("geyser"):
|
||||
endpoint, api_token = urls["geyser"]
|
||||
if endpoint and api_token:
|
||||
print(f"[INFO] Starting Geyser gRPC listener for {provider_name}")
|
||||
task = asyncio.create_task(
|
||||
listen_geyser_grpc(endpoint, api_token, provider_name, tracker, known_tokens.copy())
|
||||
listen_geyser_grpc(
|
||||
endpoint, api_token, provider_name, tracker, known_tokens.copy()
|
||||
)
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
|
||||
# Start PumpPortal listener (only once, not per provider)
|
||||
print("[INFO] Starting PumpPortal listener")
|
||||
task = asyncio.create_task(
|
||||
@@ -613,7 +690,7 @@ async def run_comparison_test(providers, test_duration=600):
|
||||
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
|
||||
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
tracker.print_summary()
|
||||
|
||||
@@ -622,21 +699,26 @@ if __name__ == "__main__":
|
||||
# Read providers from environment variables
|
||||
providers = {
|
||||
"provider_1": {
|
||||
'wss': os.environ.get("SOLANA_NODE_WSS_ENDPOINT"),
|
||||
'geyser': (
|
||||
"wss": os.environ.get("SOLANA_NODE_WSS_ENDPOINT"),
|
||||
"geyser": (
|
||||
os.environ.get("GEYSER_ENDPOINT"),
|
||||
os.environ.get("GEYSER_API_TOKEN")
|
||||
)
|
||||
os.environ.get("GEYSER_API_TOKEN"),
|
||||
),
|
||||
},
|
||||
# Add more providers to .env as needed
|
||||
}
|
||||
|
||||
|
||||
# Filter out any providers with missing endpoints
|
||||
providers = {name: urls for name, urls in providers.items()
|
||||
if (urls.get('wss')) or
|
||||
('geyser' in urls and urls['geyser'][0] and urls['geyser'][1])}
|
||||
|
||||
print(f"[INFO] Starting Pump.fun token detector comparison test for {TEST_DURATION} seconds")
|
||||
providers = {
|
||||
name: urls
|
||||
for name, urls in providers.items()
|
||||
if (urls.get("wss"))
|
||||
or ("geyser" in urls and urls["geyser"][0] and urls["geyser"][1])
|
||||
}
|
||||
|
||||
print(
|
||||
f"[INFO] Starting Pump.fun token detector comparison test for {TEST_DURATION} seconds"
|
||||
)
|
||||
print(f"[INFO] Providers: {', '.join(providers.keys())}")
|
||||
|
||||
asyncio.run(run_comparison_test(providers, test_duration=TEST_DURATION))
|
||||
|
||||
asyncio.run(run_comparison_test(providers, test_duration=TEST_DURATION))
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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")
|
||||
|
||||
@@ -5,23 +5,26 @@ import grpc
|
||||
|
||||
import 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
@@ -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}."
|
||||
)
|
||||
|
||||
@@ -37,9 +37,11 @@ async def create_geyser_connection():
|
||||
)
|
||||
else: # Default to basic auth
|
||||
auth = grpc.metadata_call_credentials(
|
||||
lambda _, callback: callback((("authorization", f"Basic {GEYSER_API_TOKEN}"),), None)
|
||||
lambda _, callback: callback(
|
||||
(("authorization", f"Basic {GEYSER_API_TOKEN}"),), None
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
creds = grpc.composite_channel_credentials(grpc.ssl_channel_credentials(), auth)
|
||||
channel = grpc.aio.secure_channel(GEYSER_ENDPOINT, creds)
|
||||
return geyser_pb2_grpc.GeyserStub(channel)
|
||||
@@ -58,14 +60,14 @@ def decode_create_instruction(ix_data: bytes, keys, accounts) -> dict:
|
||||
"""Decode a create instruction from transaction data."""
|
||||
# Skip past the 8-byte discriminator prefix
|
||||
offset = 8
|
||||
|
||||
|
||||
# Extract account keys in base58 format
|
||||
def get_account_key(index):
|
||||
if index >= len(accounts):
|
||||
return "N/A"
|
||||
account_index = accounts[index]
|
||||
return base58.b58encode(keys[account_index]).decode()
|
||||
|
||||
|
||||
# Read string fields (prefixed with length)
|
||||
def read_string():
|
||||
nonlocal offset
|
||||
@@ -73,21 +75,21 @@ def decode_create_instruction(ix_data: bytes, keys, accounts) -> dict:
|
||||
length = struct.unpack_from("<I", ix_data, offset)[0]
|
||||
offset += 4
|
||||
# Extract and decode the string
|
||||
value = ix_data[offset:offset + length].decode()
|
||||
value = ix_data[offset : offset + length].decode()
|
||||
offset += length
|
||||
return value
|
||||
|
||||
|
||||
def read_pubkey():
|
||||
nonlocal offset
|
||||
value = base58.b58encode(ix_data[offset : offset + 32]).decode("utf-8")
|
||||
offset += 32
|
||||
return value
|
||||
|
||||
|
||||
name = read_string()
|
||||
symbol = read_string()
|
||||
uri = read_string()
|
||||
creator = read_pubkey()
|
||||
|
||||
|
||||
token_info = {
|
||||
"name": name,
|
||||
"symbol": symbol,
|
||||
@@ -102,7 +104,7 @@ def decode_create_instruction(ix_data: bytes, keys, accounts) -> dict:
|
||||
"rent": get_account_key(6),
|
||||
"user": get_account_key(7),
|
||||
}
|
||||
|
||||
|
||||
return token_info
|
||||
|
||||
|
||||
@@ -122,26 +124,28 @@ async def monitor_pump():
|
||||
print(f"Starting Pump.fun token monitor using {AUTH_TYPE.upper()} authentication")
|
||||
stub = await create_geyser_connection()
|
||||
request = create_subscription_request()
|
||||
|
||||
|
||||
async for update in stub.Subscribe(iter([request])):
|
||||
# Skip non-transaction updates
|
||||
if not update.HasField("transaction"):
|
||||
continue
|
||||
|
||||
|
||||
tx = update.transaction.transaction.transaction
|
||||
msg = getattr(tx, "message", None)
|
||||
if msg is None:
|
||||
continue
|
||||
|
||||
|
||||
# Check each instruction in the transaction
|
||||
for ix in msg.instructions:
|
||||
if not ix.data.startswith(PUMP_CREATE_PREFIX):
|
||||
continue
|
||||
|
||||
info = decode_create_instruction(ix.data, msg.account_keys, ix.accounts)
|
||||
signature = base58.b58encode(bytes(update.transaction.transaction.signature)).decode()
|
||||
signature = base58.b58encode(
|
||||
bytes(update.transaction.transaction.signature)
|
||||
).decode()
|
||||
print_token_info(info, signature)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(monitor_pump())
|
||||
asyncio.run(monitor_pump())
|
||||
|
||||
@@ -22,7 +22,10 @@ load_dotenv()
|
||||
WSS_ENDPOINT = os.environ.get("SOLANA_NODE_WSS_ENDPOINT")
|
||||
PUMP_PROGRAM_ID = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
|
||||
TOKEN_PROGRAM_ID = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID = Pubkey.from_string(
|
||||
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
|
||||
)
|
||||
|
||||
|
||||
def find_associated_bonding_curve(mint: Pubkey, bonding_curve: Pubkey) -> Pubkey:
|
||||
"""
|
||||
|
||||
@@ -66,7 +66,7 @@ class BondingCurveState:
|
||||
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)
|
||||
|
||||
|
||||
@@ -95,33 +95,25 @@ def calculate_pump_curve_price(curve_state: BondingCurveState) -> float:
|
||||
|
||||
def _find_creator_vault(creator: Pubkey) -> Pubkey:
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
b"creator-vault",
|
||||
bytes(creator)
|
||||
],
|
||||
[b"creator-vault", bytes(creator)],
|
||||
PUMP_PROGRAM,
|
||||
)
|
||||
)
|
||||
return derived_address
|
||||
|
||||
|
||||
def _find_global_volume_accumulator() -> Pubkey:
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
b"global_volume_accumulator"
|
||||
],
|
||||
[b"global_volume_accumulator"],
|
||||
PUMP_PROGRAM,
|
||||
)
|
||||
)
|
||||
return derived_address
|
||||
|
||||
|
||||
def _find_user_volume_accumulator(user: Pubkey) -> Pubkey:
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
b"user_volume_accumulator",
|
||||
bytes(user)
|
||||
],
|
||||
[b"user_volume_accumulator", bytes(user)],
|
||||
PUMP_PROGRAM,
|
||||
)
|
||||
)
|
||||
return derived_address
|
||||
|
||||
|
||||
@@ -174,8 +166,16 @@ async def buy_token(
|
||||
pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(pubkey=PUMP_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=_find_global_volume_accumulator(), is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=_find_user_volume_accumulator(payer.pubkey()), is_signer=False, is_writable=True),
|
||||
AccountMeta(
|
||||
pubkey=_find_global_volume_accumulator(),
|
||||
is_signer=False,
|
||||
is_writable=True,
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=_find_user_volume_accumulator(payer.pubkey()),
|
||||
is_signer=False,
|
||||
is_writable=True,
|
||||
),
|
||||
]
|
||||
|
||||
discriminator = struct.pack("<Q", 16927863322537952870)
|
||||
@@ -205,10 +205,10 @@ async def buy_token(
|
||||
opts=opts,
|
||||
)
|
||||
tx_hash = tx_buy.value
|
||||
print(
|
||||
f"Transaction sent: https://explorer.solana.com/tx/{tx_hash}"
|
||||
print(f"Transaction sent: https://explorer.solana.com/tx/{tx_hash}")
|
||||
await client.confirm_transaction(
|
||||
tx_hash, commitment="confirmed", sleep_seconds=1
|
||||
)
|
||||
await client.confirm_transaction(tx_hash, commitment="confirmed", sleep_seconds=1)
|
||||
print("Transaction confirmed")
|
||||
return # Success, exit the function
|
||||
except Exception as e:
|
||||
@@ -221,7 +221,6 @@ async def buy_token(
|
||||
print("Max retries reached. Unable to complete the transaction.")
|
||||
|
||||
|
||||
|
||||
def load_idl(file_path):
|
||||
with open(file_path) as f:
|
||||
return json.load(f)
|
||||
@@ -367,7 +366,9 @@ async def main():
|
||||
print(
|
||||
f"Buying {amount:.6f} SOL worth of the new token with {slippage * 100:.1f}% slippage tolerance..."
|
||||
)
|
||||
await buy_token(mint, bonding_curve, associated_bonding_curve, creator_vault, amount, slippage)
|
||||
await buy_token(
|
||||
mint, bonding_curve, associated_bonding_curve, creator_vault, amount, slippage
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -60,7 +60,7 @@ class BondingCurveState:
|
||||
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)
|
||||
|
||||
|
||||
@@ -96,10 +96,7 @@ def find_associated_bonding_curve(mint: Pubkey, bonding_curve: Pubkey) -> Pubkey
|
||||
|
||||
def find_creator_vault(creator: Pubkey) -> Pubkey:
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
b"creator-vault",
|
||||
bytes(creator)
|
||||
],
|
||||
[b"creator-vault", bytes(creator)],
|
||||
PUMP_PROGRAM,
|
||||
)
|
||||
return derived_address
|
||||
@@ -161,9 +158,7 @@ async def sell_token(
|
||||
AccountMeta(pubkey=PUMP_GLOBAL, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=PUMP_FEE, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=mint, is_signer=False, is_writable=False),
|
||||
AccountMeta(
|
||||
pubkey=bonding_curve, is_signer=False, is_writable=True
|
||||
),
|
||||
AccountMeta(pubkey=bonding_curve, is_signer=False, is_writable=True),
|
||||
AccountMeta(
|
||||
pubkey=associated_bonding_curve,
|
||||
is_signer=False,
|
||||
@@ -174,12 +169,8 @@ async def sell_token(
|
||||
is_signer=False,
|
||||
is_writable=True,
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=payer.pubkey(), is_signer=True, is_writable=True
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(pubkey=payer.pubkey(), is_signer=True, is_writable=True),
|
||||
AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(
|
||||
pubkey=creator_vault,
|
||||
is_signer=False,
|
||||
@@ -191,9 +182,7 @@ async def sell_token(
|
||||
AccountMeta(
|
||||
pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=PUMP_PROGRAM, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(pubkey=PUMP_PROGRAM, is_signer=False, is_writable=False),
|
||||
]
|
||||
|
||||
discriminator = struct.pack("<Q", 12502976635542562355)
|
||||
@@ -220,7 +209,9 @@ async def sell_token(
|
||||
)
|
||||
tx_hash = tx.value
|
||||
print(f"Transaction sent: https://explorer.solana.com/tx/{tx_hash}")
|
||||
await client.confirm_transaction(tx_hash, commitment="confirmed", sleep_seconds=1)
|
||||
await client.confirm_transaction(
|
||||
tx_hash, commitment="confirmed", sleep_seconds=1
|
||||
)
|
||||
print("Transaction confirmed")
|
||||
return # Success, exit the function
|
||||
except Exception as e:
|
||||
@@ -248,7 +239,9 @@ async def main():
|
||||
|
||||
print(f"Bonding curve address: {bonding_curve}")
|
||||
print(f"Selling tokens with {slippage * 100:.1f}% slippage tolerance...")
|
||||
await sell_token(mint, bonding_curve, associated_bonding_curve, creator_vault, slippage)
|
||||
await sell_token(
|
||||
mint, bonding_curve, associated_bonding_curve, creator_vault, slippage
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -31,17 +31,35 @@ COMPUTE_UNIT_LIMIT = 250_000 # Compute unit limit for the transaction
|
||||
load_dotenv()
|
||||
|
||||
# Global constants from existing codebase
|
||||
PUMP_PROGRAM: Final[Pubkey] = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
|
||||
PUMP_GLOBAL: Final[Pubkey] = Pubkey.from_string("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf")
|
||||
PUMP_EVENT_AUTHORITY: Final[Pubkey] = Pubkey.from_string("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1")
|
||||
PUMP_FEE: Final[Pubkey] = Pubkey.from_string("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM")
|
||||
PUMP_MINT_AUTHORITY: Final[Pubkey] = Pubkey.from_string("TSLvdd1pWpHVjahSpsvCXUbgwsL3JAcvokwaKt1eokM")
|
||||
PUMP_PROGRAM: Final[Pubkey] = Pubkey.from_string(
|
||||
"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
|
||||
)
|
||||
PUMP_GLOBAL: Final[Pubkey] = Pubkey.from_string(
|
||||
"4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf"
|
||||
)
|
||||
PUMP_EVENT_AUTHORITY: Final[Pubkey] = Pubkey.from_string(
|
||||
"Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"
|
||||
)
|
||||
PUMP_FEE: Final[Pubkey] = Pubkey.from_string(
|
||||
"CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"
|
||||
)
|
||||
PUMP_MINT_AUTHORITY: Final[Pubkey] = Pubkey.from_string(
|
||||
"TSLvdd1pWpHVjahSpsvCXUbgwsL3JAcvokwaKt1eokM"
|
||||
)
|
||||
|
||||
SYSTEM_PROGRAM: Final[Pubkey] = Pubkey.from_string("11111111111111111111111111111111")
|
||||
SYSTEM_TOKEN_PROGRAM: Final[Pubkey] = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
|
||||
SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM: Final[Pubkey] = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")
|
||||
SYSTEM_RENT: Final[Pubkey] = Pubkey.from_string("SysvarRent111111111111111111111111111111111")
|
||||
METAPLEX_TOKEN_METADATA: Final[Pubkey] = Pubkey.from_string("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s")
|
||||
SYSTEM_TOKEN_PROGRAM: Final[Pubkey] = Pubkey.from_string(
|
||||
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
||||
)
|
||||
SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM: Final[Pubkey] = Pubkey.from_string(
|
||||
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
|
||||
)
|
||||
SYSTEM_RENT: Final[Pubkey] = Pubkey.from_string(
|
||||
"SysvarRent111111111111111111111111111111111"
|
||||
)
|
||||
METAPLEX_TOKEN_METADATA: Final[Pubkey] = Pubkey.from_string(
|
||||
"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
|
||||
)
|
||||
|
||||
LAMPORTS_PER_SOL: Final[int] = 1_000_000_000
|
||||
TOKEN_DECIMALS: Final[int] = 6
|
||||
@@ -57,10 +75,7 @@ PRIVATE_KEY = os.environ.get("SOLANA_PRIVATE_KEY")
|
||||
|
||||
def find_bonding_curve_address(mint: Pubkey) -> tuple[Pubkey, int]:
|
||||
"""Find the bonding curve PDA for a mint."""
|
||||
return Pubkey.find_program_address(
|
||||
[b"bonding-curve", bytes(mint)],
|
||||
PUMP_PROGRAM
|
||||
)
|
||||
return Pubkey.find_program_address([b"bonding-curve", bytes(mint)], PUMP_PROGRAM)
|
||||
|
||||
|
||||
def find_associated_bonding_curve(mint: Pubkey, bonding_curve: Pubkey) -> Pubkey:
|
||||
@@ -92,10 +107,7 @@ def find_metadata_address(mint: Pubkey) -> Pubkey:
|
||||
def find_creator_vault(creator: Pubkey) -> Pubkey:
|
||||
"""Find the creator vault PDA."""
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
b"creator-vault",
|
||||
bytes(creator)
|
||||
],
|
||||
[b"creator-vault", bytes(creator)],
|
||||
PUMP_PROGRAM,
|
||||
)
|
||||
return derived_address
|
||||
@@ -103,22 +115,17 @@ def find_creator_vault(creator: Pubkey) -> Pubkey:
|
||||
|
||||
def _find_global_volume_accumulator() -> Pubkey:
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
b"global_volume_accumulator"
|
||||
],
|
||||
[b"global_volume_accumulator"],
|
||||
PUMP_PROGRAM,
|
||||
)
|
||||
)
|
||||
return derived_address
|
||||
|
||||
|
||||
def _find_user_volume_accumulator(user: Pubkey) -> Pubkey:
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
b"user_volume_accumulator",
|
||||
bytes(user)
|
||||
],
|
||||
[b"user_volume_accumulator", bytes(user)],
|
||||
PUMP_PROGRAM,
|
||||
)
|
||||
)
|
||||
return derived_address
|
||||
|
||||
|
||||
@@ -147,28 +154,32 @@ def create_pump_create_instruction(
|
||||
AccountMeta(pubkey=user, is_signer=True, is_writable=True),
|
||||
AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(
|
||||
pubkey=SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM,
|
||||
is_signer=False,
|
||||
is_writable=False,
|
||||
),
|
||||
AccountMeta(pubkey=SYSTEM_RENT, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=PUMP_PROGRAM, is_signer=False, is_writable=False),
|
||||
]
|
||||
|
||||
|
||||
# Encode string as length-prefixed
|
||||
def encode_string(s: str) -> bytes:
|
||||
encoded = s.encode('utf-8')
|
||||
encoded = s.encode("utf-8")
|
||||
return struct.pack("<I", len(encoded)) + encoded
|
||||
|
||||
|
||||
def encode_pubkey(pubkey: Pubkey) -> bytes:
|
||||
return bytes(pubkey)
|
||||
|
||||
data = (
|
||||
CREATE_DISCRIMINATOR +
|
||||
encode_string(name) +
|
||||
encode_string(symbol) +
|
||||
encode_string(uri) +
|
||||
encode_pubkey(creator)
|
||||
CREATE_DISCRIMINATOR
|
||||
+ encode_string(name)
|
||||
+ encode_string(symbol)
|
||||
+ encode_string(uri)
|
||||
+ encode_pubkey(creator)
|
||||
)
|
||||
|
||||
|
||||
return Instruction(PUMP_PROGRAM, data, accounts)
|
||||
|
||||
|
||||
@@ -198,16 +209,22 @@ def create_buy_instruction(
|
||||
AccountMeta(pubkey=creator_vault, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=PUMP_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=_find_global_volume_accumulator(), is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=_find_user_volume_accumulator(user), is_signer=False, is_writable=True),
|
||||
AccountMeta(
|
||||
pubkey=_find_global_volume_accumulator(), is_signer=False, is_writable=True
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=_find_user_volume_accumulator(user),
|
||||
is_signer=False,
|
||||
is_writable=True,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
data = (
|
||||
BUY_DISCRIMINATOR +
|
||||
struct.pack("<Q", token_amount) +
|
||||
struct.pack("<Q", max_sol_cost)
|
||||
BUY_DISCRIMINATOR
|
||||
+ struct.pack("<Q", token_amount)
|
||||
+ struct.pack("<Q", max_sol_cost)
|
||||
)
|
||||
|
||||
|
||||
return Instruction(PUMP_PROGRAM, data, accounts)
|
||||
|
||||
|
||||
@@ -216,50 +233,53 @@ async def main():
|
||||
private_key_bytes = base58.b58decode(PRIVATE_KEY)
|
||||
payer = Keypair.from_bytes(private_key_bytes)
|
||||
mint_keypair = Keypair()
|
||||
|
||||
|
||||
print("Creating token with:")
|
||||
print(f" Name: {TOKEN_NAME}")
|
||||
print(f" Symbol: {TOKEN_SYMBOL}")
|
||||
print(f" Mint: {mint_keypair.pubkey()}")
|
||||
print(f" Creator: {payer.pubkey()}")
|
||||
|
||||
|
||||
# Derive PDAs
|
||||
bonding_curve, _ = find_bonding_curve_address(mint_keypair.pubkey())
|
||||
associated_bonding_curve = find_associated_bonding_curve(mint_keypair.pubkey(), bonding_curve)
|
||||
associated_bonding_curve = find_associated_bonding_curve(
|
||||
mint_keypair.pubkey(), bonding_curve
|
||||
)
|
||||
metadata = find_metadata_address(mint_keypair.pubkey())
|
||||
user_ata = get_associated_token_address(payer.pubkey(), mint_keypair.pubkey())
|
||||
creator_vault = find_creator_vault(payer.pubkey())
|
||||
|
||||
|
||||
print("\nDerived addresses:")
|
||||
print(f" Bonding curve: {bonding_curve}")
|
||||
print(f" Associated bonding curve: {associated_bonding_curve}")
|
||||
print(f" Metadata: {metadata}")
|
||||
print(f" User ATA: {user_ata}")
|
||||
print(f" Creator vault: {creator_vault}")
|
||||
|
||||
|
||||
# Calculate buy parameters
|
||||
# For pump.fun, we need to calculate expected tokens based on initial curve state
|
||||
# Initial virtual reserves (from pump.fun constants)
|
||||
initial_virtual_token_reserves = 1_073_000_000 * 10**TOKEN_DECIMALS
|
||||
initial_virtual_sol_reserves = 30 * LAMPORTS_PER_SOL
|
||||
initial_real_token_reserves = 793_100_000 * 10**TOKEN_DECIMALS
|
||||
|
||||
|
||||
initial_price = initial_virtual_sol_reserves / initial_virtual_token_reserves
|
||||
|
||||
|
||||
buy_amount_lamports = int(BUY_AMOUNT_SOL * LAMPORTS_PER_SOL)
|
||||
expected_tokens = int((buy_amount_lamports * 0.99) / initial_price) # 1% buffer for fees
|
||||
expected_tokens = int(
|
||||
(buy_amount_lamports * 0.99) / initial_price
|
||||
) # 1% buffer for fees
|
||||
max_sol_cost = int(buy_amount_lamports * (1 + MAX_SLIPPAGE))
|
||||
|
||||
|
||||
print("\nBuy parameters:")
|
||||
print(f" Buy amount: {BUY_AMOUNT_SOL} SOL")
|
||||
print(f" Expected tokens: {expected_tokens / 10**TOKEN_DECIMALS:.6f}")
|
||||
print(f" Max SOL cost: {max_sol_cost / LAMPORTS_PER_SOL:.6f} SOL")
|
||||
|
||||
|
||||
instructions = [
|
||||
# Priority fee instructions
|
||||
set_compute_unit_limit(COMPUTE_UNIT_LIMIT),
|
||||
set_compute_unit_price(PRIORITY_FEE_MICROLAMPORTS),
|
||||
|
||||
# Create token with pump.fun (this will handle mint account, metadata, etc.)
|
||||
create_pump_create_instruction(
|
||||
mint=mint_keypair.pubkey(),
|
||||
@@ -274,7 +294,6 @@ async def main():
|
||||
symbol=TOKEN_SYMBOL,
|
||||
uri=TOKEN_URI,
|
||||
),
|
||||
|
||||
# Create user ATA
|
||||
create_idempotent_associated_token_account(
|
||||
payer.pubkey(),
|
||||
@@ -282,7 +301,6 @@ async def main():
|
||||
mint_keypair.pubkey(),
|
||||
SYSTEM_TOKEN_PROGRAM,
|
||||
),
|
||||
|
||||
# Buy tokens
|
||||
create_buy_instruction(
|
||||
global_state=PUMP_GLOBAL,
|
||||
@@ -297,32 +315,34 @@ async def main():
|
||||
max_sol_cost=max_sol_cost,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
# Send transaction
|
||||
async with AsyncClient(RPC_ENDPOINT) as client:
|
||||
recent_blockhash = await client.get_latest_blockhash()
|
||||
message = Message(instructions, payer.pubkey())
|
||||
transaction = Transaction([payer, mint_keypair], message, recent_blockhash.value.blockhash)
|
||||
|
||||
transaction = Transaction(
|
||||
[payer, mint_keypair], message, recent_blockhash.value.blockhash
|
||||
)
|
||||
|
||||
print("\nSending transaction...")
|
||||
opts = TxOpts(skip_preflight=True, preflight_commitment=Confirmed)
|
||||
|
||||
|
||||
try:
|
||||
response = await client.send_transaction(transaction, opts)
|
||||
tx_hash = response.value
|
||||
|
||||
|
||||
print(f"Transaction sent: https://solscan.io/tx/{tx_hash}")
|
||||
|
||||
|
||||
print("Waiting for confirmation...")
|
||||
await client.confirm_transaction(tx_hash, commitment="confirmed")
|
||||
print("Transaction confirmed!")
|
||||
|
||||
|
||||
return tx_hash
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"Transaction failed: {e}")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -21,28 +21,29 @@ PUMP_AMM_PROGRAM_ID = Pubkey.from_string("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52F
|
||||
TOKEN_MINT = Pubkey.from_string("35ySx7Rt3RqeTp75QB81FgRvPT5yDY2m5BupsUYDpump")
|
||||
|
||||
|
||||
async def get_market_address_by_base_mint(base_mint_address: Pubkey, amm_program_id: Pubkey):
|
||||
async def get_market_address_by_base_mint(
|
||||
base_mint_address: Pubkey, amm_program_id: Pubkey
|
||||
):
|
||||
async with AsyncClient(RPC_ENDPOINT) as client:
|
||||
base_mint_bytes = bytes(base_mint_address)
|
||||
|
||||
|
||||
# Define the offset for base_mint field
|
||||
offset = 43
|
||||
|
||||
|
||||
# Create the filter to match the base_mint
|
||||
filters = [
|
||||
MemcmpOpts(offset=offset, bytes=base_mint_bytes)
|
||||
]
|
||||
|
||||
filters = [MemcmpOpts(offset=offset, bytes=base_mint_bytes)]
|
||||
|
||||
# Retrieve the accounts that match the filter
|
||||
response = await client.get_program_accounts(
|
||||
amm_program_id, # AMM program ID
|
||||
encoding="base64",
|
||||
filters=filters
|
||||
filters=filters,
|
||||
)
|
||||
|
||||
pool_addresses = [account.pubkey for account in response.value]
|
||||
return pool_addresses[0]
|
||||
|
||||
|
||||
|
||||
async def get_market_data(market_address: Pubkey):
|
||||
async with AsyncClient(RPC_ENDPOINT) as client:
|
||||
response = await client.get_account_info(market_address, encoding="base64")
|
||||
@@ -65,15 +66,19 @@ async def get_market_data(market_address: Pubkey):
|
||||
|
||||
for field_name, field_type in fields:
|
||||
if field_type == "pubkey":
|
||||
value = data[offset:offset + 32]
|
||||
value = data[offset : offset + 32]
|
||||
parsed_data[field_name] = base58.b58encode(value).decode("utf-8")
|
||||
offset += 32
|
||||
elif field_type in {"u64", "i64"}:
|
||||
value = struct.unpack("<Q", data[offset:offset + 8])[0] if field_type == "u64" else struct.unpack("<q", data[offset:offset + 8])[0]
|
||||
value = (
|
||||
struct.unpack("<Q", data[offset : offset + 8])[0]
|
||||
if field_type == "u64"
|
||||
else struct.unpack("<q", data[offset : offset + 8])[0]
|
||||
)
|
||||
parsed_data[field_name] = value
|
||||
offset += 8
|
||||
elif field_type == "u16":
|
||||
value = struct.unpack("<H", data[offset:offset + 2])[0]
|
||||
value = struct.unpack("<H", data[offset : offset + 2])[0]
|
||||
parsed_data[field_name] = value
|
||||
offset += 2
|
||||
elif field_type == "u8":
|
||||
@@ -83,9 +88,11 @@ async def get_market_data(market_address: Pubkey):
|
||||
|
||||
return parsed_data
|
||||
|
||||
|
||||
|
||||
async def main():
|
||||
market_address = await get_market_address_by_base_mint(TOKEN_MINT, PUMP_AMM_PROGRAM_ID)
|
||||
market_address = await get_market_address_by_base_mint(
|
||||
TOKEN_MINT, PUMP_AMM_PROGRAM_ID
|
||||
)
|
||||
print(market_address)
|
||||
|
||||
market_data = await get_market_data(market_address)
|
||||
|
||||
@@ -42,62 +42,75 @@ PAYER = Keypair.from_bytes(PRIVATE_KEY)
|
||||
SLIPPAGE = 0.3 # Slippage tolerance (30%) - the maximum price movement you'll accept
|
||||
|
||||
TOKEN_DECIMALS = 6
|
||||
BUY_DISCRIMINATOR = bytes.fromhex("66063d1201daebea") # Program instruction identifier for the buy function
|
||||
BUY_DISCRIMINATOR = bytes.fromhex(
|
||||
"66063d1201daebea"
|
||||
) # Program instruction identifier for the buy function
|
||||
|
||||
# Solana system addresses and program IDs
|
||||
SOL = Pubkey.from_string("So11111111111111111111111111111111111111112")
|
||||
PUMP_AMM_PROGRAM_ID = Pubkey.from_string("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
|
||||
PUMP_SWAP_GLOBAL_CONFIG = Pubkey.from_string("ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw")
|
||||
PUMP_PROTOCOL_FEE_RECIPIENT = Pubkey.from_string("7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ")
|
||||
PUMP_PROTOCOL_FEE_RECIPIENT_TOKEN_ACCOUNT = Pubkey.from_string("7GFUN3bWzJMKMRZ34JLsvcqdssDbXnp589SiE33KVwcC")
|
||||
PUMP_SWAP_GLOBAL_CONFIG = Pubkey.from_string(
|
||||
"ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw"
|
||||
)
|
||||
PUMP_PROTOCOL_FEE_RECIPIENT = Pubkey.from_string(
|
||||
"7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ"
|
||||
)
|
||||
PUMP_PROTOCOL_FEE_RECIPIENT_TOKEN_ACCOUNT = Pubkey.from_string(
|
||||
"7GFUN3bWzJMKMRZ34JLsvcqdssDbXnp589SiE33KVwcC"
|
||||
)
|
||||
SYSTEM_TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
|
||||
SYSTEM_PROGRAM = Pubkey.from_string("11111111111111111111111111111111")
|
||||
SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")
|
||||
PUMP_SWAP_EVENT_AUTHORITY = Pubkey.from_string("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR")
|
||||
SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string(
|
||||
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
|
||||
)
|
||||
PUMP_SWAP_EVENT_AUTHORITY = Pubkey.from_string(
|
||||
"GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR"
|
||||
)
|
||||
LAMPORTS_PER_SOL = 1_000_000_000
|
||||
COMPUTE_UNIT_PRICE = 10_000 # Price in micro-lamports per compute unit
|
||||
COMPUTE_UNIT_BUDGET = 200_000 # Maximum compute units to use
|
||||
|
||||
|
||||
async def get_market_address_by_base_mint(client: AsyncClient, base_mint_address: Pubkey, amm_program_id: Pubkey) -> Pubkey:
|
||||
async def get_market_address_by_base_mint(
|
||||
client: AsyncClient, base_mint_address: Pubkey, amm_program_id: Pubkey
|
||||
) -> Pubkey:
|
||||
"""Find the market address for a given token mint.
|
||||
|
||||
|
||||
Searches for the AMM pool that contains the specified token mint as its base token
|
||||
by querying program accounts with a filter for the base_mint field.
|
||||
|
||||
|
||||
Args:
|
||||
client: Solana RPC client instance
|
||||
base_mint_address: Address of the token mint you want to find the market for
|
||||
amm_program_id: Address of the AMM program
|
||||
|
||||
|
||||
Returns:
|
||||
The Pubkey of the market (AMM pool) for the token
|
||||
"""
|
||||
base_mint_bytes = bytes(base_mint_address)
|
||||
offset = 43 # Offset where the base_mint field is stored in the account data structure
|
||||
filters = [
|
||||
MemcmpOpts(offset=offset, bytes=base_mint_bytes)
|
||||
]
|
||||
|
||||
offset = (
|
||||
43 # Offset where the base_mint field is stored in the account data structure
|
||||
)
|
||||
filters = [MemcmpOpts(offset=offset, bytes=base_mint_bytes)]
|
||||
|
||||
response = await client.get_program_accounts(
|
||||
amm_program_id,
|
||||
encoding="base64",
|
||||
filters=filters
|
||||
amm_program_id, encoding="base64", filters=filters
|
||||
)
|
||||
|
||||
market_address = [account.pubkey for account in response.value][0]
|
||||
return market_address
|
||||
|
||||
|
||||
|
||||
async def get_market_data(client: AsyncClient, market_address: Pubkey) -> dict:
|
||||
"""Fetch and parse market data from the blockchain.
|
||||
|
||||
|
||||
Retrieves and deserializes the binary data stored in the market account
|
||||
into a structured dictionary containing key market information.
|
||||
|
||||
|
||||
Args:
|
||||
client: Solana RPC client instance
|
||||
market_address: Address of the market (AMM pool) to fetch data for
|
||||
|
||||
|
||||
Returns:
|
||||
Dictionary containing the parsed market data
|
||||
"""
|
||||
@@ -118,20 +131,24 @@ async def get_market_data(client: AsyncClient, market_address: Pubkey) -> dict:
|
||||
("pool_base_token_account", "pubkey"),
|
||||
("pool_quote_token_account", "pubkey"),
|
||||
("lp_supply", "u64"),
|
||||
("coin_creator", "pubkey")
|
||||
("coin_creator", "pubkey"),
|
||||
]
|
||||
|
||||
for field_name, field_type in fields:
|
||||
if field_type == "pubkey":
|
||||
value = data[offset:offset + 32]
|
||||
value = data[offset : offset + 32]
|
||||
parsed_data[field_name] = base58.b58encode(value).decode("utf-8")
|
||||
offset += 32
|
||||
elif field_type in {"u64", "i64"}:
|
||||
value = struct.unpack("<Q", data[offset:offset + 8])[0] if field_type == "u64" else struct.unpack("<q", data[offset:offset + 8])[0]
|
||||
value = (
|
||||
struct.unpack("<Q", data[offset : offset + 8])[0]
|
||||
if field_type == "u64"
|
||||
else struct.unpack("<q", data[offset : offset + 8])[0]
|
||||
)
|
||||
parsed_data[field_name] = value
|
||||
offset += 8
|
||||
elif field_type == "u16":
|
||||
value = struct.unpack("<H", data[offset:offset + 2])[0]
|
||||
value = struct.unpack("<H", data[offset : offset + 2])[0]
|
||||
parsed_data[field_name] = value
|
||||
offset += 2
|
||||
elif field_type == "u8":
|
||||
@@ -141,103 +158,115 @@ async def get_market_data(client: AsyncClient, market_address: Pubkey) -> dict:
|
||||
|
||||
return parsed_data
|
||||
|
||||
|
||||
def find_coin_creator_vault(coin_creator: Pubkey) -> Pubkey:
|
||||
"""Derive the Program Derived Address (PDA) for a coin creator's vault.
|
||||
|
||||
|
||||
Calculates the deterministic PDA that serves as the vault authority
|
||||
for a specific coin creator in the PUMP AMM protocol.
|
||||
|
||||
|
||||
Args:
|
||||
coin_creator: Pubkey of the coin creator account
|
||||
|
||||
|
||||
Returns:
|
||||
Pubkey of the derived coin creator vault authority
|
||||
|
||||
|
||||
Note:
|
||||
This vault is used to collect creator fees from token transactions
|
||||
"""
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
b"creator_vault",
|
||||
bytes(coin_creator)
|
||||
],
|
||||
[b"creator_vault", bytes(coin_creator)],
|
||||
PUMP_AMM_PROGRAM_ID,
|
||||
)
|
||||
)
|
||||
return derived_address
|
||||
|
||||
|
||||
def find_global_volume_accumulator() -> Pubkey:
|
||||
"""Derive the Program Derived Address (PDA) for the global volume accumulator.
|
||||
|
||||
|
||||
Calculates the deterministic PDA that tracks global trading volume
|
||||
across all pools in the PUMP AMM protocol.
|
||||
|
||||
|
||||
Returns:
|
||||
Pubkey of the derived global volume accumulator account
|
||||
"""
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
b"global_volume_accumulator"
|
||||
],
|
||||
[b"global_volume_accumulator"],
|
||||
PUMP_AMM_PROGRAM_ID,
|
||||
)
|
||||
return derived_address
|
||||
|
||||
|
||||
def find_user_volume_accumulator(user: Pubkey) -> Pubkey:
|
||||
"""Derive the Program Derived Address (PDA) for a user's volume accumulator.
|
||||
|
||||
|
||||
Calculates the deterministic PDA that tracks trading volume
|
||||
for a specific user in the PUMP AMM protocol.
|
||||
|
||||
|
||||
Args:
|
||||
user: Pubkey of the user account
|
||||
|
||||
|
||||
Returns:
|
||||
Pubkey of the derived user volume accumulator account
|
||||
"""
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
b"user_volume_accumulator",
|
||||
bytes(user)
|
||||
],
|
||||
[b"user_volume_accumulator", bytes(user)],
|
||||
PUMP_AMM_PROGRAM_ID,
|
||||
)
|
||||
return derived_address
|
||||
|
||||
async def calculate_token_pool_price(client: AsyncClient, pool_base_token_account: Pubkey, pool_quote_token_account: Pubkey) -> float:
|
||||
|
||||
async def calculate_token_pool_price(
|
||||
client: AsyncClient,
|
||||
pool_base_token_account: Pubkey,
|
||||
pool_quote_token_account: Pubkey,
|
||||
) -> float:
|
||||
"""Calculate the current price of tokens in an AMM pool.
|
||||
|
||||
|
||||
Fetches the balance of tokens in the pool and calculates the price ratio
|
||||
between the base token and quote token (typically SOL).
|
||||
|
||||
|
||||
Args:
|
||||
client: Solana RPC client instance
|
||||
pool_base_token_account: Address of the pool's base token account (your token)
|
||||
pool_quote_token_account: Address of the pool's quote token account (SOL)
|
||||
|
||||
|
||||
Returns:
|
||||
The price of the base token in terms of the quote token (SOL per token)
|
||||
"""
|
||||
base_balance_resp = await client.get_token_account_balance(pool_base_token_account)
|
||||
quote_balance_resp = await client.get_token_account_balance(pool_quote_token_account)
|
||||
|
||||
quote_balance_resp = await client.get_token_account_balance(
|
||||
pool_quote_token_account
|
||||
)
|
||||
|
||||
# Extract the UI amounts (human-readable with decimals)
|
||||
base_amount = float(base_balance_resp.value.ui_amount)
|
||||
quote_amount = float(quote_balance_resp.value.ui_amount)
|
||||
|
||||
token_price = quote_amount / base_amount
|
||||
|
||||
|
||||
return token_price
|
||||
|
||||
async def buy_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer: Keypair,
|
||||
base_mint: Pubkey, user_base_token_account: Pubkey,
|
||||
user_quote_token_account: Pubkey, pool_base_token_account: Pubkey,
|
||||
pool_quote_token_account: Pubkey, coin_creator_vault_authority: Pubkey,
|
||||
coin_creator_vault_ata: Pubkey, sol_amount_to_spend: int, slippage: float = 0.25) -> str | None:
|
||||
|
||||
async def buy_pump_swap(
|
||||
client: AsyncClient,
|
||||
pump_fun_amm_market: Pubkey,
|
||||
payer: Keypair,
|
||||
base_mint: Pubkey,
|
||||
user_base_token_account: Pubkey,
|
||||
user_quote_token_account: Pubkey,
|
||||
pool_base_token_account: Pubkey,
|
||||
pool_quote_token_account: Pubkey,
|
||||
coin_creator_vault_authority: Pubkey,
|
||||
coin_creator_vault_ata: Pubkey,
|
||||
sol_amount_to_spend: int,
|
||||
slippage: float = 0.25,
|
||||
) -> str | None:
|
||||
"""Buy tokens on the PUMP AMM with slippage protection.
|
||||
|
||||
|
||||
Executes a token purchase on the PUMP AMM protocol, calculating the expected
|
||||
token amount based on the current pool price and applying slippage protection.
|
||||
|
||||
|
||||
Args:
|
||||
client: Solana RPC client instance
|
||||
pump_fun_amm_market: Address of the AMM market
|
||||
@@ -251,12 +280,14 @@ async def buy_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer:
|
||||
coin_creator_vault_ata: Address of the coin creator's associated token account for fees
|
||||
sol_amount_to_spend: Amount of SOL to spend on the purchase (in SOL, not lamports)
|
||||
slippage: Maximum acceptable price slippage, as a decimal (0.25 = 25%)
|
||||
|
||||
|
||||
Returns:
|
||||
Transaction signature if successful, None otherwise
|
||||
"""
|
||||
# Calculate token price
|
||||
token_price_sol = await calculate_token_pool_price(client, pool_base_token_account, pool_quote_token_account)
|
||||
token_price_sol = await calculate_token_pool_price(
|
||||
client, pool_base_token_account, pool_quote_token_account
|
||||
)
|
||||
print(f"Token price in SOL: {token_price_sol:.10f} SOL")
|
||||
|
||||
# Calculate maximum SOL input with slippage protection
|
||||
@@ -273,98 +304,115 @@ async def buy_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer:
|
||||
|
||||
# Define all accounts needed for the buy instruction
|
||||
accounts = [
|
||||
AccountMeta(pubkey=pump_fun_amm_market, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=payer.pubkey(), is_signer=True, is_writable=True),
|
||||
AccountMeta(pubkey=PUMP_SWAP_GLOBAL_CONFIG, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=base_mint, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SOL, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=user_base_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=user_quote_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=pool_base_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=pool_quote_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=PUMP_PROTOCOL_FEE_RECIPIENT, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=PUMP_PROTOCOL_FEE_RECIPIENT_TOKEN_ACCOUNT, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=PUMP_SWAP_EVENT_AUTHORITY, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=PUMP_AMM_PROGRAM_ID, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=coin_creator_vault_ata, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=coin_creator_vault_authority, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=global_volume_accumulator, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=user_volume_accumulator, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=pump_fun_amm_market, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=payer.pubkey(), is_signer=True, is_writable=True),
|
||||
AccountMeta(pubkey=PUMP_SWAP_GLOBAL_CONFIG, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=base_mint, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SOL, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=user_base_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=user_quote_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=pool_base_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=pool_quote_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(
|
||||
pubkey=PUMP_PROTOCOL_FEE_RECIPIENT, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=PUMP_PROTOCOL_FEE_RECIPIENT_TOKEN_ACCOUNT,
|
||||
is_signer=False,
|
||||
is_writable=True,
|
||||
),
|
||||
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(
|
||||
pubkey=SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM,
|
||||
is_signer=False,
|
||||
is_writable=False,
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=PUMP_SWAP_EVENT_AUTHORITY, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(pubkey=PUMP_AMM_PROGRAM_ID, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=coin_creator_vault_ata, is_signer=False, is_writable=True),
|
||||
AccountMeta(
|
||||
pubkey=coin_creator_vault_authority, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=global_volume_accumulator, is_signer=False, is_writable=True
|
||||
),
|
||||
AccountMeta(pubkey=user_volume_accumulator, is_signer=False, is_writable=True),
|
||||
]
|
||||
|
||||
data = BUY_DISCRIMINATOR + struct.pack("<Q", base_amount_out) + struct.pack("<Q", max_sol_input)
|
||||
|
||||
data = (
|
||||
BUY_DISCRIMINATOR
|
||||
+ struct.pack("<Q", base_amount_out)
|
||||
+ struct.pack("<Q", max_sol_input)
|
||||
)
|
||||
|
||||
compute_limit_ix = set_compute_unit_limit(COMPUTE_UNIT_BUDGET)
|
||||
compute_price_ix = set_compute_unit_price(COMPUTE_UNIT_PRICE)
|
||||
|
||||
|
||||
# Wrapping SOL
|
||||
create_wsol_ata_ix = create_idempotent_associated_token_account(
|
||||
payer.pubkey(),
|
||||
payer.pubkey(),
|
||||
SOL,
|
||||
SYSTEM_TOKEN_PROGRAM
|
||||
payer.pubkey(), payer.pubkey(), SOL, SYSTEM_TOKEN_PROGRAM
|
||||
)
|
||||
|
||||
|
||||
# Calculate the amount of SOL to wrap(can also use `max_sol_input`), can get the SOL back by closing the WSOL account while selling
|
||||
pump_protocol_fees = sol_amount_to_spend * 0.1 # adding some buffer fees
|
||||
pump_protocol_fees = sol_amount_to_spend * 0.1 # adding some buffer fees
|
||||
wrap_amount = int((sol_amount_to_spend + pump_protocol_fees) * LAMPORTS_PER_SOL)
|
||||
|
||||
|
||||
transfer_sol_ix = transfer(
|
||||
TransferParams(
|
||||
from_pubkey=payer.pubkey(),
|
||||
to_pubkey=user_quote_token_account,
|
||||
lamports=wrap_amount
|
||||
lamports=wrap_amount,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
sync_native_ix = sync_native(
|
||||
SyncNativeParams(
|
||||
SYSTEM_TOKEN_PROGRAM, user_quote_token_account
|
||||
)
|
||||
SyncNativeParams(SYSTEM_TOKEN_PROGRAM, user_quote_token_account)
|
||||
)
|
||||
|
||||
|
||||
idempotent_ata_ix = create_idempotent_associated_token_account(
|
||||
payer.pubkey(),
|
||||
payer.pubkey(),
|
||||
base_mint,
|
||||
SYSTEM_TOKEN_PROGRAM
|
||||
payer.pubkey(), payer.pubkey(), base_mint, SYSTEM_TOKEN_PROGRAM
|
||||
)
|
||||
|
||||
|
||||
buy_ix = Instruction(PUMP_AMM_PROGRAM_ID, data, accounts)
|
||||
|
||||
|
||||
blockhash_resp = await client.get_latest_blockhash()
|
||||
recent_blockhash = blockhash_resp.value.blockhash
|
||||
|
||||
msg = Message.new_with_blockhash(
|
||||
[compute_limit_ix, compute_price_ix, create_wsol_ata_ix, transfer_sol_ix, sync_native_ix, idempotent_ata_ix, buy_ix],
|
||||
[
|
||||
compute_limit_ix,
|
||||
compute_price_ix,
|
||||
create_wsol_ata_ix,
|
||||
transfer_sol_ix,
|
||||
sync_native_ix,
|
||||
idempotent_ata_ix,
|
||||
buy_ix,
|
||||
],
|
||||
payer.pubkey(),
|
||||
recent_blockhash
|
||||
recent_blockhash,
|
||||
)
|
||||
|
||||
tx_buy = VersionedTransaction(
|
||||
message=msg,
|
||||
keypairs=[payer]
|
||||
)
|
||||
|
||||
tx_buy = VersionedTransaction(message=msg, keypairs=[payer])
|
||||
|
||||
# Optionally, you can simulate the transaction first and check for errors and get the compute units used
|
||||
simulation = await client.simulate_transaction(tx_buy)
|
||||
if simulation.value.err:
|
||||
print(f"Simulation error: {simulation.value.err}")
|
||||
return None
|
||||
|
||||
|
||||
compute_units_used = simulation.value.units_consumed
|
||||
print(f"Simulation successful, compute units used: {compute_units_used}")
|
||||
|
||||
|
||||
try:
|
||||
tx_sig = await client.send_transaction(
|
||||
tx_buy,
|
||||
opts=TxOpts(skip_preflight=True, preflight_commitment=Confirmed),
|
||||
)
|
||||
|
||||
|
||||
tx_hash = tx_sig.value
|
||||
print(f"Transaction sent: https://explorer.solana.com/tx/{tx_hash}")
|
||||
await client.confirm_transaction(tx_hash, commitment="confirmed")
|
||||
@@ -381,10 +429,16 @@ async def main():
|
||||
sol_amount_to_spend = 0.000001
|
||||
|
||||
async with AsyncClient(RPC_ENDPOINT) as client:
|
||||
market_address = await get_market_address_by_base_mint(client, TOKEN_MINT, PUMP_AMM_PROGRAM_ID)
|
||||
market_address = await get_market_address_by_base_mint(
|
||||
client, TOKEN_MINT, PUMP_AMM_PROGRAM_ID
|
||||
)
|
||||
market_data = await get_market_data(client, market_address)
|
||||
coin_creator_vault_authority = find_coin_creator_vault(Pubkey.from_string(market_data["coin_creator"]))
|
||||
coin_creator_vault_ata = get_associated_token_address(coin_creator_vault_authority, SOL)
|
||||
coin_creator_vault_authority = find_coin_creator_vault(
|
||||
Pubkey.from_string(market_data["coin_creator"])
|
||||
)
|
||||
coin_creator_vault_ata = get_associated_token_address(
|
||||
coin_creator_vault_authority, SOL
|
||||
)
|
||||
|
||||
await buy_pump_swap(
|
||||
client,
|
||||
@@ -401,5 +455,6 @@ async def main():
|
||||
SLIPPAGE,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -34,60 +34,73 @@ PAYER = Keypair.from_bytes(PRIVATE_KEY)
|
||||
SLIPPAGE = 0.25 # Slippage tolerance (25%) - the maximum price movement you'll accept
|
||||
|
||||
TOKEN_DECIMALS = 6
|
||||
SELL_DISCRIMINATOR = bytes.fromhex("33e685a4017f83ad") # Program instruction identifier for the sell function
|
||||
SELL_DISCRIMINATOR = bytes.fromhex(
|
||||
"33e685a4017f83ad"
|
||||
) # Program instruction identifier for the sell function
|
||||
|
||||
# Solana system addresses and program IDs
|
||||
SOL = Pubkey.from_string("So11111111111111111111111111111111111111112")
|
||||
PUMP_AMM_PROGRAM_ID = Pubkey.from_string("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
|
||||
PUMP_SWAP_GLOBAL_CONFIG = Pubkey.from_string("ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw")
|
||||
PUMP_PROTOCOL_FEE_RECIPIENT = Pubkey.from_string("7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ")
|
||||
PUMP_PROTOCOL_FEE_RECIPIENT_TOKEN_ACCOUNT = Pubkey.from_string("7GFUN3bWzJMKMRZ34JLsvcqdssDbXnp589SiE33KVwcC")
|
||||
PUMP_SWAP_GLOBAL_CONFIG = Pubkey.from_string(
|
||||
"ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw"
|
||||
)
|
||||
PUMP_PROTOCOL_FEE_RECIPIENT = Pubkey.from_string(
|
||||
"7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ"
|
||||
)
|
||||
PUMP_PROTOCOL_FEE_RECIPIENT_TOKEN_ACCOUNT = Pubkey.from_string(
|
||||
"7GFUN3bWzJMKMRZ34JLsvcqdssDbXnp589SiE33KVwcC"
|
||||
)
|
||||
SYSTEM_TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
|
||||
SYSTEM_PROGRAM = Pubkey.from_string("11111111111111111111111111111111")
|
||||
SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")
|
||||
PUMP_SWAP_EVENT_AUTHORITY = Pubkey.from_string("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR")
|
||||
SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string(
|
||||
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
|
||||
)
|
||||
PUMP_SWAP_EVENT_AUTHORITY = Pubkey.from_string(
|
||||
"GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR"
|
||||
)
|
||||
LAMPORTS_PER_SOL = 1_000_000_000
|
||||
COMPUTE_UNIT_PRICE = 10_000 # Price in micro-lamports per compute unit
|
||||
COMPUTE_UNIT_BUDGET = 100_000 # Maximum compute units to use
|
||||
|
||||
|
||||
async def get_market_address_by_base_mint(client: AsyncClient, base_mint_address: Pubkey, amm_program_id: Pubkey) -> Pubkey:
|
||||
async def get_market_address_by_base_mint(
|
||||
client: AsyncClient, base_mint_address: Pubkey, amm_program_id: Pubkey
|
||||
) -> Pubkey:
|
||||
"""Find the market address for a given token mint.
|
||||
|
||||
|
||||
Searches for the AMM pool that contains the specified token as its base token.
|
||||
|
||||
|
||||
Args:
|
||||
client: Solana RPC client instance
|
||||
base_mint_address: Address of the token mint you want to find the market for
|
||||
amm_program_id: Address of the AMM program
|
||||
|
||||
|
||||
Returns:
|
||||
The Pubkey of the market (AMM pool) for the token
|
||||
"""
|
||||
base_mint_bytes = bytes(base_mint_address)
|
||||
offset = 43 # Offset where the base_mint field is stored in the account data structure
|
||||
filters = [
|
||||
MemcmpOpts(offset=offset, bytes=base_mint_bytes)
|
||||
]
|
||||
|
||||
offset = (
|
||||
43 # Offset where the base_mint field is stored in the account data structure
|
||||
)
|
||||
filters = [MemcmpOpts(offset=offset, bytes=base_mint_bytes)]
|
||||
|
||||
response = await client.get_program_accounts(
|
||||
amm_program_id,
|
||||
encoding="base64",
|
||||
filters=filters
|
||||
amm_program_id, encoding="base64", filters=filters
|
||||
)
|
||||
|
||||
market_address = [account.pubkey for account in response.value][0]
|
||||
return market_address
|
||||
|
||||
|
||||
|
||||
async def get_market_data(client: AsyncClient, market_address: Pubkey) -> dict:
|
||||
"""Fetch and parse market data from the blockchain.
|
||||
|
||||
|
||||
Retrieves and deserializes the data stored in the market account.
|
||||
|
||||
|
||||
Args:
|
||||
client: Solana RPC client instance
|
||||
market_address: Address of the market (AMM pool) to fetch data for
|
||||
|
||||
|
||||
Returns:
|
||||
Dictionary containing the parsed market data
|
||||
"""
|
||||
@@ -108,20 +121,24 @@ async def get_market_data(client: AsyncClient, market_address: Pubkey) -> dict:
|
||||
("pool_base_token_account", "pubkey"),
|
||||
("pool_quote_token_account", "pubkey"),
|
||||
("lp_supply", "u64"),
|
||||
("coin_creator", "pubkey")
|
||||
("coin_creator", "pubkey"),
|
||||
]
|
||||
|
||||
for field_name, field_type in fields:
|
||||
if field_type == "pubkey":
|
||||
value = data[offset:offset + 32]
|
||||
value = data[offset : offset + 32]
|
||||
parsed_data[field_name] = base58.b58encode(value).decode("utf-8")
|
||||
offset += 32
|
||||
elif field_type in {"u64", "i64"}:
|
||||
value = struct.unpack("<Q", data[offset:offset + 8])[0] if field_type == "u64" else struct.unpack("<q", data[offset:offset + 8])[0]
|
||||
value = (
|
||||
struct.unpack("<Q", data[offset : offset + 8])[0]
|
||||
if field_type == "u64"
|
||||
else struct.unpack("<q", data[offset : offset + 8])[0]
|
||||
)
|
||||
parsed_data[field_name] = value
|
||||
offset += 8
|
||||
elif field_type == "u16":
|
||||
value = struct.unpack("<H", data[offset:offset + 2])[0]
|
||||
value = struct.unpack("<H", data[offset : offset + 2])[0]
|
||||
parsed_data[field_name] = value
|
||||
offset += 2
|
||||
elif field_type == "u8":
|
||||
@@ -131,63 +148,69 @@ async def get_market_data(client: AsyncClient, market_address: Pubkey) -> dict:
|
||||
|
||||
return parsed_data
|
||||
|
||||
|
||||
def find_coin_creator_vault(coin_creator: Pubkey) -> Pubkey:
|
||||
"""Derive the Program Derived Address (PDA) for a coin creator's vault.
|
||||
|
||||
|
||||
Calculates the deterministic PDA that serves as the vault authority
|
||||
for a specific coin creator in the PUMP AMM protocol.
|
||||
|
||||
|
||||
Args:
|
||||
coin_creator: Pubkey of the coin creator account
|
||||
|
||||
|
||||
Returns:
|
||||
Pubkey of the derived coin creator vault authority
|
||||
|
||||
|
||||
Note:
|
||||
This vault is used to collect creator fees from token transactions
|
||||
"""
|
||||
derived_address, _ = Pubkey.find_program_address(
|
||||
[
|
||||
b"creator_vault",
|
||||
bytes(coin_creator)
|
||||
],
|
||||
[b"creator_vault", bytes(coin_creator)],
|
||||
PUMP_AMM_PROGRAM_ID,
|
||||
)
|
||||
)
|
||||
return derived_address
|
||||
|
||||
async def calculate_token_pool_price(client: AsyncClient, pool_base_token_account: Pubkey, pool_quote_token_account: Pubkey) -> float:
|
||||
|
||||
async def calculate_token_pool_price(
|
||||
client: AsyncClient,
|
||||
pool_base_token_account: Pubkey,
|
||||
pool_quote_token_account: Pubkey,
|
||||
) -> float:
|
||||
"""Calculate the price of tokens in the pool.
|
||||
|
||||
|
||||
Fetches the balance of tokens in the pool and calculates the price ratio.
|
||||
|
||||
|
||||
Args:
|
||||
client: Solana RPC client instance
|
||||
pool_base_token_account: Address of the pool's base token account (your token)
|
||||
pool_quote_token_account: Address of the pool's quote token account (SOL)
|
||||
|
||||
|
||||
Returns:
|
||||
The price of the base token in terms of the quote token (usually SOL)
|
||||
"""
|
||||
base_balance_resp = await client.get_token_account_balance(pool_base_token_account)
|
||||
quote_balance_resp = await client.get_token_account_balance(pool_quote_token_account)
|
||||
|
||||
quote_balance_resp = await client.get_token_account_balance(
|
||||
pool_quote_token_account
|
||||
)
|
||||
|
||||
# Extract the UI amounts (human-readable with decimals)
|
||||
base_amount = float(base_balance_resp.value.ui_amount)
|
||||
quote_amount = float(quote_balance_resp.value.ui_amount)
|
||||
|
||||
token_price = quote_amount / base_amount
|
||||
|
||||
|
||||
return token_price
|
||||
|
||||
|
||||
def create_ata_idempotent_ix(payer_pubkey: Pubkey) -> Instruction:
|
||||
"""Create an instruction to create an Associated Token Account (ATA) if it doesn't exist.
|
||||
|
||||
|
||||
This creates an instruction that will create an Associated Token Account for SOL
|
||||
if it doesn't already exist.
|
||||
|
||||
|
||||
Args:
|
||||
payer_pubkey: The public key of the account that will pay for the creation
|
||||
|
||||
|
||||
Returns:
|
||||
An instruction to create the ATA
|
||||
"""
|
||||
@@ -201,22 +224,33 @@ def create_ata_idempotent_ix(payer_pubkey: Pubkey) -> Instruction:
|
||||
AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False),
|
||||
]
|
||||
|
||||
|
||||
# The data for creating an ATA idempotently is just a single byte with value 1
|
||||
# Check the details here:
|
||||
# https://github.com/solana-program/associated-token-account/blob/main/program/src/instruction.rs
|
||||
data = bytes([1])
|
||||
return Instruction(SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, data, instruction_accounts)
|
||||
return Instruction(
|
||||
SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, data, instruction_accounts
|
||||
)
|
||||
|
||||
async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer: Keypair,
|
||||
base_mint: Pubkey, user_base_token_account: Pubkey,
|
||||
user_quote_token_account: Pubkey, pool_base_token_account: Pubkey,
|
||||
pool_quote_token_account: Pubkey, coin_creator_vault_authority: Pubkey,
|
||||
coin_creator_vault_ata: Pubkey, slippage: float = 0.25) -> str | None:
|
||||
|
||||
async def sell_pump_swap(
|
||||
client: AsyncClient,
|
||||
pump_fun_amm_market: Pubkey,
|
||||
payer: Keypair,
|
||||
base_mint: Pubkey,
|
||||
user_base_token_account: Pubkey,
|
||||
user_quote_token_account: Pubkey,
|
||||
pool_base_token_account: Pubkey,
|
||||
pool_quote_token_account: Pubkey,
|
||||
coin_creator_vault_authority: Pubkey,
|
||||
coin_creator_vault_ata: Pubkey,
|
||||
slippage: float = 0.25,
|
||||
) -> str | None:
|
||||
"""Sell tokens on the PUMP AMM.
|
||||
|
||||
|
||||
This function sells all tokens in the user's token account with the specified slippage tolerance.
|
||||
|
||||
|
||||
Args:
|
||||
client: Solana RPC client instance
|
||||
pump_fun_amm_market: Address of the AMM market
|
||||
@@ -229,20 +263,24 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer
|
||||
coin_creator_vault_authority: Address of the coin creator's vault authority
|
||||
coin_creator_vault_ata: Address of the coin creator's associated token account for fees
|
||||
slippage: Maximum acceptable price slippage, as a decimal (0.25 = 25%)
|
||||
|
||||
|
||||
Returns:
|
||||
Transaction signature if successful, None otherwise
|
||||
"""
|
||||
# Get token balance
|
||||
token_balance = int((await client.get_token_account_balance(user_base_token_account)).value.amount)
|
||||
token_balance = int(
|
||||
(await client.get_token_account_balance(user_base_token_account)).value.amount
|
||||
)
|
||||
token_balance_decimal = token_balance / 10**TOKEN_DECIMALS
|
||||
print(f"Token balance: {token_balance_decimal}")
|
||||
if token_balance == 0:
|
||||
print("No tokens to sell.")
|
||||
return None
|
||||
|
||||
|
||||
# Calculate token price
|
||||
token_price_sol = await calculate_token_pool_price(client, pool_base_token_account, pool_quote_token_account)
|
||||
token_price_sol = await calculate_token_pool_price(
|
||||
client, pool_base_token_account, pool_quote_token_account
|
||||
)
|
||||
print(f"Price per Token: {token_price_sol:.20f} SOL")
|
||||
|
||||
# Calculate minimum SOL output with slippage protection
|
||||
@@ -256,28 +294,46 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer
|
||||
|
||||
# Define all accounts needed for the sell instruction
|
||||
accounts = [
|
||||
AccountMeta(pubkey=pump_fun_amm_market, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=payer.pubkey(), is_signer=True, is_writable=True),
|
||||
AccountMeta(pubkey=PUMP_SWAP_GLOBAL_CONFIG, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=base_mint, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SOL, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=user_base_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=user_quote_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=pool_base_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=pool_quote_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=PUMP_PROTOCOL_FEE_RECIPIENT, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=PUMP_PROTOCOL_FEE_RECIPIENT_TOKEN_ACCOUNT, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=PUMP_SWAP_EVENT_AUTHORITY, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=PUMP_AMM_PROGRAM_ID, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=coin_creator_vault_ata, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=coin_creator_vault_authority, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=pump_fun_amm_market, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=payer.pubkey(), is_signer=True, is_writable=True),
|
||||
AccountMeta(pubkey=PUMP_SWAP_GLOBAL_CONFIG, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=base_mint, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SOL, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=user_base_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=user_quote_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=pool_base_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(pubkey=pool_quote_token_account, is_signer=False, is_writable=True),
|
||||
AccountMeta(
|
||||
pubkey=PUMP_PROTOCOL_FEE_RECIPIENT, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=PUMP_PROTOCOL_FEE_RECIPIENT_TOKEN_ACCOUNT,
|
||||
is_signer=False,
|
||||
is_writable=True,
|
||||
),
|
||||
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False),
|
||||
AccountMeta(
|
||||
pubkey=SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM,
|
||||
is_signer=False,
|
||||
is_writable=False,
|
||||
),
|
||||
AccountMeta(
|
||||
pubkey=PUMP_SWAP_EVENT_AUTHORITY, is_signer=False, is_writable=False
|
||||
),
|
||||
AccountMeta(pubkey=PUMP_AMM_PROGRAM_ID, is_signer=False, is_writable=False),
|
||||
AccountMeta(pubkey=coin_creator_vault_ata, is_signer=False, is_writable=True),
|
||||
AccountMeta(
|
||||
pubkey=coin_creator_vault_authority, is_signer=False, is_writable=False
|
||||
),
|
||||
]
|
||||
|
||||
data = SELL_DISCRIMINATOR + struct.pack("<Q", amount) + struct.pack("<Q", min_sol_output)
|
||||
|
||||
data = (
|
||||
SELL_DISCRIMINATOR
|
||||
+ struct.pack("<Q", amount)
|
||||
+ struct.pack("<Q", min_sol_output)
|
||||
)
|
||||
|
||||
compute_limit_ix = set_compute_unit_limit(COMPUTE_UNIT_BUDGET)
|
||||
compute_price_ix = set_compute_unit_price(COMPUTE_UNIT_PRICE)
|
||||
@@ -285,29 +341,26 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer
|
||||
create_ata_ix = create_ata_idempotent_ix(
|
||||
payer_pubkey=payer.pubkey(),
|
||||
)
|
||||
|
||||
|
||||
sell_ix = Instruction(PUMP_AMM_PROGRAM_ID, data, accounts)
|
||||
|
||||
|
||||
blockhash_resp = await client.get_latest_blockhash()
|
||||
recent_blockhash = blockhash_resp.value.blockhash
|
||||
|
||||
msg = Message.new_with_blockhash(
|
||||
[compute_limit_ix, compute_price_ix, create_ata_ix, sell_ix],
|
||||
payer.pubkey(),
|
||||
recent_blockhash
|
||||
recent_blockhash,
|
||||
)
|
||||
|
||||
tx_sell = VersionedTransaction(
|
||||
message=msg,
|
||||
keypairs=[payer]
|
||||
)
|
||||
|
||||
tx_sell = VersionedTransaction(message=msg, keypairs=[payer])
|
||||
|
||||
try:
|
||||
tx_sig = await client.send_transaction(
|
||||
tx_sell,
|
||||
opts=TxOpts(skip_preflight=True, preflight_commitment=Confirmed),
|
||||
)
|
||||
|
||||
|
||||
tx_hash = tx_sig.value
|
||||
print(f"Transaction sent: https://explorer.solana.com/tx/{tx_hash}")
|
||||
await client.confirm_transaction(tx_hash, commitment="confirmed")
|
||||
@@ -322,10 +375,16 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer
|
||||
async def main():
|
||||
"""Main function to execute the token selling process."""
|
||||
async with AsyncClient(RPC_ENDPOINT) as client:
|
||||
market_address = await get_market_address_by_base_mint(client, TOKEN_MINT, PUMP_AMM_PROGRAM_ID)
|
||||
market_address = await get_market_address_by_base_mint(
|
||||
client, TOKEN_MINT, PUMP_AMM_PROGRAM_ID
|
||||
)
|
||||
market_data = await get_market_data(client, market_address)
|
||||
coin_creator_vault_authority = find_coin_creator_vault(Pubkey.from_string(market_data["coin_creator"]))
|
||||
coin_creator_vault_ata = get_associated_token_address(coin_creator_vault_authority, SOL)
|
||||
coin_creator_vault_authority = find_coin_creator_vault(
|
||||
Pubkey.from_string(market_data["coin_creator"])
|
||||
)
|
||||
coin_creator_vault_ata = get_associated_token_address(
|
||||
coin_creator_vault_authority, SOL
|
||||
)
|
||||
|
||||
await sell_pump_swap(
|
||||
client,
|
||||
@@ -338,8 +397,9 @@ async def main():
|
||||
Pubkey.from_string(market_data["pool_quote_token_account"]),
|
||||
coin_creator_vault_authority,
|
||||
coin_creator_vault_ata,
|
||||
SLIPPAGE
|
||||
SLIPPAGE,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
asyncio.run(main())
|
||||
|
||||
Reference in New Issue
Block a user