EVM链注册表:跨链生态的标准化数据枢纽

1 阅读2分钟

EVM链注册表:跨链生态的标准化数据枢纽

本项目是一个面向EVM(以太坊虚拟机)兼容区块链的标准化数据注册表,旨在为开发者、钱包和跨链桥提供可靠且统一的链配置信息。通过严格的验证流程和工具链,确保每条链的数据完整、符合规范,并防范潜在的安全风险(如重放攻击)。

功能特性

  • 标准化链数据格式:遵循CAIP-2标准,每条链独立存储为JSON文件。
  • 严格的验证机制:自动检查文件名与chainId的一致性,并校验JSON Schema完整性。
  • 安全优先:禁止删除链定义(仅可标记为deprecated),防止历史链被移除导致的重放攻击风险。
  • 图标与资源管理:通过IPFS存储链和浏览器的图标,限定大小与格式,确保资源可解析且轻量。
  • 扩展支持:支持L2链或分片链关联父链,并可配置跨链桥信息。
  • CI友好:提供本地验证脚本,可轻松集成至持续集成流程中。

安装指南

前置要求

  • Node.js (v12或更高版本)
  • npm 或 yarn

步骤

  1. 克隆项目仓库:

    git clone https://github.com/ethereum-lists/chains.git
    cd chains
    
  2. 安装依赖(用于本地验证工具):

    npm install
    
  3. 运行数据验证(确保修改符合规范):

    node scripts/index.js   # 移除过时的network字段(如需要)
    npm run validate        # 执行完整的Schema与文件名检查
    

依赖项

  • ajv: JSON Schema验证器
  • fs, path: Node.js内置文件系统模块

使用说明

添加或修改一条链

  1. _data/chains/ 目录下,按照 {namespace}-{reference}.json 的格式命名文件(例如 eip155-1.json)。
  2. 文件内容需符合以下模板:
{
  "name": "Ethereum Mainnet",
  "chain": "ETH",
  "rpc": ["https://mainnet.infura.io/v3/${INFURA_API_KEY}"],
  "faucets": [],
  "nativeCurrency": {
    "name": "Ether",
    "symbol": "ETH",
    "decimals": 18
  },
  "features": [{ "name": "EIP155" }, { "name": "EIP1559" }],
  "infoURL": "https://ethereum.org",
  "shortName": "eth",
  "chainId": 1,
  "networkId": 1,
  "icon": "ethereum",
  "explorers": [
    {
      "name": "etherscan",
      "url": "https://etherscan.io",
      "icon": "etherscan",
      "standard": "EIP3091"
    }
  ]
}
  1. 如果链属于L2或分片,可添加 parent 字段:
"parent": {
  "type": "L2",
  "chain": "eip155-1",
  "bridges": [{ "url": "https://bridge.arbitrum.io" }]
}

添加图标

  1. _data/icons/ 下创建与 icon 字段同名的JSON文件(例如 ethereum.json)。
  2. 图标文件内容示例:
[
  {
    "url": "ipfs://QmdwQDr6vmBtXmK2TmknkEuZNoaDqTasFdZdu3DRw8b2wt",
    "width": 1000,
    "height": 1628,
    "format": "png"
  }
]

注意:图标URL必须可通过IPFS解析,且文件小于250KB。

验证数据完整性

使用提供的验证脚本检查所有链配置:

npm run validate

该脚本将:

  • 检查文件名是否与文件内的 chainId 匹配。
  • 验证JSON Schema是否符合预定义规范。
  • 输出错误列表并返回非零退出码(用于CI失败标记)。

核心代码

链数据清理工具 (index.js)

移除历史遗留的 network 字段,保持数据结构纯净。

const fs = require('fs');
const chainFiles = fs.readdirSync('../_data/chains/');

for (const chainFile of chainFiles) {
    const fileLocation = `../_data/chains/${chainFile}`
    const fileData = fs.readFileSync(fileLocation, 'utf8')
    const fileDataJson = JSON.parse(fileData)

    if (fileDataJson.network) {
        delete fileDataJson.network
        fs.writeFileSync(fileLocation, JSON.stringify(fileDataJson, null, 2))
    }
}

验证脚本 (validate.js)

完整的链数据验证逻辑,包括文件名与chainId一致性、Schema校验。

const fs = require("fs")
const Ajv = require("ajv")
const ajv = new Ajv()
const schema = require("./schema/chainSchema.json")
const { exit } = require("process")
const path = require('path')

const resolve = (_path) => path.resolve(__dirname, _path)
const chainFiles = fs.readdirSync(resolve("../_data/chains/"))

const parseChainId = (chainId) =>
  /^(?<namespace>[-a-z0-9]{3,8})-(?<reference>[-a-zA-Z0-9]{1,32})$/u.exec(
    chainId
  )

const filesWithErrors = []
for (const chainFile of chainFiles) {
  const fileLocation = resolve(`../_data/chains/${chainFile}`)
  const fileData = fs.readFileSync(fileLocation, "utf8")
  const fileDataJson = JSON.parse(fileData)
  const fileName = chainFile.split(".")[0]
  const parsedChainId = parseChainId(fileName)?.groups
  const chainIdFromFileName = parsedChainId?.reference
  if (chainIdFromFileName != fileDataJson.chainId) {
    throw new Error(`File Name does not match with ChainID in ${chainFile}`)
  }
  const valid = ajv.validate(schema, fileDataJson)
  if (!valid) {
    console.error(ajv.errors)
    filesWithErrors.push(chainFile)
  }
}

if (filesWithErrors.length > 0) {
  filesWithErrors.forEach(file => {
    console.error(`Invalid JSON Schema in ${file}`)
  })
  exit(-1);
}
else {
  console.info("Schema check completed successfully");
  exit(0);
}

GVT9NGsW4qUHR+TkzmCCGfUB9PP7a+z/fAZNhckVobk=