Skip to content

add earn-grid yield adapter (ERC-4626 USDC vault on Base) - #2903

Draft
NukeThemAII wants to merge 4 commits into
DefiLlama:masterfrom
NukeThemAII:earn-grid-adapter
Draft

add earn-grid yield adapter (ERC-4626 USDC vault on Base)#2903
NukeThemAII wants to merge 4 commits into
DefiLlama:masterfrom
NukeThemAII:earn-grid-adapter

Conversation

@NukeThemAII

@NukeThemAII NukeThemAII commented Aug 11, 2026

Copy link
Copy Markdown

Description

Adds EarnGrid yield adapter. EarnGrid is a USDC yield vault on Base (ERC-4626) that aggregates MetaMorpho strategies.

  • Vault: 0x8694D7D44309665D51Cb5002fceC0454f1c233dE
  • Chain: Base (8453)
  • Asset: USDC
  • TVL: ~\20 (growing)
  • APY source: Morpho Blue GraphQL API — weighted average of underlying MetaMorpho strategy APYs
  • Website: https://earngrid.site

The adapter reads:

  1. totalAssets() from the vault for TVL
  2. Vault balance in each MetaMorpho strategy via balanceOf()
  3. Strategy APYs from Morpho Blue GraphQL
  4. Computes weighted average APY

Already listed in DeFiLlama TVL via ERC-4626 registry.

Summary by CodeRabbit

  • New Features
    • Added support for tracking the Base BlendedVault through EarnGrid.
    • Added USDC pool details, including total assets, strategy allocations, idle funds, active strategies, and price per share.
    • Added combined yield visibility with weighted APY and MetaMorpho strategy rates.
    • Added reliable fallback values when strategy or yield data is temporarily unavailable.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ac4712b-eb92-4f39-b1bf-c382f7bbdf25

📥 Commits

Reviewing files that changed from the base of the PR and between d6723a6 and ad4640a.

📒 Files selected for processing (1)
  • src/adaptors/earngrid/index.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/adaptors/earngrid/index.js

📝 Walkthrough

Walkthrough

Adds a Base EarnGrid adaptor. It reads vault and strategy balances on-chain, retrieves Morpho net APYs, calculates weighted APY and idle allocation, and returns a formatted USDC pool record.

Changes

EarnGrid Base adaptor

Layer / File(s) Summary
Strategy configuration and APY sources
src/adaptors/earngrid/index.js
Defines EarnGrid, USDC, receipt-token, and MetaMorpho addresses. Queries Morpho Blue GraphQL for strategy net APYs and applies zero-value fallbacks.
Vault data and APY calculation
src/adaptors/earngrid/index.js
Fetches vault assets, receipt-token supply, strategy balances, and USDC pricing concurrently. Converts values and calculates strategy allocations, weighted APY, idle balance, price per share, and active strategy count.
Pool result and adaptor metadata
src/adaptors/earngrid/index.js
Returns the formatted USDC pool record and exports protocol metadata, the APY handler, URL, and disabled timetravel support.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to ad464

This localized adapter addition has no actionable merge-blocking risk remaining and is merge-ready after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant EarnGridAdaptor
  participant BaseRPC
  participant MorphoBlueGraphQL
  participant USDCPriceSource
  EarnGridAdaptor->>BaseRPC: Fetch vault assets, receipt supply, and strategy balances
  EarnGridAdaptor->>MorphoBlueGraphQL: Fetch MetaMorpho net APYs
  EarnGridAdaptor->>USDCPriceSource: Fetch USDC price
  BaseRPC-->>EarnGridAdaptor: Return on-chain data
  MorphoBlueGraphQL-->>EarnGridAdaptor: Return APY data or failure
  USDCPriceSource-->>EarnGridAdaptor: Return USDC price
  EarnGridAdaptor->>EarnGridAdaptor: Calculate allocations, weighted APY, idle balance, and price per share
  EarnGridAdaptor-->>EarnGridAdaptor: Return formatted USDC pool record
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the addition of an EarnGrid yield adapter for a Base ERC-4626 USDC vault.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Error while running earngrid adapter:

● Running earngrid Test › Adapter exports the protocolId matching its protocol slug

    expect(received).toBe(expected) // Object.is equality

    Expected: "8384"
    Received: "earngrid"

  ● Running earngrid Test › Adapter assigns protocolId as a string literal inside module.exports

    protocolId is not a literal that yield-server-v2 can read: `protocolId: 'earngrid'`. Inline it, eg: protocolId: 'earngrid'

