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

# Migrating from React Native to the JavaScript SDK

> Move from the legacy React Native SDK to the headless JavaScript SDK, the recommended way to build React Native apps with Dynamic.

> **Recommended: JavaScript SDK for React Native**
>
> The JavaScript SDK is now the default and recommended way to build React Native apps with Dynamic. If you are starting a new app, see the [React Native Quickstart (JS SDK)](https://www.dynamic.xyz/docs/javascript/reference/react-native-quickstart.md). This guide is for migrating an existing legacy React Native SDK app.

## Who this is for

You're moving a React Native app from the legacy SDK (`@dynamic-labs/legacy-client` + `@dynamic-labs/legacy-react-native-extension`) to the headless JavaScript SDK (`@dynamic-labs-sdk/*`). The same JavaScript SDK packages run on web and React Native, so the migration is mostly a shift from a webview-based, UI-heavy SDK to a headless, function-based one.

The JavaScript SDK is **headless**: it gives you functions and React hooks for auth, wallets, and money movement, and you build every screen. The legacy SDK shipped a built-in WebView UI and orchestrated flows for you.

> [!WARNING]
>
> Do not install `@dynamic-labs/legacy-client` or `@dynamic-labs/legacy-react-native-extension` for new code. They are the legacy React Native SDK and have a different API.

## The big shifts

* **No built-in UI.** `client.ui.*`, the Dynamic WebView widget, and all modals are gone. You render every screen.
* **No `ReactNativeExtension` or webview URL.** Use `createDynamicClient` exactly as on web.
* **Extensions are added separately, not chained.** `createClient().extend(ReactNativeExtension()).extend(ViemExtension())` becomes `createDynamicClient()` followed by `addEvmExtension()`, `addSolanaExtension()`, etc.
* **React hooks return a TanStack Query result.** Destructure `.data`, e.g. `const { data: user } = useUser()`.
* **Events are for ambient state, not flow outcomes.** Old `client.<area>.on/off` becomes `onEvent` / `useOnEvent`.
* **External wallets connect through WalletConnect.** There is no built-in wallet list; you build it and deep-link users to wallet apps.
* **Post-login setup is explicit.** WaaS wallet creation, MFA, and device registration no longer happen automatically.
* **Native-specific setup is small.** Polyfills, deep-link schemes, and the WalletConnect native shim are the main additions. See [React Native Setup](https://www.dynamic.xyz/docs/javascript/react-native/setup.md) for details, then [Expo](https://www.dynamic.xyz/docs/javascript/react-native/expo.md) or [Bare React Native](https://www.dynamic.xyz/docs/javascript/react-native/bare-react-native.md) depending on how your app is built.

## Type renames

| Legacy React Native SDK | JavaScript SDK |
| - | - |
| `WalletConnector` | `WalletProvider` |
| `Wallet` | `WalletAccount` |
| `EvmNetwork` / `GenericNetwork` | `NetworkData` |

## Client initialization

### Legacy SDK

```ts theme={"system"}
import { createClient } from '@dynamic-labs/legacy-client';
import { ReactNativeExtension } from '@dynamic-labs/legacy-react-native-extension';
import { ViemExtension } from '@dynamic-labs/legacy-viem-extension';

const dynamicClient = createClient({ environmentId })
  .extend(ReactNativeExtension({ appOrigin: 'https://your-app.com' }))
  .extend(ViemExtension());
```

### JavaScript SDK

```ts theme={"system"}
import { createDynamicClient } from '@dynamic-labs-sdk/client';
import { addEvmExtension } from '@dynamic-labs-sdk/evm';

export const dynamicClient = createDynamicClient({
  environmentId: 'YOUR_ENVIRONMENT_ID',
  metadata: {
    name: 'My App',
    nativeLink: 'myapp://',
    universalLink: 'https://your-app.com',
  },
});

addEvmExtension();
```

> [!WARNING]
>
> An embedded wallet signs inside a WebView that the SDK loads with `metadata.universalLink` as its host origin, so that exact value must be listed under **Allowed Origins** in your environment's [dashboard security settings](https://app.dynamic.xyz). If it isn't, signing fails while reads keep working — see [React Native Setup](https://www.dynamic.xyz/docs/javascript/react-native/setup.md) for the exact error. Expo Go is not supported; use a development build or a bare React Native workflow.

Add the extensions for every chain you support. Chain extensions take no arguments and register against the default client.

```ts theme={"system"}
import { addSolanaExtension } from '@dynamic-labs-sdk/solana';
import { addSuiExtension } from '@dynamic-labs-sdk/sui';
import { addZerodevExtension } from '@dynamic-labs-sdk/zerodev';

addSolanaExtension();
addSuiExtension();
addZerodevExtension();
```

For WalletConnect, add `addWalletConnectEvmExtension()` / `addWalletConnectSolanaExtension()` from the respective `/wallet-connect` subpaths. See [Authenticate with WalletConnect](https://www.dynamic.xyz/docs/javascript/reference/wallets/walletconnect-integration.md).

In React, wrap the app with `<QueryClientProvider>` then `<DynamicProvider client={dynamicClient}>`:

```tsx theme={"system"}
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { DynamicProvider } from '@dynamic-labs-sdk/react-hooks';
import { dynamicClient } from './dynamicClient';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <DynamicProvider client={dynamicClient}>
        {/* Your app */}
      </DynamicProvider>
    </QueryClientProvider>
  );
}
```

For platform-specific setup — polyfills, native modules, deep-link schemes — see [React Native Setup](https://www.dynamic.xyz/docs/javascript/react-native/setup.md), then the guide for your build: [Expo](https://www.dynamic.xyz/docs/javascript/react-native/expo.md) or [Bare React Native](https://www.dynamic.xyz/docs/javascript/react-native/bare-react-native.md).

## Reading state

| Legacy React Native SDK | JavaScript SDK |
| - | - |
| `useDynamicContext().user` | `useUser().data` |
| `dynamicClient.auth.authenticatedUser` | `useUser().data` / `dynamicClient.user` |
| `dynamicClient.wallets.userWallets` | `useGetWalletAccounts().data` / `getWalletAccounts()` |
| `dynamicClient.wallets.getBalance(...)` | `useGetNativeBalance` / `getNativeBalance` |
| `dynamicClient.<area>.on/off(event, cb)` | `onEvent({ event, listener })` / `useOnEvent` |
| `useDynamicContext().network` | `useGetActiveNetworkData().data` / `getActiveNetworkData()` |
| `useReinitialize()` | `initializeClient()` |

## Events to promises

The legacy SDK was event-driven: you subscribed to `onAuthSuccess`, `onWalletAdded`, etc. The JavaScript SDK is imperative — you `await` the function or use a hook's `onSuccess`/`onError`. Events still exist for ambient changes (wallet list changes, session expiry), but they are not how you observe an action you started.

| Legacy event | JavaScript SDK equivalent |
| - | - |
| `onAuthSuccess` / `onAuthFailure` | Promise resolve / `try/catch` |
| `onWalletAdded` / `onWalletRemoved` | `onEvent({ event: 'walletAccountsChanged' })` |
| `onLogout` | `onEvent({ event: 'logout' })` or handle inline after `logout()` |
| `onSignedMessage` | `signMessage()` resolves with the signature |
| `onOtpVerificationResult` | `verifyOTP()` resolves or rejects |
| `client.<area>.on/off` | `onEvent` / `offEvent` / `useOnEvent` |

## Connecting external wallets

The legacy SDK rendered a "Connect Wallet" list inside the WebView. The JavaScript SDK ships no connect UI, so you build the list. On React Native, external wallets are reached through WalletConnect deep links (or QR on tablet/desktop).

**Expo**

```tsx theme={"system"}
import * as Linking from 'expo-linking';
import { waitForClientInitialized, getWalletConnectCatalog } from '@dynamic-labs-sdk/client';
import { connectWithWalletConnectEvm } from '@dynamic-labs-sdk/evm/wallet-connect';
import { appendWalletConnectUriToDeepLink } from '@dynamic-labs-sdk/wallet-connect';

async function connectEvmWallet() {
  await waitForClientInitialized();

  const { uri, approval } = await connectWithWalletConnectEvm({
    addToDynamicWalletAccounts: true,
  });

  const catalog = await getWalletConnectCatalog();
  const wallet = Object.values(catalog.wallets).find((w) => w.chain === 'EVM');
  const base = wallet?.deeplinks?.native ?? wallet?.deeplinks?.universal;

  if (base) {
    await Linking.openURL(
      appendWalletConnectUriToDeepLink({
        deepLinkUrl: base,
        walletConnectUri: uri,
      }),
    );
  }

  const { walletAccounts } = await approval();
  return walletAccounts;
}
```

**Bare React Native**

```tsx theme={"system"}
import { Linking } from 'react-native';
import { waitForClientInitialized, getWalletConnectCatalog } from '@dynamic-labs-sdk/client';
import { connectWithWalletConnectEvm } from '@dynamic-labs-sdk/evm/wallet-connect';
import { appendWalletConnectUriToDeepLink } from '@dynamic-labs-sdk/wallet-connect';

async function connectEvmWallet() {
  await waitForClientInitialized();

  const { uri, approval } = await connectWithWalletConnectEvm({
    addToDynamicWalletAccounts: true,
  });

  const catalog = await getWalletConnectCatalog();
  const wallet = Object.values(catalog.wallets).find((w) => w.chain === 'EVM');
  const base = wallet?.deeplinks?.native ?? wallet?.deeplinks?.universal;

  if (base) {
    await Linking.openURL(
      appendWalletConnectUriToDeepLink({
        deepLinkUrl: base,
        walletConnectUri: uri,
      }),
    );
  }

  const { walletAccounts } = await approval();
  return walletAccounts;
}
```

> [!NOTE]
>
> `connectAndVerifyWithWalletConnectEvm` over a deep link is not supported on React Native because iOS blocks a second deeplink not tied to a user gesture. For deeplink connections, call `connectWithWalletConnectEvm` first, then verify the account from a separate user tap with `verifyWalletAccount`. QR-based `connectAndVerify` works.

See [Authenticate with WalletConnect](https://www.dynamic.xyz/docs/javascript/reference/wallets/walletconnect-integration.md) for the full flow and `walletConnectUserActionRequested` handling.

## API mapping

### Client and extensions

| Legacy (`@dynamic-labs/legacy-*`) | JavaScript SDK (`@dynamic-labs-sdk/*`) |
| - | - |
| `createClient({...})` | `createDynamicClient({...})` from `client` |
| `.extend(ReactNativeExtension({ appOrigin }))` | Removed; headless, no webview |
| `.extend(ViemExtension())` | `addEvmExtension()` from `evm` |
| `.extend(SolanaExtension())` | `addSolanaExtension()` from `solana` |
| `.extend(SuiExtension())` | `addSuiExtension()` from `sui` |
| `.extend(ZeroDevExtension())` | `addZerodevExtension()` from `zerodev` |
| `.extend(EthereumGaslessExtension())` | Folded into `evm` gasless functions |

### Authentication

All new functions are from `@dynamic-labs-sdk/client` unless noted.

| Legacy | JavaScript SDK |
| - | - |
| `client.auth.email.sendOTP({ email })` | `sendEmailOTP({ email })` |
| `client.auth.sms.sendOTP({...})` | `sendSmsOTP({ isoCountryCode, phoneNumber })` |
| `client.auth.email/sms.verifyOTP(code)` | `verifyOTP({ otpVerification, verificationToken })` |
| `client.auth.social.connect({ provider })` | `signInWithSocialPopUp({ provider })` or `signInWithSocialRedirect({ provider, redirectUrl })` |
| `client.auth.social.getAllLinkedAccounts()` | `getUserSocialAccounts()` |
| `client.auth.social.unlink(id)` | `unlinkSocialAccount({ verifiedCredentialId })` |
| `client.auth.authenticatedUser` | `useUser().data` |
| `client.auth.logout()` | `logout()` |
| `client.auth.updateUser(fields)` | `updateUser({ userFields })` |
| `client.auth.verifyUserUpdateOtp(code)` | `verifyOTP({ otpVerification, verificationToken })` |
| Passkey sign-in / manage | `signInWithPasskey`, `registerPasskey`, `getPasskeys`, `deletePasskey` |

> [!NOTE]
>
> OTP is stateless now. `sendEmailOTP` and `sendSmsOTP` return an `otpVerification` object; pass it to `verifyOTP` along with the user's code as `verificationToken`.

> [!NOTE]
>
> `updateUser({ userFields })` may return an `OTPVerification` if the changed email or phone requires verification. Hold it and finish with `verifyOTP`.

### MFA and step-up authentication

| Legacy | JavaScript SDK |
| - | - |
| `client.mfa.addDevice()` | `registerTotpMfaDevice()` |
| `client.mfa.authenticateDevice({ code })` | `authenticateTotpMfaDevice({ code, deviceId })` |
| `client.mfa.getUserDevices()` | `getMfaDevices()` |
| `client.mfa.deleteUserDevice(id)` | `deleteMfaDevice(...)` |
| `client.mfa.getRecoveryCodes()` / `getNewRecoveryCodes()` | `getMfaRecoveryCodes()` / `createNewMfaRecoveryCodes()` |
| `client.mfa.authenticateRecoveryCode(c)` | `authenticateMfaRecoveryCode(...)` |
| `client.mfa.completeAcknowledgement()` | `acknowledgeRecoveryCodes()` |
| `client.stepUpAuth.isStepUpRequired()` | `isUserMissingMfaAuth()` / `isMfaRequiredForAction(...)` / `checkStepUpAuth(...)` |
| `client.stepUpAuth.promptMfa()` / `promptStepUpAuth()` | Build your own prompt; gate with `checkStepUpAuth` then call the matching `authenticate*` function |
| `client.stepUpAuth.verifyPasskeyMfa(...)` | `authenticatePasskeyMFA(...)` |

### Device registration

| Legacy | JavaScript SDK |
| - | - |
| `client.deviceRegistration.getRegisteredDevices()` | `getRegisteredDevices()` |
| `client.deviceRegistration.revokeRegisteredDevice(id)` | `revokeRegisteredDevice({ deviceRegistrationId })` |
| `client.deviceRegistration.revokeAllRegisteredDevices()` | `revokeAllRegisteredDevices()` |

### Wallets

| Legacy | JavaScript SDK |
| - | - |
| `client.wallets.userWallets` | `useGetWalletAccounts().data` / `getWalletAccounts()` |
| `client.wallets.signMessage({...})` | `signMessage({ message, walletAccount })` |
| `client.wallets.sendBalance({...})` | `transferAmount({ amount, recipient, walletAccount })` |
| `client.wallets.getBalance(...)` | `getNativeBalance` / `useGetNativeBalance` |
| `client.wallets.switchNetwork(id)` | `switchActiveNetwork({ networkId, walletAccount })` / `useSwitchActiveNetwork` |
| `client.wallets.connectWallet(...)` / `walletOptions` | On web: `connectWithWalletProvider` / `connectAndVerifyWithWalletProvider` + `getAvailableWalletProvidersData`. On React Native: WalletConnect functions (see above). |
| `client.wallets.embedded.createWallet()` | `createWaasWalletAccounts({ chains: ['EVM' \| 'SOL'], password? })` from `client/waas` |
| `client.wallets.simulateEVMTransaction(...)` | `simulateEvmTransaction(...)` from `evm` |

### WaaS (embedded wallets)

From `@dynamic-labs-sdk/client/waas` unless noted.

| Legacy (`client.wallets.waas.*`) | JavaScript SDK |
| - | - |
| `exportClientKeyshares(id, { password })` | `exportWaasClientKeyshares({ walletAccount, password })` |
| `setPassword({...})` | `setWaasWalletAccountPassword({ password, walletAccount })` |
| `updatePassword({...})` | `updateWaasPassword({ currentPassword, newPassword, walletAccount })` |
| `unlockWallet({...})` | `unlockWallet({ password, walletAccount })` |
| `getWalletRecoveryState(id)` | `getWalletRecoveryState({ walletAccount })` |
| `refreshWalletAccountShares(id)` | `refreshWaasWalletAccountShares({ walletAccount })` |
| `importPrivateKey({...})` | `importWaasPrivateKey({ chain, privateKey })` |
| `backupKeySharesToGoogleDrive(...)` | `backupWaasKeySharesToGoogleDrive({ walletAccount, googleDriveAccessToken })` — you must obtain the Google token yourself |
| `client.ui.wallets.revealEmbeddedWalletKey()` | `<ExportPrivateKey walletAccount={...} />` from `@dynamic-labs-sdk/react-native-embedded-wallet-export` |

> [!NOTE]
>
> `<ExportPrivateKey />` renders the key in a sandboxed WebView and never exposes it to JS. It fills the container you size, so give it a real height (for example, inside a flex-1 Modal).

### Chains

| Legacy | JavaScript SDK |
| - | - |
| `client.solana.getConnection()` | `getSolanaConnection({ networkData })` from `solana` |
| `client.solana.getSigner()` sign/send | `signTransaction` / `signAllTransactions` / `signAndSendTransaction` from `solana` |
| `client.solana.simulateSVMTransaction(...)` | `simulateSolanaTransaction(...)` from `solana` |
| `client.viem.createWalletClient()` | `createWalletClientForWalletAccount({ walletAccount })` from `evm/viem` |
| `client.viem.createPublicClient()` | `createPublicClientFromNetworkData({ networkData })` from `evm/viem` |
| EVM gasless | `sendSponsoredTransaction` / `signSponsoredTransaction` from `evm` |
| `client.sui.getSigner()` sign/execute | `signAndExecuteTransaction` / `signTransaction` from `sui`; `getSuiClient` |
| `client.zeroDev.createKernelClient()` | `createKernelClientForWalletAccount(...)` / `sendUserOperation(...)` from `zerodev` |
| Token swap | `getSwapQuote` + `executeSwapTransaction` from `client` |

## Gaps and changed APIs

A small number of legacy APIs have no direct equivalent in the headless SDK. In most cases you replace them with a combination of available functions or remove the feature.

| Legacy API | Status | What to do |
| - | - | - |
| `client.ui.*` (auth, mfa, wallets, funding, user profile, etc.) | Removed by design | Build the screens yourself. The JavaScript SDK has no built-in UI. |
| `client.reactNative.WebView` | Removed by design | The WebView widget is gone. For exporting a WaaS private key, use the sandboxed `<ExportPrivateKey />` component. |
| `client.wallets.accountAbstraction.isSmartWallet` | No equivalent | No smart-wallet badge. Hide the section or derive it from the wallet account data you already hold. |
| `client.wallets.accountAbstraction.getEOAWallet` | No equivalent | Omit or build around the wallet account you already hold. |
| `client.wallets.getMultichainBalances()` | No direct equivalent | Use `getMultichainTokenBalances` with the addresses you want, or `getNativeBalance` / `getTokenBalances` per wallet. |
| `client.wallets.waas.signRawMessage` | No WaaS raw signer | Use the chain-agnostic `signMessage({ walletAccount, message })`. |
| `client.wallets.simulateEVMTransactionAA` | Shape changed | Use `simulateZerodevUserOperation({ userOperation })` from `zerodev`; it needs a built user operation, not a plain transaction. |
| `client.wallets.waas.backupKeySharesToGoogleDrive` | Requires `googleDriveAccessToken` | The headless SDK does not perform Google OAuth. Obtain the token yourself and pass it in. |
| Raw session JWT (`client.auth.token`) | No getter | The profile is available via `useUser()`. If you need the JWT, fetch it through your own backend or an explicit token endpoint. |
| `client.locale.changeLanguage` | Removed | Handle localization in your own app. |
| Stateful `client.stepUpAuth.*` (prompt / sendOtp / verifyOtp / verifySocial / verifyWallet / resetState + UI) | Replaced by discrete functions | Use `checkStepUpAuth({ scope })`, then call `authenticateTotpMfaDevice`, `authenticateMfaRecoveryCode`, or `authenticatePasskeyMFA` with `requestedScopes`. |
| `viem` "use EOA wallet" toggle (`createWalletClient({ useEoaWallet })`) | Dropped | `createWalletClientForWalletAccount` always uses the wallet account you pass. |

> [!WARNING]
>
> All `@dynamic-labs-sdk/*` packages publish in lockstep. Pin every package to the same minor version or the SDK's runtime version assertion will throw. Mixed versions are the most common source of "function not found" errors after a bump.

## Version and dependency notes

* Keep all `@dynamic-labs-sdk/*` packages on the same minor version.
* WalletConnect on React Native needs `@walletconnect/react-native-compat` (Expo: `npx expo install @walletconnect/react-native-compat`; bare: also install `@react-native-community/netinfo`). Import it as the first line of your polyfills file.
* Passkeys need `react-native-passkey` and platform-specific configuration. See [Passkeys on React Native](https://www.dynamic.xyz/docs/javascript/react-native/passkeys.md).

## Next steps

* [React Native Setup](https://www.dynamic.xyz/docs/javascript/react-native/setup.md) — polyfills, native modules, and deep links
* [Expo](https://www.dynamic.xyz/docs/javascript/react-native/expo.md) / [Bare React Native](https://www.dynamic.xyz/docs/javascript/react-native/bare-react-native.md) — build-specific setup
* [React Native Quickstart (JS SDK)](https://www.dynamic.xyz/docs/javascript/reference/react-native-quickstart.md) — a complete Expo + email + EVM example
* [Adding Extensions](https://www.dynamic.xyz/docs/javascript/reference/adding-extensions.md) — enable the chains you support
* [Authenticate with WalletConnect](https://www.dynamic.xyz/docs/javascript/reference/wallets/walletconnect-integration.md) — external wallet flow on mobile
* [Passkeys on React Native](https://www.dynamic.xyz/docs/javascript/react-native/passkeys.md) — platform configuration for passkeys
* [Business Accounts on React Native](https://www.dynamic.xyz/docs/javascript/react-native/business-accounts.md) — multi-party account specifics
* [Creating a Dynamic Client](https://www.dynamic.xyz/docs/javascript/reference/client/create-dynamic-client.md)
* [Initializing the Dynamic Client](https://www.dynamic.xyz/docs/javascript/reference/client/initialize-dynamic-client.md)


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