Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@

**Self-custodial crypto portfolio and DeFi, managed by AI agents — signed on your Ledger hardware wallet.**

Recon Crypto MCP is a Model Context Protocol server that lets AI agents — **Claude Code, Claude Desktop, Cursor**, and any MCP-compatible client — read your on-chain positions across **Ethereum, Arbitrum, and Polygon** and prepare EVM transactions that you sign on your **Ledger device via WalletConnect**. Your private keys never leave the hardware wallet, and every transaction is previewed in human-readable form before you approve it on the device.
Recon Crypto MCP is a Model Context Protocol server that lets AI agents — **Claude Code, Claude Desktop, Cursor**, and any MCP-compatible client — read your on-chain positions across **Ethereum, Arbitrum, Polygon, Base**, and **TRON** and prepare EVM transactions that you sign on your **Ledger device via WalletConnect**. Your private keys never leave the hardware wallet, and every transaction is previewed in human-readable form before you approve it on the device.

Supported protocols: **Aave V3, Compound V3 (Comet), Morpho Blue, Uniswap V3 LP, Lido (stETH/wstETH), EigenLayer**, plus **LiFi** for swap/bridge aggregation and **1inch** for optional intra-chain quote comparison.

Use it when you want to:

- Ask an agent *"what are my DeFi positions across Ethereum, Arbitrum, and Polygon?"* and get a unified portfolio view (wallet balances + Aave/Compound/Morpho lending + Uniswap V3 LP + Lido/EigenLayer staking) with USD totals.
- Ask an agent *"what are my DeFi positions across Ethereum, Arbitrum, Polygon, and Base?"* and get a unified portfolio view (wallet balances + Aave/Compound/Morpho lending + Uniswap V3 LP + Lido/EigenLayer staking) with USD totals.
- Get liquidation-risk alerts (*"any position below health factor 1.5?"*) without manually checking dashboards.
- Swap or bridge tokens — the agent prepares the route via LiFi, you sign on Ledger.
- Supply, borrow, repay, withdraw on lending protocols; stake ETH on Lido; deposit into EigenLayer strategies; send ETH or ERC-20 tokens — all through Ledger-signed transactions.
Expand All @@ -34,27 +34,33 @@ This is an **agent-driven portfolio management** tool, not a wallet replacement.

## Supported chains

EVM: Ethereum, Arbitrum, Polygon.
EVM: Ethereum, Arbitrum, Polygon, Base.

Non-EVM: TRON (phase 1 — balance + staking reads; transaction preparation and Ledger signing land in follow-up phases).

Not every protocol is on every chain. Lido and EigenLayer are L1-only (Ethereum). Morpho Blue is currently enabled on Ethereum only — it is deployed on Base at the same address but the discovery scan needs a pinned deployment block, tracked as a follow-up. TRON has no lending/LP coverage in this server (none of Aave/Compound/Morpho/Uniswap are deployed there); balance reads return TRX + canonical TRC-20 stablecoins (USDT, USDC, USDD, TUSD) that together cover the vast majority of TRON token volume, and TRON-native staking (frozen TRX under Stake 2.0, pending unfreezes, claimable voting rewards) is surfaced via `get_tron_staking` and folded into the portfolio summary. Readers short-circuit cleanly on chains where a protocol isn't deployed.

## Roadmap

- **MetaMask support** (WalletConnect) — planned for the next release, alongside the existing Ledger Live integration. Will let users sign through a MetaMask-paired session when a hardware wallet isn't available.
- **Base** — coming soon. EVM L2, reuses the existing viem/Aave V3/LiFi tooling.
- **Solana** — coming soon. Non-EVM: introduces a separate SDK (`@solana/web3.js`), base58 addresses, and the WalletConnect `solana:` namespace for signing.
- **TRON transaction preparation + Ledger signing** — phase 2 and phase 3 of TRON support. Phase 2 prepares native TRX and TRC-20 sends. Phase 3 signs them via **direct USB integration with `@ledgerhq/hw-app-trx`** — Ledger Live's WalletConnect relay does *not* currently honor the `tron:` namespace (verified 2026-04-14 via a SunSwap pairing attempt), so TRON signing diverges from the Ledger-Live-at-a-distance flow used for EVM: the user's Ledger must be plugged into the host running the MCP, with the TRON app open on the device.
- **MetaMask support** (WalletConnect) — alongside the existing Ledger Live integration. Will let users sign through a MetaMask-paired session when a hardware wallet isn't available.
- **Solana** — coming later. Non-EVM: introduces a separate SDK (`@solana/web3.js`), base58 addresses, and the WalletConnect `solana:` namespace for signing.

