> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pods.finance/llms.txt
> Use this file to discover all available pages before exploring further.

# Iframe Integration

> Embed Pods DeFi widgets in any stack using a hosted iframe and postMessage protocol

## Webview Base URL

```
https://iframe.pods.finance
```

All iframe integrations point to this hosted webview. You append query parameters to configure which widget to display and how to connect to the Pods API.

***

## Iframe URL Query Parameters

| Parameter             | Type                 | Required | Description                                    |
| --------------------- | -------------------- | -------- | ---------------------------------------------- |
| `smartAccountAddress` | `0x${string}`        | Yes      | The user's smart account address               |
| `chainId`             | number               | Yes      | Target chain ID (e.g., `137` for Polygon)      |
| `theme`               | string               | Yes      | UI theme (`"dark"` or `"light"`)               |
| `preset`              | string               | Yes      | Widget preset assigned to your integration     |
| `podsApiUrl`          | string               | Yes      | Pods API base URL                              |
| `podsApiKey`          | string               | Yes      | Pods API key                                   |
| `alchemyPolicyId`     | string               | Yes      | Alchemy gas policy ID (for paymaster)          |
| `alchemyRpcId`        | string               | Yes      | Alchemy RPC/API key                            |
| `widget`              | `"earn"` \| `"swap"` | No       | Which widget to display (defaults to `"earn"`) |

### URL Builder Example

```typescript theme={null}
function buildIframeUrl(params: {
  webviewBaseUrl: string
  smartAccountAddress: string
  chainId: number
  theme?: string
  preset?: string
  podsApiUrl: string
  podsApiKey: string
  alchemyPolicyId: string
  alchemyRpcId: string
  widget?: 'earn' | 'swap'
}): string {
  const url = new URL(params.webviewBaseUrl)
  url.searchParams.set('smartAccountAddress', params.smartAccountAddress)
  url.searchParams.set('chainId', String(params.chainId))
  if (params.theme) url.searchParams.set('theme', params.theme)
  if (params.preset) url.searchParams.set('preset', params.preset)
  url.searchParams.set('podsApiUrl', params.podsApiUrl)
  url.searchParams.set('podsApiKey', params.podsApiKey)
  url.searchParams.set('alchemyPolicyId', params.alchemyPolicyId)
  url.searchParams.set('alchemyRpcId', params.alchemyRpcId)
  if (params.widget) url.searchParams.set('widget', params.widget)
  return url.toString()
}
```

***

## postMessage Protocol

The iframe and wrapper communicate via `window.postMessage`. The protocol is typed in TypeScript below — copy this into your project as `src/types/postmessage-protocol.ts`.

### Shared Types

```typescript theme={null}
export interface BytecodeTransaction {
  to: string
  data: string
  value: string
  chainId?: number
  gasLimit?: string
}

export type TxStatusPayload =
  | { type: 'HOST_ACK'; clientTxId: string }
  | { type: 'SIGNATURE_PROMPTED'; clientTxId: string }
  | { type: 'SIGNED'; clientTxId: string }
  | { type: 'TX_SUBMITTED'; clientTxId: string; chainId: number; txHash?: string; userOperationHash?: string }
  | { type: 'TX_CONFIRMED'; clientTxId: string; txHash: string; blockNumber?: number; confirmations?: number }
  | { type: 'TX_FINALIZED'; clientTxId: string; txHash?: string }
  | { type: 'SIGNATURE_DECLINED'; clientTxId: string }
  | { type: 'SIGNATURE_ERROR'; clientTxId: string; code: string; message: string }
  | { type: 'TX_REVERTED'; clientTxId: string; txHash: string; reason?: string }

export type UpdateTxStatus = (event: TxStatusPayload) => void
```

### Message Directions

```typescript theme={null}
// Webview → Wrapper
export type WebviewToWrapperMessage =
  | { kind: 'READY' }
  | { kind: 'PROCESS_BYTECODE_REQUEST'; clientTxId: string; bytecodes: BytecodeTransaction[]; simulateError?: boolean }

// Wrapper → Webview
export type WrapperToWebviewMessage =
  | { kind: 'TX_STATUS_UPDATE'; payload: TxStatusPayload }

// Union
export type PostMessage = WebviewToWrapperMessage | WrapperToWebviewMessage
```

### Type Guards

