Skip to content
Alchemy Logo

Solana signers

Solana Wallet APIs require an Ed25519 signer that can sign serialized Solana transactions. The signer parameter of createSmartWalletClient accepts a SolanaSigner when you use a Solana chain ID such as "solana:devnet" or "solana:mainnet".

type SolanaSigner = {
  address: string;
  signTransaction(input: {
    transaction: Uint8Array;
  }): Promise<{ signedTransaction: Uint8Array }>;
};

Most apps do not need to implement this interface directly. Some providers already return this shape. For the rest, use an adapter from @alchemy/wallet-apis/solana.

npm install @alchemy/wallet-apis

Install the optional package that matches your signer source:

  • @solana/wallet-adapter-react: @solana/wallet-adapter-react @solana/web3.js
  • Injected providers such as Phantom: @solana/web3.js
  • @solana/kit signers: @solana/kit
  • Wallet Standard discovery: @wallet-standard/app

What you haveWhat to use
Privy Solana wallet from @privy-io/react-auth/solanaNo adapter — see the Privy guide
useWallet() from @solana/wallet-adapter-reactfromWalletAdapter
Injected provider such as window.phantom.solanafromWalletAdapter
@solana/kit KeyPairSigner or TransactionPartialSignerfromKitSigner
Raw Ed25519 keypair or key management servicefromKeypair
Low-level Wallet Standard walletfromWalletStandard

Use fromWalletAdapter for wallets returned by useWallet(). This adapter handles the Uint8Array to VersionedTransaction conversion required by the wallet adapter API.

wallet-adapter-signer.tsx
import {
  alchemyWalletTransport,
  createSmartWalletClient,
} from "@alchemy/wallet-apis";
import { fromWalletAdapter } from "@alchemy/wallet-apis/solana";
import { useWallet } from "@solana/wallet-adapter-react";
import { useMemo } from "react";
 
function useWalletAdapterSolanaClient() {
  const wallet = useWallet();
 
  return useMemo(() => {
    if (!wallet.publicKey || !wallet.signTransaction) return undefined;
 
    return createSmartWalletClient({
      signer: fromWalletAdapter({
        publicKey: wallet.publicKey,
        signTransaction: wallet.signTransaction,
      }),
      transport: alchemyWalletTransport({ apiKey: "YOUR_ALCHEMY_API_KEY" }),
      chain: "solana:devnet",
      paymaster: { policyId: "YOUR_SOLANA_POLICY_ID" },
    });
  }, [wallet.publicKey, wallet.signTransaction]);
}

Use the same fromWalletAdapter adapter for injected providers that expose publicKey and signTransaction(VersionedTransaction), such as window.phantom.solana.

phantom-signer.ts
import {
  alchemyWalletTransport,
  createSmartWalletClient,
} from "@alchemy/wallet-apis";
import { fromWalletAdapter } from "@alchemy/wallet-apis/solana";
import type { VersionedTransaction } from "@solana/web3.js";
 
interface PhantomSolanaProvider {
  publicKey?: { toBase58(): string };
  connect(): Promise<{ publicKey: { toBase58(): string } }>;
  signTransaction<T extends VersionedTransaction>(transaction: T): Promise<T>;
}
 
declare global {
  interface Window {
    phantom?: { solana?: PhantomSolanaProvider };
  }
}
 
const phantom = window.phantom?.solana;
if (!phantom) {
  throw new Error("Install Phantom to use the injected Solana provider");
}
 
const { publicKey } = await phantom.connect();
 
const client = createSmartWalletClient({
  signer: fromWalletAdapter({
    publicKey,
    signTransaction: (transaction) => phantom.signTransaction(transaction),
  }),
  transport: alchemyWalletTransport({ apiKey: "YOUR_ALCHEMY_API_KEY" }),
  chain: "solana:devnet",
  paymaster: { policyId: "YOUR_SOLANA_POLICY_ID" },
});

Use fromKitSigner for @solana/kit signers that implement signTransactions, including KeyPairSigner and TransactionPartialSigner.

