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.
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/__init__.py +7 -0
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +17 -2
- package/src/zexus/blockchain/__init__.py +411 -0
- package/src/zexus/blockchain/accelerator.py +1160 -0
- package/src/zexus/blockchain/chain.py +660 -0
- package/src/zexus/blockchain/consensus.py +821 -0
- package/src/zexus/blockchain/contract_vm.py +1019 -0
- package/src/zexus/blockchain/crypto.py +79 -14
- package/src/zexus/blockchain/events.py +526 -0
- package/src/zexus/blockchain/loadtest.py +721 -0
- package/src/zexus/blockchain/monitoring.py +350 -0
- package/src/zexus/blockchain/mpt.py +716 -0
- package/src/zexus/blockchain/multichain.py +951 -0
- package/src/zexus/blockchain/multiprocess_executor.py +338 -0
- package/src/zexus/blockchain/network.py +886 -0
- package/src/zexus/blockchain/node.py +666 -0
- package/src/zexus/blockchain/rpc.py +1203 -0
- package/src/zexus/blockchain/rust_bridge.py +421 -0
- package/src/zexus/blockchain/storage.py +423 -0
- package/src/zexus/blockchain/tokens.py +750 -0
- package/src/zexus/blockchain/upgradeable.py +1004 -0
- package/src/zexus/blockchain/verification.py +1602 -0
- package/src/zexus/blockchain/wallet.py +621 -0
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +300 -20
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/lexer.py +10 -5
- package/src/zexus/concurrency_system.py +79 -0
- package/src/zexus/config.py +54 -0
- package/src/zexus/crypto_bridge.py +244 -8
- package/src/zexus/dap/__init__.py +10 -0
- package/src/zexus/dap/__main__.py +4 -0
- package/src/zexus/dap/dap_server.py +391 -0
- package/src/zexus/dap/debug_engine.py +298 -0
- package/src/zexus/environment.py +10 -1
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +441 -37
- package/src/zexus/evaluator/core.py +560 -49
- package/src/zexus/evaluator/expressions.py +122 -49
- package/src/zexus/evaluator/functions.py +417 -16
- package/src/zexus/evaluator/statements.py +521 -118
- package/src/zexus/evaluator/unified_execution.py +573 -72
- package/src/zexus/evaluator/utils.py +14 -2
- package/src/zexus/event_loop.py +186 -0
- package/src/zexus/lexer.py +742 -486
- package/src/zexus/lsp/__init__.py +1 -1
- package/src/zexus/lsp/definition_provider.py +163 -9
- package/src/zexus/lsp/server.py +22 -8
- package/src/zexus/lsp/symbol_provider.py +182 -9
- package/src/zexus/module_cache.py +237 -9
- package/src/zexus/object.py +64 -6
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/parser.py +786 -285
- package/src/zexus/parser/strategy_context.py +407 -66
- package/src/zexus/parser/strategy_structural.py +117 -19
- package/src/zexus/persistence.py +15 -1
- package/src/zexus/renderer/__init__.py +15 -0
- package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
- package/src/zexus/renderer/tk_backend.py +208 -0
- package/src/zexus/renderer/web_backend.py +260 -0
- package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
- package/src/zexus/runtime/file_flags.py +137 -0
- package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
- package/src/zexus/security.py +424 -34
- package/src/zexus/stdlib/fs.py +23 -18
- package/src/zexus/stdlib/http.py +289 -186
- package/src/zexus/stdlib/sockets.py +207 -163
- package/src/zexus/stdlib/websockets.py +282 -0
- package/src/zexus/stdlib_integration.py +369 -2
- package/src/zexus/strategy_recovery.py +6 -3
- package/src/zexus/type_checker.py +423 -0
- package/src/zexus/virtual_filesystem.py +189 -2
- package/src/zexus/vm/__init__.py +113 -3
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +14 -1
- package/src/zexus/vm/binary_bytecode.py +659 -0
- package/src/zexus/vm/bytecode.py +28 -1
- package/src/zexus/vm/bytecode_converter.py +26 -12
- package/src/zexus/vm/cabi.c +1985 -0
- package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/cabi.h +127 -0
- package/src/zexus/vm/cache.py +557 -17
- package/src/zexus/vm/compiler.py +703 -5
- package/src/zexus/vm/fastops.c +15743 -0
- package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/fastops.pyx +288 -0
- package/src/zexus/vm/gas_metering.py +50 -9
- package/src/zexus/vm/jit.py +83 -2
- package/src/zexus/vm/native_jit_backend.py +1816 -0
- package/src/zexus/vm/native_runtime.cpp +1388 -0
- package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/optimizer.py +161 -11
- package/src/zexus/vm/parallel_vm.py +118 -42
- package/src/zexus/vm/peephole_optimizer.py +82 -4
- package/src/zexus/vm/profiler.py +38 -18
- package/src/zexus/vm/register_allocator.py +16 -5
- package/src/zexus/vm/register_vm.py +8 -5
- package/src/zexus/vm/vm.py +3411 -573
- package/src/zexus/vm/wasm_compiler.py +658 -0
- package/src/zexus/zexus_ast.py +63 -11
- package/src/zexus/zexus_token.py +13 -5
- package/src/zexus/zpm/installer.py +55 -15
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus/zpm/registry.py +257 -28
- package/src/zexus.egg-info/PKG-INFO +7 -4
- package/src/zexus.egg-info/SOURCES.txt +116 -9
- package/src/zexus.egg-info/entry_points.txt +1 -0
- 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
|