mcpField
A
add-vault-protocol

add-vault-protocol

github.com/tradingstrategy-ai开发82962 次安装今天更新
代码仓库

关于 add-vault-protocol

用于为 eth_defi 库集成新 ERC-4626 金库协议的开发技能。它覆盖协议识别、金库类、元数据、徽标、文档和测试等实现环节。

主要功能

  1. 01下载并保存合约 ABI
  2. 02实现协议识别与金库类
  3. 03添加风险和费用信息
  4. 04创建协议元数据与徽标
  5. 05更新文档并运行测试

适用场景

  1. 01将新 ERC-4626 协议接入 eth_defi
  2. 02为协议配置检测逻辑和金库实例
  3. 03完善协议元数据、徽标及文档
  4. 04运行新协议与全量金库测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tradingstrategy-ai/web3-ethereum-defi --skill add-vault-protocol

安装后在 Claude Code 中输入/add-vault-protocol确认已加载。

详细介绍

添加金库协议

本技能将指导你为 eth_defi 库添加对新 ERC-4626 金库协议的支持。

所需输入

开始前,请向用户收集以下信息:

  1. 金库智能合约地址 - 区块链上一个示例金库合约的地址
  2. 协议名称 - 便于用户识别的名称(例如,“Plutus”“IPOR”“Morpho”)
  3. 协议 slug - 用于代码的 snake_case 标识符(例如,“plutus”“ipor”“morpho”)
  4. 链 - 使用的区块链(Ethereum、Arbitrum、Base 等)
  5. 区块浏览器 URL - 用于获取 ABI(例如,Etherscan、Arbiscan、Basescan)
  6. 单金库协议:某些协议,特别是发行自有稳定币的协议,已知只有一个用于稳定币质押的金库。Spark、Ethena、Cap 等协议就是这样的例子。在这种情况下,请在后续使用 HARDCODED_PROTOCOLS 分类,因为如果协议不需要复杂的金库智能合约检测模式,就没有必要创建这些模式。
  7. 风险等级:可选。如果未提供,请设为 None

完成要求

只有满足以下条件,新的金库协议集成才算完成:

  • 协议检测或硬编码地址分类
  • 金库类及 create_vault_instance() 接线
  • 风险和费用矩阵条目
  • eth_defi/data/vaults/metadata/ 下的协议元数据 YAML
  • 原始和后处理后的协议 logo
  • 金库文档和 API 文档条目
  • 针对新协议的专门测试

分步实现

步骤 1:下载并存储 ABI
  1. 从区块浏览器获取金库智能合约 ABI
  2. 重要:如果合约是代理合约,你需要的是实现合约 ABI,而不是代理合约 ABI
    • 检查合约是否有 implementation() 函数或类似函数
    • 使用区块浏览器的“Read as Proxy”功能获取实现合约地址
    • 下载实现合约的 ABI
  3. 创建 ABI 目录和文件:
    eth_defi/abi/{protocol_slug}/
    eth_defi/abi/{protocol_slug}/{ContractName}.json
    
  4. 参考 eth_defi/abi/lagoon/ 中的结构
步骤 2:创建金库类

按照以下示例中的模式创建 eth_defi/erc_4626/vault_protocol/{protocol_slug}/vault.py:

  • eth_defi/erc_4626/vault_protocol/plutus/vault.py - 费用硬编码的简单金库
  • eth_defi/erc_4626/vault_protocol/ipor/vault.py - 支持自定义费用读取和 multicall 的复杂金库

金库类应当:

"""Module docstring describing the protocol."""

import datetime
import logging

from eth_typing import BlockIdentifier

from eth_defi.erc_4626.vault import ERC4626Vault

logger = logging.getLogger(__name__)


class {ProtocolName}Vault(ERC4626Vault):
    """Protocol vault support.

    One line description of the protocol.

    - Add links to protocol documentation
    - Add links to example contracts on block explorers
    - Add links to github
    - If fee information is documented or available as Github source code, link into it
    """

    def get_management_fee(self, block_identifier: BlockIdentifier) -> float:
        return None

    def get_performance_fee(self, block_identifier: BlockIdentifier) -> float | None:
        return None

    def get_estimated_lock_up(self) -> datetime.timedelta | None:
        return None

    def get_link(self, referral: str | None = None) -> str:
        return f"https://protocol-url.com/vault/{self.vault_address}"