Test Suites: 1 failed, 1 total
Tests:       2 failed, 10 passed, 12 total
Snapshots:   0 total
Time:        0.263 s
Ran all test suites.

Nb of pools: 1
 

Sample pools:
┌─────────┬───────────────────────────────────────────────────┬────────┬────────────┬────────┬────────────┬─────────┬───────────┬──────────────────────────────────────────────────┬─────────────────────────┬────────────────┐
│ (index) │ pool                                              │ chain  │ project    │ symbol │ tvlUsd     │ apyBase │ apyReward │ underlyingTokens                                 │ url                     │ poolMeta       │
├─────────┼───────────────────────────────────────────────────┼────────┼────────────┼────────┼────────────┼─────────┼───────────┼──────────────────────────────────────────────────┼─────────────────────────┼────────────────┤
│ 0       │ '0x8694D7D44309665D51Cb5002fceC0454f1c233dE-base' │ 'Base' │ 'earngrid' │ 'USDC' │ 220.876898 │ 0       │ 0         │ [ '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' ] │ 'https://earngrid.site' │ '3 strategies' │
└─────────┴───────────────────────────────────────────────────┴────────┴────────────┴────────┴────────────┴─────────┴───────────┴──────────────────────────────────────────────────┴─────────────────────────┴────────────────┘
This adapter contains some pools with <10k TVL, these pools won't be shown in DefiLlama

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/adaptors/earngrid/index.js`:
- Around line 145-150: Update the protocolId value in the exported adapter
metadata object to the required inline slug "8384", preserving the other fields
such as timetravel, apy, and url.
- Around line 115-126: Update the weighted APY calculation in the allocations
reduction to divide by totalAssets instead of totalBalance, so idle USDC
represented by idle is included in the vault return denominator. Preserve the
existing positive-total guard and apyBase fallback behavior.
- Around line 85-91: Update the strategy balance-fetch flow in the STRATEGIES
map to convert each MetaMorpho share balance returned by abiBalanceOf into USDC
assets using convertToAssets before calculating balance, idle, or apyBase.
Preserve the existing per-strategy allocation calculations while ensuring they
consume asset-denominated values.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f470e83-1e9b-4db9-b63c-0619afae8f18

📥 Commits

Reviewing files that changed from the base of the PR and between 1f3a3d9 and 8a65eaf.

📒 Files selected for processing (1)
  • src/adaptors/earngrid/index.js

Comment thread src/adaptors/earngrid/index.js Outdated
Comment on lines +85 to +91
const strategyBalanceCalls = STRATEGIES.map((s) =>
sdk.api.abi.call({
chain: CHAIN,
target: s.morphoVault,
abi: abiBalanceOf,
params: [VAULT],
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

RPC='https://mainnet.base.org'
VAULT='8694d7d44309665d51cb5002fcec0454f1c233de'
STRATEGY='ee8f4ec5672f09119b96ab6fb59c27e1b7e44b61'

rpc_call() {
  curl -sS "$RPC" \
    -H 'content-type: application/json' \
    --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_call\",\"params\":[{\"to\":\"0x$1\",\"data\":\"$2\"},\"latest\"]}" |
    jq -r '.result'
}

shares="$(rpc_call "$STRATEGY" "0x70a08231000000000000000000000000$VAULT")"
assets="$(rpc_call "$STRATEGY" "0x07a2d13a${shares#0x}")"

printf 'shares=%s\nassets=%s\n' "$shares" "$assets"

Repository: DefiLlama/yield-server

Length of output: 308


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- target file ---'
sed -n '1,180p' src/adaptors/earngrid/index.js

printf '%s\n' '--- related symbols and protocol ID usage ---'
rg -n -C 3 'earngrid|protocolId|morphoVault|abiBalanceOf|convertToAssets|apyBase|idle' src/adaptors/earngrid src 2>/dev/null | head -300

Repository: DefiLlama/yield-server

Length of output: 21038


🏁 Script executed:

#!/bin/bash
set -euo pipefail

RPC='https://mainnet.base.org'
VAULT='8694d7d44309665d51cb5002fcec0454f1c233de'
STRATEGIES=(
  'ee8f4ec5672f09119b96ab6fb59c27e1b7e44b61'
  'beefe94c8ad530842bfe7d8b397938ffc1cb83b2'
  'c1256ae5ff1cf2719d4937adb3bbccab2e00a2ca'
  '236919f11ff9ea9550a4287696c2fc9e18e6e890'
  'beefa7b88064feef0cee02aae bb d95d30df3878f'
)
# Remove the visual separator in the last address.
STRATEGIES[4]='beefa7b88064feef0cee02a aebb d95d30df3878f'
STRATEGIES[4]="${STRATEGIES[4]// /}"

call() {
  local to="$1" data="$2"
  curl -sS "$RPC" -H 'content-type: application/json' \
    --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_call\",\"params\":[{\"to\":\"0x$to\",\"data\":\"0x$data\"},\"latest\"]}" |
    jq -r 'if .error then ("ERROR: " + (.error.message // "unknown")) else (.result // "NO_RESULT") end'
}

encode_address() { printf '%064s' "$1" | tr ' ' '0'; }
encode_uint() { printf '%064s' "${1#0x}" | tr ' ' '0'; }

printf 'strategy,code_bytes,decimals,totalAssets,balanceOf,convertToAssets,previewRedeem\n'
for strategy in "${STRATEGIES[@]}"; do
  code="$(curl -sS "$RPC" -H 'content-type: application/json' \
    --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getCode\",\"params\":[\"0x$strategy\",\"latest\"]}" |
    jq -r '.result')"
  shares="$(call "$strategy" "70a08231$(encode_address "$VAULT")")"
  total="$(call "$strategy" "01e1d114")"
  decimals="$(call "$strategy" "313ce567")"
  if [[ "$shares" == 0x* && "$shares" != *ERROR* ]]; then
    assets="$(call "$strategy" "07a2d13a$(encode_uint "$shares")")"
    preview="$(call "$strategy" "ಿಂಗ")"
  else
    assets='not queried'
    preview='not queried'
  fi
  # previewRedeem(uint256) selector is 4b3c5f3f.
  if [[ "$shares" == 0x* && "$shares" != *ERROR* ]]; then
    preview="$(call "$strategy" "4b3c5f3f$(encode_uint "$shares")")"
  fi
  bytes=$(( (${`#code`#0x} - 2) / 2 ))
  printf '%s,%s,%s,%s,%s,%s,%s\n' "$strategy" "$bytes" "$decimals" "$total" "$shares" "$assets" "$preview"
