| """Pydantic v2 models for the token domain. |
| |
| No FastAPI imports. Pure data shapes. |
| """ |
| from __future__ import annotations |
|
|
| from datetime import datetime |
| from enum import StrEnum |
| from typing import Any |
|
|
| from pydantic import BaseModel, ConfigDict, Field, field_validator |
|
|
|
|
| class RiskLevel(StrEnum): |
| """Token risk classification.""" |
|
|
| LOW = "low" |
| MEDIUM = "medium" |
| HIGH = "high" |
| CRITICAL = "critical" |
|
|
| @classmethod |
| def from_score(cls, score: int) -> RiskLevel: |
| if score < 20: |
| return cls.LOW |
| if score < 50: |
| return cls.MEDIUM |
| if score < 80: |
| return cls.HIGH |
| return cls.CRITICAL |
|
|
|
|
| class Token(BaseModel): |
| """Token identity.""" |
|
|
| model_config = ConfigDict(str_strip_whitespace=True) |
|
|
| address: str = Field(..., min_length=1, max_length=256) |
| chain: str = Field(default="solana") |
| name: str = "" |
| symbol: str = "" |
| decimals: int = 9 |
|
|
|
|
| class TokenDetail(BaseModel): |
| """Full token details — metadata + supply + verification.""" |
|
|
| address: str |
| chain: str |
| name: str = "" |
| symbol: str = "" |
| decimals: int = 9 |
| total_supply: float = 0.0 |
| circulating_supply: float = 0.0 |
| holders: int = 0 |
| verified: bool = False |
| metadata: dict[str, Any] = Field(default_factory=dict) |
| fetched_at: datetime = Field(default_factory=datetime.utcnow) |
|
|
|
|
| class TokenHolder(BaseModel): |
| """A token holder entry.""" |
|
|
| address: str |
| balance: float = 0.0 |
| percentage: float = 0.0 |
| label: str | None = None |
| is_contract: bool = False |
| is_locked: bool = False |
|
|
|
|
| class TokenLiquidity(BaseModel): |
| """Token liquidity summary.""" |
|
|
| address: str |
| chain: str |
| total_liquidity_usd: float = 0.0 |
| pools: list[dict[str, Any]] = Field(default_factory=list) |
| locked_percentage: float = 0.0 |
| fetched_at: datetime = Field(default_factory=datetime.utcnow) |
|
|
|
|
| class TokenRisk(BaseModel): |
| """Token risk assessment.""" |
|
|
| address: str |
| chain: str |
| risk_score: int = Field(default=0, ge=0, le=100) |
| risk_level: RiskLevel |
| flags: list[dict[str, Any]] = Field(default_factory=list) |
| honeypot: bool = False |
| can_sell: bool = True |
| can_buy: bool = True |
| take_back: bool = False |
| owner_change_balance: bool = False |
| transfer_pausable: bool = False |
| analyzed_at: datetime = Field(default_factory=datetime.utcnow) |
|
|
|
|
| class TokenScanRequest(BaseModel): |
| """Request body for the token scan endpoint.""" |
|
|
| model_config = ConfigDict(str_strip_whitespace=True) |
|
|
| address: str = Field(..., min_length=1, max_length=256) |
| chain: str = Field(default="solana") |
| tier: str = Field(default="free") |
|
|
| @field_validator("chain", "tier") |
| @classmethod |
| def _lowercase(cls, v: str) -> str: |
| return v.lower().strip() |
|
|
|
|
| class TokenScanResult(BaseModel): |
| """Full token scan result.""" |
|
|
| address: str |
| chain: str |
| safety_score: float = 100.0 |
| risk_score: int = 0 |
| risk_level: RiskLevel |
| risk_flags: list[str] = Field(default_factory=list) |
| warnings: list[str] = Field(default_factory=list) |
| modules_run: list[str] = Field(default_factory=list) |
| metadata: dict[str, Any] = Field(default_factory=dict) |
| scanned_at: datetime = Field(default_factory=datetime.utcnow) |
|
|