Skill: poploli2/defillama-api (version 0.1.0)

--- name: defillama-api description: 使用 DefiLlama API/SDK 获取 TVL、stablecoins、coins 价格、yields、volumes、fees、perps、unlocks、bridges、ETFs、narratives、token liquidity、main page、DAT、meta 等数据的 CLI 技能；当需要用命令行调用 DefiLlama 数据时使用。 homepage: https://defillama.com/ metadata: { "openclaw": { "emoji": "🦙", "requires": { "bins": ["uv"], "env": [] }, "optionalEnv": ["DEFILLAMA_API_KEY"], "install": [ { "id": "uv-brew", "kind": "brew", "formula": "uv", "bins": ["uv"], "label": "Install uv (brew)", }, ], }, } --- # DefiLlama API 只允许通过 `src/run.py` 调用，禁止直接运行 `src/scripts/` 下的脚本。 ## 安装 ```bash cd {baseDir} ``` ## 环境变量 - `DEFILLAMA_API_KEY`：可选，Pro API key。若未提供，Pro 端点会提示 `API PLAN REQUIRED`。 ## 使用方式（必须通过 run.py） ### TVL ```bash uv run {baseDir}/src/run.py tvl protocols uv run {baseDir}/src/run.py tvl protocol --protocol aave uv run {baseDir}/src/run.py tvl tvl --protocol uniswap uv run {baseDir}/src/run.py tvl chains uv run {baseDir}/src/run.py tvl historical-chain-tvl --chain Ethereum ``` ### Stablecoins ```bash uv run {baseDir}/src/run.py stablecoins list --include-prices uv run {baseDir}/src/run.py stablecoins charts-all uv run {baseDir}/src/run.py stablecoins charts-chain --chain Ethereum uv run {baseDir}/src/run.py stablecoins detail --asset 1 uv run {baseDir}/src/run.py stablecoins dominance --chain Ethereum --stablecoin-id 1 ``` ### Coins（价格与链上数据） ```bash uv run {baseDir}/src/run.py coins current --coins coingecko:ethereum,ethereum:0x0000000000000000000000000000000000000000 uv run {baseDir}/src/run.py coins historical --timestamp 1704067200 --coins coingecko:ethereum uv run {baseDir}/src/run.py coins chart --coins coingecko:ethereum --period 7d ``` ### Yields（Pro） ```bash uv run {baseDir}/src/run.py yields pools uv run {baseDir}/src/run.py yields pool-chart --pool <pool_id> ``` ### Volumes ```bash uv run {baseDir}/src/run.py volumes dex-overview uv run {baseDir}/src/run.py volumes options-overview --data-type dailyPremiumVolume ``` ### Fees ```bash uv run {baseDir}/src/run.py fees overview uv run {baseDir}/src/run.py fees summary --protocol uniswap ``` ### Perps ```bash uv run {baseDir}/src/run.py perps open-interest uv run {baseDir}/src/run.py perps derivatives-overview ``` ### Unlocks（Pro） ```bash uv run {baseDir}/src/run.py unlocks all uv run {baseDir}/src/run.py unlocks protocol --protocol hyperliquid ``` ### Main Page（Pro） ```bash uv run {baseDir}/src/run.py main-page categories uv run {baseDir}/src/run.py main-page raises ``` ### Token Liquidity（Pro） ```bash uv run {baseDir}/src/run.py token-liquidity historical --token usdt ``` ### ETFs（Pro） ```bash uv run {baseDir}/src/run.py etfs overview uv run {baseDir}/src/run.py etfs history ``` ### Narratives（Pro） ```bash uv run {baseDir}/src/run.py narratives fdv-performance --period 30 ``` ### Bridges（Pro） ```bash uv run {baseDir}/src/run.py bridges list --include-chains uv run {baseDir}/src/run.py bridges transactions --id 1 --limit 50 ``` ### Meta / DAT（Pro） ```bash uv run {baseDir}/src/run.py meta usage uv run {baseDir}/src/run.py dat institutions ``` ## 备注 - Coin 需要使用 `chain:address` 或 `coingecko:slug` 格式，例如 `coingecko:ethereum`。 - Pro 端点需要 `DEFILLAMA_API_KEY` 或 `--api-key`。 ## 常见错误 - `rate limited`：放缓速率或提供 API plan key。 - `API PLAN REQUIRED`：升级订阅或设置 `DEFILLAMA_API_KEY`。

path: src/run.py
content: #!/usr/bin/env python3
# /// script
# dependencies = ["defillama-sdk==0.1.4"]
# ///

from __future__ import annotations

import argparse
import os
import sys
from typing import Any, Callable, Optional

