> ## 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.
      </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!,
    });
    ```

    ## What to know about Circle Wallets

    When you use the Circle Wallets adapter in an App Kit operation, pass the wallet
    address in the operation context. The address tells the App Kit SDK which Circle
    Wallet acts on that blockchain:

    ```typescript TypeScript theme={null}
    from: {
      adapter,
      chain: "Base_Sepolia",
      address: walletAddress,
    }
    ```

    Use the wallet address for the same blockchain as `chain`.

    * 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.
    * **Dev-controlled SCA**: Each Circle Wallet exists on one blockchain. For
      crosschain flows, provision one SCA per source blockchain.

    <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>
  </Tab>
</Tabs>
