@metalend/widget
v2.0.0
Published
MetaLend widget - A reusable React component for integrating MetaLend DeFi functionality into any application.
Readme
MetaLend Widget
A drop-in React component for integrating MetaLend DeFi yield optimization rebalancer into any application. The widget provides a complete interface for depositing, withdrawing, and rebalancing positions across multiple chains and lending protocols — powered by the MetaLend Rebalancing API.
Access
MetaLendWidget component requires API key which you can obtain in MetaLend developer dashboard.
Features
- Single-Component Integration — one
<MetaLendWidget />with an API key and connectors - Multi-Chain — Ethereum, Base, Polygon, Arbitrum, Optimism, Avalanche, Linea, Arc
- Multi-Token — USDC, USDT, RLUSD, MUSD, PYUSD, USDG, USDE
- Multi-Protocol — Aave, Morpho, Euler with automatic rebalancing
- Project Settings — supported tokens, chains, and protocols are configured per API key via the MetaLend dashboard; no prop-level filtering needed
- Flexible Wallet Support — works with any wagmi-compatible connector (MetaMask, Coinbase Wallet, WalletConnect, Privy, etc.)
- RPC Failover — multiple RPC endpoints per chain with automatic fallback
- Coinbase Miniapp Mode — smart wallet + paymaster-sponsored transactions out of the box
- Analytics Ready — plug in any analytics SDK via the
TrackingProviderinterface
Installation
npm install @metalend/widgetor with yarn:
yarn add @metalend/widgetPeer Dependencies
These must be installed in your application:
| Package | Version |
| ----------------------- | -------------------- |
| react | ^18.0.0 || ^19.0.0 |
| react-dom | ^18.0.0 || ^19.0.0 |
| wagmi | ^2.19.5 |
| viem | ^2.51.0 |
| @tanstack/react-query | ^5.90.16 |
npm install react react-dom wagmi@2 viem @tanstack/react-queryQuick Start
import { MetaLendWidget } from '@metalend/widget'
import '@metalend/widget/style.css'
function App() {
return <MetaLendWidget apiKey="your-metalend-api-key" />
}That's it. The widget handles wallet connection, chain management, deposits, withdrawals, rebalancer configuration, and rewards — all scoped to the tokens, chains, and protocols enabled for your API key in the MetaLend dashboard.
With no connectors prop the widget owns wagmi, uses its built-in connectors, and shows the
Welcome → Connect → SIWE flow. As soon as you pass your own connectors, that flow is off by
default — add showWelcomeAuthFlow to keep it, or the widget renders without any way to sign in.
showWalletConnect is not a substitute: it renders the wallet button (connect, address,
disconnect) but never runs the SIWE challenge, so the user stays unauthenticated and balances,
deposits and withdrawals remain unavailable.
import { injected } from 'wagmi/connectors'
;<MetaLendWidget apiKey="your-metalend-api-key" connectors={[injected()]} showWelcomeAuthFlow={true} />Use Cases
1. Full DApp Integration (with Privy) - injecting existing wagmi context
For DApps that already manage their own wagmi provider (f.e. via Privy):
import { MetaLendWidget } from '@metalend/widget'
import '@metalend/widget/style.css'
import { PrivyProvider } from '@privy-io/react-auth'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { WagmiProvider, createConfig, http } from 'wagmi'
import { arbitrum, base, mainnet, optimism, polygon } from 'wagmi/chains'
const wagmiConfig = createConfig({
chains: [base, mainnet, arbitrum, optimism, polygon],
transports: {
[base.id]: http(),
[mainnet.id]: http(),
[arbitrum.id]: http(),
[optimism.id]: http(),
[polygon.id]: http(),
},
})
const queryClient = new QueryClient()
function App() {
return (
<PrivyProvider appId="your-privy-app-id">
<QueryClientProvider client={queryClient}>
<WagmiProvider config={wagmiConfig}>
<MetaLendWidget apiKey="your-metalend-api-key" useExternalWagmiProvider={true} />
</WagmiProvider>
</QueryClientProvider>
</PrivyProvider>
)
}Key points:
- Set
useExternalWagmiProvider={true}so the widget uses your existing wagmi context - All wallet connectors configured in your wagmi config work automatically
- Privy handles the wallet connection UI
2. Coinbase Miniapp
For miniapps running inside the Coinbase Wallet with sponsored transactions:
import { MetaLendWidget } from '@metalend/widget'
import '@metalend/widget/style.css'
import { coinbaseWallet } from 'wagmi/connectors'
const MINIAPP_CONNECTORS = [
coinbaseWallet({
appName: 'Your Miniapp',
preference: { options: 'smartWalletOnly' },
}),
]
function MiniApp() {
return (
<MetaLendWidget
apiKey="your-metalend-api-key"
connectors={MINIAPP_CONNECTORS}
showWelcomeAuthFlow={true}
showWalletConnect={true}
paymasterUrl="https://api.developer.coinbase.com/rpc/v1/base/YOUR_KEY"
/>
)
}Key points:
- Pass the
coinbaseWalletconnector viaconnectors— the widget builds its own wagmi config around it, so no parentWagmiProvideris needed - If your app already owns a
WagmiProvider, setuseExternalWagmiProvider={true}and wrap the widget in it; without a parent provider that flag alone leaves the widget with no wagmi context - The widget auto-detects the Coinbase Smart Wallet at runtime (on-chain ERC-1271 bytecode probe, with a
coinbaseWalletSDKconnector fallback) and uses the correct signing path (plain hash vs{ raw: hash }) — no extra prop needed paymasterUrlenables gasless (sponsored) transactions- Typically scoped to a single chain and token via project settings
- URL: portal.cdp.coinbase.com
3. Default Connectors (Zero Config)
If you omit the connectors prop and useExternalWagmiProvider is false, the widget uses sensible defaults:
[injected, baseAccount]— suitable for standard web integrations.- For specialized contexts (e.g. Coinbase miniapp with
coinbaseWallet), pass your ownconnectorsor useuseExternalWagmiProviderwith your own Wagmi config.
<MetaLendWidget apiKey="your-metalend-api-key" />Wagmi, welcome flow, and wallet UI
Pick useExternalWagmiProvider, showWelcomeAuthFlow, and optional connectors together. The table is about who owns WagmiProvider and whether the widget shows the root Welcome → Connect → SIWE flow.
| useExternalWagmiProvider | showWelcomeAuthFlow | Who provides WagmiProvider | Root welcome + connector + SIWE |
| -------------------------- | --------------------- | ------------------------------------ | -------------------------------------------------------------------- |
| false | false | Widget | Yes if you use default connectors (no connectors prop, or empty) |
| false | true | Widget | Yes (always, even with custom connectors) |
| true | false | Your app (required) | No — your wagmi handles connection |
| true | true | Widget (no parent wrap required) | Yes — widget builds wagmi from connectors or built-in defaults |
showWalletConnect (top-right wallet control):
- If
showWalletConnectis off: no built-in wallet chrome. - If on and welcome-style gate is active (
default connectorspath orshowWelcomeAuthFlow): the wallet button appears only after the user is authenticated (welcome + modals cover pre-auth). - If on with
useExternalWagmiProvideronly (showWelcomeAuthFlowoff): the wallet button is shown whenevershowWalletConnectis true (your app owns connection UX). The widget runs no SIWE challenge in this mode, so your app must supply the MetaLend session itself — otherwise enableshowWelcomeAuthFlow.
Note: If you wrap your own WagmiProvider and set useExternalWagmiProvider={true} with showWelcomeAuthFlow={true}, the widget subtree uses the widget’s inner wagmi config. Prefer one owner: either parent-only external wagmi, or widget-owned welcome mode without a duplicate parent WagmiProvider around the widget.
API Reference
<MetaLendWidget />
| Prop | Type | Required | Default | Description |
| -------------------------- | --------------------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| apiKey | string | Yes | — | MetaLend API key. Determines project settings (tokens, chains, protocols). |
| connectors | CreateConnectorFn[] | No | Built-in | Wagmi connectors. Omitted or empty with widget-owned wagmi → [injected, baseAccount]. With useExternalWagmiProvider alone, parent config defines connectors. |
| useExternalWagmiProvider | boolean | No | false | Use parent WagmiProvider instead of creating one — unless showWelcomeAuthFlow is true, then the widget still creates its own provider. |
| showWelcomeAuthFlow | boolean | No | false | Root Welcome → Connect → SIWE flow. With external wagmi flag, still mounts internal WagmiProvider so no parent wagmi wrapper is required. |
| showWalletConnect | boolean | No | false | Show the built-in wallet button (connect, address, disconnect). Wallet UI only — it does not run SIWE, so it cannot replace showWelcomeAuthFlow. |
| customRpcUrls | CustomRpcUrls | No | Built-in | Custom RPC endpoints per chain name (see below). |
| paymasterUrl | string | No | — | Paymaster URL for sponsored (gasless) transactions. Typically used by parents integrating a Coinbase Smart Wallet / miniapp. |
| trackingProvider | TrackingProvider | No | — | Analytics integration (see below). |
| theme | WidgetTheme | No | MetaLend | Theme overrides — merged with the default theme. |
| className | string | No | '' | CSS class name for the widget container. |
CustomRpcUrls
type ChainKind = 'base' | 'ethereum' | 'polygon' | 'arbitrum' | 'optimism' | 'avalanche' | 'linea' | 'arc'
type CustomRpcUrls = Partial<Record<ChainKind, string[]>>Provide an array of RPC URLs per chain. The widget ensures at least 3 endpoints per chain by merging your URLs with built-in defaults:
<MetaLendWidget
apiKey="your-api-key"
customRpcUrls={{
base: ['https://my-base-rpc.example.com', 'https://my-base-rpc-2.example.com'],
ethereum: ['https://my-eth-rpc.example.com'],
}}
/>WidgetTheme
interface WidgetTheme {
colors?: {
primary?: string
secondary?: string
background?: string
backgroundLight?: string
backgroundHeader?: string
text?: string
border?: string
error?: string
success?: string
}
borderRadius?: string
fontFamily?: string
}Only specified properties are overridden — the rest use the MetaLend defaults:
<MetaLendWidget
apiKey="your-api-key"
theme={{
colors: {
primary: '#8B5CF6',
background: '#0A0A0F',
},
borderRadius: '16px',
fontFamily: 'Inter, sans-serif',
}}
/>TrackingProvider
interface TrackingProvider {
init?: () => void | Promise<void>
identify?: (userId: string, properties?: Record<string, unknown>) => void | Promise<void>
track: (event: string, properties?: Record<string, unknown>) => void | Promise<void>
}Wrap any analytics SDK behind this interface:
import mixpanel from 'mixpanel-browser'
;<MetaLendWidget
apiKey="your-api-key"
trackingProvider={{
init: () => mixpanel.init('your-mixpanel-token'),
identify: (userId) => mixpanel.identify(userId),
track: (event, props) => mixpanel.track(event, props),
}}
/>Events emitted by the widget:
| Event | Description |
| ------------------------------------------------ | ---------------------------------------------- |
| UserLoggedIn | Wallet connected and authenticated |
| PageView \| URL: {path} \| Hash: {hash} | Route view (event name includes path and hash) |
| Open ToS/Privacy Modal | ToS/Privacy link opened |
| RebalancingCreated | Rebalancer config created (first setup) |
| RebalancingUpdated | Rebalancer config updated |
| RebalancingUpdated: Failed | Rebalancer config update failed |
| RebalancingCreated - Deposit (default) | Config created as part of a deposit |
| RebalancingUpdated - Deposit (default) | Config updated as part of a deposit |
| RebalancingUpdated - Deposit (default): Failed | Config update during deposit failed |
| RebalancingDeposit | Deposit completed successfully |
| RebalancingDepositBridging | Deposit required a cross-chain bridge |
| RebalancingDeposit: Failed | Deposit transaction failed |
| RebalancingDepositFailed | Deposit failed |
| DepositBlockedByCountry | Deposit blocked due to Geography regulations |
| RebalancingWithdraw | Withdrawal completed successfully |
| RebalancingClaimRewardsSuccess | Rewards claimed successfully |
| RebalancingClaimRewardsError | Rewards claim failed |
| ErrorLoadingBlockchainData | Error fetching blockchain data from RPC |
Geography Limitations
MetaLend complies with international regulations by restricting deposit functionality in certain countries. When a user attempts to make a deposit, the widget performs an IP-based geolocation check. If the user is detected to be in one of the restricted jurisdictions listed below, deposits will be blocked. Users can still view their balances and withdraw funds regardless of their location.
Countries currently restricted from deposits:
- Bosnia and Herzegovina (BA)
- Serbia (RS)
- Belarus (BY)
- Myanmar (MM)
- Côte d’Ivoire / Ivory Coast (CI)
- Cuba (CU)
- Congo (Democratic Republic) (CD)
- Iran (IR)
- Iraq (IQ)
- Liberia (LR)
- North Korea (KP)
- Sudan (SD)
- Syria (SY)
- Zimbabwe (ZW)
How It Works
Project Settings
Supported tokens, chains, and protocols are configured per API key through the MetaLend dashboard. When the widget mounts, it fetches your project settings from the API and scopes all UI and blockchain interactions accordingly.
This means two integrators with different API keys can show different token/chain combinations without changing any code.
The widget also merges in the user's own signed rebalancer configuration, so users can always see balances for tokens/chains they previously set up — even if you later disable them in your project settings. This ensures users can still view their positions and withdraw at any time.
Example: your project settings enable USDC on Base and Ethereum; a user previously configured USDT on Ethereum. The widget shows USDC (Base, Ethereum) + USDT (Ethereum) for that user.
Provider Architecture
<MetaLendWidget>
└── QueryClientProvider
└── WagmiProvider (skipped only when useExternalWagmiProvider is true AND showWelcomeAuthFlow is false)
└── ProjectAndUserSettingsProvider
└── ToastProvider
└── AutoConnect (session restore + forced config re-sign)
└── RouterProvider (TanStack Router, in-memory)
└── Dashboard / Deposit / Withdraw / Rewards viewsAuthentication Flow
The widget uses a challenge-verify flow for JWT issuance:
- Wallet connects via wagmi
- Widget requests a challenge from the MetaLend API
- User signs the challenge message
- Widget verifies the signature and receives a JWT
- JWT is used for deposit, withdrawal, and config update operations
- JWT is automatically invalidated when the user disconnects their wallet
The session survives a page reload: the JWT is kept in localStorage and the widget
reconnects the wallet the user last used. If that wallet cannot be restored (locked,
permission revoked), the session stays valid and the user is offered a reconnect
instead of having to sign in again.
Session invalidation on upgrade: the widget stores its own version alongside the JWT. When you ship a release built on a different widget version, existing sessions are cleared and users sign in again — this is how updated Terms of Service get re-signed. Expect one sign-in prompt per widget version bump.
Wallet Support
The widget works with any wagmi-compatible connector:
- MetaMask
- Coinbase Wallet (including Smart Wallet)
- WalletConnect
- Brave Wallet
- Trust Wallet
- Rainbow Wallet
- Any EIP-6963 compliant wallet
When using useExternalWagmiProvider={true}, all connectors from the parent wagmi config work automatically.
API Documentation
The MetaLend Rebalancing API documentation is available at: https://metalend-defi.github.io/public-api-swagger/
License
MIT © MetaLend
Support
- API Docs: MetaLend API Documentation
- Email: [email protected]
- Litepaper: MetaLend Litepaper
- Terms of Service: MetaLend Terms of Service