from defillama_sdk import (
    ApiError,
    ApiKeyRequiredError,
    DefiLlamaError,
    NotFoundError,
    RateLimitError,
)

from scripts import (
    bridges,
    coins,
    dat,
    etfs,
    fees,
    main_page,
    meta,
    narratives,
    perps,
    stablecoins,
    token_liquidity,
    tvl,
    unlocks,
    volumes,
    yields,
)
from scripts._shared import Context, build_clients, dump_json


def _build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="DefiLlama API CLI (via defillama-sdk).",
    )
    parser.add_argument(
        "--api-key",
        default=os.getenv("DEFILLAMA_API_KEY"),
        help="Pro API key (可选，默认读取 DEFILLAMA_API_KEY).",
    )
    parser.add_argument(
        "--compact",
        action="store_true",
        help="输出紧凑 JSON（默认 pretty JSON）。",
    )

    subparsers = parser.add_subparsers(dest="command", required=True)
    tvl.register(subparsers)
    stablecoins.register(subparsers)
    coins.register(subparsers)
    yields.register(subparsers)
    volumes.register(subparsers)
    fees.register(subparsers)
    perps.register(subparsers)
    unlocks.register(subparsers)
    main_page.register(subparsers)
    token_liquidity.register(subparsers)
    etfs.register(subparsers)
    narratives.register(subparsers)
    bridges.register(subparsers)
    meta.register(subparsers)
    dat.register(subparsers)

    return parser


def _print_error(message: str) -> None:
    print(message, file=sys.stderr)


def _handle_error(error: Exception) -> int:
    if isinstance(error, RateLimitError):
        _print_error("rate limited; 放缓速率或提供 API plan key。")
        return 1
    if isinstance(error, ApiKeyRequiredError):
        _print_error("API PLAN REQUIRED; 设置 DEFILLAMA_API_KEY 或升级订阅。")
        return 1
    if isinstance(error, NotFoundError):
        _print_error(str(error))
        return 1
    if isinstance(error, ApiError):
        _print_error(f"API error {error.status_code}: {error}")
        return 1
    if isinstance(error, DefiLlamaError):
        _print_error(str(error))
        return 1
    _print_error(f"Unexpected error: {error}")
    return 1


def main(argv: Optional[list[str]] = None) -> int:
    parser = _build_parser()
    args = parser.parse_args(argv)

    client, pro_client = build_clients(args.api_key)
    context = Context(
        client=client,
        pro_client=pro_client,
        compact=args.compact,
    )

    handler: Callable[[argparse.Namespace, Context], Any] = args.func
    try:
        result = handler(args, context)
    except Exception as exc:
        return _handle_error(exc)

    dump_json(result, compact=args.compact)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())


path: src/scripts/bridges.py
content: from __future__ import annotations

import argparse
from urllib.parse import quote

from ._shared import Context


