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.
Files changed (84) hide show
  1. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/PKG-INFO +1 -1
  2. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/README.md +1 -1
  3. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/README_CN.md +1 -1
  4. sol_parser_sdk_python-0.4.5/examples/meteora_damm_grpc.py +145 -0
  5. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/examples/multi_protocol_grpc.py +16 -35
  6. sol_parser_sdk_python-0.4.5/examples/pumpfun_quick_test.py +70 -0
  7. sol_parser_sdk_python-0.4.5/examples/pumpfun_trade_filter.py +160 -0
  8. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/examples/pumpfun_with_metrics.py +26 -74
  9. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/examples/pumpswap_low_latency.py +17 -41
  10. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/examples/pumpswap_with_metrics.py +28 -74
  11. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/pyproject.toml +1 -1
  12. sol_parser_sdk_python-0.4.5/sol_parser/account_fillers/pumpfun.py +162 -0
  13. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/account_fillers/pumpswap.py +40 -0
  14. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/accounts/__init__.py +386 -4
  15. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/dex_parsers.py +54 -1
  16. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/event_types.py +114 -1
  17. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/grpc_types.py +20 -0
  18. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/instructions.py +260 -21
  19. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/log_instr_dedup.py +39 -2
  20. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/merger.py +74 -2
  21. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/shredstream_pumpfun.py +6 -0
  22. sol_parser_sdk_python-0.4.5/tests/test_filter_and_account_parity.py +193 -0
  23. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/tests/test_log_instr_dedup.py +43 -0
  24. sol_parser_sdk_python-0.4.5/tests/test_pumpfun_account_fill.py +72 -0
  25. sol_parser_sdk_python-0.4.5/tests/test_pumpfun_trade_tail.py +125 -0
  26. sol_parser_sdk_python-0.4.5/tests/test_pumpfun_v2_parity.py +245 -0
  27. sol_parser_sdk_python-0.4.5/tests/test_pumpswap_instruction.py +108 -0
  28. sol_parser_sdk_python-0.4.4/examples/meteora_damm_grpc.py +0 -237
  29. sol_parser_sdk_python-0.4.4/examples/pumpfun_quick_test.py +0 -112
  30. sol_parser_sdk_python-0.4.4/examples/pumpfun_trade_filter.py +0 -263
  31. sol_parser_sdk_python-0.4.4/sol_parser/account_fillers/pumpfun.py +0 -97
  32. sol_parser_sdk_python-0.4.4/tests/test_filter_and_account_parity.py +0 -89
  33. sol_parser_sdk_python-0.4.4/tests/test_pumpfun_v2_parity.py +0 -83
  34. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/.env.example +0 -0
  35. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/.gitignore +0 -0
  36. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/examples/parse_tx_by_signature.py +0 -0
  37. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/examples/rust_example_utils.py +0 -0
  38. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/scripts/bench_parser.py +0 -0
  39. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/scripts/gen_golden_rust.sh +0 -0
  40. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/__init__.py +0 -0
  41. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/account_dispatcher.py +0 -0
  42. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/account_fillers/__init__.py +0 -0
  43. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/account_fillers/bonk.py +0 -0
  44. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/account_fillers/meteora.py +0 -0
  45. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/account_fillers/orca.py +0 -0
  46. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/account_fillers/raydium.py +0 -0
  47. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/accounts/rpc_wallet.py +0 -0
  48. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/accounts/utils.py +0 -0
  49. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/check_migration.py +0 -0
  50. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/clock.py +0 -0
  51. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/common/__init__.py +0 -0
  52. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/entries_decode.py +0 -0
  53. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/env_config.py +0 -0
  54. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/geyser_pb2.py +0 -0
  55. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/geyser_pb2_grpc.py +0 -0
  56. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/grpc/__init__.py +0 -0
  57. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/grpc/geyser_connect.py +0 -0
  58. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/grpc/subscribe_builder.py +0 -0
  59. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/grpc/transaction_meta.py +0 -0
  60. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/grpc_client.py +0 -0
  61. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/grpc_instruction_parser.py +0 -0
  62. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/inner_instruction_parser.py +0 -0
  63. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/instr/__init__.py +0 -0
  64. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/instr_account_utils.py +0 -0
  65. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/json_util.py +0 -0
  66. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/logs/__init__.py +0 -0
  67. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/order_buffer.py +0 -0
  68. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/parser.py +0 -0
  69. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/pumpfun_fee_enrich.py +0 -0
  70. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/rpc_parser.py +0 -0
  71. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/rust_api_inventory.py +0 -0
  72. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/rust_event_json.py +0 -0
  73. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/shredstream_client.py +0 -0
  74. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/shredstream_pb2.py +0 -0
  75. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/shredstream_pb2_grpc.py +0 -0
  76. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/solana_storage_pb2.py +0 -0
  77. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/solana_storage_pb2_grpc.py +0 -0
  78. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/u128_parity.py +0 -0
  79. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/sol_parser/verify_discriminators.py +0 -0
  80. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/tests/fixtures/golden_parse_log.json +0 -0
  81. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/tests/test_golden.py +0 -0
  82. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/tests/test_imports_and_inner.py +0 -0
  83. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/tests/test_order_buffer.py +0 -0
  84. {sol_parser_sdk_python-0.4.4 → sol_parser_sdk_python-0.4.5}/tests/test_raydium_clmm_instruction_parity.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sol-parser-sdk-python
