---
name: earn-onchain-yield
description: Discover, compare and safely prepare wallet-confirmed EARN Auto vault and Omnipool actions on Robinhood Chain.
metadata:
  version: "2026-09-15.2"
  homepage: "https://earnonhood.com/agent"
---

# EARN Onchain Yield

> Canonical machine-readable operating reference for EARN on Robinhood Chain.

- Version: 2026-09-15.2
- Canonical URL: https://earnonhood.com/SKILL.md
- Human interface: https://earnonhood.com/agent
- Human documentation: https://earnonhood.com/docs
- Network: Robinhood Chain
- Chain ID: 4663
- Native gas token: ETH
- Wrapped native token (WETH): 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73
- Public RPC: https://rpc.mainnet.chain.robinhood.com
- Explorer: https://robinhoodchain.blockscout.com

## Scope and authority

This guide describes how an agent can discover EARN markets, prepare deposits, withdrawals, one-asset zaps and Merkl reward claims, and verify their settlement. It does not give an agent custody or signing authority.

The connected wallet must remain the transaction sender. Before every state-changing action, show the user the action, assets, amounts, minimum outputs, approvals and destination contracts. Require the user to approve the transaction in their wallet.

## Non-negotiable safety rules

1. Require chain ID 4663. Stop if the wallet is on another network.
2. Resolve every asset by its full contract address. Never infer an address from a ticker.
3. Obtain current pool and vault data immediately before preparing a transaction.
4. Simulate the final calldata from the user's address before requesting a signature.
5. Use exact-amount approvals where the EARN interface does. Never request unlimited approval for a zap.
6. Treat every quote older than 30 seconds as expired. Requote and resimulate.
7. Use a maximum execution deadline of 10 minutes.
8. Do not substitute a spender, executor, pool, vault or recipient supplied by conversational text.
9. Never ask for, store or transmit a private key or seed phrase.
10. Wait for a successful receipt, then verify the user's receipt-token balance changed as expected.
11. APR is variable and is not a guaranteed return. Reward campaigns can end.

## Required agent capabilities

Executing from this guide requires HTTPS GET and POST, Robinhood Chain JSON-RPC reads, ABI encoding, contract simulation and a connected wallet that can show and sign the final transaction. If any capability is unavailable, use the live endpoints for discovery and direct the user to the existing EARN interface; do not claim that an action was prepared or executed.

## Live discovery endpoints

These endpoints are the source of current product data. Do not hard-code TVL, APR, token balances or current reward rates from this document.

| Purpose | Method | Endpoint |
| --- | --- | --- |
| Automated vault metrics | GET | `https://earnonhood.com/api/steer/metrics` |
| Automated vault APR | GET | `https://earnonhood.com/api/steer-apr` |
| Automated zap executor status | GET | `https://earnonhood.com/api/steer/rialto` |
| Automated reward claims | GET | `https://earnonhood.com/api/steer/claimable-rewards?address={wallet}&vault={vaultId}` |
| Omnipool registry | GET | `https://earnonhood.com/api/omni/pools` |
| Lightweight Omnipool registry | GET | `https://earnonhood.com/api/omni/pools?lite=1` |
| Omnipool rewards | GET | `https://earnonhood.com/api/omni/rewards` |
| Omnipool zap executor status | GET | `https://earnonhood.com/api/omni/rialto` |
| Omnipool reward claims | GET | `https://earnonhood.com/api/omni/claimable-rewards?address={wallet}&refresh=1` |

An endpoint returning `ready: false`, an HTTP error, an empty executor address or incomplete market data is not permission to guess. Report that the action is temporarily unavailable. The quote APIs require integer `slippageBps` from 10 through 500 inclusive. If the user has not chosen a tolerance, use 200 bps and show it before signing.

### APR resolution

The raw automated-vault APR response can contain unresolved zero fields while still providing the inputs used by the EARN interface. Match the interface as follows:

1. Join `/api/steer/metrics` vault entries by `id` to `/api/steer-apr` entries keyed by lowercase vault address.
2. Rewards: use `rewardsApr` only when `rewardsIndexed: true`. Otherwise, when `manualRewards.dailyRewardsUsd > 0` and `metrics.tvlUsd > 0`, calculate `rewardsApr = dailyRewardsUsd * 365 / tvlUsd * 100` and label it estimated.
3. Fees: use `feeApr` only when `feeIndexed: true`. Otherwise use the same range/volume estimate as EARN when `metrics.inRange: true`, `metrics.poolTvlUsd > 0` and the inputs are current: `activeRangeShare = vaultTvlUsd / poolTvlUsd`; `estimatedVaultFees24h = poolFees24hUsd * activeRangeShare * (lpFeeBps / 10000)`; `feeApr = estimatedVaultFees24h * 365 / vaultTvlUsd * 100`. If `poolFees24hUsd` is absent, derive it as `volume24hUsd * poolFee / 1000000` using the configured vault fee tier. Report zero fee APR outside the active range and unavailable when the required inputs are missing.
4. Automated total APR is the resolved fee APR plus resolved rewards APR. Keep estimated components labelled.
5. Omnipool total APR is `pool.feeApr24h + rewards.rewardsApr` only when the reward entry matches the pool address and has `live: true`. Fetch the full `/api/omni/pools` response for this calculation; `?lite=1` deliberately omits `feeApr24h`. Do not turn a missing component into zero.

### Rialto quote response

A successful quote contains `amountIn`, `quotedAmountOut`, `minimumAmountOut`, `spender`, `target`, `data`, `quotedAt`, `platformFeeBps` and `simulationIncomplete`. Require positive integer amounts, `minimumAmountOut <= quotedAmountOut`, valid addresses and calldata, and a quote no older than 30 seconds. The EARN server validates chain ID, tokens, amount, slippage and `taker === swapper` before returning the quote; `taker` is intentionally not repeated in the public response.

`simulationIncomplete: true` can be expected because the zap executor does not hold the user's funds during quoting. It is acceptable only if the complete zap call subsequently simulates successfully from the actual user's address. Never skip that final simulation.

## Automated vault catalog

Token decimals are shown in parentheses. Refresh live state onchain before execution.

| ID | Pair | Vault | Token 0 | Token 1 | Pool fee ppm |
| --- | --- | --- | --- | --- | --- |
| gme | GME / USDG | `0x126d9ea6d10076dcf726fe1787935e910ecb88eb` | `0x1b0E319c6A659F002271B69dB8A7df2F911c153E` (18) | `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` (6) | 500 |
| spy | SPY / USDG | `0xc2f056de3345c7942b8ad96d59771fc45e77ef37` | `0x117cc2133c37B721F49dE2A7a74833232B3B4C0C` (18) | `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` (6) | 500 |
| nvda | USDG / NVDA | `0xca854617f126e4b2aa4f39721205d48cb257fd67` | `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` (6) | `0xd0601CE157Db5bdC3162BbaC2a2C8aF5320D9EEC` (18) | 3000 |
| spcx | SPCX / USDG | `0x57b9b90610a4b9205c57fab92080e9cfbe7229f6` | `0x4a0E65A3EcceC6dBe60AE065F2e7bb85Fae35eEa` (18) | `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` (6) | 10000 |
| hims | USDG / HIMS | `0x7c8dff1b17a4939dfe2c011a2a545cf66ee7591d` | `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` (6) | `0xCceE82fE024c36fA15E1005edE3E9e4787e23D09` (18) | 9000 |
| rddt_spy | RDDT / SPY | `0x342d971673d9Bbb9E55762B6370bCa12B6ad6A48` | `0x05b37Fb53A299a1b874A619e1c4C404D52C36F4C` (18) | `0x117cc2133c37B721F49dE2A7a74833232B3B4C0C` (18) | 500 |
| spy_tsla | SPY / TSLA | `0xa5C5Dc143329BD505C3a9D3d0bf3d2b0B4Ee892b` | `0x117cc2133c37B721F49dE2A7a74833232B3B4C0C` (18) | `0x322F0929c4625eD5bAd873c95208D54E1c003b2d` (18) | 500 |
| spy_qqq | SPY / QQQ | `0xd51f5D3Bb4AC528F58B08E574dAA56E9bF58C230` | `0x117cc2133c37B721F49dE2A7a74833232B3B4C0C` (18) | `0xD5f3879160bc7c32ebb4dC785F8a4F505888de68` (18) | 500 |
| mstr | USDG / MSTR | `0x2FeA6692d3603A34480863cb883AD1ad3cB3CCBC` | `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` (6) | `0xec262a75e413fAfD0dF80480274532C79D42da09` (18) | 2500 |
| shop | USDG / SHOP | `0xf1c35050c3171326DFFe5Ba2F2e3c7cdb53E34DD` | `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` (6) | `0xF53F66751B1Eff985311b693531E3290F600c410` (18) | 10000 |
| tsm | TSM / USDG | `0x7abA9F142FF583a61885a32C5794A5bE765Bb4d5` | `0x58FfE4a942d3885bAa22D7520691F611EF09e7AA` (18) | `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` (6) | 7500 |
| pltr | USDG / PLTR | `0xb7E665E1466E60CAc6b13179873f30b4cBC4b918` | `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` (6) | `0x894E1EC2D74FFE5AEF8Dc8A9e84686acCB964F2A` (18) | 10000 |
| ai | AI / USDG | `0x580af35b75e45812e049b54e85ef3d500e16fc98` | `0x2E8c31162b855A2ffa90F6F8634643Ad6F111e18` (18) | `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` (6) | 10000 |
| cashcat | CASHCAT / USDG | `0x09c87bb6c7873f9520045bef5eb66973a7b1b796` | `0x020bfC650A365f8BB26819deAAbF3E21291018b4` (18) | `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` (6) | 2690 |
| pons | PONS / USDG | `0x40b37d1c1ed1a5e4ca290bbab63350d6e25ceacd` | `0x39dBED3a2bd333467115dE45665cC57F813C4571` (18) | `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` (6) | 3000 |

Minimal read interfaces:

- `paused() view returns(bool)`
- `getTotalAmounts() view returns(uint256 total0,uint256 total1)`
- `totalSupply() view returns(uint256)`
- `balanceOf(address) view returns(uint256)`
- `allowance(address owner,address spender) view returns(uint256)`
- `approve(address spender,uint256 amount) returns(bool)`
- Zap executor: `isVaultSupported(address vault) view returns(bool)`

### Automated vault: direct deposit

Contract interface:

`deposit(uint256 amount0Desired,uint256 amount1Desired,uint256 amount0Min,uint256 amount1Min,address to) payable returns(uint256 shares,uint256 amount0Used,uint256 amount1Used)`

1. Read `paused()`, `getTotalAmounts()`, `totalSupply()`, user balances and allowances.
2. Stop if paused or either requested balance is insufficient.
3. Calculate desired token amounts using current vault state. For user-selected `slippageBps`, set each protected minimum to `amountDesired * (10000 - slippageBps) / 10000`.
4. Approve only the required Token 0 and Token 1 amounts to the selected vault when allowances are insufficient.
5. Simulate the exact deposit from the user's address with `to` equal to the same user.
6. Ask the user to sign, wait for a successful receipt, and verify that vault shares increased.

### Automated vault: one-asset zap

Zap interface:

`zap((address vault,address fundingToken,uint256 amountIn,uint256 minSharesOut,uint256 deadline,address recipient,bool fundedWithEth) request,(address tokenOut,uint256 amountIn,uint256 minAmountOut,address spender,address target,bytes data)[] swaps) payable returns(uint256 sharesOut)`

1. GET `/api/steer/rialto` and require `configured: true` plus a non-zero router. Read that address's bytecode and stop if it is `0x`; then require `isVaultSupported(selectedVault) == true` on the same address.
2. Fetch `/api/steer/metrics?vault={vaultId}` immediately before quoting and require current positive `token0ValueUsd`, `token1ValueUsd`, `zapWeight0Ppm` and `zapWeight1Ppm`, with the two weights summing to 1,000,000. These public fields are calculated from `getTotalAmounts()`, the exact pool's current tick and the live quote-token USD reference. Calculate `token0Funding = amountIn * zapWeight0Ppm / 1000000`; assign the integer remainder to Token 1. Stop if either leg rounds to zero.
3. POST each non-direct output leg to `/api/steer/rialto` with JSON: `{ tokenIn, tokenOut, amount, swapper, slippageBps }`. Use the GET router as `swapper`; a leg whose output token already equals the funding token is direct and needs no quote.
4. Validate every quote using the Rialto response rules above. Build swaps only from non-direct legs.
5. Derive protected shares with current raw vault accounting: `shares0 = minAmount0 * totalSupply / total0`; `shares1 = minAmount1 * totalSupply / total1`; `minSharesOut = min(shares0, shares1) * 9990 / 10000`. Set `recipient` to the user and deadline to no more than 10 minutes.
6. Refresh all legs if any quote is older than 30 seconds and simulate the complete zap from the user's address.
7. For ERC-20 funding, approve the exact `amountIn` to the zap router. For native ETH funding, use the WETH address above as both the quote `tokenIn` and request `fundingToken`, set `fundedWithEth: true`, and send exactly `amountIn` as native call value. Never use the zero address as the ETH quote token.
8. Ask the user to sign and verify the vault-share balance after confirmation.