def register(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser("bridges", help="跨链桥数据（Pro）")
    bridges_sub = parser.add_subparsers(dest="bridges_command", required=True)

    list_cmd = bridges_sub.add_parser("list", help="桥列表")
    list_cmd.add_argument("--include-chains", action="store_true", help="包含链数据")
    list_cmd.set_defaults(func=_list)

    detail = bridges_sub.add_parser("detail", help="桥详情")
    detail.add_argument("--id", type=int, required=True, help="桥 ID")
    detail.set_defaults(func=_detail)

    volume_chain = bridges_sub.add_parser("volume-chain", help="按链桥交易量")
    volume_chain.add_argument("--chain", required=True, help="链名")
    volume_chain.add_argument("--id", type=int, help="桥 ID")
    volume_chain.set_defaults(func=_volume_chain)

    day_stats = bridges_sub.add_parser("day-stats", help="日统计")
    day_stats.add_argument("--timestamp", type=int, required=True, help="时间戳")
    day_stats.add_argument("--chain", required=True, help="链名")
    day_stats.add_argument("--id", type=int, help="桥 ID")
    day_stats.set_defaults(func=_day_stats)

    transactions = bridges_sub.add_parser("transactions", help="桥交易列表")
    transactions.add_argument("--id", type=int, required=True, help="桥 ID")
    transactions.add_argument("--starttimestamp", type=int, help="开始时间戳")
    transactions.add_argument("--endtimestamp", type=int, help="结束时间戳")
    transactions.add_argument("--sourcechain", help="来源链")
    transactions.add_argument("--address", help="地址")
    transactions.add_argument("--limit", type=int, help="数量限制")
    transactions.set_defaults(func=_transactions)


def _list(args: argparse.Namespace, context: Context):
    options = None
    if args.include_chains:
        options = {"includeChains": True}
    return context.pro_client.bridges.getAll(options)


def _detail(args: argparse.Namespace, context: Context):
    return context.pro_client.bridges.getById(args.id)


def _volume_chain(args: argparse.Namespace, context: Context):
    if args.id is None:
        return context.pro_client.bridges.getVolumeByChain(args.chain)
    params = {"id": args.id}
    return context.pro_client._client.get(
        f"/bridgevolume/{quote(args.chain)}",
        base="bridges",
        params=params,
    )


def _day_stats(args: argparse.Namespace, context: Context):
    if args.id is None:
        return context.pro_client.bridges.getDayStats(args.timestamp, args.chain)
    params = {"id": args.id}
    return context.pro_client._client.get(
        f"/bridgedaystats/{args.timestamp}/{quote(args.chain)}",
        base="bridges",
        params=params,
    )


def _transactions(args: argparse.Namespace, context: Context):
    options = {}
    if args.limit is not None:
        options["limit"] = args.limit
    if args.starttimestamp is not None:
        options["startTimestamp"] = args.starttimestamp
    if args.endtimestamp is not None:
        options["endTimestamp"] = args.endtimestamp
    if args.sourcechain:
        options["sourceChain"] = args.sourcechain
    if args.address:
        options["address"] = args.address
    return context.pro_client.bridges.getTransactions(args.id, options or None)


path: src/scripts/fees.py
content: from __future__ import annotations

import argparse

from ._shared import Context


def _build_overview_options(args: argparse.Namespace):
    options = {}
    if args.data_type:
        options["dataType"] = args.data_type
    if args.include_total_data_chart:
        options["excludeTotalDataChart"] = False
    if args.include_total_data_chart_breakdown:
        options["excludeTotalDataChartBreakdown"] = False
    return options or None


def register(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser("fees", help="费用与收入数据")
    fees_sub = parser.add_subparsers(dest="fees_command", required=True)

    overview = fees_sub.add_parser("overview", help="总体费用概览")
    overview.add_argument("--data-type", help="dailyFees/dailyRevenue/dailyHoldersRevenue")
    overview.add_argument("--include-total-data-chart", action="store_true")
    overview.add_argument("--include-total-data-chart-breakdown", action="store_true")
    overview.set_defaults(func=_overview)

    overview_chain = fees_sub.add_parser("overview-chain", help="链费用概览")
    overview_chain.add_argument("--chain", required=True, help="链名")
    overview_chain.add_argument("--data-type", help="dailyFees/dailyRevenue/dailyHoldersRevenue")
    overview_chain.add_argument("--include-total-data-chart", action="store_true")
    overview_chain.add_argument("--include-total-data-chart-breakdown", action="store_true")
    overview_chain.set_defaults(func=_overview_chain)

    summary = fees_sub.add_parser("summary", help="协议费用概览")
    summary.add_argument("--protocol", required=True, help="协议 slug")
    summary.add_argument("--data-type", help="dailyFees/dailyRevenue/dailyHoldersRevenue")
    summary.set_defaults(func=_summary)


def _overview(args: argparse.Namespace, context: Context):
    options = _build_overview_options(args)
    return context.client.fees.getOverview(options)


def _overview_chain(args: argparse.Namespace, context: Context):
    options = _build_overview_options(args)
    return context.client.fees.getOverviewByChain(args.chain, options)


def _summary(args: argparse.Namespace, context: Context):
    options = {"dataType": args.data_type} if args.data_type else None
    return context.client.fees.getSummary(args.protocol, options)


path: src/scripts/narratives.py
content: from __future__ import annotations

import argparse

from ._shared import Context


def register(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser("narratives", help="叙事表现（Pro）")
    narratives_sub = parser.add_subparsers(dest="narratives_command", required=True)

    fdv = narratives_sub.add_parser("fdv-performance", help="FDV 表现")
    fdv.add_argument("--period", required=True, help="周期（例如 30）")
    fdv.set_defaults(func=_fdv_performance)


def _fdv_performance(args: argparse.Namespace, context: Context):
    return context.pro_client.etfs.getFdvPerformance(args.period)


path: src/scripts/dat.py
content: from __future__ import annotations

import argparse

from ._shared import Context


def register(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser("dat", help="DAT 数据（Pro）")
    dat_sub = parser.add_subparsers(dest="dat_command", required=True)

    institutions = dat_sub.add_parser("institutions", help="机构列表")
    institutions.set_defaults(func=_institutions)

    institution = dat_sub.add_parser("institution", help="机构详情")
    institution.add_argument("--symbol", required=True, help="机构 symbol")
    institution.set_defaults(func=_institution)


def _institutions(args: argparse.Namespace, context: Context):
    return context.pro_client.dat.getInstitutions()


def _institution(args: argparse.Namespace, context: Context):
    return context.pro_client.dat.getInstitution(args.symbol)


path: src/scripts/__init__.py
content: """DefiLlama CLI 脚本包。"""


path: src/scripts/yields.py
content: from __future__ import annotations

import argparse

from ._shared import Context


def register(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser("yields", help="收益率数据（Pro）")
    yields_sub = parser.add_subparsers(dest="yields_command", required=True)

    pools = yields_sub.add_parser("pools", help="收益池列表")
    pools.set_defaults(func=_pools)

    pools_old = yields_sub.add_parser("pools-old", help="旧版收益池")
    pools_old.set_defaults(func=_pools_old)

    pools_borrow = yields_sub.add_parser("pools-borrow", help="借贷池")
    pools_borrow.set_defaults(func=_pools_borrow)

    pool_chart = yields_sub.add_parser("pool-chart", help="收益池历史曲线")
    pool_chart.add_argument("--pool", required=True, help="Pool ID")
    pool_chart.set_defaults(func=_pool_chart)

    lend_borrow_chart = yields_sub.add_parser("lend-borrow-chart", help="借贷曲线")
    lend_borrow_chart.add_argument("--pool", required=True, help="Pool ID")
    lend_borrow_chart.set_defaults(func=_lend_borrow_chart)

    perps = yields_sub.add_parser("perps", help="永续资金费率")
    perps.set_defaults(func=_perps)

    lsd_rates = yields_sub.add_parser("lsd-rates", help="LSD 利率")
    lsd_rates.set_defaults(func=_lsd_rates)


def _pools(args: argparse.Namespace, context: Context):
    return context.pro_client.yields.getPools()


def _pools_old(args: argparse.Namespace, context: Context):
    return context.pro_client.yields.getPoolsOld()


def _pools_borrow(args: argparse.Namespace, context: Context):
    return context.pro_client.yields.getBorrowPools()


def _pool_chart(args: argparse.Namespace, context: Context):
    return context.pro_client.yields.getPoolChart(args.pool)


def _lend_borrow_chart(args: argparse.Namespace, context: Context):
    return context.pro_client.yields.getLendBorrowChart(args.pool)


def _perps(args: argparse.Namespace, context: Context):
    return context.pro_client.yields.getPerps()


def _lsd_rates(args: argparse.Namespace, context: Context):
    return context.pro_client.yields.getLsdRates()


path: src/scripts/_shared.py
content: from __future__ import annotations

import json
from dataclasses import dataclass
from typing import Any, Optional

from defillama_sdk import DefiLlama


@dataclass
class Context:
    client: DefiLlama
    pro_client: DefiLlama
    compact: bool = False


def build_clients([REDACTED_SECRET]) -> tuple[DefiLlama, DefiLlama]:
    client = DefiLlama()
    if [REDACTED_SECRET] = DefiLlama({"api_key": api_key})
    else:
        pro_client = DefiLlama()
    return client, pro_client


def dump_json(data: Any, compact: bool = False) -> None:
    if compact:
        output = json.dumps(data, ensure_ascii=False, separators=(",", ":"))
    else:
        output = json.dumps(data, ensure_ascii=False, indent=2)
    print(output)


def split_csv(value: str) -> list[str]:
    return [item.strip() for item in value.split(",") if item.strip()]


def parse_json_value(value: str) -> Any:
    try:
        return json.loads(value)
    except json.JSONDecodeError as exc:
        raise ValueError(f"无效 JSON: {exc}") from exc


path: src/scripts/volumes.py
content: from __future__ import annotations

import argparse

from ._shared import Context


def _build_overview_options(args: argparse.Namespace):
    options = {}
    if args.data_type:
        options["dataType"] = args.data_type
    if args.include_total_data_chart:
        options["excludeTotalDataChart"] = False
    if args.include_total_data_chart_breakdown:
        options["excludeTotalDataChartBreakdown"] = False
    return options or None


def register(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser("volumes", help="交易量数据")
    volumes_sub = parser.add_subparsers(dest="volumes_command", required=True)

    dex_overview = volumes_sub.add_parser("dex-overview", help="DEX 概览")
    dex_overview.add_argument("--data-type", help="dailyVolume/totalVolume")
    dex_overview.add_argument("--include-total-data-chart", action="store_true")
    dex_overview.add_argument("--include-total-data-chart-breakdown", action="store_true")
    dex_overview.set_defaults(func=_dex_overview)

    dex_chain = volumes_sub.add_parser("dex-overview-chain", help="DEX 链概览")
    dex_chain.add_argument("--chain", required=True, help="链名")
    dex_chain.add_argument("--data-type", help="dailyVolume/totalVolume")
    dex_chain.add_argument("--include-total-data-chart", action="store_true")
    dex_chain.add_argument("--include-total-data-chart-breakdown", action="store_true")
    dex_chain.set_defaults(func=_dex_overview_chain)

    dex_summary = volumes_sub.add_parser("dex-summary", help="DEX 协议概览")
    dex_summary.add_argument("--protocol", required=True, help="协议 slug")
    dex_summary.add_argument("--data-type", help="dailyVolume/totalVolume")
    dex_summary.set_defaults(func=_dex_summary)

    options_overview = volumes_sub.add_parser("options-overview", help="期权概览")
    options_overview.add_argument("--data-type", help="dailyPremiumVolume/dailyNotionalVolume")
    options_overview.add_argument("--include-total-data-chart", action="store_true")
    options_overview.add_argument("--include-total-data-chart-breakdown", action="store_true")
    options_overview.set_defaults(func=_options_overview)

    options_chain = volumes_sub.add_parser("options-overview-chain", help="期权链概览")
    options_chain.add_argument("--chain", required=True, help="链名")
    options_chain.add_argument("--data-type", help="dailyPremiumVolume/dailyNotionalVolume")
    options_chain.add_argument("--include-total-data-chart", action="store_true")
    options_chain.add_argument("--include-total-data-chart-breakdown", action="store_true")
    options_chain.set_defaults(func=_options_overview_chain)

    options_summary = volumes_sub.add_parser("options-summary", help="期权协议概览")
    options_summary.add_argument("--protocol", required=True, help="协议 slug")
    options_summary.add_argument("--data-type", help="dailyPremiumVolume/dailyNotionalVolume")
    options_summary.set_defaults(func=_options_summary)


def _dex_overview(args: argparse.Namespace, context: Context):
    options = _build_overview_options(args)
    return context.client.volumes.getDexOverview(options)


def _dex_overview_chain(args: argparse.Namespace, context: Context):
    options = _build_overview_options(args)
    return context.client.volumes.getDexOverviewByChain(args.chain, options)


def _dex_summary(args: argparse.Namespace, context: Context):
    options = {"dataType": args.data_type} if args.data_type else None
    return context.client.volumes.getDexSummary(args.protocol, options)


def _options_overview(args: argparse.Namespace, context: Context):
    options = _build_overview_options(args)
    return context.client.volumes.getOptionsOverview(options)


def _options_overview_chain(args: argparse.Namespace, context: Context):
    options = _build_overview_options(args)
    return context.client.volumes.getOptionsOverviewByChain(args.chain, options)


def _options_summary(args: argparse.Namespace, context: Context):
    options = {"dataType": args.data_type} if args.data_type else None
    return context.client.volumes.getOptionsSummary(args.protocol, options)


path: src/scripts/stablecoins.py
content: from __future__ import annotations

import argparse

from ._shared import Context


def register(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser("stablecoins", help="稳定币数据")
    stable_sub = parser.add_subparsers(dest="stable_command", required=True)

    list_cmd = stable_sub.add_parser("list", help="稳定币列表")
    list_cmd.add_argument("--include-prices", action="store_true", help="包含价格")
    list_cmd.set_defaults(func=_list)

    charts_all = stable_sub.add_parser("charts-all", help="全部稳定币历史市值")
    charts_all.set_defaults(func=_charts_all)

    charts_chain = stable_sub.add_parser("charts-chain", help="指定链稳定币历史市值")
    charts_chain.add_argument("--chain", required=True, help="链名")
    charts_chain.set_defaults(func=_charts_chain)

    detail = stable_sub.add_parser("detail", help="稳定币详情")
    detail.add_argument("--asset", required=True, help="稳定币 ID")
    detail.set_defaults(func=_detail)

    chains = stable_sub.add_parser("chains", help="链上稳定币市值")
    chains.set_defaults(func=_chains)

    prices = stable_sub.add_parser("prices", help="稳定币历史价格")
    prices.set_defaults(func=_prices)

    dominance = stable_sub.add_parser("dominance", help="稳定币市值占比")
    dominance.add_argument("--chain", required=True, help="链名")
    dominance.add_argument("--stablecoin-id", type=int, help="稳定币 ID")
    dominance.set_defaults(func=_dominance)


def _list(args: argparse.Namespace, context: Context):
    include_prices = True if args.include_prices else None
    return context.client.stablecoins.getStablecoins(includePrices=include_prices)


def _charts_all(args: argparse.Namespace, context: Context):
    return context.client.stablecoins.getAllCharts()


def _charts_chain(args: argparse.Namespace, context: Context):
    return context.client.stablecoins.getChartsByChain(args.chain)


def _detail(args: argparse.Namespace, context: Context):
    return context.client.stablecoins.getStablecoin(args.asset)


def _chains(args: argparse.Namespace, context: Context):
    return context.client.stablecoins.getChains()


def _prices(args: argparse.Namespace, context: Context):
    return context.client.stablecoins.getPrices()


def _dominance(args: argparse.Namespace, context: Context):
    return context.pro_client.stablecoins.getDominance(
        args.chain, stablecoinId=args.stablecoin_id
    )


path: src/scripts/tvl.py
content: from __future__ import annotations

import argparse

from ._shared import Context


def register(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser("tvl", help="TVL 数据")
    tvl_sub = parser.add_subparsers(dest="tvl_command", required=True)

    protocols = tvl_sub.add_parser("protocols", help="列出所有协议 TVL")
    protocols.set_defaults(func=_protocols)

    protocol = tvl_sub.add_parser("protocol", help="协议详情")
    protocol.add_argument("--protocol", required=True, help="协议 slug")
    protocol.set_defaults(func=_protocol)

    tvl_value = tvl_sub.add_parser("tvl", help="协议当前 TVL")
    tvl_value.add_argument("--protocol", required=True, help="协议 slug")
    tvl_value.set_defaults(func=_tvl)

    chains = tvl_sub.add_parser("chains", help="链 TVL 列表")
    chains.set_defaults(func=_chains)

    historical = tvl_sub.add_parser("historical-chain-tvl", help="历史链 TVL")
    historical.add_argument("--chain", help="链名（可选）")
    historical.set_defaults(func=_historical_chain_tvl)

    token_protocols = tvl_sub.add_parser("token-protocols", help="持仓该代币的协议")
    token_protocols.add_argument("--symbol", required=True, help="代币 symbol")
    token_protocols.set_defaults(func=_token_protocols)

    inflows = tvl_sub.add_parser("inflows", help="协议资金流入流出")
    inflows.add_argument("--protocol", required=True, help="协议 slug")
    inflows.add_argument("--start", required=True, type=int, help="开始时间戳")
    inflows.add_argument("--end", required=True, type=int, help="结束时间戳")
    inflows.add_argument("--tokens-exclude", help="排除的 token 列表字符串")
    inflows.set_defaults(func=_inflows)

    chain_assets = tvl_sub.add_parser("chain-assets", help="链资产分布")
    chain_assets.set_defaults(func=_chain_assets)


def _protocols(args: argparse.Namespace, context: Context):
    return context.client.tvl.getProtocols()


def _protocol(args: argparse.Namespace, context: Context):
    return context.client.tvl.getProtocol(args.protocol)


def _tvl(args: argparse.Namespace, context: Context):
    return context.client.tvl.getTvl(args.protocol)


def _chains(args: argparse.Namespace, context: Context):
    return context.client.tvl.getChains()


def _historical_chain_tvl(args: argparse.Namespace, context: Context):
    return context.client.tvl.getHistoricalChainTvl(args.chain)


def _token_protocols(args: argparse.Namespace, context: Context):
    return context.pro_client.tvl.getTokenProtocols(args.symbol)


def _inflows(args: argparse.Namespace, context: Context):
    return context.pro_client.tvl.getInflows(
        args.protocol,
        args.start,
        args.end,
        tokens_to_exclude=args.tokens_exclude,
    )


def _chain_assets(args: argparse.Namespace, context: Context):
    return context.pro_client.tvl.getChainAssets()


path: src/scripts/unlocks.py
content: from __future__ import annotations

import argparse

from ._shared import Context


def register(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser("unlocks", help="解锁/排放数据（Pro）")
    unlocks_sub = parser.add_subparsers(dest="unlocks_command", required=True)

    all_cmd = unlocks_sub.add_parser("all", help="全部解锁数据")
    all_cmd.set_defaults(func=_all)

    protocol_cmd = unlocks_sub.add_parser("protocol", help="协议解锁数据")
    protocol_cmd.add_argument("--protocol", required=True, help="协议 slug")
    protocol_cmd.set_defaults(func=_protocol)


def _all(args: argparse.Namespace, context: Context):
    return context.pro_client.emissions.getAll()


def _protocol(args: argparse.Namespace, context: Context):
    return context.pro_client.emissions.getByProtocol(args.protocol)


path: src/scripts/coins.py
content: from __future__ import annotations

import argparse

from ._shared import Context, parse_json_value, split_csv


def register(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser("coins", help="币价与链上数据")
    coins_sub = parser.add_subparsers(dest="coins_command", required=True)

    current = coins_sub.add_parser("current", help="当前价格")
    current.add_argument("--coins", required=True, help="币种列表，逗号分隔")
    current.add_argument("--search-width", help="搜索窗口")
    current.set_defaults(func=_current)

    historical = coins_sub.add_parser("historical", help="历史价格")
    historical.add_argument("--timestamp", type=int, required=True, help="时间戳")
    historical.add_argument("--coins", required=True, help="币种列表，逗号分隔")
    historical.add_argument("--search-width", help="搜索窗口")
    historical.set_defaults(func=_historical)

    batch = coins_sub.add_parser("batch-historical", help="批量历史价格")
    batch.add_argument("--coins-json", required=True, help="JSON 字符串")
    batch.add_argument("--search-width", help="搜索窗口")
    batch.set_defaults(func=_batch_historical)

    chart = coins_sub.add_parser("chart", help="价格曲线")
    chart.add_argument("--coins", required=True, help="币种列表，逗号分隔")
    chart.add_argument("--start", type=int, help="开始时间戳")
    chart.add_argument("--end", type=int, help="结束时间戳")
    chart.add_argument("--span", type=int, help="时间间隔小时数")
    chart.add_argument("--period", help="周期（如 1d,7d）")
    chart.add_argument("--search-width", help="搜索窗口")
    chart.set_defaults(func=_chart)

    percentage = coins_sub.add_parser("percentage", help="区间涨跌幅")
    percentage.add_argument("--coins", required=True, help="币种列表，逗号分隔")
    percentage.add_argument("--timestamp", type=int, help="时间戳")
    percentage.add_argument("--look-forward", action="store_true", help="向前看")
    percentage.add_argument("--period", help="周期（如 3w）")
    percentage.set_defaults(func=_percentage)

    first = coins_sub.add_parser("first", help="最早价格")
    first.add_argument("--coins", required=True, help="币种列表，逗号分隔")
    first.set_defaults(func=_first)

    block = coins_sub.add_parser("block", help="时间戳对应区块")
    block.add_argument("--chain", required=True, help="链名")
    block.add_argument("--timestamp", type=int, required=True, help="时间戳")
    block.set_defaults(func=_block)


def _current(args: argparse.Namespace, context: Context):
    coins = split_csv(args.coins)
    return context.client.prices.getCurrentPrices(coins, searchWidth=args.search_width)


def _historical(args: argparse.Namespace, context: Context):
    coins = split_csv(args.coins)
    return context.client.prices.getHistoricalPrices(
        args.timestamp, coins, searchWidth=args.search_width
    )


def _batch_historical(args: argparse.Namespace, context: Context):
    coins_json = parse_json_value(args.coins_json)
    return context.client.prices.getBatchHistoricalPrices(
        coins_json, searchWidth=args.search_width
    )


def _chart(args: argparse.Namespace, context: Context):
    coins = split_csv(args.coins)
    options = {
        "start": args.start,
        "end": args.end,
        "span": args.span,
        "period": args.period,
        "searchWidth": args.search_width,
    }
    return context.client.prices.getChart(coins, options=options)


def _percentage(args: argparse.Namespace, context: Context):
    coins = split_csv(args.coins)
    options = {
        "timestamp": args.timestamp,
        "lookForward": True if args.look_forward else None,
        "period": args.period,
    }
    return context.client.prices.getPercentageChange(coins, options=options)


def _first(args: argparse.Namespace, context: Context):
    coins = split_csv(args.coins)
    return context.client.prices.getFirstPrices(coins)


def _block(args: argparse.Namespace, context: Context):
    return context.client.prices.getBlockAtTimestamp(args.chain, args.timestamp)


path: src/scripts/perps.py
content: from __future__ import annotations

import argparse

from ._shared import Context


def register(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser("perps", help="永续合约数据")
    perps_sub = parser.add_subparsers(dest="perps_command", required=True)

    open_interest = perps_sub.add_parser("open-interest", help="未平仓量")
    open_interest.add_argument("--include-total-data-chart", action="store_true")
    open_interest.add_argument("--include-total-data-chart-breakdown", action="store_true")
    open_interest.set_defaults(func=_open_interest)

    derivatives_overview = perps_sub.add_parser(
        "derivatives-overview", help="衍生品概览（Pro）"
    )
    derivatives_overview.set_defaults(func=_derivatives_overview)

    derivatives_summary = perps_sub.add_parser(
        "derivatives-summary", help="衍生品协议概览（Pro）"
    )
    derivatives_summary.add_argument("--protocol", required=True, help="协议 slug")
    derivatives_summary.set_defaults(func=_derivatives_summary)


def _open_interest(args: argparse.Namespace, context: Context):
    params = {
        "excludeTotalDataChart": not args.include_total_data_chart,
        "excludeTotalDataChartBreakdown": not args.include_total_data_chart_breakdown,
    }
    return context.client._client.get("/overview/open-interest", params=params)


def _derivatives_overview(args: argparse.Namespace, context: Context):
    return context.pro_client.volumes.getDerivativesOverview()


def _derivatives_summary(args: argparse.Namespace, context: Context):
    return context.pro_client.volumes.getDerivativesSummary(args.protocol)


path: src/scripts/token_liquidity.py
content: from __future__ import annotations

import argparse
from urllib.parse import quote

from ._shared import Context


def register(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser("token-liquidity", help="代币流动性（Pro）")
    token_sub = parser.add_subparsers(dest="token_command", required=True)

    historical = token_sub.add_parser("historical", help="代币流动性历史")
    historical.add_argument("--token", required=True, help="代币 symbol")
    historical.set_defaults(func=_historical)


def _historical(args: argparse.Namespace, context: Context):
    [REDACTED_SECRET])
    return context.pro_client._client.get(
        f"/historicalLiquidity/{token}",
        requires_auth=True,
    )


path: src/scripts/etfs.py
content: from __future__ import annotations

import argparse

from ._shared import Context


def register(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser("etfs", help="ETF 数据（Pro）")
    etfs_sub = parser.add_subparsers(dest="etfs_command", required=True)

    overview = etfs_sub.add_parser("overview", help="比特币 ETF 概览")
    overview.set_defaults(func=_overview)

    overview_eth = etfs_sub.add_parser("overview-eth", help="以太坊 ETF 概览")
    overview_eth.set_defaults(func=_overview_eth)

    history = etfs_sub.add_parser("history", help="比特币 ETF 流入流出历史")
    history.set_defaults(func=_history)

    history_eth = etfs_sub.add_parser("history-eth", help="以太坊 ETF 流入流出历史")
    history_eth.set_defaults(func=_history_eth)


def _overview(args: argparse.Namespace, context: Context):
    return context.pro_client.etfs.getOverview()


def _overview_eth(args: argparse.Namespace, context: Context):
    return context.pro_client.etfs.getOverviewEth()


def _history(args: argparse.Namespace, context: Context):
    return context.pro_client.etfs.getHistory()


def _history_eth(args: argparse.Namespace, context: Context):
    return context.pro_client.etfs.getHistoryEth()


path: src/scripts/main_page.py
content: from __future__ import annotations

import argparse

from ._shared import Context


def register(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser("main-page", help="主站生态数据（Pro）")
    main_sub = parser.add_subparsers(dest="main_command", required=True)

    categories = main_sub.add_parser("categories", help="分类数据")
    categories.set_defaults(func=_categories)

    forks = main_sub.add_parser("forks", help="分叉数据")
    forks.set_defaults(func=_forks)

    oracles = main_sub.add_parser("oracles", help="预言机数据")
    oracles.set_defaults(func=_oracles)

    hacks = main_sub.add_parser("hacks", help="安全事件数据")
    hacks.set_defaults(func=_hacks)

    raises = main_sub.add_parser("raises", help="融资数据")
    raises.set_defaults(func=_raises)

    treasuries = main_sub.add_parser("treasuries", help="金库数据")
    treasuries.set_defaults(func=_treasuries)

    entities = main_sub.add_parser("entities", help="实体数据")
    entities.set_defaults(func=_entities)


def _categories(args: argparse.Namespace, context: Context):
    return context.pro_client.ecosystem.getCategories()


def _forks(args: argparse.Namespace, context: Context):
    return context.pro_client.ecosystem.getForks()


def _oracles(args: argparse.Namespace, context: Context):
    return context.pro_client.ecosystem.getOracles()


def _hacks(args: argparse.Namespace, context: Context):
    return context.pro_client.ecosystem.getHacks()


def _raises(args: argparse.Namespace, context: Context):
    return context.pro_client.ecosystem.getRaises()


def _treasuries(args: argparse.Namespace, context: Context):
    return context.pro_client.ecosystem.getTreasuries()


def _entities(args: argparse.Namespace, context: Context):
    return context.pro_client.ecosystem.getEntities()


path: src/scripts/meta.py
content: from __future__ import annotations

import argparse

from ._shared import Context


def register(subparsers: argparse._SubParsersAction) -> None:
    parser = subparsers.add_parser("meta", help="账号与配额（Pro）")
    meta_sub = parser.add_subparsers(dest="meta_command", required=True)

    usage = meta_sub.add_parser("usage", help="API 使用量")
    usage.set_defaults(func=_usage)


def _usage(args: argparse.Namespace, context: Context):
    return context.pro_client.account.getUsage()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)