Create or Connect a Circles Account from a Mini App
Use @aboutcircles/miniapp-sdk to let users create a new Circles account or connect an existing one without leaving your mini app.
This guide explains the recommended integration pattern for mini apps embedded inside the Circles host. The examples work as a foundation for vanilla JavaScript, React, Vue, Svelte, and other browser-based frameworks.
Overview
A Circles mini app runs inside an <iframe> on its own origin. The surrounding Circles host owns the wallet experience and handles account creation, passkeys, Safe setup, and on-chain Circles registration.
Your mini app should not attempt to create or manage the account directly. It only needs to:
Ask the host to start the account flow with
requestCreateAccount().Subscribe to wallet state changes with
onWalletChange().Render the correct UI for connected, disconnected, and standalone states.
Mini app iframe Circles host
│ │
│ requestCreateAccount() │
├─────────────────────────────────────▶│
│ │ Opens passkey and account flow
│ │
│ onWalletChange(address) │
│◀─────────────────────────────────────┤
│ │The SDK wraps the cross-origin messaging protocol for you. In the normal integration path, your app does not need to call window.parent.postMessage() or manage browser message listeners.
When the flow succeeds, the returned address represents a registered Circles human account. You can use it immediately to read profile data, request transaction signing, or request message signing.
Prerequisites
Before integrating the SDK, make sure you have:
A browser-based application that can be served over HTTP or HTTPS.
A Circles host environment that embeds your application by URL.
A visible button or another direct user action that can start the sign-up flow.
The account flow opens a WebAuthn passkey prompt. Browsers require this prompt to originate from a real user gesture, such as a button click.
Install the SDK
Choose the command that matches your package manager:
Import the account-related SDK functions:
Core API Concepts
requestCreateAccount()
requestCreateAccount()Call this function when a user clicks your sign-up or connect button.
The host determines which experience the user needs:
If the user is already connected, the promise resolves with their address.
If the user is not connected, the host opens the account creation or login flow.
If the user cancels the flow or the request fails, the promise rejects.
Always handle the rejection with try/catch so you can restore the UI cleanly.
onWalletChange()
onWalletChange()Use this function as the source of truth for authentication state.
The callback runs immediately with the current state and runs again whenever the wallet state changes. The value is:
A wallet address when the user is connected.
nullwhen the user is not connected or before the host returns a connected account.
The function returns an unsubscribe callback. Use it during component cleanup in framework-based applications.
isMiniappMode()
isMiniappMode()Use this function to detect whether your app is running inside the Circles host.
If the app is opened as a standalone webpage, there is no host available to answer SDK requests. In that state, show an explanatory message instead of an inactive sign-up button.
Recommended Integration Pattern
Treat wallet state as reactive application state:
Subscribe to
onWalletChange()when the app initializes.Render your connected or disconnected interface from the callback value.
Call
requestCreateAccount()directly from a user click.Use the returned promise only for immediate button state and error handling.
Continue to use
onWalletChange()as the authoritative state source.
This matters because wallet state can change independently of the current click handler, including reconnects and changes initiated elsewhere.
Minimal Vanilla JavaScript Example
The following example contains the complete account integration for a simple browser app.
Why this works
The button calls
requestCreateAccount()directly from the click handler.onWalletChange()controls the rendered wallet state.Standalone mode is handled explicitly.
The button is disabled while the host flow is active, preventing duplicate requests.
React Integration
A reusable hook keeps the host integration isolated from the rest of your UI.
Use the hook in a component:
The click handler calls the SDK action directly, preserving the browser user gesture required by the passkey flow.
Important Rules and Common Pitfalls
Call requestCreateAccount() directly from a user gesture.
The host opens a WebAuthn passkey prompt. Browsers can block the prompt when the request is triggered on page load or after an unrelated asynchronous step.
Use onWalletChange() as the source of truth.
The listener updates after sign-up and whenever wallet state changes later. The result of one promise should not become your only account state.
Check isMiniappMode() before showing an interactive account button.
SDK requests cannot complete when the app is opened outside the host iframe.
Do not send attribution data from the mini app.
The host derives sign-up attribution from the iframe origin. There is no attribution parameter for the mini app to provide.
Handle cancelled flows.
A user can dismiss or reject the host flow. Keep the button usable and show a non-blocking error message.
Avoid breaking the user gesture
Start the host request immediately inside the click handler:
Avoid unrelated asynchronous work before calling the SDK:
Use the Connected Account
After the account flow completes, the user has a registered Circles account. The following examples show common next steps.
Read a Circles profile
Profile data can be read from the Circles RPC endpoint:
A newly created account may not have a profile yet. In that case, circles_getProfileByAddress can return null even though the account is registered and usable.
Request transaction signing
Use sendTransactions() to ask the host to sign and submit one or more transactions with the user's Safe:
The promise resolves with an array of transaction hashes and rejects if the user declines the request.
Request message signing
Use signMessage() when your app needs a user signature:
The default signature type is 'erc1271', which uses EIP-191 prefix hashing. Pass 'raw' only when your verifier expects raw UTF-8 byte signing:
The two signature modes are not interchangeable. Match the SDK option to the verification logic in your application.
Test the Integration Inside the Host
Serve your mini app locally:
For the repository demo, the local URL is:
Open the development host playground and pass your local mini app URL:
The playground embeds your app inside an iframe. Use the account button to run the real host-managed passkey and invitation flow.
Local testing checklist
Before debugging application logic, confirm that:
Your app is loaded through the host playground rather than opened directly.
The iframe URL matches your local development server.
The account action starts from a button click.
isMiniappMode()returnstrueinside the embedded app.Your
onWalletChange()listener is registered when the app initializes.
SDK API Reference
The following API snapshot corresponds to @aboutcircles/miniapp-sdk@0.1.44.
isMiniappMode
() => boolean
Returns true when the app is running inside the host iframe.
onWalletChange
(callback: (address: string | null) => void) => () => void
Emits the current wallet state immediately and emits future changes. Returns an unsubscribe callback.
requestCreateAccount
() => Promise<{ authenticated: boolean; address: string }>
Opens the host-managed account creation or login flow. Call it directly from a user gesture.
sendTransactions
(transactions: Transaction[]) => Promise<string[]>
Requests host signing and submission of transactions with the user's Safe. Returns transaction hashes.
signMessage
(message: string, signatureType?: 'erc1271' | 'raw') => Promise<{ signature: string; verified: boolean }>
Requests a host-managed signature. Defaults to 'erc1271'.
onAppData
(callback: (data: string) => void) => void
Receives app-specific data passed by the host through a ?data= parameter.
Type definitions
Troubleshooting
The passkey prompt does not open or is blocked.
requestCreateAccount() was not called directly from a user gesture.
Move the SDK call into the button click handler. Avoid unrelated await calls before it.
The sign-up button does nothing.
The app was opened as a standalone page, so no host can answer the SDK request.
Load the app through the host playground and check isMiniappMode().
onWalletChange() remains null.
The host has not returned a connected wallet state, the app is not embedded correctly, or the host URL is incorrect.
Verify the playground URL, confirm iframe embedding, and render an appropriate disconnected state while waiting.
The sign-up request succeeds but profile lookup returns null.
Newly registered accounts may not have profile metadata yet.
Treat the account as usable and render an empty-profile state.
Users can start duplicate requests.
The connect button remains enabled during the host flow.
Disable the button until the promise resolves or rejects.
Advanced: Raw postMessage Protocol
postMessage ProtocolPrefer the SDK for application code. It centralizes the host protocol and reduces the amount of browser messaging logic you need to maintain.
For debugging or protocol-level understanding, the account exchange uses a request identifier so that the mini app can match the response to the original request:
A minimal illustrative implementation looks like this:
This example is intentionally minimal. For production code, use the SDK rather than reimplementing the protocol.
Last updated
Was this helpful?