forked from ton-studio/ton-labels
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodels.py
More file actions
133 lines (110 loc) · 4.35 KB
/
Copy pathmodels.py
File metadata and controls
133 lines (110 loc) · 4.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import re
import json
from typing import List
from datetime import datetime
from pydantic import BaseModel, field_validator
from pytoniq_core import Address
# Load categories and tags from external files
with open("categories.json", "rt") as f:
categories_data = json.load(f)
allowed_categories = {item["name"] for item in categories_data}
with open("tags.json", "rt") as f:
tags_data = json.load(f)
allowed_tags = {item["name"] for item in tags_data}
# Define allowed subcategories for each category
allowed_subcategories = {
"merchant": {"onchain_marketplace", "offchain_marketplace"},
"scammer": {"drainer", "address-poisoning"},
"scripted-activity": {"sybil"},
"gaming": {"gambling"},
"DEX": {"perpetuals", "launchpad"},
"defi": {"lending", "yield_aggregator", "liquid_staking", "perpetuals", "cdp"},
}
class LabelledAddress(BaseModel):
address: str
source: str
comment: str
tags: List[str]
submittedBy: str
submissionTimestamp: datetime
@field_validator("address")
def validate_address(cls, address: str) -> str:
try:
address_uf = Address(address).to_str(True, is_bounceable=True)
except Exception as e:
raise ValueError(f"Invalid address: {address}: {e}")
if address_uf != address:
raise ValueError(
"Address must be in bounceable format (starts with 'EQ' or 'Ef')"
)
return address
@field_validator("tags")
def validate_tags(cls, tags: List[str]) -> List[str]:
not_in_allowed_list = set(tags) - set(allowed_tags)
if len(not_in_allowed_list) > 0:
raise ValueError(
f"Tags '{not_in_allowed_list}' are not in the allowed tags list"
)
return tags
class Metadata(BaseModel):
label: str
name: str = None
organization: str
category: str
subcategory: str
website: str
description: str
@field_validator("website")
def validate_website(cls, website):
if website == "": # Allow empty website
return ""
if not website.startswith("https://"):
raise ValueError("Website must start with https://")
if "." not in website:
raise ValueError("Website must contain '.'")
if "?" in website:
raise ValueError("Website must not contain '?'")
if website.endswith("/"):
raise ValueError("Website must not end with '/'")
return website
@field_validator("label", "organization")
def validate_key(cls, value):
if value.startswith("sybil_"):
# If it starts with sybil_, allow any characters after
return value
elif not re.match(r"^[a-z0-9._]+(?:_EQ[A-Za-z0-9_]+)?$", value):
raise ValueError(f"{value} must be lowercase and can only contain '.', '_'")
return value
@field_validator("category")
def validate_category(cls, category):
if category not in allowed_categories:
raise ValueError(
f"Category '{category}' is not in the allowed categories list"
)
return category
@field_validator("subcategory")
def validate_subcategory(cls, subcategory, info):
category = info.data.get('category')
# Allow empty subcategory
if subcategory == "":
return subcategory
# If category has allowed subcategories, validate against them
if category in allowed_subcategories:
if subcategory not in allowed_subcategories[category]:
raise ValueError(
f"Subcategory '{subcategory}' is not allowed for category '{category}'. "
f"Allowed subcategories are: {allowed_subcategories[category]}"
)
elif subcategory != "":
# If category doesn't have defined subcategories, only empty string is allowed
raise ValueError(
f"Category '{category}' does not allow any subcategories. Only empty string is allowed."
)
return subcategory
def model_post_init(self, __context):
if not self.name:
# Convert label to name format if name is not provided
self.name = self.label.replace("_", " ").capitalize()
class LabelledData(BaseModel):
metadata: Metadata
addresses: List[LabelledAddress]