Skip to main content

Why This Matters

Building DeFi UIs from scratch means weeks of work: integrating wallet connections, managing chain switching, building deposit/withdraw flows, displaying positions and P&L. Our React widgets handle all of this out of the box.

Widget Builder Studio

Build and customize widgets visually with our AI-powered widget builder:

Widget Builder Studio

Create, customize, and preview widgets with natural language. Export production-ready code.

What It Does

Pre-built React components that connect to the Compass API. Users can browse yield opportunities, deposit funds, track positions, and withdraw—all within your app’s UI. You bring your own wallet provider (RainbowKit, Privy, etc.) and we handle the DeFi complexity.

Installation

npm install @compass-labs/widgets @rainbow-me/rainbowkit wagmi viem @tanstack/react-query

Quick Start

There are three parts to setting up the widgets:
  1. Client-side providers - Wrap your app with wallet and Compass providers
  2. Server-side API route - Handle secure API calls and gas sponsorship
  3. Add widgets - Drop in the widget components

Step 1: Client-side Providers

Create a providers file that connects your wallet to the widgets:
// components/providers.tsx
"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { WagmiProvider, http, useAccount, useSignTypedData, useSwitchChain, useDisconnect } from "wagmi";
import { base, mainnet, arbitrum } from "wagmi/chains";
import { getDefaultConfig, RainbowKitProvider, useConnectModal } from "@rainbow-me/rainbowkit";
import { CompassProvider } from "@compass-labs/widgets";
import "@rainbow-me/rainbowkit/styles.css";

const config = getDefaultConfig({
  appName: "My App",
  projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID || "",
  chains: [base, mainnet, arbitrum],
  transports: {
    [base.id]: http(),
    [mainnet.id]: http(),
    [arbitrum.id]: http(),
  },
  ssr: true,
});

const queryClient = new QueryClient();

// Inner component that connects wagmi wallet to CompassProvider
function CompassWithWallet({ children }: { children: React.ReactNode }) {
  const { address, chainId } = useAccount();
  const { signTypedDataAsync } = useSignTypedData();
  const { switchChainAsync } = useSwitchChain();
  const { openConnectModal } = useConnectModal();
  const { disconnectAsync } = useDisconnect();

  return (
    <CompassProvider
      defaultChain="base"
      wallet={{
        address: address ?? null,
        chainId,
        signTypedData: async (data) => {
          return signTypedDataAsync({
            domain: data.domain,
            types: data.types,
            primaryType: data.primaryType,
            message: data.message,
          });
        },
        switchChain: async (targetChainId) => {
          await switchChainAsync({ chainId: targetChainId });
        },
        login: openConnectModal,
        logout: disconnectAsync,
      }}
    >
      {children}
    </CompassProvider>
  );
}

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <RainbowKitProvider>
          <CompassWithWallet>{children}</CompassWithWallet>
        </RainbowKitProvider>
      </QueryClientProvider>
    </WagmiProvider>
  );
}

Step 2: Server-side API Route

The widgets make API calls through your server to keep your API key secure. Create a catch-all API route:
// app/api/compass/[...path]/route.ts
import { createCompassHandler } from "@compass-labs/widgets/server";

const handler = createCompassHandler({
  apiKey: process.env.COMPASS_API_KEY!,
  gasSponsorPrivateKey: process.env.GAS_SPONSOR_PK,
  rpcUrls: {
    ethereum: process.env.ETHEREUM_MAINNET_RPC_URL,
    base: process.env.BASE_MAINNET_RPC_URL,
    arbitrum: process.env.ARBITRUM_MAINNET_RPC_URL,
  },
});

export const GET = handler;
export const POST = handler;

Step 3: Environment Variables

# .env.local

# Client-side (safe to expose - domain-restricted in WalletConnect dashboard)
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your_walletconnect_project_id

# Server-side (kept secret - never exposed to browser)
COMPASS_API_KEY=your_compass_api_key
GAS_SPONSOR_PK=your_gas_sponsor_private_key  # Optional - for gas-sponsored txs

# RPC URLs (required for gas-sponsored transactions)
ETHEREUM_MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/your_key
BASE_MAINNET_RPC_URL=https://base-mainnet.g.alchemy.com/v2/your_key
ARBITRUM_MAINNET_RPC_URL=https://arb-mainnet.g.alchemy.com/v2/your_key
Get a free WalletConnect Project ID at cloud.walletconnect.com. You can restrict which domains can use your project ID in the dashboard.