kit-signer.ts
import {
  alchemyWalletTransport,
  createSmartWalletClient,
} from "@alchemy/wallet-apis";
import { fromKitSigner } from "@alchemy/wallet-apis/solana";
import { generateKeyPairSigner } from "@solana/kit";
 
const kitSigner = await generateKeyPairSigner();
 
const client = createSmartWalletClient({
  signer: fromKitSigner(kitSigner),
  transport: alchemyWalletTransport({ apiKey: "YOUR_ALCHEMY_API_KEY" }),
  chain: "solana:devnet",
  paymaster: { policyId: "YOUR_SOLANA_POLICY_ID" },
});

Use fromKeypair when your signer exposes an address and a signMessage(bytes) method that returns a 64-byte Ed25519 signature. This is the right adapter for a bare Ed25519 keypair or a key management service.

keypair-signer.ts
import {
  alchemyWalletTransport,
  createSmartWalletClient,
} from "@alchemy/wallet-apis";
import { fromKeypair } from "@alchemy/wallet-apis/solana";
 
interface Ed25519Keypair {
  address: string;
  privateKey: CryptoKey;
}
 
async function loadEd25519Keypair(): Promise<Ed25519Keypair> {
  // Load this from your app's key storage or KMS.
  throw new Error("Implement loadEd25519Keypair for your app");
}
 
const { address, privateKey } = await loadEd25519Keypair();
 
const client = createSmartWalletClient({
  signer: fromKeypair({
    address,
    async signMessage(message) {
      const messageBytes = Uint8Array.from(message);
      return new Uint8Array(
        await crypto.subtle.sign("Ed25519", privateKey, messageBytes),
      );
    },
  }),
  transport: alchemyWalletTransport({ apiKey: "YOUR_ALCHEMY_API_KEY" }),
  chain: "solana:devnet",
  paymaster: { policyId: "YOUR_SOLANA_POLICY_ID" },
});

Your signMessage implementation must return a valid 64-byte Ed25519 signature for the message bytes. Do not return placeholder bytes; Wallet APIs rejects missing or all-zero signatures.

Use fromWalletStandard when you are working directly with the low-level Wallet Standard API. Most app developers should use Privy or @solana/wallet-adapter-react instead.

wallet-standard-signer.ts
import {
  alchemyWalletTransport,
  createSmartWalletClient,
} from "@alchemy/wallet-apis";
import {
  fromWalletStandard,
  type WalletStandardAccount,
  type WalletStandardWallet,
} from "@alchemy/wallet-apis/solana";
import { getWallets } from "@wallet-standard/app";
 
type ConnectableWalletStandardWallet = Omit<
  WalletStandardWallet,
  "features"
> & {
  features: {
    readonly [name: string]: unknown;
    readonly "standard:connect": {
      connect(): Promise<{ accounts: readonly WalletStandardAccount[] }>;
    };
    readonly "solana:signTransaction": unknown;
  };
};
 
function isSolanaStandardWallet(
  wallet: WalletStandardWallet,
): wallet is ConnectableWalletStandardWallet {
  return (
    "standard:connect" in wallet.features &&
    "solana:signTransaction" in wallet.features
  );
}
 
const wallets: readonly WalletStandardWallet[] = getWallets().get();
const wallet = wallets.find(isSolanaStandardWallet);
if (!wallet) {
  throw new Error("Connect a wallet that supports solana:signTransaction");
}
 
const { accounts } = await wallet.features["standard:connect"].connect();
const account = accounts[0];
if (!account) {
  throw new Error("No Solana account returned by the wallet");
}
 
const client = createSmartWalletClient({
  signer: fromWalletStandard(wallet, account),
  transport: alchemyWalletTransport({ apiKey: "YOUR_ALCHEMY_API_KEY" }),
  chain: "solana:devnet",
  paymaster: { policyId: "YOUR_SOLANA_POLICY_ID" },
});

Use your Solana signer to:

Was this page helpful?