Skip to content
Open
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
11 changes: 11 additions & 0 deletions meta/columns.json
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,17 @@
"cellType": null,
"group": "security"
},
"keyExportFormats": {
"key": "keyExportFormats",
"label": "Key Export Formats",
"icon": "lucide:FileKey",
"description": "Documented key/backup export artifact classes.",
"filter": "searchableMultiSelect",
"sorting": "arrayLength",
"pinning": null,
"cellType": "arrayPopover",
"group": "security"
},
"native": {
"key": "native",
"label": "Native",
Expand Down
55 changes: 55 additions & 0 deletions tools/csv_to_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"license",
)

KEY_EXPORT_FORMATS_COLUMN = "keyExportFormats"
KEY_EXPORT_FORMAT_NAMES = ("encrypted-backup", "mnemonic", "raw-private-key")


def col_letter(idx: int) -> str:
"""Convert 0-based index to Excel column letters."""
Expand Down Expand Up @@ -52,6 +55,49 @@ def is_trueish(value: str) -> bool:
return isinstance(value, str) and value.strip().lower() == "true"


def validate_key_export_formats(items: list, context: str) -> list[str]:
"""Validate keyExportFormats arrays for wallets rows (DBIP #3725).

Rules:
- blank/None is valid (not yet verified; listings inherit);
- [] is valid (explicit: none of the documented formats was found on review);
- entries must use the exact enum names (encrypted-backup, mnemonic, raw-private-key);
- entries must be unique;
- non-empty arrays must be in canonical alphabetical order;
- the existing keyExport boolean is never modified here.
"""
errors = []
for idx, item in enumerate(items):
if not isinstance(item, dict):
continue
value = item.get(KEY_EXPORT_FORMATS_COLUMN)
if value is None:
continue
slug = item.get("slug") or f"row {idx + 2}"
label = f"{context}: wallets '{slug}': {KEY_EXPORT_FORMATS_COLUMN}"
if not isinstance(value, list):
errors.append(
f"{label} must be a JSON array or null, got {type(value).__name__}"
)
continue
names_valid = True
seen = set()
for pos, name in enumerate(value):
where = f"{label}[{pos}]"
if not isinstance(name, str) or name not in KEY_EXPORT_FORMAT_NAMES:
errors.append(
f"{where} must be one of {list(KEY_EXPORT_FORMAT_NAMES)}, got {name!r}"
)
names_valid = False
continue
if name in seen:
errors.append(f"{where} duplicates format {name!r}")
seen.add(name)
if names_valid and value and list(value) != sorted(value):
errors.append(f"{label} must be sorted alphabetically, got {value!r}")
return errors


def normalize(data_by_category: dict):
result = {}
errors = []
Expand Down Expand Up @@ -773,6 +819,15 @@ def main():

ensure_sdks_tbd_fields(result)

key_export_format_errors = validate_key_export_formats(
result.get("wallets", []), context=f"network '{network_name}'"
)
if key_export_format_errors:
print(f"Validation errors for {KEY_EXPORT_FORMATS_COLUMN} in network '{network_name}':")
for e in key_export_format_errors:
print(e)
exit(1)

result["columns"] = get_column_order(
base_categories=list_categories(network_dir),
extra_categories=global_listings_categories,
Expand Down
8 changes: 8 additions & 0 deletions tools/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,14 @@
"msig": { "type": "boolean" },
"hardware": { "type": "boolean" },
"keyExport": { "type": "boolean" },
"keyExportFormats": {
"type": ["array", "null"],
"items": {
"type": "string",
"enum": ["encrypted-backup", "mnemonic", "raw-private-key"]
},
"uniqueItems": true
},
"native": { "type": "boolean" },
"evm": { "type": "boolean" },
"tendermint": { "type": "boolean" },
Expand Down
Loading