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
12 changes: 6 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ async function main() {
"estimate_staking_yield",
{
description:
"Project annual yield on a hypothetical staking amount for a given protocol (Lido or EigenLayer) using current APRs.",
"Project annual yield on a hypothetical staking amount for Lido or EigenLayer using current APRs. Use this for 'what would I earn if I staked X ETH?' questions before the user commits capital. Returns the protocol, input amount, APR used, and projected annual rewards denominated in the same asset. Purely forward-looking — does NOT read any wallet or on-chain position; pair with `get_staking_positions` for actual holdings.",
inputSchema: estimateStakingYieldInput.shape,
},
handler(estimateStakingYield)
Expand All @@ -364,7 +364,7 @@ async function main() {
"get_portfolio_summary",
{
description:
"Aggregate a complete portfolio view: native balances, top ERC-20 holdings, Aave V3 positions, Uniswap V3 LP positions, and staking — with USD totals and per-chain breakdown.",
"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.",
inputSchema: getPortfolioSummaryInput.shape,
},
handler(getPortfolioSummary)
Expand Down Expand Up @@ -595,7 +595,7 @@ async function main() {
"get_compound_positions",
{
description:
"Fetch Compound V3 (Comet) positions for a wallet across supported markets (cUSDCv3, cUSDTv3, cWETHv3, etc.). Returns base-token supplied/borrowed, per-asset collateral, and USD totals.",
"Fetch Compound V3 (Comet) positions for a wallet across all known markets on the selected chains (cUSDCv3, cUSDTv3, cWETHv3, etc.). For each market the wallet touches, returns the base-token supply or borrow balance, per-asset collateral deposits, and USD valuations. Use this to answer 'my Compound positions' or before preparing a `prepare_compound_*` action so you have the right market address. Returns an empty list if the wallet has no Compound V3 exposure on the requested chains.",
inputSchema: getCompoundPositionsInput.shape,
},
handler(getCompoundPositions)
Expand Down Expand Up @@ -625,7 +625,7 @@ async function main() {
"prepare_compound_borrow",
{
description:
"Build an unsigned Compound V3 borrow transaction — encoded as withdraw(baseToken) beyond the user's supplied balance. Base token is resolved on-chain from the Comet.",
"Build an unsigned Compound V3 borrow transaction. Compound V3 encodes a borrow as `withdraw(baseToken)` drawn beyond the wallet's supplied balance — the base token is resolved on-chain from the Comet market so you only pass the market address and amount. Requires the wallet to have already supplied enough collateral in that market; `get_compound_positions` shows the current collateral mix. Returns a handle + human-readable preview for the user to sign on Ledger; no approval step is needed (borrowing doesn't pull tokens from the wallet).",
inputSchema: prepareCompoundBorrowInput.shape,
},
txHandler(buildCompoundBorrow)
Expand Down Expand Up @@ -656,7 +656,7 @@ async function main() {
"prepare_morpho_supply",
{
description:
"Build an unsigned Morpho Blue supply transaction (deposits loan token for yield). Market params are resolved on-chain from the market id. Includes an approve step if needed.",
"Build an unsigned Morpho Blue supply transaction deposits the market's loan token to earn lending yield. Market params (loan/collateral tokens, oracle, IRM, LLTV) are resolved on-chain from the market id, so only wallet/marketId/amount are required. If the wallet's current allowance is insufficient, an ERC-20 approve tx is emitted first (chainable via `.next`); control the cap with `approvalCap` (defaults to unlimited for UX, pass 'exact' or a decimal ceiling to scope it). Returns a handle + preview for Ledger signing.",
inputSchema: prepareMorphoSupplyInput.shape,
},
txHandler(buildMorphoSupply)
Expand Down Expand Up @@ -706,7 +706,7 @@ async function main() {
"prepare_morpho_withdraw_collateral",
{
description:
"Build an unsigned Morpho Blue withdrawCollateral transaction — removes collateral from a market. Explicit amount only.",
"Build an unsigned Morpho Blue withdrawCollateral transaction — removes collateral from a market to send back to the wallet. Only withdraws the exact amount specified; `\"max\"` is NOT supported because Morpho's isolated-market accounting doesn't expose a clean max-safe value without simulating against the market's oracle/LLTV (query `get_morpho_positions` first to know your deposited collateral). Will revert on-chain if the withdrawal would push the position below the liquidation threshold. No approval step needed. Returns a handle + preview for Ledger signing.",
inputSchema: prepareMorphoWithdrawCollateralInput.shape,
},
txHandler(buildMorphoWithdrawCollateral)
Expand Down
28 changes: 22 additions & 6 deletions src/modules/compound/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,33 @@ import { SUPPORTED_CHAINS } from "../../types/index.js";
import { approvalCapSchema } from "../shared/approval.js";

const chainEnum = z.enum(SUPPORTED_CHAINS as unknown as [string, ...string[]]);
const walletSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/);
const walletSchema = z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/)
.describe("0x-prefixed EVM wallet address (40 hex chars) that will execute this action.");
const addressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/);

export const getCompoundPositionsInput = z.object({
wallet: walletSchema,
chains: z.array(chainEnum).optional(),
chains: z
.array(chainEnum)
.optional()
.describe(
"Subset of chains to scan for Compound V3 markets. Omit to scan all supported chains."
),
});

const baseCometAction = z.object({
wallet: walletSchema,
chain: chainEnum.default("ethereum"),
chain: chainEnum
.default("ethereum")
.describe("EVM chain the Comet market lives on. Defaults to ethereum."),
market: addressSchema.describe(
"Comet market address (e.g. cUSDCv3). Discover via get_compound_positions or the Compound registry."
),
asset: addressSchema,
asset: addressSchema.describe(
"ERC-20 token address being supplied or withdrawn — either the market's base token or a listed collateral token."
),
amount: z
.string()
.describe(
Expand All @@ -34,8 +46,12 @@ export const prepareCompoundWithdrawInput = baseCometAction;
/** Convenience wrappers — borrow = withdraw(baseToken); repay = supply(baseToken). */
export const prepareCompoundBorrowInput = z.object({
wallet: walletSchema,
chain: chainEnum.default("ethereum"),
market: addressSchema,
chain: chainEnum
.default("ethereum")
.describe("EVM chain the Comet market lives on. Defaults to ethereum."),
market: addressSchema.describe(
"Comet market address (e.g. cUSDCv3). The base token is resolved on-chain."
),
amount: z
.string()
.describe(
Expand Down
35 changes: 22 additions & 13 deletions src/modules/morpho/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,35 @@ import { SUPPORTED_CHAINS } from "../../types/index.js";
import { approvalCapSchema } from "../shared/approval.js";

const chainEnum = z.enum(SUPPORTED_CHAINS as unknown as [string, ...string[]]);
const walletSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/);
const marketIdSchema = z.string().regex(/^0x[a-fA-F0-9]{64}$/);
const walletSchema = z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/)
.describe("0x-prefixed EVM wallet address (40 hex chars) that will execute this action.");
const marketIdSchema = z
.string()
.regex(/^0x[a-fA-F0-9]{64}$/)
.describe(
"Morpho Blue market id — 32-byte hex (0x + 64 hex chars). Identifies the market's (loanToken, collateralToken, oracle, irm, lltv) tuple. Discover via get_morpho_positions."
);

export const getMorphoPositionsInput = z.object({
wallet: walletSchema,
chain: chainEnum.default("ethereum"),
/**
* Morpho Blue market IDs (bytes32 each) to check. If omitted, the server
* discovers the wallet's markets by scanning Morpho Blue event logs
* (Supply / Borrow / SupplyCollateral with `onBehalf == wallet`). Pass this
* explicitly as a fast path when the set of markets is already known —
* discovery on cold lookups walks from Morpho's deploy block to head in
* ~10k-block chunks and can take several seconds.
*/
marketIds: z.array(marketIdSchema).optional(),
chain: chainEnum
.default("ethereum")
.describe("EVM chain Morpho Blue is deployed on. Currently only ethereum is enabled."),
marketIds: z
.array(marketIdSchema)
.optional()
.describe(
"Morpho Blue market ids (bytes32 each) to check. If omitted, the server auto-discovers the wallet's markets by scanning Morpho Blue event logs (Supply / Borrow / SupplyCollateral with onBehalf == wallet). Pass explicitly as a fast path — cold discovery walks from Morpho's deploy block to head in ~10k-block chunks and can take several seconds."
),
});

const baseMarketAction = z.object({
wallet: walletSchema,
chain: chainEnum.default("ethereum"),
chain: chainEnum
.default("ethereum")
.describe("EVM chain Morpho Blue is deployed on. Currently only ethereum is enabled."),
marketId: marketIdSchema,
amount: z
.string()
Expand Down
27 changes: 22 additions & 5 deletions src/modules/portfolio/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,34 @@ import { z } from "zod";
import { SUPPORTED_CHAINS } from "../../types/index.js";

const chainEnum = z.enum(SUPPORTED_CHAINS as unknown as [string, ...string[]]);
const walletSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/);
const walletSchema = z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/)
.describe("0x-prefixed EVM wallet address (40 hex chars).");

/**
* Raw shape — MCP requires a bare ZodObject (no .refine) so it can expose `.shape`
* to build the JSON schema. Cross-field validation is enforced in the handler.
*/
export const getPortfolioSummaryInput = z.object({
/** Single wallet — kept for backward compatibility. Use `wallets` for multi-wallet reports. */
wallet: walletSchema.optional(),
wallets: z.array(walletSchema).min(1).optional(),
chains: z.array(chainEnum).optional(),
wallet: walletSchema
.optional()
.describe(
"Single wallet address. Provide this OR `wallets` (not both). Use `wallets` for multi-wallet aggregated reports."
),
wallets: z
.array(walletSchema)
.min(1)
.optional()
.describe(
"Multiple wallet addresses to aggregate into one combined portfolio view. Mutually exclusive with `wallet`."
),
chains: z
.array(chainEnum)
.optional()
.describe(
"Subset of supported chains to scan (ethereum, arbitrum, polygon, base). Omit to scan all supported chains."
),
});

export type GetPortfolioSummaryArgs = z.infer<typeof getPortfolioSummaryInput>;
24 changes: 20 additions & 4 deletions src/modules/staking/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,36 @@ import { z } from "zod";
import { SUPPORTED_CHAINS } from "../../types/index.js";

const chainEnum = z.enum(SUPPORTED_CHAINS as unknown as [string, ...string[]]);
const walletSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/, "must be a 0x-prefixed EVM address");
const walletSchema = z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/, "must be a 0x-prefixed EVM address")
.describe("0x-prefixed EVM wallet address (40 hex chars) to inspect.");

export const getStakingPositionsInput = z.object({
wallet: walletSchema,
chains: z.array(chainEnum).optional(),
chains: z
.array(chainEnum)
.optional()
.describe(
"Subset of chains to scan. Omit to scan all chains where staking is supported (Lido: ethereum + arbitrum; EigenLayer: ethereum only)."
),
});

export const getStakingRewardsInput = z.object({
wallet: walletSchema,
period: z.enum(["7d", "30d", "90d", "1y"]).optional().default("30d"),
period: z
.enum(["7d", "30d", "90d", "1y"])
.optional()
.default("30d")
.describe("Lookback window for aggregating accrued rewards. Defaults to 30d."),
});

export const estimateStakingYieldInput = z.object({
protocol: z.enum(["lido", "eigenlayer"]),
protocol: z
.enum(["lido", "eigenlayer"])
.describe(
'Which staking protocol to project yield for. "lido" = native ETH liquid staking (stETH APR); "eigenlayer" = restaking (LST deposit APR, protocol-dependent).'
),
amount: z
.number()
.positive()
Expand Down
Loading