Moly — Lido Liquid Staking Skill

**For AI agents:** This skill document provides the mental model and operational guidelines for interacting with Lido through Moly. Read this before calling any tools.

What is Moly?

Moly is an AI-native interface to the Lido liquid staking protocol. It exposes 28 tools for staking, withdrawals, wrapping, governance, bridging, and monitoring — all designed for autonomous agent use.
Three deployment methods:
**CLI** (`@moly-mcp/lido`) — primary, with setup wizard, ledger, alerts, bounds
**MCP Server** — for embedding in custom agent setups
**Dashboard** — web UI with agentic chat

What is Lido?

Lido is the largest liquid staking protocol on Ethereum. You deposit ETH, receive **stETH** (staked ETH) in return, and earn validator rewards without locking up your funds or running a validator. Your stETH can be withdrawn back to ETH via an asynchronous withdrawal queue.

Core Concepts

stETH — The Rebasing Token

**stETH rebases daily.** Your balance increases automatically as Ethereum validator rewards accumulate:
No claiming needed — rewards appear directly in your wallet each day
1 stETH ≈ 1 ETH in value (1:1 plus accrued daily rewards)
Daily APR: ~3–4% annually (varies with validator participation)
Oracle reports rewards once per day, typically around 12:00 UTC
**Why this matters for agents:**
When you check balance tomorrow, your stETH count will be ~0.01% higher
Rebasing makes stETH incompatible with some DeFi protocols (AMMs, lending pools)
Use wstETH instead if you plan to use the position in DeFi

wstETH — The Non-Rebasing Wrapper

**wstETH (wrapped stETH)** solves the rebase problem for DeFi:
Fixed balance — your wstETH count never changes
Growing value — 1 wstETH becomes worth more stETH over time
DeFi compatible — Aave, Uniswap v3, Curve all support wstETH natively
Lossless — wrapping/unwrapping preserves underlying position exactly
**Conversion flow:**
1 stETH → wrap → 1 wstETH (receives slightly less due to accrued rewards)
1 wstETH → unwrap → 1+ stETH (redeems more as value has grown)
**When to use:**
**stETH:** Holding on mainnet, watching rewards grow visually, simple staking
**wstETH:** DeFi activity, bridging to L2 (Base, Arbitrum), avoiding rebase tax events

Staking Lifecycle

ETH → stake_eth → stETH (rebasing, earning daily)
wrap_steth → wstETH (fixed balance, growing value)
unwrap_wsteth → back to stETH
request_withdrawal → enter queue → finalized → claim_withdrawals → ETH

Withdrawal Queue Mechanics

Withdrawals are **asynchronous and non-instant**:
1. **request_withdrawal** — burns your stETH, mints an ERC-721 NFT (queue position)
2. **Validator exits** — Lido coordinates validator exits to cover the withdrawal. Takes hours to days.
3. **Finalization** — Once enough validators have exited, `get_withdrawal_status` returns `isFinalized: true`
4. **claim_withdrawals** — burns your NFT, receives ETH
**Limits:**
Minimum: 0.1 stETH per request
Maximum: 1000 stETH per request
Large withdrawals: split into multiple 1000 stETH requests
Wait time: typically 1–5 days (depends on queue depth and validator exit rate)

Governance (Lido DAO)

Lido is governed by **LDO token holders** via Aragon Voting:
Proposals are called "votes" with numeric IDs
Voting requires LDO tokens at the proposal's snapshot block
Options: YEA (support) or NAY (against)
Proposals pass if: support > threshold AND quorum is met
**Governance flow:**
get_proposals() → list all active proposals
get_proposal(id) → fetch details, voting window, thresholds
cast_vote(id, true/false) → YEA or NAY (requires LDO balance at snapshot)
**Key rule:** You must hold LDO tokens **at the proposal's snapshot block**, not just when you vote. Historical LDO balance matters.

Deployment Modes

Moly supports two independent toggles:

Mode: Simulation vs Live

ModeNetworkWritesUse When
|------|---------|--------|----------|
`simulation`Hoodi Testnet (560048)dry_run by defaultDevelopment, testing, demos, trying commands
`live`Ethereum Mainnet (1)Real transactionsProduction, actual staking
**Simulation behavior:**
All write tools simulate by default
You can see gas estimates and expected outputs without broadcasting
Useful for testing agent logic before going live
**Live behavior:**
Requires a private key
Requires explicit permission (bounds + policy controls)
Real transactions broadcast to Ethereum mainnet
All operations logged to activity ledger

Network: Hoodi Testnet vs Ethereum Mainnet

NetworkChain IDstETH AddressUse When
|---------|----------|---------------|----------|
**Hoodi Testnet**560048`0x3508A952...`Testing without real funds
**Ethereum Mainnet**1`0xae7ab965...`Production staking with real ETH
Both toggles are independent — you can simulate on mainnet or go live on testnet.

Safe Agent Patterns

