Embedded Mini Apps

This is a practical, end-to-end technical guide for building miniapps that run inside the Circles miniapp host and communicate through @aboutcircles/miniapp-sdk.

If you are a vibecode developer, you can simply copy the entire page using the button above and paste in your coding environment

Mental Model: How Embedded Miniapps Run?

A embedded miniapp is a web app rendered in an iframe inside a host application.

  • Your app owns UI, local state, validation, and business flow.

  • The host owns wallet session UX and transaction approval.

  • @aboutcircles/miniapp-sdk bridges both sides using postMessage.

This means:

  • Local browser testing is useful for UI and logic.

  • End-to-end wallet/transaction testing must happen in the host.

SDK Capabilities

The SDK currently exposes:

  • isMiniappMode(): boolean

  • onAppData(fn: (data: string) => void): void

  • onWalletChange(fn: (address: string | null) => void): () => void

  • sendTransactions(transactions: Transaction[]): Promise<string[]>

  • signMessage(message: string, signatureType?: 'erc1271' | 'raw'): Promise<{ signature: string; verified: boolean }>

Where Transaction is:

Project Setup

Create the app

Add additional Circles SDK packages only if your use case needs them:

Suggested structure

Keep host bridge logic isolated from domain logic.

Bootstrapping the Host Bridge

Create a dedicated bridge module:

Use onWalletChange as the app’s wallet truth source.

Wallet Lifecycle Pattern

Implement wallet lifecycle as a finite-state flow:

  1. disconnected

  2. connecting (optional UI state)

  3. connected

  4. error (recoverable or fatal)

Minimal pattern:

Rules:

  • Always reset account-scoped caches on wallet changes.

  • Never assume prior in-memory state is still valid after reconnects.

Transaction Submission Pattern

Always format and submit transactions through one adapter.

Why this is critical:

  • Prevents value-encoding inconsistencies.

  • Keeps write paths auditable.

  • Makes retries and logging straightforward.

Standard Action Pipeline

For every write action (any state-changing on-chain operation), use this pipeline:

  1. Validate input

  2. Normalize addresses/amounts

  3. Optional preflight simulation

  4. Build one or more tx payloads

  5. Show pending UI

  6. Submit via host bridge

  7. Wait for confirmations (if required by UX)

  8. Show success/failure

  9. Refresh affected data

Example shell:

Signing Flows (signMessage)

Use signMessage when your backend or protocol needs host-backed signatures.

Guidance:

  • Prefer 'erc1271' unless you explicitly require raw bytes semantics.

  • Persist signature type with the signature payload for verifier correctness.

App Data Injection (onAppData)

The host can pass app-specific context via query data.

Use cases:

  • entry context (resource id, mode, source)

  • feature flags

  • campaign or referral metadata

Pattern:

Never trust raw host data without validation.

Validation and Type Safety

Minimum validation set:

  • address: checksum normalization, strict format checks

  • amount: decimal precision + non-negative + max bounds

  • url: protocol allowlist (https:// by default)

  • text: length limits and rendering-safe escaping

Keep pure validation helpers in a dedicated module and test them directly.

Error Handling and Recovery

Design a predictable error model:

  • validation_error

  • user_rejected

  • network_error

  • host_bridge_error

  • unexpected_error

Normalize unknown errors:

For recoverable host issues:

  • show actionable guidance

  • preserve user input where safe

  • do not silently retry state-changing actions

Concurrency and Race Control

Use request IDs for async operations that may race:

Apply to:

  • debounced search

  • paginated loads

  • wallet-dependent initialization

Generic Starter Skeleton

Last updated

Was this helpful?