对于 get_link(),请检查协议网站,查找指向其金库的直接链接 URL 模式。常见格式:

  • 按地址
  • 按链 ID 和地址——例如,Ethereum 的链 ID 是 1
  • 按链名称和地址——使用 get_chain_name(chain_id).lower() 或类似方法
  • 对于只有一个金库的协议,可能会提供没有模式的单一链接
  • 如果无法确定,就链接到协议主页
步骤 3:添加协议功能枚举

编辑 eth_defi/erc_4626/core.py,向 ERC4626Feature 添加新的枚举成员:

#: {Protocol Name}
#:
#: {Protocol URL}
{protocol_slug}_like = "{protocol_slug}_like"

同时更新 get_vault_protocol_name(),使其返回协议名称:

elif ERC4626Feature.{protocol_slug}_like in features:
    return "{Protocol Name}"
步骤 4:添加协议识别探测

编辑 eth_defi/erc_4626/classification.py:

  1. 在 create_probe_calls() 中添加一个能够唯一识别此协议的探测调用:
    • 分析 ABI 和金库实现智能合约的源代码,找到此协议独有的函数
    • 查找类似 getProtocolSpecificData() 的函数、自定义角色常量等,并与 create_probe_calls() 中已有的实现进行比较
    • 确保此调用不会与已配置的协议冲突
    • 你也可以使用区块链浏览器的 Contract > Read contract 或 Contract Read contract,作为代理来查找能够检测此类智能合约的合适 ABI 调用
    • 如果该协议是单一金库协议,请改用 classification.py 中的 HARDCODED_PROTOCOLS

如果在 ABI 或金库智能合约源代码中找不到此类访问器函数,请中断此技能并请求用户介入。

# {Protocol Name}
# {Block explorer link}
{protocol_slug}_call = EncodedCall.from_keccak_signature(
    address=address,
    signature=Web3.keccak(text="uniqueFunction()")[0:4],
    function="uniqueFunction",
    data=b"",
    extra_data=None,
)
yield {protocol_slug}_call
  1. 在 identify_vault_features() 中添加检测逻辑:
if calls["uniqueFunction"].success:
    features.add(ERC4626Feature.{protocol_slug}_like)
步骤 5:更新 create_vault_instance()

在 eth_defi/erc_4626/classification.py 中,为新协议在 create_vault_instance() 里添加一个分支:

elif ERC4626Feature.{protocol_slug}_like in features:
    from eth_defi.erc_4626.vault_protocol.{protocol_slug}.vault import {ProtocolName}Vault

    return {ProtocolName}Vault(web3, spec, token_cache=token_cache, features=features)
步骤 6:更新风险和费用信息

在 eth_defi/vault/risk.py 中添加协议占位项。

在 VAULT_PROTOCOL_RISK_MATRIX 中为该协议设置初始风险等级。 如果没有给出,请使用 None,之后会由人工判断更新。

在 eth_defi/vault/fee.py 中添加协议占位项。

对于新添加的协议,将 VAULT_PROTOCOL_FEE_MATRIX 设为 None。

协议名称的拼写应与 get_vault_protocol_name() 一致。

步骤 7:添加协议元数据 YAML

创建 eth_defi/data/vaults/metadata/{protocol-slug}.yaml。

  • 如果协议 slug 包含多个词,元数据文件名中使用带连字符的 slug
  • 包含名称、slug、简短描述、详细描述、费用说明、链接和示例智能合约
  • 将 eth_defi/data/vaults/README.md 用作架构参考
  • 即使 Trading Strategy 列表尚未上线,也要包含 trading_strategy 和 integration_documentation 链接
  • 面向一般读者撰写描述。用通俗语言说明产品提供什么、谁可以使用、有哪些重要的资格或流动性限制,以及费用会如何影响持有人。
  • 不要包含库实现、智能合约接口或数据管道的细节。尤其要避免使用 ERC-4626、gem()、navprice() 和 WAD 缩放等标准和函数名称,以及适配器、扫描器和 TVL 计算的内部细节。将这些细节放在技术集成和 API 文档中。

