> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getpara.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Non-Custodial DeFi via vaults.fyi

> Add yield across 1,000+ vaults, Aave and Morpho lending, Pendle fixed-term, and Solana yield to your app using vaults.fyi's non-custodial API and Para embedded wallets.

<Tip>
  For AI-friendly documentation, see [Para llms.txt](https://docs.getpara.com/llms.txt) and [vaults.fyi llms.txt](https://docs.vaults.fyi/llms.txt).
</Tip>

If you're using Para for embedded wallets, you can add a multi-product DeFi experience to your app using the [vaults.fyi](https://vaults.fyi) API. Your users see 1,000+ vaults across 82 protocols, every major Aave and Morpho borrow market, Pendle fixed-yield positions, and Solana yield vaults, all with a single signing surface via Para.

## How It Works

vaults.fyi provides a typed, non-custodial DeFi API. You query an endpoint, get back an ordered array of unsigned transactions, and the user signs each one via their Para wallet. Every operation returns the same `{ currentActionIndex, actions[] }` envelope, so a single signing loop handles every EVM product, and the Solana path is structurally identical with base64-encoded transactions in place of viem calldata. Transactions route directly to canonical protocol contracts. No wrapper, no required fee, no custody.

This walkthrough covers four products:

* **Earn** (EVM yield): discover, deposit into, track, redeem, and claim rewards from 1,000+ vaults across 82 protocols on Ethereum, Base, Arbitrum, Optimism, and 17 other chains
* **Borrow** (EVM lending): discover and use Aave and Morpho lending markets to supply collateral, borrow, repay, and withdraw
* **Fixed-Term** (Pendle PT): swap into fixed-yield Pendle Principal Tokens at a known rate locked until maturity, and swap out before or after expiry
* **Solana** (SVM yield): discover and use Solana-native yield vaults using Para's Solana signer

## Why Combine Para and vaults.fyi

| Para Wallets                                            | vaults.fyi                                                             |
| ------------------------------------------------------- | ---------------------------------------------------------------------- |
| Email/social login, no seed phrase or browser extension | Vault coverage across 21 EVM chains plus Solana                        |
| Embedded plus external wallet support                   | Multi-curator coverage: Gauntlet, Steakhouse, Chaos Labs, Sentora, kpk |
| EIP-712 signing, users see structured data not hex      | Reputation Score and risk methodology per vault                        |
| Multi-chain plus Solana signer                          | Unified envelope across yield, borrow, fixed-term, and SVM operations  |

What makes vaults.fyi distinctive: breadth of coverage (every major DeFi vault and lending market across EVM and Solana), curator diversity (every major risk team's vaults surfaced in one feed), and a simple integration model. The user's Para wallet signs and submits directly. No per-user smart accounts to deploy, no gas-sponsor server to operate, no abstraction layer between the user and the canonical vault contract.

## Example Use Cases

1. **Multi-Curator Earn Product.** Users browse top vaults across every protocol, sorted by APY, with curator and risk-score context. Embed a fee on yield. Works on every EVM chain plus Solana.

2. **Embedded Lending.** Let users borrow USDC against ETH or wBTC collateral via Aave or Morpho. Display health factor and liquidation risk. Repay and withdraw with the same `actions[]` flow.

3. **Fixed-Yield Treasury Allocation.** Lock a portion of treasury USDC into Pendle PT positions for a fixed APY until maturity. Surface time-to-expiry and current yield-to-maturity in your UI.

4. **Cross-Ecosystem Yield Dashboard.** Show every yield-earning position the user holds across every supported EVM protocol AND every Solana vault, with current APY and balances, including positions opened outside your app.

5. **Agent-Driven Allocation.** Pair vaults.fyi's discovery endpoints with an LLM agent that monitors rate environments and rebalances across chains and products on the user's behalf. Para signs each rebalance via the same loop.

## What You Need

* A [Para SDK setup](https://docs.getpara.com/v3/react/setup/nextjs) with embedded wallets configured
* A **vaults.fyi API key**, free for development. Sign up at [portal.vaults.fyi](https://portal.vaults.fyi)
* **vaults.fyi SDK**: [`@vaultsfyi/sdk`](https://www.npmjs.com/package/@vaultsfyi/sdk), typed TypeScript wrapper around the v2 (stable) endpoints. Borrow, Fixed-Term, and Solana live on the beta tier and are accessed via direct HTTP through the same proxy.
* **viem 2.x** plus **`@getpara/viem-v2-integration`** for the Para-backed EVM wallet client
* **`@solana/web3.js`** plus **`@getpara/solana-web3.js-v1-integration`** for the Para-backed Solana signer (Solana product only)

## Step-by-Step Integration

The first three steps are shared across all four products. After that, pick the tab for the product you want to integrate.

### Step 1: Install Dependencies

```bash theme={null} theme={null}
yarn add @vaultsfyi/sdk @getpara/viem-v2-integration @getpara/solana-web3.js-v1-integration viem @solana/web3.js buffer
```

Add these environment variables to `.env.local`:

```bash theme={null} theme={null}
NEXT_PUBLIC_PARA_API_KEY=your_para_api_key
NEXT_PUBLIC_PARA_ENVIRONMENT=BETA
NEXT_PUBLIC_BASE_RPC_URL=https://mainnet.base.org
NEXT_PUBLIC_SOLANA_RPC_URL=https://api.mainnet-beta.solana.com

# Server-side only. Never prefix with NEXT_PUBLIC_ or it will be exposed in the browser bundle.
VAULTS_FYI_API_KEY=your_vaultsfyi_api_key
```

### Step 2: Proxy the vaults.fyi API server-side

The vaults.fyi API uses an `x-api-key` header. To keep that key out of the browser bundle, add a Next.js catch-all route that forwards every `/api/vaultsfyi/*` request upstream with the header attached server-side. The same proxy serves v2 (stable) and beta endpoints. Every vaults.fyi endpoint is `GET`, so a single handler is sufficient.

```typescript theme={null} theme={null}
// src/app/api/vaultsfyi/[...path]/route.ts
import { NextRequest, NextResponse } from "next/server";

const API_BASE = "https://api.vaults.fyi";

async function proxy(
  request: NextRequest,
  params: Promise<{ path: string[] }>,
) {
  const apiKey = process.env.VAULTS_FYI_API_KEY;
  if (!apiKey) {
    return NextResponse.json(
      { error: "VAULTS_FYI_API_KEY is not set on the server." },
      { status: 500 },
    );
  }

  const { path } = await params;
  const url = new URL(`${API_BASE}/${path.join("/")}`);
  request.nextUrl.searchParams.forEach((value, key) => {
    url.searchParams.append(key, value);
  });

  const upstream = await fetch(url, {
    method: request.method,
    headers: { "x-api-key": apiKey, Accept: "application/json" },
  });

  const body = await upstream.text();
  return new NextResponse(body, {
    status: upstream.status,
    headers: {
      "Content-Type":
        upstream.headers.get("Content-Type") ?? "application/json",
    },
  });
}

export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ path: string[] }> },
) {
  return proxy(request, params);
}
```

Configure the SDK to point at your proxy:

```typescript theme={null} theme={null}
// src/lib/vaultsFyi.ts
import { VaultsSdk } from "@vaultsfyi/sdk";

export const sdk = new VaultsSdk(
  { apiKey: "proxied" },
  {
    apiBaseUrl:
      typeof window !== "undefined"
        ? `${window.location.origin}/api/vaultsfyi`
        : "/api/vaultsfyi",
  },
);
```

### Step 3: Build the Para-backed signers

Para's viem integration gives you a standard viem `WalletClient` for EVM operations. Para's Solana integration gives you a `ParaSolanaWeb3Signer` for Solana operations. Both delegate signing to the user's MPC wallet.

The EVM setup below is intentionally Base-only. A transaction must be submitted through wallet and public clients configured for its `tx.chainId`. If your app accepts vaults or markets on other networks, create clients for each supported chain and select the matching pair before executing each action.

The legacy `@solana/web3.js` integration expects Node's `Buffer` global, which browsers do not provide. Install the `buffer` package as shown in Step 1 and assign it to `globalThis.Buffer` before constructing the Para Solana signer.

```typescript theme={null} theme={null}
// src/hooks/useSigners.tsx
"use client";

import { useEffect, useMemo, useState } from "react";
import { useAccount, useClient } from "@getpara/react-sdk";
import {
  createParaViemAccount,
  createParaViemClient,
} from "@getpara/viem-v2-integration";
import { ParaSolanaWeb3Signer } from "@getpara/solana-web3.js-v1-integration";
import { Connection } from "@solana/web3.js";
import { Buffer } from "buffer";
import { createPublicClient, http, type PublicClient } from "viem";
import { base } from "viem/chains";

const BASE_RPC = process.env.NEXT_PUBLIC_BASE_RPC_URL || "https://mainnet.base.org";
const SOLANA_RPC = process.env.NEXT_PUBLIC_SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com";

export function useSigners() {
  const client = useClient();
  const { isConnected } = useAccount();
  const [solanaAddress, setSolanaAddress] = useState<string | null>(null);

  // EVM signer (Earn and Borrow)
  const evm = useMemo(() => {
    if (!client || !isConnected) {
      return { walletClient: null, publicClient: null, address: null, isInitialized: false };
    }
    const account = createParaViemAccount({ para: client });
    const walletClient = createParaViemClient({
      para: client,
      walletClientConfig: { account, chain: base, transport: http(BASE_RPC) },
    });
    const publicClient = createPublicClient({
      chain: base,
      transport: http(BASE_RPC),
    }) as PublicClient;
    return { walletClient, publicClient, address: account.address, isInitialized: true };
  }, [client, isConnected]);

  // Solana signer
  const solana = useMemo(() => {
    if (!client || !isConnected) {
      return { signer: null, connection: null, address: null, isInitialized: false };
    }
    if (!("Buffer" in globalThis)) {
      (globalThis as typeof globalThis & { Buffer: typeof Buffer }).Buffer = Buffer;
    }
    const connection = new Connection(SOLANA_RPC, "confirmed");
    const signer = new ParaSolanaWeb3Signer(client, connection);
    return { signer, connection, address: solanaAddress, isInitialized: true };
  }, [client, isConnected, solanaAddress]);

  // Resolve the Solana address asynchronously
  useEffect(() => {
    if (solana.signer && isConnected) {
      (async () => {
        const publicKey = await solana.signer!.sender;
        if (publicKey) setSolanaAddress(publicKey.toBase58());
      })();
    } else {
      setSolanaAddress(null);
    }
  }, [solana.signer, isConnected]);

  return { evm, solana };
}
```

### Step 4: Pick a product

<Tabs>
  <Tab title="Earn (EVM Yield)">
    Discover EVM vaults, deposit, track positions, redeem, and claim rewards. Uses the stable v2 API via the `@vaultsfyi/sdk` typed client.

    #### Discover vaults

    Query vaults.fyi for the top USDC vaults on Base, sorted by 7-day APY.

    ```typescript theme={null} theme={null}
    // src/hooks/useDepositOptions.ts
    import { useQuery } from "@tanstack/react-query";
    import { sdk } from "@/lib/vaultsFyi";

    export type VaultOption = Awaited<
      ReturnType<typeof sdk.getAllVaults>
    >["data"][number];

    export function useDepositOptions() {
      return useQuery({
        queryKey: ["depositOptions"],
        queryFn: async () => {
          const result = await sdk.getAllVaults({
            query: {
              allowedAssets: ["USDC"],
              allowedNetworks: ["base"],
              onlyTransactional: true,
              sortBy: "apy7day",
              sortOrder: "desc",
              perPage: 20,
            },
          });
          return result.data.filter(
            (v) =>
              v.transactionalProperties?.depositStepsType !== "complex" &&
              v.transactionalProperties?.redeemStepsType !== "complex",
          );
        },
      });
    }
    ```

    A few defaults to know: `allowedNetworks` defaults to four networks (Ethereum, Base, Arbitrum, Optimism); pass the full list explicitly for broader coverage. `minTvl` defaults to 100,000 USD. `sortOrder` defaults to ascending. `apy.{window}.total` is a raw decimal: `0.0543` means 5.43%.

    #### Read the user's vault context

    Before quoting a deposit, fetch the user's current state for the vault. The response includes the user's underlying-asset balance, current allowance, and step in any multi-step flow.

    ```typescript theme={null} theme={null}
    // src/hooks/useTransactionContext.ts
    import { useQuery } from "@tanstack/react-query";
    import { sdk } from "@/lib/vaultsFyi";

    export function useTransactionContext(
      userAddress: string | undefined,
      network: string,
      vaultId: string | undefined,
    ) {
      return useQuery({
        queryKey: ["transactionContext", userAddress, network, vaultId],
        queryFn: () =>
          sdk.getTransactionsContext({
            path: {
              userAddress: userAddress!,
              network: network as "base",
              vaultId: vaultId!,
            },
          }),
        enabled: !!userAddress && !!vaultId,
      });
    }
    ```

    #### Deposit

    The deposit endpoint returns an ordered `actions[]` array. Each entry has `tx.to`, `tx.data`, `tx.value`, `tx.chainId` plus a `currentActionIndex` for resumption. Some vaults need a single transaction; others need approve plus deposit.

    This walkthrough executes Base actions only. The explicit chain ID check below prevents an action for another network from being submitted through the Base client. For multi-network support, resolve a wallet/public client pair configured for `step.tx.chainId` inside the loop instead.

    ```typescript theme={null} theme={null}
    // src/hooks/useExecuteEvm.ts
    "use client";

    import { useCallback, useState } from "react";
    import { useSigners } from "./useSigners";

    type ActionStep = {
      name: string;
      tx: { to: string; chainId: number; data?: string; value?: string };
    };

    export function useExecuteEvm() {
      const [state, setState] = useState({
        running: false,
        step: null as { current: number; total: number } | null,
        hashes: [] as { hash: string; name: string }[],
        error: null as string | null,
      });
      const { evm } = useSigners();

      const execute = useCallback(
        async (currentActionIndex: number, actions: ActionStep[]) => {
          if (!evm.isInitialized || !evm.walletClient || !evm.publicClient) return;

          const remaining = actions.slice(currentActionIndex);
          setState({ running: true, step: null, hashes: [], error: null });
          const hashes: { hash: string; name: string }[] = [];

          try {
            for (let i = 0; i < remaining.length; i++) {
              const step = remaining[i];
              if (step.tx.chainId !== evm.walletClient.chain?.id) {
                throw new Error(
                  `Action requires chain ${step.tx.chainId}, but the configured client uses chain ${evm.walletClient.chain?.id}.`,
                );
              }
              setState((s) => ({
                ...s,
                step: { current: i + 1, total: remaining.length },
              }));
              const hash = await evm.walletClient.sendTransaction({
                account: evm.walletClient.account!,
                chain: evm.walletClient.chain,
                to: step.tx.to as `0x${string}`,
                data: step.tx.data as `0x${string}` | undefined,
                value: step.tx.value ? BigInt(step.tx.value) : undefined,
              });
              await evm.publicClient.waitForTransactionReceipt({ hash, confirmations: 2 });
              hashes.push({ hash, name: step.name });
            }
            setState({ running: false, step: null, hashes, error: null });
          } catch (error) {
            const message = error instanceof Error ? error.message : "Unknown error";
            setState((s) => ({ ...s, running: false, step: null, error: message }));
          }
        },
        [evm],
      );

      return { ...state, execute };
    }
    ```

    Wire it into a deposit handler inside any React component (the `execute` function comes from `useExecuteEvm()`):

    ```typescript theme={null} theme={null}
    import { parseUnits } from "viem";
    import { sdk } from "@/lib/vaultsFyi";
    import { useExecuteEvm } from "@/hooks/useExecuteEvm";

    function DepositForm({ userAddress, vaultId, assetAddress }: {
      userAddress: string; vaultId: string; assetAddress: string;
    }) {
      const { execute, running, step } = useExecuteEvm();

      async function handleDeposit(amount: string) {
        const { currentActionIndex, actions } = await sdk.getActions({
          path: { action: "deposit", userAddress, network: "base", vaultId },
          query: {
            assetAddress,
            amount: parseUnits(amount, 6).toString(), // USDC has 6 decimals
          },
        });
        await execute(currentActionIndex, actions);
      }

      return <button onClick={() => handleDeposit("1.0")} disabled={running}>
        {running && step ? `Depositing ${step.current}/${step.total}` : "Deposit 1 USDC"}
      </button>;
    }
    ```

    #### Track positions

    The positions endpoint returns every vault position the user holds across every supported EVM protocol, read directly from on-chain state.

    ```typescript theme={null} theme={null}
    const { data } = useQuery({
      queryKey: ["positions", userAddress],
      queryFn: () => sdk.getPositions({ path: { userAddress } }),
      enabled: !!userAddress,
    });
    ```

    #### Redeem

    Same `actions[]` pattern as deposit. Pass `all: true` to redeem the full position.

    ```typescript theme={null} theme={null}
    const { currentActionIndex, actions } = await sdk.getActions({
      path: {
        action: "redeem",
        userAddress,
        network: position.network.name,
        vaultId: position.vaultId,
      },
      query: { assetAddress: position.asset.address, all: true },
    });
    await execute(currentActionIndex, actions);
    ```

    #### Claim rewards

    Two-step flow. Discover claim IDs, then fetch the per-network claim transactions.

    ```typescript theme={null} theme={null}
    const ctx = await sdk.getRewardsTransactionsContext({
      path: { userAddress },
    });
    const networkRewards = ctx.claimable["base"] ?? [];

    if (networkRewards.length > 0) {
      const claim = await sdk.getRewardsClaimActions({
        path: { userAddress },
        query: { claimIds: networkRewards.map((r) => r.claimId) },
      });
      const networkClaim = claim["base"];
      if (networkClaim && networkClaim.actions.length > 0) {
        await execute(networkClaim.currentActionIndex, networkClaim.actions);
      }
    }
    ```
  </Tab>

  <Tab title="Borrow (EVM Lending)">
    Discover Aave and Morpho lending markets. Supply collateral, borrow, repay, and withdraw using the same `actions[]` flow as Earn. Borrow lives on the beta tier (no SDK method yet, so direct HTTP via the proxy).

    #### Discover markets

    List every borrow market vaults.fyi indexes (paginated), or filter to one network:

    ```typescript theme={null} theme={null}
    // All markets across every supported network
    async function fetchAllMarkets() {
      const res = await fetch(`/api/vaultsfyi/beta/borrow/markets/?perPage=50`);
      if (!res.ok) throw new Error("Failed to fetch markets");
      return res.json();
    }

    // Markets on a specific network (network is a path param, not a query param)
    async function fetchMarketsByNetwork(network: string) {
      const res = await fetch(`/api/vaultsfyi/beta/borrow/markets/${network}`);
      if (!res.ok) throw new Error("Failed to fetch markets");
      return res.json();
    }
    ```

    Each market returns `marketId`, `network`, `protocol` (Aave or Morpho), supported assets with per-asset supply/borrow APY, LTV, and liquidation threshold.

    #### Read the user's market context

    Before any borrow operation, fetch the user's position state for that market. The response includes the user's collateral and debt asset state plus four parallel step counters (`currentSupplyStep`/`currentBorrowStep`/`currentRepayStep`/`currentWithdrawStep` and their corresponding `*Steps` arrays) so one call tells you where you are in any of the four flows.

    ```typescript theme={null} theme={null}
    async function fetchBorrowContext(
      userAddress: string,
      network: string,
      marketId: string,
      assetAddress: string,
    ) {
      const res = await fetch(
        `/api/vaultsfyi/beta/borrow/markets/transactions/context/${userAddress}/${network}/${marketId}/${assetAddress}`,
      );
      return res.json();
    }
    ```

    #### Supply collateral, borrow, repay, withdraw

    All four operations use the same endpoint with a different `action` path parameter (`supply`, `withdraw`, `borrow`, `repay`). The response shape is the same `{ currentActionIndex, actions[] }` envelope as Earn. The EVM signer hook from Step 3 works without changes for Base; for another network, configure and select clients whose chain ID matches each action's `tx.chainId`.

    Inside any component using `useExecuteEvm()`:

    ```typescript theme={null} theme={null}
    import { useExecuteEvm } from "@/hooks/useExecuteEvm";

    function BorrowControls(props: { userAddress: string; network: string; marketId: string; assetAddress: string }) {
      const { execute } = useExecuteEvm();

      async function borrowAction(
        action: "supply" | "withdraw" | "borrow" | "repay",
        amount: string, // wei string
        opts?: { all?: boolean }, // `all: true` repays/withdraws the full position
      ) {
        const params = new URLSearchParams({ amount });
        if (opts?.all) params.set("all", "true");

        const res = await fetch(
          `/api/vaultsfyi/beta/borrow/markets/transactions/${action}/${props.userAddress}/${props.network}/${props.marketId}/${props.assetAddress}?${params}`,
        );
        const { currentActionIndex, actions } = await res.json();
        await execute(currentActionIndex, actions);
      }

      // ... wire to buttons
    }
    ```

    Typical flow:

    1. **Supply collateral**: `borrowAction("supply", ..., parseUnits("0.5", 18).toString())` to deposit 0.5 ETH
    2. **Borrow against it**: `borrowAction("borrow", ..., parseUnits("500", 6).toString())` to borrow 500 USDC
    3. **Repay**: `borrowAction("repay", ..., amount)`, or pass `?all=true` to repay the full debt
    4. **Withdraw collateral**: `borrowAction("withdraw", ..., amount)`

    #### Track borrow positions

    The portfolio endpoint returns every borrow position the user holds across every supported protocol:

    ```typescript theme={null} theme={null}
    async function fetchBorrowPositions(userAddress: string) {
      const res = await fetch(`/api/vaultsfyi/beta/borrow/portfolio/positions/${userAddress}`);
      return res.json();
    }
    ```

    Each record returns `marketId`, `network`, `protocol`, `assets` (collateral and debt per asset), `ltv`, and `healthFactor`. Use this to render a "your debt" panel and warn the user before health factor approaches liquidation thresholds.

    <Tip>
      `actions[]` returns each step as a separate transaction the user signs in order (e.g. ERC-20 approve, then supply). Same multi-step receipt-wait loop as Earn.
    </Tip>
  </Tab>

  <Tab title="Fixed-Term (Pendle PT)">
    Swap into Pendle Principal Tokens (PT) at a fixed yield-to-maturity, and swap out before or after expiry. Pendle PT positions lock in a known APY for a defined term, ideal for treasury allocation or any user-facing experience that needs predictable returns. Lives on the beta tier (direct HTTP via the proxy), supports every EVM network vaults.fyi indexes.

    #### Read vault details

    Pendle vaults are accessed by `vaultId` through `/beta/fixed-term/detailed/{network}/{vaultId}`. A public list-discovery endpoint isn't part of the beta surface yet; if you need a catalog of Pendle vaults, contact [team@vaults.fyi](mailto:team@vaults.fyi) for the current vaultId set per network, or hard-code the PT vaults relevant to your product. Once you have a `vaultId`, fetch the Pendle-specific metadata:

    ```typescript theme={null} theme={null}
    async function fetchFixedTermDetails(network: string, vaultId: string) {
      const res = await fetch(`/api/vaultsfyi/beta/fixed-term/detailed/${network}/${vaultId}`);
      return res.json(); // returns { name, apy, asset, expiry, lpToken, protocol, tvl }
    }
    ```

    Use `expiry` (unix seconds) to render the time-to-maturity and `apy` to render the fixed yield-to-maturity at the current rate.

    #### Read the user's vault context

    ```typescript theme={null} theme={null}
    async function fetchFixedTermContext(
      userAddress: string,
      network: string,
      vaultId: string,
    ) {
      const res = await fetch(
        `/api/vaultsfyi/beta/fixed-term/context/${userAddress}/${network}/${vaultId}`,
      );
      return res.json();
    }
    ```

    Returns the user's underlying-asset balance, their current PT holdings (in `lpToken`), and the step state for any in-progress swap.

    #### Swap in (deposit) and swap out (redeem)

    Pendle actions are `swap-in` (convert underlying → PT, locking the fixed yield) and `swap-out` (convert PT → underlying). They use the same `{ currentActionIndex, actions[] }` envelope and the same Base-only EVM signer hook from Step 3. For another network, configure and select clients whose chain ID matches each action's `tx.chainId`.

    Inside any component using `useExecuteEvm()`:

    ```typescript theme={null} theme={null}
    import { useExecuteEvm } from "@/hooks/useExecuteEvm";

    function FixedTermControls(props: { userAddress: string; network: string; vaultId: string; assetAddress: string }) {
      const { execute } = useExecuteEvm();

      async function fixedTermAction(
        action: "swap-in" | "swap-out",
        amount: string, // wei string
      ) {
        const params = new URLSearchParams({ assetAddress: props.assetAddress, amount });
        const res = await fetch(
          `/api/vaultsfyi/beta/fixed-term/transactions/${action}/${props.userAddress}/${props.network}/${props.vaultId}?${params}`,
        );
        const { currentActionIndex, actions } = await res.json();
        await execute(currentActionIndex, actions);
      }

      // ... wire to buttons
    }
    ```

    PT positions show up in the standard EVM positions endpoint (`/v2/portfolio/positions/{userAddress}`) alongside the user's other vault holdings. No separate Pendle portfolio endpoint needed.

    <Tip>
      Pendle PT yield is fixed at the time of swap-in, not floating. If you display "APY" in your UI, label it as "Fixed APY (to maturity)" to set the right user expectation. The `expiry` timestamp tells you the maturity date; after expiry, swap-out converts PT 1:1 back to the underlying.
    </Tip>
  </Tab>

  <Tab title="Solana Yield">
    Discover Solana-native yield vaults, deposit, and redeem. SVM transactions are base64-encoded serialized `VersionedTransaction`s, signed via Para's Solana signer rather than viem. Lives on the beta tier (direct HTTP via the proxy).

    #### Discover Solana vaults

    ```typescript theme={null} theme={null}
    async function fetchSolanaVaults() {
      const params = new URLSearchParams({
        allowedAssets: "USDC",
        sortBy: "apy7day",
        sortOrder: "desc",
        perPage: "20",
      });
      const res = await fetch(`/api/vaultsfyi/beta/svm/detailed-vaults?${params}`);
      return res.json();
    }
    ```

    Each vault returns a Solana `vaultAddress` (base58, not an EVM `0x...` hex), the underlying asset, APY breakdown, TVL, and curator metadata.

    #### Read the user's vault context

    ```typescript theme={null} theme={null}
    async function fetchSvmContext(userAddress: string, vaultAddress: string) {
      const res = await fetch(
        `/api/vaultsfyi/beta/svm/transactions/context/${userAddress}/${vaultAddress}`,
      );
      return res.json();
    }
    ```

    Returns the user's USDC (or other asset) balance, current vault position, and the current step in any multi-step flow.

    #### Deposit

    The deposit endpoint returns the same `{ currentActionIndex, actions[] }` envelope, but each `tx` carries a base64-encoded Solana transaction instead of EVM calldata. Sign with Para's Solana signer.

    ```typescript theme={null} theme={null}
    // src/hooks/useExecuteSolana.ts
    "use client";

    import { useCallback, useState } from "react";
    import { Buffer } from "buffer";
    import { VersionedTransaction } from "@solana/web3.js";
    import { useSigners } from "./useSigners";

    type SolanaActionStep = {
      name: string;
      tx: { type: "solana"; transaction: string }; // base64
    };

    export function useExecuteSolana() {
      const [state, setState] = useState({
        running: false,
        signatures: [] as { signature: string; name: string }[],
        error: null as string | null,
      });
      const { solana } = useSigners();

      const execute = useCallback(
        async (currentActionIndex: number, actions: SolanaActionStep[]) => {
          if (!solana.isInitialized || !solana.signer || !solana.connection) return;

          const remaining = actions.slice(currentActionIndex);
          setState({ running: true, signatures: [], error: null });
          const signatures: { signature: string; name: string }[] = [];

          try {
            for (const step of remaining) {
              const txBuffer = Buffer.from(step.tx.transaction, "base64");
              const tx = VersionedTransaction.deserialize(txBuffer);
              const signed = await solana.signer.signVersionedTransaction(tx);

              const signature = await solana.connection.sendRawTransaction(
                signed.serialize(),
                { skipPreflight: false, maxRetries: 3 },
              );

              // Poll the submitted signature. Do not fetch a new blockhash here:
              // it would be unrelated to the blockhash in the signed transaction.
              const confirmationDeadline = Date.now() + 90_000;
              let confirmed = false;
              while (Date.now() < confirmationDeadline) {
                const { value: status } =
                  await solana.connection.getSignatureStatus(signature, {
                    searchTransactionHistory: true,
                  });

                if (status?.err) {
                  throw new Error(
                    `Transaction failed: ${JSON.stringify(status.err)}`,
                  );
                }
                if (
                  status?.confirmationStatus === "confirmed" ||
                  status?.confirmationStatus === "finalized"
                ) {
                  confirmed = true;
                  break;
                }
                await new Promise((resolve) => setTimeout(resolve, 500));
              }

              if (!confirmed) {
                throw new Error(
                  `Confirmation timed out for ${signature}. Check its status before retrying.`,
                );
              }
              signatures.push({ signature, name: step.name });
            }
            setState({ running: false, signatures, error: null });
          } catch (error) {
            const message = error instanceof Error ? error.message : "Unknown error";
            setState((s) => ({ ...s, running: false, error: message }));
          }
        },
        [solana],
      );

      return { ...state, execute };
    }
    ```

    Wire it into a component (the `execute` function comes from `useExecuteSolana()`):

    ```typescript theme={null} theme={null}
    import { useExecuteSolana } from "@/hooks/useExecuteSolana";
    import { useSigners } from "@/hooks/useSigners";

    function SolanaDepositForm({ vaultAddress, assetAddress }: { vaultAddress: string; assetAddress: string }) {
      const { execute, running } = useExecuteSolana();
      const { solana } = useSigners();

      async function handleSolanaDeposit(amount: string /* base units; USDC has 6 decimals */) {
        if (!solana.address) return;
        const params = new URLSearchParams({ assetAddress, amount });
        const res = await fetch(
          `/api/vaultsfyi/beta/svm/transactions/deposit/${solana.address}/${vaultAddress}?${params}`,
        );
        const { currentActionIndex, actions } = await res.json();
        await execute(currentActionIndex, actions);
      }

      return <button onClick={() => handleSolanaDeposit("1000000")} disabled={!solana.address || running}>
        Deposit 1 USDC
      </button>;
    }
    ```

    #### Track Solana positions

    ```typescript theme={null} theme={null}
    async function fetchSolanaPositions(userAddress: string) {
      const res = await fetch(`/api/vaultsfyi/beta/svm/portfolio/positions/${userAddress}`);
      return res.json();
    }
    ```

    #### Redeem

    Solana actions are `deposit`, `redeem`, `request-redeem`, `confirm-redeem`, `complete-redeem`. Most vaults use single-step `redeem`; vaults with cooldowns use the three-step request/confirm/complete flow.

    Unlike the EVM endpoints, SVM does not accept `all: true` as a shortcut. Pass the explicit `amount` of shares (in base units) to redeem the full position. Read the current share balance from `/beta/svm/portfolio/positions/{userAddress}`.

    ```typescript theme={null} theme={null}
    const params = new URLSearchParams({ assetAddress, amount: sharesToRedeem });
    const res = await fetch(
      `/api/vaultsfyi/beta/svm/transactions/redeem/${userAddress}/${vaultAddress}?${params}`,
    );
    const { currentActionIndex, actions } = await res.json();
    await execute(currentActionIndex, actions);
    ```

    <Tip>
      The Para Solana signer is the same pattern used in Para's [Solana Transfers walkthrough](/v3/walkthroughs/solana-transfers) and the `with-jupiter-dex-api` examples-hub entry. Drop-in compatible if you already have Solana operations elsewhere in your app.
    </Tip>
  </Tab>
</Tabs>

## Querying Data

Beyond the operational flows above, vaults.fyi exposes a broader set of endpoints for discovery, history, and analytics:

* `/v2/detailed-vaults`: every indexed EVM vault with 21 filter parameters (curator, protocol, network, asset, TVL, APY thresholds)
* `/v2/portfolio/best-deposit-options/{userAddress}`: personalized vault recommendations ranked by the assets the user actually holds
* `/v2/portfolio/idle-assets/{userAddress}`: cash sitting in the user's wallet that could be earning yield
* `/v2/historical/{network}/{vaultId}`: time-series APY and TVL data
* `/v2/benchmarks/{network}`: market-wide USD and ETH benchmark rates
* `/v2/portfolio/total-returns/{userAddress}/{network}/{vaultId}`: lifetime PnL for a position
* `/v2/nrt/vault/{network}/{vaultId}`: near-real-time onchain reads (share price, total assets, total supply)
* `/beta/borrow/markets/historical/{network}/{marketId}/{assetAddress}`: historical supply/borrow APY and utilization
* `/beta/svm/portfolio/idle-assets/{userAddress}`: Solana idle assets that could be earning yield
* `/beta/svm/portfolio/events/{userAddress}`: Solana deposit/withdrawal event history

See the [vaults.fyi API reference](https://docs.vaults.fyi) for the full surface.

## Related Resources

* [vaults.fyi documentation](https://docs.vaults.fyi)
* [@vaultsfyi/sdk reference](https://docs.vaults.fyi/sdk/reference)
* [vaults.fyi OpenAPI: v2 stable](https://api.vaults.fyi/v2/documentation/static/index.html), [beta](https://api.vaults.fyi/beta/documentation/static/index.html)
* [vaults.fyi llms.txt for AI agents](https://docs.vaults.fyi/llms.txt)
* [vaults.fyi MCP server](https://docs.vaults.fyi/ai-agents/mcp-server)
* [Para React + Next.js setup](/v3/react/setup/nextjs)
* [Para Ethereum Transfers walkthrough](/v3/walkthroughs/ethereum-transfers): EVM signing patterns with viem
* [Para Solana Transfers walkthrough](/v3/walkthroughs/solana-transfers): Solana signing patterns with Para's Solana signer
