Skip to content
Open
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
7 changes: 7 additions & 0 deletions examples/nextjs-defi-yield-vaultsfyi/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Dynamic environment ID from https://app.dynamic.xyz → Developer Settings → SDK & API Keys
NEXT_PUBLIC_DYNAMIC_ENV_ID=YOUR_DYNAMIC_ENVIRONMENT_ID

# vaults.fyi API key from https://portal.vaults.fyi (issued within ~1 business day)
# Server-side only — read by /api/vaultsfyi/[...path]/route.ts and injected as the x-api-key
# header. Never exposed to the browser. Do not prefix with NEXT_PUBLIC_.
VAULTS_FYI_API_KEY=your-vaultsfyi-api-key
42 changes: 42 additions & 0 deletions examples/nextjs-defi-yield-vaultsfyi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*
!.env.example

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
128 changes: 128 additions & 0 deletions examples/nextjs-defi-yield-vaultsfyi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# vaults.fyi Yield with Dynamic

A Next.js recipe demonstrating five DeFi yield operations against [vaults.fyi](https://vaults.fyi)'s coverage of 1,000+ vaults across 80+ protocols, with [Dynamic](https://dynamic.xyz)'s embedded MPC wallets handling auth and transaction signing. Multi-chain (Base default, plus Ethereum, Arbitrum, Optimism, Polygon).

## How this relates to the other Dynamic yield recipes

Dynamic publishes three [yield recipes](https://www.dynamic.xyz/docs/recipes/integrations/yield/) today:

- **[Aave V3](https://www.dynamic.xyz/docs/recipes/integrations/yield/aave)** — single-protocol lending via the Aave SDK
- **[Morpho](https://www.dynamic.xyz/docs/recipes/integrations/yield/morpho)** — curated vaults with Sentora and Galaxy as curators
- **[Pods (Deframe)](https://www.dynamic.xyz/docs/recipes/integrations/yield/stablecoin-yield-pods)** — yield aggregator via Deframe-curated strategies

vaults.fyi extends Dynamic's curator footprint. The Morpho recipe already names Sentora — a vaults.fyi partner curator. This recipe adds Gauntlet, Steakhouse, Chaos Labs, kpk, Re7, and others across 80+ protocols and 20+ chains. Use Pods when Deframe's curated strategies fit, use Aave or Morpho for single-protocol depth, use vaults.fyi when you want multi-curator coverage in one API surface.

## Features

- Authenticate via Dynamic (Google OAuth, email + OTP, or external EVM wallet) with embedded MPC wallet provisioning on first sign-in
- Discover top USDC vaults on the active chain, ranked by 7-day APY across every protocol vaults.fyi indexes
- Deposit into any vault — calldata generated by the vaults.fyi API, signed via a viem WalletClient built by `createWalletClientForWalletAccount` from the Dynamic EVM extension
- Track on-chain positions across every protocol the user holds vault shares in, including positions opened outside this app
- Claim accrued rewards across protocols via the two-step rewards flow
- Multi-chain: switch the active network in the header and the discover / positions / rewards panels update; transactions auto-switch the wallet's active network if a vaults.fyi action specifies a different `tx.chainId`

## Stack

```
Next.js 15 (App Router) + TypeScript + Tailwind 4
@dynamic-labs-sdk/client 0.27.2 + @dynamic-labs-sdk/evm 0.27.2
@tanstack/react-query 5
viem 2 (WalletClient via createWalletClientForWalletAccount)
@vaultsfyi/sdk 2.3.5
```

## Getting Started

### 1. Environment

```bash
cp .env.example .env.local
```

Fill in:

```
NEXT_PUBLIC_DYNAMIC_ENV_ID=your-environment-id
VAULTS_FYI_API_KEY=your-vaultsfyi-api-key
```

- **Dynamic Environment ID** — sign up at [app.dynamic.xyz](https://app.dynamic.xyz), create a project, copy the Environment ID from Developer Settings → SDK & API Keys. Free.
- **vaults.fyi API key** — register at [portal.vaults.fyi](https://portal.vaults.fyi). Keys are typically issued within one business day.

`VAULTS_FYI_API_KEY` is **not** prefixed with `NEXT_PUBLIC_`. It is read server-side by the `/api/vaultsfyi/[...path]` proxy route and injected as the `x-api-key` header. The key never reaches the browser bundle.

### 2. Install

```bash
pnpm install
```

### 3. Run

```bash
pnpm dev
```

Open [http://localhost:3000](http://localhost:3000). Sign in via Google or email OTP. Pick a vault, deposit 1 USDC. Watch the Positions panel update. Claim any rewards.

## How It Works

1. **Auth & wallet.** Dynamic's `createDynamicClient` + `addEvmExtension` initializes once in `src/lib/dynamic.ts`. `src/lib/providers.tsx` wraps the app in a React context that reads `getWalletAccounts(dynamicClient)` synchronously and refreshes via `onEvent({ event: "walletAccountsChanged" })` subscriptions. On first sign-in, `createWaasWalletAccounts({ chains: ["EVM"] })` provisions the embedded WaaS wallet.
2. **Discover.** `GET /v2/detailed-vaults` filtered by `allowedAssets: ["USDC"]`, `allowedNetworks: [activeChain]`, `sortBy: "apy7day"`, `sortOrder: "desc"`, `onlyTransactional: true`. Three filter defaults (network, TVL, sort order) bite if left unset — see comments in `src/lib/useDiscover.ts`.
3. **Deposit.** `GET /v2/transactions/deposit/:user/:network/:vaultId?assetAddress=...&amount=...` returns an ordered `actions[]` array. Each step is signed by a fresh viem WalletClient from `createWalletClientForWalletAccount({ walletAccount })`. The active Dynamic network is switched via `switchActiveNetwork` first if `tx.chainId` doesn't match.
4. **Track.** `GET /v2/portfolio/positions/:userAddress` returns every position the user holds across every protocol vaults.fyi indexes — including positions opened outside this app.
5. **Withdraw.** `GET /v2/transactions/redeem/:user/:network/:vaultId?assetAddress=...&all=true` returns the same `actions[]` shape as deposit.
6. **Claim.** Two-step. `GET /v2/transactions/rewards/context/:userAddress` returns claim IDs by network. `GET /v2/transactions/rewards/claim/:userAddress?claimIds=[...]` returns per-network actions.

## Endpoints used

| Step | Endpoint | What you get |
|---|---|---|
| Discover | `GET /v2/detailed-vaults` | Top vaults on the active chain ranked by 7-day APY |
| Deposit | `GET /v2/transactions/deposit/:user/:network/:vaultId` | Ordered `actions[]` array, each with `tx.to`, `tx.data`, `tx.chainId`, `tx.value` ready to sign |
| Track | `GET /v2/portfolio/positions/:userAddress` | Every vault position the user holds across every protocol |
| Withdraw | `GET /v2/transactions/redeem/:user/:network/:vaultId?all=true` | Same shape as deposit |
| Claim | `GET /v2/transactions/rewards/context/:userAddress` then `/rewards/claim/:userAddress` | Two-step. Discover claim IDs, then per-network transactions |

## Note about real funds

vaults.fyi indexes mainnet vaults only. There is no testnet equivalent for the Morpho, Aave, Sky, or Spark vaults you'd deposit into. The deposit flow in this recipe defaults to 1 USDC, which is enough to runtime-test the integration end-to-end for ~$1 of real funds. Use a fresh embedded wallet for testing if you'd prefer to keep this isolated.

## Architecture

```
src/
├── app/
│ ├── layout.tsx # Providers wrap + Header
│ ├── page.tsx # dynamic-imported YieldInterface
│ ├── globals.css
│ └── api/vaultsfyi/[...path]/ # Server-side proxy for x-api-key injection
│ └── route.ts
├── components/
│ ├── ui/card.tsx
│ ├── Header.tsx # Dynamic auth (Google + email OTP) + network switcher
│ ├── YieldInterface.tsx # 5-panel layout
│ ├── DiscoverPanel.tsx
│ ├── ActionPanel.tsx
│ ├── PositionsPanel.tsx
│ └── RewardsPanel.tsx
└── lib/
├── dynamic.ts # createDynamicClient + addEvmExtension
├── providers.tsx # WalletContext + QueryClient, event-driven refresh
├── networks.ts # SUPPORTED_NETWORKS (Base, Ethereum, Arbitrum, Optimism, Polygon)
├── constants.ts
├── utils.ts # formatApy, formatToken, shortAddress, parseAmount
├── vaultsFyi.ts # @vaultsfyi/sdk pointed at the proxy
├── useDiscover.ts # GET /v2/detailed-vaults
├── usePositions.ts # GET /v2/portfolio/positions
├── useRewards.ts # GET /v2/transactions/rewards/context
└── useExecuteAction.ts # Multi-step signed-tx loop with chain switching
```

## Learn More

- Dynamic Documentation: [docs.dynamic.xyz](https://docs.dynamic.xyz)
- Dynamic existing yield recipes: [Aave](https://www.dynamic.xyz/docs/recipes/integrations/yield/aave), [Morpho](https://www.dynamic.xyz/docs/recipes/integrations/yield/morpho), [Pods](https://www.dynamic.xyz/docs/recipes/integrations/yield/stablecoin-yield-pods)
- vaults.fyi Documentation: [docs.vaults.fyi](https://docs.vaults.fyi)
- vaults.fyi OpenAPI spec: [api.vaults.fyi/v2/documentation](https://api.vaults.fyi/v2/documentation/static/index.html)
- @vaultsfyi/sdk reference: [docs.vaults.fyi/sdk/reference](https://docs.vaults.fyi/sdk/reference)
16 changes: 16 additions & 0 deletions examples/nextjs-defi-yield-vaultsfyi/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
baseDirectory: __dirname,
});

const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];

export default eslintConfig;
10 changes: 10 additions & 0 deletions examples/nextjs-defi-yield-vaultsfyi/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
webpack: (config) => {
config.externals.push("pino-pretty", "lokijs", "encoding");
return config;
},
};

export default nextConfig;
43 changes: 43 additions & 0 deletions examples/nextjs-defi-yield-vaultsfyi/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "nextjs-defi-yield-vaultsfyi",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"type-check": "tsc --noEmit",
"clean": "rm -rf .next && rm -rf node_modules/.cache"
},
"dependencies": {
"@dynamic-labs-sdk/client": "0.27.2",
"@dynamic-labs-sdk/evm": "0.27.2",
"@tanstack/react-query": "5.83.0",
"@vaultsfyi/sdk": "2.3.5",
"clsx": "2.1.1",
"crypto-browserify": "3.12.1",
"lucide-react": "0.542.0",
"next": "15.4.10",
"process": "0.11.10",
"react": "19.1.2",
"react-dom": "19.1.2",
"stream-browserify": "3.0.0",
"tailwind-merge": "3.3.1",
"viem": "2.38.0"
},
"devDependencies": {
"@eslint/eslintrc": "3.3.1",
"@tailwindcss/postcss": "4.1.13",
"@types/node": "20.19.9",
"@types/react": "19.1.8",
"@types/react-dom": "19.1.6",
"autoprefixer": "10.4.21",
"eslint": "9.31.0",
"eslint-config-next": "15.4.1",
"postcss": "8.5.6",
"tailwindcss": "4.1.13",
"typescript": "5.8.3"
}
}
Loading