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

# Yield on Morpho

> Earn yield on USDC by depositing into Morpho Vaults with Magic Embedded Wallets on Base

## Overview

Morpho is a decentralized lending protocol that offers optimized yield through curated vaults. This guide shows how to use Magic's Embedded Wallet to deposit USDC into a Morpho Vault on Base and start earning yield — all from within your application.

Morpho Vaults implement the [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) tokenized vault standard. Users deposit an underlying asset (e.g., USDC), receive vault shares representing their position, and earn yield as the share price appreciates over time.

***

## Prerequisites

Before starting, ensure you have:

1. A Magic **Publishable** API Key from your [Magic Dashboard](https://dashboard.magic.link)
2. A Base RPC endpoint (e.g., from [Alchemy](https://www.alchemy.com/) or [QuickNode](https://www.quicknode.com/))
3. USDC on Base in the user's wallet

### How It Works

The deposit and withdrawal flow uses standard ERC-4626 vault interactions:

1. User authenticates with Magic (Email OTP or Social Login)
2. Magic SDK creates an Embedded Wallet for the user
3. User approves USDC spend for the Morpho Vault
4. User deposits USDC and receives vault shares
5. Yield accrues as the vault's share price increases
6. User redeems shares to withdraw USDC plus earned yield

<Info>
  Morpho Vaults are non-custodial and permissionless. Deposits and withdrawals can happen at any time — there is no lock-up period.
</Info>

***

## Setting Up the Client

Initialize Magic and create a viem wallet client connected to Base.

```typescript TypeScript icon="square-js" theme={null}
import { Magic } from "magic-sdk";
import { createWalletClient, createPublicClient, custom, http } from "viem";
import { base } from "viem/chains";

const magic = new Magic("YOUR_PUBLISHABLE_KEY", {
  network: {
    rpcUrl: "https://base-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_KEY",
    chainId: 8453,
  },
});

// Public client for read operations
const publicClient = createPublicClient({
  chain: base,
  transport: http("https://base-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_KEY"),
});

// Wallet client for transactions (after user authenticates)
const walletClient = createWalletClient({
  chain: base,
  transport: custom(magic.rpcProvider),
});

const [userAddress] = await walletClient.getAddresses();
```

***

## Contract Setup

Define the vault and token addresses along with the minimal ABIs needed for interaction.

```typescript TypeScript icon="square-js" theme={null}
import { type Address, parseUnits, formatUnits } from "viem";

// USDC on Base
const USDC_ADDRESS: Address = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const USDC_DECIMALS = 6;

// Morpho USDC vault on Base (example — query the API for current vaults)
const VAULT_ADDRESS: Address = "0xeE8F4eC5672F09119b96Ab6fB59C27E1b7e44b61";

const erc20Abi = [
  {
    name: "approve",
    type: "function",
    inputs: [
      { name: "spender", type: "address" },
      { name: "value", type: "uint256" },
    ],
    outputs: [{ type: "bool" }],
    stateMutability: "nonpayable",
  },
  {
    name: "allowance",
    type: "function",
    inputs: [
      { name: "owner", type: "address" },
      { name: "spender", type: "address" },
    ],
    outputs: [{ type: "uint256" }],
    stateMutability: "view",
  },
  {
    name: "balanceOf",
    type: "function",
    inputs: [{ name: "account", type: "address" }],
    outputs: [{ type: "uint256" }],
    stateMutability: "view",
  },
] as const;

const vaultAbi = [
  {
    name: "deposit",
    type: "function",
    inputs: [
      { name: "assets", type: "uint256" },
      { name: "receiver", type: "address" },
    ],
    outputs: [{ name: "shares", type: "uint256" }],
    stateMutability: "nonpayable",
  },
  {
    name: "redeem",
    type: "function",
    inputs: [
      { name: "shares", type: "uint256" },
      { name: "receiver", type: "address" },
      { name: "owner", type: "address" },
    ],
    outputs: [{ name: "assets", type: "uint256" }],
    stateMutability: "nonpayable",
  },
  {
    name: "balanceOf",
    type: "function",
    inputs: [{ name: "account", type: "address" }],
    outputs: [{ type: "uint256" }],
    stateMutability: "view",
  },
  {
    name: "previewRedeem",
    type: "function",
    inputs: [{ name: "shares", type: "uint256" }],
    outputs: [{ name: "assets", type: "uint256" }],
    stateMutability: "view",
  },
  {
    name: "convertToAssets",
    type: "function",
    inputs: [{ name: "shares", type: "uint256" }],
    outputs: [{ name: "assets", type: "uint256" }],
    stateMutability: "view",
  },
] as const;
```

<Warning>
  The vault address above is an example. Query the [Morpho API](#fetching-vault-data) or visit [app.morpho.org](https://app.morpho.org) to find the vault that best fits your use case.
</Warning>

***

## Depositing USDC

Depositing requires two transactions: an ERC-20 approval followed by the vault deposit.

<Tabs>
  <Tab title="Approve & Deposit">
    ```typescript TypeScript icon="square-js" theme={null}
    async function depositToVault(amount: string) {
      const depositAmount = parseUnits(amount, USDC_DECIMALS);

      // Check current allowance
      const allowance = await publicClient.readContract({
        address: USDC_ADDRESS,
        abi: erc20Abi,
        functionName: "allowance",
        args: [userAddress, VAULT_ADDRESS],
      });

      // Approve if needed
      if (allowance < depositAmount) {
        const approveHash = await walletClient.writeContract({
          address: USDC_ADDRESS,
          abi: erc20Abi,
          functionName: "approve",
          args: [VAULT_ADDRESS, depositAmount],
          account: userAddress,
        });
        await publicClient.waitForTransactionReceipt({ hash: approveHash });
      }

      // Deposit into vault
      const depositHash = await walletClient.writeContract({
        address: VAULT_ADDRESS,
        abi: vaultAbi,
        functionName: "deposit",
        args: [depositAmount, userAddress],
        account: userAddress,
      });
      const receipt = await publicClient.waitForTransactionReceipt({
        hash: depositHash,
      });

      return receipt.transactionHash;
    }

    // Deposit 100 USDC
    await depositToVault("100");
    ```
  </Tab>

  <Tab title="With Slippage Check">
    ```typescript TypeScript icon="square-js" theme={null}
    async function depositWithSlippageCheck(
      amount: string,
      maxSlippageBps: number = 100 // 1% default
    ) {
      const depositAmount = parseUnits(amount, USDC_DECIMALS);

      // Preview expected shares
      const expectedShares = await publicClient.readContract({
        address: VAULT_ADDRESS,
        abi: [
          {
            name: "previewDeposit",
            type: "function",
            inputs: [{ name: "assets", type: "uint256" }],
            outputs: [{ name: "shares", type: "uint256" }],
            stateMutability: "view",
          },
        ] as const,
        functionName: "previewDeposit",
        args: [depositAmount],
      });

      // Approve and deposit
      const approveHash = await walletClient.writeContract({
        address: USDC_ADDRESS,
        abi: erc20Abi,
        functionName: "approve",
        args: [VAULT_ADDRESS, depositAmount],
        account: userAddress,
      });
      await publicClient.waitForTransactionReceipt({ hash: approveHash });

      const depositHash = await walletClient.writeContract({
        address: VAULT_ADDRESS,
        abi: vaultAbi,
        functionName: "deposit",
        args: [depositAmount, userAddress],
        account: userAddress,
      });
      const receipt = await publicClient.waitForTransactionReceipt({
        hash: depositHash,
      });

      // Verify shares received
      const sharesReceived = await publicClient.readContract({
        address: VAULT_ADDRESS,
        abi: vaultAbi,
        functionName: "balanceOf",
        args: [userAddress],
      });

      const minShares =
        expectedShares - (expectedShares * BigInt(maxSlippageBps)) / 10000n;
      if (sharesReceived < minShares) {
        console.warn("Slippage exceeded expected threshold");
      }

      return receipt.transactionHash;
    }
    ```
  </Tab>
</Tabs>

***

## Checking Position & Yield

Query the user's vault shares and convert them to the underlying USDC value to show earned yield.

```typescript TypeScript icon="square-js" theme={null}
async function getPosition() {
  // Get user's vault shares
  const shares = await publicClient.readContract({
    address: VAULT_ADDRESS,
    abi: vaultAbi,
    functionName: "balanceOf",
    args: [userAddress],
  });

  if (shares === 0n) {
    return { shares: "0", assetsValue: "0" };
  }

  // Convert shares to underlying asset value
  const assetsValue = await publicClient.readContract({
    address: VAULT_ADDRESS,
    abi: vaultAbi,
    functionName: "convertToAssets",
    args: [shares],
  });

  return {
    shares: shares.toString(),
    assetsValue: formatUnits(assetsValue, USDC_DECIMALS),
  };
}

const position = await getPosition();
console.log(`Position: ${position.assetsValue} USDC`);
```

***

## Withdrawing

Redeem vault shares to withdraw USDC plus any earned yield.

<Tabs>
  <Tab title="Full Withdrawal">
    ```typescript TypeScript icon="square-js" theme={null}
    async function withdrawAll() {
      const shares = await publicClient.readContract({
        address: VAULT_ADDRESS,
        abi: vaultAbi,
        functionName: "balanceOf",
        args: [userAddress],
      });

      if (shares === 0n) {
        throw new Error("No position to withdraw");
      }

      // Preview how much USDC we'll receive
      const expectedAssets = await publicClient.readContract({
        address: VAULT_ADDRESS,
        abi: vaultAbi,
        functionName: "previewRedeem",
        args: [shares],
      });

      console.log(`Withdrawing ~${formatUnits(expectedAssets, USDC_DECIMALS)} USDC`);

      const redeemHash = await walletClient.writeContract({
        address: VAULT_ADDRESS,
        abi: vaultAbi,
        functionName: "redeem",
        args: [shares, userAddress, userAddress],
        account: userAddress,
      });
      const receipt = await publicClient.waitForTransactionReceipt({
        hash: redeemHash,
      });

      return receipt.transactionHash;
    }
    ```
  </Tab>

  <Tab title="Partial Withdrawal">
    ```typescript TypeScript icon="square-js" theme={null}
    async function withdrawAmount(amount: string) {
      const withdrawAmount = parseUnits(amount, USDC_DECIMALS);

      // Use withdraw() for exact asset amounts
      const withdrawHash = await walletClient.writeContract({
        address: VAULT_ADDRESS,
        abi: [
          {
            name: "withdraw",
            type: "function",
            inputs: [
              { name: "assets", type: "uint256" },
              { name: "receiver", type: "address" },
              { name: "owner", type: "address" },
            ],
            outputs: [{ name: "shares", type: "uint256" }],
            stateMutability: "nonpayable",
          },
        ] as const,
        functionName: "withdraw",
        args: [withdrawAmount, userAddress, userAddress],
        account: userAddress,
      });
      const receipt = await publicClient.waitForTransactionReceipt({
        hash: withdrawHash,
      });

      return receipt.transactionHash;
    }

    // Withdraw exactly 50 USDC
    await withdrawAmount("50");
    ```
  </Tab>
</Tabs>

***

## Fetching Vault Data

Use Morpho's GraphQL API to query available vaults, APYs, and TVL.

```typescript TypeScript icon="square-js" theme={null}
const MORPHO_API = "https://api.morpho.org/graphql";

async function getVaultInfo(vaultAddress: string, chainId: number = 8453) {
  const query = `
    query GetVault($address: String!, $chainId: Int!) {
      vaultByAddress(address: $address, chainId: $chainId) {
        address
        name
        symbol
        asset { address, symbol, decimals }
        state {
          totalAssetsUsd
          netApy
          apy
          fee
        }
      }
    }
  `;

  const response = await fetch(MORPHO_API, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      query,
      variables: { address: vaultAddress, chainId },
    }),
  });

  const { data } = await response.json();
  return data.vaultByAddress;
}

const vault = await getVaultInfo("0xeE8F4eC5672F09119b96Ab6fB59C27E1b7e44b61");
console.log(`${vault.name} — APY: ${(vault.state.netApy * 100).toFixed(2)}%`);
```

***

## Contract Addresses

Key addresses on Base (chain ID 8453):

```typescript TypeScript icon="square-js" theme={null}
// USDC on Base
export const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
export const USDC_DECIMALS = 6;

// Morpho core contracts on Base
export const MORPHO_VAULT_V2_FACTORY = "0xA1D94F746dEfa1928926b84fB2596c06926C0405";
export const MORPHO_REGISTRY = "0x3696c5eAe4a7Ffd04Ea163564571E9CD8Ed9364e";
```

<Info>
  Individual vault addresses change as new vaults are deployed by curators. Use the [Morpho API](#fetching-vault-data) or browse [app.morpho.org](https://app.morpho.org) to find current vaults.
</Info>

***

## Key Dependencies

| Package                                                | Purpose                                   |
| ------------------------------------------------------ | ----------------------------------------- |
| [`magic-sdk`](https://www.npmjs.com/package/magic-sdk) | Magic authentication and Embedded Wallet  |
| [`viem`](https://viem.sh/)                             | Ethereum client for contract interactions |

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Deposit transaction reverts">
    **Symptoms:** The deposit call fails or reverts on-chain.

    **Solutions:**

    * Ensure the USDC approval was confirmed before depositing
    * Check that the user has sufficient USDC balance on Base
    * Verify the vault address is correct and the vault is active
    * Some vaults have deposit caps — check if the vault is full
  </Accordion>

  <Accordion title="Approval not working">
    **Symptoms:** The approve transaction succeeds but deposit still fails.

    **Solutions:**

    * Confirm the approval amount is greater than or equal to the deposit amount
    * Wait for the approval transaction receipt before calling deposit
    * Check that you're approving the correct token (USDC, not USDbC)
  </Accordion>

  <Accordion title="Shares show zero value">
    **Symptoms:** User has vault shares but `convertToAssets` returns zero.

    **Solutions:**

    * Ensure you're querying the correct vault contract
    * The vault may have just been created — share price starts at 1:1
    * Check the vault on [app.morpho.org](https://app.morpho.org) for current state
  </Accordion>

  <Accordion title="Withdrawal fails with insufficient liquidity">
    **Symptoms:** Redeem or withdraw reverts.

    **Solutions:**

    * The vault's assets may be fully allocated to markets — try a smaller amount
    * Check `previewRedeem()` to see the maximum withdrawable amount
    * Wait for market utilization to decrease, or try again later
  </Accordion>
</AccordionGroup>

***

## Resources

<CardGroup cols={2}>
  <Card title="Morpho Yield Demo" icon="github" href="https://github.com/magiclabs/example-morpho-yield">
    Complete Next.js app with Magic + Morpho on Base
  </Card>

  <Card title="Morpho Documentation" icon="book" href="https://docs.morpho.org/">
    Official Morpho protocol documentation
  </Card>

  <Card title="ERC-4626 Standard" icon="file-lines" href="https://eips.ethereum.org/EIPS/eip-4626">
    The tokenized vault standard used by Morpho Vaults
  </Card>

  <Card title="Morpho App" icon="chart-line" href="https://app.morpho.org/">
    Browse vaults, check APYs, and explore the protocol
  </Card>
</CardGroup>

***