done

Repository: DefiLlama/yield-server

Length of output: 298


🏁 Script executed:

#!/bin/bash
set -euo pipefail

RPC='https://mainnet.base.org'
VAULT='8694d7d44309665d51cb5002fcec0454f1c233de'
STRATEGIES=(
  'ee8f4ec5672f09119b96ab6fb59c27e1b7e44b61'
  'beefe94c8ad530842bfe7d8b397938ffc1cb83b2'
  'c1256ae5ff1cf2719d4937adb3bbccab2e00a2ca'
  '236919f11ff9ea9550a4287696c2fc9e18e6e890'
  'beefa7b88064feef0cee02aaebbd95d30df3878f'
)

call() {
  local to="$1" data="$2"
  curl -sS "$RPC" -H 'content-type: application/json' \
    --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_call\",\"params\":[{\"to\":\"0x$to\",\"data\":\"0x$data\"},\"latest\"]}" |
    jq -r 'if .error then ("ERROR: " + (.error.message // "unknown")) else (.result // "NO_RESULT") end'
}

encode_address() {
  printf '%064s' "$1" | tr ' ' '0'
}

encode_uint() {
  printf '%064s' "${1#0x}" | tr ' ' '0'
}

printf 'strategy,code_bytes,decimals,totalAssets,balanceOf,convertToAssets,previewRedeem\n'
for strategy in "${STRATEGIES[@]}"; do
  code="$(curl -sS "$RPC" -H 'content-type: application/json' \
    --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getCode\",\"params\":[\"0x$strategy\",\"latest\"]}" |
    jq -r '.result')"

  shares="$(call "$strategy" "70a08231$(encode_address "$VAULT")")"
  total="$(call "$strategy" "01e1d114")"
  decimals="$(call "$strategy" "313ce567")"
  assets='not queried'
  preview='not queried'

  if [[ "$shares" == 0x* ]]; then
    assets="$(call "$strategy" "07a2d13a$(encode_uint "$shares")")"
    preview="$(call "$strategy" "4b3c5f3f$(encode_uint "$shares")")"
  fi

  bytes=$(( (${`#code`#0x} - 2) / 2 ))
  printf '%s,%s,%s,%s,%s,%s,%s\n' \
    "$strategy" "$bytes" "$decimals" "$total" "$shares" "$assets" "$preview"
done

Repository: DefiLlama/yield-server

Length of output: 298


🏁 Script executed:

#!/bin/bash
set -euo pipefail

RPC='https://mainnet.base.org'
VAULT='8694d7d44309665d51cb5002fcec0454f1c233de'
STRATEGIES=(
  'ee8f4ec5672f09119b96ab6fb59c27e1b7e44b61'
  'beefe94c8ad530842bfe7d8b397938ffc1cb83b2'
  'c1256ae5ff1cf2719d4937adb3bbccab2e00a2ca'
  '236919f11ff9ea9550a4287696c2fc9e18e6e890'
  'beefa7b88064feef0cee02aaebbd95d30df3878f'
)

call() {
  local to="$1" data="$2"
  curl -sS "$RPC" -H 'content-type: application/json' \
    --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_call\",\"params\":[{\"to\":\"0x$to\",\"data\":\"0x$data\"},\"latest\"]}" |
    jq -r 'if .error then ("ERROR: " + (.error.message // "unknown")) else (.result // "NO_RESULT") end'
}

encode_address() {
  printf '%064s' "$1" | tr ' ' '0'
}

encode_uint() {
  printf '%064s' "${1#0x}" | tr ' ' '0'
}

printf 'strategy,code_bytes,decimals,totalAssets,balanceOf,convertToAssets,previewRedeem\n'
for strategy in "${STRATEGIES[@]}"; do
  code="$(curl -sS "$RPC" -H 'content-type: application/json' \
    --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getCode\",\"params\":[\"0x$strategy\",\"latest\"]}" |
    jq -r '.result')"

  shares="$(call "$strategy" "70a08231$(encode_address "$VAULT")")"
  total="$(call "$strategy" "01e1d114")"
  decimals="$(call "$strategy" "313ce567")"
  assets='not queried'
  preview='not queried'

  if [[ "$shares" == 0x* ]]; then
    assets="$(call "$strategy" "07a2d13a$(encode_uint "$shares")")"
    preview="$(call "$strategy" "4b3c5f3f$(encode_uint "$shares")")"
  fi

  code_hex_len=${`#code`}
  code_bytes=$(( (code_hex_len - 2) / 2 ))
  printf '%s,%s,%s,%s,%s,%s,%s\n' \
    "$strategy" "$code_bytes" "$decimals" "$total" "$shares" "$assets" "$preview"
done

Repository: DefiLlama/yield-server

Length of output: 1183


🏁 Script executed:

#!/bin/bash
set -euo pipefail

RPC='https://mainnet.base.org'
VAULT='8694d7d44309665d51cb5002fcec0454f1c233de'
STRATEGIES=(
  'ee8f4ec5672f09119b96ab6fb59c27e1b7e44b61'
  'beefe94c8ad530842bfe7d8b397938ffc1cb83b2'
  'c1256ae5ff1cf2719d4937adb3bbccab2e00a2ca'
  '236919f11ff9ea9550a4287696c2fc9e18e6e890'
  'beefa7b88064feef0cee02aaebbd95d30df3878f'
)

call() {
  local to="$1" data="$2"
  curl -sS "$RPC" -H 'content-type: application/json' \
    --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_call\",\"params\":[{\"to\":\"0x$to\",\"data\":\"0x$data\"},\"latest\"]}" |
    jq -r 'if .error then ("ERROR: " + (.error.message // "unknown")) else (.result // "NO_RESULT") end'
}

encode_address() { printf '%064s' "$1" | tr ' ' '0'; }
encode_uint() { printf '%064s' "${1#0x}" | tr ' ' '0'; }

printf 'strategy,code_bytes,decimals,totalAssets,balanceOf,convertToAssets,previewRedeem\n'
for strategy in "${STRATEGIES[@]}"; do
  code="$(curl -sS "$RPC" -H 'content-type: application/json' \
    --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getCode\",\"params\":[\"0x$strategy\",\"latest\"]}" |
    jq -r '.result')"
  shares="$(call "$strategy" "70a08231$(encode_address "$VAULT")")"
  total="$(call "$strategy" "01e1d114")"
  decimals="$(call "$strategy" "313ce567")"
  assets='not queried'
  preview='not queried'

  if [[ "$shares" == 0x* ]]; then
    assets="$(call "$strategy" "07a2d13a$(encode_uint "$shares")")"
    preview="$(call "$strategy" "4b3c5f3f$(encode_uint "$shares")")"
  fi

  code_bytes=$(( (${`#code`} - 2) / 2 ))
  printf '%s,%s,%s,%s,%s,%s,%s\n' \
    "$strategy" "$code_bytes" "$decimals" "$total" "$shares" "$assets" "$preview"
done

Repository: DefiLlama/yield-server

Length of output: 1139


🏁 Script executed:

python3 - <<'PY'
import json
import urllib.request

rpc = "https://mainnet.base.org"
vault = "8694d7d44309665d51cb5002fcec0454f1c233de"
strategies = [
    "ee8f4ec5672f09119b96ab6fb59c27e1b7e44b61",
    "beefe94c8ad530842bfe7d8b397938ffc1cb83b2",
    "c1256ae5ff1cf2719d4937adb3bbccab2e00a2ca",
    "236919f11ff9ea9550a4287696c2fc9e18e6e890",
    "beefa7b88064feef0cee02aaebbd95d30df3878f",
]

def word(value):
    return value.removeprefix("0x").rjust(64, "0")

calls = []
labels = []
for strategy in strategies:
    for label, selector, arg in [
        ("decimals", "313ce567", ""),
        ("asset", "38d52e0f", ""),
        ("totalAssets", "01e1d114", ""),
        ("totalSupply", "18160ddd", ""),
        ("balanceOf", "70a08231", word(vault)),
    ]:
        calls.append({
            "jsonrpc": "2.0",
            "id": len(calls) + 1,
            "method": "eth_call",
            "params": [{"to": "0x" + strategy, "data": "0x" + selector + arg}, "latest"],
        })
        labels.append((strategy, label))

# Use one HTTP request to avoid per-call rate limiting.
request = urllib.request.Request(
    rpc,
    data=json.dumps(calls).encode(),
    headers={"content-type": "application/json"},
)
with urllib.request.urlopen(request, timeout=30) as response:
    results = {item["id"]: item for item in json.load(response)}

for index, (strategy, label) in enumerate(labels, start=1):
    item = results[index]
    if "error" in item:
        value = "ERROR: " + item["error"].get("message", "unknown")
    else:
        raw = item.get("result")
        try:
            value = f"{int(raw, 16)} ({raw})"
        except (TypeError, ValueError):
            value = raw
    print(f"{strategy},{label},{value}")
PY

Repository: DefiLlama/yield-server

Length of output: 2447


🏁 Script executed:

python3 - <<'PY'
import json
import urllib.request

rpc = "https://mainnet.base.org"
vault = "8694d7d44309665d51cb5002fcec0454f1c233de"
strategies = [
    "ee8f4ec5672f09119b96ab6fb59c27e1b7e44b61",
    "beefe94c8ad530842bfe7d8b397938ffc1cb83b2",
    "c1256ae5ff1cf2719d4937adb3bbccab2e00a2ca",
    "236919f11ff9ea9550a4287696c2fc9e18e6e890",
    "beefa7b88064feef0cee02aae bbd95d30df3878f".replace(" ", ""),
]

def word(value):
    return value.removeprefix("0x").rjust(64, "0")

calls = []
labels = []
for strategy in strategies:
    for label, selector, arg in [
        ("decimals", "313ce567", ""),
        ("asset", "38d52e0f", ""),
        ("totalAssets", "01e1d114", ""),
        ("totalSupply", "18160ddd", ""),
        ("balanceOf", "70a08231", word(vault)),
    ]:
        calls.append({
            "jsonrpc": "2.0",
            "id": len(calls) + 1,
            "method": "eth_call",
            "params": [{"to": "0x" + strategy, "data": "0x" + selector + arg}, "latest"],
        })
        labels.append((strategy, label))

request = urllib.request.Request(
    rpc,
    data=json.dumps(calls).encode(),
    headers={"content-type": "application/json"},
)
with urllib.request.urlopen(request, timeout=30) as response:
    results = {item["id"]: item for item in json.load(response)}

for index, (strategy, label) in enumerate(labels, start=1):
    item = results[index]
    if "error" in item:
        value = "ERROR: " + item["error"].get("message", "unknown")
    else:
        raw = item.get("result")
        try:
            value = f"{int(raw, 16)} ({raw})"
        except (TypeError, ValueError):
            value = raw
    print(f"{strategy},{label},{value}")
PY

Repository: DefiLlama/yield-server

Length of output: 2447


🏁 Script executed:

python3 - <<'PY' >/tmp/base-batch.json
import json

vault = "8694d7d44309665d51cb5002fcec0454f1c233de"
strategies = [
    "ee8f4ec5672f09119b96ab6fb59c27e1b7e44b61",
    "beefe94c8ad530842bfe7d8b397938ffc1cb83b2",
    "c1256ae5ff1cf2719d4937adb3bbccab2e00a2ca",
    "236919f11ff9ea9550a4287696c2fc9e18e6e890",
    "beefa7b88064feef0cee02aaebbd95d30df3878f",
]

def word(value):
    return value.removeprefix("0x").rjust(64, "0")

calls = []
labels = []
for strategy in strategies:
    for label, selector, arg in [
        ("decimals", "313ce567", ""),
        ("asset", "38d52e0f", ""),
        ("totalAssets", "01e1d114", ""),
        ("totalSupply", "18160ddd", ""),
        ("balanceOf", "70a08231", word(vault)),
    ]:
        calls.append({
            "jsonrpc": "2.0",
            "id": len(calls) + 1,
            "method": "eth_call",
            "params": [{"to": "0x" + strategy, "data": "0x" + selector + arg}, "latest"],
        })
        labels.append((strategy, label))

print(json.dumps(calls))
PY

curl -sS -k 'https://mainnet.base.org' \
  -H 'content-type: application/json' \
  --data-binary `@/tmp/base-batch.json` |
python3 -c '
import json, sys
strategies = [
    "ee8f4ec5672f09119b96ab6fb59c27e1b7e44b61",
    "beefe94c8ad530842bfe7d8b397938ffc1cb83b2",
    "c1256ae5ff1cf2719d4937adb3bbccab2e00a2ca",
    "236919f11ff9ea9550a4287696c2fc9e18e6e890",
    "beefa7b88064feef0cee02aae bbd95d30df3878f".replace(" ", ""),
]
labels = ["decimals", "asset", "totalAssets", "totalSupply", "balanceOf"]
items = {x["id"]: x for x in json.load(sys.stdin)}
for i, strategy in enumerate(strategies):
    for j, label in enumerate(labels):
        item = items[i * 5 + j + 1]
        if "error" in item:
            value = "ERROR: " + item["error"].get("message", "unknown")
        else:
            raw = item.get("result")
            try:
                value = f"{int(raw, 16)} ({raw})"
            except (TypeError, ValueError):
                value = raw
        print(f"{strategy},{label},{value}")
'

Repository: DefiLlama/yield-server

Length of output: 320


🏁 Script executed:

curl -sS -k 'https://base.publicnode.com' \
  -H 'content-type: application/json' \
  --data '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"0xee8f4ec5672f09119b96ab6fb59c27e1b7e44b61","data":"0x07a2d13a000000000000000000000000000000000000000000000000000000bbf4dca14e"},"latest"]}' |
jq .

Repository: DefiLlama/yield-server

Length of output: 276


🏁 Script executed:

#!/bin/bash
set -euo pipefail

RPC='https://base.publicnode.com'
VAULT='8694d7d44309665d51cb5002fcec0454f1c233de'
STRATEGY='ee8f4ec5672f09119b96ab6fb59c27e1b7e44b61'

call() {
  curl -sS -k "$RPC" \
    -H 'content-type: application/json' \
    --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_call\",\"params\":[{\"to\":\"0x$STRATEGY\",\"data\":\"0x$1\"},\"latest\"]}" |
    jq -r 'if .error then ("ERROR: " + (.error.message // "unknown")) else .result end'
}

address_word="$(printf '%064s' "$VAULT" | tr ' ' '0')"
shares="$(call "70a08231$address_word")"
shares_word="${shares#0x}"
shares_word="$(printf '%064s' "$shares_word" | tr ' ' '0')"

printf 'decimals=%s\n' "$(call '313ce567')"
printf 'asset=%s\n' "$(call '38d52e0f')"
printf 'totalAssets=%s\n' "$(call '01e1d114')"
printf 'totalSupply=%s\n' "$(call '18160ddd')"
printf 'balanceOf=%s\n' "$shares"
printf 'convertToAssets=%s\n' "$(call "07a2d13a$shares_word")"
printf 'previewRedeem=%s\n' "$(call "4b3c5f3f$shares_word")"

Repository: DefiLlama/yield-server

Length of output: 667


🏁 Script executed:

#!/bin/bash
set -euo pipefail

RPC='https://base.publicnode.com'
VAULT='8694d7d44309665d51cb5002fcec0454f1c233de'
STRATEGY='ee8f4ec5672f09119b96ab6fb59c27e1b7e44b61'

call() {
  curl -sS -k "$RPC" \
    -H 'content-type: application/json' \
    --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_call\",\"params\":[{\"to\":\"0x$STRATEGY\",\"data\":\"0x$1\"},\"latest\"]}" |
    jq -r 'if .error then ("ERROR: " + (.error.message // "unknown")) else .result end'
}

address_word="$(printf '%064s' "$VAULT" | tr ' ' '0')"
shares="$(call "70a08231$address_word")"
shares_word="$(printf '%064s' "${shares#0x}" | tr ' ' '0')"

printf 'decimals=%s\n' "$(call '313ce567')"
printf 'asset=%s\n' "$(call '38d52e0f')"
printf 'totalAssets=%s\n' "$(call '01e1d114')"
printf 'totalSupply=%s\n' "$(call '18160ddd')"
printf 'balanceOf=%s\n' "$shares"
printf 'convertToAssets=%s\n' "$(call "07a2d13a$shares_word")"
printf 'previewRedeem=%s\n' "$(call "4b3c5f3f$shares_word")"

Repository: DefiLlama/yield-server

Length of output: 667


🏁 Script executed:

python3 - <<'PY'
from decimal import Decimal, getcontext
getcontext().prec = 40

total_assets = int("182da02e8e33c", 16)
total_supply = int("13ddad2d3e32697812c5ca6", 16)
shares = int("bbf4dca14e", 16)

print("totalAssets_raw =", total_assets)
print("totalSupply_raw =", total_supply)
print("balanceOf_raw   =", shares)
print("adapter_balance =", Decimal(shares) / Decimal(10**6))
print("share_units     =", Decimal(shares) / Decimal(10**18))
print("proportional_assets_raw =", Decimal(shares) * Decimal(total_assets) / Decimal(total_supply))
print("proportional_assets_usdc =", Decimal(shares) * Decimal(total_assets) / Decimal(total_supply) / Decimal(10**6))

Repository: DefiLlama/yield-server

Length of output: 564


Convert MetaMorpho shares to USDC assets before allocation.

balanceOf(VAULT) returns 18-decimal shares, but the adapter treats them as 6-decimal USDC. Call convertToAssets(balanceOf(VAULT)) for each strategy before calculating balance, idle, and apyBase; otherwise the allocation is overstated by the unit mismatch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/adaptors/earngrid/index.js` around lines 85 - 91, Update the strategy
balance-fetch flow in the STRATEGIES map to convert each MetaMorpho share
balance returned by abiBalanceOf into USDC assets using convertToAssets before
calculating balance, idle, or apyBase. Preserve the existing per-strategy
allocation calculations while ensuring they consume asset-denominated values.

Comment thread src/adaptors/earngrid/index.js Outdated
Comment thread src/adaptors/earngrid/index.js
@github-actions

Copy link
Copy Markdown

The earngrid adapter exports pools:

Test Suites: 1 passed, 1 total
Tests: 12 passed, 12 total
Snapshots: 0 total
Time: 0.234 s
Ran all test suites.

Nb of pools: 1
 

Sample pools:
┌─────────┬───────────────────────────────────────────────────┬────────┬────────────┬────────┬────────────┬─────────┬───────────┬──────────────────────────────────────────────────┬─────────────────────────┬────────────────┐
│ (index) │ pool                                              │ chain  │ project    │ symbol │ tvlUsd     │ apyBase │ apyReward │ underlyingTokens                                 │ url                     │ poolMeta       │
├─────────┼───────────────────────────────────────────────────┼────────┼────────────┼────────┼────────────┼─────────┼───────────┼──────────────────────────────────────────────────┼─────────────────────────┼────────────────┤
│ 0       │ '0x8694D7D44309665D51Cb5002fceC0454f1c233dE-base' │ 'Base' │ 'earngrid' │ 'USDC' │ 220.877044 │ 0       │ 0         │ [ '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' ] │ 'https://earngrid.site' │ '3 strategies' │
└─────────┴───────────────────────────────────────────────────┴────────┴────────────┴────────┴────────────┴─────────┴───────────┴──────────────────────────────────────────────────┴─────────────────────────┴────────────────┘
This adapter contains some pools with <10k TVL, these pools won't be shown in DefiLlama

@github-actions

Copy link
Copy Markdown

The earngrid adapter exports pools:

Test Suites: 1 passed, 1 total
Tests: 15 passed, 15 total
Snapshots: 0 total
Time: 0.233 s
Ran all test suites.

Nb of pools: 1
 

Sample pools:
┌─────────┬───────────────────────────────────────────────────┬────────┬────────────┬────────┬────────────────┬───────────────────┬───────────┬──────────────────────────────────────────────────┬─────────────────────────┬────────────────┬──────────────────────────────────────────────┬──────────────────────────┐
│ (index) │ pool                                              │ chain  │ project    │ symbol │ tvlUsd         │ apyBase           │ apyReward │ underlyingTokens                                 │ url                     │ poolMeta       │ token                                        │ pricePerShare            │
├─────────┼───────────────────────────────────────────────────┼────────┼────────────┼────────┼────────────────┼───────────────────┼───────────┼──────────────────────────────────────────────────┼─────────────────────────┼────────────────┼──────────────────────────────────────────────┼──────────────────────────┤
│ 0       │ '0x8694D7D44309665D51Cb5002fceC0454f1c233dE-base' │ 'Base' │ 'earngrid' │ 'USDC' │ 220.7923713073 │ 5846803483429.761 │ 0         │ [ '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' ] │ 'https://earngrid.site' │ '3 strategies' │ '0x8694D7D44309665D51Cb5002fceC0454f1c233dE' │ 0.0000010001058537809505 │
└─────────┴───────────────────────────────────────────────────┴────────┴────────────┴────────┴────────────────┴───────────────────┴───────────┴──────────────────────────────────────────────────┴─────────────────────────┴────────────────┴──────────────────────────────────────────────┴──────────────────────────┘
This adapter contains some pools with <10k TVL, these pools won't be shown in DefiLlama

@NukeThemAII

Copy link
Copy Markdown
Author

Can we try this one here, thanks. @RohanNero

@0xkr3p 0xkr3p left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi @NukeThemAII, thanks for your PR. We have a 10K TVL threshold on our yields and earn-grid pool is $220, pls give me a ping when TVL reaches near 10K until then I will review

@NukeThemAII

Copy link
Copy Markdown
Author

Updated for the v2 redeploy:

  • Vault repointed from retired v1.0.4 (0x8694D7D4…) to live v2.0.0 (0xbDacA8B7…, Sourcify exact_match)
  • Fixed two decimals bugs found in local testing: MetaMorpho balanceOf (18-dec shares) and bvUSDC totalSupply (12-dec shares) were both divided by 1e6 — that inflated APY by 1e12 and understated pricePerShare by 1e6
  • Local run now reports: tvlUsd $219.97, apyBase 5.49%, pricePerShare 1.000087 — matches onchain totalAssets() = $220.04

TVL still below the 10K threshold — no rush on review, just keeping the adapter current and CI-green.

@github-actions

Copy link
Copy Markdown

The earngrid adapter exports pools:

Test Suites: 1 passed, 1 total
Tests: 15 passed, 15 total
Snapshots: 0 total
Time: 0.224 s
Ran all test suites.

Nb of pools: 1
 

Sample pools:
┌─────────┬───────────────────────────────────────────────────┬────────┬────────────┬────────┬────────────────────┬───────────────────┬───────────┬──────────────────────────────────────────────────┬─────────────────────────┬───────────────────────┬──────────────────────────────────────────────┬────────────────────┐
│ (index) │ pool                                              │ chain  │ project    │ symbol │ tvlUsd             │ apyBase           │ apyReward │ underlyingTokens                                 │ url                     │ poolMeta              │ token                                        │ pricePerShare      │
├─────────┼───────────────────────────────────────────────────┼────────┼────────────┼────────┼────────────────────┼───────────────────┼───────────┼──────────────────────────────────────────────────┼─────────────────────────┼───────────────────────┼──────────────────────────────────────────────┼────────────────────┤
│ 0       │ '0xbDacA8B7782C66cc0ee32Cf70F835EBe86cb20D3-base' │ 'Base' │ 'earngrid' │ 'USDC' │ 219.96581540008583 │ 5.494866962064846 │ 0         │ [ '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' ] │ 'https://earngrid.site' │ '1 strategies + idle' │ '0xbDacA8B7782C66cc0ee32Cf70F835EBe86cb20D3' │ 1.0000872385071002 │
└─────────┴───────────────────────────────────────────────────┴────────┴────────────┴────────┴────────────────────┴───────────────────┴───────────┴──────────────────────────────────────────────────┴─────────────────────────┴───────────────────────┴──────────────────────────────────────────────┴────────────────────┘
This adapter contains some pools with <10k TVL, these pools won't be shown in DefiLlama

@0xkr3p
0xkr3p marked this pull request as draft August 15, 2026 07:22
@0xkr3p

0xkr3p commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Updated for the v2 redeploy:

  • Vault repointed from retired v1.0.4 (0x8694D7D4…) to live v2.0.0 (0xbDacA8B7…, Sourcify exact_match)
  • Fixed two decimals bugs found in local testing: MetaMorpho balanceOf (18-dec shares) and bvUSDC totalSupply (12-dec shares) were both divided by 1e6 — that inflated APY by 1e12 and understated pricePerShare by 1e6
  • Local run now reports: tvlUsd $219.97, apyBase 5.49%, pricePerShare 1.000087 — matches onchain totalAssets() = $220.04

TVL still below the 10K threshold — no rush on review, just keeping the adapter current and CI-green.

thanks for the update.. i've switched the PR to draft, pls change it back once ready

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants