Hydrex Plugin
[!IMPORTANT] Run Base MCP onboarding first (see SKILL.md). Obtain the user’s wallet address viaget_wallets— it is required asfromorrecipientin every prepare call. The Hydrex prepare server must be running locally before any write operations.
Overview
Hydrex is an Omni-Liquidity MetaDEX on Base — concentrated-liquidity swaps aggregated across 0x, OpenOcean, OKX, and KyberSwap, plus liquidity positions that earn fees and rewards automatically. Adding liquidity creates an active earning position immediately; there is no separate staking step. The plugin calls a local prepare server (https://hydrex-agent.com) to fetch unsigned calldata, then submits via send_calls on Base mainnet (chainId: 8453).
Surface Routing
| Capability | Harness surface (Cursor, Claude Code, Codex) | Chat-only surface (Claude.ai, ChatGPT) |
|---|---|---|
| Read state (quote, positions, portfolio, pools) | Harness HTTP tool → GET https://hydrex-agent.com/state/* | User-paste fallback: construct full URL, ask user to open in browser and paste JSON response |
| Prepare calldata (swap, add/remove liquidity) | Harness HTTP tool → GET https://hydrex-agent.com/prepare/* | User-paste fallback: same as above |
| Submit transaction | send_calls (Base MCP) | send_calls (Base MCP) |
https://hydrex-agent.com) is not on the Base MCP web_request allowlist. On chat-only surfaces, construct the full GET URL with all query parameters and ask the user to open it in a browser, paste the JSON response into chat, then continue with send_calls.
Endpoints
Server URL:https://hydrex-agent.com
GET /health
Response:{ "ok": true, "service": "hydrex-base-skill-server", "chainId": 8453 }
GET /state/quote
| Parameter | Type | Required | Description |
|---|---|---|---|
tokenIn | address | ✓ | Input token contract address |
tokenOut | address | ✓ | Output token contract address |
amount | string (wei) | ✓ | Input amount in raw units |
recipient | address | ✓ | Wallet that receives output tokens |
slippage | number | — | Slippage tolerance in bps (default: 50 = 0.5%) |
source | string | — | Force aggregator: ZEROX, OPENOCEAN, OKX, KYBERSWAP |
amountOut (human-readable) and priceImpact to the user before executing. Warn and require confirmation if priceImpact > 5%.
GET /state/portfolio
GET /state/positions
positionId with /prepare/remove-liquidity.
GET /state/trade-history
GET /state/pools
Discovers Hydrex pools — including each pool’s contract address, token addresses, and decimals — from the Hydrex stats API. This is how the agent resolves apool address (and token0/token1/decimals0/decimals1) for /prepare/add-liquidity and /prepare/remove-liquidity when the user only gives a token pair.
| Parameter | Type | Required | Description |
|---|---|---|---|
tokenA | string | — | Token symbol (e.g. USDC) or address; matches either side of the pair (order-independent) |
tokenB | string | — | Second token symbol or address, for unordered pair matching |
token0 | string | — | Order-specific token0 symbol or address |
token1 | string | — | Order-specific token1 symbol or address |
liquidityType | string | — | Filter by pool type: integral, classic-volatile, classic-stable |
days | number | — | Stats lookback window in days, 1–30 (default: 1) |
limit | number | — | Max upstream pairs to scan, 1–1000 (default: 1000) |
tokenA + tokenB to find pools for a specific pair regardless of token order. With no token filters it returns every pool. Results are sorted by TVL (USD) descending, so pools[0] is the deepest pool for the pair.
Response:
pool address plus token0/token1 addresses and decimals — exactly the inputs /prepare/add-liquidity needs (use them directly, preserving the returned token0/token1 order). Handling:
count = 0— no pool exists for that pair; tell the user and do not call a prepare endpoint.count > 1— multiple pools/fee tiers; show the options (tvlUsd,fee,liquidityType) and let the user choose before proceeding.count = 1— use it; confirm with the user before any write.
GET /prepare/swap
| Parameter | Type | Required | Description |
|---|---|---|---|
tokenIn | address | ✓ | Input token address |
tokenOut | address | ✓ | Output token address |
amount | string | ✓ | Human-readable input amount (e.g. "1.5") |
decimals | number | — | Decimals of tokenIn (default: 18) |
recipient | address | ✓ | Wallet that receives output tokens |
slippage | number | — | Slippage in bps (default: 50) |
source | string | — | Optional aggregator override |
transactions[] array length is not fixed. For an ERC-20 tokenIn, an approve-tokenIn step is prepended only when the wallet’s current allowance is too low — if allowance is already sufficient, just the swap step is returned. Native ETH swaps never include an approve step. Always pass every returned transaction to send_calls in order, rather than assuming a single call.
GET /prepare/add-liquidity
| Parameter | Type | Required | Description |
|---|---|---|---|
from | address | ✓ | Wallet providing liquidity |
pool | address | ✓ | Pool contract address — resolve from the token pair via GET /state/pools, or ask the user (also listed on https://hydrex.fi) |
token0 | address | ✓ | token0 address (must match pool order) |
token1 | address | ✓ | token1 address (must match pool order) |
decimals0 | number | — | token0 decimals (default: 18) |
decimals1 | number | — | token1 decimals (default: 18) |
amount0 | string | ✓ | Desired token0 amount, human-readable |
amount1 | string | ✓ | Desired token1 amount, human-readable |
rangePreset | string | — | Named range around current price: tight (±1%), narrow (±5%), common (±12.5%), wide (±20%). Used when no other range option is given (default: common) |
priceLower | number | — | Custom lower price bound (token1/token0). Must be sent together with priceUpper |
priceUpper | number | — | Custom upper price bound (token1/token0). Must be sent together with priceLower |
fullRange | boolean | — | If true, spreads liquidity across the entire price range (always-active, V2-style). Default false |
slippage | number | — | Slippage in bps (default: 50) |
rangePreset, a custom priceLower+priceUpper, or fullRange. Sending more than one returns 400 (“Choose only one range option…”); sending only one of priceLower/priceUpper returns 400 (“A custom range requires both…”). If you send none, the server uses the common preset (±12.5%).
Response — three transactions, always in this order:
Price range selection
Choose one of three mutually exclusive ways to set the range (the server does the percentage→price math on-chain, so the agent only passes the choice):| Option | How to send it | Resulting range |
|---|---|---|
| Named preset | rangePreset=tight | ±1% of current price |
| Named preset | rangePreset=narrow | ±5% |
| Named preset | rangePreset=common (default) | ±12.5% |
| Named preset | rangePreset=wide | ±20% |
| Custom | priceLower=<p>&priceUpper=<p> | exact bounds you supply |
| Full range | fullRange=true | entire price range (always-active) |
common preset (±12.5%). Always tell the user which range was used, e.g. “I’m using the common ±12.5% range around the current price — say the word for a tighter (narrow/tight), wider (wide), full, or custom range.”
GET /prepare/remove-liquidity
| Parameter | Type | Required | Description |
|---|---|---|---|
from | address | ✓ | Wallet that owns the position |
positionId | number | ✓ | NFT tokenId from /state/positions |
pool | address | ✓ | Pool contract address — the matching position from /state/positions identifies the pair (token0/token1/fee); resolve the address via GET /state/pools (or ask the user) |
decimals0 | number | — | token0 decimals (default: 18) |
decimals1 | number | — | token1 decimals (default: 18) |
liquidityPercent | number | — | Percentage to remove, 1–100 (default: 100) |
slippage | number | — | Slippage in bps (default: 50) |
{ "ok": false, "error": "..." } on failure — surface the error field to the user and do not call send_calls.
Orchestration
Swap
Add liquidity (enter a position)
Remove liquidity (exit a position)
Submission
Target tool:send_calls
Map every transactions[] array from a prepare endpoint into send_calls:
calls array — Base MCP executes them atomically in one user approval. After send_calls returns, immediately call get_request_status(requestId) and poll until the status is success or failed. Do not ask the user to type or paste anything during polling. See approval-mode.md.
Example Prompts
“Swap 5 USDC for ETH on Hydrex”get_wallets→ wallet addressGET /state/quote?tokenIn=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&tokenOut=0x4200000000000000000000000000000000000006&amount=5000000&recipient=<address>→ show amountOut, priceImpactGET /prepare/swap?tokenIn=0x833589...&tokenOut=0x420000...&amount=5&decimals=6&recipient=<address>&slippage=50send_calls(chain="base", calls=[swap tx])- Poll
get_request_status→ report outcome
get_wallets→ wallet addressGET /state/positions?address=<address>→ display each positionId, token pair, tick range, and liquidity
get_wallets→ wallet addressGET /state/positions?address=<address>→ existing position context (count may be 0 — fine for a first position)GET /state/pools?tokenA=USDC&tokenB=WETH→ pick the pool (if count > 1, let the user choose); read its pool/token0/token1/decimals. Range defaults to thecommonpreset (±12.5%) unless the user picks another; inform the user of the range usedGET /prepare/add-liquidity?from=<address>&pool=<pool>&token0=<USDC>&token1=<WETH>&decimals0=6&decimals1=18&amount0=100&amount1=0.04- Show returned
position(tickLower, tickUpper, amounts) to user send_calls(chain="base", calls=[approve-token0, approve-token1, mint])- Poll
get_request_status→ report outcome
get_wallets→ wallet addressweb_requestcannot reachhttps://hydrex-agent.com→ construct the full URL and ask the user to open it in a browser and paste the JSON response into chat- On receiving JSON,
send_calls(chain="base", calls from transactions[]) - Poll
get_request_status→ report outcome
Risks & Warnings
- Slippage — swap and liquidity operations fill at market price; actual output can differ from the quote. Default tolerance is 50 bps (0.5%). Always check
priceImpactbefore executing; ifpriceImpact > 5%, warn the user and wait for explicit confirmation. Never auto-raise slippage. - Irreversible — onchain transactions cannot be undone once approved. Always show the user the full operation details (amounts, price range for LP positions, positionId for removals) and confirm before calling
send_calls.
Notes
Well-known token addresses (Base mainnet)
| Symbol | Address | Decimals |
|---|---|---|
| USDC | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | 6 |
| WETH | 0x4200000000000000000000000000000000000006 | 18 |
| ETH (native) | Use the value field; no tokenIn address needed | 18 |
GET /state/pools (returns token0/token1 with address, symbol, and decimals), or ask the user to supply the contract address.
Error handling
| Condition | Action |
|---|---|
get_wallets returns no wallet | Tell user to connect their Base Account and retry |
/state/positions returns count: 0 | For add-liquidity, proceed — this is the user’s first position. For remove-liquidity, tell the user they have no open positions and stop |
| Pool address unknown | Resolve via GET /state/pools (by token pair); if it returns count: 0, no pool exists — tell the user and stop. Never guess an address |
/state/pools returns count > 1 | Multiple pools/fee tiers for the pair; show tvlUsd/fee/liquidityType and let the user choose |
/state/quote returns priceImpact > 5% | Warn user; require confirmation before proceeding |
Prepare endpoint returns ok: false | Surface the error field; do not call send_calls |
send_calls approval rejected | Inform user the transaction was cancelled; offer to retry |
get_request_status shows failure | Parse the failure reason and suggest next steps |
Liquidity notes
- Adding liquidity creates a concentrated liquidity position that earns fees and rewards automatically — no separate staking step is required.
- Removing liquidity fully exits the position and returns both tokens to the wallet.
- The
positionIdis the NFT tokenId from the NonfungiblePositionManager; always fetch current positions via/state/positionsbefore a remove.