Use EarnWidget when you want Deframe yield UX in your app while keeping wallet custody and transaction execution in your infrastructure.
What You Own vs What Deframe Owns
| Area | Owner |
|---|
| Wallet connection, signatures, tx submission | Your app |
| Strategy data, quote/bytecode generation, protocol routing | Deframe API |
| UI state and yield screens (overview, details, deposit, withdraw) | EarnWidget |
Verified integration contract
Current verified package set:
pnpm add deframe-sdk@0.2.0 @deframe-sdk/components@0.1.23 @privy-io/react-auth@2.25.0 permissionless@0.2.57 viem@2.37.7 @reduxjs/toolkit@^2 react-redux@^9 redux@^5
Do not install @privy-io/react-auth/smart-wallets as a separate dependency. Import it from the @privy-io/react-auth package subpath.
Styles and Theme
Import the published component stylesheet in the host entrypoint before your app CSS:
import '@deframe-sdk/components/styles.css'
import './index.css'
This is required for the full widget visual system. The SDK injects part of the widget styling, but the component-library stylesheet still needs to be present in the host.
Treat .deframe-widget as a style boundary owned by the SDK:
- Avoid global resets like
* { ... } or broad element resets that can leak into the widget.
- Scope host app styles to your own containers.
- Use
DeframeProvider.config.theme as the source of truth for widget theming.
- Use widget-scoped CSS variables only as host-side branding extensions.
.host-shell {
/* Host app styles */
}
.widget-slot {
/* Layout/spacing around widget */
}
.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:
NEXT_PUBLIC_DEFRAME_URL=https://api.deframe.io
NEXT_PUBLIC_DEFRAME_API_KEY=your_api_key
NEXT_PUBLIC_DEFRAME_WEBSOCKET_URL=wss://api.deframe.io/updates
NEXT_PUBLIC_PRIVY_APP_ID=your_privy_app_id
If your host uses Vite and you want to keep these same names, configure:
import path from 'node:path'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
envPrefix: ['VITE_', 'NEXT_PUBLIC_'],
resolve: {
alias: {
'next/link': path.resolve(__dirname, 'src/shims/next-link.tsx'),
},
},
})
For non-Next hosts, add the shim used by that alias:
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.
import type { UpdateTxStatus } from 'deframe-sdk'
type TxIntent = {
clientTxId: string
bytecodes: Array<{
chainId?: number
to: string
data: string
value: string
gasLimit?: string
}>
}
export async function processBytecode(
payload: TxIntent,
ctx: { updateTxStatus: UpdateTxStatus }
) {
const { clientTxId, bytecodes } = payload
try {
ctx.updateTxStatus({ type: 'HOST_ACK', clientTxId })
for (const tx of bytecodes) {
ctx.updateTxStatus({ type: 'SIGNATURE_PROMPTED', clientTxId })
const txHash = await sendTransactionWithYourWallet(tx)
ctx.updateTxStatus({ type: 'TX_SUBMITTED', clientTxId, chainId: tx.chainId, txHash })
const receipt = await waitForReceipt(txHash)
if (receipt.status !== 1) {
ctx.updateTxStatus({ type: 'TX_REVERTED', clientTxId, txHash, reason: 'Execution reverted' })
return
}
ctx.updateTxStatus({ type: 'TX_CONFIRMED', clientTxId, txHash, blockNumber: receipt.blockNumber })
}
ctx.updateTxStatus({ type: 'TX_FINALIZED', clientTxId })
} catch (error: any) {
if (error?.code === 4001 || error?.code === 'ACTION_REJECTED') {
ctx.updateTxStatus({ type: 'SIGNATURE_DECLINED', clientTxId })
return
}
ctx.updateTxStatus({
type: 'SIGNATURE_ERROR',
clientTxId,
code: String(error?.code ?? 'UNKNOWN_ERROR'),
message: String(error?.message ?? 'Failed to process transaction')
})
}
}
For the complete Privy + smart-wallet host flow, see:
import { DeframeProvider, EarnWidget } from 'deframe-sdk'
import { useState } from 'react'
export function EarnWidgetScreen({ walletAddress }: { walletAddress: string }) {
const [routeName, setRouteName] = useState('overview')
return (
<DeframeProvider
config={{
DEFRAME_API_URL: process.env.NEXT_PUBLIC_DEFRAME_URL,
DEFRAME_API_KEY: process.env.NEXT_PUBLIC_DEFRAME_API_KEY,
DEFRAME_WEBSOCKET_URL: process.env.NEXT_PUBLIC_DEFRAME_WEBSOCKET_URL,
walletAddress,
language: 'EN',
enableCrossChainInvestments: true,
theme: {
mode: 'dark',
preset: 'default',
overrides: {
dark: {
colors: {
brandPrimary: '#2DD881',
brandSecondary: '#8EF0B0',
bgDefault: '#0B0F14',
bgSubtle: '#121820',
bgMuted: '#1B2330',
bgRaised: '#161E29',
textPrimary: '#F6F8FB',
textSecondary: '#AAB6C5',
textDisabled: '#64748B',
}
}
}
}
}}
processBytecode={processBytecode}
>
<div className="host-shell">
<section className="widget-slot">
<EarnWidget autoHeight onRouteChange={setRouteName} />
</section>
</div>
</DeframeProvider>
)
}
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.
API Endpoints Used in Earn Flows
GET /strategies for strategy discovery
GET /strategies/{strategyId}/bytecode for deposit/withdraw transaction data
GET /wallets/{wallet} for portfolio state
GET /wallets/{wallet}/history/{strategyId} for investment activity
Guides:
Customer Integration Checklist
- Create customer + API key and store mapping in your backend.
- Pass the connected wallet address into
DeframeProvider.config.walletAddress.
- Implement
processBytecode with your wallet stack.
- Emit all required tx lifecycle statuses back to the widget.
- Configure
DEFRAME_WEBSOCKET_URL so the widget can recover realtime cross-chain state.
- Track completed transactions in your own ledger for reconciliation/support.