docs(claude): add claude code rules

This commit is contained in:
smypmsa
2025-08-11 05:35:25 +00:00
parent f2efd56f81
commit 8ab8932168
56 changed files with 4193 additions and 2605 deletions
@@ -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")
+6 -2
View File
@@ -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:
"""
+23 -22
View File
@@ -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__":
+12 -19
View File
@@ -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__":
+85 -65
View File
@@ -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)
+175 -120
View File
@@ -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())