This guide will demonstrate how to integrate Privy with Solana to enable wallet login as well as message and transaction signing.

Resources

Creating your Privy app

If you haven’t set up Privy yet, follow our React quickstart guide to get your app ID and configure your app.

Privy’s React SDK provides a secure way to authenticate users and manage wallets in your frontend application. Learn more about getting started with React.

Configuring Privy

In order to detect external Solana wallets, your app needs to enable Solana connectors:

// components/providers.tsx
'use client';

import {PrivyProvider} from '@privy-io/react-auth';
import {toSolanaWalletConnectors} from '@privy-io/react-auth/solana';
import {ReactNode} from 'react';

export function Providers({children}: {children: ReactNode}) {
  return (
    <PrivyProvider
      appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID}
      config={{
        appearance: {
          showWalletLoginFirst: true,
          walletChainType: 'solana-only'
        },
        loginMethods: ['wallet', 'email'],
        externalWallets: {
          solana: {
            connectors: toSolanaWalletConnectors() // For detecting EOA browser wallets
          }
        },
        embeddedWallets: {
          createOnLogin: 'all-users'
        }
      }}
    >
      {children}
    </PrivyProvider>
  );
}

Using Privy in your app

With Privy now integrated into your app, you can leverage its hooks to authenticate users, generate embedded wallets, and facilitate message and transaction signing.

Log in with Privy

To log in users with Privy, you can use the useLogin hook from the @privy-io/react-auth package. This hook provides a function to log in users.

// components/loginButton.tsx
'use client';
import {useLogin} from '@privy-io/react-auth';
import {useRouter} from 'next/navigation';

export function LoginButton() {
  const router = useRouter();
  const {login} = useLogin({
    onError: (error) => {
      console.error('Login error:', error);
    },
    onComplete: (user) => {
      console.log('User logged in:', user);
      // Redirect to the dashboard or another page after login
      router.replace('/dashboard');
    }
  });

  return <button onClick={login}>Log in with Privy</button>;
}

Creating a Solana embedded wallet

To create a Solana embedded wallet, you can use the useSolanaWallets hook from @privy-io/react-auth. This hook provides a createWallet function to create an embedded wallet.

// components/createWalletButton.tsx
'use client';
import {useSolanaWallets} from '@privy-io/react-auth/solana';

export function CreateWalletButton(props: {createAdditional: boolean}) {
  const {createWallet, ready} = useSolanaWallets();

  if (!ready) {
    return <div>Loading...</div>;
  }

  const handleCreateWallet = async () => {
    try {
      // If createAdditional is true, it will create an additional HD wallet for the user.
      const wallet = await createWallet({createAdditional: props.createAdditional});
      console.log('Embedded wallet created:', wallet);
    } catch (error) {
      console.error('Error creating embedded wallet:', error);
    }
  };

  return <button onClick={handleCreateWallet}>Create Embedded Wallet</button>;
}

Using wallets

Privy provides the useSignMessage, useSignTransaction, and useSendTransaction hooks to sign messages and transactions with embedded wallets. You can also use linked EOA wallets directly for signing messages and transactions.

Signing a message

To sign a message with an embedded wallet, use the useSignMessage hook:

import {useSignMessage, useSolanaWallets} from '@privy-io/react-auth/solana';

const {signMessage} = useSignMessage();
const {wallets} = useSolanaWallets(); // This hook provides access to both embedded and EOA wallets

const handleSignMessage = async () => {
  try {
    const wallet = wallets.find((w) => w.connectorType === 'embedded'); // Find the first embedded wallet
    if (!wallet) throw new Error('No embedded wallet found');
    const signature = await signMessage({
      message: new TextEncoder().encode('Hello from Privy!'), // Solana messages are typically encoded as Uint8Array
      options: {
        address: wallet.address
      }
    });
    console.log('Message signed:', signature);
  } catch (error) {
    console.error('Error signing message:', error);
  }
};

This function signs a message using the first embedded wallet.

Preparing a transaction

Before signing or sending a transaction, you need to prepare it. Here’s how you can create a simple SOL transfer transaction:

import {Transaction, SystemProgram, PublicKey, Connection} from '@solana/web3.js';

