关于 add-vault-protocol
用于为 eth_defi 库集成新 ERC-4626 金库协议的开发技能。它覆盖协议识别、金库类、元数据、徽标、文档和测试等实现环节。
主要功能
- 01下载并保存合约 ABI
- 02实现协议识别与金库类
- 03添加风险和费用信息
- 04创建协议元数据与徽标
- 05更新文档并运行测试
适用场景
- 01将新 ERC-4626 协议接入 eth_defi
- 02为协议配置检测逻辑和金库实例
- 03完善协议元数据、徽标及文档
- 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 金库协议的支持。
所需输入
开始前,请向用户收集以下信息:
- 金库智能合约地址 - 区块链上一个示例金库合约的地址
- 协议名称 - 便于用户识别的名称(例如,“Plutus”“IPOR”“Morpho”)
- 协议 slug - 用于代码的 snake_case 标识符(例如,“plutus”“ipor”“morpho”)
- 链 - 使用的区块链(Ethereum、Arbitrum、Base 等)
- 区块浏览器 URL - 用于获取 ABI(例如,Etherscan、Arbiscan、Basescan)
- 单金库协议:某些协议,特别是发行自有稳定币的协议,已知只有一个用于稳定币质押的金库。Spark、Ethena、Cap 等协议就是这样的例子。在这种情况下,请在后续使用
HARDCODED_PROTOCOLS分类,因为如果协议不需要复杂的金库智能合约检测模式,就没有必要创建这些模式。 - 风险等级:可选。如果未提供,请设为
None
完成要求
只有满足以下条件,新的金库协议集成才算完成:
- 协议检测或硬编码地址分类
- 金库类及
create_vault_instance()接线 - 风险和费用矩阵条目
eth_defi/data/vaults/metadata/下的协议元数据 YAML- 原始和后处理后的协议 logo
- 金库文档和 API 文档条目
- 针对新协议的专门测试
分步实现
步骤 1:下载并存储 ABI
- 从区块浏览器获取金库智能合约 ABI
- 重要:如果合约是代理合约,你需要的是实现合约 ABI,而不是代理合约 ABI
- 检查合约是否有
implementation()函数或类似函数 - 使用区块浏览器的“Read as Proxy”功能获取实现合约地址
- 下载实现合约的 ABI
- 检查合约是否有
- 创建 ABI 目录和文件:
eth_defi/abi/{protocol_slug}/ eth_defi/abi/{protocol_slug}/{ContractName}.json - 参考
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:
- 在
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
- 在
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 中说明原因。
- 使用仓库本地的
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,记录来源和选择依据
- 阅读
- 使用仓库本地的
post-process-logo技能。- 阅读
.claude/skills/post-process-logo/SKILL.md - 在
eth_defi/data/vaults/formatted_logos/{protocol-slug}/下创建后处理的 256x256 PNG 徽标 - 如果来源支持浅色和深色版本,至少生成
light.png和dark.png;否则生成最佳可用版本并说明限制
- 阅读
- 验证元数据导出器能够找到这些徽标:
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}
填写字段时:
- feeder-id:与文件名相同的 slug(不含
.yaml) - name:易读的协议名称,与
get_vault_protocol_name()的输出一致 - role:金库协议始终使用
protocol - website:协议主页 URL(已在前面的步骤中收集)
- twitter:不带
@的 Twitter/X 账号名——在协议主页上查找。如果找不到 Twitter,则省略此字段。 - linkedin:LinkedIn 公司页面 slug(即
linkedin.com/company/后面的部分)。通过网络搜索"{protocol name}" site:linkedin.com/company查找。如果找不到,则省略此字段。 - 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}
- Medium 博客:
简单示例:
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
查找唯一的协议标识符
查找能够唯一识别该协议的函数:
-
阅读 ABI 并查找:
- 协议专属的角色常量(例如,Plutus 的
SAY_TRADER_ROLE()) - 自定义 getter 函数(例如,IPOR 的
getPerformanceFeeData()) - 协议注册表调用(例如,Morpho 的
MORPHO()) - 唯一的配置函数
- 协议专属的角色常量(例如,Plutus 的
-
检查该函数是否确实唯一,确认其他协议中不存在该函数
-
如果不存在唯一函数,某些协议可能需要基于名称进行检测:
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"
}
]