more-vaults について
既存のコードベースに ERC-4626 金庫プロトコルを追加するためのコーディングスキルです。ABI、プロトコル判定、金庫クラス、テスト、ドキュメントの更新を案内します。
主な機能
- 01プロトコルとコントラクト情報の収集
- 02コントラクト ABI の追加と保存
- 03プロトコル用金庫クラスの実装
- 04プロトコル判定とインスタンス生成の追加
- 05テスト、ドキュメント、変更履歴の更新
活用シーン
- 01新しい金庫プロトコルの統合
- 02プロトコル固有の金庫判定の追加
- 03金庫対応のテストと記録
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tradingstrategy-ai/ web3-ethereum-defi --skill more-vaultsインストール後、Claude Code で/more-vaultsを入力して読み込まれたか確認します。
詳細
プロトコルにボールトを追加する
このスキルでは、既存のボールトにスマートコントラクトの種類を追加する方法を説明します。
必要な入力情報
開始する前に、以下の情報をユーザーから集めてください。
- ボールトプロトコル名:コードベースにある既存のボールトプロトコル
- ボールトクラス名:このボールトのスマートコントラクトの種類に使用する Python クラス名
- ボールトのスマートコントラクトアドレス - ブロックチェーン上にあるサンプルのボールトコントラクトのアドレス
- チェーン - どのブロックチェーンか(Ethereum、Arbitrum、Base など)
- ブロックエクスプローラーの URL - ABI を取得するためのもの(例:Etherscan、Arbiscan、Basescan)
- 単一ボールトプロトコル:独自のステーブルコインを発行するプロトコルなど、一部のプロトコルでは、ステーブルコインのステーキングに使うボールトが1つしかないことが知られています。Spark、Ethena、Cap などがその例です。この場合は、複雑なボールトスマートコントラクトの検出パターンを作成する必要がないため、後の手順で
HARDCODED_PROTOCOLSに分類してください。
実装手順
ステップ 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:ボールトクラスを作成する
既存のボールトプロトコルの Python モジュールを更新し、クラス定義を追加します。
ボールトクラスは、既存のモジュールと同じ階層にある新しいモジュールに配置します。
"""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 {VaultClassName}(ERC4626Vault):
"""Protocol vault support.
Add few lines descriptiong of the protocol here, from the protocol documentation.
- Add links to protocol documentation
- Add links to example contracts on block explorers
- Add links to github
"""
def has_custom_fees(self) -> bool:
"""Whether this vault has deposit/withdrawal fees."""
return False # Adjust based on protocol
def get_management_fee(self, block_identifier: BlockIdentifier) -> float:
"""Get the current management fee as a percent.
:return:
0.1 = 10%
"""
# Implement based on protocol's fee structure
# Generated: Human can add details later
return None
def get_performance_fee(self, block_identifier: BlockIdentifier) -> float | None:
"""Get the current performance fee as a percent.
:return:
0.1 = 10%
"""
# Implement based on protocol's fee structure
# Generated: Human can add details later
return None
def get_estimated_lock_up(self) -> datetime.timedelta | None:
"""Get estimated lock-up period if any."""
return None
def get_link(self, referral: str | None = None) -> str:
"""Get the vault's web UI link."""
return f"https://protocol-url.com/vault/{self.vault_address}"
get_link() については、プロトコルのウェブサイトを確認し、ボールトへの直接リンクの URL パターンを探してください。一般的な形式は以下のとおりです。
- アドレスによるもの
- チェーン ID とアドレスによるもの(例:Ethereum のチェーン ID は 1)
- チェーン名とアドレスによるもの -
get_chain_name(chain_id).lower()などを使用します - ボールトが1つしかないプロトコルでは、パターンのない単一のリンクの場合があります
- 判断できない場合は、プロトコルのホームページにリンクしてください
ステップ 3:プロトコル識別プローブを追加する
eth_defi/erc_4626/classification.py を編集します。
create_probe_calls()で、このプロトコルを一意に識別するプローブ呼び出しを追加します。- ABI とボールト実装のスマートコントラクトのソースコードを分析し、このプロトコルに固有の関数を探します
getProtocolSpecificData()やカスタムロール定数などの関数を探し、すでにcreate_probe_calls()に実装されているものと比較します- この呼び出しが、すでに設定されているプロトコルと競合しないことを確認します
- ブロックチェーンエクスプローラーの Contract > Read contract、または Contract Read contract as proxy を使って、この種類のスマートコントラクトを検出するために適した 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)
ステップ 5:テストファイルを更新する
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)
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
- テストファイルを正しいブロックチェーン用に更新します
- ブロックチェーンエクスプローラーで最新のブロック番号を調べ、
fork_block_numberを更新します - テストを実行したときに、このチェーンの JSON-RPC がユーザーの環境に設定されていない場合は、スキルを中断し、テスト環境変数を更新するようユーザーに伝えてください
追加後、テストモジュールを実行し、問題があれば修正します。
ステップ 7:Vault プロトコル関連のすべてのテスト
Vault を追加したため、Vault プロトコルのテストをすべて再実行します。
ステップ 8:コードベースのフォーマット
新しく追加したファイルを poetry run ruff format でフォーマットします。
ステップ 9:ドキュメントの更新
docs/source/vaults に、既存のモジュールとともに新しく作成したモジュールを追加します。
index.rst も忘れずに更新してください。
ステップ 10:検証チェックリスト
実装後、以下を確認してください。
- ABI ファイルが
eth_defi/abi/{protocol_slug}/に正しく配置されている - Vault クラスが
ERC4626Vaultを継承している -
ERC4626Featureenum に新しいプロトコルが追加されている -
get_vault_protocol_name()が正しい名前を返す -
create_probe_calls()にそのプロトコル固有のプローブがある -
identify_vault_features()がそのプロトコルを正しく識別する -
create_vault_instance()が正しい Vault クラスを作成する - テストファイルが次のコマンドで正常に実行される:
source .local-test.env && poetry run pytest tests/erc_4626/vault_protocol/test_{protocol_slug}.py -v - API ドキュメントが更新されている
- API ドキュメントのホームページリンクが正しいホームページにつながることを確認する
- API ドキュメントの Twitter リンクが機能し、プロトコルのホームページに記載されているものと同じ Twitter アカウントにつながることを確認する
チェックリストに問題がある場合は、人に支援を求めてください。
ステップ 12:変更履歴
CHANGELOG.mdの変更履歴の行を更新し、新しいプロトコルを追加した旨を記載します
ステップ 12:プルリクエスト(任意)
すべて完了したら、ユーザーから依頼された場合に限り、プルリクエストを作成します。
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()) - カスタムゲッター関数(例: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"
}
]