验证元数据能够解析:

poetry run python - <<'PY'
from pathlib import Path
from eth_defi.vault.protocol_metadata import build_metadata_json
print(build_metadata_json(Path("eth_defi/data/vaults/metadata/{protocol-slug}.yaml"), "https://example.invalid")["name"])
PY
步骤 8:提取并后处理协议徽标

协议徽标是金库协议元数据和前端列表的必需内容。除非按照徽标提取工作流程查找后,仍找不到官方或有充分依据的徽标来源,否则不要跳过此步骤;如果跳过,请在最终回复和徽标 README 中说明原因。

  1. 使用仓库本地的 extract-vault-protocol-logo 技能。
    • 阅读 .claude/skills/extract-vault-protocol-logo/SKILL.md
    • 使用 eth_defi/data/vaults/metadata/{protocol-slug}.yaml 中的主页
    • 将原始徽标保存到 eth_defi/data/vaults/original_logos/{protocol-slug}/
    • 在原始徽标文件夹中添加 README.md,记录来源和选择依据
  2. 使用仓库本地的 post-process-logo 技能。
    • 阅读 .claude/skills/post-process-logo/SKILL.md
    • 在 eth_defi/data/vaults/formatted_logos/{protocol-slug}/ 下创建后处理的 256x256 PNG 徽标
    • 如果来源支持浅色和深色版本,至少生成 light.png 和 dark.png;否则生成最佳可用版本并说明限制
  3. 验证元数据导出器能够找到这些徽标:
poetry run python - <<'PY'
from pathlib import Path
from eth_defi.vault.protocol_metadata import build_metadata_json
metadata = build_metadata_json(Path("eth_defi/data/vaults/metadata/{protocol-slug}.yaml"), "https://example.invalid")
print(metadata["logos"])
PY
步骤 9:创建测试文件

首先,使用 get-block-number 技能获取所选链的最新区块号。

按照 tests/erc_4626/vault_protocol/test_plutus.py 和 tests/erc_4626/vault_protocol/test_goat.py 中的模式,创建 tests/erc_4626/vault_protocol/test_{protocol_slug}.py:

"""Test {Protocol Name} vault metadata"""

import os
from pathlib import Path

import pytest
from web3 import Web3
import flaky

from eth_defi.erc_4626.classification import create_vault_instance_autodetect
from eth_defi.erc_4626.core import get_vault_protocol_name
from eth_defi.erc_4626.vault_protocol.{protocol_slug}.vault import {ProtocolName}Vault
from eth_defi.provider.anvil import fork_network_anvil, AnvilLaunch
from eth_defi.provider.multi_provider import create_multi_provider_web3
from eth_defi.vault.base import VaultTechnicalRisk
from eth_defi.erc_4626.core import ERC4626Feature

JSON_RPC_{CHAIN} = os.environ.get("JSON_RPC_{CHAIN}")

pytestmark = pytest.mark.skipif(
    JSON_RPC_{CHAIN} is None,
    reason="JSON_RPC_{CHAIN} needed to run these tests"
)


@pytest.fixture(scope="module")
def anvil_{chain}_fork(request) -> AnvilLaunch:
    """Fork at a specific block for reproducibility"""
    launch = fork_network_anvil(JSON_RPC_{CHAIN}, fork_block_number={block_number})
    try:
        yield launch
    finally:
        launch.close()


@pytest.fixture(scope="module")
def web3(anvil_{chain}_fork):
    web3 = create_multi_provider_web3(anvil_{chain}_fork.json_rpc_url, retries=2)
    return web3