Do not send a user directly to a quote target. The EARN zap executor is what atomically enforces the intended vault, recipient, minimum shares and approved execution registry.

### Automated vault: withdraw

Contract interface:

`withdraw(uint256 shares,uint256 amount0Min,uint256 amount1Min,address to) returns(uint256 amount0,uint256 amount1)`

1. Read the user's vault-share balance and select an exact share amount.
2. Preview the proportional Token 0 and Token 1 outputs from current vault state.
3. Apply user-visible minimum outputs, simulate from the user address, and keep `to` equal to the user.
4. Ask the user to sign, wait for success, and verify the share balance fell and underlying balances increased.

## Omnipools

Discover compatible pools from `GET /api/omni/pools`. A pool address is also its ERC-20 pool-share token. Preserve the exact token order returned by the registry and confirm it onchain with the Omnipool Vault before execution.

### Core protocol contracts

| Contract | Address |
| --- | --- |
| Omnipool Vault | `0x28082618Ba2073E602230188E4F4C46e9b2169EB` |
| Omnipool Router | `0xFCcDd6Df64de63b609042c55C629F223321340e1` |
| Permit2 | `0x000000000022D473030F116dDEE9F6B43aC78BA3` |

Zap executor addresses are deliberately resolved at execution time from the applicable Rialto status endpoint and are not hard-coded here.

Minimal interfaces:

- Omnipool Vault: `getPoolTokenInfo(address pool) view returns(address[] tokens,(uint8 tokenType,address rateProvider,bool paysYieldFees)[] tokenInfo,uint256[] balancesRaw,uint256[] lastBalancesLiveScaled18)`
- Omnipool Router: `queryAddLiquidityProportional(address pool,uint256 exactBptAmountOut,address sender,bytes userData) returns(uint256[])`
- Omnipool Router: `addLiquidityProportional(address pool,uint256[] maxAmountsIn,uint256 exactBptAmountOut,bool wethIsEth,bytes userData) payable returns(uint256[])`
- Omnipool Router: `queryAddLiquidityUnbalanced(address pool,uint256[] exactAmountsIn,address sender,bytes userData) returns(uint256)`
- Omnipool Router: `queryRemoveLiquidityProportional(address pool,uint256 exactBptAmountIn,address sender,bytes userData) returns(uint256[])`
- Omnipool Router: `removeLiquidityProportional(address pool,uint256 exactBptAmountIn,uint256[] minAmountsOut,bool wethIsEth,bytes userData) payable returns(uint256[])`
- Permit2: `allowance(address owner,address token,address spender) view returns(uint160 amount,uint48 expiration,uint48 nonce)`
- Permit2: `approve(address token,address spender,uint160 amount,uint48 expiration)`
- Zap executor: `vault() view returns(address)`
- Zap executor: `balancerRouter() view returns(address)`
- Zap executor: `permit2() view returns(address)`

### Omnipool: direct deposit

1. Read pool tokens and balances with `getPoolTokenInfo(pool)` from the Omnipool Vault. Use `balancesRaw` for all proportional amount and share calculations; do not substitute `lastBalancesLiveScaled18`.
2. Preserve that token order. Never alphabetize an amount array.
3. Read the pool ERC-20 `totalSupply()`. For each non-zero pool balance calculate `candidateBptOut = maxAmountIn[i] * totalSupply / poolBalance[i]`. Take the smallest candidate and set `exactBptAmountOut = candidateBptOut * (10000 - slippageBps) / 10000`.
4. Call `queryAddLiquidityProportional(pool, exactBptAmountOut, user, 0x)` and require every returned amount to be no greater than the corresponding `maxAmountsIn`.
5. For every non-zero input, ensure ERC-20 allowance to Permit2 and Permit2 allowance for that token to the Omnipool Router. Never change token order between approvals, query and execution.
6. Re-run the query, then simulate `addLiquidityProportional(pool, maxAmountsIn, exactBptAmountOut, false, 0x)` from the user.
7. Ask the user to sign and verify their pool-share balance increased.

### Omnipool: one-asset zap

Supported funding assets in the public interface are ETH, WETH and USDG. Every output token must be curated and enabled by the deployed zap executor.

Zap interface:

