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

# EarnWidget Integration

> Integrate Pods EarnWidget for customer-facing yield flows

Use `EarnWidget` when you want Pods yield UX in your app while keeping wallet custody and transaction execution in your infrastructure.

## What You Own vs What Pods Owns

| Area                                                                                           | Owner        |
| ---------------------------------------------------------------------------------------------- | ------------ |
| Wallet connection, signatures, tx submission                                                   | Your app     |
| Strategy data, quote/bytecode generation, protocol routing                                     | Pods API     |
| UI state and yield screens (overview, details, deposit, withdraw, investment details, history) | `EarnWidget` |

## Verified integration contract

Install the current widget package set:

```bash theme={null}
pnpm add pods-sdk@0.2.87 @deframe-sdk/components@0.1.91 @reduxjs/toolkit@^2 react-redux@^9 redux@^5
```

<Note>
  Use the same SDK package for `DeframeProvider`, `EarnWidget`, and `SwapWidget`. The Pods Wallet-style widget stack imports them from `pods-sdk` and uses `@deframe-sdk/components` for the shared visual system and swap view override.
</Note>

For Privy smart-wallet hosts, add the wallet stack from the [Privy Integration](/external-integrations/privy-integration) guide.

## Styles and Theme

Import the published component stylesheet in your app entrypoint before your own app CSS.

For Next.js App Router:

```tsx theme={null}
// app/layout.tsx
import '@deframe-sdk/components/styles.css'
import './globals.css'
```

For Vite React:

```tsx theme={null}
// src/main.tsx
import '@deframe-sdk/components/styles.css'
import './index.css'
```

Treat `.deframe-widget` as the style boundary owned by the SDK:

* Scope host app styles to your own containers.
* Avoid global resets like `* { ... }` or broad element resets that can leak into the widget.
* Use `DeframeProvider.config.theme` as the source of truth for widget theming.
* Use widget-scoped CSS variables only as host-side branding extensions.
* Do not copy internal SDK view overrides into customer integrations unless you own and test those override components.

```css theme={null}
.pods-host-shell {
  width: min(1180px, 100%);
  margin: 0 auto;
  overflow-x: hidden;
}

.deframe-widget-slot {
  width: min(760px, 100%);
  margin: 0 auto;
  overflow-x: hidden;
}

.pods-host-shell .deframe-widget {
  --deframe-widget-color-brand-primary: #2dd881;
  --deframe-widget-color-bg-primary: #0b0f14;
  --deframe-widget-color-bg-raised: #161e29;
  --deframe-widget-color-text-primary: #f6f8fb;
  --deframe-widget-color-text-secondary: #aab6c5;
  --deframe-widget-size-radius-lg: 18px;
}
```

## Environment Variables

Canonical client env names for widget hosts:

```bash theme={null}
NEXT_PUBLIC_PODS_API_URL=https://api.pods.finance
NEXT_PUBLIC_PODS_API_KEY=your_api_key
NEXT_PUBLIC_PODS_WEBSOCKET_URL=wss://api.pods.finance/updates
```

<Tip>
  If you proxy Pods API calls through your own backend, pass the proxy route as `NEXT_PUBLIC_PODS_API_URL` and inject the API key server-side. The browser still needs `NEXT_PUBLIC_PODS_WEBSOCKET_URL` when realtime action updates should connect directly.
</Tip>

If your host uses Vite and you want to keep these same names, configure:

```ts theme={null}
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  envPrefix: ['VITE_', 'NEXT_PUBLIC_'],
  resolve: {
    alias: {
      'next/link': fileURLToPath(new URL('./src/shims/next-link.tsx', import.meta.url)),
    },
  },
})
```

For non-Next hosts, add the shim used by that alias:

```tsx theme={null}
import type { AnchorHTMLAttributes, PropsWithChildren } from 'react'

type NextLinkShimProps = PropsWithChildren<
  AnchorHTMLAttributes<HTMLAnchorElement> & { href: string }
>

export default function Link({ href, children, ...props }: NextLinkShimProps) {
  return (
    <a href={href} {...props}>
      {children}
    </a>
  )
}
```

## Step 1: Implement `processBytecode`

`processBytecode` is the host bridge between widget actions and wallet execution. **Batch all legs
on the same `chainId` into one atomic wallet submission** — see
[processBytecode Contract](/widgets/process-bytecode) and
[Executing Bytecode](/getting-started/executing-bytecode).

```tsx theme={null}
import type { UpdateTxStatus } from 'pods-sdk'

// Group by chainId → sendBatchedTransactionWithWallet({ chainId, legs }) per chain
// Emit: HOST_ACK → SIGNATURE_PROMPTED → TX_SUBMITTED → TX_CONFIRMED → TX_FINALIZED
// See widgets/process-bytecode.mdx for the full minimal implementation.
```