Step 4: Add Layout and Widgets

// app/layout.tsx
import { Providers } from "@/components/providers";
import "@compass-labs/widgets/styles.css";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
// app/page.tsx
"use client";

import { ConnectButton } from "@rainbow-me/rainbowkit";
import { VaultsList } from "@compass-labs/widgets";

export default function Home() {
  return (
    <main className="min-h-screen p-8">
      <div className="flex justify-between items-center mb-8">
        <h1 className="text-2xl font-bold">Earn</h1>
        <ConnectButton />
      </div>

      <VaultsList
        showApy={true}
        showTvl={true}
        showUserPosition={true}
        showPnL={true}
        showSearch={true}
        showSort={true}
      />
    </main>
  );
}

Step 5: Configure Tailwind (if using)

If you’re using Tailwind CSS, add the widgets package to your content config:
// tailwind.config.ts
const config = {
  content: [
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./node_modules/@compass-labs/widgets/**/*.{js,mjs,ts,tsx}",
  ],
  // ...
};

Available Widgets

CompassEarnWidget

The complete earn experience in a single component. Includes tabbed navigation between Vaults, Aave, Pendle, and Swap.
<CompassEarnWidget
  defaultTab="vaults"
  onTabChange={(tab) => console.log('Tab changed:', tab)}
/>
PropTypeDefaultDescription
defaultTab’vaults’ | ‘aave’ | ‘pendle’ | ‘swap''vaults’Initial active tab
onTabChangefunctionundefinedCallback when tab changes

SwapWidget

Token swap interface with real-time quotes and gas-sponsored execution.
<SwapWidget
  defaultFromToken="ETH"
  defaultToToken="USDC"
  allowedTokens={['USDC', 'ETH', 'WETH', 'WBTC', 'DAI', 'USDT']}
  showReverseButton={true}
  onSwapSuccess={(fromToken, toToken, fromAmount, toAmount, txHash) => {
    console.log('Swap successful:', txHash);
  }}
  onSwapError={(error) => console.error('Swap failed:', error)}
/>
PropTypeDefaultDescription
defaultFromTokenstring’ETH’Default input token
defaultToTokenstring’USDC’Default output token
allowedTokensstring[][‘USDC’, ‘ETH’, ‘WETH’, ‘WBTC’, ‘DAI’, ‘USDT’]Tokens available for swap
showReverseButtonbooleantrueShow button to reverse token pair
showSettingsbooleanfalseShow settings (slippage, etc.)
onSwapSuccessfunctionundefinedCallback after successful swap
onSwapErrorfunctionundefinedCallback on swap error

VaultsList

Displays ERC-4626 yield vaults with APY, TVL, and user positions.
<VaultsList
  showApy={true}
  apyPeriods={['7d', '30d', '90d']}
  showTvl={true}
  showUserPosition={true}
  showPnL={true}
  showHistory={true}
  showSearch={true}
  showSort={true}
  actionMode="modal"
  defaultSort="apy_7d"
  assetFilter={['USDC', 'USDT']}
  onVaultSelect={(vault) => console.log('Selected:', vault)}
  onDeposit={(vault, amount, txHash) => console.log('Deposited:', amount)}
  onWithdraw={(vault, amount, txHash) => console.log('Withdrew:', amount)}
/>
PropTypeDefaultDescription
showApybooleantrueShow APY metrics
apyPeriodsstring[][‘7d’, ‘30d’, ‘90d’]Which APY periods to display
showTvlbooleantrueShow total value locked
showUserPositionbooleantrueShow user’s current position
showPnLbooleantrueShow profit/loss summary
showHistorybooleantrueShow transaction history
showSearchbooleantrueEnable search functionality
showSortbooleantrueEnable sorting options
actionMode’modal’ | ‘inline''modal’How to show deposit/withdraw form
defaultSortstring’apy_7d’Default sort option
assetFilterstring[]undefinedFilter by asset symbols
onVaultSelectfunctionundefinedCallback when vault is clicked
onDepositfunctionundefinedCallback after successful deposit
onWithdrawfunctionundefinedCallback after successful withdraw

AaveMarketsList