## Tools exposed to the agent

Read-only (no Ledger pairing required):

- `get_portfolio_summary` — cross-chain portfolio aggregation with USD totals
- `get_portfolio_summary` — cross-chain portfolio aggregation with USD totals; pass an optional `tronAddress` (base58, prefix T) alongside an EVM `wallet` to fold TRX + TRC-20 balances + TRON staking (frozen + pending-unfreeze + claimable rewards) into the same total (returned under `breakdown.tron`, `tronUsd`, and `tronStakingUsd`)
- `get_lending_positions` — Aave V3 collateral/debt/health-factor per wallet
- `get_compound_positions` — Compound V3 (Comet) base + collateral positions
- `get_morpho_positions` — Morpho Blue positions across specified markets
- `get_morpho_positions` — Morpho Blue positions; auto-discovers the wallet's markets via event-log scan when `marketIds` is omitted (pass explicit ids for a fast path)
- `get_lp_positions` — Uniswap V3 LP positions, fee tier, in-range, IL estimate
- `get_staking_positions`, `get_staking_rewards`, `estimate_staking_yield` — Lido + EigenLayer
- `get_health_alerts` — Aave positions near liquidation
- `simulate_position_change` — projected Aave health factor for a hypothetical action
- `get_token_balance`, `get_token_price` — balances and DefiLlama prices
- `simulate_transaction` — run `eth_call` against a prepared or arbitrary tx to preview success/revert before signing; prepared txs are re-simulated automatically at send time
- `get_token_balance`, `get_token_price` — balances and DefiLlama prices; `get_token_balance` accepts `chain: "tron"` with a base58 wallet and a base58 TRC-20 address (or `token: "native"` for TRX), returning a `TronBalance` shape
- `get_tron_staking` — TRON-native staking state for a base58 address: claimable voting rewards (WithdrawBalance-ready), frozen TRX under Stake 2.0 (bandwidth + energy), and pending unfreezes with ISO unlock timestamps. Read-only; the actual claim/withdraw transactions land in TRON Phase 2.
- `resolve_ens_name`, `reverse_resolve_ens` — ENS forward/reverse
- `get_swap_quote` — LiFi quote (optionally cross-checked against 1inch)
- `check_contract_security`, `check_permission_risks`, `get_protocol_risk_score` — risk tooling
Expand All @@ -66,7 +72,7 @@ Meta:

Execution (Ledger-signed via WalletConnect):

- `pair_ledger_live`, `get_ledger_status` — session management and account discovery
- `pair_ledger_live`, `get_ledger_status` — session management and account discovery; `get_ledger_status` returns per-chain exposure (`accountDetails[]` with `address`, `chainIds`, `chains`) so duplicate-looking addresses across chains are disambiguated
- `prepare_aave_supply` / `_withdraw` / `_borrow` / `_repay`
- `prepare_compound_supply` / `_withdraw` / `_borrow` / `_repay`
- `prepare_morpho_supply` / `_withdraw` / `_borrow` / `_repay` / `_supply_collateral` / `_withdraw_collateral`
Expand All @@ -80,7 +86,7 @@ Execution (Ledger-signed via WalletConnect):

- Node.js >= 18.17
- An RPC provider (Infura, Alchemy, or custom) for the EVM chains
- Optional: Etherscan API key, 1inch Developer Portal API key (enables swap-quote comparison), WalletConnect Cloud project ID (required for Ledger signing)
- Optional: Etherscan API key, 1inch Developer Portal API key (enables swap-quote comparison), WalletConnect Cloud project ID (required for Ledger signing), TronGrid API key (enables TRX + TRC-20 balance reads)

## Install

Expand Down Expand Up @@ -132,10 +138,11 @@ The setup script prints a ready-to-paste snippet.

All are optional if the matching field is in `~/.recon-crypto-mcp/config.json`; env vars take precedence when both are set.