const generateTransaction = async () => {
  // Simple SOL transfer transaction
  const transferInstruction = SystemProgram.transfer({
    fromPubkey: new PublicKey(wallets[0].address), // Replace with the sender's address
    toPubkey: new PublicKey('RecipientPublicKeyHere'), // Replace with the recipient's address
    lamports: 1000000 // Amount in lamports (1 SOL = 1,000,000,000 lamports)
  });

  const tx = new Transaction().add(transferInstruction);
  const connection = new Connection('https://api.mainnet-beta.solana.com'); // Replace with your Solana RPC endpoint
  const recentBlockhash = await connection.getLatestBlockhash();

  tx.feePayer = new PublicKey(wallets[0].address); // Set fee payer
  tx.recentBlockhash = recentBlockhash.blockhash; // Replace with a recent blockhash
  return tx;
};

This code creates a transaction object that can be signed or sent later. Replace the placeholder values with actual addresses.

Signing a transaction

To sign a transaction, use the useSignTransaction hook:

import {useSignTransaction, useSolanaWallets} from '@privy-io/react-auth/solana';
import {Connection} from '@solana/web3.js';

const {signTransaction} = useSignTransaction();
const {wallets} = useSolanaWallets(); // This hook provides access to both embedded and EOA wallets

const handleSignTransaction = async () => {
  try {
    const wallet = wallets.find((w) => w.connectorType === 'embedded'); // Find the first embedded wallet
    if (!wallet) throw new Error('No embedded wallet found');

    const transaction = await generateTransaction(); // The transaction prepared earlier
    const transactionSignature = await signTransaction({
      transaction,
      address: wallet.address,
      connection: new Connection('https://api.mainnet-beta.solana.com') // Replace with your Solana RPC endpoint
    });
    console.log('Transaction signed:', transactionSignature);
  } catch (error) {
    console.error('Error signing transaction:', error);
  }
};

This function signs the prepared transaction using the embedded wallet and a specified Solana RPC endpoint.

Sending a transaction

To send a signed transaction to the Solana network, use the useSendTransaction hook:

import {useSendTransaction, useSolanaWallets} from '@privy-io/react-auth/solana';
import {Connection} from '@solana/web3.js';

const {sendTransaction} = useSendTransaction();
const {wallets} = useSolanaWallets(); // This hook provides access to both embedded and EOA wallets

const handleSendTransaction = async () => {
  try {
    const wallet = wallets.find((w) => w.connectorType === 'embedded'); // Find the first embedded wallet
    if (!wallet) throw new Error('No embedded wallet found');

    const transaction = await generateTransaction(); // The transaction prepared earlier
    const transactionSignature = await sendTransaction({
      transaction,
      address: wallet.address,
      connection: new Connection('https://api.mainnet-beta.solana.com') // Replace with your Solana RPC endpoint
    });
    console.log('Transaction sent:', transactionSignature);
  } catch (error) {
    console.error('Error sending transaction:', error);
  }
};

This function sends the signed transaction to the Solana network and logs the transaction signature.

Using EOA wallets

In the case of EOA wallets, you can use the wallet methods directly from the wallet object. For example, to sign a message with an EOA wallet, you can use the signMessage method from the wallet object:

// ... code from previous example
const handleSignMessageWithEOA = async () => {
  try {
    const wallet = wallets.find((w) => w.connectorType === 'solana_adapter'); // Find the first EOA wallet
    if (!wallet) throw new Error('No EOA wallet found');

    const signature = await wallet.signMessage(new TextEncoder().encode('Hello from Privy!'));
    console.log('Message signed with EOA wallet:', signature);
  } catch (error) {
    console.error('Error signing message with EOA wallet:', error);
  }
};

const handleSendTransactionWithEOA = async () => {
  try {
    const wallet = wallets.find((w) => w.connectorType === 'solana_adapter'); // Find the first EOA wallet
    if (!wallet) throw new Error('No EOA wallet found');

    const transaction = await generateTransaction(); // The transaction prepared earlier
    const transactionSignature = await wallet.sendTransaction(transaction, connection);
    console.log('Transaction sent with EOA wallet:', transactionSignature);
  } catch (error) {
    console.error('Error sending transaction with EOA wallet:', error);
  }
};

Conclusion

This guide has shown you how to integrate Privy with Solana into an application. You can now log in users, create embedded wallets, and sign messages and transactions using the Privy React SDK.