Skip to main content
Returns a complete DeFi portfolio for any wallet address: native MON balance, ERC20 token holdings, LST positions (aprMON, gMON, shMON, sMON), and Euler V2 vault positions - all with USD values.

Endpoint

GET /api/portfolio

Query Parameters

ParameterTypeRequiredDescription
addressstringYesWallet address (0x...)
includestringNoComma-separated sections: tokens, lst, lending, or all (default all)

Response

interface TokenBalance {
  address: string
  symbol: string
  balance: number
  valueUSD: number
}

interface LSTPosition {
  token: string
  balance: number
  valueUSD: number
  underlyingMON: number
}

interface EulerPosition {
  vault: string
  asset: string
  supplied: number
  valueUSD: number
  apr: number
}

interface Portfolio {
  address: string
  nativeBalance: number
  nativeValueUSD: number
  tokens: TokenBalance[]
  lstPositions: LSTPosition[]
  eulerPositions: EulerPosition[]
  totalUSD: number
  timestamp: number
}

Example Response

{
  "data": {
    "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    "nativeBalance": 142.5,
    "nativeValueUSD": 50.445,
    "tokens": [
      {
        "address": "0xf817257fed379853cDe0fa4F97AB987181B1E5Ea",
        "symbol": "USDC",
        "balance": 2500.0,
        "valueUSD": 2500.0
      }
    ],
    "lstPositions": [
      {
        "token": "aprMON",
        "balance": 50.0,
        "valueUSD": 17.70,
        "underlyingMON": 51.23
      }
    ],
    "eulerPositions": [
      {
        "vault": "0x...",
        "asset": "USDC",
        "supplied": 1000.0,
        "valueUSD": 1000.0,
        "apr": 0.0821
      }
    ],
    "totalUSD": 3568.145,
    "timestamp": 1713600000000
  },
  "timestamp": 1713600000000
}

cURL

# Full portfolio
curl "https://your-app.vercel.app/api/portfolio?address=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"

# Tokens and LST positions only
curl "https://your-app.vercel.app/api/portfolio?address=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045&include=tokens,lst"

# Lending positions only
curl "https://your-app.vercel.app/api/portfolio?address=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045&include=lending"

TypeScript Fetch

interface PortfolioResponse {
  data: Portfolio
  timestamp: number
  error?: string
}

async function fetchPortfolio(
  address: string,
  include = 'all'
): Promise<Portfolio> {
  const url = `https://your-app.vercel.app/api/portfolio?address=${address}&include=${include}`
  const res = await fetch(url)
  const json: PortfolioResponse = await res.json()
  if (json.error) throw new Error(json.error)
  return json.data
}

const portfolio = await fetchPortfolio('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')
console.log(`Total portfolio value: $${portfolio.totalUSD.toFixed(2)}`)
console.log(`MON balance: ${portfolio.nativeBalance.toFixed(4)}`)

for (const lst of portfolio.lstPositions) {
  console.log(`${lst.token}: ${lst.balance.toFixed(4)} (≈ $${lst.valueUSD.toFixed(2)})`)
}

Next.js Route Implementation

// pages/api/portfolio.ts
import { getPortfolio } from 'rampart-monad'
import { isAddress } from 'viem'
import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { address, include = 'all' } = req.query

  if (!address || typeof address !== 'string' || !isAddress(address)) {
    return res.status(400).json({
      data: null,
      timestamp: Date.now(),
      error: 'Invalid or missing wallet address',
    })
  }

  try {
    const portfolio = await getPortfolio(address as `0x${string}`)

    // Optionally filter sections
    const sections = (include as string).split(',')
    const filtered = sections.includes('all') ? portfolio : {
      address: portfolio.address,
      nativeBalance: portfolio.nativeBalance,
      nativeValueUSD: portfolio.nativeValueUSD,
      tokens: sections.includes('tokens') ? portfolio.tokens : [],
      lstPositions: sections.includes('lst') ? portfolio.lstPositions : [],
      eulerPositions: sections.includes('lending') ? portfolio.eulerPositions : [],
      totalUSD: portfolio.totalUSD,
      timestamp: portfolio.timestamp,
    }

    res
      .setHeader('Cache-Control', 's-maxage=15, stale-while-revalidate=30')
      .json({ data: filtered, timestamp: Date.now() })
  } catch (err) {
    res.status(500).json({ data: null, timestamp: Date.now(), error: String(err) })
  }
}

Notes

  • USD values use the cross-oracle verified price from getVerifiedPrice - Kuru spot price is used for MON.
  • LST positions include underlyingMON - the MON redemption value at the current exchange rate.
  • The include parameter reduces RPC calls: use tokens if you only need ERC20 balances.
  • Wallet addresses must be checksummed or lowercase - viem normalises them internally.