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

# Create a modular wallet

> Create a passkey-backed modular wallet for your user in a web, iOS, or Android app.

Modular wallets sign with a passkey on the user's device and execute through a
Modular Smart Contract Account (MSCA). Register the user's passkey and create
their MSCA to give them a passkey-backed modular wallet ready to transact.

To use a non-passkey signer instead, see
[Use Dynamic as a signer](/wallets/modular/use-dynamic-as-a-signer).

<Info>
  For a complete working app, see the [Circle Smart Account
  example](https://github.com/circlefin/modularwallets-web-sdk/tree/master/examples/circle-smart-account)
  in the modular wallets web SDK repository.
</Info>

## Prerequisites

Before you begin this tutorial, ensure that you've:

* Created a [Circle Console](https://console.circle.com/) account and
  configured:
  * A [client key](/api-reference/keys#client-keys) for the modular wallets SDK:
    **Console → Keys → Create a key → Client Key**
  * A passkey domain matching the client key's web domain: **Console → Wallets →
    Modular Wallets → Passkey**
* Installed [Node.js 22+](https://nodejs.org/) (web),
  [Xcode](https://developer.apple.com/xcode/) (iOS), or
  [Android Studio](https://developer.android.com/studio) (Android).
* Installed the modular wallets SDK for your platform:
  [Web](https://www.npmjs.com/package/@circle-fin/modular-wallets-core),
  [iOS](https://github.com/circlefin/modularwallets-ios-sdk#installation), or
  [Android](https://github.com/circlefin/modularwallets-android-sdk#installation).

  <Note>
    For iOS and Android apps, passkeys are bound to a web domain. Publish an
    Apple [associated domains
    file](https://developer.apple.com/documentation/xcode/configuring-an-associated-domain)
    or Android [Digital Asset Links
    file](https://developer.android.com/identity/sign-in/credential-manager#add-support-dal)
    at your domain root so your native app can use the passkey.
  </Note>

## Step 1. Configure your client key and backend URL

Make your client key and the modular wallets backend URL available to your app.
The backend URL is the same for every app:
`https://modular-sdk.circle.com/v1/rpc/w3s/buidl`.

<CodeGroup>
  ```text Web SDK (TypeScript) theme={null}
  # .env
  VITE_CLIENT_KEY=YOUR_CLIENT_KEY
  VITE_CLIENT_URL=https://modular-sdk.circle.com/v1/rpc/w3s/buidl
  ```

  ```swift iOS SDK (Swift) theme={null}
  let CLIENT_KEY = "YOUR_CLIENT_KEY"
  let clientUrl = "https://modular-sdk.circle.com/v1/rpc/w3s/buidl"
  ```

  ```kotlin Android SDK (Kotlin) theme={null}
  const val CLIENT_KEY = "YOUR_CLIENT_KEY"
  const val clientUrl = "https://modular-sdk.circle.com/v1/rpc/w3s/buidl"
  ```

  ```java Android SDK (Java) theme={null}
  public static final String CLIENT_KEY = "YOUR_CLIENT_KEY";
  public static final String CLIENT_URL = "https://modular-sdk.circle.com/v1/rpc/w3s/buidl";
  ```
</CodeGroup>

## Step 2. Register a passkey for the user

Create a passkey transport, then register a new passkey credential. The
credential becomes the wallet's signer. To sign a returning user back in, pass
`WebAuthnMode.Login` instead of `Register`.

<CodeGroup>
  ```typescript Web SDK (TypeScript) theme={null}
  import {
    toPasskeyTransport,
    toWebAuthnCredential,
    WebAuthnMode,
  } from "@circle-fin/modular-wallets-core";

  const clientKey = import.meta.env.VITE_CLIENT_KEY as string;
  const clientUrl = import.meta.env.VITE_CLIENT_URL as string;

  const passkeyTransport = toPasskeyTransport(clientUrl, clientKey);

  const credential = await toWebAuthnCredential({
    transport: passkeyTransport,
    mode: WebAuthnMode.Register,
    username: "your-username",
  });
  ```

  ```swift iOS SDK (Swift) theme={null}
  import CircleModularWalletsCore

  Task {
      do {
          let transport = toPasskeyTransport(clientKey: CLIENT_KEY)

          let credential = try await toWebAuthnCredential(
              transport: transport,
              userName: "your-username",
              mode: WebAuthnMode.register
          )

          // Create a WebAuthn owner account from the credential.
          let webAuthnAccount = toWebAuthnAccount(credential)
      } catch {
          print(error)
      }
  }
  ```

  ```kotlin Android SDK (Kotlin) theme={null}
  CoroutineScope(Dispatchers.IO).launch {
      try {
          val transport = toPasskeyTransport(context, CLIENT_KEY, clientUrl)

          val credential = toWebAuthnCredential(
              context,
              transport,
              "your-username",
              WebAuthnMode.Register,
          )

          // Create a WebAuthn owner account from the credential.
          val webAuthnAccount = toWebAuthnAccount(credential)
      } catch (e: Exception) {
          e.printStackTrace()
      }
  }
  ```

  ```java Android SDK (Java) theme={null}
  try {
      HttpTransport transport = toPasskeyTransport(
          context,
          CLIENT_KEY,
          clientUrl
      );

      CompletableFuture<WebAuthnCredential> credentialFuture = new CompletableFuture<>();
      toWebAuthnCredential(
          context,
          transport,
          "your-username",
          WebAuthnMode.Register,
          new CustomContinuation<>(credentialFuture)
      );
      WebAuthnCredential credential = credentialFuture.join();

      // Create a WebAuthn owner account from the credential.
      WebAuthnAccount webAuthnAccount = toWebAuthnAccount(credential);
  } catch (Exception e) {
      e.printStackTrace();
  }
  ```
</CodeGroup>

## Step 3. Create a transport

The modular transport routes user operations through Circle's bundler and
paymaster for the blockchain you target. The example below uses Arc Testnet. For
other options, see [supported blockchains](/wallets/supported-blockchains) and
the URL format documented in
[`toModularTransport`](/sdks/modular/web-sdk#function-tomodulartransport).

<CodeGroup>
  ```typescript Web SDK (TypeScript) theme={null}
  import { toModularTransport } from "@circle-fin/modular-wallets-core";
  import { arcTestnet } from "viem/chains";

  const modularTransport = toModularTransport(
    `${clientUrl}/arcTestnet`,
    clientKey,
  );
  ```

  ```swift iOS SDK (Swift) theme={null}
          let modularTransport = toModularTransport(
              clientKey: CLIENT_KEY,
              url: "\(clientUrl)/arcTestnet"
          )
  ```

  ```kotlin Android SDK (Kotlin) theme={null}
          val modularTransport = toModularTransport(
              context,
              CLIENT_KEY,
              "$clientUrl/arcTestnet",
          )
  ```

  ```java Android SDK (Java) theme={null}
      Transport modularTransport = toModularTransport(
          context,
          CLIENT_KEY,
          clientUrl + "/arcTestnet"
      );
  ```
</CodeGroup>

## Step 4. Create the bundler client and smart account

The bundler client submits user operations through Circle's bundler. The smart
account is the user's MSCA, with the passkey credential as its signer. Its
address is deterministic, so you can read it before the account is deployed
onchain.

<CodeGroup>
  ```typescript Web SDK (TypeScript) theme={null}
  import { toCircleSmartAccount } from "@circle-fin/modular-wallets-core";
  import { createPublicClient } from "viem";
  import {
    createBundlerClient,
    toWebAuthnAccount,
  } from "viem/account-abstraction";
  import { arcTestnet } from "viem/chains";

  const client = createPublicClient({
    chain: arcTestnet,
    transport: modularTransport,
  });

  const smartAccount = await toCircleSmartAccount({
    client,
    owner: toWebAuthnAccount({ credential }),
  });

  const bundlerClient = createBundlerClient({
    account: smartAccount,
    chain: arcTestnet,
    transport: modularTransport,
  });

  console.log("Smart account address:", smartAccount.address);
  ```

  ```swift iOS SDK (Swift) theme={null}
          let bundlerClient = BundlerClient(
              chain: ArcTestnet,
              transport: modularTransport
          )

          let smartAccount = try await toCircleSmartAccount(
              client: bundlerClient,
              owner: webAuthnAccount
          )

          print("Smart account address: \(smartAccount.address)")
  ```

  ```kotlin Android SDK (Kotlin) theme={null}
          val bundlerClient = BundlerClient(
              ArcTestnet,
              modularTransport,
          )

          val smartAccount = toCircleSmartAccount(
              bundlerClient,
              webAuthnAccount,
          )

          println("Smart account address: ${smartAccount.address}")
  ```

  ```java Android SDK (Java) theme={null}
      BundlerClient bundlerClient = new BundlerClient(
          ArcTestnet.INSTANCE,
          modularTransport
      );

      CompletableFuture<CircleSmartAccount> smartAccountFuture = new CompletableFuture<>();
      toCircleSmartAccount(
          bundlerClient,
          webAuthnAccount,
          new CustomContinuation<>(smartAccountFuture)
      );
      CircleSmartAccount smartAccount = smartAccountFuture.join();

      System.out.println("Smart account address: " + smartAccount.getAddress());
  ```
</CodeGroup>

The user now has a modular wallet, signed by their passkey, ready to transact.

## Next steps

* [Set up passkey recovery](/wallets/modular/set-up-passkey-recovery) so the
  user can restore access if they lose their passkey. Strongly recommended
  before shipping to production.
* [Transfer tokens](/wallets/modular/transfer-tokens) from the modular wallet
  with gas sponsored by Circle's paymaster.
* [Sign and verify messages](/wallets/modular/sign-and-verify-messages) for
  Sign-In With Ethereum (SIWE) or EIP-712 typed data.