- `ETHEREUM_RPC_URL`, `ARBITRUM_RPC_URL`, `POLYGON_RPC_URL` — custom RPC endpoints
- `ETHEREUM_RPC_URL`, `ARBITRUM_RPC_URL`, `POLYGON_RPC_URL`, `BASE_RPC_URL` — custom RPC endpoints
- `RPC_PROVIDER` (`infura` | `alchemy`) + `RPC_API_KEY` — alternative to custom URLs
- `ETHERSCAN_API_KEY` — contract verification lookups
- `ONEINCH_API_KEY` — enables 1inch quote comparison in `get_swap_quote`
- `TRON_API_KEY` — TronGrid API key (sent as `TRON-PRO-API-KEY`). Required in practice to read TRON balances — anonymous TronGrid calls are capped at ~15 req/min, which the portfolio fan-out exceeds. Free to create at [trongrid.io](https://www.trongrid.io).
- `WALLETCONNECT_PROJECT_ID` — required for Ledger Live signing
- `RPC_BATCH=1` — opt into JSON-RPC batching (off by default; many public endpoints mishandle batched POSTs)
- `RECON_ALLOW_INSECURE_RPC=1` — opt out of the https/private-IP check on RPC URLs. Only set this when pointing at a local anvil/hardhat fork; never in production.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions src/config/tron.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* TRON mainnet configuration.
*
* TRON is not EVM: addresses are base58 (prefix `T`, 34 chars), the RPC is a
* REST API (TronGrid) rather than JSON-RPC, and transaction signing uses a
* different wire format. The server treats TRON as strictly additive via
* `AnyChain = SupportedChain | SupportedNonEvmChain` — existing EVM modules
* never see TRON, and the TRON reader lives in src/modules/tron/.
*/

/** TronGrid REST endpoint. Anonymous requests are rate-limited to ~15 req/min. */
export const TRONGRID_BASE_URL = "https://api.trongrid.io";

/**
* Canonical TRC-20 tokens we enumerate in the portfolio summary. Keys are the
* displayed symbol; values are the TRC-20 contract addresses in base58.
*
* TRON is dominated by USDT (Tether issues more on TRON than on any other
* chain by volume), so the wallet balance fan-out is small on purpose —
* USDT, USDC, and the few stablecoins that matter cover >95% of balances in
* practice. TronScan top-holders data confirms the long tail is negligible
* compared to the Ethereum equivalent.
*/
export const TRON_TOKENS = {
USDT: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
USDC: "TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8",
USDD: "TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR",
TUSD: "TUpMhErZL2fhh4sVNULAbNKLokS4GjC1F4",
} as const;

/** Native TRX symbol + decimals. TRX uses 6 decimals (1 TRX = 1_000_000 sun). */
export const TRX_DECIMALS = 6;
export const TRX_SYMBOL = "TRX";

/**
* Validate a TRON mainnet base58 address. Mainnet addresses are 34 chars and
* start with `T` (the mainnet prefix byte 0x41 encodes to `T...` in base58check).
*
* This is a cheap shape check, not a full base58check-with-payload-checksum
* validation — callers that round-trip an address through TronGrid get a
* stronger guarantee (TronGrid itself rejects malformed addresses).
*/
export function isTronAddress(s: string): boolean {
return typeof s === "string" && /^T[1-9A-HJ-NP-Za-km-z]{33}$/.test(s);
}
10 changes: 10 additions & 0 deletions src/config/user-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ export function resolveOneInchApiKey(userConfig: UserConfig | null): string | un
return process.env.ONEINCH_API_KEY || userConfig?.oneInchApiKey;
}

/**
* Pull the TronGrid API key from env or user config; undefined if none set.
* An undefined key means TRON reads are either disabled or fall back to
* anonymous TronGrid (rate-limited — the reader flags that in its errored
* coverage status rather than silently degrading).
*/
export function resolveTronApiKey(userConfig: UserConfig | null): string | undefined {
return process.env.TRON_API_KEY || userConfig?.tronApiKey;
}

