---
updatedAt: 2026-03-24T09:19:13.000Z
---

Fetch the complete documentation index at: https://docs.blockdaemon.com/llms.txt. Use this file to discover all available pages before exploring further. Append .md to any documentation page URL to get its markdown version.

# 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](https://docs.blockdaemon.com/docs/copy-of-widget-embed-guide#widget-deployment)**: Confirm your `https://<client-slug>.widget.blockdaemon.com` deployment and target environment (staging vs production).
* **[Embedding the Widget](https://docs.blockdaemon.com/docs/copy-of-widget-embed-guide#embedding-the-widget)**: Embed and size the iframe appropriately within your UI.
* **[Configuration Model](https://docs.blockdaemon.com/docs/copy-of-widget-embed-guide#configuration-model)**: Align features configuration with your product scope (staking, DeFi, swaps) and regulatory requirements.
* **[Appearance & Branding](https://docs.blockdaemon.com/docs/copy-of-widget-embed-guide#appearance--branding)**: Configure appearance to meet brand, theme, locale, and currency guidelines.
* **[Wallet Integration Modes](https://docs.blockdaemon.com/docs/copy-of-widget-embed-guide#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:​

```html
<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:

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

### **Features Configuration**

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

```json
{
  "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         | Description                                                                                                                   |
| :------------ | :---------------------------------------------------------------------------------------------------------------------------- |
| `portfolio`   | Enables a unified view to track and manage user balances.                                                                     |
| `staking`     | Provides the interface for depositing assets to earn protocol rewards.                                                        |
| `swap`        | Enables token exchanges within a single chain or across chains.                                                               |
| `defi`        | Connects users to liquidity markets for lending and borrowing assets.                                                         |
| `walletInfo`  | Controls the visibility of connected wallet addresses and balance data.                                                       |
| `walletMode`  | Determines whether the widget or host application manages wallet connectivity.                                                |
| `balanceMode` | Internal: widget fetches balances via SDK. External: host pushes balances via PostMessage. Requires `walletMode: "external"`. |
| `batchOption` | Batch transaction config. Omit to disable batching. See [Batch Transactions](#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.

```json
// 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:​

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

<Table align={["left","left"]}>
  <thead>
    <tr>
      <th>
        Field
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        `width`
      </td>

      <td>
        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.
      </td>
    </tr>

    <tr>
      <td>
        `height
      </td>

      <td>
        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.
      </td>
    </tr>

    <tr>
      <td>
        `theme`
      </td>

      <td>
        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.
      </td>
    </tr>

    <tr>
      <td>
        `logoUrl`
      </td>

      <td>
        The widget supports a **single logo** or **individual logos** tailored for light and dark color schemes.
      </td>
    </tr>

    <tr>
      <td>
        `builtByBlockdaemon`
      </td>

      <td>
        The flag determines whether the **"Built by Blockdaemon" logo** appears at the bottom of the widget UI.
      </td>
    </tr>

    <tr>
      <td>
        `locale`
      </td>

      <td>
        Determines **language** for balances and yields.​
      </td>
    </tr>

    <tr>
      <td>
        `currency`
      </td>

      <td>
        Determines **displayed currency** for balances and yields.​
      </td>
    </tr>

    <tr>
      <td>
        `coreNavigation` 
      </td>

      <td>
        Show/hide main navigation tabs
      </td>
    </tr>

    <tr>
      <td>
        `externalLinks`
      </td>

      <td>
        Allow/block external links (e.g., block explorer links)
      </td>
    </tr>
  </tbody>
</Table>

***

## **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:

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

### **Example: App ↔ Widget Integration**

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

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

```json
{
  "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)](https://docs.blockdaemon.com/docs/eip-5792-batching-support)

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

**Config:**

```json
{ "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:**

```json
{ "batchOption": { "eip7702": { "batcherContract": "0xA1fdd934a977898B72ddBFbeC5525e266b7e9991" } } }
```

When EIP-7702 is configured, the widget may send a `sendTransaction` with additional fields:

```json
{
  "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.​

<br />

## 👋 Need Help?

Contact us through [email](mailto:support@blockdaemon.com) or our [support page](https://www.blockdaemon.com/support) for any issues, bugs, or assistance you may need.