はじめに
DeFi(分散型金融)の世界では、数千以上のプロトコルが稼働しており、それぞれが異なるチェーン上でイールドファーミングの機会を提供しています。しかし、これらのプールを手作業で比較するのは非常に困難です。
そこで、DeFiLlama APIを活用して、16,000以上のDeFiプールからリアルタイムで最適なイールドを検索できるCLIツール「defi-yield-finder」を作りました。
特徴:
- 外部依存ゼロ(Node.js標準の
httpsモジュールのみ) - 単一ファイル(150行未満)
- 8チェーン対応: Ethereum, Solana, Arbitrum, Optimism, Polygon, BSC, Base, Avalanche
- ステーブルコイン・シングルアセットフィルタリング
- 7日間APYトレンド追跡
- JSON出力でパイプライン連携可能
DeFiLlama Yields APIについて
DeFiLlama はDeFiのTVL(Total Value Locked)データを集約するオープンソースプロジェクトです。Yields APIは無料・APIキー不要で利用できます。
エンドポイント:
GET https://yields.llama.fi/pools
レスポンスには以下のようなデータが含まれます:
{
"data": [
{
"chain": "Ethereum",
"project": "lido",
"symbol": "STETH",
"tvlUsd": 21080000000,
"apy": 2.38,
"apyBase": 2.38,
"apyReward": 0,
"stablecoin": false,
"exposure": "single",
"apyPct7D": -0.03,
"predictions": { "predictedClass": "Stable/Up" }
}
]
}
1回のリクエストで全プール(16,000件以上)のデータが返ってくるため、クライアント側でフィルタリング・ソートを行います。
実装の詳細
1. HTTPクライアント(依存ゼロ)
外部ライブラリを使わず、Node.js標準のhttpsモジュールだけでHTTPリクエストを実装しています:
const https = require('https');
function fetch(url) {
return new Promise((resolve, reject) => {
https.get(url, res => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => resolve(JSON.parse(data)));
res.on('error', reject);
}).on('error', reject);
});
}
node-fetchやaxiosを使えば短く書けますが、依存ゼロにこだわることでnpx defi-yield-finder一発で動作するようにしています。インストール時間もゼロです。
2. フィルタリングロジック
取得した16,000件以上のプールデータに対して、複数の条件でフィルタリングします:
const CHAINS = ['Ethereum', 'Solana', 'Arbitrum', 'Optimism',
'Polygon', 'BSC', 'Base', 'Avalanche'];
pools = pools.filter(p => {
if (!p.apy || p.apy < minApy) return false; // 最低APY
if (!p.tvlUsd || p.tvlUsd < minTvl) return false; // 最低TVL
if (chainFilter && p.chain.toLowerCase() !== chainFilter.toLowerCase())
return false; // チェーン指定
if (stablesOnly && !p.stablecoin) return false; // ステーブルのみ
if (singleOnly && p.exposure !== 'single') return false; // シングルのみ
if (p.outlier) return false; // 異常値除外
return CHAINS.includes(p.chain) || chainFilter; // 対応チェーン
});
ポイントは以下の3つです:
-
outlierフラグの除外: DeFiLlama側でAPYが異常に高いプール(バグやエクスプロイトの可能性)にフラグが立っています。これを除外することで安全なプールのみを表示します。 - デフォルトTVL $1M以上: TVLが低いプールはラグプルリスクが高いため、デフォルトで100万ドル以上に制限しています。
-
exposureフィールド:single(シングルアセット)のプールはインパーマネントロスのリスクがないため、--singleフラグで絞り込めます。
3. テーブル表示のフォーマット
CLIツールらしい見やすいテーブル出力を実装しています:
function formatUsd(n) {
if (n >= 1e9) return `$${(n / 1e9).toFixed(2)}B`;
if (n >= 1e6) return `$${(n / 1e6).toFixed(2)}M`;
if (n >= 1e3) return `$${(n / 1e3).toFixed(0)}K`;
return `$${n.toFixed(0)}`;
}
function printTable(pools, title) {
console.log(`\n${'═'.repeat(90)}`);
console.log(` ${title}`);
console.log('═'.repeat(90));
console.log(
' ' +
'Protocol'.padEnd(18) +
'Pool'.padEnd(22) +
'Chain'.padEnd(12) +
'APY'.padStart(10) +
'TVL'.padStart(14) +
'Trend(7d)'.padStart(12)
);
console.log('─'.repeat(90));
for (const p of pools) {
const trend = p.apyPct7D > 0
? `+${p.apyPct7D.toFixed(2)}%`
: `${(p.apyPct7D || 0).toFixed(2)}%`;
console.log(
' ' +
p.project.padEnd(18).slice(0, 18) +
p.symbol.padEnd(22).slice(0, 22) +
p.chain.padEnd(12).slice(0, 12) +
`${p.apy.toFixed(2)}%`.padStart(10) +
formatUsd(p.tvlUsd).padStart(14) +
trend.padStart(12)
);
}
console.log('─'.repeat(90));
}
padEnd()とpadStart()で列を揃え、formatUsd()で$21.08Bのような人間が読みやすい形式に変換しています。
4. JSON出力モード
--jsonフラグを付けると、プログラムで処理しやすいJSON形式で出力します:
if (jsonOutput) {
const output = pools.map(p => ({
protocol: p.project,
pool: p.symbol,
chain: p.chain,
apy: p.apy,
apyBase: p.apyBase,
apyReward: p.apyReward,
tvlUsd: p.tvlUsd,
stablecoin: p.stablecoin,
ilRisk: p.ilRisk,
trend7d: p.apyPct7D,
prediction: p.predictions?.predictedClass
}));
console.log(JSON.stringify(output, null, 2));
return;
}
これによりjqやPythonスクリプトとパイプで連携できます:
# APY上位5件をjqで加工
node index.js --json --top 5 | jq '.[].apy'
# Pythonでチェーン別集計
node index.js --json --top 100 | python3 -c "
import json, sys
from collections import Counter
data = json.load(sys.stdin)
chains = Counter(p['chain'] for p in data)
for chain, count in chains.most_common():
print(f'{chain}: {count} pools')
"
5. CLIオプション解析
外部ライブラリなしでprocess.argvから直接パースしています:
const args = process.argv.slice(2);
const chainFilter = args.includes('--chain')
? args[args.indexOf('--chain') + 1] : null;
const minTvl = args.includes('--min-tvl')
? Number(args[args.indexOf('--min-tvl') + 1]) : 1000000;
const minApy = args.includes('--min-apy')
? Number(args[args.indexOf('--min-apy') + 1]) : 1;
const top = args.includes('--top')
? Number(args[args.indexOf('--top') + 1]) : 20;
const stablesOnly = args.includes('--stables');
const singleOnly = args.includes('--single');
const jsonOutput = args.includes('--json');
commanderやyargsを使わない理由は、やはり依存ゼロの方針です。CLIツールはnpxで一発実行できることが重要で、依存パッケージが多いとインストールに時間がかかります。
使い方の例
基本: 全チェーンのトップ20
npx defi-yield-finder
Solanaで5%以上のAPY
node index.js --chain Solana --min-apy 5
ステーブルコインプールで$10M以上のTVL
node index.js --stables --min-tvl 10000000
DeFiの世界でリスクを抑えたい場合、ステーブルコインプールは重要な選択肢です。TVLが$10M以上のプールに絞ることで、ラグプルリスクを大幅に軽減できます。
シングルアセットプール(インパーマネントロスなし)
node index.js --single --top 10
流動性提供(LP)で一番怖いのがインパーマネントロスです。--singleフラグを使えば、単一トークンのステーキング/レンディングプールのみに絞り込めます。
設計上のこだわり
なぜ依存ゼロなのか
npm パッケージの依存関係は、セキュリティリスクの元です。特にDeFi関連ツールでは、サプライチェーン攻撃によりウォレットの秘密鍵を盗むマルウェアが仕込まれるケースが報告されています。
依存ゼロにすることで:
- セキュリティ: サプライチェーン攻撃のリスクゼロ
-
速度:
npx実行時のインストール時間ゼロ - 監査性: 150行のソースを目視確認可能
- 可搬性: Node.js 18+があればどこでも動く
なぜCLIなのか
WebアプリではなくCLIにした理由:
- 自動化: cronジョブやシェルスクリプトに組み込める
-
パイプライン:
jqやPythonと組み合わせて分析可能 - プライバシー: ローカル実行でIPアドレスがDeFiLlamaにのみ送信される
- 軽量: ブラウザ不要、SSHターミナルから実行可能
まとめ
defi-yield-finderは、DeFiLlama APIを活用した依存ゼロのCLIツールです。16,000以上のプールからリアルタイムで最適なイールドを検索でき、ステーブルコイン・シングルアセットフィルタリング、JSON出力によるパイプライン連携が可能です。
ソースコードは150行未満で、セキュリティ監査も容易です。DeFiの利回り調査を効率化したい方はぜひ試してみてください。
リポジトリ: https://github.com/Ai-chan-0411/defi-yield-finder
この記事が参考になったら、ぜひLGTMをお願いします!