3
- Version: 0.4.4
3
+ Version: 0.4.5
4
4
  Summary: Solana DEX program log parsing (pure Python, API aligned with sol-parser-sdk)
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: base58>=2.0
@@ -56,7 +56,7 @@
56
56
  **From PyPI**
57
57
 
58
58
  ```bash
59
- pip install sol-parser-sdk-python
59
+ pip install sol-parser-sdk-python==0.4.5
60
60
  ```
61
61
 
62
62
  **From source**
@@ -56,7 +56,7 @@
56
56
  **PyPI**
57
57
 
58
58
  ```bash
59
- pip install sol-parser-sdk-python
59
+ pip install sol-parser-sdk-python==0.4.5
60
60
  ```
61
61
 
62
62
  **源码**
@@ -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, parse_logs_only
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
- SubscribeCallbacks,
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.subscribe_transactions(
98
- tx_filter,
99
- SubscribeCallbacks(
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
- await asyncio.Event().wait()
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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """PumpFun + 性能指标对齐 ``sol-parser-sdk/examples/pumpfun_with_metrics.rs``。
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, parse_logs_only
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
- total = total_latency
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"║ 队列长度: {queue_len:>10} ║")
50
+ print(f"║ 队列长度: {queue.qsize():>10} ║")
57
51
  print(f"║ 平均延迟: {avg:>10} μs ║")
58
52
  print(f"║ 最小延迟: {min_l:>10} μs ║")
59
- print(f"║ 最大延迟: {max_l:>10} μs ║")
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
- d = ev.data
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
- print("📋 Event Filter: Buy, Sell, BuyExactSolIn, Create")
121
-
122
- await client.connect()
123
- asyncio.create_task(stats_reporter())
124
-
125
- def on_update(update):
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
- sb = bytes(tx_info.signature) if tx_info.signature else b""
137
- sig = base58.b58encode(sb).decode("ascii") if len(sb) == 64 else ""
110
+ print("📋 Event Filter: Buy, Sell, BuyExactSolIn, Create")
111
+ print("🛑 Press Ctrl+C to stop...")
138
112
 
139
- for ev in parse_logs_only(
140
- logs, sig, slot, None, subscribe_tx_info=tx_info
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
- if not event_filter.should_include(ev.type):
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 - grpc_recv_us_opt
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接收时间: {grpc_recv_us_opt} μs")
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"队列长度: {queue_len}")
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...")