Displays Aave V3 lending markets with supply APY and user positions.
<AaveMarketsList
  showApy={true}
  showUserPosition={true}
  showPnL={true}
  showHistory={true}
  showSearch={true}
  actionMode="modal"
  assetFilter={['USDC', 'WETH']}
  onMarketSelect={(market) => console.log('Selected:', market)}
  onSupply={(market, amount, txHash) => console.log('Supplied:', amount)}
  onWithdraw={(market, amount, txHash) => console.log('Withdrew:', amount)}
/>

PendleMarketsList

Displays Pendle fixed-yield markets with APY, expiry dates, and TVL.
<PendleMarketsList
  showApy={true}
  showTvl={true}
  showExpiry={true}
  showUserPosition={true}
  showPnL={true}
  showHistory={true}
  showSearch={true}
  showSort={true}
  actionMode="modal"
  defaultSort="fixed_apy"
  assetFilter={['USDC']}
  onMarketSelect={(market) => console.log('Selected:', market)}
  onDeposit={(market, amount, txHash) => console.log('Deposited:', amount)}
  onWithdraw={(market, amount, txHash) => console.log('Withdrew:', amount)}
/>

Wallet Adapter

The CompassProvider accepts a wallet prop to connect your wallet library. This allows the widgets to:
  • Display the connected wallet address
  • Request transaction signatures
  • Switch chains when needed
interface WalletAdapter {
  // Connected wallet address, or null if not connected
  address: Address | null;

  // Current chain ID (optional - for chain verification)
  chainId?: number;

  // Sign EIP-712 typed data for transactions
  signTypedData: (data: TypedDataToSign) => Promise<string>;

  // Switch to a different chain (optional)
  switchChain?: (chainId: number) => Promise<void>;

  // Trigger wallet connection (optional)
  login?: () => void;

  // Disconnect wallet (optional)
  logout?: () => Promise<void>;
}

Using with Privy

import { usePrivy, useWallets } from '@privy-io/react-auth';

function CompassWithPrivy({ children }: { children: React.ReactNode }) {
  const { login, logout, authenticated } = usePrivy();
  const { wallets } = useWallets();
  const wallet = wallets[0];

  return (
    <CompassProvider
      defaultChain="base"
      wallet={{
        address: wallet?.address ?? null,
        signTypedData: async (data) => {
          const provider = await wallet?.getEthereumProvider();
          return provider.request({
            method: 'eth_signTypedData_v4',
            params: [wallet.address, JSON.stringify(data)],
          });
        },
        login: () => login(),
        logout: () => logout(),
      }}
    >
      {children}
    </CompassProvider>
  );
}

Theming

The widgets support three theming approaches:
  1. Preset name - Use a built-in theme: theme="compass-dark"
  2. Preset with overrides - Start from a preset and customize: theme={{ preset: 'compass-dark', overrides: {...} }}
  3. Complete custom theme - Provide a full theme object

Built-in Theme Presets

PresetDescription
compass-darkDefault dark theme with indigo/purple accents and glassmorphism
compass-lightLight theme with indigo primary and subtle shadows
minimal-darkFlat dark design with white/gray primary, sharp corners, no shadows
minimal-lightFlat light design with black/gray primary, sharp corners
high-contrast-darkAccessible dark theme with blue primary, bold borders, larger text
high-contrast-lightAccessible light theme with bold colors and larger text
<CompassProvider
  theme="compass-dark"
  defaultChain="base"
>
  {/* widgets */}
</CompassProvider>

Custom Theme Overrides

Customize any theme property by providing a preset with overrides:
<CompassProvider
  theme={{
    preset: 'compass-dark',
    overrides: {
      colors: {
        brand: {
          primary: '#8b5cf6',
          primaryHover: '#a78bfa',
        },
      },
    },
  }}
  defaultChain="base"
>
  {/* widgets */}
</CompassProvider>

Colors

Brand Colors (colors.brand)

PropertyDescription
primaryPrimary brand color for buttons, links, and accents
primaryHoverHover state for primary color
primaryMutedMuted/subtle variant of primary (for backgrounds, badges)
primaryTextText color on primary-colored backgrounds
secondarySecondary accent color
secondaryHoverHover state for secondary color

Background Colors (colors.backgrounds)

PropertyDescription
backgroundMain page/widget background
surfaceCard and panel backgrounds
surfaceHoverHover state for surfaces
overlayModal and overlay backdrop color

Text Colors (colors.text)

PropertyDescription
textPrimary text color
textSecondarySecondary/muted text
textTertiaryTertiary/disabled text

