
Yield Optimizer
- 2 installs
- 1 repo stars
- Updated July 29, 2026
- starchild-ai-agent/community-skills
Scan 60+ USDC lending pools across 6 chains and 8+ protocols, auto-route deposits to the highest-yielding protocol, and manage withdrawals via a web UI.
About
Scans real-time USDC yields from DeFi Llama, Morpho, and Pendle across Aave V3, Morpho, Pendle, Compound, Spark, Fluid, and Euler, then auto-routes deposits to the best protocol per chain. A developer uses it to autonomously optimize stablecoin yield with an included Node/Express + ethers.js web UI.
- Auto-routes deposits to max APY among Aave, Morpho, or Pendle per chain
- Free data sources (DeFi Llama, Morpho GraphQL, Pendle SDK); full web UI
Yield Optimizer by the numbers
- 2 all-time installs (skills.sh)
- Ranked #408 of 479 Web3 & Blockchain skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
npx skills add https://github.com/starchild-ai-agent/community-skills --skill yield-optimizerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 1 |
| Last updated | July 29, 2026 |
| Repository | starchild-ai-agent/community-skills ↗ |
What it does
Scan 60+ USDC lending pools across 6 chains and 8+ protocols, auto-route deposits to the highest-yielding protocol, and manage withdrawals via a web UI.
Files
Yield Optimizer v2
Autonomous USDC yield optimization across DeFi lending protocols and chains. Auto-routes deposits to the best protocol per chain — Aave V3, Morpho (ERC-4626), or Pendle (PT fixed-rate).
What This Does
1. Scans real-time yields from DeFi Llama + Morpho GraphQL + Pendle Hosted SDK 2. Compares protocols per chain and picks the winner 3. Routes deposits to the optimal protocol automatically 4. Withdraws from whichever protocol holds the position 5. Full web UI with wallet integration (MetaMask, Rabby, etc.)
Data sources: DeFi Llama Yields API, Morpho GraphQL API, Pendle Hosted SDK API (all free, no key).
Supported Protocols & Chains
| Protocol | Type | Deposit Method | Chains |
|---|---|---|---|
| Aave V3 | Lending pool | pool.supply(asset, amount, onBehalfOf, 0) | ETH, ARB, Base, OP, Polygon, Avax |
| Morpho | ERC-4626 vaults | vault.deposit(assets, receiver) | ETH, ARB, Base, OP |
| Pendle | PT fixed-rate | Hosted SDK → router swap | ETH, ARB, Base |
| Compound V3 | Lending | comet.supply(asset, amount) | ETH, ARB, Base, OP, Polygon |
| Spark / Sky | Lending | pool.supply(...) | ETH |
| Fluid | Lending | vault.deposit(...) | ETH |
| Euler | Lending | vault.deposit(...) | ETH, ARB, Base |
Stablecoins tracked: USDC, USDT, DAI, USDS, sDAI, sUSDe, GHO
Architecture
Backend (Node.js / Express)
server.js
├── /api/pools — best protocol per chain (Aave vs Morpho vs Pendle)
├── /api/chains — supported chain list
├── /api/tvl — total TVL from DeFi Llama
├── /api/agent-activity — recent deposit/withdraw log
├── /api/vault/:addr — user positions across all protocols
├── /api/deposit — record deposit event
├── /api/withdraw — record withdraw event
├── /api/usdc-balance/:addr — cross-chain USDC balance (RPC reads)
└── /api/pendle-swap — get Pendle SDK calldata for PT swapFrontend (Vanilla JS + ethers.js)
public/
├── index.html — landing page + dashboard
├── styles.css — dark theme, glass-morphic UI
├── app.js — dashboard logic, deposit/withdraw modals
└── wallet.js — multi-protocol deposit routingProtocol Routing Logic
For each chain:
1. Fetch Aave V3 APY from DeFi Llama
2. Fetch Morpho vault APY from GraphQL (if vault exists on chain)
3. Fetch Pendle PT implied APY from Hosted SDK (if market exists on chain)
4. Return winner = max(aave_apy, morpho_apy, pendle_apy)
On deposit:
IF winner == "aave" → approve USDC → pool.supply()
IF winner == "morpho" → approve USDC → vault.deposit()
IF winner == "pendle" → approve USDC → router.swapExactTokenForPt()
On withdraw:
Read user balances across all protocols
IF has aTokens → pool.withdraw()
IF has Morpho shares → vault.withdraw() or vault.redeem()
IF has Pendle PT → router swap PT → USDC (or redeem at maturity)Core Workflows
1. Scan & Report (CLI)
When user asks "what are the best yields?" or "where should I put my USDC?":
1. Run python3 skills/yield-optimizer/scripts/scan_pools.py 2. Present top opportunities grouped by risk tier 3. Highlight the best rate per chain and the overall best
python3 skills/yield-optimizer/scripts/scan_pools.py # Full report
python3 skills/yield-optimizer/scripts/scan_pools.py --json # JSON for automation
python3 skills/yield-optimizer/scripts/scan_pools.py --chain Arbitrum # Filter by chain
python3 skills/yield-optimizer/scripts/scan_pools.py --protocol pendle # Filter by protocol
python3 skills/yield-optimizer/scripts/scan_pools.py --amount 10000 --risk balanced # Allocation2. Launch Web UI
For the full visual experience with wallet connection:
cd output/yield-agent-v4
npm install
node server.js # starts on port 3456Then use preview_serve to expose it.
3. Execute Deposit (Programmatic)
When user confirms a recommendation:
1. Check wallet balances via wallet skill 2. If USDC is on wrong chain, suggest bridging (across-bridge skill) 3. Determine best protocol for target chain (from /api/pools) 4. Execute the protocol-specific deposit flow 5. Verify position via balance read
Prerequisites: Load wallet-policy skill and propose standard wildcard policy before on-chain ops.
4. Monitor & Rebalance
For scheduled autonomous operation:
1. Scan yields every hour 2. Compare current position APY vs best available 3. If delta > 1% AND gas cost < 0.1% of position → recommend rebalance 4. Log all decisions with reasoning
schedule_task(
command="python3 skills/yield-optimizer/scripts/scan_pools.py --json",
schedule="every 1 hour"
)Risk Tiers
| Tier | Protocols | Characteristics |
|---|---|---|
| 🟢 Safe | Aave V3, Compound V3, Spark | Established, audited, >$100M TVL |
| 🟡 Moderate | Morpho, Euler, Fluid | Newer but audited, ERC-4626, >$10M TVL |
| 🔴 Aggressive | Pendle | Higher yields via PT fixed-rate, maturity risk |
Contract Addresses
See references/contracts.json for:
- Aave V3 pool addresses per chain
- USDC token addresses per chain
- aToken addresses per chain
- RPC endpoints per chain
Morpho vault addresses:
- Ethereum:
0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB(Steakhouse USDC) - Base:
0xc1256Ae5FF1cf2719D4937adb3bbCCab2E00A2Ca(Steakhouse USDC) - Arbitrum:
0x2C8FBB630Bae56bBC27E286d0D4816D62e4C1509(Steakhouse USDC) - Optimism:
0x7BfA7e0e3De0e3a6B4399F9CAa1E444Dd3d55c54(Gauntlet USDC Prime)
Key Gotchas
- APY ≠ APR. DeFi Llama returns APY (compounded). Don't double-compound.
- Reward APY is temporary.
apyReward(token incentives) can vanish. WeightapyBasehigher. - TVL matters. High APY + low TVL = unsustainable. Filter >$100K minimum, prefer >$10M.
- Gas costs eat small positions. Ethereum mainnet ≈ $5-15. Don't rebalance $500 for 0.3% more.
- Pendle PT has maturity dates. Full implied APY only if held to maturity. Early exit via AMM may differ.
- Morpho vaults are ERC-4626. Standard deposit/withdraw, but shares ≠ assets (use
convertToAssets). - Bridge time. Cross-chain moves take 2-15 minutes via Across. Don't show as instant.
- USDC decimals. Always 6 decimals.
parseUnits(amount, 6)not 18.
Integration with Other Skills
| Skill | Purpose |
|---|---|
| wallet | Balance checks, transaction execution |
| across-bridge | Cross-chain USDC transfers when rebalancing |
| wallet-policy | Ensure wallet policy is set before on-chain ops |
| coinglass / market-data | Broader market context for yield decisions |
| charting | Visualize yield trends over time |
{
"aave_v3_pools": {
"Base": {
"chainId": 8453,
"pool": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5",
"usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"aUSDC": "0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB",
"rpc": "https://mainnet.base.org",
"explorer": "https://basescan.org"
},
"Ethereum": {
"chainId": 1,
"pool": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
"usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"aUSDC": "0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c",
"rpc": "https://eth.llamarpc.com",
"explorer": "https://etherscan.io"
},
"Arbitrum": {
"chainId": 42161,
"pool": "0x794a61358D6845594F94dc1DB02A252b5b4814aD",
"usdc": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"aUSDC": "0x724dc807b04555b71ed48a6896b6F41593b8C637",
"rpc": "https://arb1.arbitrum.io/rpc",
"explorer": "https://arbiscan.io"
},
"Optimism": {
"chainId": 10,
"pool": "0x794a61358D6845594F94dc1DB02A252b5b4814aD",
"usdc": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"aUSDC": "0x38d693cE1dF5AaDF7bC62CAe1A8ADba061f1B5A0",
"rpc": "https://mainnet.optimism.io",
"explorer": "https://optimistic.etherscan.io"
},
"Polygon": {
"chainId": 137,
"pool": "0x794a61358D6845594F94dc1DB02A252b5b4814aD",
"usdc": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"aUSDC": "0xA4D94019934D8333Ef880ABFFbF2FDd611C0b978",
"rpc": "https://polygon-rpc.com",
"explorer": "https://polygonscan.com"
},
"Avalanche": {
"chainId": 43114,
"pool": "0x794a61358D6845594F94dc1DB02A252b5b4814aD",
"usdc": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
"aUSDC": "0x625E7708f30cA75bfd92586e17077590C60eb4cD",
"rpc": "https://api.avax.network/ext/bc/C/rpc",
"explorer": "https://snowtrace.io"
}
},
"morpho_vaults": {
"Ethereum": {
"chainId": 1,
"vault": "0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB",
"name": "Steakhouse USDC",
"usdc": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"Base": {
"chainId": 8453,
"vault": "0xc1256Ae5FF1cf2719D4937adb3bbCCab2E00A2Ca",
"name": "Steakhouse USDC",
"usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
},
"Arbitrum": {
"chainId": 42161,
"vault": "0x2C8FBB630Bae56bBC27E286d0D4816D62e4C1509",
"name": "Steakhouse USDC",
"usdc": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
},
"Optimism": {
"chainId": 10,
"vault": "0x7BfA7e0e3De0e3a6B4399F9CAa1E444Dd3d55c54",
"name": "Gauntlet USDC Prime",
"usdc": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85"
}
},
"pendle_markets": {
"Ethereum": {
"chainId": 1,
"router": "0x888888888889758F76e7103c6CbF23ABbF58F946",
"sdk_base_url": "https://api-v2.pendle.finance/sdk/api",
"note": "Markets change frequently — use Pendle SDK /markets endpoint to discover active PT-USDC markets"
},
"Arbitrum": {
"chainId": 42161,
"router": "0x888888888889758F76e7103c6CbF23ABbF58F946",
"sdk_base_url": "https://api-v2.pendle.finance/sdk/api",
"note": "Markets change frequently — use Pendle SDK /markets endpoint to discover active PT-USDC markets"
},
"Base": {
"chainId": 8453,
"router": "0x888888888889758F76e7103c6CbF23ABbF58F946",
"sdk_base_url": "https://api-v2.pendle.finance/sdk/api",
"note": "Markets change frequently — use Pendle SDK /markets endpoint to discover active PT-USDC markets"
}
},
"erc20_abi": [
"function approve(address spender, uint256 amount) returns (bool)",
"function allowance(address owner, address spender) view returns (uint256)",
"function balanceOf(address account) view returns (uint256)",
"function decimals() view returns (uint8)"
],
"aave_pool_abi": [
"function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)",
"function withdraw(address asset, uint256 amount, address to) returns (uint256)"
],
"morpho_vault_abi": [
"function deposit(uint256 assets, address receiver) returns (uint256 shares)",
"function withdraw(uint256 assets, address receiver, address owner) returns (uint256 shares)",
"function redeem(uint256 shares, address receiver, address owner) returns (uint256 assets)",
"function convertToAssets(uint256 shares) view returns (uint256)",
"function convertToShares(uint256 assets) view returns (uint256)",
"function balanceOf(address account) view returns (uint256)",
"function totalAssets() view returns (uint256)"
]
}
#!/usr/bin/env python3
"""
Starchild Yield Optimizer — Pool Scanner
Fetches real-time stablecoin yields from DeFi Llama.
Ranks by risk-adjusted return across 8+ protocols, 6 chains.
Usage:
python3 scan_pools.py # Full formatted report
python3 scan_pools.py --json # JSON output for automation
python3 scan_pools.py --chain Base # Filter by chain
python3 scan_pools.py --protocol aave-v3 # Filter by protocol
python3 scan_pools.py --top 5 # Top N only
python3 scan_pools.py --token USDC # Filter by token
python3 scan_pools.py --amount 10000 --risk balanced # Allocation recommendation
"""
import requests, json, sys, argparse
from datetime import datetime, timezone
DEFILLAMA_YIELDS = "https://yields.llama.fi/pools"
PROTOCOLS = [
"aave-v3","compound-v3","morpho-v1","morpho-blue","pendle",
"fluid-lending","sky-lending","sparklend","euler","seamless-protocol","moonwell-v2",
]
CHAINS = ["Ethereum","Arbitrum","Base","Optimism","Polygon","Avalanche"]
STABLES = ["USDC","USDT","DAI","USDS","sDAI","sUSDe","GHO","SUSDAI"]
RISK_TIERS = {
"aave-v3":1,"compound-v3":1,"sparklend":1,"sky-lending":1,
"morpho-v1":2,"morpho-blue":2,"euler":2,"fluid-lending":2,
"seamless-protocol":2,"moonwell-v2":2,"pendle":3,
}
CHAIN_MAP = {
"Ethereum":{"tool":"ethereum","id":1},"Arbitrum":{"tool":"arbitrum","id":42161},
"Base":{"tool":"base","id":8453},"Optimism":{"tool":"optimism","id":10},
"Polygon":{"tool":"polygon","id":137},"Avalanche":{"tool":"avalanche","id":43114},
}
GAS_EST = {"Ethereum":8.0,"Arbitrum":0.15,"Base":0.08,"Optimism":0.15,"Polygon":0.03,"Avalanche":0.15}
def fetch_pools():
resp = requests.get(DEFILLAMA_YIELDS, timeout=30)
resp.raise_for_status()
return resp.json()["data"]
def filter_pools(raw, chain=None, protocol=None, token=None, min_tvl=100_000):
pools = []
for p in raw:
proj, sym, ch = p.get("project",""), (p.get("symbol") or "").upper(), p.get("chain","")
tvl, apy = p.get("tvlUsd",0) or 0, p.get("apy",0) or 0
if proj not in PROTOCOLS or ch not in CHAINS: continue
if not any(s in sym for s in STABLES): continue
if tvl < min_tvl: continue
if chain and ch.lower() != chain.lower(): continue
if protocol and proj != protocol: continue
if token and token.upper() not in sym: continue
pools.append({
"pool_id":p.get("pool",""), "protocol":proj, "chain":ch,
"chain_tool":CHAIN_MAP.get(ch,{}).get("tool",ch.lower()),
"chain_id":CHAIN_MAP.get(ch,{}).get("id",0), "symbol":sym,
"apy":round(apy,4), "apy_base":round(p.get("apyBase",0) or 0,4),
"apy_reward":round(p.get("apyReward",0) or 0,4),
"tvl":round(tvl), "risk_tier":RISK_TIERS.get(proj,3),
"gas_estimate":GAS_EST.get(ch,1.0),
})
pools.sort(key=lambda x: x["apy"], reverse=True)
return pools
def compute_stats(pools):
if not pools: return {}
total_tvl = sum(p["tvl"] for p in pools)
avg_apy = sum(p["apy"] for p in pools) / len(pools)
best_chain, best_proto = {}, {}
for p in pools:
if p["chain"] not in best_chain or p["apy"] > best_chain[p["chain"]]["apy"]:
best_chain[p["chain"]] = p
if p["protocol"] not in best_proto or p["apy"] > best_proto[p["protocol"]]["apy"]:
best_proto[p["protocol"]] = p
tiers = {1:0,2:0,3:0}
for p in pools: tiers[p["risk_tier"]] = tiers.get(p["risk_tier"],0)+1
high_yield = [p for p in pools if p["apy"] > avg_apy * 1.5][:5]
return {
"total_pools":len(pools), "total_tvl":total_tvl, "avg_apy":round(avg_apy,2),
"best_pool":pools[0],
"best_by_chain":{k:{"chain":v["chain"],"protocol":v["protocol"],"symbol":v["symbol"],"apy":v["apy"]}
for k,v in sorted(best_chain.items(), key=lambda x:x[1]["apy"], reverse=True)},
"best_by_protocol":{k:{"chain":v["chain"],"symbol":v["symbol"],"apy":v["apy"]}
for k,v in sorted(best_proto.items(), key=lambda x:x[1]["apy"], reverse=True)},
"tier_counts":tiers,
"high_yield_alerts":[{"protocol":p["protocol"],"chain":p["chain"],"symbol":p["symbol"],"apy":p["apy"]} for p in high_yield],
"chains_monitored":len(best_chain), "protocols_monitored":len(best_proto),
"scan_time":datetime.now(timezone.utc).isoformat(),
}
def recommend_allocation(pools, amount, risk="balanced"):
if not pools or amount <= 0: return None
t1 = [p for p in pools if p["risk_tier"]==1]
t2 = [p for p in pools if p["risk_tier"]==2]
t3 = [p for p in pools if p["risk_tier"]==3]
splits = {"conservative":[(t1,1.0)],"balanced":[(t1,0.7),(t2,0.3)],"aggressive":[(t1,0.5),(t2,0.3),(t3,0.2)]}
alloc, wapy = [], 0
for tp, w in splits.get(risk, splits["balanced"]):
if not tp: continue
b = tp[0]; amt = round(amount*w,2)
alloc.append({"pool":f"{b['protocol']} {b['chain']} {b['symbol']}","protocol":b["protocol"],
"chain":b["chain"],"chain_tool":b["chain_tool"],"symbol":b["symbol"],"apy":b["apy"],
"amount_usd":amt,"weight_pct":round(w*100),"annual_yield_usd":round(amt*b["apy"]/100,2),
"risk_tier":b["risk_tier"]})
wapy += b["apy"]*w
return {"risk_profile":risk,"total_amount":amount,"allocations":alloc,
"blended_apy":round(wapy,2),"projected_annual_yield":round(amount*wapy/100,2),
"projected_monthly_yield":round(amount*wapy/100/12,2)}
def print_report(pools, stats):
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
print(f"🌾 Starchild Yield Optimizer — {now}")
print(f"📊 {stats['total_pools']} pools | {stats['chains_monitored']} chains | {stats['protocols_monitored']} protocols | ${stats['total_tvl']/1e9:.1f}B TVL")
print("="*70)
print(f"\n🏆 TOP YIELDS")
print(f"{'#':<4} {'Protocol':<16} {'Chain':<12} {'Symbol':<10} {'APY%':<9} {'TVL($M)':<10} {'Tier'}")
print("-"*70)
for i,p in enumerate(pools[:15],1):
t = {1:"🟢",2:"🟡",3:"🔴"}.get(p["risk_tier"],"⚪")
print(f"{i:<4} {p['protocol']:<16} {p['chain']:<12} {p['symbol']:<10} {p['apy']:<9.2f} {p['tvl']/1e6:<10.1f} {t}")
print(f"\n⛓️ BEST PER CHAIN")
for ch,info in stats["best_by_chain"].items():
print(f" {ch:<12} → {info['apy']:.2f}% ({info['protocol']} {info['symbol']})")
if stats.get("high_yield_alerts"):
print(f"\n🔥 HIGH YIELD (>{stats['avg_apy']*1.5:.1f}%)")
for a in stats["high_yield_alerts"]:
print(f" ⚡ {a['protocol']} {a['chain']} — {a['symbol']} at {a['apy']:.2f}%")
tc = stats["tier_counts"]
print(f"\n📊 🟢 Safe:{tc.get(1,0)} | 🟡 Moderate:{tc.get(2,0)} | 🔴 Aggressive:{tc.get(3,0)} | Avg APY: {stats['avg_apy']:.2f}%")
def main():
pa = argparse.ArgumentParser(description="Starchild Yield Optimizer")
pa.add_argument("--json", action="store_true")
pa.add_argument("--chain", type=str)
pa.add_argument("--protocol", type=str)
pa.add_argument("--token", type=str)
pa.add_argument("--top", type=int, default=0)
pa.add_argument("--amount", type=float, default=0)
pa.add_argument("--risk", type=str, default="balanced", choices=["conservative","balanced","aggressive"])
pa.add_argument("--min-tvl", type=float, default=100000)
args = pa.parse_args()
raw = fetch_pools()
pools = filter_pools(raw, chain=args.chain, protocol=args.protocol, token=args.token, min_tvl=args.min_tvl)
if args.top > 0: pools = pools[:args.top]
stats = compute_stats(pools)
if args.json:
out = {"pools":pools[:30],"stats":stats}
if args.amount > 0: out["recommendation"] = recommend_allocation(pools, args.amount, args.risk)
print(json.dumps(out, indent=2))
else:
print_report(pools, stats)
if args.amount > 0:
rec = recommend_allocation(pools, args.amount, args.risk)
if rec:
print(f"\n💰 ALLOCATION ({rec['risk_profile'].upper()}) for ${rec['total_amount']:,.0f}")
print("-"*50)
for a in rec["allocations"]:
print(f" {a['weight_pct']}% → {a['pool']} at {a['apy']:.2f}% = ${a['annual_yield_usd']:,.0f}/yr")
print(f"\n Blended: {rec['blended_apy']:.2f}% | ${rec['projected_annual_yield']:,.0f}/yr | ${rec['projected_monthly_yield']:,.0f}/mo")
if __name__ == "__main__":
main()