zexus 1.7.1 → 1.7.2

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 (159) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/src/__init__.py +7 -0
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
  6. package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
  7. package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
  8. package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
  9. package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
  10. package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
  11. package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
  12. package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
  13. package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
  14. package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
  15. package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
  16. package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
  17. package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
  18. package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  19. package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
  20. package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
  22. package/src/zexus/advanced_types.py +17 -2
  23. package/src/zexus/blockchain/__init__.py +411 -0
  24. package/src/zexus/blockchain/accelerator.py +1160 -0
  25. package/src/zexus/blockchain/chain.py +660 -0
  26. package/src/zexus/blockchain/consensus.py +821 -0
  27. package/src/zexus/blockchain/contract_vm.py +1019 -0
  28. package/src/zexus/blockchain/crypto.py +79 -14
  29. package/src/zexus/blockchain/events.py +526 -0
  30. package/src/zexus/blockchain/loadtest.py +721 -0
  31. package/src/zexus/blockchain/monitoring.py +350 -0
  32. package/src/zexus/blockchain/mpt.py +716 -0
  33. package/src/zexus/blockchain/multichain.py +951 -0
  34. package/src/zexus/blockchain/multiprocess_executor.py +338 -0
  35. package/src/zexus/blockchain/network.py +886 -0
  36. package/src/zexus/blockchain/node.py +666 -0
  37. package/src/zexus/blockchain/rpc.py +1203 -0
  38. package/src/zexus/blockchain/rust_bridge.py +421 -0
  39. package/src/zexus/blockchain/storage.py +423 -0
  40. package/src/zexus/blockchain/tokens.py +750 -0
  41. package/src/zexus/blockchain/upgradeable.py +1004 -0
  42. package/src/zexus/blockchain/verification.py +1602 -0
  43. package/src/zexus/blockchain/wallet.py +621 -0
  44. package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
  45. package/src/zexus/cli/main.py +300 -20
  46. package/src/zexus/cli/zpm.py +1 -1
  47. package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
  48. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  49. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  50. package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
  51. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  52. package/src/zexus/compiler/lexer.py +10 -5
  53. package/src/zexus/concurrency_system.py +79 -0
  54. package/src/zexus/config.py +54 -0
  55. package/src/zexus/crypto_bridge.py +244 -8
  56. package/src/zexus/dap/__init__.py +10 -0
  57. package/src/zexus/dap/__main__.py +4 -0
  58. package/src/zexus/dap/dap_server.py +391 -0
  59. package/src/zexus/dap/debug_engine.py +298 -0
  60. package/src/zexus/environment.py +10 -1
  61. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  62. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  63. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  64. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  65. package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
  66. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  67. package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
  68. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  69. package/src/zexus/evaluator/bytecode_compiler.py +441 -37
  70. package/src/zexus/evaluator/core.py +560 -49
  71. package/src/zexus/evaluator/expressions.py +122 -49
  72. package/src/zexus/evaluator/functions.py +417 -16
  73. package/src/zexus/evaluator/statements.py +521 -118
  74. package/src/zexus/evaluator/unified_execution.py +573 -72
  75. package/src/zexus/evaluator/utils.py +14 -2
  76. package/src/zexus/event_loop.py +186 -0
  77. package/src/zexus/lexer.py +742 -486
  78. package/src/zexus/lsp/__init__.py +1 -1
  79. package/src/zexus/lsp/definition_provider.py +163 -9
  80. package/src/zexus/lsp/server.py +22 -8
  81. package/src/zexus/lsp/symbol_provider.py +182 -9
  82. package/src/zexus/module_cache.py +237 -9
  83. package/src/zexus/object.py +64 -6
  84. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  85. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  86. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  87. package/src/zexus/parser/parser.py +786 -285
  88. package/src/zexus/parser/strategy_context.py +407 -66
  89. package/src/zexus/parser/strategy_structural.py +117 -19
  90. package/src/zexus/persistence.py +15 -1
  91. package/src/zexus/renderer/__init__.py +15 -0
  92. package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
  93. package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
  94. package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
  95. package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
  96. package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
  97. package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
  98. package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
  99. package/src/zexus/renderer/tk_backend.py +208 -0
  100. package/src/zexus/renderer/web_backend.py +260 -0
  101. package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
  103. package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
  104. package/src/zexus/runtime/file_flags.py +137 -0
  105. package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
  106. package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
  107. package/src/zexus/security.py +424 -34
  108. package/src/zexus/stdlib/fs.py +23 -18
  109. package/src/zexus/stdlib/http.py +289 -186
  110. package/src/zexus/stdlib/sockets.py +207 -163
  111. package/src/zexus/stdlib/websockets.py +282 -0
  112. package/src/zexus/stdlib_integration.py +369 -2
  113. package/src/zexus/strategy_recovery.py +6 -3
  114. package/src/zexus/type_checker.py +423 -0
  115. package/src/zexus/virtual_filesystem.py +189 -2
  116. package/src/zexus/vm/__init__.py +113 -3
  117. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  118. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  119. package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
  120. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  121. package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
  122. package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
  123. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  124. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  125. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  126. package/src/zexus/vm/async_optimizer.py +14 -1
  127. package/src/zexus/vm/binary_bytecode.py +659 -0
  128. package/src/zexus/vm/bytecode.py +28 -1
  129. package/src/zexus/vm/bytecode_converter.py +26 -12
  130. package/src/zexus/vm/cabi.c +1985 -0
  131. package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
  132. package/src/zexus/vm/cabi.h +127 -0
  133. package/src/zexus/vm/cache.py +557 -17
  134. package/src/zexus/vm/compiler.py +703 -5
  135. package/src/zexus/vm/fastops.c +15743 -0
  136. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  137. package/src/zexus/vm/fastops.pyx +288 -0
  138. package/src/zexus/vm/gas_metering.py +50 -9
  139. package/src/zexus/vm/jit.py +83 -2
  140. package/src/zexus/vm/native_jit_backend.py +1816 -0
  141. package/src/zexus/vm/native_runtime.cpp +1388 -0
  142. package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
  143. package/src/zexus/vm/optimizer.py +161 -11
  144. package/src/zexus/vm/parallel_vm.py +118 -42
  145. package/src/zexus/vm/peephole_optimizer.py +82 -4
  146. package/src/zexus/vm/profiler.py +38 -18
  147. package/src/zexus/vm/register_allocator.py +16 -5
  148. package/src/zexus/vm/register_vm.py +8 -5
  149. package/src/zexus/vm/vm.py +3411 -573
  150. package/src/zexus/vm/wasm_compiler.py +658 -0
  151. package/src/zexus/zexus_ast.py +63 -11
  152. package/src/zexus/zexus_token.py +13 -5
  153. package/src/zexus/zpm/installer.py +55 -15
  154. package/src/zexus/zpm/package_manager.py +1 -1
  155. package/src/zexus/zpm/registry.py +257 -28
  156. package/src/zexus.egg-info/PKG-INFO +7 -4
  157. package/src/zexus.egg-info/SOURCES.txt +116 -9
  158. package/src/zexus.egg-info/entry_points.txt +1 -0
  159. package/src/zexus.egg-info/requires.txt +4 -0
@@ -0,0 +1,750 @@
1
+ """
2
+ Token Standard Interfaces for the Zexus Blockchain.
3
+
4
+ Defines three token standards analogous to Ethereum's ERC ecosystem:
5
+
6
+ - **ZX20** — Fungible token (like ERC-20)
7
+ - **ZX721** — Non-fungible token / NFT (like ERC-721)
8
+ - **ZX1155** — Multi-token (like ERC-1155, both fungible and NFT)
9
+
10
+ Each standard is an abstract base class with a complete reference
11
+ implementation, ready to be deployed in the ContractVM. The standards
12
+ define the canonical event names, required methods, and metadata
13
+ structures.
14
+
15
+ Usage::
16
+
17
+ token = ZX20Token("MyToken", "MTK", 18, initial_supply=1_000_000)
18
+ token.transfer(sender, recipient, 500)
19
+
20
+ nft = ZX721Token("MyNFT", "MNFT")
21
+ nft.mint(owner, token_id=1, token_uri="ipfs://...")
22
+
23
+ multi = ZX1155Token("ipfs://metadata/{id}.json")
24
+ multi.mint(owner, token_id=1, amount=100, data=b"")
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import abc
30
+ import hashlib
31
+ import time
32
+ from dataclasses import dataclass, field
33
+ from typing import Any, Dict, List, Optional, Set, Tuple
34
+
35
+ import logging
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ # ══════════════════════════════════════════════════════════════════════
41
+ # Events — canonical event structures
42
+ # ══════════════════════════════════════════════════════════════════════
43
+
44
+ @dataclass
45
+ class TokenEvent:
46
+ """Base token event."""
47
+ event_name: str
48
+ contract_address: str = ""
49
+ timestamp: float = field(default_factory=time.time)
50
+ data: Dict[str, Any] = field(default_factory=dict)
51
+
52
+ def to_dict(self) -> Dict[str, Any]:
53
+ return {
54
+ "event": self.event_name,
55
+ "contract": self.contract_address,
56
+ "timestamp": self.timestamp,
57
+ **self.data,
58
+ }
59
+
60
+
61
+ class TransferEvent(TokenEvent):
62
+ """ZX20/ZX721 Transfer event."""
63
+ def __init__(self, from_addr: str, to_addr: str, value: int,
64
+ contract_address: str = ""):
65
+ super().__init__(
66
+ event_name="Transfer",
67
+ contract_address=contract_address,
68
+ data={"from": from_addr, "to": to_addr, "value": value},
69
+ )
70
+
71
+
72
+ class ApprovalEvent(TokenEvent):
73
+ """ZX20/ZX721 Approval event."""
74
+ def __init__(self, owner: str, spender: str, value: int,
75
+ contract_address: str = ""):
76
+ super().__init__(
77
+ event_name="Approval",
78
+ contract_address=contract_address,
79
+ data={"owner": owner, "spender": spender, "value": value},
80
+ )
81
+
82
+
83
+ class TransferSingleEvent(TokenEvent):
84
+ """ZX1155 TransferSingle event."""
85
+ def __init__(self, operator: str, from_addr: str, to_addr: str,
86
+ token_id: int, amount: int, contract_address: str = ""):
87
+ super().__init__(
88
+ event_name="TransferSingle",
89
+ contract_address=contract_address,
90
+ data={
91
+ "operator": operator, "from": from_addr, "to": to_addr,
92
+ "id": token_id, "value": amount,
93
+ },
94
+ )
95
+
96
+
97
+ class TransferBatchEvent(TokenEvent):
98
+ """ZX1155 TransferBatch event."""
99
+ def __init__(self, operator: str, from_addr: str, to_addr: str,
100
+ ids: List[int], amounts: List[int],
101
+ contract_address: str = ""):
102
+ super().__init__(
103
+ event_name="TransferBatch",
104
+ contract_address=contract_address,
105
+ data={
106
+ "operator": operator, "from": from_addr, "to": to_addr,
107
+ "ids": ids, "values": amounts,
108
+ },
109
+ )
110
+
111
+
112
+ class ApprovalForAllEvent(TokenEvent):
113
+ """ZX721/ZX1155 ApprovalForAll event."""
114
+ def __init__(self, owner: str, operator: str, approved: bool,
115
+ contract_address: str = ""):
116
+ super().__init__(
117
+ event_name="ApprovalForAll",
118
+ contract_address=contract_address,
119
+ data={"owner": owner, "operator": operator, "approved": approved},
120
+ )
121
+
122
+
123
+ # ══════════════════════════════════════════════════════════════════════
124
+ # ZX-20 — Fungible Token Standard
125
+ # ══════════════════════════════════════════════════════════════════════
126
+
127
+ ZERO_ADDRESS = "0x" + "0" * 40
128
+
129
+
130
+ class ZX20Interface(abc.ABC):
131
+ """Abstract interface for ZX-20 fungible tokens."""
132
+
133
+ @abc.abstractmethod
134
+ def name(self) -> str: ...
135
+
136
+ @abc.abstractmethod
137
+ def symbol(self) -> str: ...
138
+
139
+ @abc.abstractmethod
140
+ def decimals(self) -> int: ...
141
+
142
+ @abc.abstractmethod
143
+ def total_supply(self) -> int: ...
144
+
145
+ @abc.abstractmethod
146
+ def balance_of(self, account: str) -> int: ...
147
+
148
+ @abc.abstractmethod
149
+ def transfer(self, sender: str, recipient: str, amount: int) -> bool: ...
150
+
151
+ @abc.abstractmethod
152
+ def allowance(self, owner: str, spender: str) -> int: ...
153
+
154
+ @abc.abstractmethod
155
+ def approve(self, owner: str, spender: str, amount: int) -> bool: ...
156
+
157
+ @abc.abstractmethod
158
+ def transfer_from(self, spender: str, from_addr: str,
159
+ to_addr: str, amount: int) -> bool: ...
160
+
161
+
162
+ class ZX20Token(ZX20Interface):
163
+ """Reference implementation of the ZX-20 fungible token standard.
164
+
165
+ All amounts are in the smallest unit (like wei for ETH).
166
+ """
167
+
168
+ def __init__(self, token_name: str, token_symbol: str,
169
+ token_decimals: int = 18,
170
+ initial_supply: int = 0,
171
+ owner: str = ""):
172
+ self._name = token_name
173
+ self._symbol = token_symbol
174
+ self._decimals = token_decimals
175
+ self._total_supply: int = 0
176
+ self._balances: Dict[str, int] = {}
177
+ self._allowances: Dict[str, Dict[str, int]] = {} # owner -> spender -> amount
178
+ self._owner: str = owner
179
+
180
+ # Event log
181
+ self.events: List[TokenEvent] = []
182
+
183
+ # Contract address (set when deployed)
184
+ self.contract_address: str = ""
185
+
186
+ # Mint initial supply to owner
187
+ if initial_supply > 0 and owner:
188
+ self._mint(owner, initial_supply)
189
+
190
+ def name(self) -> str:
191
+ return self._name
192
+
193
+ def symbol(self) -> str:
194
+ return self._symbol
195
+
196
+ def decimals(self) -> int:
197
+ return self._decimals
198
+
199
+ def total_supply(self) -> int:
200
+ return self._total_supply
201
+
202
+ def balance_of(self, account: str) -> int:
203
+ return self._balances.get(account, 0)
204
+
205
+ def transfer(self, sender: str, recipient: str, amount: int) -> bool:
206
+ if amount < 0:
207
+ raise ValueError("Transfer amount must be non-negative")
208
+ if not recipient or recipient == ZERO_ADDRESS:
209
+ raise ValueError("Transfer to zero address")
210
+ if self._balances.get(sender, 0) < amount:
211
+ raise ValueError("Insufficient balance")
212
+
213
+ self._balances[sender] = self._balances.get(sender, 0) - amount
214
+ self._balances[recipient] = self._balances.get(recipient, 0) + amount
215
+
216
+ self._emit(TransferEvent(sender, recipient, amount, self.contract_address))
217
+ return True
218
+
219
+ def allowance(self, owner: str, spender: str) -> int:
220
+ return self._allowances.get(owner, {}).get(spender, 0)
221
+
222
+ def approve(self, owner: str, spender: str, amount: int) -> bool:
223
+ if amount < 0:
224
+ raise ValueError("Approval amount must be non-negative")
225
+ if owner not in self._allowances:
226
+ self._allowances[owner] = {}
227
+ self._allowances[owner][spender] = amount
228
+ self._emit(ApprovalEvent(owner, spender, amount, self.contract_address))
229
+ return True
230
+
231
+ def transfer_from(self, spender: str, from_addr: str,
232
+ to_addr: str, amount: int) -> bool:
233
+ allowed = self.allowance(from_addr, spender)
234
+ if allowed < amount:
235
+ raise ValueError("Allowance exceeded")
236
+
237
+ self.transfer(from_addr, to_addr, amount)
238
+
239
+ # Decrease allowance
240
+ self._allowances[from_addr][spender] = allowed - amount
241
+ return True
242
+
243
+ # ── Extensions ────────────────────────────────────────────────
244
+
245
+ def _mint(self, to: str, amount: int) -> None:
246
+ """Mint new tokens to an address."""
247
+ if amount < 0:
248
+ raise ValueError("Mint amount must be non-negative")
249
+ self._total_supply += amount
250
+ self._balances[to] = self._balances.get(to, 0) + amount
251
+ self._emit(TransferEvent(ZERO_ADDRESS, to, amount, self.contract_address))
252
+
253
+ def mint(self, caller: str, to: str, amount: int) -> None:
254
+ """Public mint (owner-only)."""
255
+ if self._owner and caller != self._owner:
256
+ raise PermissionError("Only owner can mint")
257
+ self._mint(to, amount)
258
+
259
+ def burn(self, owner: str, amount: int) -> None:
260
+ """Burn tokens from an address."""
261
+ if self._balances.get(owner, 0) < amount:
262
+ raise ValueError("Burn amount exceeds balance")
263
+ self._balances[owner] -= amount
264
+ self._total_supply -= amount
265
+ self._emit(TransferEvent(owner, ZERO_ADDRESS, amount, self.contract_address))
266
+
267
+ def _emit(self, event: TokenEvent) -> None:
268
+ self.events.append(event)
269
+
270
+ def to_dict(self) -> Dict[str, Any]:
271
+ """Serialize token state."""
272
+ return {
273
+ "standard": "ZX-20",
274
+ "name": self._name,
275
+ "symbol": self._symbol,
276
+ "decimals": self._decimals,
277
+ "total_supply": self._total_supply,
278
+ "owner": self._owner,
279
+ "balances": dict(self._balances),
280
+ "allowances": {
281
+ owner: dict(spenders)
282
+ for owner, spenders in self._allowances.items()
283
+ },
284
+ }
285
+
286
+ @classmethod
287
+ def from_dict(cls, data: Dict[str, Any]) -> "ZX20Token":
288
+ token = cls(
289
+ token_name=data["name"],
290
+ token_symbol=data["symbol"],
291
+ token_decimals=data.get("decimals", 18),
292
+ owner=data.get("owner", ""),
293
+ )
294
+ token._total_supply = data.get("total_supply", 0)
295
+ token._balances = data.get("balances", {})
296
+ token._allowances = {
297
+ owner: dict(spenders)
298
+ for owner, spenders in data.get("allowances", {}).items()
299
+ }
300
+ return token
301
+
302
+
303
+ # ══════════════════════════════════════════════════════════════════════
304
+ # ZX-721 — Non-Fungible Token (NFT) Standard
305
+ # ══════════════════════════════════════════════════════════════════════
306
+
307
+ class ZX721Interface(abc.ABC):
308
+ """Abstract interface for ZX-721 non-fungible tokens."""
309
+
310
+ @abc.abstractmethod
311
+ def name(self) -> str: ...
312
+
313
+ @abc.abstractmethod
314
+ def symbol(self) -> str: ...
315
+
316
+ @abc.abstractmethod
317
+ def balance_of(self, owner: str) -> int: ...
318
+
319
+ @abc.abstractmethod
320
+ def owner_of(self, token_id: int) -> str: ...
321
+
322
+ @abc.abstractmethod
323
+ def transfer_from(self, caller: str, from_addr: str,
324
+ to_addr: str, token_id: int) -> None: ...
325
+
326
+ @abc.abstractmethod
327
+ def approve(self, caller: str, to: str, token_id: int) -> None: ...
328
+
329
+ @abc.abstractmethod
330
+ def get_approved(self, token_id: int) -> str: ...
331
+
332
+ @abc.abstractmethod
333
+ def set_approval_for_all(self, owner: str, operator: str,
334
+ approved: bool) -> None: ...
335
+
336
+ @abc.abstractmethod
337
+ def is_approved_for_all(self, owner: str, operator: str) -> bool: ...
338
+
339
+
340
+ class ZX721Token(ZX721Interface):
341
+ """Reference implementation of the ZX-721 NFT standard."""
342
+
343
+ def __init__(self, token_name: str, token_symbol: str,
344
+ owner: str = ""):
345
+ self._name = token_name
346
+ self._symbol = token_symbol
347
+ self._owner = owner
348
+
349
+ # token_id -> owner address
350
+ self._owners: Dict[int, str] = {}
351
+ # owner -> token count
352
+ self._balances: Dict[str, int] = {}
353
+ # token_id -> approved address
354
+ self._token_approvals: Dict[int, str] = {}
355
+ # owner -> operator -> bool
356
+ self._operator_approvals: Dict[str, Dict[str, bool]] = {}
357
+ # token_id -> URI string
358
+ self._token_uris: Dict[int, str] = {}
359
+
360
+ self._total_supply: int = 0
361
+
362
+ self.events: List[TokenEvent] = []
363
+ self.contract_address: str = ""
364
+
365
+ def name(self) -> str:
366
+ return self._name
367
+
368
+ def symbol(self) -> str:
369
+ return self._symbol
370
+
371
+ def total_supply(self) -> int:
372
+ return self._total_supply
373
+
374
+ def balance_of(self, owner: str) -> int:
375
+ if not owner or owner == ZERO_ADDRESS:
376
+ raise ValueError("Balance query for zero address")
377
+ return self._balances.get(owner, 0)
378
+
379
+ def owner_of(self, token_id: int) -> str:
380
+ owner = self._owners.get(token_id, "")
381
+ if not owner:
382
+ raise ValueError(f"Token {token_id} does not exist")
383
+ return owner
384
+
385
+ def token_uri(self, token_id: int) -> str:
386
+ if token_id not in self._owners:
387
+ raise ValueError(f"Token {token_id} does not exist")
388
+ return self._token_uris.get(token_id, "")
389
+
390
+ def approve(self, caller: str, to: str, token_id: int) -> None:
391
+ owner = self.owner_of(token_id)
392
+ if caller != owner and not self.is_approved_for_all(owner, caller):
393
+ raise PermissionError("Not owner or approved operator")
394
+ self._token_approvals[token_id] = to
395
+ self._emit(ApprovalEvent(owner, to, token_id, self.contract_address))
396
+
397
+ def get_approved(self, token_id: int) -> str:
398
+ if token_id not in self._owners:
399
+ raise ValueError(f"Token {token_id} does not exist")
400
+ return self._token_approvals.get(token_id, "")
401
+
402
+ def set_approval_for_all(self, owner: str, operator: str,
403
+ approved: bool) -> None:
404
+ if owner == operator:
405
+ raise ValueError("Cannot approve self")
406
+ if owner not in self._operator_approvals:
407
+ self._operator_approvals[owner] = {}
408
+ self._operator_approvals[owner][operator] = approved
409
+ self._emit(ApprovalForAllEvent(owner, operator, approved, self.contract_address))
410
+
411
+ def is_approved_for_all(self, owner: str, operator: str) -> bool:
412
+ return self._operator_approvals.get(owner, {}).get(operator, False)
413
+
414
+ def _is_approved_or_owner(self, spender: str, token_id: int) -> bool:
415
+ owner = self.owner_of(token_id)
416
+ return (
417
+ spender == owner
418
+ or self.get_approved(token_id) == spender
419
+ or self.is_approved_for_all(owner, spender)
420
+ )
421
+
422
+ def transfer_from(self, caller: str, from_addr: str,
423
+ to_addr: str, token_id: int) -> None:
424
+ if not self._is_approved_or_owner(caller, token_id):
425
+ raise PermissionError("Not approved or owner")
426
+ owner = self.owner_of(token_id)
427
+ if owner != from_addr:
428
+ raise ValueError("Transfer from incorrect owner")
429
+ if not to_addr or to_addr == ZERO_ADDRESS:
430
+ raise ValueError("Transfer to zero address")
431
+
432
+ # Clear approval
433
+ self._token_approvals.pop(token_id, None)
434
+
435
+ self._balances[from_addr] = self._balances.get(from_addr, 0) - 1
436
+ self._balances[to_addr] = self._balances.get(to_addr, 0) + 1
437
+ self._owners[token_id] = to_addr
438
+
439
+ self._emit(TransferEvent(from_addr, to_addr, token_id, self.contract_address))
440
+
441
+ def safe_transfer_from(self, caller: str, from_addr: str,
442
+ to_addr: str, token_id: int) -> None:
443
+ """Transfer with safety check (same as transfer_from here)."""
444
+ self.transfer_from(caller, from_addr, to_addr, token_id)
445
+
446
+ # ── Minting / Burning ─────────────────────────────────────────
447
+
448
+ def mint(self, to: str, token_id: int, token_uri: str = "") -> None:
449
+ """Mint a new NFT."""
450
+ if token_id in self._owners:
451
+ raise ValueError(f"Token {token_id} already exists")
452
+ if not to or to == ZERO_ADDRESS:
453
+ raise ValueError("Mint to zero address")
454
+
455
+ self._owners[token_id] = to
456
+ self._balances[to] = self._balances.get(to, 0) + 1
457
+ self._total_supply += 1
458
+
459
+ if token_uri:
460
+ self._token_uris[token_id] = token_uri
461
+
462
+ self._emit(TransferEvent(ZERO_ADDRESS, to, token_id, self.contract_address))
463
+
464
+ def burn(self, caller: str, token_id: int) -> None:
465
+ """Burn an NFT."""
466
+ if not self._is_approved_or_owner(caller, token_id):
467
+ raise PermissionError("Not approved or owner")
468
+
469
+ owner = self.owner_of(token_id)
470
+ self._token_approvals.pop(token_id, None)
471
+ self._balances[owner] = self._balances.get(owner, 0) - 1
472
+ del self._owners[token_id]
473
+ self._token_uris.pop(token_id, None)
474
+ self._total_supply -= 1
475
+
476
+ self._emit(TransferEvent(owner, ZERO_ADDRESS, token_id, self.contract_address))
477
+
478
+ def tokens_of_owner(self, owner: str) -> List[int]:
479
+ """Return all token IDs owned by an address."""
480
+ return [tid for tid, o in self._owners.items() if o == owner]
481
+
482
+ def _emit(self, event: TokenEvent) -> None:
483
+ self.events.append(event)
484
+
485
+ def to_dict(self) -> Dict[str, Any]:
486
+ return {
487
+ "standard": "ZX-721",
488
+ "name": self._name,
489
+ "symbol": self._symbol,
490
+ "owner": self._owner,
491
+ "total_supply": self._total_supply,
492
+ "owners": {str(k): v for k, v in self._owners.items()},
493
+ "token_uris": {str(k): v for k, v in self._token_uris.items()},
494
+ "balances": dict(self._balances),
495
+ "operator_approvals": {
496
+ o: dict(ops) for o, ops in self._operator_approvals.items()
497
+ },
498
+ }
499
+
500
+ @classmethod
501
+ def from_dict(cls, data: Dict[str, Any]) -> "ZX721Token":
502
+ token = cls(
503
+ token_name=data["name"],
504
+ token_symbol=data["symbol"],
505
+ owner=data.get("owner", ""),
506
+ )
507
+ token._total_supply = data.get("total_supply", 0)
508
+ token._owners = {int(k): v for k, v in data.get("owners", {}).items()}
509
+ token._token_uris = {int(k): v for k, v in data.get("token_uris", {}).items()}
510
+ token._balances = data.get("balances", {})
511
+ token._operator_approvals = {
512
+ o: dict(ops) for o, ops in data.get("operator_approvals", {}).items()
513
+ }
514
+ return token
515
+
516
+
517
+ # ══════════════════════════════════════════════════════════════════════
518
+ # ZX-1155 — Multi-Token Standard
519
+ # ══════════════════════════════════════════════════════════════════════
520
+
521
+ class ZX1155Interface(abc.ABC):
522
+ """Abstract interface for ZX-1155 multi-tokens."""
523
+
524
+ @abc.abstractmethod
525
+ def balance_of(self, account: str, token_id: int) -> int: ...
526
+
527
+ @abc.abstractmethod
528
+ def balance_of_batch(self, accounts: List[str],
529
+ ids: List[int]) -> List[int]: ...
530
+
531
+ @abc.abstractmethod
532
+ def set_approval_for_all(self, owner: str, operator: str,
533
+ approved: bool) -> None: ...
534
+
535
+ @abc.abstractmethod
536
+ def is_approved_for_all(self, owner: str, operator: str) -> bool: ...
537
+
538
+ @abc.abstractmethod
539
+ def safe_transfer_from(self, operator: str, from_addr: str,
540
+ to_addr: str, token_id: int,
541
+ amount: int, data: bytes) -> None: ...
542
+
543
+ @abc.abstractmethod
544
+ def safe_batch_transfer_from(self, operator: str, from_addr: str,
545
+ to_addr: str, ids: List[int],
546
+ amounts: List[int],
547
+ data: bytes) -> None: ...
548
+
549
+
550
+ class ZX1155Token(ZX1155Interface):
551
+ """Reference implementation of the ZX-1155 multi-token standard.
552
+
553
+ Supports both fungible (supply > 1) and non-fungible (supply = 1)
554
+ tokens within a single contract.
555
+ """
556
+
557
+ def __init__(self, base_uri: str = "", owner: str = ""):
558
+ self._base_uri = base_uri
559
+ self._owner = owner
560
+
561
+ # (token_id, account) -> balance
562
+ self._balances: Dict[int, Dict[str, int]] = {}
563
+ # owner -> operator -> bool
564
+ self._operator_approvals: Dict[str, Dict[str, bool]] = {}
565
+ # token_id -> total supply
566
+ self._supplies: Dict[int, int] = {}
567
+ # token_id -> custom URI (overrides base_uri)
568
+ self._token_uris: Dict[int, str] = {}
569
+
570
+ self.events: List[TokenEvent] = []
571
+ self.contract_address: str = ""
572
+
573
+ def uri(self, token_id: int) -> str:
574
+ """Return the URI for a token (replaces {id} in base_uri)."""
575
+ custom = self._token_uris.get(token_id)
576
+ if custom:
577
+ return custom
578
+ return self._base_uri.replace("{id}", str(token_id))
579
+
580
+ def set_uri(self, token_id: int, token_uri: str) -> None:
581
+ self._token_uris[token_id] = token_uri
582
+
583
+ def total_supply(self, token_id: int) -> int:
584
+ return self._supplies.get(token_id, 0)
585
+
586
+ def exists(self, token_id: int) -> bool:
587
+ return self._supplies.get(token_id, 0) > 0
588
+
589
+ def balance_of(self, account: str, token_id: int) -> int:
590
+ return self._balances.get(token_id, {}).get(account, 0)
591
+
592
+ def balance_of_batch(self, accounts: List[str],
593
+ ids: List[int]) -> List[int]:
594
+ if len(accounts) != len(ids):
595
+ raise ValueError("Accounts and IDs length mismatch")
596
+ return [self.balance_of(a, i) for a, i in zip(accounts, ids)]
597
+
598
+ def set_approval_for_all(self, owner: str, operator: str,
599
+ approved: bool) -> None:
600
+ if owner == operator:
601
+ raise ValueError("Cannot approve self")
602
+ if owner not in self._operator_approvals:
603
+ self._operator_approvals[owner] = {}
604
+ self._operator_approvals[owner][operator] = approved
605
+ self._emit(ApprovalForAllEvent(owner, operator, approved, self.contract_address))
606
+
607
+ def is_approved_for_all(self, owner: str, operator: str) -> bool:
608
+ return self._operator_approvals.get(owner, {}).get(operator, False)
609
+
610
+ def safe_transfer_from(self, operator: str, from_addr: str,
611
+ to_addr: str, token_id: int,
612
+ amount: int, data: bytes = b"") -> None:
613
+ if operator != from_addr and not self.is_approved_for_all(from_addr, operator):
614
+ raise PermissionError("Not owner or approved")
615
+ if not to_addr or to_addr == ZERO_ADDRESS:
616
+ raise ValueError("Transfer to zero address")
617
+
618
+ from_bal = self.balance_of(from_addr, token_id)
619
+ if from_bal < amount:
620
+ raise ValueError("Insufficient balance")
621
+
622
+ self._balances.setdefault(token_id, {})[from_addr] = from_bal - amount
623
+ to_bal = self.balance_of(to_addr, token_id)
624
+ self._balances.setdefault(token_id, {})[to_addr] = to_bal + amount
625
+
626
+ self._emit(TransferSingleEvent(
627
+ operator, from_addr, to_addr, token_id, amount, self.contract_address
628
+ ))
629
+
630
+ def safe_batch_transfer_from(self, operator: str, from_addr: str,
631
+ to_addr: str, ids: List[int],
632
+ amounts: List[int],
633
+ data: bytes = b"") -> None:
634
+ if len(ids) != len(amounts):
635
+ raise ValueError("IDs and amounts length mismatch")
636
+ if operator != from_addr and not self.is_approved_for_all(from_addr, operator):
637
+ raise PermissionError("Not owner or approved")
638
+ if not to_addr or to_addr == ZERO_ADDRESS:
639
+ raise ValueError("Transfer to zero address")
640
+
641
+ for token_id, amount in zip(ids, amounts):
642
+ from_bal = self.balance_of(from_addr, token_id)
643
+ if from_bal < amount:
644
+ raise ValueError(f"Insufficient balance for token {token_id}")
645
+ self._balances.setdefault(token_id, {})[from_addr] = from_bal - amount
646
+ to_bal = self.balance_of(to_addr, token_id)
647
+ self._balances.setdefault(token_id, {})[to_addr] = to_bal + amount
648
+
649
+ self._emit(TransferBatchEvent(
650
+ operator, from_addr, to_addr, ids, amounts, self.contract_address
651
+ ))
652
+
653
+ # ── Minting / Burning ─────────────────────────────────────────
654
+
655
+ def mint(self, to: str, token_id: int, amount: int,
656
+ data: bytes = b"") -> None:
657
+ """Mint tokens."""
658
+ if not to or to == ZERO_ADDRESS:
659
+ raise ValueError("Mint to zero address")
660
+
661
+ bal = self.balance_of(to, token_id)
662
+ self._balances.setdefault(token_id, {})[to] = bal + amount
663
+ self._supplies[token_id] = self._supplies.get(token_id, 0) + amount
664
+
665
+ self._emit(TransferSingleEvent(
666
+ to, ZERO_ADDRESS, to, token_id, amount, self.contract_address
667
+ ))
668
+
669
+ def mint_batch(self, to: str, ids: List[int], amounts: List[int],
670
+ data: bytes = b"") -> None:
671
+ """Batch mint."""
672
+ if len(ids) != len(amounts):
673
+ raise ValueError("IDs and amounts length mismatch")
674
+ if not to or to == ZERO_ADDRESS:
675
+ raise ValueError("Mint to zero address")
676
+
677
+ for token_id, amount in zip(ids, amounts):
678
+ bal = self.balance_of(to, token_id)
679
+ self._balances.setdefault(token_id, {})[to] = bal + amount
680
+ self._supplies[token_id] = self._supplies.get(token_id, 0) + amount
681
+
682
+ self._emit(TransferBatchEvent(
683
+ to, ZERO_ADDRESS, to, ids, amounts, self.contract_address
684
+ ))
685
+
686
+ def burn(self, owner: str, token_id: int, amount: int) -> None:
687
+ """Burn tokens."""
688
+ bal = self.balance_of(owner, token_id)
689
+ if bal < amount:
690
+ raise ValueError("Burn amount exceeds balance")
691
+
692
+ self._balances.setdefault(token_id, {})[owner] = bal - amount
693
+ self._supplies[token_id] = self._supplies.get(token_id, 0) - amount
694
+
695
+ self._emit(TransferSingleEvent(
696
+ owner, owner, ZERO_ADDRESS, token_id, amount, self.contract_address
697
+ ))
698
+
699
+ def burn_batch(self, owner: str, ids: List[int],
700
+ amounts: List[int]) -> None:
701
+ """Batch burn."""
702
+ if len(ids) != len(amounts):
703
+ raise ValueError("IDs and amounts length mismatch")
704
+
705
+ for token_id, amount in zip(ids, amounts):
706
+ bal = self.balance_of(owner, token_id)
707
+ if bal < amount:
708
+ raise ValueError(f"Burn amount exceeds balance for token {token_id}")
709
+ self._balances.setdefault(token_id, {})[owner] = bal - amount
710
+ self._supplies[token_id] = self._supplies.get(token_id, 0) - amount
711
+
712
+ self._emit(TransferBatchEvent(
713
+ owner, owner, ZERO_ADDRESS, ids, amounts, self.contract_address
714
+ ))
715
+
716
+ def _emit(self, event: TokenEvent) -> None:
717
+ self.events.append(event)
718
+
719
+ def to_dict(self) -> Dict[str, Any]:
720
+ return {
721
+ "standard": "ZX-1155",
722
+ "base_uri": self._base_uri,
723
+ "owner": self._owner,
724
+ "supplies": {str(k): v for k, v in self._supplies.items()},
725
+ "balances": {
726
+ str(tid): dict(accts)
727
+ for tid, accts in self._balances.items()
728
+ },
729
+ "token_uris": {str(k): v for k, v in self._token_uris.items()},
730
+ "operator_approvals": {
731
+ o: dict(ops) for o, ops in self._operator_approvals.items()
732
+ },
733
+ }
734
+
735
+ @classmethod
736
+ def from_dict(cls, data: Dict[str, Any]) -> "ZX1155Token":
737
+ token = cls(
738
+ base_uri=data.get("base_uri", ""),
739
+ owner=data.get("owner", ""),
740
+ )
741
+ token._supplies = {int(k): v for k, v in data.get("supplies", {}).items()}
742
+ token._balances = {
743
+ int(tid): dict(accts)
744
+ for tid, accts in data.get("balances", {}).items()
745
+ }
746
+ token._token_uris = {int(k): v for k, v in data.get("token_uris", {}).items()}
747
+ token._operator_approvals = {
748
+ o: dict(ops) for o, ops in data.get("operator_approvals", {}).items()
749
+ }
750
+ return token