/** Pull the WalletConnect project ID from env or user config; undefined if none set. */
export function resolveWalletConnectProjectId(userConfig: UserConfig | null): string | undefined {
return process.env.WALLETCONNECT_PROJECT_ID || userConfig?.walletConnect?.projectId;
Expand Down
29 changes: 25 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ import {
reverseResolveInput,
} from "./modules/balances/schemas.js";

import { getTronStaking } from "./modules/tron/staking.js";
import { getTronStakingInput } from "./modules/tron/schemas.js";

import { getCompoundPositions } from "./modules/compound/index.js";
import {
buildCompoundSupply,
Expand Down Expand Up @@ -202,6 +205,13 @@ async function main() {
"- their DeFi positions on Ethereum, Arbitrum, Polygon, or Base — Aave V3 lending/",
" borrowing, Compound V3 (Comet), Morpho Blue (Ethereum), Uniswap V3 LP, Lido staking",
" (Ethereum/Arbitrum), EigenLayer restaking (Ethereum)",
"- their TRON balances (TRX + TRC-20 — USDT, USDC, USDD, TUSD) when the user",
" supplies a base58 address (prefix T) via the `tronAddress` arg on",
" `get_portfolio_summary` or the `chain: \"tron\"` branch of `get_token_balance`.",
"- their TRON staking: claimable voting rewards, frozen TRX (Stake 2.0),",
" and pending unfreezes — via `get_tron_staking` or folded into",
" `get_portfolio_summary` when a `tronAddress` is passed. TRON has no",
" lending/LP coverage in this server (not deployed there).",
"- portfolio value, cross-chain aggregation, health-factor / liquidation risk",
"- executing on-chain actions: supply, borrow, repay, withdraw, stake, unstake,",
" send ETH/tokens, swap, bridge",
Expand Down Expand Up @@ -232,8 +242,9 @@ async function main() {
"get_lp_positions, get_compound_positions, get_morpho_positions, get_staking_positions,",
"get_staking_rewards, estimate_staking_yield, get_portfolio_summary, get_swap_quote,",
"get_token_balance, get_token_price, resolve_ens_name, reverse_resolve_ens,",
"get_health_alerts, simulate_position_change, check_contract_security,",
"check_permission_risks, get_protocol_risk_score, get_transaction_status.",
"get_tron_staking, get_health_alerts, simulate_position_change,",
"check_contract_security, check_permission_risks, get_protocol_risk_score,",
"get_transaction_status.",
"",
"SWAP/BRIDGE ROUTING: prefer `prepare_swap` (LiFi aggregator) over building DEX",
"router calls directly — LiFi handles route selection, approvals, and cross-chain",
Expand Down Expand Up @@ -364,7 +375,7 @@ async function main() {
"get_portfolio_summary",
{
description:
"One-shot cross-chain portfolio aggregation for one or more wallets. Fans out across Ethereum/Arbitrum/Polygon/Base (unless `chains` narrows it) and assembles: native ETH/MATIC balances, top ERC-20 holdings, Aave V3 and Compound V3 lending positions, Uniswap V3 LP positions, and Lido/EigenLayer staking — each valued in USD via DefiLlama. Returns a `totalUsd`, a `breakdown` by category and by chain, and the raw per-protocol position arrays. Default tool for 'what's in my portfolio?' / 'total value' questions; prefer it over calling each per-protocol reader separately.",
"One-shot cross-chain portfolio aggregation for one or more wallets. Fans out across Ethereum/Arbitrum/Polygon/Base (unless `chains` narrows it) and assembles: native ETH/MATIC balances, top ERC-20 holdings, Aave V3 and Compound V3 lending positions, Uniswap V3 LP positions, and Lido/EigenLayer staking — each valued in USD via DefiLlama. Pass `tronAddress` (base58, prefix T) alongside a single `wallet` to fold TRX + TRC-20 balances plus TRON staking into the same totals; `breakdown.tron` holds the TRON slice, `tronUsd` the subtotal, and `tronStakingUsd` the staking portion. Returns a `totalUsd`, a `breakdown` by category and by chain, and the raw per-protocol position arrays. Default tool for 'what's in my portfolio?' / 'total value' questions; prefer it over calling each per-protocol reader separately.",
inputSchema: getPortfolioSummaryInput.shape,
},
handler(getPortfolioSummary)
Expand Down Expand Up @@ -534,7 +545,7 @@ async function main() {
"get_token_balance",
{
description:
"Fetch a wallet's balance of any ERC-20 token or the chain's native coin. Pass `token: \"native\"` for ETH (or chain-native asset) or an ERC-20 contract address. Returns amount, decimals, symbol, and USD value.",
"Fetch a wallet's balance of any ERC-20 token or the chain's native coin. Pass `token: \"native\"` for ETH (or chain-native asset) or an ERC-20 contract address. Returns amount, decimals, symbol, and USD value. For TRON, pass `chain: \"tron\"` with a base58 wallet (prefix T) and either `token: \"native\"` for TRX or a base58 TRC-20 address; returns a TronBalance (same fields, base58 token id).",
inputSchema: getTokenBalanceInput.shape,
},
handler(getTokenBalance)
Expand Down Expand Up @@ -570,6 +581,16 @@ async function main() {
handler(reverseResolve)
);

server.registerTool(
"get_tron_staking",
{
description:
"Read TRON staking state for a base58 address: claimable voting rewards (WithdrawBalance-ready), frozen TRX under Stake 2.0 (bandwidth + energy), and pending unfreezes with their unlock timestamps. Returns raw SUN + formatted TRX + USD values, plus a `totalStakedUsd` rollup. Read-only; the WithdrawBalance transaction to actually claim rewards lands in TRON Phase 2.",
inputSchema: getTronStakingInput.shape,
},
handler((args: { address: string }) => getTronStaking(args.address))
);

server.registerTool(
"prepare_native_send",
{
Expand Down
Loading
Loading