Skip to content

Commit d77e045

Browse files
WIP: support for cip67/68
1 parent eec89fd commit d77e045

File tree

3 files changed

+138
-7
lines changed

3 files changed

+138
-7
lines changed

pycardano/cip/cip67.py

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from typing import Union
2+
3+
from crc8 import crc8
4+
5+
from pycardano.transaction import AssetName
6+
7+
8+
class InvalidCIP67Token(Exception):
9+
pass
10+
11+
12+
class CIP67TokenName(AssetName):
13+
def __repr__(self):
14+
return f"{self.__class__.__name__}({self.payload})"
15+
16+
def __init__(self, data: Union[bytes, str, AssetName]):
17+
if isinstance(data, AssetName):
18+
data = data.payload
19+
20+
if isinstance(data, bytes):
21+
data = data.hex()
22+
23+
if data[0] != "0" or data[7] != "0":
24+
raise InvalidCIP67Token(
25+
"The first and eighth hex values must be 0. Instead found:\n"
26+
+ f"first={data[0]}\n"
27+
+ f"eigth={data[7]}"
28+
)
29+
30+
checksum = crc8(bytes.fromhex(data[1:5])).hexdigest()
31+
if data[5:7] != checksum:
32+
raise InvalidCIP67Token(
33+
f"Token label {data[1:5]} does not match token checksum.\n"
34+
+ f"expected={checksum}\n"
35+
+ f"received={data[5:7]}"
36+
)
37+
38+
super().__init__(bytes.fromhex(data))
39+
40+
@property
41+
def label(self) -> int:
42+
return int.from_bytes(self.payload[:3], "big") >> 4

pycardano/cip/cip68.py

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from typing import Union
2+
3+
from pycardano.cip.cip67 import CIP67TokenName
4+
from pycardano.plutus import PlutusData
5+
from pycardano.serialization import ArrayCBORSerializable
6+
from pycardano.serialization import MapCBORSerializable
7+
from pycardano.transaction import AssetName
8+
9+
10+
class InvalidCIP68ReferenceNFT(Exception):
11+
pass
12+
13+
14+
class CIP68TokenName(CIP67TokenName):
15+
@property
16+
def reference_token(self) -> "CIP68ReferenceNFTName":
17+
ref_token = self.payload.hex()[0] + "00643b" + self.payload.hex()[7:]
18+
19+
return CIP68ReferenceNFTName(ref_token)
20+
21+
22+
class CIP68ReferenceNFTName(CIP68TokenName):
23+
def __init__(self, data: Union[bytes, str, AssetName]):
24+
super().__init__(data)
25+
26+
if self.label != 100:
27+
raise InvalidCIP68ReferenceNFT("Reference NFT must have label 100.")
28+
29+
30+
class CIP68UserNFTName(CIP68TokenName):
31+
def __init__(self, data: Union[bytes, str, AssetName]):
32+
super().__init__(data)
33+
34+
if self.label != 222:
35+
raise InvalidCIP68ReferenceNFT("User NFT must have label 222.")
36+
37+
38+
class CIP68UserNFTFiles(MapCBORSerializable):
39+
name: Union[bytes, None] = None
40+
mediaType: bytes
41+
src: bytes
42+
43+
44+
class CIP68UserNFTMetadata(MapCBORSerializable):
45+
name: bytes
46+
image: bytes
47+
description: Union[bytes, None] = None
48+
files: Union[CIP68UserNFTFiles, None] = None
49+
50+
51+
class CIP68UserFTName(CIP68TokenName):
52+
def __init__(self, data: Union[bytes, str, AssetName]):
53+
super().__init__(data)
54+
55+
if self.label != 333:
56+
raise InvalidCIP68ReferenceNFT("User NFT must have label 333.")
57+
58+
59+
class CIP68UserFTMetadata(MapCBORSerializable):
60+
name: bytes
61+
description: bytes
62+
ticker: Union[bytes, None] = None
63+
url: Union[bytes, None] = None
64+
decimals: Union[int, None] = None
65+
logo: Union[bytes, None] = None
66+
67+
68+
class CIP68UserRFTName(CIP68TokenName):
69+
def __init__(self, data: Union[bytes, str, AssetName]):
70+
super().__init__(data)
71+
72+
if self.label != 444:
73+
raise InvalidCIP68ReferenceNFT("User NFT must have label 444.")
74+
75+
76+
class CIP68UserRFTMetadata(MapCBORSerializable):
77+
name: bytes
78+
image: bytes
79+
description: Union[bytes, None] = None
80+
decimals: Union[int, None] = None
81+
files: Union[CIP68UserNFTFiles, None] = None
82+
83+
84+
class CIP68Metadata(ArrayCBORSerializable):
85+
metadata: Union[
86+
CIP68UserNFTMetadata,
87+
CIP68UserFTMetadata,
88+
CIP68UserRFTMetadata,
89+
MapCBORSerializable,
90+
ArrayCBORSerializable,
91+
]
92+
version: int
93+
extra: Union[PlutusData, None] = None

pyproject.toml

+3-7
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ ECPy = "^1.2.5"
3535
frozendict = "^2.3.8"
3636
frozenlist = "^1.3.3"
3737
cachetools = "^5.3.0"
38+
crc8 = "0.2.0"
3839

3940
[tool.poetry.dev-dependencies]
4041
Sphinx = "^4.3.2"
@@ -58,10 +59,7 @@ build-backend = "poetry.core.masonry.api"
5859
[tool.pytest.ini_options]
5960
addopts = "--doctest-modules --ignore=examples --ignore=integration-test --ignore=test/resources/haskell"
6061
minversion = "6.0"
61-
markers = [
62-
"post_alonzo",
63-
"single"
64-
]
62+
markers = ["post_alonzo", "single"]
6563

6664
[tool.isort]
6765
profile = "black"
@@ -70,6 +68,4 @@ profile = "black"
7068
ignore_missing_imports = true
7169
disable_error_code = ["str-bytes-safe"]
7270
python_version = 3.7
73-
exclude = [
74-
'^pycardano/crypto/bech32.py$',
75-
]
71+
exclude = ['^pycardano/crypto/bech32.py$']

0 commit comments

Comments
 (0)