> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://www.openfort.io/api/mcp` to find what you need.
>
> **Have feedback?** Use `submit_feedback` on the same MCP server.

# Yield on Morpho

Build a web application that lets users supply USDC to a Morpho Blue vault on Base and withdraw with earned yield — all through Openfort embedded wallets.

:::warning
The vault is on **Base mainnet**, so this recipe moves real USDC and needs your project's live keys (`pk_live_...`).
:::

:::tip
You'll build a web app where users authenticate with Openfort, view their USDC wallet balance and Morpho vault position with live APY, and deposit or withdraw USDC from the vault — with optional gas sponsorship.
:::

![Morpho recipe interface](https://www.openfort.io/images/blog/morph_d715e21926.png?updated_at=2025-09-19T12:00:10.277Z)

[View Sample Code](https://github.com/openfort-xyz/recipes-hub/tree/main/morpho) — GitHub Repository. Complete source code for the Morpho Blue integration with frontend, backend, and vault interaction logic.

## Build it with your agent

Adding this to an app you already have? Set up your coding agent with the [Openfort docs MCP server and skill](https://www.openfort.io/docs/overview/building-with-ai), then give it this prompt:

```text
Add USDC deposits into a Morpho vault to this app using Openfort. Follow the "Add this to your app" section of
https://github.com/openfort-xyz/recipes-hub/blob/main/morpho/AGENTS.md
```

The same file lists every Openfort primitive the recipe uses and the errors you're likely to hit, with their fixes.

## Getting started

::::steps
### Set up your project

Clone the recipe and install dependencies:

```bash
pnpx gitpick openfort-xyz/recipes-hub/tree/main/morpho openfort-morpho
cd openfort-morpho
pnpm install
```

### Configure your Openfort credentials

This recipe uses **passkey** wallet recovery (client-side WebAuthn), so it needs no backend — only your Shield publishable key. Get your credentials from the [Openfort Dashboard](https://dashboard.openfort.io):

1. Navigate to **Developers** → **API keys** and copy your **Publishable key** (starts with `pk_`)
2. Copy your Shield **Publishable key** from the **Embedded wallet keys (Shield)** section of the same page
3. Optionally, go to **Gas sponsorship** → create a new gas sponsorship on Base mainnet → copy the **gas sponsorship ID**
4. Optionally, get a [WalletConnect Project ID](https://cloud.reown.com/)

Create the environment file and fill in your values:

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

```bash
VITE_OPENFORT_PUBLISHABLE_KEY=pk_...
VITE_OPENFORT_SHIELD_PUBLISHABLE_KEY=...
VITE_OPENFORT_FEE_SPONSORSHIP_ID=pol_...           # Optional — enables gas sponsorship
VITE_WALLET_CONNECT_PROJECT_ID=...               # Optional — unset skips the WalletConnect connector
```

:::tip
The app validates required environment variables at startup. If any are missing, you'll see an error modal explaining exactly what's needed.
:::

### Understand the provider setup

The app configures Wagmi, React Query, and Openfort providers. The Openfort SDK integrates as a wagmi connector for the Base chain:

```tsx
// src/Providers.tsx
import { embeddedWalletConnector, OpenfortWagmiBridge } from "@openfort/react/wagmi";

const walletConnectProjectId = import.meta.env.VITE_WALLET_CONNECT_PROJECT_ID;
const connectors = [embeddedWalletConnector(), injected()];
if (walletConnectProjectId) connectors.push(walletConnect({ projectId: walletConnectProjectId }));

const wagmiConfig = createConfig({
  chains: [base],
  connectors,
  transports: { [base.id]: http() },
});

<QueryClientProvider client={queryClient}>
  <WagmiProvider config={wagmiConfig}>
    <OpenfortWagmiBridge>
      <OpenfortProvider
        publishableKey={import.meta.env.VITE_OPENFORT_PUBLISHABLE_KEY}
        walletConfig={{
          shieldPublishableKey: import.meta.env.VITE_OPENFORT_SHIELD_PUBLISHABLE_KEY,
          ethereum: {
            ethereumFeeSponsorshipId: import.meta.env.VITE_OPENFORT_FEE_SPONSORSHIP_ID || undefined,
          },
        }}
        uiConfig={{
          walletRecovery: { defaultMethod: RecoveryMethod.PASSKEY },
        }}
      >
        {children}
      </OpenfortProvider>
    </OpenfortWagmiBridge>
  </WagmiProvider>
</QueryClientProvider>
```

`embeddedWalletConnector()` from `@openfort/react/wagmi` adds the Openfort embedded wallet as a wagmi connector. This means you use standard wagmi hooks like `useAccount()`, `useReadContract()`, and `useWriteContract()` — Openfort handles signing behind the scenes.

### Explore the vault deposit flow

The core vault logic lives in the `useVaultOperations` hook. When a user supplies USDC to the Morpho vault, two transactions are needed: an ERC-20 approval followed by a vault deposit.

```typescript
// src/hooks/useVaultOperations.ts
const VAULT_ADDRESS = "0xbeeF010f9cb27031ad51e3333f9aF9C6B1228183";
const SUPPLY_AMOUNT = 100_000n; // 0.1 USDC (6 decimals)

const publicClient = usePublicClient();
const { writeContractAsync } = useWriteContract();

// Step 1: Approve the vault to spend USDC
const approveHash = await writeContractAsync({
  address: USDC_CONTRACT_ADDRESS,
  abi: usdcAbi,
  functionName: 'approve',
  args: [VAULT_ADDRESS, SUPPLY_AMOUNT],
});
await publicClient.waitForTransactionReceipt({ hash: approveHash });

// Step 2: Deposit USDC into the Morpho vault
const depositHash = await writeContractAsync({
  address: VAULT_ADDRESS,
  abi: VAULT_ABI,
  functionName: 'deposit',
  args: [SUPPLY_AMOUNT, address],
});
await publicClient.waitForTransactionReceipt({ hash: depositHash });
```

The Morpho vault follows the ERC-4626 tokenized vault standard — `deposit` accepts an asset amount and mints vault shares to the receiver. The approval step authorizes the vault contract to pull USDC from the user's wallet.

### Run the application

```bash
pnpm dev
```

Open [http://localhost:5173](http://localhost:5173). Click **Connect Wallet** to authenticate with Openfort, then:

1. Fund your embedded wallet with USDC on Base
2. Click **Supply 0.1 USDC to pool** to deposit into the Morpho vault
3. Watch your vault balance and APY update
4. Click **Withdraw all from pool** to redeem your shares for USDC plus earned yield
::::

## How it works

The app interacts with two contracts on Base: the USDC token (`0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`) and the Morpho Blue USDC vault (`0xbeeF010f9cb27031ad51e3333f9aF9C6B1228183`).

**Vault balance** is read in two steps — first getting the user's share balance, then converting shares to the underlying USDC amount:

```typescript
// src/hooks/useVaultOperations.ts
const { data: userShares } = useReadContract({
  address: VAULT_ADDRESS,
  abi: VAULT_ABI,
  functionName: 'balanceOf',
  args: address ? [address] : undefined,
  query: { enabled: !!address },
});

const { data: userVaultAssets } = useReadContract({
  address: VAULT_ADDRESS,
  abi: VAULT_ABI,
  functionName: 'convertToAssets',
  args: userShares ? [userShares] : undefined,
  query: { enabled: !!userShares && userShares > 0n },
});
```

**Vault APY** is fetched from Morpho's public GraphQL API, providing real-time yield data:

```typescript
// src/hooks/useVaultApy.ts
const GET_VAULT_APY = gql`
  query VaultApy($vaultAddress: String!, $chainId: Int!) {
    vaultByAddress(address: $vaultAddress, chainId: $chainId) {
      state { netApy }
    }
  }
`;

const client = new GraphQLClient("https://api.morpho.org/graphql");
const data = await client.request(GET_VAULT_APY, {
  vaultAddress: VAULT_ADDRESS,
  chainId: chainId,
});
const apyPercent = (Number(data.vaultByAddress.state.netApy) * 100).toFixed(2);
```

**Withdrawals** redeem all vault shares for the underlying USDC. The hook passes the user's current share balance (`userShares` from above) to `redeem`:

```typescript
// src/hooks/useVaultOperations.ts
const redeemHash = await writeContractAsync({
  address: VAULT_ADDRESS,
  abi: VAULT_ABI,
  functionName: 'redeem',
  args: [userShares, address, address],
  gas: 500_000n,
});
await publicClient.waitForTransactionReceipt({ hash: redeemHash });
```

After each transaction receipt, the app refetches the wallet balance, share balance, and vault position so the UI stays in sync.
