add-vault-protocol について
eth_defi ライブラリに新しい ERC-4626 ボールトプロトコルを統合するための開発スキルです。プロトコル検出、ボールトクラス、メタデータ、ロゴ、ドキュメント、テストの作成を案内します。
主な機能
- 01コントラクト ABI の取得と保存
- 02プロトコル検出とボールトクラスの実装
- 03リスク情報と手数料情報の追加
- 04プロトコルのメタデータとロゴの作成
- 05ドキュメント更新とテスト実行
活用シーン
- 01eth_defi への新規 ERC-4626 プロトコル統合
- 02プロトコル検出とボールトインスタンスの設定
- 03プロトコルのメタデータ、ロゴ、ドキュメントの整備
- 04新規プロトコルとボールト統合のテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tradingstrategy-ai/ web3-ethereum-defi --skill add-vault-protocolインストール後、Claude Code で/add-vault-protocolを入力して読み込まれたか確認します。
詳細
vault プロトコルを追加する
このスキルでは、eth_defi ライブラリに新しい ERC-4626 vault プロトコルのサポートを追加する手順を説明します。
必要な情報
開始する前に、ユーザーから次の情報を集めます。
- vault のスマートコントラクトアドレス - ブロックチェーン上にあるサンプルの vault コントラクトのアドレス
- プロトコル名 - 人が読める名前(例:「Plutus」、「IPOR」、「Morpho」)
- プロトコルのスラッグ - コードで使う snake_case の識別子(例:「plutus」、「ipor」、「morpho」)
- チェーン - 対象のブロックチェーン(Ethereum、Arbitrum、Base など)
- ブロックエクスプローラーの URL - ABI の取得に使用します(例:Etherscan、Arbiscan、Basescan)
- 単一 vault プロトコル:独自のステーブルコインを発行するプロトコルなど、ステーブルコインのステーキング用 vault が 1 つしかないことが分かっているプロトコルもあります。Spark、Ethena、Cap などが該当します。この場合は、後の手順で
HARDCODED_PROTOCOLSに分類します。検出が不要なプロトコルに対し、複雑な vault スマートコントラクト検出パターンを作成する必要はありません。 - リスクレベル:任意です。指定がない場合は
Noneを設定します。
完了要件
新しい vault プロトコルの統合が完了したといえるのは、次の項目がすべて含まれている場合のみです。
- プロトコルの検出、またはアドレスのハードコード分類
- vault クラスと
create_vault_instance()の配線 - リスクと手数料のマトリックス項目
eth_defi/data/vaults/metadata/以下のプロトコルメタデータ YAML- 元のプロトコルロゴと後処理済みロゴ
- vault のドキュメントと API ドキュメントの項目
- 新しいプロトコルに特化したテスト
実装手順
手順 1:ABI をダウンロードして保存する
- ブロックチェーンエクスプローラーから vault スマートコントラクトの 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:vault クラスを作成する
eth_defi/erc_4626/vault_protocol/{protocol_slug}/vault.py を、次のパターンに従って作成します。
eth_defi/erc_4626/vault_protocol/plutus/vault.py- 手数料がハードコードされたシンプルな vaulteth_defi/erc_4626/vault_protocol/ipor/vault.py- カスタムの手数料読み取りと multicall サポートを備えた複雑な vault
vault クラスには、次の内容を含めます。
"""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() については、プロトコルのウェブサイトを確認し、vault への直接リンクの URL パターンを調べます。一般的な形式は次のとおりです。
- アドレスによるリンク
- チェーン ID とアドレスによるリンク(例:Ethereum のチェーン ID は 1)
- チェーン名とアドレスによるリンク -
get_chain_name(chain_id).lower()などを使用します。 - vault が 1 つだけのプロトコルでは、パターンを使わない単一のリンクの場合があります。
- 判別できない場合は、プロトコルのホームページへのリンクにします。
手順 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 を作成します。
- プロトコルのスラッグに複数の単語が含まれる場合は、メタデータのファイル名にハイフン区切りのスラッグを使用します。
- 名前、スラッグ、短い説明、長い説明、手数料の説明、リンク、例となるスマートコントラクトを含めます。
- スキーマの参照には
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を読みます。- 後処理した 256x256 の PNG ロゴを
eth_defi/data/vaults/formatted_logos/{protocol-slug}/に作成します。 - ソースが両方に対応している場合は、少なくとも
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:module 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 スタブへの相互参照を追加します- プロトコル名を含めます
- Web で検索し、簡潔な説明を 2 段落で作成します
- プロトコルのホームページとドキュメントへのリンクを追加します
- Github/web でスマートコントラクトの Github リポジトリへのリンクを検索します
- プロトコルのホームページで Twitter のリンクを検索し、ドキュメントに追加します
- プロトコルのホームページとドキュメントで監査ページを検索します
- プロトコルのホームページとドキュメントで手数料ページを検索します
- DefiLlama にこのプロトコルのページがあるか確認します
- 新しいモジュールをプロトコルのインデックスページの TOC に追加します
- プロトコルを
docs/source/vaults/index.rstのマスターインデックスに追加します
例:
docs/source/vaults/plutus/index.rst、docs/source/vaults/truefi/index.rst、docs/source/api/vaults/index.rst、
ステップ 12:すべてのボールトプロトコル検出テストを実行する
新しいボールトプロトコルを追加した後、すべての ERC-4626 テストがパスすることを確認するため、tests/erc_4626/vault_protocol フォルダー内のすべてのテストを実行します。
すべてのボールトテストを実行します。
source .local-test.env && poetry run pytest -n auto -k vault_protocol
問題が見つかった場合は修正します。
ステップ 13:コードベースをフォーマットする
新しく追加したファイルを poetry run ruff format でフォーマットします。
ステップ 14:フィードプロトコルの YAML エントリーを追加する
eth_defi/data/feeds/protocols/{protocol-slug}.yaml にフィード YAML ファイルを作成し、フィードスキャナーがプロトコルのソーシャルメディア投稿を収集できるようにします。スキーマの詳細と収集動作については、eth_defi/feed/README-feed.md を参照してください。
アンダースコアではなくハイフンを使ったプロトコルスラッグを使用します。例: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:ファイル名と同じスラッグ(
.yamlを除く) - name:人が読めるプロトコル名。
get_vault_protocol_name()の出力と一致させます - role:ボールトプロトコルでは常に
protocolを指定します - website:プロトコルのホームページ URL(前の手順で取得済み)
- twitter:
@を付けない Twitter/X ハンドル。プロトコルのホームページで確認します。Twitter が見つからない場合は、このフィールドを省略します - linkedin:LinkedIn 企業ページのスラッグ(
linkedin.com/company/の後の部分)。"{protocol name}" site:linkedin.com/companyを Web 検索して確認します。見つからない場合は、このフィールドを省略します - rss:RSS/Atom フィードの 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 フィードがない場合は、コメント
# 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 アカウントにつながることを確認する
- フィード 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"
}
]