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

# JavaScript Quickstart

Get started with Dynamic's JavaScript SDK to add embedded wallets, EVM support, and flexible authentication (including email OTP) to your app in just a few lines.

<Note>
  Before you start: a JavaScript-compatible environment and a Dynamic environment ID from the [Dynamic developer console](https://console.dynamic.xyz).
</Note>

<Tabs>
  <Tab title="Agent-friendly">
    <Tip>
      Add the Dynamic docs MCP to your AI editor first — your agent can then query the docs directly.
    </Tip>

    <Tabs>
      <Tab title="Cursor">
        [Click here to add the MCP to Cursor.](https://cursor.com/en/install-mcp?name=dynamic\&config=eyJ1cmwiOiJodHRwczovL3d3dy5keW5hbWljLnh5ei9kb2NzL21jcCJ9)
      </Tab>

      <Tab title="Claude Code">
        ```bash theme={"system"}
        claude mcp add --transport http dynamic https://www.dynamic.xyz/docs/mcp
        ```
      </Tab>

      <Tab title="Codex">
        ```bash theme={"system"}
        codex mcp add dynamic --url https://www.dynamic.xyz/docs/mcp
        ```
      </Tab>

      <Tab title="Other">
        If your editor or agent supports remote HTTP MCP, add a server that points at `https://www.dynamic.xyz/docs/mcp`. The exact steps depend on the product; use its MCP documentation for URL-based or HTTP transport setup.
      </Tab>
    </Tabs>

    Copy this prompt into your LLM (e.g. Claude, ChatGPT, Cursor) to implement Dynamic in your JavaScript app.

    <div data-llm-quickstart>
      ````markdown theme={"system"}
      # Javascript SDK Quickstart

      ## Purpose and Usage

      This document provides structured quickstart information for the Dynamic Javascript SDK. It is designed to be consumed by LLMs to generate accurate installation commands and code examples based on user requirements.

      **How to use this document:**

      Start by asking the user which path they want:

      > "Would you like a **Quick start** (email login + EVM + embedded wallet, zero configuration — recommended if you're just getting started) or a **Custom setup** (choose your chains, auth method, and wallet type)?"

      - If the user chooses **Quick start**: follow the Quick Start Path section exactly. Do not ask any configuration questions — execute the happy path as written.
      - If the user chooses **Custom setup**: follow the Custom Setup Path section and ask all configuration questions before proceeding.

      ---

      ## Quick Start Path

      > Use this path when the user chooses Quick start. Execute every step in order. Do not skip steps. Do not ask configuration questions.

      **Stack:** Vite + npm + EVM only + email OTP + embedded WaaS wallet

      ### Step 1 — Prerequisites

      Tell the user:
      - Node.js 18+ is required
      - A Vite-based project (or equivalent bundler) is required — the SDK uses ES module imports that cannot be resolved by a plain `<script>` tag without a bundler or dev server
      - A Dynamic environment ID is required — get one at https://console.dynamic.xyz/dashboard/developer/api

      Ask the user for their environment ID before proceeding.

      ### Step 2 — Install

      ```
      npm i @dynamic-labs-sdk/client @dynamic-labs-sdk/evm viem
      ```

      `viem` is a peer dependency of `@dynamic-labs-sdk/evm`, so install it explicitly.

      ### Step 3 — Initialize the client and register the EVM extension

      ```typescript
      import { createDynamicClient, initializeClient } from "@dynamic-labs-sdk/client";
      import { addEvmExtension } from "@dynamic-labs-sdk/evm";

      const client = createDynamicClient({
        autoInitialize: false,
        environmentId: "YOUR_ENVIRONMENT_ID",
        metadata: {
          name: "My App",
          universalLink: "http://localhost:5173",
        },
      });

      // Register extensions immediately after creating the client, before initialization.
      // The client argument is optional: omit it unless you run multiple clients.
      addEvmExtension();

      void initializeClient();
      ```

      ### Step 4 — Build a wallet picker UI (required — SDK has no built-in modal)

      The JS SDK is headless — there is no built-in wallet picker or modal. You must build one using `getAvailableWalletProvidersData()`.

      ```typescript
      import { getAvailableWalletProvidersData, connectAndVerifyWithWalletProvider } from "@dynamic-labs-sdk/client";

      // Synchronous: returns the current list, no await
      const providers = getAvailableWalletProvidersData();

      // Render a button for each provider in your UI, then on user selection
      // pass its `key` as `walletProviderKey`. Resolves to the verified WalletAccount.
      const walletAccount = await connectAndVerifyWithWalletProvider({
        walletProviderKey: selectedProvider.key,
      });
      ```

      ### Step 5 — Email OTP authentication

      ```typescript
      import { sendEmailOTP, verifyOTP } from "@dynamic-labs-sdk/client";

      // 1. Send OTP to the user's email. Resolves to the OTPVerification object itself.
      const otpVerification = await sendEmailOTP({ email: "user@example.com" });

      // 2. After user enters the OTP code from their email:
      // IMPORTANT: the parameter is `verificationToken`, not `otp`
      await verifyOTP({
        otpVerification,
        verificationToken: "123456",
      });
      ```

      ### Step 6 — Create the WaaS wallet (required — not automatic)

      **After `verifyOTP` succeeds, the wallet does not exist yet.** You must call `createWaasWalletAccounts()` explicitly. Do not guard this with an `accounts.length === 0` check — the SDK may return a stale non-zero list immediately after auth, causing the creation step to be silently skipped.

      ```typescript
      // WaaS functions are exported from the /waas subpath
      import { getChainsMissingWaasWalletAccounts, createWaasWalletAccounts } from "@dynamic-labs-sdk/client/waas";

      // Call unconditionally immediately after verifyOTP succeeds
      const missingChains = getChainsMissingWaasWalletAccounts();
      await createWaasWalletAccounts({ chains: missingChains });
      ```

      ### Step 7 — Display the wallet address

      ```typescript
      import { getWalletAccounts } from "@dynamic-labs-sdk/client";

      // Synchronous, and use wallet.address — not wallet.accountAddress
      const accounts = getWalletAccounts();
      const address = accounts[0]?.address;
      console.log("Wallet address:", address);
      ```

      ### Step 8 — Listen for wallet state changes

      ```typescript
      import { onEvent } from "@dynamic-labs-sdk/client";

      // Use onEvent(), not client.on() — client.on() does not exist.
      // The listener is part of the first argument, and receives { walletAccounts }.
      const unsubscribe = onEvent({
        event: "walletAccountsChanged",
        listener: ({ walletAccounts }) => {
          console.log("Wallet updated:", walletAccounts[0]?.address);
        },
      });
      ```

      ### Step 9 — Logout

      ```typescript
      import { logout } from "@dynamic-labs-sdk/client";

      await logout();
      ```

      ---

      ## Custom Setup Path

      > Use this path when the user chooses Custom setup. Ask ALL questions below before generating any code.

      **Questions to ask the user:**

      1. Which package manager do you prefer? (npm, yarn, pnpm, bun)
      2. Which chains do you want to support? (EVM, Solana, Sui, Aptos, Bitcoin, Tron, Starknet, TON, Cosmos, Aleo, Stellar — one or more)
      3. If EVM or Solana: do you need only embedded wallets (smaller bundle) or the full extension including external wallet discovery?
      4. If EVM or Solana: do you need WalletConnect for cross-device connections (QR code / deep link)?

      **Only after receiving answers**, use the sections below to generate the correct setup.

      ### Package Manager Commands
      - `npm`: `npm i`
      - `yarn`: `yarn add`
      - `pnpm`: `pnpm add`
      - `bun`: `bun add`

      ### Package Mapping

      Install every peer dependency listed below explicitly: npm resolves peers automatically, pnpm and yarn do not.

      - Core (always required): `@dynamic-labs-sdk/client`
      - EVM: `@dynamic-labs-sdk/evm` plus its `viem` peer dependency
      - Solana: `@dynamic-labs-sdk/solana`
      - Sui: `@dynamic-labs-sdk/sui`
      - Aptos: `@dynamic-labs-sdk/aptos`
      - Bitcoin: `@dynamic-labs-sdk/bitcoin`
      - Tron: `@dynamic-labs-sdk/tron`
      - Starknet: `@dynamic-labs-sdk/starknet`
      - TON: `@dynamic-labs-sdk/ton` plus its peer dependencies `@ton/core`, `@ton/crypto`, `@ton/ton`, `@tonconnect/sdk`, `@tonconnect/ui`
      - Cosmos: `@dynamic-labs-sdk/cosmos`
      - Aleo: `@dynamic-labs-sdk/aleo` (no default extension: use `addWaasAleoExtension` from `@dynamic-labs-sdk/aleo/waas` for embedded wallets, or `addAleoWalletStandardExtension` from `@dynamic-labs-sdk/aleo/walletStandard` for external wallets)
      - Stellar: `@dynamic-labs-sdk/stellar`
      - WalletConnect (EVM/Solana only, optional): use `addWalletConnectEvmExtension` from `@dynamic-labs-sdk/evm/wallet-connect`, `addWalletConnectSolanaExtension` from `@dynamic-labs-sdk/solana/wallet-connect`. See [WalletConnect Integration](/javascript/reference/wallets/walletconnect-integration).

      ### Extension Notes
      - **Default extensions** (`addEvmExtension`, `addSolanaExtension`, `addTonExtension`, `addSuiExtension`, `addTronExtension`, `addBitcoinExtension`) bundle external wallet discovery + embedded (WaaS) support
      - **Embedded (WaaS) wallets** are supported on EVM, Solana, Sui, TON, Tron, Bitcoin, and Aleo. **Cosmos, Starknet, Aptos, and Stellar** are injected-wallet-only, and `createWaasWalletAccounts()` ignores chains outside the supported set
      - **Standalone embedded-only extensions** (`addWaasEvmExtension`, `addWaasSolanaExtension`, `addWaasTonExtension`, `addWaasSuiExtension`, `addWaasTronExtension`, `addWaasBitcoinExtension`, `addWaasAleoExtension`) produce a smaller bundle — use when the user only needs embedded wallets. They live on the chain package's `/waas` subpath, for example `import { addWaasEvmExtension } from '@dynamic-labs-sdk/evm/waas';`
      - Every extension and SDK function takes an **optional trailing client argument** that defaults to the client created by `createDynamicClient()`. Omit it unless the app runs multiple clients
      - Create the client with `autoInitialize: false`, register extensions immediately after `createDynamicClient()`, then call `void initializeClient()`
      - See [Adding EVM Extensions](/javascript/reference/evm/adding-evm-extensions), [Adding Solana Extensions](/javascript/reference/solana/adding-solana-extensions), and [Adding TON Extensions](/javascript/reference/ton/adding-ton-extension) for the full list of standalone options

      ### External wallet picker (only if using external wallets)

      The JS SDK is headless, so there is no built-in wallet picker or modal. Build one with `getAvailableWalletProvidersData()` and connect the user with `connectAndVerifyWithWalletProvider()`:

      ```typescript
      import { getAvailableWalletProvidersData, connectAndVerifyWithWalletProvider } from "@dynamic-labs-sdk/client";

      const providers = getAvailableWalletProvidersData();

      // Replace "metamask" with the key of the provider the user selected.
      const walletAccount = await connectAndVerifyWithWalletProvider({
        walletProviderKey: "metamask",
      });

      console.log("Connected wallet:", walletAccount.address);
      ```

      ### Client initialization

      Create the client with `autoInitialize: false`, register extensions, then start initialization:

      ```typescript
      import { createDynamicClient, initializeClient } from "@dynamic-labs-sdk/client";
      // import the extension add functions for the chains you selected

      const client = createDynamicClient({
        autoInitialize: false,
        environmentId: "YOUR_ENVIRONMENT_ID",
        metadata: {
          name: "My App",
          universalLink: "http://localhost:5173",
        },
      });

      // addEvmExtension();
      // addSolanaExtension();
      // ... register all selected extensions

      void initializeClient();
      ```

      ### Post-Auth Patterns (apply to all custom setups that use embedded wallets)

      These steps are **required** and are not in the client init — they must be added to your post-auth flow:

      - **WaaS wallet creation** — not automatic. Call immediately after auth succeeds:
        ```typescript
        import { getChainsMissingWaasWalletAccounts, createWaasWalletAccounts } from "@dynamic-labs-sdk/client/waas";

        const missingChains = getChainsMissingWaasWalletAccounts();
        await createWaasWalletAccounts({ chains: missingChains });
        ```
      - **Wallet address** — use `getWalletAccounts()` (synchronous) → `wallet.address` (not `wallet.accountAddress`)
      - **Events** — use `onEvent({ event, listener })`, not `client.on()` (does not exist)
      - **OTP verification** — parameter is `verificationToken`, not `otp`

      ### Valid Combinations
      - Any single chain, or any combination of two or more chains
      - At least one chain must be selected

      ### Documentation
      All docs for this SDK: https://docs.dynamic.xyz (paths starting with `/javascript/`)
      Environment ID: https://console.dynamic.xyz/dashboard/developer/api

      ---

      ## React Native (Expo and bare)

      **For React Native, use the dedicated React Native Quickstart (JS SDK)** (`/javascript/reference/react-native-quickstart`, agent prompt id: `javascript-react-native`) — not this document and not the Legacy React Native SDK (`/react-native/reference/quickstart`, agent prompt id: `react-native`).

      That guide covers Expo vs bare setup, polyfills, `metadata.nativeLink` / `universalLink`, providers, and the same email + EVM + WaaS happy path as the React web quickstart. Platform details: https://docs.dynamic.xyz/javascript/react-native/setup

      ---

      ## Critical API Reference (apply to both paths)

      | Correct | Incorrect | Notes |
      |---|---|---|
      | `addEvmExtension()` | `addEvmExtension(myClient)` when there is one client | The client argument is optional and defaults to the client you created |
      | `const otpVerification = await sendEmailOTP({ email })` | `const { otpVerification } = await sendEmailOTP({ email })` | `sendEmailOTP` resolves to the `OTPVerification` object itself |
      | `verifyOTP({ otpVerification, verificationToken: '123456' })` | `verifyOTP({ otpVerification, otp: '123456' })` | Parameter is `verificationToken` not `otp` |
      | `connectAndVerifyWithWalletProvider({ walletProviderKey })` | `connectAndVerifyWithWalletProvider({ walletProvider })` | Pass the provider's `key`; it resolves to a `WalletAccount` |
      | `const accounts = getWalletAccounts()` | `await getWalletAccounts()` | `getWalletAccounts` and `getAvailableWalletProvidersData` are synchronous |
      | `wallet.address` | `wallet.accountAddress` | Use `address` on objects from `getWalletAccounts()` |
      | `onEvent({ event, listener })` | `onEvent({ event }, listener)` or `client.on(event, listener)` | The listener goes inside the first argument and receives `{ walletAccounts }` |
      | Call `createWaasWalletAccounts()` unconditionally after auth | Guard with `accounts.length === 0` | Stale list may cause silent skip |

      ---

      ## Building a headless integration?

      The JavaScript SDK is always headless. See the [Authentication screens](/javascript/authentication-methods/headless-authentication) for a full list of screens your app needs to handle.

      ---

      ## Step-Up Authentication

      **Required before accepting the `2026_04_01` API version** — verify your
      minimum API version in [Dashboard > Developers > API & SDK Keys](https://console.dynamic.xyz/dashboard/developer/api).

      The JavaScript SDK is always headless — there is no built-in step-up UI.
      **You must always handle step-up authentication manually:** check
      requirements, call the appropriate verification method, and wait for the
      elevated token before proceeding with the sensitive operation.

      See [Step-up authentication](/javascript/authentication-methods/step-up-auth/overview) for the full implementation guide.

      ---

      ## Device Registration

      **Required before accepting the `2026_04_01` API version** — verify your
      minimum API version in [Dashboard > Developers > API & SDK Keys](https://console.dynamic.xyz/dashboard/developer/api).

      The JavaScript SDK is always headless — there is no built-in device
      registration UI. **You must always handle device registration manually:**
      check whether the current device needs registration after auth, detect and
      process the email verification redirect, and listen for completion events.

      Full guide: https://docs.dynamic.xyz/javascript/authentication-methods/device-registration

      ---

      ## Troubleshooting — Dashboard Configuration

      If the app builds successfully but login fails, wallets don't appear, or you see network/auth errors, the most common causes are Dynamic dashboard settings that haven't been configured. Ask the user to verify each of the following in their Dynamic dashboard at https://console.dynamic.xyz:

      ### 1 — Chains not enabled
      The EVM chain (or any other chain used in the quickstart) must be enabled under **Chains & Networks** in the dashboard. If the chain isn't toggled on, wallet creation and signing will silently fail or return empty results.

      ### 2 — Login method not enabled
      Email OTP (or whichever login method the app uses) must be toggled on under **Sign-in Methods**. If it isn't enabled, the auth flow will fail at the point of sending the OTP.

      ### 3 — Embedded wallets not enabled
      If the app uses WaaS embedded wallets, the **Embedded Wallets** feature must be enabled under **Wallets** in the dashboard. Without it, `createWaasWalletAccounts()` will return an error or produce no wallet.

      ### 4 — CORS origin not allowlisted
      The URL the app is running on (e.g. `http://localhost:5173`) must be added to the **Allowed Origins** list in the dashboard under **Security**. Without it, all SDK requests will be blocked by CORS. Add the exact origin including port.

      If all four are configured and the app is still not working, check the browser console for error codes and refer to https://docs.dynamic.xyz/overview/troubleshooting/general.
      ````
    </div>
  </Tab>

  <Tab title="Manual">
    <iframe frameBorder="0" width="100%" height="600px" src="https://documentation-quickstarts.vercel.app/javascript?color-scheme=dark" allow="clipboard-write" className="rn-qs-dark">
      {' '}
    </iframe>

    <iframe frameBorder="0" width="100%" height="600px" src="https://documentation-quickstarts.vercel.app/javascript?color-scheme=light" allow="clipboard-write" className="rn-qs-light">
      {' '}
    </iframe>

    <Tip>
      To quickly test the login flow, you can enable [Test
      Accounts](/docs/platform/dashboard/test-accounts) in Sandbox mode.
    </Tip>

    <Tip>
      For more information about the client, check out the two in depth guides below:

      * [Creating a Dynamic Client](/docs/javascript/reference/client/create-dynamic-client)
      * [Initializing the Dynamic Client](/docs/javascript/reference/client/initialize-dynamic-client)
    </Tip>

    <Tip>
      **For sponsored gas (no end-user gas):** See [ZeroDev](/docs/javascript/reference/zerodev/adding-zerodev-extension) (EVM) and [SVM gas sponsorship](/docs/javascript/reference/solana/svm-gas-sponsorship) (Solana).
    </Tip>

    <Tip>
      **Extensions at a glance:** To add chain or wallet support, add the extension for your chain—e.g. [EVM](/docs/javascript/reference/evm/adding-evm-extensions), [Solana](/docs/javascript/reference/solana/adding-solana-extensions), [ZeroDev (gasless EVM)](/docs/javascript/reference/zerodev/adding-zerodev-extension)—or see [Adding extensions](/docs/javascript/reference/adding-extensions) and the [Wallets](/docs/javascript/reference/waas/checking-if-waas-is-enabled) section for the full list.
    </Tip>

    <Tip>
      Running into issues? See [Troubleshooting](/docs/get-started/troubleshooting/general) in the Overview docs.
    </Tip>

    ## Next Steps

    <Warning>
      The JavaScript SDK is always headless — you must implement step-up
      authentication and device registration manually. Without these, users
      will be blocked during login or sensitive actions.
      See the [upgrade guide](/docs/get-started/migrations/api/2026_04_01).
    </Warning>

    * [Step-up authentication](/docs/javascript/authentication-methods/step-up-auth/overview)
    * [Device registration](/docs/javascript/authentication-methods/device-registration)

    <Note>
      The JavaScript SDK is always headless. See the [Authentication screens](/docs/javascript/authentication-methods/headless-authentication) for a full list of screens your app needs to handle.
    </Note>
  </Tab>
</Tabs>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.