**Always follow these patterns before taking action:**
1. **Always dry_run first** — especially on mainnet. Check gas estimates and expected outputs.
```
stake_eth(amount="1.0", dry_run=true) // see gas estimate first
stake_eth(amount="1.0", dry_run=false) // then broadcast if OK
```
2. **Check balance before staking** — ensure sufficient ETH for both stake amount + gas fee.
```
get_balance() // verify ETH balance >= stake amount + 0.01 ETH gas buffer
```
3. **Don't withdraw immediately** — the withdrawal queue adds 1–5 day overhead. Only request withdrawal if the agent/user actually needs ETH back.
4. **Wrap for DeFi** — if the position will be used in a DeFi protocol, wrap to wstETH first to avoid rebase issues.
```
wrap_steth(amount_steth="10.0", dry_run=true)
wrap_steth(amount_steth="10.0", dry_run=false) // then broadcast
```
5. **Governance requires LDO** — voting on proposals requires LDO tokens, not stETH. Check LDO balance before attempting to vote.
```
get_balance(address) // check LDO field
cast_vote(proposal_id=123, support=true) // will fail if no LDO
```
6. **Split large withdrawals** — requests over 1000 stETH will fail. Split into multiple 1000 stETH requests.
```
request_withdrawal(amount_steth="5000", dry_run=true) // will fail
// Instead:
request_withdrawal(amount_steth="1000", dry_run=false)
request_withdrawal(amount_steth="1000", dry_run=false)
// ... up to 5000 total
```

Tool Categories (28 total)

Read Tools (Balance, Rewards, Status)

ToolDescriptionParams
|------|-------------|--------|
`get_balance`ETH, stETH, wstETH balances + APR`address` (optional)
`get_rewards`Staking reward history`address` (optional), `days` (1–365)
`get_conversion_rate`stETH ↔ wstETH exchange rate(none)
`get_total_position`Cross-chain: ETH + stETH + wstETH across mainnet + Base + Arbitrum in ETH equivalent`address` (optional)
`get_withdrawal_requests`List pending withdrawal NFT IDs`address` (optional)
`get_withdrawal_status`Check finalization status per request ID`request_ids` (array)
`get_proposals`List recent Lido DAO governance proposals`count` (1–20, default 5)
`get_proposal`Detailed info on a specific proposal`proposal_id` (integer)
`get_trade_history`Query activity ledger with filters`tool` (name), `since` (date), `limit`
`get_staking_summary`Aggregate stats from ledger`since` (date)
`get_settings`Current mode, network, RPC (keys redacted)(none)
`get_bounds`Current policy bounds gating write operations(none)

Write Tools (All support `dry_run`)

ToolDescriptionKey Params
|------|-------------|-----------|
`stake_eth`Stake ETH → stETH (liquid staking)`amount_eth`, `dry_run`
`request_withdrawal`Queue withdrawal stETH → ETH`amount_steth` (0.1–1000), `dry_run`
`claim_withdrawals`Claim finalized withdrawals back to ETH`request_ids` (array), `dry_run`
`wrap_steth`Wrap stETH → wstETH (for DeFi)`amount_steth`, `dry_run`
`unwrap_wsteth`Unwrap wstETH → stETH (for rewards)`amount_wsteth`, `dry_run`
`cast_vote`Vote YEA/NAY on Lido DAO proposal`proposal_id`, `support` (true=YEA, false=NAY), `dry_run`
`update_settings`Change mode/network/RPC mid-conversation`mode`, `network`, `rpc`
`set_bounds`Update policy bounds (max stake, daily cap, gas reserve, auto-vote)`maxStakePerTx`, `maxDailyStake`, etc.

Bridge Tools (Mainnet only, L2 → L1 via LI.FI)

ToolDescriptionKey Params
|------|-------------|-----------|
`get_l2_balance`ETH and wstETH balances on Base or Arbitrum`source_chain` (base\arbitrum), `address`
`get_bridge_quote`Quote for bridging ETH or wstETH L2 → L1`source_chain`, `token`, `amount`
`bridge_to_ethereum`Execute bridge (requires private key)`source_chain`, `token`, `amount`, `dry_run`
`get_bridge_status`Check in-progress bridge tx status`tx_hash`, `source_chain`

Alert Tools (CLI + Daemon)

ToolDescriptionConditions
|------|-------------|-----------|
`set_alert`Create alert`balance_below`, `balance_above`, `reward_rate_below`, `reward_rate_above`, `withdrawal_ready`, `proposal_new`, `conversion_rate_above/below`
`list_alerts`List all configured alerts(none)
`remove_alert`Delete an alert by ID`id`
`configure_alert_channels`Set Telegram or webhook notifications`telegram_token`, `telegram_chat_id`, `webhook_url`

Policy Bounds (Safety Gates)

Write operations are gated by human-set bounds in `~/.moly/bounds.json`:
BoundDefaultPurpose
|-------|---------|---------|
`maxStakePerTx`1.0 ETHReject stake_eth calls over this amount
`maxDailyStake`10 ETHReject if total staking today would exceed this
`minEthReserve`0.5 ETHReject if ETH balance would drop below this
`governanceAutoVote`falseAllow agent to auto-vote on proposals (requires explicit opt-in)
`autoRestakeThreshold`0.01 ETHAuto-restake rewards once this threshold is reached
**Agent behavior:** Bounds are checked before any write operation. If a request violates bounds, the tool rejects it with a reason message.

