sol-parser-sdk-python 0.4.4__py3-none-any.whl

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 (54) hide show
  1. sol_parser/__init__.py +400 -0
  2. sol_parser/account_dispatcher.py +209 -0
  3. sol_parser/account_fillers/__init__.py +5 -0
  4. sol_parser/account_fillers/bonk.py +30 -0
  5. sol_parser/account_fillers/meteora.py +51 -0
  6. sol_parser/account_fillers/orca.py +40 -0
  7. sol_parser/account_fillers/pumpfun.py +97 -0
  8. sol_parser/account_fillers/pumpswap.py +93 -0
  9. sol_parser/account_fillers/raydium.py +119 -0
  10. sol_parser/accounts/__init__.py +461 -0
  11. sol_parser/accounts/rpc_wallet.py +64 -0
  12. sol_parser/accounts/utils.py +71 -0
  13. sol_parser/check_migration.py +18 -0
  14. sol_parser/clock.py +10 -0
  15. sol_parser/common/__init__.py +27 -0
  16. sol_parser/dex_parsers.py +2576 -0
  17. sol_parser/entries_decode.py +186 -0
  18. sol_parser/env_config.py +215 -0
  19. sol_parser/event_types.py +1750 -0
  20. sol_parser/geyser_pb2.py +148 -0
  21. sol_parser/geyser_pb2_grpc.py +398 -0
  22. sol_parser/grpc/__init__.py +61 -0
  23. sol_parser/grpc/geyser_connect.py +42 -0
  24. sol_parser/grpc/subscribe_builder.py +133 -0
  25. sol_parser/grpc/transaction_meta.py +183 -0
  26. sol_parser/grpc_client.py +870 -0
  27. sol_parser/grpc_instruction_parser.py +318 -0
  28. sol_parser/grpc_types.py +919 -0
  29. sol_parser/inner_instruction_parser.py +281 -0
  30. sol_parser/instr/__init__.py +15 -0
  31. sol_parser/instr_account_utils.py +58 -0
  32. sol_parser/instructions.py +1026 -0
  33. sol_parser/json_util.py +41 -0
  34. sol_parser/log_instr_dedup.py +284 -0
  35. sol_parser/logs/__init__.py +15 -0
  36. sol_parser/merger.py +233 -0
  37. sol_parser/order_buffer.py +171 -0
  38. sol_parser/parser.py +300 -0
  39. sol_parser/pumpfun_fee_enrich.py +75 -0
  40. sol_parser/rpc_parser.py +655 -0
  41. sol_parser/rust_api_inventory.py +42 -0
  42. sol_parser/rust_event_json.py +50 -0
  43. sol_parser/shredstream_client.py +191 -0
  44. sol_parser/shredstream_pb2.py +40 -0
  45. sol_parser/shredstream_pb2_grpc.py +81 -0
  46. sol_parser/shredstream_pumpfun.py +296 -0
  47. sol_parser/solana_storage_pb2.py +75 -0
  48. sol_parser/solana_storage_pb2_grpc.py +24 -0
  49. sol_parser/u128_parity.py +115 -0
  50. sol_parser/verify_discriminators.py +85 -0
  51. sol_parser_sdk_python-0.4.4.dist-info/METADATA +14 -0
  52. sol_parser_sdk_python-0.4.4.dist-info/RECORD +54 -0
  53. sol_parser_sdk_python-0.4.4.dist-info/WHEEL +4 -0
  54. sol_parser_sdk_python-0.4.4.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,186 @@