For the complete transaction lifecycle, see [processBytecode Contract](/widgets/process-bytecode).

## Step 2: Create a Shared Provider Wrapper

Use a single host wrapper for `EarnWidget`, `SwapWidget`, and any SDK hooks that need provider context.

```tsx theme={null}
'use client'

import { PodsSwapFormView } from '@deframe-sdk/components'
import { DeframeProvider, useDeframe } from 'pods-sdk'
import { useEffect, useMemo, useRef, type ReactNode } from 'react'
import { useRouter } from 'next/navigation'
import { processBytecode } from './processBytecode'

type DeframeWidgetProviderProps = {
  children: ReactNode
  walletAddress?: string
  userId?: string
}

type NavigateToSwapEvent = {
  toTokenAddress: string
  toChainId: number
  fromTokenAddress?: string
  fromChainId?: number
}

type RouteChangeRequestEvent = {
  route: string
}

function DeframeEventBridge() {
  const router = useRouter()
  const { addEventListener, removeEventListener } = useDeframe()
  const subscribedRef = useRef(false)

  useEffect(() => {
    if (subscribedRef.current) return
    subscribedRef.current = true

    const handleNavigateToSwap = (eventData: unknown) => {
      const data = eventData as NavigateToSwapEvent
      const params = new URLSearchParams()
      params.set('toTokenAddress', data.toTokenAddress)
      params.set('toChainId', String(data.toChainId))
      if (data.fromTokenAddress) params.set('fromTokenAddress', data.fromTokenAddress)
      if (data.fromChainId) params.set('fromChainId', String(data.fromChainId))
      router.push(`/dashboard/swap?${params.toString()}`)
    }

    const handleRouteChangeRequest = (eventData: unknown) => {
      const data = eventData as RouteChangeRequestEvent
      if (data.route === 'history') router.push('/dashboard/history')
      if (data.route === 'wallet') router.push('/dashboard')
      if (data.route === 'swap') router.push('/dashboard/swap')
      if (data.route === 'earn') router.push('/dashboard/earn')
    }

    addEventListener('navigateToSwap', handleNavigateToSwap)
    addEventListener('routeChangeRequest', handleRouteChangeRequest)

    return () => {
      removeEventListener('navigateToSwap', handleNavigateToSwap)
      removeEventListener('routeChangeRequest', handleRouteChangeRequest)
      subscribedRef.current = false
    }
  }, [addEventListener, removeEventListener, router])

  return null
}

export function DeframeWidgetProvider({
  children,
  walletAddress,
  userId,
}: DeframeWidgetProviderProps) {
  const podsApiUrl = process.env.NEXT_PUBLIC_PODS_API_URL
  const podsApiKey = process.env.NEXT_PUBLIC_PODS_API_KEY
  const podsWebSocketUrl = process.env.NEXT_PUBLIC_PODS_WEBSOCKET_URL

  // Next.js client example. In Vite, replace process.env with import.meta.env.
  const config = useMemo(() => ({
    // pods-sdk@0.2.87 still expects these provider field names.
    DEFRAME_API_URL: podsApiUrl,
    DEFRAME_API_KEY: podsApiKey,
    DEFRAME_WEBSOCKET_URL: podsWebSocketUrl,
    walletAddress,
    userId,
    language: 'EN' as const,
    globalCurrency: 'USD' as const,
    globalCurrencyExchangeRate: 1,
    enableCrossChainInvestments: true,
    components: {
      SwapFormView: PodsSwapFormView,
    },
    debug: process.env.NODE_ENV === 'development',
    theme: {
      mode: 'dark' as const,
      preset: 'default' as const,
      overrides: {
        dark: {
          colors: {
            brandPrimary: '#1FC16B',
            brandSecondary: '#A9ABF7',
            bgDefault: '#121212',
            bgSubtle: '#1E1E1E',
            bgMuted: '#2C2C2C',
            bgRaised: '#232323',
            textPrimary: '#FFFFFF',
            textSecondary: '#99A0AE',
            textDisabled: '#525866',
            stateSuccess: '#2BA176',
            stateWarning: '#F6A700',
            stateError: '#FF4D4F',
            stateInfo: '#58B4FF',
          },
        },
      },
    },
  }), [podsApiKey, podsApiUrl, podsWebSocketUrl, walletAddress, userId])

  if (!walletAddress) {
    return <div>Connect a wallet before loading Pods widgets.</div>
  }

  return (
    <DeframeProvider config={config} processBytecode={processBytecode}>
      <DeframeEventBridge />
      {children}
    </DeframeProvider>
  )
}
```