Activity Ledger

Every write operation is logged to `~/.moly/ledger.db` (SQLite):
Tool name, timestamp, parameters, result, gas used
Queryable by tool name, date range, limit
Exportable to CSV/JSON for auditing
Useful for tracking agent behavior and costs
**Example:**
moly ledger list --tool stake_eth --since 2026-03-01 --limit 10
moly ledger stats --since 2026-03-01
moly ledger export --format csv

Security Model

**Private keys** stored in `~/.moly/config.json` (chmod 600) or OWS encrypted vault (`~/.ows/wallets/`)
**Keys never logged** — not printed, not sent to remote servers
**API keys** stored alongside config, same protections
**Simulation mode default** — nothing broadcasts unless explicitly set to live
**update_settings tool** intentionally cannot change private keys (only via `moly setup`)
**Policy bounds** — human-set limits gate all write operations before broadcast
**Activity ledger** — audit trail for compliance

Configuration Files

All stored in `~/.moly/`:
FilePurposePermissions
|------|---------|-------------|
`config.json`Network, mode, RPC, wallet, AI providerchmod 600
`bounds.json`Policy bounds gating write operationschmod 600
`alerts.json`Alert rules, daemon state, channel configchmod 600
`ledger.db`SQLite activity ledger (all tool executions)chmod 600

Common Agent Workflows

Workflow 1: Stake and Hold

1. get_balance() → check ETH available
2. stake_eth(amount_eth="0.5", dry_run=true) → see gas estimate
3. stake_eth(amount_eth="0.5", dry_run=false) → broadcast
4. Monitor get_balance() daily → see stETH balance grow

Workflow 2: Stake for DeFi

1. get_balance() → check ETH
2. stake_eth(amount_eth="1.0", dry_run=true)
3. stake_eth(amount_eth="1.0", dry_run=false)
4. wrap_steth(amount_steth="1.0", dry_run=true)
5. wrap_steth(amount_steth="1.0", dry_run=false) → now ready for DeFi

Workflow 3: Unstake and Withdraw

1. get_withdrawal_requests() → check pending requests
2. request_withdrawal(amount_steth="0.5", dry_run=true)
3. request_withdrawal(amount_steth="0.5", dry_run=false) → get NFT ID
4. (wait 1–5 days for finalization)
5. get_withdrawal_status(request_ids=["123", "124"]) → check finalization
6. claim_withdrawals(request_ids=["123"], dry_run=true)
7. claim_withdrawals(request_ids=["123"], dry_run=false) → receive ETH

Workflow 4: Governance Voting

1. get_balance() → check LDO balance
2. get_proposals(count=10) → list active proposals
3. get_proposal(proposal_id=42) → read proposal details
4. cast_vote(proposal_id=42, support=true, dry_run=true)
5. cast_vote(proposal_id=42, support=true, dry_run=false) → vote recorded

Troubleshooting

**"Insufficient ETH"**
`stake_eth` failed due to low balance
Solution: check `get_balance()`, ensure ETH ≥ (stake amount + 0.01 ETH gas buffer)
**"Bounds violation"**
Request exceeds policy bounds (maxStakePerTx, maxDailyStake, minEthReserve)
Solution: check `get_bounds()`, update with `set_bounds()` if needed
**"Governance requires LDO"**
`cast_vote` failed because LDO balance is zero at proposal snapshot
Solution: check `get_balance()` LDO field, acquire LDO before voting
**"Withdrawal not finalized"**
`claim_withdrawals` failed because validators haven't exited yet
Solution: check `get_withdrawal_status()`, wait 1–5 days, retry
**"Transaction dry run failed"**
Simulation call failed — likely insufficient gas or invalid parameters
Solution: check params, increase gas estimate, verify on testnet first

Key Resources

**Lido Docs:** https://docs.lido.fi
**Lido Deployed Contracts:** https://docs.lido.fi/deployed-contracts
**Lido SDK:** https://github.com/lidofinance/lido-ethereum-sdk
**Lido Governance:** https://dao.lido.fi (Aragon interface)
**Withdrawal Queue Spec:** https://docs.lido.fi/contracts/withdrawal-queue-erc721

Agent Integration Checklist

Before deploying an agent to use Moly:
[ ] Read this skill document fully
[ ] Test on Hoodi Testnet first (simulation mode)
[ ] Set up bounds in `~/.moly/bounds.json` (safety gates)
[ ] Configure alerts if monitoring needed (Telegram or webhook)
[ ] Run `moly setup` to configure wallet and mode
[ ] Test a small dry_run workflow on mainnet (simulation mode)
[ ] Review activity ledger (`moly ledger list`) to audit operations
[ ] Deploy to live mode only after validation
[ ] Monitor `moly monitor start` for ongoing alerts

**Last updated:** March 2026
**Moly Version:** 0.1.0+

Raw markdown: /api/skill