trc-8004-sdk 0.1.0b1__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.
sdk/chain_utils.py ADDED
@@ -0,0 +1,278 @@
1
+ """
2
+ TRC-8004 SDK 链上工具模块
3
+
4
+ 提供区块链数据加载和事件监听的工具函数。
5
+
6
+ Functions:
7
+ normalize_hash: 规范化哈希字符串
8
+ load_request_data: 从 URI 加载请求数据
9
+ fetch_event_logs: 获取合约事件日志
10
+ fetch_trongrid_events: 从 TronGrid API 获取事件
11
+
12
+ Supported URI Schemes:
13
+ - file://: 本地文件
14
+ - ipfs://: IPFS 内容(通过网关)
15
+ - http://, https://: HTTP(S) URL
16
+
17
+ Example:
18
+ >>> from sdk.chain_utils import load_request_data, normalize_hash
19
+ >>> data = load_request_data("ipfs://QmXxx...")
20
+ >>> hash_str = normalize_hash("0xABC123") # -> "abc123"
21
+
22
+ Environment Variables:
23
+ IPFS_GATEWAY_URL: IPFS 网关地址,默认 https://ipfs.io/ipfs
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import os
29
+ from typing import Optional, Any
30
+
31
+ import httpx
32
+
33
+
34
+ def normalize_hash(value: Optional[str]) -> str:
35
+ """
36
+ 规范化哈希字符串。
37
+
38
+ 将哈希转换为小写并移除 0x 前缀。
39
+
40
+ Args:
41
+ value: 哈希字符串,可带 0x 前缀
42
+
43
+ Returns:
44
+ 规范化的哈希字符串(小写,无前缀),
45
+ 如果输入为空则返回空字符串
46
+
47
+ Example:
48
+ >>> normalize_hash("0xABC123")
49
+ 'abc123'
50
+ >>> normalize_hash("DEF456")
51
+ 'def456'
52
+ >>> normalize_hash(None)
53
+ ''
54
+ """
55
+ if not value:
56
+ return ""
57
+ cleaned = value.lower()
58
+ if cleaned.startswith("0x"):
59
+ cleaned = cleaned[2:]
60
+ return cleaned
61
+
62
+
63
+ def load_request_data(request_uri: str) -> str:
64
+ """
65
+ 从 URI 加载请求数据。
66
+
67
+ 支持多种 URI 协议:
68
+ - file://: 从本地文件系统读取
69
+ - ipfs://: 通过 IPFS 网关获取
70
+ - http://, https://: 直接 HTTP 请求
71
+
72
+ Args:
73
+ request_uri: 数据 URI
74
+
75
+ Returns:
76
+ 加载的数据内容(字符串)
77
+
78
+ Raises:
79
+ FileNotFoundError: 本地文件不存在
80
+ httpx.HTTPStatusError: HTTP 请求失败
81
+ httpx.TimeoutException: 请求超时
82
+
83
+ Example:
84
+ >>> # 从本地文件加载
85
+ >>> data = load_request_data("file:///path/to/file.json")
86
+ >>>
87
+ >>> # 从 IPFS 加载
88
+ >>> data = load_request_data("ipfs://QmXxx...")
89
+ >>>
90
+ >>> # 从 HTTP URL 加载
91
+ >>> data = load_request_data("https://example.com/data.json")
92
+
93
+ Note:
94
+ - IPFS 网关可通过 IPFS_GATEWAY_URL 环境变量配置
95
+ - HTTP 请求超时时间为 10 秒
96
+ - 如果 URI 不匹配任何已知协议,原样返回
97
+ """
98
+ # 本地文件
99
+ if request_uri.startswith("file://"):
100
+ path = request_uri.replace("file://", "", 1)
101
+ with open(path, "r", encoding="utf-8") as handle:
102
+ return handle.read()
103
+
104
+ # IPFS 内容
105
+ if request_uri.startswith("ipfs://"):
106
+ cid = request_uri.replace("ipfs://", "", 1)
107
+ gateway = os.getenv("IPFS_GATEWAY_URL", "https://ipfs.io/ipfs")
108
+ url = f"{gateway.rstrip('/')}/{cid}"
109
+ with httpx.Client(timeout=10) as client:
110
+ response = client.get(url)
111
+ response.raise_for_status()
112
+ return response.text
113
+
114
+ # HTTP(S) URL
115
+ if request_uri.startswith("http://") or request_uri.startswith("https://"):
116
+ with httpx.Client(timeout=10) as client:
117
+ response = client.get(request_uri)
118
+ response.raise_for_status()
119
+ return response.text
120
+
121
+ # 未知协议,原样返回
122
+ return request_uri
123
+
124
+
125
+ def fetch_event_logs(
126
+ client: Any,
127
+ contract_address: str,
128
+ event_name: str,
129
+ from_block: int,
130
+ to_block: int,
131
+ rpc_url: Optional[str] = None,
132
+ ) -> list[dict]:
133
+ """
134
+ 获取合约事件日志。
135
+
136
+ 尝试多种方式获取事件:
137
+ 1. 使用合约对象的 events 属性
138
+ 2. 使用客户端的 get_event_result 方法
139
+ 3. 回退到 TronGrid API
140
+
141
+ Args:
142
+ client: 区块链客户端对象(如 tronpy.Tron)
143
+ contract_address: 合约地址
144
+ event_name: 事件名称(如 "ValidationRequest")
145
+ from_block: 起始区块号
146
+ to_block: 结束区块号
147
+ rpc_url: RPC URL,用于 TronGrid API 回退
148
+
149
+ Returns:
150
+ 事件日志列表,每个事件为字典格式
151
+
152
+ Example:
153
+ >>> from tronpy import Tron
154
+ >>> client = Tron()
155
+ >>> events = fetch_event_logs(
156
+ ... client=client,
157
+ ... contract_address="TValidationRegistry...",
158
+ ... event_name="ValidationRequest",
159
+ ... from_block=1000000,
160
+ ... to_block=1001000,
161
+ ... )
162
+ >>> for event in events:
163
+ ... print(event["transaction_id"])
164
+
165
+ Note:
166
+ - 不同的获取方式返回的事件格式可能略有不同
167
+ - 建议指定 rpc_url 以确保回退机制可用
168
+ """
169
+ # 方式 1: 使用合约的 events 属性
170
+ contract = client.get_contract(contract_address)
171
+ event = getattr(contract.events, event_name, None)
172
+ if event and hasattr(event, "get_logs"):
173
+ try:
174
+ return event.get_logs(from_block=from_block, to_block=to_block)
175
+ except Exception:
176
+ pass
177
+
178
+ # 方式 2: 使用客户端的 get_event_result 方法
179
+ if hasattr(client, "get_event_result"):
180
+ try:
181
+ return client.get_event_result(
182
+ contract_address=contract_address,
183
+ event_name=event_name,
184
+ from_block=from_block,
185
+ to_block=to_block,
186
+ only_confirmed=True,
187
+ limit=200,
188
+ )
189
+ except Exception:
190
+ pass
191
+
192
+ # 方式 3: 回退到 TronGrid API
193
+ if rpc_url:
194
+ return fetch_trongrid_events(rpc_url, contract_address, event_name, from_block, to_block)
195
+
196
+ return []
197
+
198
+
199
+ def fetch_trongrid_events(
200
+ rpc_url: str,
201
+ contract_address: str,
202
+ event_name: str,
203
+ from_block: int,
204
+ to_block: int,
205
+ ) -> list[dict]:
206
+ """
207
+ 从 TronGrid API 获取合约事件。
208
+
209
+ 使用 TronGrid 的 REST API 分页获取事件,
210
+ 并按区块范围过滤。
211
+
212
+ Args:
213
+ rpc_url: TronGrid API 基础 URL(如 https://api.trongrid.io)
214
+ contract_address: 合约地址
215
+ event_name: 事件名称
216
+ from_block: 起始区块号(包含)
217
+ to_block: 结束区块号(包含)
218
+
219
+ Returns:
220
+ 事件日志列表
221
+
222
+ Raises:
223
+ httpx.HTTPStatusError: API 请求失败
224
+
225
+ Example:
226
+ >>> events = fetch_trongrid_events(
227
+ ... rpc_url="https://api.trongrid.io",
228
+ ... contract_address="TValidationRegistry...",
229
+ ... event_name="ValidationRequest",
230
+ ... from_block=1000000,
231
+ ... to_block=1001000,
232
+ ... )
233
+
234
+ Note:
235
+ - 使用分页获取所有事件(每页最多 200 条)
236
+ - 区块范围过滤在客户端进行
237
+ - 仅返回已确认的事件
238
+ """
239
+ base = rpc_url.rstrip("/")
240
+ url = f"{base}/v1/contracts/{contract_address}/events"
241
+ params: dict[str, Any] = {
242
+ "event_name": event_name,
243
+ "only_confirmed": "true",
244
+ "limit": 200,
245
+ }
246
+
247
+ items: list[dict] = []
248
+
249
+ # 分页获取所有事件
250
+ while True:
251
+ resp = httpx.get(url, params=params, timeout=10)
252
+ resp.raise_for_status()
253
+ payload = resp.json()
254
+ batch = payload.get("data") or []
255
+ items.extend(batch)
256
+
257
+ # 检查是否有下一页
258
+ fingerprint = (payload.get("meta") or {}).get("fingerprint")
259
+ if not fingerprint:
260
+ break
261
+ params["fingerprint"] = fingerprint
262
+
263
+ # 按区块范围过滤
264
+ if from_block or to_block:
265
+ filtered = []
266
+ for item in items:
267
+ block = item.get("block_number")
268
+ if block is None:
269
+ filtered.append(item)
270
+ continue
271
+ if block < from_block:
272
+ continue
273
+ if block > to_block:
274
+ continue
275
+ filtered.append(item)
276
+ return filtered
277
+
278
+ return items