Skip to content
Alchemy Logo

Privy

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

Follow these steps to use Privy signers with the Wallet Client SDK. EVM and Solana share the same setup; each chain family has its own send-transaction flow below.

npm install @alchemy/wallet-apis @privy-io/react-auth viem

For Solana support, see Send Solana transactions below for the additional peer dependencies.

  • Alchemy API key:
  • Gas sponsorship Policy ID (Gas Manager):
    • Create a gas sponsorship policy in the dashboard and copy its Policy ID
  • Privy App ID:

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

Wrap your app with PrivyProvider from @privy-io/react-auth:

import { PrivyProvider } from "@privy-io/react-auth";
 
export function App() {
  return (
    <PrivyProvider
      appId="your-privy-app-id"
      config={{
        embeddedWallets: {
          ethereum: {
            createOnLogin: "all-users",
          },
          showWalletUIs: false,
        },
      }}
    >
      <YourApp />
    </PrivyProvider>
  );
}

Use toViemAccount and useWallets from @privy-io/react-auth to convert a Privy embedded wallet into a viem LocalAccount:

import { toViemAccount, useWallets } from "@privy-io/react-auth";
import { useEffect, useState } from "react";
import type { LocalAccount } from "viem";
 
const usePrivySigner = () => {
  const {
    wallets: [wallet],
  } = useWallets();
 
  const [signer, setSigner] = useState<LocalAccount>();
 
  useEffect(() => {
    if (!wallet || signer) return;
    toViemAccount({ wallet }).then(setSigner);
  }, [wallet, signer]);
 
  return signer;
};

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

import { usePrivy } from "@privy-io/react-auth";
 
function PrivyWallet() {
  const { ready, authenticated, login, logout } = usePrivy();
  const signer = usePrivySigner();
 
  if (!ready) return <p>Loading...</p>;
 
  if (!authenticated) {
    return <button onClick={() => login()}>Login with Privy</button>;
  }
 
  return (
    <div>
      <button onClick={() => logout()}>Logout</button>
      {signer ? <SendTransaction signer={signer} /> : <p>Loading signer...</p>}
    </div>
  );
}

Pass the Privy signer to createSmartWalletClient and use it to send transactions:

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>;
}

  • The client defaults to EIP-7702, which delegates the Privy wallet to a smart wallet at send time. No deployment or wallet migration needed. See the EIP-7702 guide for non-7702 mode.
  • See the Sponsor gas guide for more on gas sponsorship configuration.

The example below uses @solana/kit and @solana-program/system to build the transfer call:

npm install @solana/kit @solana-program/system

Enable Solana embedded wallets in your PrivyProvider config:

<PrivyProvider
  appId="your-privy-app-id"
  config={{
    embeddedWallets: {
      ethereum: { createOnLogin: "all-users" },
      solana: { createOnLogin: "all-users" },
      showWalletUIs: false,
    },
  }}
>
  <YourApp />
</PrivyProvider>

Use useWallets from @privy-io/react-auth/solana to get the connected Solana wallet. The returned wallet can be passed directly as the Wallet APIs Solana signer.

SendSponsoredSolanaTransaction.tsx
import {
  alchemyWalletTransport,
  createSmartWalletClient,
} from "@alchemy/wallet-apis";
import { useWallets } from "@privy-io/react-auth/solana";
import { useCallback, useMemo } from "react";
import { transferSolCall } from "./utils";
 
export function SendSponsoredSolanaTransaction() {
  const { wallets } = useWallets();
  const signer = wallets[0];
 
  const client = useMemo(() => {
    if (!signer) return undefined;
 
    return createSmartWalletClient({
      transport: alchemyWalletTransport({ apiKey: "YOUR_ALCHEMY_API_KEY" }),
      chain: "solana:devnet",
      signer,
      paymaster: { policyId: "YOUR_SOLANA_POLICY_ID" },
    });
  }, [signer]);
 
  const handleSend = useCallback(async () => {
    if (!client) return;
 
    const { id } = await client.sendCalls({
      calls: [
        transferSolCall({
          from: client.solanaAccount,
          to: client.solanaAccount,
          lamports: 0n,
        }),
      ],
    });
 
    const result = await client.waitForCallsStatus({ id });
    console.log("Sponsored Solana transaction status:", result.status);
  }, [client]);
 
  return (
    <button onClick={handleSend} disabled={!client}>
      Send sponsored Solana transaction
    </button>
  );
}

  • Solana sponsorship requires a Solana gas sponsorship policy. See Solana sponsorship.
  • For non-Privy Solana signer sources (Phantom, @solana/wallet-adapter-react, raw keypairs, Wallet Standard), see Solana signer adapters.
Was this page helpful?