Widget Embed Guide

How to easily add staking and DeFi to your application

Overview

The Blockdaemon Earn Widget lets you offer staking and DeFi lending 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.​

The purpose of this guide is to support the evaluation and integration of the widget into your staging or production environments.​


Supported Features

The Blockdaemon Earn Widget currently supports the following functionality:​

  • 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).

Implementation Checklist

When preparing to go live, typical steps include:​

  • Widget Deployment: Confirm your https://<client-slug>.widget.blockdaemon.com deployment and target environment (staging vs production).
  • Embedding the Widget: Embed and size the iframe appropriately within your UI.
  • Configuration Model: Align features configuration with your product scope (staking, DeFi, swaps) and regulatory requirements.
  • Appearance & Branding: Configure appearance to meet brand, theme, locale, and currency guidelines.
  • Wallet Integration Modes: Select wallet mode (internal vs external) and, for external mode, implement the postMessage integration contract.
  • Validate transaction flows, balances, and lifecycle (connect / transact / disconnect) in your staging environment.

Widget Deployment

Each customer receives a dedicated widget instance hosted at:

https://<client-slug>.widget.blockdaemon.com

Blockdaemon manages the deployment lifecycle, upgrades, and configuration of the widget instance. A client-specific slug will be provided as part of your implementation onboarding.​


Embedding the Widget

To embed the widget, add the iframe to the relevant view or component in your application:​

<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>

Consider the following when embedding:​

  • embed=true activates PostMessage communication mode. Required for external wallet and external balance integration.
  • Sizing: The widget supports two sizing modes via appearance.sizingMode.
    • Fixed mode: Use fixed mode when you want a predictable, static widget footprint. The numeric sizing limits require a width between 375 and 1000 and a height between 480 and 1200.
    • Responsive mode: Use the responsive mode when the widget should adapt to the host layout. In this mode, the widget requires a minimum width of 375px and a minimum height of 480px.
    📘

    Recommended Usage

    • Use responsive mode + host CSS for adaptive embeds.
    • Use fixed mode for stable, known-size placements.
    • Use URL overrides for per-placement tuning or testing.
  • Scroll behavior: The widget's internal scroll area depends on host container sizing. Constrained iframe heights trigger internal scrolling, while unconstrained heights shift it to the outer page.
  • Security & Content Security Policy (CSP): Ensure you allow the widget origin in frame-src, e.g. https://<client-slug>.widget.blockdaemon.com.

Configuration Model

Blockdaemon currently manages widget configuration on your behalf; self-serve configuration will be provided via the Blockdaemon Partner Portal in a future release. Configuration can be applied per widget instance, with these specific parameters also available via URL query parameters:

ParameterValuesPurpose
embedtrueActivate PostMessage mode (required for external wallet/balance)
colorSchemelight, darkOverride theme
widthmobile, tablet, or 3751000Override widget width
heightmobile, tablet, or 4801200Override widget height
currencyUSD, KRW, JPYOverride display currency
localeen-US, ko-KR, ja-JPOverride locale
batchModeEIP-5792, eip5792, EIP-7702 (if config has batcher), offOverride batch mode (canonical labels or legacy slugs)
atomicRequiredtrue, falseRequire atomic execution for EIP-5792 batched calls
enableFeetrue, falseOverride fee extraction (swap.fees.enabled)

Features Configuration

The widget’s functionality is controlled through a features configuration object:​

{
  "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
  }
}
FieldDescription
portfolioEnables a unified view to track and manage user balances.
stakingProvides the interface for depositing assets to earn protocol rewards.
swapEnables token exchanges within a single chain or across chains.
defiConnects users to liquidity markets for lending and borrowing assets.
walletInfoControls the visibility of connected wallet addresses and balance data.
walletModeDetermines whether the widget or host application manages wallet connectivity.
balanceModeInternal: widget fetches balances via SDK. External: host pushes balances via PostMessage. Requires walletMode: "external".
batchOptionBatch transaction config. Omit to disable batching. See Batch Transactions.

Typical configuration patterns include:​

  • Enabling only staking for a particular chain or product line.
  • Restricting swap or DeFi to a curated allow-list of assets, chains, or markets.
  • Selecting wallet mode (internal vs external) to match your custody or wallet strategy.

Configuration changes are coordinated with your Blockdaemon contact and applied to your dedicated widget deployment.​

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

The widget supports appearance configuration to align with your brand and layout:​

{
  "appearance": {
    "sizingMode": "responsive",
    "width": "mobile",
    "height": "mobile",
    "theme": "light",
    "builtByBlockdaemon": true,
    "logoUrl": "",
    "coreNavigation": true,
    "externalLinks": true,
    "locale": "en-US",
    "currency": "USD"
  }
}

Field

Description

width

Sets the horizontal size of the widget using presets or custom pixel values to fit your application's layout.

Once configured, the iframe container should be adjusted to match the widget’s configured size to ensure a proper visual fit.

