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/agent_sdk.py ADDED
@@ -0,0 +1,1549 @@
1
+ """
2
+ TRC-8004 Agent SDK
3
+
4
+ 提供 Agent 与链上合约交互的统一接口,支持:
5
+ - 身份注册与元数据管理 (IdentityRegistry)
6
+ - 验证请求与响应 (ValidationRegistry)
7
+ - 信誉反馈提交 (ReputationRegistry)
8
+ - 签名构建与验证
9
+ - 请求构建辅助
10
+
11
+ Example:
12
+ >>> from sdk import AgentSDK
13
+ >>> sdk = AgentSDK(
14
+ ... private_key="your_hex_private_key",
15
+ ... rpc_url="https://nile.trongrid.io",
16
+ ... network="tron:nile",
17
+ ... identity_registry="TIdentityAddr",
18
+ ... validation_registry="TValidationAddr",
19
+ ... reputation_registry="TReputationAddr",
20
+ ... )
21
+ >>> tx_id = sdk.register_agent(token_uri="https://example.com/agent.json")
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import logging
27
+ from dataclasses import dataclass, field
28
+ from typing import Optional
29
+
30
+ import httpx
31
+
32
+ from .contract_adapter import ContractAdapter, DummyContractAdapter, TronContractAdapter
33
+ from .exceptions import (
34
+ ChainIdResolutionError,
35
+ ConfigurationError,
36
+ InvalidAddressError,
37
+ InvalidPrivateKeyError,
38
+ NetworkError,
39
+ SignerNotAvailableError,
40
+ )
41
+ from .retry import RetryConfig, DEFAULT_RETRY_CONFIG, retry
42
+ from .signer import Signer, SimpleSigner, TronSigner
43
+ from .utils import canonical_json, canonical_json_str, keccak256_hex, keccak256_bytes
44
+
45
+ logger = logging.getLogger("trc8004.sdk")
46
+
47
+
48
+ def _is_hex_key(value: str) -> bool:
49
+ """检查字符串是否为有效的十六进制私钥"""
50
+ if not value:
51
+ return False
52
+ try:
53
+ bytes.fromhex(value)
54
+ return len(value) in (64, 66) # 32 bytes, with or without 0x
55
+ except ValueError:
56
+ return False
57
+
58
+
59
+ def _is_hex_string(value: str) -> bool:
60
+ """检查字符串是否为有效的十六进制字符串"""
61
+ if not value:
62
+ return False
63
+ try:
64
+ bytes.fromhex(value)
65
+ return True
66
+ except ValueError:
67
+ return False
68
+
69
+
70
+ @dataclass
71
+ class SDKConfig:
72
+ """
73
+ SDK 配置类
74
+
75
+ Attributes:
76
+ rpc_url: 区块链 RPC 节点地址
77
+ network: 网络标识 (如 "tron:nile", "tron:mainnet", "evm:1")
78
+ timeout: HTTP 请求超时时间(秒)
79
+ identity_registry: IdentityRegistry 合约地址
80
+ validation_registry: ValidationRegistry 合约地址
81
+ reputation_registry: ReputationRegistry 合约地址
82
+ retry_config: 重试配置
83
+ """
84
+
85
+ rpc_url: str = "https://nile.trongrid.io"
86
+ network: str = "tron:nile"
87
+ timeout: int = 10
88
+ identity_registry: Optional[str] = None
89
+ validation_registry: Optional[str] = None
90
+ reputation_registry: Optional[str] = None
91
+ retry_config: RetryConfig = field(default_factory=lambda: DEFAULT_RETRY_CONFIG)
92
+
93
+
94
+ class AgentSDK:
95
+ """
96
+ TRC-8004 Agent SDK 主类
97
+
98
+ 提供与链上合约交互的统一接口,包括:
99
+ - 身份注册与元数据管理
100
+ - 验证请求与响应
101
+ - 信誉反馈提交
102
+ - 签名构建
103
+
104
+ Args:
105
+ private_key: 私钥(十六进制字符串,可带 0x 前缀)
106
+ rpc_url: RPC 节点地址
107
+ network: 网络标识(如 "tron:nile")
108
+ identity_registry: IdentityRegistry 合约地址
109
+ validation_registry: ValidationRegistry 合约地址
110
+ reputation_registry: ReputationRegistry 合约地址
111
+ fee_limit: 交易费用上限(TRON 特有)
112
+ signer: 自定义签名器(可选)
113
+ contract_adapter: 自定义合约适配器(可选)
114
+ retry_config: 重试配置(可选)
115
+
116
+ Raises:
117
+ InvalidPrivateKeyError: 私钥格式无效
118
+ ConfigurationError: 配置错误
119
+
120
+ Example:
121
+ >>> sdk = AgentSDK(
122
+ ... private_key="your_private_key",
123
+ ... rpc_url="https://nile.trongrid.io",
124
+ ... network="tron:nile",
125
+ ... )
126
+ """
127
+
128
+ def __init__(
129
+ self,
130
+ private_key: Optional[str] = None,
131
+ rpc_url: Optional[str] = None,
132
+ network: Optional[str] = None,
133
+ identity_registry: Optional[str] = None,
134
+ validation_registry: Optional[str] = None,
135
+ reputation_registry: Optional[str] = None,
136
+ identity_registry_abi_path: Optional[str] = None,
137
+ validation_registry_abi_path: Optional[str] = None,
138
+ reputation_registry_abi_path: Optional[str] = None,
139
+ fee_limit: Optional[int] = None,
140
+ signer: Optional[Signer] = None,
141
+ contract_adapter: Optional[ContractAdapter] = None,
142
+ retry_config: Optional[RetryConfig] = None,
143
+ ) -> None:
144
+ # 初始化配置
145
+ self.config = SDKConfig()
146
+ if rpc_url is not None:
147
+ self.config.rpc_url = rpc_url
148
+ if network is not None:
149
+ self.config.network = network
150
+ if identity_registry is not None:
151
+ self.config.identity_registry = identity_registry
152
+ if validation_registry is not None:
153
+ self.config.validation_registry = validation_registry
154
+ if reputation_registry is not None:
155
+ self.config.reputation_registry = reputation_registry
156
+ if retry_config is not None:
157
+ self.config.retry_config = retry_config
158
+
159
+ # 初始化签名器
160
+ if signer is None:
161
+ signer = self._create_signer(private_key)
162
+ self.signer = signer
163
+
164
+ # 初始化合约适配器
165
+ if contract_adapter is None:
166
+ contract_adapter = self._create_contract_adapter(
167
+ identity_registry_abi_path,
168
+ validation_registry_abi_path,
169
+ reputation_registry_abi_path,
170
+ fee_limit,
171
+ )
172
+ self.contract_adapter = contract_adapter
173
+
174
+ logger.info(
175
+ "SDK initialized: network=%s, rpc=%s, signer=%s",
176
+ self.config.network,
177
+ self.config.rpc_url,
178
+ type(self.signer).__name__,
179
+ )
180
+
181
+ @property
182
+ def address(self) -> Optional[str]:
183
+ """
184
+ 获取签名器的地址
185
+
186
+ Returns:
187
+ 签名器地址,如果没有签名器则返回 None
188
+ """
189
+ if self.signer is None:
190
+ return None
191
+ return self.signer.get_address()
192
+
193
+ def _create_signer(self, private_key: Optional[str]) -> Signer:
194
+ """创建签名器"""
195
+ if self.config.network.startswith("tron") and private_key:
196
+ cleaned_key = private_key.replace("0x", "")
197
+ if _is_hex_key(cleaned_key):
198
+ try:
199
+ return TronSigner(private_key=cleaned_key)
200
+ except Exception as e:
201
+ raise InvalidPrivateKeyError(str(e)) from e
202
+ else:
203
+ logger.warning("Private key is not hex format, using SimpleSigner")
204
+ return SimpleSigner(private_key=private_key)
205
+ return SimpleSigner(private_key=private_key)
206
+
207
+ def _create_contract_adapter(
208
+ self,
209
+ identity_abi_path: Optional[str],
210
+ validation_abi_path: Optional[str],
211
+ reputation_abi_path: Optional[str],
212
+ fee_limit: Optional[int],
213
+ ) -> ContractAdapter:
214
+ """创建合约适配器"""
215
+ if self.config.network.startswith("tron"):
216
+ return TronContractAdapter(
217
+ rpc_url=self.config.rpc_url,
218
+ identity_registry=self.config.identity_registry,
219
+ validation_registry=self.config.validation_registry,
220
+ reputation_registry=self.config.reputation_registry,
221
+ identity_registry_abi_path=identity_abi_path,
222
+ validation_registry_abi_path=validation_abi_path,
223
+ reputation_registry_abi_path=reputation_abi_path,
224
+ fee_limit=fee_limit,
225
+ retry_config=self.config.retry_config,
226
+ )
227
+ return DummyContractAdapter()
228
+
229
+ def validation_request(
230
+ self,
231
+ validator_addr: str,
232
+ agent_id: int,
233
+ request_uri: str,
234
+ request_hash: Optional[str] = None,
235
+ signer: Optional[Signer] = None,
236
+ ) -> str:
237
+ """
238
+ 发起验证请求
239
+
240
+ 将执行结果提交到 ValidationRegistry,请求验证者进行验证。
241
+
242
+ Args:
243
+ validator_addr: 验证者地址
244
+ agent_id: Agent ID(IdentityRegistry 中的 token ID)
245
+ request_uri: 请求数据 URI(如 ipfs://Qm...)
246
+ request_hash: 请求数据哈希(32 bytes,可选,会自动补零)
247
+ signer: 自定义签名器(可选)
248
+
249
+ Returns:
250
+ 交易 ID
251
+
252
+ Raises:
253
+ ContractCallError: 合约调用失败
254
+ SignerNotAvailableError: 签名器不可用
255
+
256
+ Example:
257
+ >>> tx_id = sdk.validation_request(
258
+ ... validator_addr="TValidator...",
259
+ ... agent_id=1,
260
+ ... request_uri="ipfs://QmXxx",
261
+ ... request_hash="0x" + "aa" * 32,
262
+ ... )
263
+ """
264
+ signer = signer or self.signer
265
+ if signer is None:
266
+ raise SignerNotAvailableError()
267
+
268
+ params = [validator_addr, agent_id, request_uri, self._normalize_bytes32(request_hash)]
269
+ logger.debug("validation_request: validator=%s, agent_id=%d", validator_addr, agent_id)
270
+ return self.contract_adapter.send("validation", "validationRequest", params, signer)
271
+
272
+ def validation_response(
273
+ self,
274
+ request_hash: str,
275
+ response: int,
276
+ response_uri: str = "",
277
+ response_hash: Optional[str] = None,
278
+ tag: str = "",
279
+ signer: Optional[Signer] = None,
280
+ ) -> str:
281
+ """
282
+ 提交验证响应 (Jan 2026 Update)
283
+
284
+ 验证者调用此方法提交验证结果。
285
+
286
+ Args:
287
+ request_hash: 验证请求哈希(32 bytes)
288
+ response: 验证评分(0-100)
289
+ response_uri: 响应数据 URI(可选)
290
+ response_hash: 响应数据哈希(可选)
291
+ tag: 标签(可选,字符串)
292
+ signer: 自定义签名器(可选)
293
+
294
+ Returns:
295
+ 交易 ID
296
+
297
+ Raises:
298
+ ContractCallError: 合约调用失败
299
+ """
300
+ signer = signer or self.signer
301
+ if signer is None:
302
+ raise SignerNotAvailableError()
303
+
304
+ params = [
305
+ self._normalize_bytes32(request_hash),
306
+ response,
307
+ response_uri,
308
+ self._normalize_bytes32(response_hash),
309
+ tag,
310
+ ]
311
+ logger.debug("validation_response: request_hash=%s, response=%d", request_hash[:18], response)
312
+ return self.contract_adapter.send("validation", "validationResponse", params, signer)
313
+
314
+ def submit_reputation(
315
+ self,
316
+ agent_id: int,
317
+ score: int,
318
+ tag1: str = "",
319
+ tag2: str = "",
320
+ endpoint: str = "",
321
+ feedback_uri: str = "",
322
+ feedback_hash: Optional[str] = None,
323
+ signer: Optional[Signer] = None,
324
+ ) -> str:
325
+ """
326
+ 提交信誉反馈 (Jan 2026 Update)
327
+
328
+ 向 ReputationRegistry 提交对 Agent 的评分反馈。
329
+
330
+ 注意:Jan 2026 更新移除了 feedbackAuth 预授权机制,现在任何人都可以直接提交反馈。
331
+ Spam/Sybil 防护通过链下过滤和信誉系统处理。
332
+
333
+ Args:
334
+ agent_id: Agent ID
335
+ score: 评分(0-100)
336
+ tag1: 标签1(可选,字符串)
337
+ tag2: 标签2(可选,字符串)
338
+ endpoint: 使用的 endpoint(可选)
339
+ feedback_uri: 反馈文件 URI(可选)
340
+ feedback_hash: 反馈文件哈希(可选,IPFS 不需要)
341
+ signer: 自定义签名器(可选)
342
+
343
+ Returns:
344
+ 交易 ID
345
+
346
+ Raises:
347
+ ContractCallError: 合约调用失败
348
+
349
+ Example:
350
+ >>> tx_id = sdk.submit_reputation(
351
+ ... agent_id=1,
352
+ ... score=95,
353
+ ... tag1="execution",
354
+ ... tag2="market-swap",
355
+ ... endpoint="/a2a/x402/execute",
356
+ ... )
357
+ """
358
+ signer = signer or self.signer
359
+ if signer is None:
360
+ raise SignerNotAvailableError()
361
+
362
+ params = [
363
+ agent_id,
364
+ score,
365
+ tag1,
366
+ tag2,
367
+ endpoint,
368
+ feedback_uri,
369
+ self._normalize_bytes32(feedback_hash),
370
+ ]
371
+ logger.debug("submit_reputation: agent_id=%d, score=%d", agent_id, score)
372
+ return self.contract_adapter.send("reputation", "giveFeedback", params, signer)
373
+
374
+ def register_agent(
375
+ self,
376
+ token_uri: Optional[str] = None,
377
+ metadata: Optional[list[dict]] = None,
378
+ signer: Optional[Signer] = None,
379
+ ) -> str:
380
+ """
381
+ 注册 Agent
382
+
383
+ 在 IdentityRegistry 中注册新的 Agent,获得唯一的 Agent ID。
384
+
385
+ Args:
386
+ token_uri: Agent 元数据 URI(如 https://example.com/agent.json)
387
+ metadata: 初始元数据列表,格式为 [{"key": "name", "value": "MyAgent"}, ...]
388
+ signer: 自定义签名器(可选)
389
+
390
+ Returns:
391
+ 交易 ID
392
+
393
+ Raises:
394
+ ContractCallError: 合约调用失败
395
+
396
+ Example:
397
+ >>> tx_id = sdk.register_agent(
398
+ ... token_uri="https://example.com/agent.json",
399
+ ... metadata=[{"key": "name", "value": "MyAgent"}],
400
+ ... )
401
+ """
402
+ signer = signer or self.signer
403
+ if signer is None:
404
+ raise SignerNotAvailableError()
405
+
406
+ token_uri = token_uri or ""
407
+ if metadata is not None:
408
+ normalized = self._normalize_metadata_entries(metadata)
409
+ params = [token_uri, normalized]
410
+ logger.debug("register_agent: uri=%s, metadata_count=%d", token_uri, len(normalized))
411
+ return self.contract_adapter.send("identity", "register", params, signer)
412
+
413
+ if token_uri:
414
+ params = [token_uri]
415
+ else:
416
+ params = []
417
+ logger.debug("register_agent: uri=%s", token_uri or "(empty)")
418
+ return self.contract_adapter.send("identity", "register", params, signer)
419
+
420
+ @staticmethod
421
+ def extract_metadata_from_card(card: dict) -> list[dict]:
422
+ """
423
+ 从 agent-card.json 提取关键信息作为链上 metadata。
424
+
425
+ 注意:根据 ERC-8004 规范,链上 metadata 应该是最小化的。
426
+ 大部分信息应该存储在 token_uri 指向的 registration file 中。
427
+
428
+ 此方法只提取真正需要链上可组合性的字段:
429
+ - name: Agent 名称(便于链上查询)
430
+ - version: 版本号
431
+
432
+ 其他信息(description, skills, endpoints, tags 等)应通过 token_uri 获取。
433
+
434
+ Args:
435
+ card: agent-card.json 内容
436
+
437
+ Returns:
438
+ metadata 列表,格式为 [{"key": "name", "value": "MyAgent"}, ...]
439
+
440
+ Example:
441
+ >>> with open("agent-card.json") as f:
442
+ ... card = json.load(f)
443
+ >>> metadata = AgentSDK.extract_metadata_from_card(card)
444
+ >>> tx_id = sdk.register_agent(token_uri="https://...", metadata=metadata)
445
+ """
446
+ metadata = []
447
+
448
+ # 只提取最关键的字段用于链上查询
449
+ if card.get("name"):
450
+ metadata.append({"key": "name", "value": card["name"]})
451
+ if card.get("version"):
452
+ metadata.append({"key": "version", "value": card["version"]})
453
+
454
+ return metadata
455
+
456
+ @staticmethod
457
+ def extract_full_metadata_from_card(card: dict) -> list[dict]:
458
+ """
459
+ 从 agent-card.json 提取完整信息作为链上 metadata。
460
+
461
+ 警告:这会将大量数据写入链上,增加 gas 成本。
462
+ 通常不推荐使用,除非有特殊的链上可组合性需求。
463
+
464
+ 根据 ERC-8004 规范,建议使用 token_uri 指向链下 registration file。
465
+
466
+ Args:
467
+ card: agent-card.json 内容
468
+
469
+ Returns:
470
+ metadata 列表
471
+ """
472
+ import json as json_module
473
+ metadata = []
474
+
475
+ # 基础字段
476
+ if card.get("name"):
477
+ metadata.append({"key": "name", "value": card["name"]})
478
+ if card.get("description"):
479
+ metadata.append({"key": "description", "value": card["description"]})
480
+ if card.get("version"):
481
+ metadata.append({"key": "version", "value": card["version"]})
482
+ if card.get("url"):
483
+ metadata.append({"key": "url", "value": card["url"]})
484
+
485
+ # 复杂字段 (JSON 序列化)
486
+ if card.get("skills"):
487
+ skills_summary = [{"id": s.get("id"), "name": s.get("name")} for s in card["skills"]]
488
+ metadata.append({"key": "skills", "value": json_module.dumps(skills_summary, ensure_ascii=False)})
489
+
490
+ if card.get("tags"):
491
+ metadata.append({"key": "tags", "value": json_module.dumps(card["tags"], ensure_ascii=False)})
492
+
493
+ if card.get("endpoints"):
494
+ endpoints_summary = [{"name": e.get("name"), "endpoint": e.get("endpoint")} for e in card["endpoints"]]
495
+ metadata.append({"key": "endpoints", "value": json_module.dumps(endpoints_summary, ensure_ascii=False)})
496
+
497
+ if card.get("capabilities"):
498
+ metadata.append({"key": "capabilities", "value": json_module.dumps(card["capabilities"], ensure_ascii=False)})
499
+
500
+ return metadata
501
+
502
+ def update_metadata(
503
+ self,
504
+ agent_id: int,
505
+ key: str,
506
+ value: str | bytes,
507
+ signer: Optional[Signer] = None,
508
+ ) -> str:
509
+ """
510
+ 更新 Agent 元数据
511
+
512
+ Args:
513
+ agent_id: Agent ID
514
+ key: 元数据键
515
+ value: 元数据值(字符串或字节)
516
+ signer: 自定义签名器(可选)
517
+
518
+ Returns:
519
+ 交易 ID
520
+
521
+ Raises:
522
+ ContractCallError: 合约调用失败
523
+ """
524
+ signer = signer or self.signer
525
+ if signer is None:
526
+ raise SignerNotAvailableError()
527
+
528
+ if isinstance(value, str):
529
+ value = value.encode("utf-8")
530
+ params = [agent_id, key, value]
531
+ logger.debug("update_metadata: agent_id=%d, key=%s", agent_id, key)
532
+ return self.contract_adapter.send("identity", "setMetadata", params, signer)
533
+
534
+ def set_agent_wallet(
535
+ self,
536
+ agent_id: int,
537
+ wallet_address: str,
538
+ deadline: int,
539
+ wallet_signer: Optional[Signer] = None,
540
+ signer: Optional[Signer] = None,
541
+ ) -> str:
542
+ """
543
+ 设置 Agent 钱包地址(需要 EIP-712 签名验证)(Jan 2026 Update)
544
+
545
+ 根据 ERC-8004 规范,agentWallet 是保留字段,设置时需要证明调用者控制该钱包。
546
+ 此方法会自动生成 EIP-712 格式的钱包所有权证明签名。
547
+
548
+ Args:
549
+ agent_id: Agent ID
550
+ wallet_address: 要设置的钱包地址
551
+ deadline: 签名过期时间(Unix 时间戳)
552
+ wallet_signer: 钱包签名器(用于生成所有权证明,默认使用 self.signer)
553
+ signer: 交易签名器(Agent owner,默认使用 self.signer)
554
+
555
+ Returns:
556
+ 交易 ID
557
+
558
+ Raises:
559
+ ContractCallError: 合约调用失败
560
+ SignerNotAvailableError: 签名器不可用
561
+
562
+ Example:
563
+ >>> import time
564
+ >>> deadline = int(time.time()) + 3600 # 1 hour from now
565
+ >>>
566
+ >>> # 设置自己的钱包(signer 同时是 owner 和 wallet)
567
+ >>> tx_id = sdk.set_agent_wallet(
568
+ ... agent_id=1,
569
+ ... wallet_address="TWallet...",
570
+ ... deadline=deadline,
571
+ ... )
572
+ >>>
573
+ >>> # 设置其他钱包(需要该钱包的签名器)
574
+ >>> wallet_signer = TronSigner(private_key="wallet_private_key")
575
+ >>> tx_id = sdk.set_agent_wallet(
576
+ ... agent_id=1,
577
+ ... wallet_address="TWallet...",
578
+ ... deadline=deadline,
579
+ ... wallet_signer=wallet_signer,
580
+ ... )
581
+ """
582
+ signer = signer or self.signer
583
+ if signer is None:
584
+ raise SignerNotAvailableError()
585
+
586
+ wallet_signer = wallet_signer or self.signer
587
+ if wallet_signer is None:
588
+ raise SignerNotAvailableError("Wallet signer required for ownership proof")
589
+
590
+ # 构建 EIP-712 钱包所有权证明签名
591
+ signature = self._build_eip712_wallet_signature(
592
+ agent_id=agent_id,
593
+ wallet_address=wallet_address,
594
+ deadline=deadline,
595
+ wallet_signer=wallet_signer,
596
+ )
597
+
598
+ params = [agent_id, wallet_address, deadline, signature]
599
+ logger.debug("set_agent_wallet: agent_id=%d, wallet=%s, deadline=%d", agent_id, wallet_address[:12], deadline)
600
+ return self.contract_adapter.send("identity", "setAgentWallet", params, signer)
601
+
602
+ def _build_eip712_wallet_signature(
603
+ self,
604
+ agent_id: int,
605
+ wallet_address: str,
606
+ deadline: int,
607
+ wallet_signer: Signer,
608
+ ) -> bytes:
609
+ """
610
+ 构建 EIP-712 钱包所有权证明签名 (Jan 2026 Update)
611
+
612
+ EIP-712 Domain:
613
+ name: "ERC-8004 IdentityRegistry"
614
+ version: "1.1"
615
+ chainId: <chain_id>
616
+ verifyingContract: <identity_registry>
617
+
618
+ TypeHash: SetAgentWallet(uint256 agentId,address newWallet,uint256 deadline)
619
+
620
+ Args:
621
+ agent_id: Agent ID
622
+ wallet_address: 钱包地址
623
+ deadline: 签名过期时间
624
+ wallet_signer: 钱包签名器
625
+
626
+ Returns:
627
+ 签名字节
628
+ """
629
+ chain_id = self.resolve_chain_id()
630
+ if chain_id is None:
631
+ # 默认使用 TRON Nile testnet chain ID
632
+ chain_id = 3448148188
633
+ logger.warning("Could not resolve chain ID, using default: %d", chain_id)
634
+
635
+ identity_registry = self.config.identity_registry or ""
636
+
637
+ # EIP-712 Domain Separator
638
+ domain_type_hash = keccak256_bytes(
639
+ b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
640
+ )
641
+ domain_separator = keccak256_bytes(b"".join([
642
+ domain_type_hash,
643
+ keccak256_bytes(b"ERC-8004 IdentityRegistry"),
644
+ keccak256_bytes(b"1.1"),
645
+ self._abi_encode_uint(chain_id),
646
+ self._abi_encode_address(identity_registry),
647
+ ]))
648
+
649
+ # SetAgentWallet struct hash
650
+ set_agent_wallet_typehash = keccak256_bytes(
651
+ b"SetAgentWallet(uint256 agentId,address newWallet,uint256 deadline)"
652
+ )
653
+ struct_hash = keccak256_bytes(b"".join([
654
+ set_agent_wallet_typehash,
655
+ self._abi_encode_uint(agent_id),
656
+ self._abi_encode_address(wallet_address),
657
+ self._abi_encode_uint(deadline),
658
+ ]))
659
+
660
+ # EIP-712 digest
661
+ digest = keccak256_bytes(
662
+ b"\x19\x01" + domain_separator + struct_hash
663
+ )
664
+
665
+ # Sign the digest
666
+ signature = self._normalize_bytes(wallet_signer.sign_message(digest))
667
+
668
+ # 规范化签名(处理 v 值)
669
+ if len(signature) == 65:
670
+ v = signature[-1]
671
+ if v in (0, 1):
672
+ v += 27
673
+ signature = signature[:64] + bytes([v])
674
+
675
+ return signature
676
+
677
+ def set_agent_uri(
678
+ self,
679
+ agent_id: int,
680
+ new_uri: str,
681
+ signer: Optional[Signer] = None,
682
+ ) -> str:
683
+ """
684
+ 更新 Agent 的 URI (Jan 2026 Update)
685
+
686
+ 更新 Agent 的 registration file URI。只有 owner 或 approved operator 可以调用。
687
+
688
+ Args:
689
+ agent_id: Agent ID
690
+ new_uri: 新的 URI
691
+ signer: 自定义签名器(可选)
692
+
693
+ Returns:
694
+ 交易 ID
695
+
696
+ Raises:
697
+ ContractCallError: 合约调用失败
698
+ SignerNotAvailableError: 签名器不可用
699
+
700
+ Example:
701
+ >>> tx_id = sdk.set_agent_uri(
702
+ ... agent_id=1,
703
+ ... new_uri="https://example.com/new-agent.json",
704
+ ... )
705
+ """
706
+ signer = signer or self.signer
707
+ if signer is None:
708
+ raise SignerNotAvailableError()
709
+
710
+ params = [agent_id, new_uri]
711
+ logger.debug("set_agent_uri: agent_id=%d, uri=%s", agent_id, new_uri[:50])
712
+ return self.contract_adapter.send("identity", "setAgentURI", params, signer)
713
+
714
+ # ==================== Identity Registry 只读方法 ====================
715
+
716
+ def get_agent_uri(self, agent_id: int) -> str:
717
+ """
718
+ 获取 Agent 的 tokenURI
719
+
720
+ Args:
721
+ agent_id: Agent ID
722
+
723
+ Returns:
724
+ Agent 的 tokenURI(指向 registration file)
725
+
726
+ Example:
727
+ >>> uri = sdk.get_agent_uri(1)
728
+ >>> print(uri) # "https://example.com/agent.json"
729
+ """
730
+ params = [agent_id]
731
+ return self.contract_adapter.call("identity", "tokenURI", params)
732
+
733
+ def get_metadata(self, agent_id: int, key: str) -> bytes:
734
+ """
735
+ 获取 Agent 的链上 metadata
736
+
737
+ Args:
738
+ agent_id: Agent ID
739
+ key: metadata 键名
740
+
741
+ Returns:
742
+ metadata 值(bytes)
743
+
744
+ Example:
745
+ >>> name = sdk.get_metadata(1, "name")
746
+ >>> print(name.decode("utf-8")) # "MyAgent"
747
+ """
748
+ params = [agent_id, key]
749
+ return self.contract_adapter.call("identity", "getMetadata", params)
750
+
751
+ def agent_exists(self, agent_id: int) -> bool:
752
+ """
753
+ 检查 Agent 是否存在
754
+
755
+ Args:
756
+ agent_id: Agent ID
757
+
758
+ Returns:
759
+ 是否存在
760
+ """
761
+ params = [agent_id]
762
+ return self.contract_adapter.call("identity", "agentExists", params)
763
+
764
+ def get_agent_owner(self, agent_id: int) -> str:
765
+ """
766
+ 获取 Agent 的所有者地址
767
+
768
+ Args:
769
+ agent_id: Agent ID
770
+
771
+ Returns:
772
+ 所有者地址
773
+ """
774
+ params = [agent_id]
775
+ return self.contract_adapter.call("identity", "ownerOf", params)
776
+
777
+ def total_agents(self) -> int:
778
+ """
779
+ 获取已注册的 Agent 总数
780
+
781
+ Returns:
782
+ Agent 总数
783
+ """
784
+ return self.contract_adapter.call("identity", "totalAgents", [])
785
+
786
+ def get_agent_wallet(self, agent_id: int) -> str:
787
+ """
788
+ 获取 Agent 的钱包地址
789
+
790
+ Args:
791
+ agent_id: Agent ID
792
+
793
+ Returns:
794
+ 钱包地址(如果未设置返回零地址)
795
+
796
+ Example:
797
+ >>> wallet = sdk.get_agent_wallet(1)
798
+ >>> print(wallet) # "TWallet..."
799
+ """
800
+ params = [agent_id]
801
+ return self.contract_adapter.call("identity", "getAgentWallet", params)
802
+
803
+ # ==================== Validation Registry 只读方法 ====================
804
+
805
+ def get_validation_status(self, request_hash: str) -> dict:
806
+ """
807
+ 获取验证状态 (Jan 2026 Update)
808
+
809
+ Args:
810
+ request_hash: 验证请求哈希(32 bytes)
811
+
812
+ Returns:
813
+ 验证结果字典,包含:
814
+ - validatorAddress: 验证者地址 (address(0) if no response yet)
815
+ - agentId: Agent ID (0 if no response yet)
816
+ - response: 验证评分 (0-100, or 0 if no response yet)
817
+ - tag: 标签 (string)
818
+ - lastUpdate: 最后更新时间戳 (0 if no response yet)
819
+
820
+ Note:
821
+ 返回默认值表示请求待处理(无响应),不会抛出异常。
822
+ 要区分不存在的请求和待处理的请求,请使用 request_exists()。
823
+
824
+ Example:
825
+ >>> result = sdk.get_validation_status("0x" + "aa" * 32)
826
+ >>> print(result["response"]) # 100
827
+ """
828
+ params = [self._normalize_bytes32(request_hash)]
829
+ result = self.contract_adapter.call("validation", "getValidationStatus", params)
830
+ if isinstance(result, (list, tuple)) and len(result) >= 5:
831
+ return {
832
+ "validatorAddress": result[0],
833
+ "agentId": result[1],
834
+ "response": result[2],
835
+ "tag": result[3],
836
+ "lastUpdate": result[4],
837
+ }
838
+ return result
839
+
840
+ def get_validation(self, request_hash: str) -> dict:
841
+ """
842
+ 获取验证结果 (已弃用,请使用 get_validation_status)
843
+
844
+ Args:
845
+ request_hash: 验证请求哈希(32 bytes)
846
+
847
+ Returns:
848
+ 验证结果字典
849
+ """
850
+ logger.warning("get_validation() is deprecated, use get_validation_status() instead")
851
+ return self.get_validation_status(request_hash)
852
+
853
+ def request_exists(self, request_hash: str) -> bool:
854
+ """
855
+ 检查验证请求是否存在 (Jan 2026 Update)
856
+
857
+ Args:
858
+ request_hash: 验证请求哈希(32 bytes)
859
+
860
+ Returns:
861
+ 是否存在
862
+
863
+ Example:
864
+ >>> exists = sdk.request_exists("0x" + "aa" * 32)
865
+ """
866
+ params = [self._normalize_bytes32(request_hash)]
867
+ return self.contract_adapter.call("validation", "requestExists", params)
868
+
869
+ def get_validation_request(self, request_hash: str) -> dict:
870
+ """
871
+ 获取验证请求详情 (Jan 2026 Update)
872
+
873
+ Args:
874
+ request_hash: 验证请求哈希(32 bytes)
875
+
876
+ Returns:
877
+ 请求详情字典,包含:
878
+ - validatorAddress: 验证者地址
879
+ - agentId: Agent ID
880
+ - requestURI: 请求 URI
881
+ - timestamp: 请求时间戳
882
+
883
+ Example:
884
+ >>> request = sdk.get_validation_request("0x" + "aa" * 32)
885
+ >>> print(request["requestURI"])
886
+ """
887
+ params = [self._normalize_bytes32(request_hash)]
888
+ result = self.contract_adapter.call("validation", "getRequest", params)
889
+ if isinstance(result, (list, tuple)) and len(result) >= 4:
890
+ return {
891
+ "validatorAddress": result[0],
892
+ "agentId": result[1],
893
+ "requestURI": result[2],
894
+ "timestamp": result[3],
895
+ }
896
+ return result
897
+
898
+ def get_validation_summary(
899
+ self,
900
+ agent_id: int,
901
+ validator_addresses: Optional[list[str]] = None,
902
+ tag: str = "",
903
+ ) -> dict:
904
+ """
905
+ 获取 Agent 的验证汇总 (Jan 2026 Update)
906
+
907
+ Args:
908
+ agent_id: Agent ID
909
+ validator_addresses: 验证者地址列表(可选,用于过滤)
910
+ tag: 标签(可选)
911
+
912
+ Returns:
913
+ 汇总结果字典,包含:
914
+ - count: 验证数量
915
+ - averageResponse: 平均评分
916
+
917
+ Example:
918
+ >>> summary = sdk.get_validation_summary(1)
919
+ >>> print(f"Count: {summary['count']}, Avg: {summary['averageResponse']}")
920
+ """
921
+ params = [agent_id, validator_addresses or [], tag]
922
+ result = self.contract_adapter.call("validation", "getSummary", params)
923
+ if isinstance(result, (list, tuple)) and len(result) >= 2:
924
+ return {
925
+ "count": result[0],
926
+ "averageResponse": result[1],
927
+ }
928
+ return result
929
+
930
+ def get_agent_validations(self, agent_id: int) -> list[str]:
931
+ """
932
+ 获取 Agent 的所有验证请求哈希 (Jan 2026 Update)
933
+
934
+ Args:
935
+ agent_id: Agent ID
936
+
937
+ Returns:
938
+ 请求哈希列表
939
+ """
940
+ params = [agent_id]
941
+ return self.contract_adapter.call("validation", "getAgentValidations", params)
942
+
943
+ def get_validator_requests(self, validator_address: str) -> list[str]:
944
+ """
945
+ 获取验证者的所有验证请求哈希 (Jan 2026 Update)
946
+
947
+ Args:
948
+ validator_address: 验证者地址
949
+
950
+ Returns:
951
+ 请求哈希列表
952
+ """
953
+ params = [validator_address]
954
+ return self.contract_adapter.call("validation", "getValidatorRequests", params)
955
+
956
+ # ==================== Reputation Registry 只读方法 ====================
957
+
958
+ def get_feedback_summary(
959
+ self,
960
+ agent_id: int,
961
+ client_addresses: Optional[list[str]] = None,
962
+ tag1: str = "",
963
+ tag2: str = "",
964
+ ) -> dict:
965
+ """
966
+ 获取 Agent 的反馈汇总
967
+
968
+ Args:
969
+ agent_id: Agent ID
970
+ client_addresses: 客户端地址列表(可选,用于过滤)
971
+ tag1: 标签1(可选)
972
+ tag2: 标签2(可选)
973
+
974
+ Returns:
975
+ 汇总结果字典,包含:
976
+ - count: 反馈数量
977
+ - averageScore: 平均评分
978
+
979
+ Example:
980
+ >>> summary = sdk.get_feedback_summary(1)
981
+ >>> print(f"Count: {summary['count']}, Avg: {summary['averageScore']}")
982
+ """
983
+ params = [agent_id, client_addresses or [], tag1, tag2]
984
+ result = self.contract_adapter.call("reputation", "getSummary", params)
985
+ if isinstance(result, (list, tuple)) and len(result) >= 2:
986
+ return {
987
+ "count": result[0],
988
+ "averageScore": result[1],
989
+ }
990
+ return result
991
+
992
+ def read_feedback(
993
+ self,
994
+ agent_id: int,
995
+ client_address: str,
996
+ feedback_index: int,
997
+ ) -> dict:
998
+ """
999
+ 读取单条反馈
1000
+
1001
+ Args:
1002
+ agent_id: Agent ID
1003
+ client_address: 客户端地址
1004
+ feedback_index: 反馈索引
1005
+
1006
+ Returns:
1007
+ 反馈详情字典,包含:
1008
+ - score: 评分 (0-100)
1009
+ - tag1: 标签1
1010
+ - tag2: 标签2
1011
+ - isRevoked: 是否已撤销
1012
+
1013
+ Example:
1014
+ >>> feedback = sdk.read_feedback(1, "TClient...", 0)
1015
+ >>> print(f"Score: {feedback['score']}")
1016
+ """
1017
+ params = [agent_id, client_address, feedback_index]
1018
+ result = self.contract_adapter.call("reputation", "readFeedback", params)
1019
+ if isinstance(result, (list, tuple)) and len(result) >= 4:
1020
+ return {
1021
+ "score": result[0],
1022
+ "tag1": result[1],
1023
+ "tag2": result[2],
1024
+ "isRevoked": result[3],
1025
+ }
1026
+ return result
1027
+
1028
+ def get_feedback_clients(self, agent_id: int) -> list[str]:
1029
+ """
1030
+ 获取给 Agent 提交过反馈的所有客户端地址
1031
+
1032
+ Args:
1033
+ agent_id: Agent ID
1034
+
1035
+ Returns:
1036
+ 客户端地址列表
1037
+ """
1038
+ params = [agent_id]
1039
+ return self.contract_adapter.call("reputation", "getClients", params)
1040
+
1041
+ def get_last_feedback_index(self, agent_id: int, client_address: str) -> int:
1042
+ """
1043
+ 获取客户端对 Agent 的最后一条反馈索引
1044
+
1045
+ Args:
1046
+ agent_id: Agent ID
1047
+ client_address: 客户端地址
1048
+
1049
+ Returns:
1050
+ 最后一条反馈的索引
1051
+ """
1052
+ params = [agent_id, client_address]
1053
+ return self.contract_adapter.call("reputation", "getLastIndex", params)
1054
+
1055
+ # ==================== Reputation Registry 写入方法 ====================
1056
+
1057
+ def revoke_feedback(
1058
+ self,
1059
+ agent_id: int,
1060
+ feedback_index: int,
1061
+ signer: Optional[Signer] = None,
1062
+ ) -> str:
1063
+ """
1064
+ 撤销反馈
1065
+
1066
+ 只有原始提交者可以撤销自己的反馈。
1067
+
1068
+ Args:
1069
+ agent_id: Agent ID
1070
+ feedback_index: 反馈索引
1071
+ signer: 自定义签名器(可选)
1072
+
1073
+ Returns:
1074
+ 交易 ID
1075
+
1076
+ Example:
1077
+ >>> tx_id = sdk.revoke_feedback(agent_id=1, feedback_index=0)
1078
+ """
1079
+ signer = signer or self.signer
1080
+ if signer is None:
1081
+ raise SignerNotAvailableError()
1082
+
1083
+ params = [agent_id, feedback_index]
1084
+ logger.debug("revoke_feedback: agent_id=%d, index=%d", agent_id, feedback_index)
1085
+ return self.contract_adapter.send("reputation", "revokeFeedback", params, signer)
1086
+
1087
+ def append_feedback_response(
1088
+ self,
1089
+ agent_id: int,
1090
+ client_address: str,
1091
+ feedback_index: int,
1092
+ response_uri: str,
1093
+ response_hash: Optional[str] = None,
1094
+ signer: Optional[Signer] = None,
1095
+ ) -> str:
1096
+ """
1097
+ 追加反馈响应
1098
+
1099
+ 任何人都可以追加响应(如 Agent 展示退款证明,或数据分析服务标记垃圾反馈)。
1100
+
1101
+ Args:
1102
+ agent_id: Agent ID
1103
+ client_address: 原始反馈的客户端地址
1104
+ feedback_index: 反馈索引
1105
+ response_uri: 响应文件 URI
1106
+ response_hash: 响应文件哈希(可选,IPFS URI 不需要)
1107
+ signer: 自定义签名器(可选)
1108
+
1109
+ Returns:
1110
+ 交易 ID
1111
+
1112
+ Example:
1113
+ >>> tx_id = sdk.append_feedback_response(
1114
+ ... agent_id=1,
1115
+ ... client_address="TClient...",
1116
+ ... feedback_index=0,
1117
+ ... response_uri="ipfs://Qm...",
1118
+ ... )
1119
+ """
1120
+ signer = signer or self.signer
1121
+ if signer is None:
1122
+ raise SignerNotAvailableError()
1123
+
1124
+ params = [
1125
+ agent_id,
1126
+ client_address,
1127
+ feedback_index,
1128
+ response_uri,
1129
+ self._normalize_bytes32(response_hash),
1130
+ ]
1131
+ logger.debug("append_feedback_response: agent_id=%d, index=%d", agent_id, feedback_index)
1132
+ return self.contract_adapter.send("reputation", "appendResponse", params, signer)
1133
+
1134
+ def build_feedback_auth(
1135
+ self,
1136
+ agent_id: int,
1137
+ client_addr: str,
1138
+ index_limit: int,
1139
+ expiry: int,
1140
+ chain_id: Optional[int],
1141
+ identity_registry: str,
1142
+ signer: Optional[Signer] = None,
1143
+ ) -> str:
1144
+ """
1145
+ 构建反馈授权签名 (已弃用 - Jan 2026 Update)
1146
+
1147
+ 警告:Jan 2026 更新移除了 feedbackAuth 预授权机制。
1148
+ 现在任何人都可以直接调用 giveFeedback() 提交反馈,无需预授权。
1149
+ 此方法保留仅为向后兼容,将在未来版本中移除。
1150
+
1151
+ Args:
1152
+ agent_id: Agent ID
1153
+ client_addr: 被授权的客户端地址
1154
+ index_limit: 反馈索引上限
1155
+ expiry: 授权过期时间(Unix 时间戳)
1156
+ chain_id: 链 ID(可选,会自动解析)
1157
+ identity_registry: IdentityRegistry 合约地址
1158
+ signer: 自定义签名器(可选)
1159
+
1160
+ Returns:
1161
+ 反馈授权签名(0x 前缀的十六进制字符串)
1162
+
1163
+ Raises:
1164
+ DeprecationWarning: 此方法已弃用
1165
+ """
1166
+ import warnings
1167
+ warnings.warn(
1168
+ "build_feedback_auth() is deprecated since Jan 2026 Update. "
1169
+ "feedbackAuth pre-authorization has been removed from the contract. "
1170
+ "Use submit_reputation() directly without feedbackAuth.",
1171
+ DeprecationWarning,
1172
+ stacklevel=2,
1173
+ )
1174
+
1175
+ signer = signer or self.signer
1176
+ if signer is None:
1177
+ raise SignerNotAvailableError()
1178
+
1179
+ if chain_id is None:
1180
+ chain_id = self.resolve_chain_id()
1181
+ if chain_id is None:
1182
+ raise ChainIdResolutionError(self.config.rpc_url)
1183
+
1184
+ signer_addr = signer.get_address()
1185
+
1186
+ # 构建 feedbackAuth 结构体 (legacy format)
1187
+ struct_bytes = b"".join(
1188
+ [
1189
+ self._abi_encode_uint(agent_id),
1190
+ self._abi_encode_address(client_addr),
1191
+ self._abi_encode_uint(index_limit),
1192
+ self._abi_encode_uint(expiry),
1193
+ self._abi_encode_uint(chain_id),
1194
+ self._abi_encode_address(identity_registry),
1195
+ self._abi_encode_address(signer_addr),
1196
+ ]
1197
+ )
1198
+
1199
+ # EIP-191 签名
1200
+ struct_hash = keccak256_bytes(struct_bytes)
1201
+ message = keccak256_bytes(b"\x19Ethereum Signed Message:\n32" + struct_hash)
1202
+ signature = self._normalize_bytes(signer.sign_message(message))
1203
+
1204
+ # 规范化签名(处理 v 值和 s 值)
1205
+ if len(signature) == 65:
1206
+ v = signature[-1]
1207
+ if v in (0, 1):
1208
+ v += 27
1209
+ r = int.from_bytes(signature[:32], byteorder="big")
1210
+ s = int.from_bytes(signature[32:64], byteorder="big")
1211
+ secp256k1_n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
1212
+ if s > secp256k1_n // 2:
1213
+ s = secp256k1_n - s
1214
+ v = 27 if v == 28 else 28
1215
+ signature = (
1216
+ r.to_bytes(32, byteorder="big")
1217
+ + s.to_bytes(32, byteorder="big")
1218
+ + bytes([v])
1219
+ )
1220
+
1221
+ logger.debug("build_feedback_auth (DEPRECATED): agent_id=%d, client=%s", agent_id, client_addr[:12])
1222
+ return "0x" + (struct_bytes + signature).hex()
1223
+
1224
+ @staticmethod
1225
+ def _normalize_metadata_entries(entries: list[dict]) -> list[tuple]:
1226
+ """
1227
+ 规范化元数据条目为 tuple 格式 (Jan 2026 Update)
1228
+
1229
+ 合约期望的格式是 (string metadataKey, bytes metadataValue) 的 tuple 数组
1230
+
1231
+ 注意:Jan 2026 更新将 struct 字段名从 (key, value) 改为 (metadataKey, metadataValue)
1232
+ """
1233
+ if not isinstance(entries, list):
1234
+ raise TypeError("metadata must be a list of {key,value} objects")
1235
+ normalized = []
1236
+ for entry in entries:
1237
+ if not isinstance(entry, dict):
1238
+ raise TypeError("metadata entry must be an object")
1239
+ # 支持新旧两种字段名
1240
+ key = entry.get("metadataKey") or entry.get("key")
1241
+ value = entry.get("metadataValue") or entry.get("value")
1242
+ if not key:
1243
+ raise ValueError("metadata entry missing key (metadataKey or key)")
1244
+ if isinstance(value, bytes):
1245
+ value_bytes = value
1246
+ elif isinstance(value, str):
1247
+ if value.startswith("0x") and _is_hex_string(value[2:]):
1248
+ value_bytes = bytes.fromhex(value[2:])
1249
+ else:
1250
+ value_bytes = value.encode("utf-8")
1251
+ elif value is None:
1252
+ value_bytes = b""
1253
+ else:
1254
+ raise TypeError("metadata value must be bytes or string")
1255
+ # 返回 tuple 格式,符合 Solidity struct 编码要求
1256
+ # 字段名为 (metadataKey, metadataValue) 但 tuple 编码只需要值
1257
+ normalized.append((key, value_bytes))
1258
+ return normalized
1259
+
1260
+ def resolve_chain_id(self) -> Optional[int]:
1261
+ """
1262
+ 从 RPC 节点解析 Chain ID
1263
+
1264
+ Returns:
1265
+ Chain ID,解析失败返回 None
1266
+ """
1267
+ rpc_url = self.config.rpc_url
1268
+ if not rpc_url:
1269
+ return None
1270
+ url = rpc_url.rstrip("/") + "/jsonrpc"
1271
+ try:
1272
+ response = httpx.post(
1273
+ url,
1274
+ json={"jsonrpc": "2.0", "method": "eth_chainId", "params": [], "id": 1},
1275
+ timeout=self.config.timeout,
1276
+ )
1277
+ response.raise_for_status()
1278
+ result = response.json().get("result")
1279
+ if isinstance(result, str) and result.startswith("0x"):
1280
+ return int(result, 16)
1281
+ except Exception as e:
1282
+ logger.warning("Failed to resolve chain ID: %s", e)
1283
+ return None
1284
+ return None
1285
+
1286
+ def build_commitment(self, order_params: dict) -> str:
1287
+ """
1288
+ 构建订单承诺哈希
1289
+
1290
+ 对订单参数进行规范化 JSON 序列化后计算 keccak256 哈希。
1291
+
1292
+ Args:
1293
+ order_params: 订单参数字典
1294
+
1295
+ Returns:
1296
+ 承诺哈希(0x 前缀)
1297
+
1298
+ Example:
1299
+ >>> commitment = sdk.build_commitment({
1300
+ ... "asset": "TRX/USDT",
1301
+ ... "amount": 100.0,
1302
+ ... "slippage": 0.01,
1303
+ ... })
1304
+ """
1305
+ payload = canonical_json(order_params)
1306
+ return keccak256_hex(payload)
1307
+
1308
+ def compute_request_hash(self, request_payload: str | dict) -> str:
1309
+ """
1310
+ 计算请求数据哈希
1311
+
1312
+ Args:
1313
+ request_payload: 请求数据(字典或 JSON 字符串)
1314
+
1315
+ Returns:
1316
+ 请求哈希(0x 前缀)
1317
+ """
1318
+ if isinstance(request_payload, dict):
1319
+ payload_bytes = canonical_json(request_payload)
1320
+ else:
1321
+ payload_bytes = str(request_payload).encode("utf-8")
1322
+ return keccak256_hex(payload_bytes)
1323
+
1324
+ def dump_canonical(self, payload: dict) -> str:
1325
+ """
1326
+ 规范化 JSON 序列化
1327
+
1328
+ Args:
1329
+ payload: 待序列化的字典
1330
+
1331
+ Returns:
1332
+ 规范化的 JSON 字符串(键排序,无空格)
1333
+ """
1334
+ return canonical_json_str(payload)
1335
+
1336
+ def build_a2a_signature(
1337
+ self,
1338
+ action_commitment: str,
1339
+ timestamp: int,
1340
+ caller_address: str,
1341
+ signer: Optional[Signer] = None,
1342
+ ) -> str:
1343
+ """
1344
+ 构建 A2A 请求签名
1345
+
1346
+ Args:
1347
+ action_commitment: 操作承诺哈希
1348
+ timestamp: 时间戳
1349
+ caller_address: 调用方地址
1350
+ signer: 自定义签名器(可选)
1351
+
1352
+ Returns:
1353
+ 签名(0x 前缀)
1354
+ """
1355
+ signer = signer or self.signer
1356
+ if signer is None:
1357
+ raise SignerNotAvailableError()
1358
+
1359
+ payload = {
1360
+ "actionCommitment": action_commitment,
1361
+ "timestamp": timestamp,
1362
+ "callerAddress": caller_address,
1363
+ }
1364
+ message = keccak256_bytes(canonical_json(payload))
1365
+ return signer.sign_message(message)
1366
+
1367
+ def build_market_order_quote_request(self, asset: str, amount: float, slippage: float = 0.01) -> dict:
1368
+ """
1369
+ 构建市价单报价请求
1370
+
1371
+ Args:
1372
+ asset: 交易对(如 "TRX/USDT")
1373
+ amount: 交易数量
1374
+ slippage: 滑点容忍度(默认 1%)
1375
+
1376
+ Returns:
1377
+ 报价请求字典
1378
+ """
1379
+ return {
1380
+ "asset": asset,
1381
+ "amount": amount,
1382
+ "slippage": slippage,
1383
+ }
1384
+
1385
+ def build_market_order_new_request(
1386
+ self,
1387
+ asset: str,
1388
+ amount: float,
1389
+ payment_tx_hash: str,
1390
+ slippage: float = 0.01,
1391
+ ) -> dict:
1392
+ """
1393
+ 构建新建市价单请求
1394
+
1395
+ Args:
1396
+ asset: 交易对
1397
+ amount: 交易数量
1398
+ payment_tx_hash: 支付交易哈希
1399
+ slippage: 滑点容忍度
1400
+
1401
+ Returns:
1402
+ 新建订单请求字典
1403
+ """
1404
+ return {
1405
+ "asset": asset,
1406
+ "amount": amount,
1407
+ "slippage": slippage,
1408
+ "paymentTxHash": payment_tx_hash,
1409
+ }
1410
+
1411
+ def build_x402_quote_request(self, order_params: dict) -> dict:
1412
+ """
1413
+ 构建 X402 报价请求
1414
+
1415
+ Args:
1416
+ order_params: 订单参数
1417
+
1418
+ Returns:
1419
+ X402 报价请求字典
1420
+ """
1421
+ return {"orderParams": order_params}
1422
+
1423
+ def build_x402_execute_request(
1424
+ self,
1425
+ action_commitment: str,
1426
+ order_params: dict,
1427
+ payment_tx_hash: str,
1428
+ timestamp: int,
1429
+ caller_address: str,
1430
+ include_signature: bool = True,
1431
+ ) -> dict:
1432
+ """
1433
+ 构建 X402 执行请求
1434
+
1435
+ Args:
1436
+ action_commitment: 操作承诺哈希
1437
+ order_params: 订单参数
1438
+ payment_tx_hash: 支付交易哈希
1439
+ timestamp: 时间戳
1440
+ caller_address: 调用方地址
1441
+ include_signature: 是否包含签名
1442
+
1443
+ Returns:
1444
+ X402 执行请求字典
1445
+ """
1446
+ payload = {
1447
+ "actionCommitment": action_commitment,
1448
+ "orderParams": order_params,
1449
+ "paymentTxHash": payment_tx_hash,
1450
+ "timestamp": timestamp,
1451
+ }
1452
+ if include_signature:
1453
+ payload["signature"] = self.build_a2a_signature(
1454
+ action_commitment, timestamp, caller_address
1455
+ )
1456
+ return payload
1457
+
1458
+ def build_payment_signature(
1459
+ self,
1460
+ action_commitment: str,
1461
+ payment_address: str,
1462
+ amount: str,
1463
+ timestamp: int,
1464
+ signer: Optional[Signer] = None,
1465
+ ) -> str:
1466
+ """
1467
+ 构建支付签名
1468
+
1469
+ Args:
1470
+ action_commitment: 操作承诺哈希
1471
+ payment_address: 收款地址
1472
+ amount: 支付金额
1473
+ timestamp: 时间戳
1474
+ signer: 自定义签名器(可选)
1475
+
1476
+ Returns:
1477
+ 支付签名(0x 前缀)
1478
+ """
1479
+ signer = signer or self.signer
1480
+ if signer is None:
1481
+ raise SignerNotAvailableError()
1482
+
1483
+ payload = {
1484
+ "actionCommitment": action_commitment,
1485
+ "paymentAddress": payment_address,
1486
+ "amount": amount,
1487
+ "timestamp": timestamp,
1488
+ }
1489
+ message = keccak256_bytes(canonical_json(payload))
1490
+ return signer.sign_message(message)
1491
+
1492
+ @staticmethod
1493
+ def _normalize_bytes32(value: Optional[str | bytes]) -> bytes:
1494
+ """规范化为 32 字节"""
1495
+ if value is None:
1496
+ return b"\x00" * 32
1497
+ if isinstance(value, bytes):
1498
+ if len(value) < 32:
1499
+ return value.ljust(32, b"\x00")
1500
+ return value[:32]
1501
+ cleaned = value[2:] if value.startswith("0x") else value
1502
+ if not cleaned:
1503
+ return b"\x00" * 32
1504
+ raw = bytes.fromhex(cleaned)
1505
+ if len(raw) < 32:
1506
+ return raw.ljust(32, b"\x00")
1507
+ return raw[:32]
1508
+
1509
+ @staticmethod
1510
+ def _normalize_bytes(value: Optional[str | bytes]) -> bytes:
1511
+ """规范化为字节"""
1512
+ if value is None:
1513
+ return b""
1514
+ if isinstance(value, bytes):
1515
+ return value
1516
+ cleaned = value[2:] if value.startswith("0x") else value
1517
+ if not cleaned:
1518
+ return b""
1519
+ return bytes.fromhex(cleaned)
1520
+
1521
+ @staticmethod
1522
+ def _abi_encode_uint(value: int) -> bytes:
1523
+ """ABI 编码无符号整数(32 字节)"""
1524
+ return int(value).to_bytes(32, byteorder="big")
1525
+
1526
+ @staticmethod
1527
+ def _abi_encode_address(address: str) -> bytes:
1528
+ """
1529
+ ABI 编码地址(32 字节,左填充零)
1530
+
1531
+ 支持 TRON base58 地址和 EVM hex 地址。
1532
+
1533
+ Raises:
1534
+ InvalidAddressError: 地址格式无效
1535
+ """
1536
+ addr = address
1537
+ if addr.startswith("T"):
1538
+ try:
1539
+ from tronpy.keys import to_hex_address
1540
+ except Exception as exc:
1541
+ raise InvalidAddressError(address, "tronpy required for base58") from exc
1542
+ addr = to_hex_address(addr)
1543
+ if addr.startswith("0x"):
1544
+ addr = addr[2:]
1545
+ if len(addr) == 42 and addr.startswith("41"):
1546
+ addr = addr[2:]
1547
+ if len(addr) != 40:
1548
+ raise InvalidAddressError(address, "expected 20 bytes hex")
1549
+ return bytes.fromhex(addr).rjust(32, b"\x00")