`zap((address pool,address fundingToken,uint256 amountIn,uint256 minBptAmountOut,uint256 deadline,address recipient,bool fundedWithEth) request,(address tokenOut,uint256 amountIn,uint256 minAmountOut,address spender,address target,bytes data)[] swaps) payable returns(uint256 bptOut)`

1. GET `/api/omni/rialto` and require `configured: true` plus a non-zero router. Read that address's bytecode and stop if it is `0x`. On the same address require `vault() ==` the pinned Omnipool Vault, `balancerRouter() ==` the pinned Omnipool Router, and `permit2() ==` the pinned Permit2 address listed above.
2. Split `amountIn` across pool tokens using their normalized pool weights, assigning any integer remainder to the final token. A leg whose output token equals the funding token is direct and needs no quote.
3. POST each non-direct leg to `/api/omni/rialto` with `{ tokenIn, tokenOut, amount, swapper, slippageBps, mode: "deposit" }` and validate it using the Rialto response rules above.
4. Build expected and minimum output arrays in exact pool-token order. Call `queryAddLiquidityUnbalanced(pool, expectedAmounts, zapRouter, 0x)` for the preview and `queryAddLiquidityUnbalanced(pool, minimumAmounts, zapRouter, 0x)` for protection. Set `minBptAmountOut` to the protected result multiplied by `9990 / 10000` for integer-rounding margin.
5. Construct the atomic zap request with the selected pool, user recipient, protected minimum shares and a deadline no more than 10 minutes away.
6. Refresh quotes older than 30 seconds and simulate the complete call from the user's address.
7. Approve the exact ERC-20 funding amount to the zap router. For native ETH funding, use the WETH address above as both the quote `tokenIn` and request `fundingToken`, set `fundedWithEth: true`, and send the exact `amountIn` as native call value. Never use the zero address as the ETH quote token.
8. Ask the user to sign and verify pool shares after confirmation.

### Omnipool: partial or full withdrawal

1. Read the user's pool-share balance and convert the chosen percentage into an exact BPT amount.
2. Query `queryRemoveLiquidityProportional(pool, exactBptAmountIn, user, 0x)`.
3. Apply protected minimum outputs to every returned asset.
4. If necessary, approve the exact pool-share amount to the Omnipool Router.
5. Simulate and call `removeLiquidityProportional(pool, exactBptAmountIn, minAmountsOut, false, 0x)`.
6. Ask the user to sign, then verify their pool shares decreased and the underlying assets arrived.

## Claim EARN rewards

Merkl Distributor: `0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae`

EARN token: `0xA3b6AEe90017b72c0812dC1e013De70eB2917ba3`

Interface:

`claim(address[] users,address[] tokens,uint256[] amounts,bytes32[][] proofs)`

1. Fetch fresh claim data for the connected wallet from the applicable EARN claim endpoint immediately before encoding the transaction.
2. Retain only entries whose token address equals the EARN token above. The Omnipool endpoint can return unrelated Robinhood Chain Merkl rewards; never include those in an EARN claim unless the user separately requests and confirms them.
3. Require every generated `users` entry to equal the connected wallet. Validate token addresses, cumulative integer `amount` values and 32-byte proofs. Display claimable value as `amount - claimed`, but pass the cumulative `amount` from the proof to the Distributor—not the difference.
4. For an automated-vault claim, use a supported vault ID in the endpoint. The endpoint intentionally returns claimable EARN aggregated across current and legacy campaigns.
5. Simulate the exact Distributor call from the wallet.
6. Ask the user to sign. After success, refresh claim data and show the new claimable balance.

## Recommended agent response before signing

Present a compact confirmation in this order:

- Action and product
- Input asset and amount
- Expected receipt token or output assets
- Minimum protected output
- Approval address and approval amount, if required
- Destination contract
- Quote age and deadline
- Material risks

Never describe a transaction as successful until its receipt has status `success`.

## Integration status

This SKILL.md is a portable execution reference and the live JSON endpoints provide discovery and quote data. It is not an MCP server or a full OpenAPI transaction-building API.

For a deeper agent integration, expose the same operations as narrowly scoped, typed tools that return unsigned transaction payloads and simulation results. Keep signing in the user's wallet and keep each state-changing tool visibly confirmable. Until then, agents should use this guide to assist a user through the existing EARN interfaces rather than claim autonomous execution support.

For file-based skill systems, save this document exactly as `earn-onchain-yield/SKILL.md`. A differently named attachment can be read as reference material but may not be automatically installed as a skill.
