# 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`](https://www.npmjs.com/package/@aboutcircles/miniapp-sdk).

{% hint style="info" %}
If you are a vibecode developer, you can simply copy the entire page using the button above and paste in your coding environment
{% endhint %}

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

```ts
type Transaction = {
  to: string;
  data?: string;
  value?: string; // hex string, e.g. "0x0"
};
```

#### Project Setup

**Create the app**

```bash
npm create vite@latest my-miniapp -- --template vanilla
cd my-miniapp
npm i
npm i @aboutcircles/miniapp-sdk viem
```

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

```bash
npm i @aboutcircles/sdk-core @aboutcircles/sdk-rpc @aboutcircles/sdk-profiles @aboutcircles/sdk-transfers @aboutcircles/sdk-utils
```

**Suggested structure**

```
src/
  app/
    state.js
    actions.js
    ui.js
  host/
    bridge.js
  chain/
    client.js
  utils/
    format.js
    validation.js
main.js
```

Keep host bridge logic isolated from domain logic.

#### Bootstrapping the Host Bridge

Create a dedicated bridge module:

```js
// src/host/bridge.js
import {
  isMiniappMode,
  onAppData,
  onWalletChange,
  sendTransactions,
  signMessage,
} from '@aboutcircles/miniapp-sdk';

export { isMiniappMode, onAppData, onWalletChange, sendTransactions, signMessage };
```

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:

```js
import { getAddress } from 'viem';
import { onWalletChange } from './host/bridge.js';

let connectedAddress = null;

onWalletChange(async (address) => {
  try {
    connectedAddress = address ? getAddress(address) : null;
  } catch {
    connectedAddress = null;
  }

  if (!connectedAddress) {
    resetAccountScopedState();
    renderDisconnected();
    return;
  }

  resetAccountScopedState();
  await loadInitialDataFor(connectedAddress);
  renderConnected(connectedAddress);
});
```

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.

```js
import { sendTransactions } from './host/bridge.js';

function toHexValue(value) {
  return value ? `0x${BigInt(value).toString(16)}` : '0x0';
}

export function formatTxForHost(tx) {
  return {
    to: tx.to,
    data: tx.data || '0x',
    value: toHexValue(tx.value || 0n),
  };
}

export async function submitTransactions(txs) {
  return sendTransactions(txs.map(formatTxForHost));
}
```

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:

```js
export async function performAction(input) {
  validateInput(input);

  const txs = buildTransactions(input);
  setStatus('pending', 'Submitting transaction...');

  try {
    const hashes = await submitTransactions(txs);
    await maybeWaitForReceipts(hashes);
    setStatus('success', `Submitted ${hashes.length} transaction(s).`);
    await reloadRelevantState();
  } catch (err) {
    setStatus('error', normalizeError(err));
  }
}
```

#### Signing Flows (`signMessage`)

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

```js
import { signMessage } from './host/bridge.js';

const result = await signMessage('Example message', 'erc1271');
// result: { signature, verified }
```

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:

```js
import { onAppData } from './host/bridge.js';

onAppData((raw) => {
  const data = safelyParseHostData(raw); // your parser/validator
  applyHostContext(data);
});
```

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:

```js
export function normalizeError(err) {
  if (!err) return 'Unknown error';
  if (typeof err === 'string') return err;
  return err.shortMessage || err.message || String(err);
}
```

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:

```js
let reqId = 0;

export async function runLatest(task) {
  const id = ++reqId;
  const result = await task();
  if (id !== reqId) return null; // stale
  return result;
}
```

Apply to:

* debounced search
* paginated loads
* wallet-dependent initialization

#### Generic Starter Skeleton

```js
import { getAddress, isAddress } from 'viem';
import {
  isMiniappMode,
  onWalletChange,
  sendTransactions,
} from '@aboutcircles/miniapp-sdk';

let connected = null;

function toHexValue(value) {
  return value ? `0x${BigInt(value).toString(16)}` : '0x0';
}

function status(msg) {
  document.getElementById('status').textContent = msg;
}

async function sendExample() {
  if (!connected || !isAddress(connected)) throw new Error('Wallet not connected');
  const txs = [{ to: connected, data: '0x', value: toHexValue(0n) }];
  const hashes = await sendTransactions(txs);
  status(`Submitted: ${hashes.join(', ')}`);
}

onWalletChange((address) => {
  try {
    connected = address ? getAddress(address) : null;
  } catch {
    connected = null;
  }
  status(connected ? `Connected: ${connected}` : 'Disconnected');
});

status(isMiniappMode() ? 'Host mode' : 'Standalone mode');
document.getElementById('send').addEventListener('click', () => {
  sendExample().catch((err) => status(`Error: ${err.message}`));
});
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.aboutcircles.com/miniapps/embedded-mini-apps.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