```typescript theme={null}
export function isWebviewMessage(msg: unknown): msg is WebviewToWrapperMessage {
  if (typeof msg !== 'object' || msg === null) return false
  const m = msg as Record<string, unknown>
  return m.kind === 'READY' || m.kind === 'PROCESS_BYTECODE_REQUEST'
}

export function isWrapperMessage(msg: unknown): msg is WrapperToWebviewMessage {
  if (typeof msg !== 'object' || msg === null) return false
  const m = msg as Record<string, unknown>
  return m.kind === 'TX_STATUS_UPDATE'
}
```

***

## processBytecode Flow via postMessage

The iframe version of `processBytecode` works over `postMessage` instead of in-process callbacks. The transaction lifecycle statuses and error handling are the same as the [widget processBytecode](/widgets/process-bytecode) — the only difference is the transport: messages are wrapped in `{ kind: 'TX_STATUS_UPDATE', payload }` and sent via `postMessage` instead of calling `ctx.updateTxStatus()` directly.

### Happy Path

```
Webview → Wrapper:  { kind: 'PROCESS_BYTECODE_REQUEST', clientTxId, bytecodes }
Wrapper → Webview:  { kind: 'TX_STATUS_UPDATE', payload: { type: 'HOST_ACK', clientTxId } }
Wrapper → Webview:  { kind: 'TX_STATUS_UPDATE', payload: { type: 'SIGNATURE_PROMPTED', clientTxId } }
  ... user signs in wallet / smart wallet signs automatically ...
Wrapper → Webview:  { kind: 'TX_STATUS_UPDATE', payload: { type: 'SIGNED', clientTxId } }
Wrapper → Webview:  { kind: 'TX_STATUS_UPDATE', payload: { type: 'TX_SUBMITTED', clientTxId, chainId, txHash } }
  ... wait for confirmation ...
Wrapper → Webview:  { kind: 'TX_STATUS_UPDATE', payload: { type: 'TX_CONFIRMED', clientTxId, txHash, blockNumber } }
Wrapper → Webview:  { kind: 'TX_STATUS_UPDATE', payload: { type: 'TX_FINALIZED', clientTxId, txHash } }
```

### Error Paths

See [processBytecode — Error Handling](/widgets/process-bytecode) for the full list of error statuses (`SIGNATURE_DECLINED`, `SIGNATURE_ERROR`, `TX_REVERTED`).

***

## Listening for postMessage Events

The wrapper must listen for `message` events on `window` and filter using the type guards from the protocol types:

```typescript theme={null}
useEffect(() => {
  const handler = (event: MessageEvent) => {
    if (!isWebviewMessage(event.data)) return

    if (event.data.kind === 'READY') {
      console.log('Iframe is ready')
    }

    if (event.data.kind === 'PROCESS_BYTECODE_REQUEST') {
      const { clientTxId, bytecodes } = event.data
      processBytecode(clientTxId, bytecodes)
    }
  }

  window.addEventListener('message', handler)
  return () => window.removeEventListener('message', handler)
}, [processBytecode])
```

***

## Iframe HTML Element

```html theme={null}
<iframe
  src={iframeUrl}
  allow="clipboard-write"
  style="width: 100%; height: 100%; border: none;"
/>
```

No iframe should be present in the DOM before the smart account is ready. Show a loading state or placeholder instead.

***

## Wrapper Implementations

We provide two reference wrapper implementations, each targeting a different wallet/auth stack:

<CardGroup cols={2}>
  <Card title="wagmi + viem" icon="wallet" href="/iframe/wagmi-viem">
    Browser wallet (MetaMask) with ERC-4337 smart accounts via `permissionless`. Single-chain (Polygon).
  </Card>

  <Card title="Privy" icon="shield-halved" href="/iframe/privy">
    Privy authentication (email OTP) with embedded smart wallets. Multi-chain support (6 chains).
  </Card>
</CardGroup>

***

## LLM Resources

Use these resources to generate iframe wrapper projects with AI assistants.

### One-Shot Prompts

| Resource                                                                 | Stack                         | Use                                                                  |
| ------------------------------------------------------------------------ | ----------------------------- | -------------------------------------------------------------------- |
| [`/llms-iframe-wagmi-viem.txt`](/llms-iframe-wagmi-viem.txt)             | wagmi + viem + permissionless | Generate a complete iframe wrapper with browser wallet + ERC-4337    |
| [`/llms-iframe-privy-multichain.txt`](/llms-iframe-privy-multichain.txt) | Privy + multi-chain           | Generate a complete iframe wrapper with Privy auth + 6-chain support |