## Step 3: Mount the Invest View

The Pods Wallet-style Invest page lets the overview use a wider layout and narrows detail or transaction routes.

```tsx theme={null}
'use client'

import { EarnWidget } from 'pods-sdk'
import { useState } from 'react'
import { DeframeWidgetProvider } from './DeframeWidgetProvider'

export function InvestPage({
  walletAddress,
  userId,
}: {
  walletAddress: string
  userId?: string
}) {
  const [routeName, setRouteName] = useState('overview')
  const isOverview = routeName === 'overview'

  return (
    <div className={`pods-host-shell ${isOverview ? 'pods-host-shell-wide' : ''}`}>
      <div className={isOverview ? 'pods-invest-overview-slot' : 'deframe-widget-slot'}>
        <DeframeWidgetProvider walletAddress={walletAddress} userId={userId}>
          <EarnWidget autoHeight onRouteChange={setRouteName} />
        </DeframeWidgetProvider>
      </div>
    </div>
  )
}
```

```css theme={null}
.pods-host-shell-wide {
  width: min(1400px, 100%);
}

.pods-invest-overview-slot {
  width: min(1400px, 100%);
  margin: 0 auto;
  overflow-x: hidden;
}
```

## Route Names Exposed by `onRouteChange`

Common values: `overview`, `details`, `deposit`, `withdraw`, `investment-details`, `history`, `history-deposit-details`, `history-withdraw-details`.

Use these to adapt host shell layout, breadcrumbs, and analytics tags.

## Host Env and Provider Config Reference

| Field                            | Purpose                                                                                                 |
| -------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `NEXT_PUBLIC_PODS_API_URL`       | Pods API base URL or your host proxy route.                                                             |
| `NEXT_PUBLIC_PODS_API_KEY`       | API key when calling Pods directly from the browser. Omit it if your proxy injects the key server-side. |
| `NEXT_PUBLIC_PODS_WEBSOCKET_URL` | Realtime action update endpoint.                                                                        |
| `walletAddress`                  | Connected wallet or smart-wallet address used for balances, positions, and history.                     |
| `userId`                         | Optional host user ID for analytics/reconciliation.                                                     |
| `language`                       | `EN` or `PT`.                                                                                           |
| `globalCurrency`                 | `USD` or `BRL`.                                                                                         |
| `globalCurrencyExchangeRate`     | USD-to-selected-currency rate. Use `1` for USD.                                                         |
| `enableCrossChainInvestments`    | Enables network selection for cross-chain deposit/withdraw flows.                                       |
| `hiddenChainIds`                 | Optional chain IDs to hide from SDK network selectors.                                                  |
| `chains`                         | Optional host-owned chain list with `id`, `name`, `iconUrl`, `explorerTxUrl`, and optional `swapSlug`.  |
| `strategyAssetWhitelists`        | Optional strategy-specific token/network allowlists.                                                    |
| `theme`                          | Preferred theme API: `mode`, `preset`, and `overrides`.                                                 |
| `debug`                          | Enables SDK debug logging in development.                                                               |

`pods-sdk@0.2.87` still exposes the provider fields as `DEFRAME_API_URL`, `DEFRAME_API_KEY`, and `DEFRAME_WEBSOCKET_URL`. Keep the host env names branded as `PODS_*`, then map them into those provider fields in the wrapper.

## API Endpoints Used in Earn Flows

* `GET /strategies` for strategy discovery
* `GET /strategies/{strategyId}` for strategy details
* `GET /strategies/{strategyId}/bytecode` for deposit/withdraw transaction data
* `GET /wallets/{wallet}` for portfolio state
* `GET /wallets/{wallet}/history/{strategyId}` for investment activity

Guides:

* [Check Protocol Info](/guides/yield/check-protocol-info)
* [Deposit to Yield](/guides/yield/deposit)
* [Check Positions](/guides/yield/check-positions)
* [Withdraw from Yield](/guides/yield/withdraw)

## Customer Integration Checklist

1. Install `pods-sdk`, `@deframe-sdk/components`, and Redux peer packages.
2. Import `@deframe-sdk/components/styles.css` before your host CSS.
3. Pass the connected wallet or smart-wallet address into `DeframeProvider.config.walletAddress`.
4. Implement `processBytecode` with your wallet stack.
5. Emit all required tx lifecycle statuses back to the widget.
6. Configure `NEXT_PUBLIC_PODS_WEBSOCKET_URL` so the widget can recover realtime cross-chain state.
7. Keep host CSS scoped to containers around `.deframe-widget`.
8. Track completed transactions in your own ledger for reconciliation/support.