@flaky.flaky
def test_{protocol_slug}(
    web3: Web3,
    tmp_path: Path,
):
    """Read {Protocol Name} vault metadata"""

    vault = create_vault_instance_autodetect(
        web3,
        vault_address="{vault_address}",
    )

    assert isinstance(vault, {ProtocolName}Vault)
    assert vault.get_protocol_name() == "{Protocol Name}"

    # Add assertation about vault feature flags here, like:
    # assert vault.features == {ERC4626Feature.goat_like}

    # Add assertions for fee data we know
    # assert vault.get_management_fee("latest") == ...
    # assert vault.get_performance_fee("latest") == ...

    # Add assertion for the protcol risk level
    # assert vault.get_risk() == VaultTechnicalRisk.unknown

  • 更新测试文件,使其对应正确的区块链
  • 使用区块链浏览器,通过给定的 JSON-RPC URL 和 Python 的 Web3.py web3.eth.block_number 调用获取最新区块号
  • 运行测试时,如果用户未配置此链的 JSON-RPC,请中断技能并告知用户更新其测试环境变量

添加后,运行测试模块并修复所有问题。

步骤 10:添加模块 init.py

创建 eth_defi/erc_4626/vault_protocol/{protocol_slug}/__init__.py:

"""{Protocol Name} protocol integration."""

步骤 11:更新文档

  • 将协议添加到 docs/source/vaults
  • 将协议添加到 docs/source/vaults/index.rst
  • 在 docs/source/api/{protocol_slug}/index.rst 下添加 API 存根
  • 在 docs/source/api/index.rst 中交叉引用该 API 存根
  • 加入协议名称
  • 搜索网络,撰写两段简短介绍
  • 添加协议主页和文档的链接
  • 在 GitHub 或网络上搜索智能合约的 GitHub 仓库链接
  • 搜索协议主页上的 Twitter 链接,并将其添加到文档中
  • 搜索协议主页和文档中的审计页面
  • 搜索协议主页和文档中的费用页面
  • 检查 DefiLlama 是否有该协议的页面
  • 将新模块添加到协议索引页的目录中
  • 将协议添加到主索引 docs/source/vaults/index.rst

示例包括:

  • docs/source/vaults/plutus/index.rst、docs/source/vaults/truefi/index.rst、docs/source/api/vaults/index.rst,

步骤 12:运行所有金库协议检测测试

添加新金库协议后,运行 tests/erc_4626/vault_protocol 文件夹中的所有测试,确认所有 ERC-4626 测试均通过。

运行所有金库测试:

source .local-test.env && poetry run pytest -n auto -k vault_protocol

如果发现问题,请修复。

步骤 13:格式化代码库

使用 poetry run ruff format 格式化新添加的文件。

步骤 14:添加 feed 协议 YAML 条目

在 eth_defi/data/feeds/protocols/{protocol-slug}.yaml 创建 feed YAML 文件,以便 feed 扫描器收集该协议的社交媒体帖子。完整的架构文档和收集行为说明,请参阅 eth_defi/feed/README-feed.md。

使用带连字符的协议 slug(不要使用下划线)。例如:lagoon-finance、ipor-fusion、goat-protocol。

文件应遵循以下格式:

feeder-id: {protocol-slug}
name: {Protocol Name}
role: protocol
website: {homepage URL}
twitter: {twitter handle without @}
linkedin: {linkedin company slug}
rss: {RSS or Atom feed URL}

填写字段时:

  1. feeder-id:与文件名相同的 slug(不含 .yaml)
  2. name:易读的协议名称,与 get_vault_protocol_name() 的输出一致
  3. role:金库协议始终使用 protocol
  4. website:协议主页 URL(已在前面的步骤中收集)
  5. twitter:不带 @ 的 Twitter/X 账号名——在协议主页上查找。如果找不到 Twitter,则省略此字段。
  6. linkedin:LinkedIn 公司页面 slug(即 linkedin.com/company/ 后面的部分)。通过网络搜索 "{protocol name}" site:linkedin.com/company 查找。如果找不到,则省略此字段。
  7. rss:查找 RSS/Atom feed URL。常见格式:
    • Medium 博客:https://medium.com/feed/@{handle} 或 https://medium.com/feed/{publication}
    • Substack:https://{name}.substack.com/feed
    • 博客页面:检查页面源码中是否有 <link rel="alternate" type="application/rss+xml">
    • 如果没有 RSS feed,请添加注释:# rss: not found — {reason}

