Files

293 lines
9.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Pezkuwi Wallet Chain Config Merger
This script merges Nova's chain configurations with Pezkuwi-specific chains.
Uses a blacklist to exclude broken/paused chains.
Usage:
python3 merge-chains.py [--version v22] [--full] [--update]
Options:
--full Include ALL Nova chains (including broken ones) - NOT recommended
--update Update Nova submodule first
"""
import json
import argparse
from pathlib import Path
# Base paths
SCRIPT_DIR = Path(__file__).parent
ROOT_DIR = SCRIPT_DIR.parent
NOVA_BASE = ROOT_DIR / "nova-base"
PEZKUWI_OVERLAY = ROOT_DIR / "pezkuwi-overlay"
OUTPUT_DIR = ROOT_DIR / "chains"
# Chains with known broken RPC endpoints
BROKEN_CHAIN_KEYWORDS = [
'aleph zero',
'alephzero',
'quartz',
'invarch',
'exosama',
'deepbrain',
]
# These chains have broken endpoints or are not useful
EXCLUDED_CHAIN_IDS = {
# AlephZero - DNS failures
'70255b4d28de0fc4e1a193d7e175ad1ccef431598211c55538f1018651a0344e',
# Quartz - DNS failures
'cd4d732201ebe5d6b014edda071c4203e16867305332f43c2e25ae6c9a1b7e6f',
# InvArch - PAUSED
'31a7d8914fb31c249b972f18c115f1e22b4b039abbcb03c73b6774c5642f9efe',
# Aleph Zero EVM - PAUSED
'eip155:41455',
# Darwinia Crab - DNS failure
'86e49c195aeae7c5c4a86ced251f1a28c67b3c35d8289c387ede1776cdd88b24',
# DeepBrain - SSL certificate mismatch
'03aa6b475a03f8baf7f83e448513b00eaab03aefa4ed64bd1d31160dce028add',
# Exosama - 403 Forbidden
'eip155:2109',
}
def load_json(path: Path) -> list | dict:
"""Load JSON file."""
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
def save_json(path: Path, data: list | dict, indent: int = 2):
"""Save JSON file."""
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=indent, ensure_ascii=False)
print(f"✓ Saved: {path}")
def is_chain_excluded(chain: dict) -> tuple[bool, str]:
"""
Check if a chain should be excluded.
Returns:
(excluded: bool, reason: str)
"""
chain_id = chain.get('chainId', '')
name = chain.get('name', '')
options = chain.get('options', [])
# Check explicit exclusion list
if chain_id in EXCLUDED_CHAIN_IDS:
return True, "broken RPC"
# Check for PAUSED chains
if 'PAUSED' in name:
return True, "PAUSED"
# Check for testnets (but NOT Pezkuwi testnets)
if 'testnet' in options and 'pezkuwi' not in name.lower() and 'zagros' not in name.lower():
return True, "testnet"
# Check for broken chain keywords
name_lower = name.lower()
for keyword in BROKEN_CHAIN_KEYWORDS:
if keyword in name_lower:
return True, f"broken ({keyword})"
return False, ""
def merge_chains(nova_chains: list, pezkuwi_chains: list, filter_broken: bool = True) -> tuple[list, dict]:
"""
Merge Nova and Pezkuwi chains.
Args:
nova_chains: Nova's chain list
pezkuwi_chains: Pezkuwi's chain list
filter_broken: Whether to filter out broken chains
Returns:
(merged_list, stats_dict)
"""
# Create a set of Pezkuwi chain IDs to avoid duplicates
pezkuwi_chain_ids = {c['chainId'] for c in pezkuwi_chains}
stats = {
'pezkuwi': len(pezkuwi_chains),
'nova_total': len(nova_chains),
'nova_included': 0,
'excluded_paused': 0,
'excluded_testnet': 0,
'excluded_broken': 0,
'excluded_duplicate': 0,
}
nova_filtered = []
excluded_chains = []
for chain in nova_chains:
chain_id = chain.get('chainId', '')
# Skip duplicates
if chain_id in pezkuwi_chain_ids:
stats['excluded_duplicate'] += 1
continue
# Check if should be excluded
if filter_broken:
excluded, reason = is_chain_excluded(chain)
if excluded:
excluded_chains.append((chain.get('name', 'Unknown'), reason))
if 'PAUSED' in reason:
stats['excluded_paused'] += 1
elif 'testnet' in reason:
stats['excluded_testnet'] += 1
else:
stats['excluded_broken'] += 1
continue
nova_filtered.append(chain)
stats['nova_included'] += 1
# Pezkuwi first, then Nova
merged = pezkuwi_chains + nova_filtered
stats['total'] = len(merged)
stats['excluded_list'] = excluded_chains
return merged, stats
def merge_version(version: str = "v22", filter_broken: bool = True):
"""Merge chains for a specific version."""
print(f"\n{'='*60}")
print(f"Merging chains for {version}")
print(f"Mode: {'FILTERED (exclude broken)' if filter_broken else 'FULL (all chains)'}")
print(f"{'='*60}")
# Paths
nova_chains_path = NOVA_BASE / "chains" / version / "chains.json"
pezkuwi_chains_path = PEZKUWI_OVERLAY / "chains" / "pezkuwi-chains.json"
output_path = OUTPUT_DIR / version / "chains.json"
# Check if Nova chains exist
if not nova_chains_path.exists():
print(f"⚠ Nova chains not found: {nova_chains_path}")
nova_chains_path = NOVA_BASE / "chains" / "chains.json"
if not nova_chains_path.exists():
print(f"✗ Nova chains not found at root level either")
return False
# Load Nova chains
print(f"\nLoading Nova chains from: {nova_chains_path}")
nova_chains = load_json(nova_chains_path)
print(f"{len(nova_chains)} Nova chains available")
# Load Pezkuwi chains
if not pezkuwi_chains_path.exists():
print(f"⚠ Pezkuwi chains not found: {pezkuwi_chains_path}")
pezkuwi_chains = []
else:
print(f"Loading Pezkuwi chains from: {pezkuwi_chains_path}")
pezkuwi_chains = load_json(pezkuwi_chains_path)
print(f"{len(pezkuwi_chains)} Pezkuwi chains loaded")
# Merge
merged, stats = merge_chains(nova_chains, pezkuwi_chains, filter_broken)
# Print stats
print(f"\n{''*40}")
print("📊 Merge Statistics:")
print(f"{''*40}")
print(f" Pezkuwi chains: {stats['pezkuwi']:3} (priority)")
print(f" Nova available: {stats['nova_total']:3}")
print(f" Nova included: {stats['nova_included']:3}")
print(f"{''*40}")
if filter_broken:
print(f" Excluded (PAUSED): {stats['excluded_paused']:3}")
print(f" Excluded (testnet): {stats['excluded_testnet']:3}")
print(f" Excluded (broken): {stats['excluded_broken']:3}")
print(f" Excluded (dupes): {stats['excluded_duplicate']:3}")
print(f"{''*40}")
print(f" TOTAL OUTPUT: {stats['total']:3} chains")
print(f"{''*40}")
# Save
save_json(output_path, merged)
# Also copy to root chains.json for compatibility
root_output = OUTPUT_DIR / "chains.json"
save_json(root_output, merged)
# Also save to android subdirectory (this is what the app fetches)
android_output = OUTPUT_DIR / version / "android" / "chains.json"
save_json(android_output, merged)
return True
def update_nova_submodule():
"""Pull latest Nova changes."""
import subprocess
print("\nUpdating Nova submodule...")
try:
result = subprocess.run(
["git", "submodule", "update", "--remote", "nova-base"],
cwd=ROOT_DIR,
capture_output=True,
text=True
)
if result.returncode == 0:
print("✓ Nova submodule updated")
else:
print(f"⚠ Update warning: {result.stderr}")
except Exception as e:
print(f"✗ Failed to update submodule: {e}")
def main():
parser = argparse.ArgumentParser(description="Merge Nova and Pezkuwi chain configs")
parser.add_argument("--version", "-v", default="v22", help="Chain config version (default: v22)")
parser.add_argument("--update", "-u", action="store_true", help="Update Nova submodule first")
parser.add_argument("--full", "-f", action="store_true", help="Include ALL chains (no filtering)")
parser.add_argument("--all", "-a", action="store_true", help="Merge all versions")
args = parser.parse_args()
print("╔════════════════════════════════════════════════════════════╗")
print("║ Pezkuwi Wallet Chain Config Merger ║")
print("║ Nova Base + Pezkuwi Overlay Architecture ║")
print("╚════════════════════════════════════════════════════════════╝")
# Update Nova if requested
if args.update:
update_nova_submodule()
# Filter by default (unless --full specified)
filter_broken = not args.full
# Merge
if args.all:
versions = ["v21", "v22"]
for v in versions:
merge_version(v, filter_broken)
else:
merge_version(args.version, filter_broken)
print("\n" + "="*60)
print("✓ Merge complete!")
print("="*60)
if filter_broken:
print("\n📋 Filtered mode:")
print(" - PAUSED chains excluded")
print(" - Testnets excluded (except Pezkuwi)")
print(" - Broken RPC chains excluded")
print("\n💡 To include all 98 Nova chains, run with --full flag")
if __name__ == "__main__":
main()