Skip to main content

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 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
  2. A Base RPC endpoint (e.g., from Alchemy or QuickNode)
  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
Morpho Vaults are non-custodial and permissionless. Deposits and withdrawals can happen at any time — there is no lock-up period.

Setting Up the Client

Initialize Magic and create a viem wallet client connected to Base.
TypeScript
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
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;
The vault address above is an example. Query the Morpho API or visit app.morpho.org to find the vault that best fits your use case.

Depositing USDC

Depositing requires two transactions: an ERC-20 approval followed by the vault deposit.
TypeScript
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");

Checking Position & Yield

Query the user’s vault shares and convert them to the underlying USDC value to show earned yield.
TypeScript
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.
TypeScript
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;
}

Fetching Vault Data

Use Morpho’s GraphQL API to query available vaults, APYs, and TVL.
TypeScript
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
// 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";
Individual vault addresses change as new vaults are deployed by curators. Use the Morpho API or browse app.morpho.org to find current vaults.

Key Dependencies

PackagePurpose
magic-sdkMagic authentication and Embedded Wallet
viemEthereum client for contract interactions

Troubleshooting

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
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)
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 for current state
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

Resources

Morpho Yield Demo

Complete Next.js app with Magic + Morpho on Base

Morpho Documentation

Official Morpho protocol documentation

ERC-4626 Standard

The tokenized vault standard used by Morpho Vaults

Morpho App

Browse vaults, check APYs, and explore the protocol