sol-parser-sdk-python 0.4.4__tar.gz → 0.4.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/PKG-INFO +1 -1
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/README.md +1 -1
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/README_CN.md +1 -1
- sol_parser_sdk_python-0.4.5/examples/meteora_damm_grpc.py +145 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/examples/multi_protocol_grpc.py +16 -35
- sol_parser_sdk_python-0.4.5/examples/pumpfun_quick_test.py +70 -0
- sol_parser_sdk_python-0.4.5/examples/pumpfun_trade_filter.py +160 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/examples/pumpfun_with_metrics.py +26 -74
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/examples/pumpswap_low_latency.py +17 -41
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/examples/pumpswap_with_metrics.py +28 -74
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/pyproject.toml +1 -1
- sol_parser_sdk_python-0.4.5/sol_parser/account_fillers/pumpfun.py +162 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/account_fillers/pumpswap.py +40 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/accounts/__init__.py +386 -4
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/dex_parsers.py +54 -1
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/event_types.py +114 -1
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/grpc_types.py +20 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/instructions.py +260 -21
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/log_instr_dedup.py +39 -2
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/merger.py +74 -2
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/shredstream_pumpfun.py +6 -0
- sol_parser_sdk_python-0.4.5/tests/test_filter_and_account_parity.py +193 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/tests/test_log_instr_dedup.py +43 -0
- sol_parser_sdk_python-0.4.5/tests/test_pumpfun_account_fill.py +72 -0
- sol_parser_sdk_python-0.4.5/tests/test_pumpfun_trade_tail.py +125 -0
- sol_parser_sdk_python-0.4.5/tests/test_pumpfun_v2_parity.py +245 -0
- sol_parser_sdk_python-0.4.5/tests/test_pumpswap_instruction.py +108 -0
- sol_parser_sdk_python-0.4.4/examples/meteora_damm_grpc.py +0 -237
- sol_parser_sdk_python-0.4.4/examples/pumpfun_quick_test.py +0 -112
- sol_parser_sdk_python-0.4.4/examples/pumpfun_trade_filter.py +0 -263
- sol_parser_sdk_python-0.4.4/sol_parser/account_fillers/pumpfun.py +0 -97
- sol_parser_sdk_python-0.4.4/tests/test_filter_and_account_parity.py +0 -89
- sol_parser_sdk_python-0.4.4/tests/test_pumpfun_v2_parity.py +0 -83
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/.env.example +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/.gitignore +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/examples/parse_tx_by_signature.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/examples/rust_example_utils.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/scripts/bench_parser.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/scripts/gen_golden_rust.sh +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/__init__.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/account_dispatcher.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/account_fillers/__init__.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/account_fillers/bonk.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/account_fillers/meteora.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/account_fillers/orca.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/account_fillers/raydium.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/accounts/rpc_wallet.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/accounts/utils.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/check_migration.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/clock.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/common/__init__.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/entries_decode.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/env_config.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/geyser_pb2.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/geyser_pb2_grpc.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/grpc/__init__.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/grpc/geyser_connect.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/grpc/subscribe_builder.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/grpc/transaction_meta.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/grpc_client.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/grpc_instruction_parser.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/inner_instruction_parser.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/instr/__init__.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/instr_account_utils.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/json_util.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/logs/__init__.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/order_buffer.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/parser.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/pumpfun_fee_enrich.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/rpc_parser.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/rust_api_inventory.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/rust_event_json.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/shredstream_client.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/shredstream_pb2.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/shredstream_pb2_grpc.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/solana_storage_pb2.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/solana_storage_pb2_grpc.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/u128_parity.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/verify_discriminators.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/tests/fixtures/golden_parse_log.json +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/tests/test_golden.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/tests/test_imports_and_inner.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/tests/test_order_buffer.py +0 -0
- {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/tests/test_raydium_clmm_instruction_parity.py +0 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Meteora DAMM gRPC — latest ``subscribe_dex_events`` queue API.
|
|
3
|
+
|
|
4
|
+
Run: ``python examples/meteora_damm_grpc.py``
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
14
|
+
|
|
15
|
+
from sol_parser import now_micros
|
|
16
|
+
from sol_parser.env_config import load_dotenv_silent, parse_grpc_credentials
|
|
17
|
+
from sol_parser.event_types import DexEvent
|
|
18
|
+
from sol_parser.grpc_client import YellowstoneGrpc
|
|
19
|
+
from sol_parser.grpc_types import (
|
|
20
|
+
ClientConfig,
|
|
21
|
+
EventType,
|
|
22
|
+
OrderMode,
|
|
23
|
+
Protocol,
|
|
24
|
+
event_type_filter_include_only,
|
|
25
|
+
transaction_filter_for_protocols,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
event_count = 0
|
|
29
|
+
swap_count = 0
|
|
30
|
+
add_liquidity_count = 0
|
|
31
|
+
remove_liquidity_count = 0
|
|
32
|
+
create_position_count = 0
|
|
33
|
+
close_position_count = 0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _metadata(ev: DexEvent):
|
|
37
|
+
return getattr(ev.data, "metadata", None) if ev.data is not None else None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _latency_us(ev: DexEvent) -> int:
|
|
41
|
+
meta = _metadata(ev)
|
|
42
|
+
grpc_recv_us = int(getattr(meta, "grpc_recv_us", 0) or 0) if meta else 0
|
|
43
|
+
return max(0, now_micros() - grpc_recv_us) if grpc_recv_us else 0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def main() -> None:
|
|
47
|
+
global event_count, swap_count, add_liquidity_count, remove_liquidity_count
|
|
48
|
+
global create_position_count, close_position_count
|
|
49
|
+
|
|
50
|
+
load_dotenv_silent()
|
|
51
|
+
endpoint, token = parse_grpc_credentials(
|
|
52
|
+
sys.argv[1:],
|
|
53
|
+
default_endpoint="solana-yellowstone-grpc.publicnode.com:443",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
print("🚀 Meteora DAMM gRPC Streaming Example")
|
|
57
|
+
print("========================================\n")
|
|
58
|
+
|
|
59
|
+
config = ClientConfig.default()
|
|
60
|
+
config.enable_metrics = True
|
|
61
|
+
config.connection_timeout_ms = 10000
|
|
62
|
+
config.request_timeout_ms = 30000
|
|
63
|
+
config.enable_tls = True
|
|
64
|
+
config.order_mode = OrderMode.UNORDERED
|
|
65
|
+
|
|
66
|
+
client = YellowstoneGrpc.new_with_config(endpoint, token or None, config)
|
|
67
|
+
protocols = [Protocol.METEORA_DAMM_V2]
|
|
68
|
+
print(f"📊 Protocols: {[p.value for p in protocols]}")
|
|
69
|
+
|
|
70
|
+
tx_filter = transaction_filter_for_protocols(protocols)
|
|
71
|
+
tx_filter.vote = False
|
|
72
|
+
tx_filter.failed = False
|
|
73
|
+
event_filter = event_type_filter_include_only(
|
|
74
|
+
[
|
|
75
|
+
EventType.METEORA_DAMM_V2_SWAP,
|
|
76
|
+
EventType.METEORA_DAMM_V2_ADD_LIQUIDITY,
|
|
77
|
+
EventType.METEORA_DAMM_V2_REMOVE_LIQUIDITY,
|
|
78
|
+
EventType.METEORA_DAMM_V2_CREATE_POSITION,
|
|
79
|
+
EventType.METEORA_DAMM_V2_CLOSE_POSITION,
|
|
80
|
+
]
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
queue: asyncio.Queue[DexEvent] = await client.subscribe_dex_events(
|
|
84
|
+
[tx_filter],
|
|
85
|
+
[],
|
|
86
|
+
event_filter,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
print("🎯 Event Filter: Swap, AddLiquidity, RemoveLiquidity, CreatePosition, ClosePosition")
|
|
90
|
+
print("🛑 Press Ctrl+C to stop...\n")
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
while True:
|
|
94
|
+
ev = await queue.get()
|
|
95
|
+
if not isinstance(ev, DexEvent) or ev.data is None:
|
|
96
|
+
continue
|
|
97
|
+
event_count += 1
|
|
98
|
+
d = ev.data
|
|
99
|
+
meta = _metadata(ev)
|
|
100
|
+
sig = getattr(meta, "signature", "") if meta else ""
|
|
101
|
+
slot = int(getattr(meta, "slot", 0) or 0) if meta else 0
|
|
102
|
+
latency_us = _latency_us(ev)
|
|
103
|
+
|
|
104
|
+
if ev.type == EventType.METEORA_DAMM_V2_SWAP:
|
|
105
|
+
swap_count += 1
|
|
106
|
+
direction = "A->B" if getattr(d, "trade_direction", 0) == 0 else "B->A"
|
|
107
|
+
print("┌─────────────────────────────────────────────────────────────")
|
|
108
|
+
print(f"│ 🔄 Meteora DAMM SWAP (V2) #{event_count}")
|
|
109
|
+
print("├─────────────────────────────────────────────────────────────")
|
|
110
|
+
print(f"│ Signature : {sig}")
|
|
111
|
+
print(f"│ Slot : {slot}")
|
|
112
|
+
print("├─────────────────────────────────────────────────────────────")
|
|
113
|
+
print(f"│ Pool : {getattr(d, 'pool', '')}")
|
|
114
|
+
print(f"│ Direction : {direction}")
|
|
115
|
+
print(f"│ Amount In : {getattr(d, 'amount_in', 0)}")
|
|
116
|
+
print(f"│ Actual Out : {getattr(d, 'output_amount', 0)}")
|
|
117
|
+
print(f"│ Protocol : {getattr(d, 'protocol_fee', 0)}")
|
|
118
|
+
print("├─────────────────────────────────────────────────────────────")
|
|
119
|
+
print(f"│ 📊 Latency : {latency_us} μs")
|
|
120
|
+
print(
|
|
121
|
+
f"│ 📊 Stats : Swap={swap_count} AddLiq={add_liquidity_count} RemLiq={remove_liquidity_count}"
|
|
122
|
+
)
|
|
123
|
+
print("└─────────────────────────────────────────────────────────────\n")
|
|
124
|
+
elif ev.type == EventType.METEORA_DAMM_V2_ADD_LIQUIDITY:
|
|
125
|
+
add_liquidity_count += 1
|
|
126
|
+
print(f"➕ ADD_LIQUIDITY #{add_liquidity_count} sig={sig} slot={slot} latency={latency_us}μs")
|
|
127
|
+
elif ev.type == EventType.METEORA_DAMM_V2_REMOVE_LIQUIDITY:
|
|
128
|
+
remove_liquidity_count += 1
|
|
129
|
+
print(f"➖ REMOVE_LIQUIDITY #{remove_liquidity_count} sig={sig} slot={slot} latency={latency_us}μs")
|
|
130
|
+
elif ev.type == EventType.METEORA_DAMM_V2_CREATE_POSITION:
|
|
131
|
+
create_position_count += 1
|
|
132
|
+
print(f"📌 CREATE_POSITION #{create_position_count} sig={sig} slot={slot} latency={latency_us}μs")
|
|
133
|
+
elif ev.type == EventType.METEORA_DAMM_V2_CLOSE_POSITION:
|
|
134
|
+
close_position_count += 1
|
|
135
|
+
print(f"❌ CLOSE_POSITION #{close_position_count} sig={sig} slot={slot} latency={latency_us}μs")
|
|
136
|
+
finally:
|
|
137
|
+
await client.disconnect()
|
|
138
|
+
print("\n👋 Shutting down gracefully...")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
try:
|
|
143
|
+
asyncio.run(main())
|
|
144
|
+
except KeyboardInterrupt:
|
|
145
|
+
pass
|
|
@@ -10,18 +10,17 @@ import asyncio
|
|
|
10
10
|
import os
|
|
11
11
|
import sys
|
|
12
12
|
|
|
13
|
-
import base58
|
|
14
|
-
|
|
15
13
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
16
14
|
|
|
17
|
-
from sol_parser import format_dex_event_json
|
|
15
|
+
from sol_parser import format_dex_event_json
|
|
18
16
|
from sol_parser.env_config import load_dotenv_silent, parse_grpc_credentials
|
|
19
17
|
from sol_parser.event_types import DexEvent
|
|
20
18
|
from sol_parser.grpc_client import YellowstoneGrpc
|
|
21
19
|
from sol_parser.grpc_types import (
|
|
22
20
|
ClientConfig,
|
|
21
|
+
OrderMode,
|
|
23
22
|
Protocol,
|
|
24
|
-
|
|
23
|
+
account_filter_for_protocols,
|
|
25
24
|
transaction_filter_for_protocols,
|
|
26
25
|
)
|
|
27
26
|
|
|
@@ -64,50 +63,32 @@ async def main() -> None:
|
|
|
64
63
|
|
|
65
64
|
cfg = ClientConfig.default()
|
|
66
65
|
cfg.enable_metrics = True
|
|
66
|
+
cfg.order_mode = OrderMode.UNORDERED
|
|
67
67
|
client = YellowstoneGrpc.new_with_config(endpoint, token or None, cfg)
|
|
68
68
|
|
|
69
|
-
await client.connect()
|
|
70
69
|
asyncio.create_task(stats_reporter())
|
|
71
70
|
|
|
72
|
-
def on_update(update):
|
|
73
|
-
if update.transaction is None or update.transaction.transaction is None:
|
|
74
|
-
return
|
|
75
|
-
tx_info = update.transaction.transaction
|
|
76
|
-
slot = update.transaction.slot
|
|
77
|
-
logs = tx_info.log_messages
|
|
78
|
-
if not logs:
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
sb = bytes(tx_info.signature) if tx_info.signature else b""
|
|
82
|
-
sig_b58 = base58.b58encode(sb).decode("ascii") if len(sb) == 64 else ""
|
|
83
|
-
|
|
84
|
-
for ev in parse_logs_only(
|
|
85
|
-
logs, sig_b58, slot, None, subscribe_tx_info=tx_info
|
|
86
|
-
):
|
|
87
|
-
if not isinstance(ev, DexEvent):
|
|
88
|
-
continue
|
|
89
|
-
key = str(ev.type.value)
|
|
90
|
-
stats[key] = stats.get(key, 0) + 1
|
|
91
|
-
print(format_dex_event_json(ev))
|
|
92
|
-
|
|
93
71
|
tx_filter = transaction_filter_for_protocols(PROTOCOLS)
|
|
94
72
|
tx_filter.vote = False
|
|
95
73
|
tx_filter.failed = False
|
|
74
|
+
account_filter = account_filter_for_protocols(PROTOCOLS)
|
|
96
75
|
|
|
97
|
-
await client.
|
|
98
|
-
tx_filter,
|
|
99
|
-
|
|
100
|
-
on_update=on_update,
|
|
101
|
-
on_error=lambda e: print(f"Stream error: {e}", file=sys.stderr),
|
|
102
|
-
on_end=lambda: print("Stream ended"),
|
|
103
|
-
),
|
|
76
|
+
queue: asyncio.Queue[DexEvent] = await client.subscribe_dex_events(
|
|
77
|
+
[tx_filter],
|
|
78
|
+
[account_filter],
|
|
104
79
|
)
|
|
105
80
|
|
|
106
81
|
print(f"✅ Subscribed")
|
|
107
82
|
print("🛑 Press Ctrl+C to stop...\n")
|
|
108
83
|
|
|
109
84
|
try:
|
|
110
|
-
|
|
85
|
+
while True:
|
|
86
|
+
ev = await queue.get()
|
|
87
|
+
if not isinstance(ev, DexEvent):
|
|
88
|
+
continue
|
|
89
|
+
key = str(ev.type.value)
|
|
90
|
+
stats[key] = stats.get(key, 0) + 1
|
|
91
|
+
print(format_dex_event_json(ev))
|
|
111
92
|
except KeyboardInterrupt:
|
|
112
93
|
pass
|
|
113
94
|
finally:
|
|
@@ -118,4 +99,4 @@ async def main() -> None:
|
|
|
118
99
|
|
|
119
100
|
|
|
120
101
|
if __name__ == "__main__":
|
|
121
|
-
asyncio.run(main())
|
|
102
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Quick Test — 对齐最新 ``subscribe_dex_events`` 队列 API。
|
|
3
|
+
|
|
4
|
+
Run: ``python examples/pumpfun_quick_test.py``
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
14
|
+
|
|
15
|
+
from sol_parser.env_config import load_dotenv_silent, parse_grpc_credentials
|
|
16
|
+
from sol_parser.event_types import DexEvent
|
|
17
|
+
from sol_parser.grpc_client import YellowstoneGrpc
|
|
18
|
+
from sol_parser.grpc_types import ClientConfig, Protocol, transaction_filter_for_protocols
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def main() -> None:
|
|
22
|
+
load_dotenv_silent()
|
|
23
|
+
endpoint, token = parse_grpc_credentials(
|
|
24
|
+
sys.argv[1:],
|
|
25
|
+
default_endpoint="solana-yellowstone-grpc.publicnode.com:443",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
print("🚀 Quick Test - Subscribing to ALL PumpFun events...")
|
|
29
|
+
|
|
30
|
+
config = ClientConfig.default()
|
|
31
|
+
config.enable_metrics = True
|
|
32
|
+
client = YellowstoneGrpc.new_with_config(endpoint, token or None, config)
|
|
33
|
+
|
|
34
|
+
protocols = [Protocol.PUMP_FUN]
|
|
35
|
+
tx_filter = transaction_filter_for_protocols(protocols)
|
|
36
|
+
tx_filter.vote = False
|
|
37
|
+
tx_filter.failed = False
|
|
38
|
+
|
|
39
|
+
queue: asyncio.Queue[DexEvent] = await client.subscribe_dex_events([tx_filter], [])
|
|
40
|
+
|
|
41
|
+
print("✅ Subscribing... (no event filter - will show ALL PumpFun events)")
|
|
42
|
+
print("🎧 Listening for events... (waiting up to 60 seconds)\n")
|
|
43
|
+
|
|
44
|
+
event_count = 0
|
|
45
|
+
try:
|
|
46
|
+
while event_count < 10:
|
|
47
|
+
ev = await asyncio.wait_for(queue.get(), timeout=60.0)
|
|
48
|
+
if not isinstance(ev, DexEvent) or not str(ev.type.value).startswith("PumpFun"):
|
|
49
|
+
continue
|
|
50
|
+
event_count += 1
|
|
51
|
+
meta = getattr(ev.data, "metadata", None)
|
|
52
|
+
slot = int(getattr(meta, "slot", 0) or 0) if meta else 0
|
|
53
|
+
print(f"✅ Event #{event_count}: {ev.type.value} (slot={slot})")
|
|
54
|
+
|
|
55
|
+
print(f"\n🎉 Received {event_count} events! Test successful!")
|
|
56
|
+
except asyncio.TimeoutError:
|
|
57
|
+
if event_count == 0:
|
|
58
|
+
print("⏰ Timeout: No events received in 60 seconds.")
|
|
59
|
+
print(" This might indicate:")
|
|
60
|
+
print(" - Network connectivity issues")
|
|
61
|
+
print(" - gRPC endpoint is down")
|
|
62
|
+
print(" - Very low market activity (rare)")
|
|
63
|
+
else:
|
|
64
|
+
print(f"\n✅ Received {event_count} events in 60 seconds")
|
|
65
|
+
finally:
|
|
66
|
+
await client.disconnect()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""PumpFun Trade Event Filter Example — latest ``subscribe_dex_events`` API.
|
|
3
|
+
|
|
4
|
+
Run: ``python examples/pumpfun_trade_filter.py``
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
14
|
+
|
|
15
|
+
from sol_parser import now_micros
|
|
16
|
+
from sol_parser.env_config import load_dotenv_silent, parse_grpc_credentials
|
|
17
|
+
from sol_parser.event_types import DexEvent
|
|
18
|
+
from sol_parser.grpc_client import YellowstoneGrpc
|
|
19
|
+
from sol_parser.grpc_types import (
|
|
20
|
+
ClientConfig,
|
|
21
|
+
EventType,
|
|
22
|
+
OrderMode,
|
|
23
|
+
Protocol,
|
|
24
|
+
event_type_filter_include_only,
|
|
25
|
+
transaction_filter_for_protocols,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
event_count = 0
|
|
29
|
+
buy_count = 0
|
|
30
|
+
sell_count = 0
|
|
31
|
+
buy_exact_count = 0
|
|
32
|
+
create_count = 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _metadata(ev: DexEvent):
|
|
36
|
+
return getattr(ev.data, "metadata", None) if ev.data is not None else None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _latency_us(ev: DexEvent) -> int:
|
|
40
|
+
meta = _metadata(ev)
|
|
41
|
+
grpc_recv_us = int(getattr(meta, "grpc_recv_us", 0) or 0) if meta else 0
|
|
42
|
+
return max(0, now_micros() - grpc_recv_us) if grpc_recv_us else 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _print_trade(title: str, ev: DexEvent, latency_us: int) -> None:
|
|
46
|
+
meta = _metadata(ev)
|
|
47
|
+
d = ev.data
|
|
48
|
+
print("┌─────────────────────────────────────────────────────────────")
|
|
49
|
+
print(f"│ {title} #{event_count}")
|
|
50
|
+
print("├─────────────────────────────────────────────────────────────")
|
|
51
|
+
print(f"│ Signature : {getattr(meta, 'signature', '') if meta else ''}")
|
|
52
|
+
print(f"│ Slot : {getattr(meta, 'slot', 0) if meta else 0}")
|
|
53
|
+
print("├─────────────────────────────────────────────────────────────")
|
|
54
|
+
print(f"│ Mint : {getattr(d, 'mint', '')}")
|
|
55
|
+
print(f"│ SOL Amount : {getattr(d, 'sol_amount', 0)} lamports")
|
|
56
|
+
print(f"│ Token Amt : {getattr(d, 'token_amount', 0)}")
|
|
57
|
+
print(f"│ User : {getattr(d, 'user', '')}")
|
|
58
|
+
print("├─────────────────────────────────────────────────────────────")
|
|
59
|
+
print(f"│ 📊 Latency : {latency_us} μs")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def main() -> None:
|
|
63
|
+
global event_count, buy_count, sell_count, buy_exact_count, create_count
|
|
64
|
+
|
|
65
|
+
load_dotenv_silent()
|
|
66
|
+
endpoint, token = parse_grpc_credentials(
|
|
67
|
+
sys.argv[1:],
|
|
68
|
+
default_endpoint="solana-yellowstone-grpc.publicnode.com:443",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
print("🚀 PumpFun Trade Event Filter Example")
|
|
72
|
+
print("======================================\n")
|
|
73
|
+
|
|
74
|
+
config = ClientConfig.default()
|
|
75
|
+
config.enable_metrics = True
|
|
76
|
+
config.connection_timeout_ms = 10000
|
|
77
|
+
config.request_timeout_ms = 30000
|
|
78
|
+
config.enable_tls = True
|
|
79
|
+
config.order_mode = OrderMode.UNORDERED
|
|
80
|
+
|
|
81
|
+
client = YellowstoneGrpc.new_with_config(endpoint, token or None, config)
|
|
82
|
+
protocols = [Protocol.PUMP_FUN]
|
|
83
|
+
print(f"📊 Protocols: {[p.value for p in protocols]}")
|
|
84
|
+
|
|
85
|
+
tx_filter = transaction_filter_for_protocols(protocols)
|
|
86
|
+
tx_filter.vote = False
|
|
87
|
+
tx_filter.failed = False
|
|
88
|
+
event_filter = event_type_filter_include_only(
|
|
89
|
+
[
|
|
90
|
+
EventType.PUMP_FUN_BUY,
|
|
91
|
+
EventType.PUMP_FUN_SELL,
|
|
92
|
+
EventType.PUMP_FUN_BUY_EXACT_SOL_IN,
|
|
93
|
+
EventType.PUMP_FUN_CREATE,
|
|
94
|
+
EventType.PUMP_FUN_CREATE_V2,
|
|
95
|
+
]
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
queue: asyncio.Queue[DexEvent] = await client.subscribe_dex_events(
|
|
99
|
+
[tx_filter],
|
|
100
|
+
[],
|
|
101
|
+
event_filter,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
print("🎯 Event Filter: Buy, Sell, BuyExactSolIn, Create")
|
|
105
|
+
print("🛑 Press Ctrl+C to stop...\n")
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
while True:
|
|
109
|
+
ev = await queue.get()
|
|
110
|
+
if not isinstance(ev, DexEvent) or ev.data is None:
|
|
111
|
+
continue
|
|
112
|
+
latency_us = _latency_us(ev)
|
|
113
|
+
event_count += 1
|
|
114
|
+
|
|
115
|
+
if ev.type == EventType.PUMP_FUN_BUY:
|
|
116
|
+
buy_count += 1
|
|
117
|
+
_print_trade("🟢 PumpFun BUY", ev, latency_us)
|
|
118
|
+
print(f"│ 📊 Stats : Buy={buy_count} Sell={sell_count} BuyExact={buy_exact_count}")
|
|
119
|
+
print("└─────────────────────────────────────────────────────────────\n")
|
|
120
|
+
elif ev.type == EventType.PUMP_FUN_SELL:
|
|
121
|
+
sell_count += 1
|
|
122
|
+
_print_trade("🔴 PumpFun SELL", ev, latency_us)
|
|
123
|
+
print(f"│ 📊 Stats : Buy={buy_count} Sell={sell_count} BuyExact={buy_exact_count}")
|
|
124
|
+
print("└─────────────────────────────────────────────────────────────\n")
|
|
125
|
+
elif ev.type == EventType.PUMP_FUN_BUY_EXACT_SOL_IN:
|
|
126
|
+
buy_exact_count += 1
|
|
127
|
+
_print_trade("🟡 PumpFun BUY_EXACT_SOL_IN", ev, latency_us)
|
|
128
|
+
print(f"│ 📊 Stats : Buy={buy_count} Sell={sell_count} BuyExact={buy_exact_count}")
|
|
129
|
+
print("└─────────────────────────────────────────────────────────────\n")
|
|
130
|
+
elif ev.type in (EventType.PUMP_FUN_CREATE, EventType.PUMP_FUN_CREATE_V2):
|
|
131
|
+
create_count += 1
|
|
132
|
+
meta = _metadata(ev)
|
|
133
|
+
d = ev.data
|
|
134
|
+
print("┌─────────────────────────────────────────────────────────────")
|
|
135
|
+
print(f"│ 🆕 PumpFun CREATE #{event_count}")
|
|
136
|
+
print("├─────────────────────────────────────────────────────────────")
|
|
137
|
+
print(f"│ Signature : {getattr(meta, 'signature', '') if meta else ''}")
|
|
138
|
+
print(f"│ Slot : {getattr(meta, 'slot', 0) if meta else 0}")
|
|
139
|
+
print("├─────────────────────────────────────────────────────────────")
|
|
140
|
+
print(f"│ Name : {getattr(d, 'name', '')}")
|
|
141
|
+
print(f"│ Symbol : {getattr(d, 'symbol', '')}")
|
|
142
|
+
print(f"│ Mint : {getattr(d, 'mint', '')}")
|
|
143
|
+
print(f"│ Creator : {getattr(d, 'creator', '')}")
|
|
144
|
+
print("├─────────────────────────────────────────────────────────────")
|
|
145
|
+
print(f"│ 📊 Latency : {latency_us} μs")
|
|
146
|
+
print(f"│ 📊 Creates : {create_count}")
|
|
147
|
+
print("└─────────────────────────────────────────────────────────────\n")
|
|
148
|
+
finally:
|
|
149
|
+
await client.disconnect()
|
|
150
|
+
print(
|
|
151
|
+
f"\n👋 Total events: {event_count} "
|
|
152
|
+
f"(Buy={buy_count} Sell={sell_count} BuyExact={buy_exact_count} Create={create_count})"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__":
|
|
157
|
+
try:
|
|
158
|
+
asyncio.run(main())
|
|
159
|
+
except KeyboardInterrupt:
|
|
160
|
+
pass
|
{sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/examples/pumpfun_with_metrics.py
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""PumpFun +
|
|
2
|
+
"""PumpFun + performance metrics — latest ``subscribe_dex_events`` API.
|
|
3
3
|
|
|
4
4
|
Run: ``python examples/pumpfun_with_metrics.py``
|
|
5
5
|
"""
|
|
@@ -10,19 +10,17 @@ import asyncio
|
|
|
10
10
|
import os
|
|
11
11
|
import sys
|
|
12
12
|
|
|
13
|
-
import base58
|
|
14
|
-
|
|
15
13
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
16
14
|
|
|
17
|
-
from sol_parser import format_dex_event_json, now_micros
|
|
15
|
+
from sol_parser import format_dex_event_json, now_micros
|
|
18
16
|
from sol_parser.env_config import load_dotenv_silent, parse_grpc_credentials
|
|
19
17
|
from sol_parser.event_types import DexEvent
|
|
20
18
|
from sol_parser.grpc_client import YellowstoneGrpc
|
|
21
19
|
from sol_parser.grpc_types import (
|
|
22
20
|
ClientConfig,
|
|
23
21
|
EventType,
|
|
22
|
+
OrderMode,
|
|
24
23
|
Protocol,
|
|
25
|
-
SubscribeCallbacks,
|
|
26
24
|
event_type_filter_include_only,
|
|
27
25
|
transaction_filter_for_protocols,
|
|
28
26
|
)
|
|
@@ -34,42 +32,31 @@ max_latency = 0
|
|
|
34
32
|
last_count = 0
|
|
35
33
|
|
|
36
34
|
|
|
37
|
-
async def stats_reporter():
|
|
35
|
+
async def stats_reporter(queue: asyncio.Queue[DexEvent]):
|
|
38
36
|
global last_count
|
|
39
37
|
while True:
|
|
40
38
|
await asyncio.sleep(10)
|
|
41
39
|
if event_count == 0:
|
|
42
40
|
continue
|
|
43
41
|
count = event_count
|
|
44
|
-
|
|
45
|
-
min_l = min_latency if min_latency < 2**62 else 0
|
|
46
|
-
max_l = max_latency
|
|
47
|
-
queue_len = 0
|
|
48
|
-
avg = total // count if count else 0
|
|
42
|
+
avg = total_latency // count if count else 0
|
|
49
43
|
events_per_sec = (count - last_count) / 10.0
|
|
50
|
-
|
|
44
|
+
min_l = min_latency if min_latency < 2**62 else 0
|
|
51
45
|
print("\n╔════════════════════════════════════════════════════╗")
|
|
52
46
|
print("║ 性能统计 (10秒间隔) ║")
|
|
53
47
|
print("╠════════════════════════════════════════════════════╣")
|
|
54
48
|
print(f"║ 事件总数: {count:>10} ║")
|
|
55
49
|
print(f"║ 事件速率: {events_per_sec:>10.1f} events/sec ║")
|
|
56
|
-
print(f"║ 队列长度: {
|
|
50
|
+
print(f"║ 队列长度: {queue.qsize():>10} ║")
|
|
57
51
|
print(f"║ 平均延迟: {avg:>10} μs ║")
|
|
58
52
|
print(f"║ 最小延迟: {min_l:>10} μs ║")
|
|
59
|
-
print(f"║ 最大延迟: {
|
|
53
|
+
print(f"║ 最大延迟: {max_latency:>10} μs ║")
|
|
60
54
|
print("╚════════════════════════════════════════════════════╝\n")
|
|
61
|
-
|
|
62
|
-
if queue_len > 1000:
|
|
63
|
-
print(f"⚠️ 警告: 队列堆积 ({queue_len}), 消费速度 < 生产速度")
|
|
64
|
-
|
|
65
55
|
last_count = count
|
|
66
56
|
|
|
67
57
|
|
|
68
58
|
def _grpc_recv_us(ev: DexEvent) -> int | None:
|
|
69
|
-
|
|
70
|
-
if d is None:
|
|
71
|
-
return None
|
|
72
|
-
meta = getattr(d, "metadata", None)
|
|
59
|
+
meta = getattr(ev.data, "metadata", None) if ev.data is not None else None
|
|
73
60
|
if meta is None:
|
|
74
61
|
return None
|
|
75
62
|
v = int(getattr(meta, "grpc_recv_us", 0) or 0)
|
|
@@ -93,9 +80,9 @@ async def main() -> None:
|
|
|
93
80
|
config.connection_timeout_ms = 10000
|
|
94
81
|
config.request_timeout_ms = 30000
|
|
95
82
|
config.enable_tls = True
|
|
83
|
+
config.order_mode = OrderMode.UNORDERED
|
|
96
84
|
|
|
97
85
|
client = YellowstoneGrpc.new_with_config(endpoint, token or None, config)
|
|
98
|
-
print("✅ gRPC client created successfully")
|
|
99
86
|
|
|
100
87
|
protocols = [Protocol.PUMP_FUN]
|
|
101
88
|
print(f"📊 Protocols to monitor: {[p.value for p in protocols]}")
|
|
@@ -103,10 +90,6 @@ async def main() -> None:
|
|
|
103
90
|
tx_filter = transaction_filter_for_protocols(protocols)
|
|
104
91
|
tx_filter.vote = False
|
|
105
92
|
tx_filter.failed = False
|
|
106
|
-
|
|
107
|
-
print("🎧 Starting subscription...")
|
|
108
|
-
print("🔍 Monitoring programs for DEX events...")
|
|
109
|
-
|
|
110
93
|
event_filter = event_type_filter_include_only(
|
|
111
94
|
[
|
|
112
95
|
EventType.PUMP_FUN_BUY,
|
|
@@ -117,71 +100,40 @@ async def main() -> None:
|
|
|
117
100
|
]
|
|
118
101
|
)
|
|
119
102
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
global event_count, total_latency, min_latency, max_latency
|
|
127
|
-
|
|
128
|
-
if update.transaction is None or update.transaction.transaction is None:
|
|
129
|
-
return
|
|
130
|
-
tx_info = update.transaction.transaction
|
|
131
|
-
slot = update.transaction.slot
|
|
132
|
-
logs = tx_info.log_messages
|
|
133
|
-
if not logs:
|
|
134
|
-
return
|
|
103
|
+
queue: asyncio.Queue[DexEvent] = await client.subscribe_dex_events(
|
|
104
|
+
[tx_filter],
|
|
105
|
+
[],
|
|
106
|
+
event_filter,
|
|
107
|
+
)
|
|
108
|
+
asyncio.create_task(stats_reporter(queue))
|
|
135
109
|
|
|
136
|
-
|
|
137
|
-
|
|
110
|
+
print("📋 Event Filter: Buy, Sell, BuyExactSolIn, Create")
|
|
111
|
+
print("🛑 Press Ctrl+C to stop...")
|
|
138
112
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
113
|
+
try:
|
|
114
|
+
while True:
|
|
115
|
+
ev = await queue.get()
|
|
142
116
|
if not isinstance(ev, DexEvent) or ev.data is None:
|
|
143
117
|
continue
|
|
144
|
-
|
|
118
|
+
grpc_recv_us = _grpc_recv_us(ev)
|
|
119
|
+
if grpc_recv_us is None:
|
|
145
120
|
continue
|
|
146
|
-
|
|
147
|
-
grpc_recv_us_opt = _grpc_recv_us(ev)
|
|
148
|
-
if grpc_recv_us_opt is None:
|
|
149
|
-
continue
|
|
150
|
-
|
|
151
121
|
queue_recv_us = now_micros()
|
|
152
|
-
latency_us = queue_recv_us -
|
|
153
|
-
if latency_us < 0:
|
|
154
|
-
latency_us = 0
|
|
122
|
+
latency_us = max(0, queue_recv_us - grpc_recv_us)
|
|
155
123
|
|
|
156
124
|
event_count += 1
|
|
157
125
|
total_latency += latency_us
|
|
158
126
|
min_latency = min(min_latency, latency_us)
|
|
159
127
|
max_latency = max(max_latency, latency_us)
|
|
160
128
|
|
|
161
|
-
queue_len = 0
|
|
162
|
-
|
|
163
129
|
print("\n================================================")
|
|
164
|
-
print(f"gRPC接收时间: {
|
|
130
|
+
print(f"gRPC接收时间: {grpc_recv_us} μs")
|
|
165
131
|
print(f"事件接收时间: {queue_recv_us} μs")
|
|
166
132
|
print(f"延迟时间: {latency_us} μs")
|
|
167
|
-
print(f"队列长度: {
|
|
133
|
+
print(f"队列长度: {queue.qsize()}")
|
|
168
134
|
print("================================================")
|
|
169
135
|
print(format_dex_event_json(ev))
|
|
170
136
|
print()
|
|
171
|
-
|
|
172
|
-
await client.subscribe_transactions(
|
|
173
|
-
tx_filter,
|
|
174
|
-
SubscribeCallbacks(
|
|
175
|
-
on_update=on_update,
|
|
176
|
-
on_error=lambda e: print(f"Stream error: {e}", file=sys.stderr),
|
|
177
|
-
on_end=lambda: None,
|
|
178
|
-
),
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
print("🛑 Press Ctrl+C to stop...")
|
|
182
|
-
|
|
183
|
-
try:
|
|
184
|
-
await asyncio.Event().wait()
|
|
185
137
|
finally:
|
|
186
138
|
await client.disconnect()
|
|
187
139
|
print("👋 Shutting down gracefully...")
|