Border Colors (colors.borders)

PropertyDescription
borderDefault border color
borderHoverBorder hover state
borderFocusFocus ring color for inputs

Semantic Colors (colors.semantic)

Each semantic color has a DEFAULT (main color) and muted (background/subtle) variant:
PropertyDescription
success.DEFAULTSuccess/positive state color
success.mutedSuccess background/subtle color
warning.DEFAULTWarning/caution state color
warning.mutedWarning background/subtle color
error.DEFAULTError/negative state color
error.mutedError background/subtle color
info.DEFAULTInfo/neutral state color
info.mutedInfo background/subtle color

Typography

Customize fonts and text styles across five scales.

Font Families (typography)

PropertyDescription
fontFamilyPrimary font stack for all text
fontFamilyMonoMonospace font for code and numbers

Text Scales

Each scale (heading, subheading, body, caption, label) supports:
PropertyDescription
fontSizeFont size (e.g., '1.5rem')
lineHeightLine height (e.g., '1.4')
fontWeightFont weight (e.g., '600')
letterSpacingLetter spacing (e.g., '-0.02em') - optional
Example:
<CompassProvider
  theme={{
    preset: 'compass-dark',
    overrides: {
      typography: {
        fontFamily: 'Inter, system-ui, sans-serif',
        fontFamilyMono: 'JetBrains Mono, monospace',
        heading: {
          fontSize: '1.5rem',
          lineHeight: '1.3',
          fontWeight: '700',
        },
        body: {
          fontSize: '0.9375rem',
          lineHeight: '1.5',
          fontWeight: '400',
        },
      },
    },
  }}
  defaultChain="base"
>
  {/* widgets */}
</CompassProvider>

Spacing

Control padding and spacing throughout the widgets.
PropertyDescription
unitBase spacing unit (e.g., '4px')
containerPaddingOuter container padding
cardPaddingPadding inside cards and panels
inputPaddingPadding inside input fields and buttons
Example:
<CompassProvider
  theme={{
    preset: 'compass-dark',
    overrides: {
      spacing: {
        unit: '4px',
        containerPadding: '1.5rem',
        cardPadding: '1.25rem',
        inputPadding: '0.75rem 1rem',
      },
    },
  }}
  defaultChain="base"
>
  {/* widgets */}
</CompassProvider>

Shape

Control border radius and border widths.

Border Radius (shape.borderRadius)

A scale of radius values used throughout the widgets:
PropertyDescription
noneNo radius (e.g., '0')
smSmall radius for subtle rounding
mdMedium radius for inputs and buttons
lgLarge radius for cards
xlExtra large radius for modals
fullFully rounded (e.g., '9999px')

Border Width (shape.borderWidth)

PropertyDescription
borderWidthDefault border width (e.g., '1px')
Example:
<CompassProvider
  theme={{
    preset: 'minimal-dark',
    overrides: {
      shape: {
        borderRadius: {
          none: '0',
          sm: '4px',
          md: '8px',
          lg: '12px',
          xl: '16px',
          full: '9999px',
        },
        borderWidth: '1px',
      },
    },
  }}
  defaultChain="base"
>
  {/* widgets */}
</CompassProvider>
The minimal-dark and minimal-light presets use sharp corners (0 radius). Start from these if you want a flat design.

Effects

Control shadows, blur effects, and transition timing.

Shadows (effects.shadow)

PropertyDescription
smSubtle shadow for hover states
mdMedium shadow for cards
lgLarge shadow for modals and dropdowns

Blur (effects.blur)

PropertyDescription
smSubtle blur for overlays
mdMedium blur for glassmorphism
lgHeavy blur for backgrounds

Transitions (effects.transition)

PropertyDescription
fastQuick interactions (150ms)
normalStandard transitions (200ms)
slowDeliberate animations (300ms)
Example:
<CompassProvider
  theme={{
    preset: 'compass-dark',
    overrides: {
      effects: {
        shadow: {
          sm: '0 1px 2px rgba(0, 0, 0, 0.1)',
          md: '0 4px 12px rgba(0, 0, 0, 0.15)',
          lg: '0 8px 24px rgba(0, 0, 0, 0.2)',
        },
        blur: {
          sm: '4px',
          md: '8px',
          lg: '16px',
        },
        transition: {
          fast: '150ms ease',
          normal: '200ms ease',
          slow: '300ms ease',
        },
      },
    },
  }}
  defaultChain="base"