`height

Sets the vertical size of the widget using presets or custom pixel values to fit your application's layout.

Once configured, the iframe container should be adjusted to match the widget’s configured size to ensure a proper visual fit.

theme

Configuration supports light or dark modes with a default option available. Apps with a light / dark toggle should update this parameter to reflect the active selection.

logoUrl

The widget supports a single logo or individual logos tailored for light and dark color schemes.

builtByBlockdaemon

The flag determines whether the "Built by Blockdaemon" logo appears at the bottom of the widget UI.

locale

Determines language for balances and yields.​

currency

Determines displayed currency for balances and yields.​

coreNavigation

Show/hide main navigation tabs

externalLinks

Allow/block external links (e.g., block explorer links)


Wallet Integration Modes

The widget supports two wallet integration modes:​

  1. Internal mode The widget manages wallet connection and network switching within the iframe. Users connect and approve transactions directly in the widget UI.
  2. External mode The host application owns wallet state, connection flow, and transaction execution. The widget requests wallet actions via postMessage, and the host responds with wallet state and transaction results.

In both modes, wallet connectivity is non-custodial and your application remains in control of user session and authentication outside the widget.​

Before a wallet is connected, the widget displays a default, disconnected state; once a wallet is connected, the widget updates to a fully interactive state.


External Wallet Mode: Messaging Contract

When using external wallet mode, the host application and the widget communicate via the standard window.postMessage API. All postMessage events must be filtered by origin, and only the widget origin (https://<client-slug>.widget.blockdaemon.com) should be trusted.​

Message Types

The following table serves as the formal communication contract between the Widget and the Host:

DirectionTypePayloadPurposeRequired Host Behavior
Widget → HostgetWalletStatenoneRequest current wallet statusRespond with walletState
Widget → HostconnectWalletnoneRequest wallet connectionOpen connection flow, then send walletState
Widget → HostdisconnectWalletnoneRequest wallet disconnectionDisconnect wallet, then send walletState
Widget → HostsendTransaction{ to, data, value?, chainId?, type?, authorizationList? }Submit a transactionExecute with host wallet, respond with sendTransactionResponse
Widget → HostswitchNetwork{ chainId }Switch wallet network (CAIP-2)Trigger switch, then send updated walletState
Widget → HostsendBatchCalls{ calls, chainId, atomicRequired }Submit EIP-5792 batchCall wallet_sendCalls, respond with sendBatchCallsResponse
Widget → HostgetCallsStatus{ bundleId }Poll batch confirmationCall wallet_getCallsStatus, respond with getCallsStatusResponse
Widget → HostgetBalancesnoneRequest token balancesRespond with balanceUpdate
Host → WidgetwalletState{ isConnected, address?, chainId?, walletInfo?, namespace? }Current wallet stateSend on request and after any wallet/network change
Host → WidgetsendTransactionResponse{ success, data?, error? }Transaction resultInclude hash + chainId on success
Host → WidgetsendBatchCallsResponse{ success, data?, error? }Batch resultReturn { bundleId } or { hash }
Host → WidgetgetCallsStatusResponse{ success, data?, error? }Batch poll resultReturn { status, receipts }
Host → WidgetbalanceUpdateExternalTokenBalance[]Push balance dataCan send at any time

Example: App ↔ Widget Integration

Below is a reference implementation illustrating event handling and wallet state propagation:​

<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

  1. Widget sends getBalances on load, after wallet connect, after transactions, and every 10 seconds.
  2. Host responds with balanceUpdate containing an array of token balances.
  3. Host can also push balanceUpdate at any time (not just in response to getBalances).

Balance Payload

{
  "type": "balanceUpdate",
  "data": [
    {
      "chainId": "eip155:1",
      "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "rawBalance": "5000000000"
    },
    {
      "chainId": "eip155:1",
      "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
      "rawBalance": "1500000000000000000"
    }
  ]
}
FieldTypeDescription
chainIdstringCAIP-2 chain ID (e.g., "eip155:1")
addressstringToken contract address. Use "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" for native tokens (ETH, MATIC, etc.)
rawBalancestringBalance 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.

Uses wallet_sendCalls — the wallet itself batches and submits the calls.

Config:

{ "batchOption": { "eip5792": { "atomicRequired": false } } }

PostMessage flow (external wallet mode):

  1. Widget sends sendBatchCalls with { calls, chainId, atomicRequired }.
  2. Host calls wallet_sendCalls on the connected wallet.
  3. Host responds with sendBatchCallsResponse:
    • Two-phase: Return { bundleId }. The widget will poll via getCallsStatus every 2 seconds (up to 30 attempts).
    • One-phase: Return { hash } after polling internally. Widget skips polling.
📘

MetaMask Smart Transactions

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 and Support

For configuration changes, production cutover, or to discuss additional feature flags and chains, please contact your Blockdaemon representative or support channel. Blockdaemon can work with you to align widget behavior, branding, and wallet flows with your institutional requirements.​


👋 Need Help?

Contact us through email or our support page for any issues, bugs, or assistance you may need.