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

# Adapter setups

> Configure Viem, Ethers, Solana, or Circle Wallets adapters for the App Kit SDK

The App Kit SDK uses adapters to connect SDK calls to a client and signer. The
client reads blockchain data and submits signed transactions while the signer
authorizes transactions from a private key, browser wallet, or wallet provider.
The following adapters are supported:

* [`viem`](https://viem.sh/) v2 for EVM blockchains
* [`ethers`](https://ethers.org/) v6 for EVM blockchains
* [`solana`](https://solana.com/) for the Solana blockchain
* [`circle-wallets`](https://developers.circle.com/wallets/dev-controlled) for
  Circle-managed wallets

<Note>
  Before setting up an adapter, you should already have created the wallet,
  account, API keys, and policies in your wallet provider.
</Note>

Pick your adapter to see its setup options.

<Tabs>
  <Tab title="Viem">
    Use the Viem adapter for EVM apps that use `viem` accounts or wallet clients.

    ## Server-side wallet

    Use a server-side wallet when your backend signs transactions. The signer can be
    a private key or a wallet provider.

    <Tabs>
      <Tab title="Private key">
        Create one adapter from your wallet private key. The adapter works across EVM
        blockchains and uses built-in public RPC endpoints.

        <Note>
          Default RPC URLs are shared and may be rate-limited. For a more stable
          connection, configure a [custom RPC](#custom-rpc).
        </Note>

        ```typescript TypeScript theme={null}
        import { createViemAdapterFromPrivateKey } from "@circle-fin/adapter-viem-v2";

        const adapter = createViemAdapterFromPrivateKey({
          privateKey: process.env.PRIVATE_KEY as string,
        });
        ```
      </Tab>

      <Tab title="Turnkey">
        Use Turnkey Company Wallets with the Viem adapter for backend signing. The setup
        uses `@turnkey/viem` to create a Turnkey-backed `viem` account, then passes that
        account to the Viem adapter.

        Set these environment variables from your Turnkey account:

        ```text .env theme={null}
        TURNKEY_API_PUBLIC_KEY=YOUR_TURNKEY_API_PUBLIC_KEY
        TURNKEY_API_PRIVATE_KEY=YOUR_TURNKEY_API_PRIVATE_KEY
        TURNKEY_ORGANIZATION_ID=YOUR_TURNKEY_ORGANIZATION_ID
        TURNKEY_WALLET_ADDRESS=YOUR_TURNKEY_ETHEREUM_WALLET_ADDRESS
        ```

        Adapter setup:

        ```typescript TypeScript theme={null}
        import { AppKit } from "@circle-fin/app-kit";
        import { ViemAdapter } from "@circle-fin/adapter-viem-v2";
        import { Turnkey as TurnkeyServerSDK } from "@turnkey/sdk-server";
        import { createAccount } from "@turnkey/viem";
        import { createPublicClient, createWalletClient, http } from "viem";

        const turnkey = new TurnkeyServerSDK({
          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 account = await createAccount({
          client: turnkey.apiClient(),
          organizationId: process.env.TURNKEY_ORGANIZATION_ID!,
          signWith: process.env.TURNKEY_WALLET_ADDRESS!,
        });

        const appKit = new AppKit();
        const supportedChains = (await appKit.getSupportedChains()).filter(
          (chain) => chain.type === "evm",
        );

        const adapter = new ViemAdapter(
          {
            getPublicClient: ({ chain }) =>
              createPublicClient({
                chain,
                transport: http(),
              }),
            getWalletClient: ({ chain }) =>
              createWalletClient({
                account,
                chain,
                transport: http(),
              }),
          },
          {
            addressContext: "user-controlled",
            supportedChains,
          },
        );
        ```

        **What to know about this setup**:

        * Turnkey supports both `viem` and `ethers`. This setup uses `viem` because
          `@turnkey/viem` creates a Turnkey-backed account directly, which keeps the
          adapter setup shorter.
        * Use this setup only on your backend. `TURNKEY_API_PRIVATE_KEY` authenticates
          requests to Turnkey and must not be exposed in browser code.
        * For crosschain flows, create one adapter per blockchain if your wallet client
          or RPC setup is blockchain-specific.
      </Tab>
    </Tabs>

    ## Browser wallet

    This setup expects a wallet extension in the browser (for example
    `window.ethereum` or `window.solana`), not a Node.js server. You can use wallets
    like [MetaMask](https://metamask.io/) or [Phantom](https://phantom.com/):

    <Tabs>
      <Tab title="MetaMask (EIP-6963)">
        Use EIP-6963 to discover injected browser wallets, select a provider, request
        access to the user's accounts, then pass the provider to the Viem adapter.

        The example selects MetaMask by reverse-DNS identifier. To use any injected
        wallet, omit `requiredRdns`.

        ```typescript TypeScript theme={null}
        import {
          createViemAdapterFromProvider,
          type CreateViemAdapterFromProviderParams,
        } from "@circle-fin/adapter-viem-v2";

        type BrowserWalletProvider = CreateViemAdapterFromProviderParams["provider"];

        type EIP6963ProviderDetail = {
          info: {
            uuid: string;
            name: string;
            icon: string;
            rdns: string;
          };
          provider: BrowserWalletProvider;
        };

        declare global {
          interface WindowEventMap {
            "eip6963:announceProvider": CustomEvent<EIP6963ProviderDetail>;
          }
        }

        async function getInjectedWalletProvider(
          requiredRdns?: string,
        ): Promise<BrowserWalletProvider> {
          const providers = new Map<string, EIP6963ProviderDetail>();

          const onAnnounce = ((event: CustomEvent<EIP6963ProviderDetail>) => {
            providers.set(event.detail.info.uuid, event.detail);
          }) as EventListener;

          window.addEventListener("eip6963:announceProvider", onAnnounce);
          window.dispatchEvent(new Event("eip6963:requestProvider"));
          await new Promise((resolve) => window.setTimeout(resolve, 250));
          window.removeEventListener("eip6963:announceProvider", onAnnounce);

          const provider = requiredRdns
            ? [...providers.values()].find(({ info }) => info.rdns === requiredRdns)
                ?.provider
            : [...providers.values()][0]?.provider;

          if (!provider) {
            throw new Error(
              requiredRdns
                ? `No EIP-6963 wallet found for ${requiredRdns}`
                : "No EIP-6963 browser wallet found",
            );
          }

          return provider;
        }

        const provider = await getInjectedWalletProvider("io.metamask");
        // For any injected wallet, use:
        // const provider = await getInjectedWalletProvider();

        await provider.request({
          method: "eth_requestAccounts",
          params: undefined,
        });

        const adapter = await createViemAdapterFromProvider({
          provider,
        });
        ```
      </Tab>

      <Tab title="window.ethereum">
        Use `window.ethereum` only when the wallet does not support EIP-6963 or your app
        intentionally accepts the default injected provider.

        ```typescript TypeScript theme={null}
        import { createViemAdapterFromProvider } from "@circle-fin/adapter-viem-v2";
        import type { EIP1193Provider } from "viem";

        declare global {
          interface Window {
            ethereum?: EIP1193Provider;
          }
        }

        if (!window.ethereum) {
          throw new Error("No wallet provider found");
        }

        const adapter = await createViemAdapterFromProvider({
          provider: window.ethereum,
        });
        ```
      </Tab>
    </Tabs>

    ## Custom RPC

    By default, adapters use built-in RPC endpoints, which may be rate-limited or
    unreliable. Override them with your own provider.
    [Alchemy](https://www.alchemy.com/), [QuickNode](https://www.quicknode.com/),
    and [chainlist.org](https://chainlist.org/) are common places to source
    endpoints. This example uses Alchemy.

    The `getPublicClient`/`getWalletClient` override pairs with any signer setup
    that uses `viem`, such as a private key or browser wallet.

    ```typescript TypeScript theme={null}
    import { createViemAdapterFromPrivateKey } from "@circle-fin/adapter-viem-v2";
    import { EthereumSepolia, ArcTestnet } from "@circle-fin/app-kit/chains";
    import { createPublicClient, createWalletClient, http } from "viem";

    const RPC_BY_CHAIN_NAME: Record<string, string> = {
      [EthereumSepolia.name]: `https://eth-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`,
      [ArcTestnet.name]: `https://arc-testnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`,
    };

    const adapter = createViemAdapterFromPrivateKey({
      privateKey: process.env.PRIVATE_KEY as string,
      getPublicClient: ({ chain }) => {
        const rpcUrl = RPC_BY_CHAIN_NAME[chain.name];
        if (!rpcUrl) {
          throw new Error(`No RPC configured for chain: ${chain.name}`);
        }
        return createPublicClient({
          chain,
          transport: http(rpcUrl, {
            retryCount: 3,
            timeout: 10000,
          }),
        });
      },
      getWalletClient: ({ chain, account }) => {
        const rpcUrl = RPC_BY_CHAIN_NAME[chain.name];
        if (!rpcUrl) {
          throw new Error(`No RPC configured for chain: ${chain.name}`);
        }
        return createWalletClient({
          account,
          chain,
          transport: http(rpcUrl, {
            retryCount: 3,
            timeout: 10000,
          }),
        });
      },
    });
    ```
  </Tab>

  <Tab title="Ethers">
    Use the Ethers adapter for EVM apps that use `ethers` providers or signers.

    ## Server-side wallet

    Use a server-side wallet when your backend signs transactions. The signer can be
    a private key or a wallet provider.

    <Tabs>
      <Tab title="Private key">
        Create one adapter from your wallet private key. The adapter works across EVM
        blockchains and uses built-in public RPC endpoints.

        <Note>
          Default RPC URLs are shared and may be rate-limited. For a more stable
          connection, configure a custom RPC.
        </Note>

        ```typescript TypeScript theme={null}
        import { createEthersAdapterFromPrivateKey } from "@circle-fin/adapter-ethers-v6";

        const adapter = createEthersAdapterFromPrivateKey({
          privateKey: process.env.PRIVATE_KEY as string,
        });
        ```
      </Tab>

      <Tab title="Privy">
        Use Privy server wallets with the Ethers adapter when Privy signs for your
        backend. Privy server wallets sign through the Privy wallet API by `walletId`,
        so the setup needs a small custom `ethers` signer.

        Set these environment variables from your Privy app and server wallet:

        ```text .env theme={null}
        PRIVY_APP_ID=YOUR_PRIVY_APP_ID
        PRIVY_APP_SECRET=YOUR_PRIVY_APP_SECRET
        PRIVY_AUTHORIZATION_PRIVATE_KEY=YOUR_PRIVY_AUTHORIZATION_PRIVATE_KEY
        PRIVY_WALLET_ID=YOUR_PRIVY_SERVER_WALLET_ID
        PRIVY_WALLET_ADDRESS=YOUR_PRIVY_SERVER_WALLET_ADDRESS
        ```

        Use a server-owned Privy wallet. `PRIVY_AUTHORIZATION_PRIVATE_KEY` must belong
        to the wallet owner or an authorized signer for `PRIVY_WALLET_ID`.

        Create the `PrivyServerSigner.ts` helper first. The adapter setup imports this
        helper in the next step.

        ```typescript TypeScript expandable theme={null}
        import { PrivyClient, type Hex, type Quantity } from "@privy-io/server-auth";
        import { ethers } from "ethers";

        interface PrivyServerSignerOptions {
          walletId: string;
          walletAddress: string;
          privy: PrivyClient;
          provider: ethers.Provider;
        }

        type JsonSafe =
          | string
          | number
          | boolean
          | null
          | JsonSafe[]
          | { [key: string]: JsonSafe };

        type PrivySignTransactionRequest = Parameters<
          PrivyClient["walletApi"]["ethereum"]["signTransaction"]
        >[0];

        type PrivySignMessageRequest = Parameters<
          PrivyClient["walletApi"]["ethereum"]["signMessage"]
        >[0];

        type PrivySignTypedDataRequest = Parameters<
          PrivyClient["walletApi"]["ethereum"]["signTypedData"]
        >[0];

        export class PrivyServerSigner extends ethers.AbstractSigner {
          private walletId: string;
          private walletAddress: string;
          private privy: PrivyClient;

          constructor(options: PrivyServerSignerOptions) {
            super(options.provider);
            this.walletId = options.walletId;
            this.walletAddress = options.walletAddress;
            this.privy = options.privy;
          }

          connect(provider: ethers.Provider): PrivyServerSigner {
            return new PrivyServerSigner({
              walletId: this.walletId,
              walletAddress: this.walletAddress,
              privy: this.privy,
              provider,
            });
          }

          async getAddress(): Promise<string> {
            return this.walletAddress;
          }

          async signTransaction(tx: ethers.TransactionRequest): Promise<string> {
            const toQuantity = (
              value: ethers.BigNumberish | null | undefined,
            ): Quantity | undefined =>
              value != null ? (ethers.toBeHex(value) as Hex) : undefined;

            const toHex = (value: unknown): Hex | undefined =>
              value != null ? (value.toString() as Hex) : undefined;

            const request: PrivySignTransactionRequest = {
              walletId: this.walletId,
              chainType: "ethereum",
              transaction: {
                to: toHex(tx.to),
                nonce: tx.nonce != null ? Number(tx.nonce) : undefined,
                chainId: tx.chainId != null ? Number(tx.chainId) : undefined,
                data: toHex(tx.data),
                value: toQuantity(tx.value),
                type: (tx.type ?? 2) as 0 | 1 | 2,
                gasLimit: toQuantity(tx.gasLimit),
                maxFeePerGas: toQuantity(tx.maxFeePerGas),
                maxPriorityFeePerGas: toQuantity(tx.maxPriorityFeePerGas),
              },
            };

            const { signedTransaction } =
              await this.privy.walletApi.ethereum.signTransaction(request);

            return signedTransaction;
          }

          async signMessage(message: string | Uint8Array): Promise<string> {
            const request: PrivySignMessageRequest = {
              walletId: this.walletId,
              chainType: "ethereum",
              message: typeof message === "string" ? message : ethers.hexlify(message),
            };

            const { signature } =
              await this.privy.walletApi.ethereum.signMessage(request);

            return signature;
          }

          async signTypedData(
            domain: ethers.TypedDataDomain,
            types: Record<string, ethers.TypedDataField[]>,
            value: Record<string, unknown>,
          ): Promise<string> {
            const toJsonSafe = (input: unknown): JsonSafe => {
              if (typeof input === "bigint") {
                return input.toString();
              }
              if (Array.isArray(input)) {
                return input.map(toJsonSafe);
              }
              if (input && typeof input === "object") {
                return Object.fromEntries(
                  Object.entries(input).map(([key, nestedValue]) => [
                    key,
                    toJsonSafe(nestedValue),
                  ]),
                );
              }
              return input as JsonSafe;
            };

            const primaryType =
              Object.keys(types).find((key) => key !== "EIP712Domain") ?? "";

            const jsonSafeDomain = toJsonSafe(domain) as Record<string, unknown>;
            const jsonSafeMessage = toJsonSafe(value) as Record<string, unknown>;

            const request: PrivySignTypedDataRequest = {
              walletId: this.walletId,
              typedData: {
                domain: jsonSafeDomain,
                types,
                message: jsonSafeMessage,
                primaryType,
              },
            };

            const { signature } =
              await this.privy.walletApi.ethereum.signTypedData(request);

            return signature;
          }
        }
        ```

        After adding the helper, create the adapter:

        ```typescript TypeScript theme={null}
        import { AppKit } from "@circle-fin/app-kit";
        import { EthersAdapter } from "@circle-fin/adapter-ethers-v6";
        import { PrivyClient } from "@privy-io/server-auth";
        import { ethers } from "ethers";
        import { PrivyServerSigner } from "./PrivyServerSigner";

        const privy = new PrivyClient(
          process.env.PRIVY_APP_ID!,
          process.env.PRIVY_APP_SECRET!,
          {
            walletApi: {
              authorizationPrivateKey: process.env.PRIVY_AUTHORIZATION_PRIVATE_KEY!,
            },
          },
        );

        const appKit = new AppKit();
        const supportedChains = (await appKit.getSupportedChains()).filter(
          (chain) => chain.type === "evm",
        );
        const arcTestnet = supportedChains.find(
          (chain) => chain.chain === "Arc_Testnet",
        );

        if (!arcTestnet) {
          throw new Error("Arc Testnet is not supported");
        }

        const provider = new ethers.JsonRpcProvider(arcTestnet.rpcEndpoints[0]);

        const signer = new PrivyServerSigner({
          walletId: process.env.PRIVY_WALLET_ID!,
          walletAddress: process.env.PRIVY_WALLET_ADDRESS!,
          privy,
          provider,
        });

        const adapter = new EthersAdapter(
          {
            signer,
            getProvider: ({ chain }) =>
              new ethers.JsonRpcProvider(chain.rpcEndpoints[0]),
          },
          {
            addressContext: "user-controlled",
            supportedChains,
          },
        );
        ```

        **What to know about this setup**:

        * Privy's stock `createEthersSigner` is built for user-linked wallets. Server
          wallets sign by `walletId`, which is why this setup uses `PrivyServerSigner`.
        * User-owned embedded wallets are not server wallets. For this setup, create a
          server-owned wallet or add your backend key as an authorized signer.
        * Automatic chain switching does not support this custom signer. For crosschain
          flows, create one adapter per blockchain.
        * Privy server wallets cannot sign their own Unified Balance spends. Use the
          [delegate workflow](/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend):
          Privy stays the depositor, and a non-Privy EOA signs each spend.
      </Tab>
    </Tabs>

    ## Browser wallet

    This setup expects a wallet extension in the browser (for example
    `window.ethereum` or `window.solana`), not a Node.js server. You can use wallets
    like [MetaMask](https://metamask.io/) or [Phantom](https://phantom.com/):

    <Note>
      In crosschain flows, use the Viem adapter for injected browser wallets. The
      Ethers adapter can work for same-chain browser wallet operations, but the
      `BrowserProvider` from `ethers` can throw a network-change error when the wallet
      switches blockchains during a bridge or other crosschain flow.
    </Note>

    <Tabs>
      <Tab title="MetaMask (EIP-6963)">
        Use EIP-6963 to discover injected browser wallets, select a provider, request
        access to the user's accounts, then pass the provider to the Ethers adapter.

        The example selects MetaMask by reverse-DNS identifier. To use any injected
        wallet, omit `requiredRdns`.

        ```typescript TypeScript theme={null}
        import {
          createEthersAdapterFromProvider,
          type CreateEthersAdapterFromProviderParams,
        } from "@circle-fin/adapter-ethers-v6";

        type BrowserWalletProvider = CreateEthersAdapterFromProviderParams["provider"];

        type EIP6963ProviderDetail = {
          info: {
            uuid: string;
            name: string;
            icon: string;
            rdns: string;
          };
          provider: BrowserWalletProvider;
        };

        declare global {
          interface WindowEventMap {
            "eip6963:announceProvider": CustomEvent<EIP6963ProviderDetail>;
          }
        }

        async function getInjectedWalletProvider(
          requiredRdns?: string,
        ): Promise<BrowserWalletProvider> {
          const providers = new Map<string, EIP6963ProviderDetail>();

          const onAnnounce = ((event: CustomEvent<EIP6963ProviderDetail>) => {
            providers.set(event.detail.info.uuid, event.detail);
          }) as EventListener;

          window.addEventListener("eip6963:announceProvider", onAnnounce);
          window.dispatchEvent(new Event("eip6963:requestProvider"));
          await new Promise((resolve) => window.setTimeout(resolve, 250));
          window.removeEventListener("eip6963:announceProvider", onAnnounce);

          const provider = requiredRdns
            ? [...providers.values()].find(({ info }) => info.rdns === requiredRdns)
                ?.provider
            : [...providers.values()][0]?.provider;

          if (!provider) {
            throw new Error(
              requiredRdns
                ? `No EIP-6963 wallet found for ${requiredRdns}`
                : "No EIP-6963 browser wallet found",
            );
          }

          return provider;
        }

        const provider = await getInjectedWalletProvider("io.metamask");
        // For any injected wallet, use:
        // const provider = await getInjectedWalletProvider();

        await provider.request({
          method: "eth_requestAccounts",
          params: undefined,
        });

        const adapter = await createEthersAdapterFromProvider({
          provider,
        });
        ```
      </Tab>

      <Tab title="window.ethereum">
        Use `window.ethereum` only when the wallet does not support EIP-6963 or your app
        intentionally accepts the default injected provider.

        ```typescript TypeScript theme={null}
        import { createEthersAdapterFromProvider } from "@circle-fin/adapter-ethers-v6";
        import type { Eip1193Provider } from "ethers";

        declare global {
          interface Window {
            ethereum?: Eip1193Provider;
          }
        }

        if (!window.ethereum) {
          throw new Error("No wallet provider found");
        }

        const adapter = await createEthersAdapterFromProvider({
          provider: window.ethereum,
        });
        ```
      </Tab>
    </Tabs>

    ## Custom RPC

    By default, adapters use built-in RPC endpoints, which may be rate-limited or
    unreliable. Override them with your own provider.
    [Alchemy](https://www.alchemy.com/), [QuickNode](https://www.quicknode.com/),
    and [chainlist.org](https://chainlist.org/) are common places to source
    endpoints. This example uses Alchemy.

    The `getProvider` override pairs with any signer setup that uses `ethers`, such
    as a private key or browser wallet.

    ```typescript TypeScript theme={null}
    import { createEthersAdapterFromPrivateKey } from "@circle-fin/adapter-ethers-v6";
    import { EthereumSepolia, ArcTestnet } from "@circle-fin/app-kit/chains";
    import { JsonRpcProvider } from "ethers";

    const RPC_BY_CHAIN_NAME: Record<string, string> = {
      [EthereumSepolia.name]: `https://eth-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`,
      [ArcTestnet.name]: `https://arc-testnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`,
    };

    const adapter = createEthersAdapterFromPrivateKey({
      privateKey: process.env.PRIVATE_KEY as string,
      getProvider: ({ chain }) => {
        const rpcUrl = RPC_BY_CHAIN_NAME[chain.name];
        if (!rpcUrl) {
          throw new Error(`No RPC configured for chain: ${chain.name}`);
        }
        return new JsonRpcProvider(rpcUrl);
      },
    });
    ```
  </Tab>

  <Tab title="Solana">
    Use the Solana adapter for Solana wallets and RPC clients.

    ## Server-side wallet

    Use a server-side wallet when your backend signs transactions.

    ### Private key

    Create one adapter from your Solana private key. Solana accepts Base58, Base64,
    or JSON array format private keys.

    <Note>
      Default RPC URLs are shared and may be rate-limited. For a more stable
      connection, configure a custom RPC.
    </Note>

    ```typescript TypeScript theme={null}
    import { createSolanaKitAdapterFromPrivateKey } from "@circle-fin/adapter-solana-kit";

    const adapter = createSolanaKitAdapterFromPrivateKey({
      privateKey: process.env.PRIVATE_KEY as string,
    });
    ```

    ## Browser wallet

    This setup expects a wallet extension in the browser (for example
    `window.ethereum` or `window.solana`), not a Node.js server. You can use wallets
    like [MetaMask](https://metamask.io/) or [Phantom](https://phantom.com/):

    ```typescript TypeScript theme={null}
    import { createSolanaKitAdapterFromProvider } from "@circle-fin/adapter-solana-kit";

    type SolanaKitWalletProvider = Parameters<
      typeof createSolanaKitAdapterFromProvider
    >[0]["provider"];

    declare global {
      interface Window {
        solana?: SolanaKitWalletProvider;
      }
    }

    if (!window.solana) {
      throw new Error("No Solana wallet provider found");
    }

    const adapter = await createSolanaKitAdapterFromProvider({
      provider: window.solana,
    });
    ```

    ## Custom RPC

    By default, adapters use built-in RPC endpoints, which may be rate-limited or
    unreliable. Override them with your own provider.
    [Alchemy](https://www.alchemy.com/), [QuickNode](https://www.quicknode.com/),
    and [chainlist.org](https://chainlist.org/) are common places to source
    endpoints. This example uses Alchemy.

    The `getRpc` override pairs with any Solana signer setup, such as a private key
    or browser wallet.

    ```typescript TypeScript theme={null}
    import { createSolanaKitAdapterFromPrivateKey } from "@circle-fin/adapter-solana-kit";
    import { createSolanaRpc } from "@solana/kit";

    const adapter = createSolanaKitAdapterFromPrivateKey({
      privateKey: process.env.SOLANA_PRIVATE_KEY as string,
      getRpc: () =>
        createSolanaRpc(
          `https://solana-devnet.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`,
        ),
    });
    ```
  </Tab>

  <Tab title="Circle Wallets">
    <Badge color="blue">server-side only</Badge>

    Use the Circle Wallets adapter if you already
    [manage wallets through Circle](https://developers.circle.com/wallets/dev-controlled).
    The adapter is suited for backend apps. It uses developer-controlled wallets so
    you can use the App Kit SDK on
    [supported blockchains](/app-kit/references/supported-blockchains#adapters)
    without managing private keys yourself.

    The Circle Wallets adapter requires these credentials from the
    [Circle Console](https://console.circle.com):

    * **[API Key](https://developers.circle.com/w3s/circle-developer-account#creating-an-api-key-for-developer-services):**
      Environment-prefixed (examples: `TEST_API_KEY:abc123:def456`,
      `LIVE_API_KEY:xyz:uvw`) or Base64-encoded
    * **[Entity Secret](https://developers.circle.com/wallets/dev-controlled/register-entity-secret):**
      64 lowercase alphanumeric characters

    This code block initializes the Circle Wallets adapter:

    ```typescript TypeScript theme={null}
    import { createCircleWalletsAdapter } from "@circle-fin/adapter-circle-wallets";

    const adapter = createCircleWalletsAdapter({
      apiKey: process.env.CIRCLE_API_KEY!,
      entitySecret: process.env.CIRCLE_ENTITY_SECRET!,
    });
    ```

    <Warning>
      The Circle Wallets adapter does not support
      [gas sponsorship](https://developers.circle.com/wallets/gas-station) for
      crosschain transfers that originate on Solana. For those transactions, the
      user's Solana wallet must hold sufficient SOL to pay network fees.
    </Warning>

    ## What to know about Circle Wallets

    The wallet address you pass to App Kit operations determines whether Circle
    handles the transaction as a dev-controlled externally owned account (EOA) or
    smart contract account (SCA) wallet. See
    [Account type comparison](https://developers.circle.com/wallets/account-types#account-type-comparison)
    to understand the difference.

    ### Dev-controlled EOA

    * Each Circle Wallet exists on one blockchain. For crosschain flows, provision
      one wallet per source blockchain. The onchain addresses differ.
    * For Unified Balance spend, create one adapter instance per source. The adapter
      is stateless, so call `createCircleWalletsAdapter` again.
    * Bridges from Arc Testnet must exceed the CCTPv2 max fee. If the amount is too
      low, the burn step reverts with `"Max fee must be less than amount"`.

    ### Dev-controlled SCA

    * Each Circle Wallet exists on one blockchain. For crosschain flows, provision
      one SCA per source blockchain.
    * For Unified Balance spend, create one adapter instance per source.
    * Swap and Unified Balance deposit require `allowanceStrategy: "approve"`. USDC
      permit signatures use `ecrecover`, which does not accept the SCA's ERC-1271
      signature, so the SDK uses an onchain `approve`.
    * SCAs cannot sign their own Unified Balance spends. Use the
      [delegate workflow](/app-kit/quickstarts/unified-balance-delegate-deposit-and-spend):
      the SCA stays the depositor, and an authorized EOA signs each spend.
    * Bridges from Arc Testnet must exceed the CCTPv2 max fee.
  </Tab>
</Tabs>
