1616PUMP_PROGRAM_ID = Pubkey .from_string ("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" )
1717PUMP_CREATE_PREFIX = struct .pack ("<Q" , 8576854823835016728 )
1818
19+
1920async def create_geyser_connection ():
2021 """Establish a secure connection to the Geyser endpoint."""
2122 if AUTH_TYPE == "x-token" :
@@ -24,15 +25,19 @@ async def create_geyser_connection():
2425 )
2526 else : # Basic authentication
2627 auth = grpc .metadata_call_credentials (
27- lambda _ , callback : callback ((("authorization" , f"Basic { GEYSER_API_TOKEN } " ),), None )
28+ lambda _ , callback : callback (
29+ (("authorization" , f"Basic { GEYSER_API_TOKEN } " ),), None
30+ )
2831 )
29-
32+
3033 creds = grpc .composite_channel_credentials (grpc .ssl_channel_credentials (), auth )
34+
35+ # gRPC keepalive options (complementary to Yellowstone pings)
3136 keepalive_options = [
32- (' grpc.keepalive_time_ms' , 30000 ),
33- (' grpc.keepalive_timeout_ms' , 10000 ),
34- (' grpc.keepalive_permit_without_calls' , True ),
35- (' grpc.http2.min_time_between_pings_ms' , 10000 ),
37+ (" grpc.keepalive_time_ms" , 30000 ),
38+ (" grpc.keepalive_timeout_ms" , 10000 ),
39+ (" grpc.keepalive_permit_without_calls" , True ),
40+ (" grpc.http2.min_time_between_pings_ms" , 10000 ),
3641 ]
3742
3843 channel = grpc .aio .secure_channel (GEYSER_ENDPOINT , creds , options = keepalive_options )
@@ -48,48 +53,66 @@ def create_subscription_request():
4853 return request
4954
5055
56+ def create_ping_request ():
57+ """Create a ping request to keep connection alive."""
58+ ping_request = geyser_pb2 .SubscribeRequest ()
59+ ping_request .ping = True # Yellowstone-specific ping
60+ return ping_request
61+
62+
63+ async def request_generator ():
64+ """Generate subscription requests with periodic pings."""
65+ # Send initial subscription
66+ yield create_subscription_request ()
67+
68+ # Send pings every 30 seconds to keep connection alive
69+ while True :
70+ await asyncio .sleep (30 )
71+ yield create_ping_request ()
72+
73+
5174def decode_create_instruction (ix_data : bytes , keys , accounts ) -> dict :
5275 """Decode a create instruction from transaction data."""
5376 offset = 8 # Skip the 8-byte discriminator
54-
77+
5578 def get_account_key (index ):
5679 """Extract account public key by index."""
5780 if index >= len (accounts ):
5881 return "N/A"
5982 account_index = accounts [index ]
6083 return base58 .b58encode (keys [account_index ]).decode ()
61-
84+
6285 def read_string ():
6386 """Read length-prefixed string from instruction data."""
6487 nonlocal offset
6588 length = struct .unpack_from ("<I" , ix_data , offset )[0 ] # Read 4-byte length
6689 offset += 4
67- value = ix_data [offset : offset + length ].decode () # Read string data
90+ value = ix_data [offset : offset + length ].decode () # Read string data
6891 offset += length
6992 return value
70-
93+
7194 def read_pubkey ():
7295 """Read 32-byte public key from instruction data."""
7396 nonlocal offset
74- value = base58 .b58encode (ix_data [offset : offset + 32 ]).decode ()
97+ value = base58 .b58encode (ix_data [offset : offset + 32 ]).decode ()
7598 offset += 32
7699 return value
77-
100+
78101 # Parse instruction data according to pump.fun's create schema
79102 name = read_string ()
80- symbol = read_string ()
103+ symbol = read_string ()
81104 uri = read_string ()
82105 creator = read_pubkey ()
83-
106+
84107 return {
85108 "name" : name ,
86109 "symbol" : symbol ,
87110 "uri" : uri ,
88111 "creator" : creator ,
89- "mint" : get_account_key (0 ), # New token mint address
112+ "mint" : get_account_key (0 ), # New token mint address
90113 "bonding_curve" : get_account_key (2 ), # Price discovery mechanism
91114 "associated_bonding_curve" : get_account_key (3 ), # Token account for curve
92- "user" : get_account_key (7 ), # Transaction signer
115+ "user" : get_account_key (7 ), # Transaction signer
93116 }
94117
95118
@@ -107,30 +130,59 @@ def print_token_info(info, signature):
107130async def monitor_pump ():
108131 """Monitor Solana blockchain for new Pump.fun token creations."""
109132 print (f"Starting Pump.fun token monitor using { AUTH_TYPE .upper ()} authentication" )
110- stub = await create_geyser_connection ()
111- request = create_subscription_request ()
112-
113- async for update in stub .Subscribe (iter ([request ])):
114- # Only process transaction updates
115- if not update .HasField ("transaction" ):
116- continue
117-
118- tx = update .transaction .transaction .transaction
119- msg = getattr (tx , "message" , None )
120- if msg is None :
121- continue
122-
123- # Check each instruction in the transaction
124- for ix in msg .instructions :
125- # Quick check: is this a pump.fun create instruction?
126- if not ix .data .startswith (PUMP_CREATE_PREFIX ):
133+ print ("📡 Connecting to Yellowstone gRPC..." )
134+
135+ try :
136+ stub = await create_geyser_connection ()
137+ print ("✅ Connected successfully!" )
138+ print ("🔍 Monitoring for new Pump.fun token launches..." )
139+ print ("🏓 Ping system active (every 30s)" )
140+ print ("-" * 50 )
141+
142+ async for update in stub .Subscribe (request_generator ()):
143+ # Handle ping/pong responses
144+ if update .HasField ("pong" ):
145+ print ("🏓 Pong received from server" )
146+ continue
147+
148+ # Only process transaction updates
149+ if not update .HasField ("transaction" ):
150+ continue
151+
152+ tx = update .transaction .transaction .transaction
153+ msg = getattr (tx , "message" , None )
154+ if msg is None :
127155 continue
128156
129- # Decode and display token information
130- info = decode_create_instruction (ix .data , msg .account_keys , ix .accounts )
131- signature = base58 .b58encode (bytes (update .transaction .transaction .signature )).decode ()
132- print_token_info (info , signature )
157+ # Check each instruction in the transaction
158+ for ix in msg .instructions :
159+ # Quick check: is this a pump.fun create instruction?
160+ if not ix .data .startswith (PUMP_CREATE_PREFIX ):
161+ continue
162+
163+ # Decode and display token information
164+ try :
165+ info = decode_create_instruction (
166+ ix .data , msg .account_keys , ix .accounts
167+ )
168+ signature = base58 .b58encode (
169+ bytes (update .transaction .transaction .signature )
170+ ).decode ()
171+ print_token_info (info , signature )
172+ except Exception as e :
173+ print (f"⚠️ Error decoding instruction: { e } " )
174+ continue
175+
176+ except grpc .RpcError as e :
177+ print (f"❌ gRPC Error: { e } " )
178+ except Exception as e :
179+ print (f"❌ Unexpected error: { e } " )
133180
134181
135182if __name__ == "__main__" :
136- asyncio .run (monitor_pump ())
183+ try :
184+ asyncio .run (monitor_pump ())
185+ except KeyboardInterrupt :
186+ print ("\n 🛑 Monitor stopped by user" )
187+ except Exception as e :
188+ print (f"❌ Fatal error: { e } " )
0 commit comments