Widget Embed Guide
How to easily add staking and DeFi to your application
Overview
The Blockdaemon Earn Widget lets you offer staking, DeFi lending, and token swaps in your application via a hosted widget, without building protocol integrations or on-chain logic in-house. Blockdaemon deploys the widget on a per-client basis and connects directly to the Blockdaemon Earn Stack.
Supported Features
- Staking: Ethereum (via StakeWise vaults) and Solana (native delegation with validator selection, unstaking, and reward claiming).
- DeFi lending: Aave on Ethereum, Base, Arbitrum, Optimism; Morpho and additional chains are planned.
- Swaps: Configurable per deployment via chain allow/block lists (commonly Ethereum, Base, Arbitrum, Optimism, Kaia).
Widget Deployment
Each customer receives a dedicated deployment at:
https://<client-slug>.widget.blockdaemon.com
Blockdaemon manages deployment lifecycle, upgrades, and configuration. A client-specific slug is provided during onboarding.
Embedding the Widget
<iframe
src="https://<client-slug>.widget.blockdaemon.com?embed=true"
id="blockdaemon-widget"
title="Blockdaemon Earn Widget"
allow="clipboard-write"
style="width: 100%; height: 100%;">
</iframe>Notes:
embed=trueactivates PostMessage communication mode. Required for external wallet and external balance integration.- Container sizing: Place the iframe in a responsive container. Typical layouts use full-width in a main content column or a fixed-height panel in a dashboard card.
- Scroll behavior: Controlled by your container and CSS, not the widget.
- CSP: Ensure your Content Security Policy allows
https://<client-slug>.widget.blockdaemon.cominframe-src.
URL Parameters
The following parameters can be appended to the widget URL to override configuration at runtime.
| Parameter | Values | Purpose |
|---|---|---|
embed | true | Activate PostMessage mode (required for external wallet/balance) |
colorScheme | light, dark | Override theme |
width | mobile, tablet, or 375–1000 | Override widget width |
height | mobile, tablet, or 480–1200 | Override widget height |
currency | USD, KRW, JPY | Override display currency |
locale | en-US, ko-KR, ja-JP | Override locale |
batchMode | EIP-5792, eip5792, EIP-7702 (if config has batcher), off | Override batch mode (canonical labels or legacy slugs) |
atomicRequired | true, false | Require atomic execution for EIP-5792 batched calls |
enableFee | true, false | Override fee extraction (swap.fees.enabled) |
Example:
https://acme.widget.blockdaemon.com?embed=true&colorScheme=dark&locale=ko-KR&batchMode=eip5792&atomicRequired=true
Configuration Model
Blockdaemon manages widget configuration on your behalf. Configuration changes are coordinated with your Blockdaemon contact. Self-serve configuration via the Partner Portal is planned.
Features Configuration
{
"features": {
"portfolio": { "enabled": false },
"staking": { "enabled": true },
"swap": {
"enabled": true,
"chains": { "allowList": [], "blockList": [] },
"assets": { "allowList": [], "blockList": [] },
"dexes": { "allowList": [], "blockList": [] },
"bridges": { "allowList": [], "blockList": [] },
"fees": { "enabled": false }
},
"defi": {
"enabled": true,
"markets": [],
"chains": { "allowList": [], "blockList": [] },
"assets": { "allowList": [], "blockList": [] }
},
"walletInfo": true,
"walletMode": "internal",
"balanceMode": "internal",
"batchOption": null
}
}| Field | Type | Description |
|---|---|---|
walletMode | "internal" | "external" | Internal: widget manages wallet UI. External: host manages wallet via PostMessage. |
balanceMode | "internal" | "external" | Internal: widget fetches balances via SDK. External: host pushes balances via PostMessage. Requires walletMode: "external". |
batchOption | object | omitted | Batch transaction config. Omit to disable batching. See Batch Transactions. |
walletInfo | boolean | Show/hide wallet info bar. Auto-disabled when walletMode: "external". |
Batch Option
Exactly one of eip5792 or eip7702 must be set. Omit batchOption entirely to disable batching.
// EIP-5792: wallet-side batching via wallet_sendCalls
{ "batchOption": { "eip5792": { "atomicRequired": false } } }
// EIP-7702: batcher contract, Type 4 transaction
{ "batchOption": { "eip7702": { "batcherContract": "0xA1fdd934a977898B72ddBFbeC5525e266b7e9991" } } }Appearance & Branding
{
"appearance": {
"sizingMode": "responsive",
"width": "mobile",
"height": "mobile",
"theme": "light",
"builtByBlockdaemon": true,
"logoUrl": "",
"coreNavigation": true,
"externalLinks": true,
"locale": "en-US",
"currency": "USD"
}
}| Field | Type | Default | Description |
|---|---|---|---|
sizingMode | "fixed" | "responsive" | "fixed" | Fixed dimensions vs. adapt to host container |
width | "mobile" | "tablet" | number | — | Widget width preset or custom pixel value (375–1000) |
height | "mobile" | "tablet" | number | "mobile" | Widget height preset or custom pixel value (480–1200) |
theme | "light" | "dark" | "light" | Color theme |
builtByBlockdaemon | boolean | true | Show "Built by Blockdaemon" badge |
logoUrl | string | — | Custom logo URL |
coreNavigation | boolean | true | Show/hide main navigation tabs |
externalLinks | boolean | true | Allow/block external links (e.g., block explorer links) |
locale | string | "en-US" | Language (en-US, ko-KR, ja-JP) |
currency | string | "USD" | Display currency (USD, KRW, JPY) |
Wallet Integration Modes
Internal Mode
The widget renders its own wallet connection bar. End users connect wallets directly within the widget UI. No PostMessage integration needed.
External Mode
Your application owns wallet connectivity and injects wallet state into the widget via PostMessage. Set walletMode: "external" in the config and include ?embed=true in the iframe URL.
Before a wallet is connected, the widget displays a disconnected state. Once connected, the widget updates to a fully interactive state.
External Wallet: PostMessage Contract
All messages use window.postMessage. Filter by origin — only trust https://<client-slug>.widget.blockdaemon.com.
Message Types
| Direction | Type | Payload | Purpose | Required Host Behavior |
|---|---|---|---|---|
| Widget → Host | getWalletState | none | Request current wallet status | Respond with walletState |
| Widget → Host | connectWallet | none | Request wallet connection | Open connection flow, then send walletState |
| Widget → Host | disconnectWallet | none | Request wallet disconnection | Disconnect wallet, then send walletState |
| Widget → Host | sendTransaction | { to, data, value?, chainId?, type?, authorizationList? } | Submit a transaction | Execute with host wallet, respond with sendTransactionResponse |
| Widget → Host | switchNetwork | { chainId } | Switch wallet network (CAIP-2) | Trigger switch, then send updated walletState |
| Widget → Host | sendBatchCalls | { calls, chainId, atomicRequired } | Submit EIP-5792 batch | Call wallet_sendCalls, respond with sendBatchCallsResponse |
| Widget → Host | getCallsStatus | { bundleId } | Poll batch confirmation | Call wallet_getCallsStatus, respond with getCallsStatusResponse |
| Widget → Host | getBalances | none | Request token balances | Respond with balanceUpdate |
| Host → Widget | walletState | { isConnected, address?, chainId?, walletInfo?, namespace? } | Current wallet state | Send on request and after any wallet/network change |
| Host → Widget | sendTransactionResponse | { success, data?, error? } | Transaction result | Include hash + chainId on success |
| Host → Widget | sendBatchCallsResponse | { success, data?, error? } | Batch result | Return { bundleId } or { hash } |
| Host → Widget | getCallsStatusResponse | { success, data?, error? } | Batch poll result | Return { status, receipts } |
| Host → Widget | balanceUpdate | ExternalTokenBalance[] | Push balance data | Can send at any time |
Wallet State Payload
{
"isConnected": true,
"address": "0x1234...",
"chainId": "eip155:1",
"walletInfo": "Client Wallet",
"namespace": "eip155"
}chainIduses CAIP-2 format (e.g.,"eip155:1"for Ethereum mainnet).
Integration Example
Below is a reference implementation illustrating event handling and wallet state propagation, including batch transactions and external balance support.
<script>
const WIDGET_ORIGIN = 'https://<client-slug>.widget.blockdaemon.com';
const iframe = document.getElementById('blockdaemon-widget');
// Your app's wallet state (you manage this)
let walletState = {
isConnected: false,
address: null,
chainId: null,
walletInfo: "Client Wallet"
};
// Listen for messages FROM the widget
window.addEventListener('message', function(event) {
if (event.origin !== WIDGET_ORIGIN) return;
const { type, data } = event.data;
switch (type) {
case 'getWalletState':
sendWalletState();
break;
case 'connectWallet':
connectYourWallet();
break;
case 'disconnectWallet':
disconnectYourWallet();
break;
case 'sendTransaction':
handleTransaction(data);
break;
case 'switchNetwork':
handleSwitchNetwork(data.chainId);
break;
case 'sendBatchCalls':
handleBatchCalls(data);
break;
case 'getCallsStatus':
handleGetCallsStatus(data.bundleId);
break;
case 'getBalances':
sendBalances();
break;
}
});
// --- Wallet State ---
function sendWalletState() {
iframe.contentWindow.postMessage(
{ type: 'walletState', data: walletState },
WIDGET_ORIGIN
);
}
function connectYourWallet() {
// Implement your wallet connection logic here
walletState = {
isConnected: true,
address: '0x1234...',
chainId: 'eip155:1',
walletInfo: "Client Wallet"
};
sendWalletState();
}
function disconnectYourWallet() {
walletState = {
isConnected: false,
address: null,
chainId: null,
walletInfo: "Client Wallet"
};
sendWalletState();
}
// --- Transactions ---
function handleTransaction(txData) {
// Execute the transaction with your wallet provider
// txData: { to, data, value?, chainId?, type?, authorizationList? }
try {
const hash = '0x...'; // Your wallet provider's response
iframe.contentWindow.postMessage({
type: 'sendTransactionResponse',
success: true,
data: { hash, chainId: walletState.chainId }
}, WIDGET_ORIGIN);
} catch (err) {
iframe.contentWindow.postMessage({
type: 'sendTransactionResponse',
success: false,
error: err.message
}, WIDGET_ORIGIN);
}
}
function handleSwitchNetwork(chainId) {
// Switch your wallet to the requested chain (CAIP-2 format)
// After switching, update walletState.chainId and notify the widget
walletState.chainId = chainId;
sendWalletState();
}
// --- Batch Transactions (EIP-5792) ---
async function handleBatchCalls({ calls, chainId, atomicRequired }) {
// calls: Array<{ to, value, data }>
// Use wallet_sendCalls on the connected wallet
try {
const result = await wallet.sendCalls({ calls, chainId, atomicRequired });
// Option A: Return bundleId — widget will poll via getCallsStatus
iframe.contentWindow.postMessage({
type: 'sendBatchCallsResponse',
success: true,
data: { bundleId: result.bundleId }
}, WIDGET_ORIGIN);
// Option B: Poll internally, return final hash
// iframe.contentWindow.postMessage({
// type: 'sendBatchCallsResponse',
// success: true,
// data: { hash: result.transactionHash }
// }, WIDGET_ORIGIN);
} catch (err) {
iframe.contentWindow.postMessage({
type: 'sendBatchCallsResponse',
success: false,
error: err.message
}, WIDGET_ORIGIN);
}
}
async function handleGetCallsStatus(bundleId) {
// Call wallet_getCallsStatus(bundleId) and return the result
try {
const status = await wallet.getCallsStatus(bundleId);
iframe.contentWindow.postMessage({
type: 'getCallsStatusResponse',
success: true,
data: status // { status: "CONFIRMED", receipts: [{ transactionHash, status }] }
}, WIDGET_ORIGIN);
} catch (err) {
iframe.contentWindow.postMessage({
type: 'getCallsStatusResponse',
success: false,
error: err.message
}, WIDGET_ORIGIN);
}
}
// --- External Balances ---
function sendBalances() {
// Provide token balances in smallest denomination (e.g., wei)
// Use 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee for native tokens
iframe.contentWindow.postMessage({
type: 'balanceUpdate',
data: [
{
chainId: 'eip155:1',
address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
rawBalance: '1500000000000000000'
},
{
chainId: 'eip155:1',
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
rawBalance: '5000000000'
}
]
}, WIDGET_ORIGIN);
}
// Send initial wallet state when iframe loads
iframe.addEventListener('load', function() {
setTimeout(() => sendWalletState());
});
</script>External Balance Mode
When balanceMode: "external" is configured (requires walletMode: "external"), the host provides token balances instead of the widget fetching them via the Blockdaemon SDK.
Flow
- Widget sends
getBalanceson load, after wallet connect, after transactions, and every 10 seconds. - Host responds with
balanceUpdatecontaining an array of token balances. - Host can also push
balanceUpdateat any time (not just in response togetBalances).
Balance Payload
{
"type": "balanceUpdate",
"data": [
{
"chainId": "eip155:1",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"rawBalance": "5000000000"
},
{
"chainId": "eip155:1",
"address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"rawBalance": "1500000000000000000"
}
]
}| Field | Type | Description |
|---|---|---|
chainId | string | CAIP-2 chain ID (e.g., "eip155:1") |
address | string | Token contract address. Use "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" for native tokens (ETH, MATIC, etc.) |
rawBalance | string | Balance in smallest denomination (e.g., wei for ETH, 6-decimal units for USDC) |
The widget handles decimal conversion using on-chain token metadata. Invalid balance entries are silently dropped.
Batch Transactions
Batch mode combines fee + approval + swap into a single wallet confirmation. Two protocols are supported.
EIP-5792 (Wallet-Side Batching)
Uses wallet_sendCalls — the wallet itself batches and submits the calls.
Config:
{ "batchOption": { "eip5792": { "atomicRequired": false } } }PostMessage flow (external wallet mode):
- Widget sends
sendBatchCallswith{ calls, chainId, atomicRequired }. - Host calls
wallet_sendCallson the connected wallet. - Host responds with
sendBatchCallsResponse:- Two-phase: Return
{ bundleId }. The widget will poll viagetCallsStatusevery 2 seconds (up to 30 attempts). - One-phase: Return
{ hash }after polling internally. Widget skips polling.
- Two-phase: Return
MetaMask Smart Transactions note: MetaMask Smart Transactions don't support wallet_getCallsStatus. If the host forwards a "No matching bundle found" error, the widget retries up to 5 times, then treats the transaction as submitted but unconfirmed (success UI shown without explorer link).
EIP-7702 (Batcher Contract)
Uses a Type 4 transaction with an authorization list to delegate execution to a batcher contract.
Config:
{ "batchOption": { "eip7702": { "batcherContract": "0xA1fdd934a977898B72ddBFbeC5525e266b7e9991" } } }When EIP-7702 is configured, the widget may send a sendTransaction with additional fields:
{
"type": "sendTransaction",
"data": {
"to": "0xUserEOA",
"data": "0xe9ae5c53...",
"value": "0x0",
"chainId": "eip155:8453",
"type": "0x04",
"authorizationList": [
{ "chainId": "0x2105", "address": "0xBatcherContract" }
]
}
}The host must construct and sign the Type 4 transaction accordingly.
Next Steps
For configuration changes, production cutover, or to discuss additional features, contact your Blockdaemon representative. Blockdaemon works with you to align widget behavior, branding, and wallet flows with your requirements.
Updated 7 days ago