简单示例:

feeder-id: plutus
name: Plutus
role: protocol
website: https://plutus.fi/
twitter: plutus_fi_x
rss: https://medium.com/feed/@plutus.fi

无 RSS 的示例:

feeder-id: lagoon-finance
name: Lagoon Finance
role: protocol
website: https://lagoon.finance/
twitter: lagoon_finance
linkedin: lagoon-finance
# rss: not found — blog is at lagoon.finance/blog but has no RSS feed

第 15 步:验证清单

实现后,请验证:

  • ABI 文件已正确放置在 eth_defi/abi/{protocol_slug}/ 中,或该协议有意使用 HARDCODED_PROTOCOLS
  • Vault 类继承自 ERC4626Vault
  • ERC4626Feature 枚举包含新协议
  • get_vault_protocol_name() 返回正确的名称
  • create_probe_calls() 为该协议提供了唯一探测项,或该协议有意使用 HARDCODED_PROTOCOLS
  • identify_vault_features() 或 HARDCODED_PROTOCOLS 能正确识别该协议
  • create_vault_instance() 创建了正确的 Vault 类
  • 测试文件运行成功,命令为:source .local-test.env && poetry run pytest tests/erc_4626/vault_protocol/test_{protocol_slug}.py -v
  • 元数据 YAML 解析成功
  • 原始徽标已保存到 eth_defi/data/vaults/original_logos/{protocol-slug}/
  • 后处理后的徽标已保存到 eth_defi/data/vaults/formatted_logos/{protocol-slug}/
  • 元数据导出器能够识别后处理后的徽标
  • API 文档已更新
  • 检查 API 文档中的主页链接是否指向正确的主页
  • 检查 API 文档中的 Twitter 链接是否有效,并指向与协议主页上列出的相同 Twitter 账号
  • Feed YAML 文件已存在于 eth_defi/data/feeds/protocols/{protocol-slug}.yaml

如果清单检查中遇到问题,请寻求人类协助。

第 16 步:变更日志

  • 更新 CHANGELOG.md 中的变更日志条目,并添加一条关于新增协议的说明

第 17 步:拉取请求(可选)

全部完成后,可以创建拉取请求,但仅限用户提出要求时。

gh pr create \
  --title "Add new vault protocol: {protocol name}" \
  --body  $'Protocol: {protocok name}\nHomepage: {homepage link}\nGithub: {github link}\nDocs: {docs link}\nExample contract: {blockchain explorer link}" \
  --base master

查找唯一的协议标识符

查找能够唯一识别该协议的函数:

  1. 阅读 ABI 并查找:

    • 协议专属的角色常量(例如,Plutus 的 SAY_TRADER_ROLE())
    • 自定义 getter 函数(例如,IPOR 的 getPerformanceFeeData())
    • 协议注册表调用(例如,Morpho 的 MORPHO())
    • 唯一的配置函数
  2. 检查该函数是否确实唯一,确认其他协议中不存在该函数

  3. 如果不存在唯一函数,某些协议可能需要基于名称进行检测:

    name = calls["name"].result
    if name:
        name = name.decode("utf-8", errors="ignore")
        if "ProtocolName" in name:
            features.add(ERC4626Feature.{protocol_slug}_like)
    

ABI 示例结构

ABI JSON 文件应包含合约的 ABI 数组。示例:

{
  "abi": [
    {
      "inputs": [],
      "name": "totalAssets",
      "outputs": [{ "type": "uint256" }],
      "stateMutability": "view",
      "type": "function"
    }
  ]
}

或者直接使用数组:

[
  {
    "inputs": [],
    "name": "totalAssets",
    "outputs": [{ "type": "uint256" }],
    "stateMutability": "view",
    "type": "function"
  }
]

评论

支持 Markdown