Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/Asset.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**asset_id** | **int** | |
**symbol** | **str** | |
**l1_decimals** | **int** | |
**l1_decimals** | **int** | L1 token decimals when supplied by registration. Omitted for Core-native and EVM-native assets. | [optional]
**evm_decimals** | **int** | Canonical EVM token decimals for EVM-native assets. Zero is a valid precision; omission means unavailable. | [optional]
**canonical_domain** | **int** | Asset origin: 0 legacy, 1 L1, 2 Core, 3 EVM. Omitted by older API versions. | [optional]
**evm_token** | **str** | Linked EVM token or wrapper address. The zero address means unlinked; older API versions omit this field. | [optional]
**decimals** | **int** | |
**min_transfer_amount** | **str** | |
**min_withdrawal_amount** | **str** | |
Expand Down
24 changes: 20 additions & 4 deletions lighter/models/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
import re # noqa: F401
import json

from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr, field_validator
from typing import Any, ClassVar, Dict, List
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr, field_validator
from typing import Any, ClassVar, Dict, List, Optional
from typing import Optional, Set
from typing_extensions import Self

Expand All @@ -28,7 +28,10 @@ class Asset(BaseModel):
""" # noqa: E501
asset_id: StrictInt
symbol: StrictStr
l1_decimals: StrictInt
l1_decimals: Optional[StrictInt] = Field(default=None, description="L1 token decimals when supplied by registration. Omitted for Core-native and EVM-native assets.")
evm_decimals: Optional[StrictInt] = Field(default=None, description="Canonical EVM token decimals for EVM-native assets. Zero is a valid precision; omission means unavailable.")
canonical_domain: Optional[StrictInt] = Field(default=None, description="Asset origin: 0 legacy, 1 L1, 2 Core, 3 EVM. Omitted by older API versions.")
evm_token: Optional[StrictStr] = Field(default=None, description="Linked EVM token or wrapper address. The zero address means unlinked; older API versions omit this field.")
decimals: StrictInt
min_transfer_amount: StrictStr
min_withdrawal_amount: StrictStr
Expand All @@ -45,7 +48,17 @@ class Asset(BaseModel):
liquidation_factor: StrictStr
multiplier: StrictStr
additional_properties: Dict[str, Any] = {}
__properties: ClassVar[List[str]] = ["asset_id", "symbol", "l1_decimals", "decimals", "min_transfer_amount", "min_withdrawal_amount", "margin_mode", "index_price", "l1_address", "global_supply_cap", "liquidation_fee", "liquidation_threshold", "loan_to_value", "price_decimals", "total_supplied", "user_supply_cap", "liquidation_factor", "multiplier"]
__properties: ClassVar[List[str]] = ["asset_id", "symbol", "l1_decimals", "evm_decimals", "canonical_domain", "evm_token", "decimals", "min_transfer_amount", "min_withdrawal_amount", "margin_mode", "index_price", "l1_address", "global_supply_cap", "liquidation_fee", "liquidation_threshold", "loan_to_value", "price_decimals", "total_supplied", "user_supply_cap", "liquidation_factor", "multiplier"]

@field_validator('canonical_domain')
def canonical_domain_validate_enum(cls, value):
"""Validates the enum"""
if value is None:
return value

if value not in set([0, 1, 2, 3]):
raise ValueError("must be one of enum values (0, 1, 2, 3)")
return value

@field_validator('margin_mode')
def margin_mode_validate_enum(cls, value):
Expand Down Expand Up @@ -115,6 +128,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"asset_id": obj.get("asset_id"),
"symbol": obj.get("symbol"),
"l1_decimals": obj.get("l1_decimals"),
"evm_decimals": obj.get("evm_decimals"),
"canonical_domain": obj.get("canonical_domain"),
"evm_token": obj.get("evm_token"),
"decimals": obj.get("decimals"),
"min_transfer_amount": obj.get("min_transfer_amount"),
"min_withdrawal_amount": obj.get("min_withdrawal_amount"),
Expand Down
26 changes: 24 additions & 2 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -5548,7 +5548,30 @@
"l1_decimals": {
"type": "integer",
"format": "uint8",
"example": 18
"example": 18,
"description": "L1 token decimals when supplied by registration. Omitted for Core-native and EVM-native assets."
},
"evm_decimals": {
"type": "integer",
"format": "uint8",
"example": 18,
"description": "Canonical EVM token decimals for EVM-native assets. Zero is a valid precision; omission means unavailable."
},
"canonical_domain": {
"type": "integer",
"format": "uint8",
"enum": [
0,
1,
2,
3
],
"description": "Asset origin: 0 legacy, 1 L1, 2 Core, 3 EVM. Omitted by older API versions."
},
"evm_token": {
"type": "string",
"example": "0x0000000000000000000000000000000000000000",
"description": "Linked EVM token or wrapper address. The zero address means unlinked; older API versions omit this field."
},
"decimals": {
"type": "integer",
Expand Down Expand Up @@ -5621,7 +5644,6 @@
"required": [
"asset_id",
"symbol",
"l1_decimals",
"decimals",
"min_transfer_amount",
"min_withdrawal_amount",
Expand Down
35 changes: 35 additions & 0 deletions test/test_asset_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Decode asset metadata from legacy and native-domain API responses."""

import pytest

from lighter.models.asset import Asset
from lighter.models.asset_details import AssetDetails


@pytest.mark.parametrize("metadata", [
{"l1_decimals": 6},
{"canonical_domain": 1, "l1_decimals": 18, "evm_token": "0x" + "11" * 20},
{"canonical_domain": 2, "evm_token": "0x" + "00" * 20},
{"canonical_domain": 3, "evm_decimals": 18, "evm_token": "0x" + "22" * 20},
{"canonical_domain": 3, "evm_decimals": 0, "evm_token": "0x" + "00" * 20},
])
def test_asset_metadata_response(metadata):
payload = {
"asset_id": 7, "symbol": "TOKEN", "decimals": 8,
"min_transfer_amount": "0.01", "min_withdrawal_amount": "0.01",
"margin_mode": "disabled", "index_price": "2.500000000",
"l1_address": "0x" + "00" * 20, "price_decimals": 9,
"global_supply_cap": "0", "user_supply_cap": "0", "total_supplied": "0",
"liquidation_fee": "0", "liquidation_threshold": "0", "loan_to_value": "0",
"liquidation_factor": "0", "multiplier": "1", **metadata,
}
response = AssetDetails.from_dict({"code": 200, "asset_details": [payload]})
asset = response.asset_details[0]
# Direct model validation must support the same field presence as HTTP decoding.
validated = Asset.model_validate(payload)
for field in ("l1_decimals", "evm_decimals", "canonical_domain", "evm_token"):
assert getattr(asset, field) == metadata.get(field)
assert getattr(validated, field) == metadata.get(field)
assert (field in asset.to_dict()) == (field in metadata)
assert asset.to_dict() == payload
assert asset.additional_properties == {}