# Turnkey

> Use Turnkey with Wallet APIs for EIP-7702, sponsorship, and batching

> For the complete documentation index, see [llms.txt](/docs/llms.txt).

<Info>`@alchemy/wallet-apis` (v5.x.x) is currently in beta but is the recommended replacement for `@account-kit/wallet-client` (v4.x.x). If you run into any issues, please [reach out](mailto:support@alchemy.com).</Info>

Upgrade existing Turnkey wallets to Wallet APIs to enable gasless transactions, batching, and more in under 10 minutes. Keep Turnkey for key management, no wallet migration needed. Add battle-tested transaction infrastructure using EIP-7702 to upgrade EOAs to Wallet APIs:

* [#1 gas abstraction infrastructure](https://www.bundlebear.com/erc4337-bundlers/all) on the market
* [370M+](https://www.bundlebear.com/erc4337-paymasters/all) sponsored transactions
* 99.9% SLAs
* Trusted by Worldcoin, JP Morgan, Gensyn, and more

<Tabs>
  <Tab title="React" language="react">
    ## Setup

    Follow these steps to use Turnkey signers with the Wallet Client SDK.

    ### Installation

    <CodeGroup>
    ```shell npm
    npm install @alchemy/wallet-apis @turnkey/react-wallet-kit @turnkey/viem viem
    ```

    ```shell bun
    bun add @alchemy/wallet-apis @turnkey/react-wallet-kit @turnkey/viem viem
    ```

    ```shell yarn
    yarn add @alchemy/wallet-apis @turnkey/react-wallet-kit @turnkey/viem viem
    ```

    ```shell pnpm
    pnpm add @alchemy/wallet-apis @turnkey/react-wallet-kit @turnkey/viem viem
    ```
    </CodeGroup>

    ### Prerequisites: Get your keys (API key, Policy ID, Turnkey Organization ID)

    * Alchemy API key:
      * Go to the [Alchemy Dashboard](https://dashboard.alchemy.com/)
      * Create or select an app and copy the API key
    * Gas sponsorship Policy ID (Gas Manager):
      * Create a gas sponsorship policy in the [dashboard](https://dashboard.alchemy.com/services/gas-manager/configuration) and copy its Policy ID
    * Turnkey Organization ID & Auth Proxy Config ID:
      * Go to the [Turnkey Dashboard](https://app.turnkey.com/)
      * Create or select an organization and copy the Organization ID
      * Set up an Auth Proxy and copy the Config ID

    <Warning>
      The gas sponsorship policy must be linked to the application behind your Alchemy API key for sponsorship to work.
    </Warning>

    ### 1. Configure TurnkeyProvider

    Wrap your app with `TurnkeyProvider` from `@turnkey/react-wallet-kit`:

    ```tsx
    import { TurnkeyProvider } from "@turnkey/react-wallet-kit";
    import "@turnkey/react-wallet-kit/styles.css";

    export function App() {
      return (
        <TurnkeyProvider
          config={{
            organizationId: "YOUR_TURNKEY_ORGANIZATION_ID",
            authProxyConfigId: "YOUR_TURNKEY_AUTH_PROXY_CONFIG_ID",
            auth: {
              createSuborgParams: {
                emailOtpAuth: {
                  customWallet: {
                    walletName: "Default Wallet",
                    walletAccounts: [
                      {
                        curve: "CURVE_SECP256K1",
                        pathFormat: "PATH_FORMAT_BIP32",
                        path: "m/44'/60'/0'/0/0",
                        addressFormat: "ADDRESS_FORMAT_ETHEREUM",
                      },
                    ],
                  },
                },
              },
            },
          }}
        >
          <YourApp />
        </TurnkeyProvider>
      );
    }
    ```

    ### 2. Get a signer from Turnkey

    Use `useTurnkey` from `@turnkey/react-wallet-kit` and `createAccount` from `@turnkey/viem` to convert a Turnkey wallet into a viem `LocalAccount`:

    ```tsx
    import { useTurnkey } from "@turnkey/react-wallet-kit";
    import { createAccount } from "@turnkey/viem";
    import { useEffect, useState } from "react";
    import type { LocalAccount } from "viem";

    const useTurnkeySigner = () => {
      const { httpClient, wallets, session } = useTurnkey();

      const [signer, setSigner] = useState<LocalAccount>();

      // Use the first Ethereum account from the first wallet
      const account = wallets[0]?.accounts?.find(
        (a) => a.addressFormat === "ADDRESS_FORMAT_ETHEREUM",
      );

      useEffect(() => {
        if (!httpClient || !account || !session || signer) return;
        createAccount({
          client: httpClient,
          organizationId: session.organizationId,
          signWith: account.address,
        }).then(setSigner);
      }, [httpClient, account, session, signer]);

      return signer;
    };
    ```

    ### 3. Handle login

    Use `useTurnkey` to manage authentication state and conditionally render your wallet UI once the user is logged in:

    ```tsx
    import { useTurnkey, ClientState, AuthState } from "@turnkey/react-wallet-kit";

    function TurnkeyWallet() {
      const { clientState, authState, handleLogin, logout } = useTurnkey();
      const signer = useTurnkeySigner();

      if (clientState !== ClientState.Ready) return <p>Loading...</p>;

      if (authState !== AuthState.Authenticated) {
        return <button onClick={() => handleLogin()}>Login with Turnkey</button>;
      }

      return (
        <div>
          <button onClick={() => logout()}>Logout</button>
          {signer ? <SendTransaction signer={signer} /> : <p>Loading signer...</p>}
        </div>
      );
    }
    ```

    ### 4. Create the client and send a transaction

    Pass the Turnkey signer to `createSmartWalletClient` and use it to send transactions:

    ```tsx
    import { useMemo, useCallback } from "react";
    import { zeroAddress } from "viem";
    import { createSmartWalletClient, alchemyWalletTransport } from "@alchemy/wallet-apis";
    import { arbitrumSepolia } from "viem/chains";

    function SendTransaction({ signer }: { signer: LocalAccount }) {
      const client = useMemo(
        () =>
          createSmartWalletClient({
            signer,
            transport: alchemyWalletTransport({
              apiKey: "YOUR_ALCHEMY_API_KEY",
            }),
            chain: arbitrumSepolia,
            paymaster: {
              policyId: "YOUR_GAS_MANAGER_POLICY_ID",
            },
          }),
        [signer],
      );

      const handleSend = useCallback(async () => {
        // Send the transaction
        const { id } = await client.sendCalls({
          calls: [{ to: zeroAddress, value: BigInt(0), data: "0x" }],
        });

        // Wait for the transaction to be confirmed
        const result = await client.waitForCallsStatus({ id });
        console.log(`Transaction hash: ${result.receipts?.[0]?.transactionHash}`);
      }, [client]);

      return <button onClick={handleSend}>Send transaction</button>;
    }
    ```

    ### Notes

    * The client defaults to [EIP-7702](/docs/wallets/transactions/using-eip-7702), which delegates the Turnkey wallet to a smart wallet at send time. No deployment or wallet migration needed. See the [EIP-7702 guide](/docs/wallets/transactions/using-eip-7702) for non-7702 mode.
    * See the [Sponsor gas](/docs/wallets/transactions/sponsor-gas) guide for more on gas sponsorship configuration.
  </Tab>

  <Tab title="JavaScript" language="typescript">
    ## Setup

    Use Turnkey's `@turnkey/sdk-server` package with the Wallet Client SDK in a server environment.

    ### Installation

    <CodeGroup>
    ```shell npm
    npm install @turnkey/sdk-server @turnkey/viem @alchemy/wallet-apis viem
    ```

    ```shell bun
    bun add @turnkey/sdk-server @turnkey/viem @alchemy/wallet-apis viem
    ```

    ```shell yarn
    yarn add @turnkey/sdk-server @turnkey/viem @alchemy/wallet-apis viem
    ```

    ```shell pnpm
    pnpm add @turnkey/sdk-server @turnkey/viem @alchemy/wallet-apis viem
    ```
    </CodeGroup>

    ### Prerequisites: Get your keys

    * Alchemy API key:
      * Go to the [Alchemy Dashboard](https://dashboard.alchemy.com/)
      * Create or select an app and copy the API key
    * Gas sponsorship Policy ID (Gas Manager):
      * Create a gas sponsorship policy in the [dashboard](https://dashboard.alchemy.com/services/gas-manager/configuration) and copy its Policy ID
    * Turnkey Credentials:
      * Go to the [Turnkey Dashboard](https://app.turnkey.com/)
      * Create or select an organization and copy the Organization ID
      * Create an API key pair and copy the API Public Key and Private Key

    <Warning>
      The gas sponsorship policy must be linked to the application behind your Alchemy API key for sponsorship to work.
    </Warning>

    ### Send a transaction

    ```ts
    import { Turnkey } from "@turnkey/sdk-server";
    import { createAccount } from "@turnkey/viem";
    import { createSmartWalletClient, alchemyWalletTransport } from "@alchemy/wallet-apis";
    import { arbitrumSepolia } from "viem/chains";

    const turnkeySdk = new Turnkey({
      apiBaseUrl: "https://api.turnkey.com",
      apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!,
      apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!,
      defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID!,
    });

    const turnkeyClient = turnkeySdk.apiClient();

    // Get or create a wallet
    const { wallets } = await turnkeyClient.getWallets();
    let address: string;

    if (wallets.length > 0) {
      // Reuse existing wallet — get its first Ethereum account
      const { accounts } = await turnkeyClient.getWalletAccounts({
        walletId: wallets[0]!.walletId,
      });
      address = accounts[0]!.address;
    } else {
      // No wallets yet — create one
      const { addresses } = await turnkeyClient.createWallet({
        walletName: "Smart Wallet",
        accounts: [
          {
            curve: "CURVE_SECP256K1",
            pathFormat: "PATH_FORMAT_BIP32",
            path: "m/44'/60'/0'/0/0",
            addressFormat: "ADDRESS_FORMAT_ETHEREUM",
          },
        ],
      });
      address = addresses[0]!;
    }

    // Get a viem-compatible signer from Turnkey
    const signer = await createAccount({
      client: turnkeyClient,
      organizationId: process.env.TURNKEY_ORGANIZATION_ID!,
      signWith: address,
    });

    // Create the Smart Wallet client
    const client = createSmartWalletClient({
      signer,
      transport: alchemyWalletTransport({ apiKey: process.env.ALCHEMY_API_KEY! }),
      chain: arbitrumSepolia,
      paymaster: { policyId: process.env.ALCHEMY_POLICY_ID! },
    });

    // Send the transaction
    const { id } = await client.sendCalls({
      calls: [
        {
          to: "0x0000000000000000000000000000000000000000",
          value: BigInt(0),
          data: "0x",
        },
      ],
    });

    // Wait for the transaction to be confirmed
    const result = await client.waitForCallsStatus({ id });
    console.log(`Transaction hash: ${result.receipts?.[0]?.transactionHash}`);
    ```

    ### Notes

    * The client defaults to [EIP-7702](/docs/wallets/transactions/using-eip-7702), which upgrades the Turnkey wallet to a smart wallet at transaction time without migration or separate deployment. See the [EIP-7702 guide](/docs/wallets/transactions/using-eip-7702) for non-7702 mode.
    * See the [Sponsor gas](/docs/wallets/transactions/sponsor-gas) guide for more on gas sponsorship configuration.
    * This example uses Turnkey's `@turnkey/sdk-server` package which is designed for server environments. `@alchemy/wallet-apis` can be used in any JavaScript environment.
  </Tab>
</Tabs>