>
  {/* widgets */}
</CompassProvider>
The minimal-dark and minimal-light presets disable shadows. The compass-dark preset uses glassmorphism with blur effects.

Full Theme Example

Here’s a complete example showing all customizable properties:
<CompassProvider
  theme={{
    preset: 'compass-dark',
    overrides: {
      colors: {
        brand: {
          primary: '#6366f1',
          primaryHover: '#818cf8',
          primaryMuted: 'rgba(99, 102, 241, 0.15)',
          primaryText: '#ffffff',
          secondary: '#8b5cf6',
          secondaryHover: '#a78bfa',
        },
        backgrounds: {
          background: '#050507',
          surface: '#0c0c10',
          surfaceHover: '#12121a',
          overlay: 'rgba(0, 0, 0, 0.7)',
        },
        text: {
          text: '#f4f4f5',
          textSecondary: '#a1a1aa',
          textTertiary: '#71717a',
        },
        borders: {
          border: 'rgba(255, 255, 255, 0.08)',
          borderHover: 'rgba(255, 255, 255, 0.12)',
          borderFocus: '#6366f1',
        },
        semantic: {
          success: { DEFAULT: '#10b981', muted: 'rgba(16, 185, 129, 0.15)' },
          warning: { DEFAULT: '#f59e0b', muted: 'rgba(245, 158, 11, 0.15)' },
          error: { DEFAULT: '#ef4444', muted: 'rgba(239, 68, 68, 0.15)' },
          info: { DEFAULT: '#6366f1', muted: 'rgba(99, 102, 241, 0.15)' },
        },
      },
      typography: {
        fontFamily: 'Inter, system-ui, sans-serif',
        fontFamilyMono: 'JetBrains Mono, monospace',
        heading: { fontSize: '1.5rem', lineHeight: '1.3', fontWeight: '700' },
        subheading: { fontSize: '1.125rem', lineHeight: '1.4', fontWeight: '600' },
        body: { fontSize: '0.9375rem', lineHeight: '1.5', fontWeight: '400' },
        caption: { fontSize: '0.8125rem', lineHeight: '1.4', fontWeight: '400' },
        label: { fontSize: '0.75rem', lineHeight: '1.3', fontWeight: '500' },
      },
      spacing: {
        unit: '4px',
        containerPadding: '1.5rem',
        cardPadding: '1.25rem',
        inputPadding: '0.75rem 1rem',
      },
      shape: {
        borderRadius: {
          none: '0',
          sm: '6px',
          md: '8px',
          lg: '12px',
          xl: '16px',
          full: '9999px',
        },
        borderWidth: '1px',
      },
      effects: {
        shadow: {
          sm: '0 1px 2px rgba(0, 0, 0, 0.1)',
          md: '0 4px 12px rgba(0, 0, 0, 0.15)',
          lg: '0 8px 24px rgba(0, 0, 0, 0.2)',
        },
        blur: { sm: '4px', md: '8px', lg: '16px' },
        transition: { fast: '150ms ease', normal: '200ms ease', slow: '300ms ease' },
      },
    },
  }}
  defaultChain="base"
>
  {/* widgets */}
</CompassProvider>
You only need to override the properties you want to change. All other values inherit from the selected preset.

Supported Chains

The widgets support these chains:
ChainID
Ethereumethereum
Basebase
Arbitrumarbitrum
Set the default chain in the provider:
<CompassProvider defaultChain="base">
Users can switch chains using the built-in chain switcher component included in each widget.

Gas Sponsorship

To sponsor gas for your users, you need:
  1. A gas sponsor private key with ETH on each chain
  2. RPC URLs configured in the API route handler
The widgets will automatically use sponsored transactions when gasSponsorPrivateKey is configured. See Gas Sponsorship for more details.

Use Cases

Yield Aggregator: Build a savings product that shows the best yields across Aave, Morpho vaults, and Pendle. Users deposit in one click, you earn embedded fees. Wallet Integration: Add an “Earn” tab to your wallet app. Users see their positions, P&L, and can manage deposits without leaving your app. Treasury Dashboard: Let businesses earn on idle funds. Display positions, historical transactions, and yield performance in a CFO-friendly interface. DEX Integration: Add the SwapWidget to let users swap tokens directly within your app with gas-sponsored transactions.

Next Steps