1
+ """gRPC ShredStream ``Entry.entries`` 负载解码(对齐 Node ``entries_decode.ts`` / Go ``DecodeEntriesBincode``)。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import struct
6
+ from dataclasses import dataclass
7
+ from typing import List, Optional
8
+
9
+ MAX_VEC_ENTRIES = 100_000
10
+
11
+
12
+ def read_u64_le(buf: bytes, offset: int) -> int:
13
+ return struct.unpack_from("<Q", buf, offset)[0]
14
+
15
+
16
+ def bincode_vec_entry_count(entries_bytes: bytes) -> int:
17
+ if len(entries_bytes) < 8:
18
+ raise ValueError("shredstream: entries payload too short for vec length")
19
+ n = read_u64_le(entries_bytes, 0)
20
+ if n > MAX_VEC_ENTRIES:
21
+ raise ValueError(f"shredstream: corrupt entry_count {n} exceeds limit")
22
+ return n
23
+
24
+
25
+ def decode_compact_u16(buf: bytes, pos: int) -> Optional[tuple[int, int]]:
26
+ if pos >= len(buf):
27
+ return None
28
+ b0 = buf[pos]
29
+ if b0 < 0x80:
30
+ return (b0, 1)
31
+ if pos + 1 >= len(buf):
32
+ return None
33
+ b1 = buf[pos + 1]
34
+ if b1 < 0x80:
35
+ return ((b0 & 0x7F) | (b1 << 7), 2)
36
+ if pos + 2 >= len(buf):
37
+ return None
38
+ b2 = buf[pos + 2]
39
+ return ((b0 & 0x7F) | ((b1 & 0x7F) << 7) | (b2 << 14), 3)
40
+
41
+
42
+ @dataclass
43
+ class _ParsedTx:
44
+ tx_len: int
45
+ signatures: List[bytes]
46
+
47
+
48
+ def parse_transaction_wire(buf: bytes, pos: int) -> Optional[_ParsedTx]:
49
+ start = pos
50
+ if pos >= len(buf):
51
+ return None
52
+ sig_count_enc = decode_compact_u16(buf, pos)
53
+ if not sig_count_enc:
54
+ return None
55
+ p = pos + sig_count_enc[1]
56
+ sig_count = sig_count_enc[0]
57
+ sigs_end = p + sig_count * 64
58
+ if sigs_end > len(buf):
59
+ return None
60
+ sigs: List[bytes] = []
61
+ for _ in range(sig_count):
62
+ sigs.append(buf[p : p + 64])
63
+ p += 64
64
+ if p >= len(buf):
65
+ return None
66
+ msg_first = buf[p]
67
+ is_v0 = msg_first >= 0x80
68
+ if is_v0:
69
+ p += 1
70
+ p += 3
71
+ if p > len(buf):
72
+ return None
73
+ acct_enc = decode_compact_u16(buf, p)
74
+ if not acct_enc:
75
+ return None
76
+ p += acct_enc[1]
77
+ p += acct_enc[0] * 32
78
+ if p > len(buf):
79
+ return None
80
+ ix_count_enc = decode_compact_u16(buf, p)
81
+ if not ix_count_enc:
82
+ return None
83
+ p += ix_count_enc[1]
84
+ ix_count = ix_count_enc[0]
85
+ for _ in range(ix_count):
86
+ p += 1
87
+ if p > len(buf):
88
+ return None
89
+ acct_len_enc = decode_compact_u16(buf, p)
90
+ if not acct_len_enc:
91
+ return None
92
+ p += acct_len_enc[1]
93
+ p += acct_len_enc[0]
94
+ if p > len(buf):
95
+ return None
96
+ data_len_enc = decode_compact_u16(buf, p)
97
+ if not data_len_enc:
98
+ return None
99
+ p += data_len_enc[1]
100
+ p += data_len_enc[0]
101
+ if p > len(buf):
102
+ return None
103
+ if is_v0:
104
+ if p >= len(buf):
105
+ return None
106
+ atl_count_enc = decode_compact_u16(buf, p)
107
+ if not atl_count_enc:
108
+ return None
109
+ p += atl_count_enc[1]
110
+ atl_count = atl_count_enc[0]
111
+ for _ in range(atl_count):
112
+ p += 32
113
+ if p > len(buf):
114
+ return None
115
+ w_len_enc = decode_compact_u16(buf, p)
116
+ if not w_len_enc:
117
+ return None
118
+ p += w_len_enc[1]
119
+ p += w_len_enc[0]
120
+ if p > len(buf):
121
+ return None
122
+ r_len_enc = decode_compact_u16(buf, p)
123
+ if not r_len_enc:
124
+ return None
125
+ p += r_len_enc[1]
126
+ p += r_len_enc[0]
127
+ if p > len(buf):
128
+ return None
129
+ return _ParsedTx(tx_len=p - start, signatures=sigs)
130
+
131
+
132
+ class _BatchDecoder:
133
+ def __init__(self) -> None:
134
+ self.buffer = bytearray()
135
+ self.expected_entry_count = 0
136
+ self.entries_yielded = 0
137
+ self.cursor = 0
138
+
139
+ def append(self, payload: bytes) -> None:
140
+ self.buffer.extend(payload)
141
+
142
+ def try_decode_entry(self) -> Optional[List[bytes]]:
143
+ pos = self.cursor
144
+ buf = self.buffer
145
+ if pos + 48 > len(buf):
146
+ return None
147
+ p = pos + 8 + 32
148
+ tx_count = read_u64_le(buf, p)
149
+ p += 8
150
+ txs: List[bytes] = []
151
+ for _ in range(tx_count):
152
+ tx_start = p
153
+ parsed = parse_transaction_wire(buf, p)
154
+ if not parsed:
155
+ raise ValueError("shredstream: truncated transaction in entry")
156
+ raw = bytes(buf[tx_start : tx_start + parsed.tx_len])
157
+ txs.append(raw)
158
+ p = tx_start + parsed.tx_len
159
+ self.cursor = p
160
+ return txs
161
+
162
+ def push_flat(self, payload: bytes) -> List[bytes]:
163
+ self.append(payload)
164
+ if self.expected_entry_count == 0 and self.entries_yielded == 0 and self.cursor == 0:
165
+ if len(self.buffer) < 8:
166
+ raise ValueError("shredstream: entries payload too short for vec length")
167
+ count = read_u64_le(self.buffer, 0)
168
+ if count > MAX_VEC_ENTRIES:
169
+ raise ValueError(f"shredstream: corrupt entry_count {count} exceeds limit")
170
+ self.expected_entry_count = int(count)
171
+ self.cursor = 8
172
+ out: List[bytes] = []
173
+ while self.entries_yielded < self.expected_entry_count:
174
+ entry_txs = self.try_decode_entry()
175
+ if entry_txs is None:
176
+ raise ValueError(f"shredstream: incomplete entry at offset {self.cursor}")
177
+ out.extend(entry_txs)
178
+ self.entries_yielded += 1
179
+ return out
180
+
181
+
182
+ def decode_entries_bincode_flat(entries_bytes: bytes) -> List[bytes]:
183
+ if not entries_bytes:
184
+ return []
185
+ dec = _BatchDecoder()
186
+ return dec.push_flat(entries_bytes)
@@ -0,0 +1,215 @@
1
+ """Environment and CLI helpers aligned with sol-parser-sdk-nodejs (GRPC_URL, .env, flags)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+ from typing import Optional, Sequence, Tuple
8
+
9
+
10
+ def load_dotenv_silent(dotenv_path: Optional[str] = None) -> bool:
11
+ """Load ``.env`` from the current working directory (or ``dotenv_path``).
12
+
13
+ Does **not** override variables already set in the process environment
14
+ (same idea as ``dotenv`` on Node). Returns ``False`` if ``python-dotenv``
15
+ is not installed.
16
+ """
17
+ try:
18
+ from dotenv import load_dotenv
19
+ except ImportError:
20
+ return False
21
+ load_dotenv(dotenv_path, override=False)
22
+ return True
23
+
24
+
25
+ def _first_nonempty(*values: Optional[str]) -> str:
26
+ for v in values:
27
+ if v is None:
28
+ continue
29
+ s = str(v).strip()
30
+ if s:
31
+ return s
32
+ return ""
33
+
34
+
35
+ def _parse_grpc_cli(argv: Sequence[str]) -> tuple[Optional[str], Optional[str]]:
36
+ """Parse ``--grpc-url``, ``--grpc-token``, and short aliases from *argv*."""
37
+ url: Optional[str] = None
38
+ token: Optional[str] = None
39
+ i = 0
40
+ n = len(argv)
41
+ while i < n:
42
+ a = argv[i]
43
+ if a in ("--grpc-url", "--grpc-endpoint", "-g") and i + 1 < n:
44
+ url = argv[i + 1].strip()
45
+ i += 2
46
+ continue
47
+ if a.startswith("--grpc-url="):
48
+ url = a.split("=", 1)[1].strip()
49
+ i += 1
50
+ continue
51
+ if a.startswith("--grpc-endpoint="):
52
+ url = a.split("=", 1)[1].strip()
53
+ i += 1
54
+ continue
55
+ if a in ("--grpc-token", "--token", "-t") and i + 1 < n:
56
+ token = argv[i + 1].strip()
57
+ i += 2
58
+ continue
59
+ if a.startswith("--grpc-token="):
60
+ token = a.split("=", 1)[1].strip()
61
+ i += 1
62
+ continue
63
+ if a.startswith("--token="):
64
+ token = a.split("=", 1)[1].strip()
65
+ i += 1
66
+ continue
67
+ i += 1
68
+ return url, token
69
+
70
+
71
+ def parse_grpc_credentials(
72
+ argv: Optional[Sequence[str]] = None,
73
+ *,
74
+ default_endpoint: str = "solana-yellowstone-grpc.publicnode.com:443",
75
+ require_token: bool = False,
76
+ ) -> Tuple[str, str]:
77
+ """Resolve Yellowstone gRPC URL and x-token.
78
+
79
+ **Precedence (each field):** CLI flags > ``GRPC_URL`` / ``GRPC_ENDPOINT`` /
80
+ ``GRPC_AUTH_TOKEN`` / ``GRPC_TOKEN`` > legacy ``GEYSER_*`` >
81
+ *default_endpoint* (URL only).
82
+
83
+ Call :func:`load_dotenv_silent` first so a project-local ``.env`` is applied.
84
+
85
+ :param require_token: If ``True``, print an error and ``sys.exit(1)`` when the
86
+ token is still empty after resolution.
87
+ """
88
+ load_dotenv_silent()
89
+ cli_url, cli_token = _parse_grpc_cli(list(argv) if argv is not None else [])
90
+ url = _first_nonempty(
91
+ cli_url,
92
+ os.environ.get("GRPC_URL"),
93
+ os.environ.get("GRPC_ENDPOINT"),
94
+ os.environ.get("GEYSER_ENDPOINT"),
95
+ default_endpoint,
96
+ )
97
+ token = _first_nonempty(
98
+ cli_token,
99
+ os.environ.get("GRPC_AUTH_TOKEN"),
100
+ os.environ.get("GRPC_TOKEN"),
101
+ os.environ.get("GEYSER_API_TOKEN"),
102
+ )
103
+ if require_token and not token:
104
+ print(
105
+ "Error: GRPC_AUTH_TOKEN / GRPC_TOKEN is required (x-token for the gRPC endpoint).\n"
106
+ " Copy .env.example to .env in the package root and set GRPC_AUTH_TOKEN or GRPC_TOKEN, or:\n"
107
+ " export GRPC_AUTH_TOKEN=<your_token>\n"
108
+ " You can also pass: --grpc-token <token> or --token=<token>",
109
+ file=sys.stderr,
110
+ )
111
+ sys.exit(1)
112
+ return url, token
113
+
114
+
115
+ def require_grpc_env(argv: Optional[Sequence[str]] = None) -> Tuple[str, str]:
116
+ """Require both gRPC URL and token (same contract as Node ``scripts/grpc_env.ts``).
117
+
118
+ No default URL: set ``GRPC_URL`` / ``GEYSER_ENDPOINT`` or ``--grpc-url``.
119
+ """
120
+ load_dotenv_silent()
121
+ argv = list(sys.argv[1:] if argv is None else argv)
122
+ cli_url, cli_token = _parse_grpc_cli(argv)
123
+ url = _first_nonempty(
124
+ cli_url,
125
+ os.environ.get("GRPC_URL"),
126
+ os.environ.get("GRPC_ENDPOINT"),
127
+ os.environ.get("GEYSER_ENDPOINT"),
128
+ )
129
+ token = _first_nonempty(
130
+ cli_token,
131
+ os.environ.get("GRPC_AUTH_TOKEN"),
132
+ os.environ.get("GRPC_TOKEN"),
133
+ os.environ.get("GEYSER_API_TOKEN"),
134
+ )
135
+ if not url:
136
+ print(
137
+ "Error: GRPC_URL / GRPC_ENDPOINT is required.\n"
138
+ " Copy .env.example to .env in the package root and set GRPC_URL (and GRPC_AUTH_TOKEN), or:\n"
139
+ " export GRPC_URL=https://your-yellowstone-host:443\n"
140
+ " CLI: --grpc-url <url> or -g <url>",
141
+ file=sys.stderr,
142
+ )
143
+ sys.exit(1)
144
+ if not token:
145
+ print(
146
+ "Error: GRPC_AUTH_TOKEN / GRPC_TOKEN is required (x-token header for the gRPC endpoint).\n"
147
+ " Copy .env.example to .env in the package root and set GRPC_AUTH_TOKEN, or:\n"
148
+ " export GRPC_AUTH_TOKEN=<your_token>",
149
+ file=sys.stderr,
150
+ )
151
+ sys.exit(1)
152
+ return url, token
153
+
154
+
155
+ def parse_shredstream_url(
156
+ argv: Optional[Sequence[str]] = None,
157
+ *,
158
+ default_url: str = "http://127.0.0.1:10800",
159
+ ) -> str:
160
+ """ShredStream HTTP endpoint: ``--url`` / ``-u`` / ``--endpoint=`` > ``SHREDSTREAM_URL`` / ``SHRED_URL`` > default."""
161
+ load_dotenv_silent()
162
+ argv = list(sys.argv[1:] if argv is None else argv)
163
+ for i, a in enumerate(argv):
164
+ if a in ("--url", "-u", "--endpoint") and i + 1 < len(argv):
165
+ v = argv[i + 1].strip()
166
+ if v:
167
+ return v
168
+ if a.startswith("--url="):
169
+ return a[6:].strip()
170
+ if a.startswith("--endpoint="):
171
+ return a[11:].strip()
172
+ env_url = _first_nonempty(os.environ.get("SHREDSTREAM_URL"), os.environ.get("SHRED_URL"))
173
+ return env_url or default_url
174
+
175
+
176
+ def parse_optional_rpc_url(
177
+ argv: Optional[Sequence[str]] = None,
178
+ *,
179
+ default_rpc: str = "https://api.mainnet-beta.solana.com",
180
+ ) -> str:
181
+ """``--rpc`` / ``-r`` / ``--rpc=`` > ``RPC_URL`` > *default_rpc*."""
182
+ load_dotenv_silent()
183
+ argv = list(sys.argv[1:] if argv is None else argv)
184
+ for i, a in enumerate(argv):
185
+ if a in ("--rpc", "-r") and i + 1 < len(argv):
186
+ v = argv[i + 1].strip()
187
+ if v:
188
+ return v
189
+ if a.startswith("--rpc="):
190
+ return a[6:].strip()
191
+ return _first_nonempty(os.environ.get("RPC_URL"), os.environ.get("SOLANA_RPC_URL")) or default_rpc
192
+
193
+
194
+ def _parse_sig_cli(argv: Sequence[str]) -> Optional[str]:
195
+ for i, a in enumerate(argv):
196
+ if a in ("--sig", "-s") and i + 1 < len(argv):
197
+ v = argv[i + 1].strip()
198
+ if v:
199
+ return v
200
+ if a.startswith("--sig="):
201
+ return a[6:].strip()
202
+ return None
203
+
204
+
205
+ def parse_rpc_and_tx_signature(
206
+ argv: Optional[Sequence[str]] = None,
207
+ *,
208
+ default_signature: str,
209
+ ) -> Tuple[str, str]:
210
+ """For ``parse_tx_by_signature``-style tools: RPC URL + transaction signature."""
211
+ load_dotenv_silent()
212
+ argv = list(sys.argv[1:] if argv is None else argv)
213
+ rpc = parse_optional_rpc_url(argv)
214
+ sig = _first_nonempty(_parse_sig_cli(argv), os.environ.get("TX_SIGNATURE"), default_signature)
215
+ return rpc, sig