CuyFI: shipping a cross-chain yield agent at ETH Global in 36 hours

A postmortem on building CuyFI at ETH Global Argentina — LayerZero bridges, Privy wallets, LangChain agents, and ERC4626 vaults in 36 hours. What worked, what broke, and what I'd do differently.

November 25, 2025
#hackathon#AI agents#DeFi#cross-chain#LayerZero#Privy
36 hours
ETH Global Argentina — one working cross-chain demo

ETH Global Argentina runs 36 hours. That’s not a lot of time to build something that actually works — not just a demo that passes the judges’ 2-minute slot, but something where the actual cross-chain message goes through, the vault accepts the deposit, and the agent reads the transaction receipt without exploding.

We built CuyFI. A cross-chain AI yield agent. Users send stablecoins once via a Telegram bot, and an LLM agent figures out where the best yield is — even if it’s on a different chain — and moves the funds there automatically.

The stack

Telegram Bot
Frontend
UX layer
LangChain + OpenAI
AI Agent
Python, intent parsing
Privy SDK
Wallets
Non-custodial per user
LayerZero OFT
Cross-chain
Polygon → Arbitrum
LayerTechnologyRole
FrontendTelegram BotUser interface — send messages, get confirmations
AI AgentPython / LangChain / OpenAIParse intent, select yield strategy
Yield DataCaesarAI via x402Real-time rate research (micropayment-gated)
WalletsPrivy server-side SDKNon-custodial smart wallets per user
Cross-chainLayerZero OFT + ComposerBridge USDT Polygon → Arbitrum
Hub ContractDiamond proxy EIP-2535, ArbitrumVault management + strategy execution
Spoke ContractPolygonUSDT deposit + bridge initiation
VaultsERC-4626 (Aave 4.2%, Pendle 6.1%)Yield-bearing strategy destinations
Atomic bridgeAvail Nexus SDKBridge-and-execute (partially shipped)

Hackathon timeline

0–6h Architecture + scaffold Repo, contracts, repo setup
6–12h Diamond + LZ deployed OFT adapter on testnet
12–18h LangChain agent wired get_yield, deposit_to_vault
18–24h Privy wallets Smart wallet per Telegram user
24–28h Bridge tested E2E Polygon → Arbitrum ✓
28–32h Avail Nexus Bridge ok, execute stalled
32–36h Demo hardened Pre-ran bridge txs for judges

What shipped vs. what didn’t

Shipped
Not shipped

Shipped

  • Telegram bot UX — full conversation, confirmation step
  • LangChain agent with tools: get_yield_opportunities, deposit_to_vault, bridge_funds
  • Privy embedded wallets — non-custodial, one per user
  • CaesarAI x402 yield data — blew through $20 budget in testing
  • LayerZero Polygon → Arbitrum — OFT adapter + Composer receiver
  • ERC-4626 vault deposit — Pendle USDC pool at 6.1% selected

Not shipped

  • Avail Nexus atomic execute — bridge half worked; execute needed ~4 more hours
  • x402 query caching — should have been Day 1 — 180 queries at $0.12 each

The LangChain tool definition

The agent’s tools are what made the natural-language → on-chain execution handoff work. Each tool has a strict typed schema — no hallucinated parameters.

@tool
def deposit_to_vault(
    vault_address: str,
    amount_usdc: float,
    chain: Literal["arbitrum", "polygon"],
    user_wallet: str,
) -> dict:
    """
    Deposit USDC into an ERC-4626 vault on the specified chain.
    Returns transaction hash and share amount received.
    Requires explicit user confirmation before calling.
    """
    # Privy signs the tx on behalf of the user's smart wallet
    tx = privy_client.sign_and_send(
        wallet=user_wallet,
        to=vault_address,
        data=encode_deposit(amount_usdc),
        chain_id=CHAIN_IDS[chain],
    )
    return {"tx_hash": tx.hash, "status": "pending"}

The Literal["arbitrum", "polygon"] constraint is load-bearing — without it the LLM picks arbitrary chain IDs.

Architecture

flowchart TD U["User (Telegram)"] --> A["LangChain Agent\n(Python / OpenAI)"] A <-->|"yield rates (x402)"| C["CaesarAI"] A <-->|"wallet balance"| P["Privy\n(smart wallet)"] A -->|"deposit"| V["ERC-4626 Vault\n(Arbitrum — Pendle 6.1%)"] A -->|"bridge"| LZ["LayerZero OFT adapter\n(Polygon → Arbitrum)"] LZ -->|"Composer receive"| D["Diamond Hub\n(Arbitrum EIP-2535)"] D --> VF["VaultFacet\n(strategy management)"] D --> LF["LZReceiverFacet\n(OFT + Composer)"] D --> OF["OracleFacet\n(share price cross-chain)"]

What I’d do differently

Diamond proxy was the wrong call for a hackathon. We spent 6 hours on the Diamond setup — 6 hours we could have used on the Avail integration. A simple UUPS proxy would have been fine.

Start with the demo path, add features backward. We built the full architecture first, then integrated the demo path. Should have been the reverse.

Budget the x402 micropayments in advance. 180 queries × $0.12 = $21.60 on a $20 budget. The fix — 5-minute yield data cache — should have been built Day 1.

The Telegram UX saved us. When contract deployment broke at hour 28, the working Telegram bot gave judges context. “This is what the user sees” is a powerful frame when the underlying tech is partially broken.

On LayerZero

DVN confirmation time on testnet: 12–15 minutes. Fine for real users, not great for live judging. We pre-ran bridge transactions to show completed receipts.

What CuyFI taught me about agent architecture

The experience that stuck with me most wasn’t the cross-chain engineering — it was how the LangChain agent handled ambiguity.

Users would send “put some money in yield” with no amount. The agent asked a clarifying question. The user replied “maybe $50?” — and the agent proceeded. That handoff between natural language and deterministic financial execution is where most AI DeFi projects fail.

The key: treat the Telegram conversation as stateful (agent remembered prior messages), and require explicit confirmation before any financial parameter. That confirmation step felt like friction in testing. In the live demo, it felt like trustworthiness.

CuyFI was a proof of concept. But it was a working one. That’s what hackathons are for.

Available for contracts