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

# Solana Integration Guide for Farcaster Mini Apps

> Learn how to integrate Solana wallet features in your Farcaster Mini App with conditional support, message signing, and transaction handling

# Solana Integration Guide for Farcaster Mini Apps

Guide for using Solana wallet features in your Farcaster Mini App template.

## How It Works

### Conditional Solana Support

Not all Farcaster clients support Solana wallets, so your app should gracefully handle both scenarios.

```typescript theme={"system"}
import { useHasSolanaProvider } from "~/components/providers/SafeFarcasterSolanaProvider";
import { useConnection as useSolanaConnection, useWallet as useSolanaWallet } from '@solana/wallet-adapter-react';

function MyComponent() {
  const hasSolanaProvider = useHasSolanaProvider();
  
  // Only declare Solana hooks when provider is available
  let solanaWallet, solanaPublicKey, solanaSignMessage, solanaAddress;
  if (hasSolanaProvider) {
    solanaWallet = useSolanaWallet();
    ({ publicKey: solanaPublicKey, signMessage: solanaSignMessage } = solanaWallet);
    solanaAddress = solanaPublicKey?.toBase58();
  }

  return (
    <div>
      {/* EVM features always available */}
      <EvmFeatures />
      
      {/* Solana features when supported, not all clients support Solana */}
      {solanaAddress && (
        <div>
          <h2>Solana</h2>
          <div>Address: {solanaAddress}</div>
          <SignSolanaMessage signMessage={solanaSignMessage} />
          <SendSolana />
        </div>
      )}
    </div>
  );
}
```

### Sign Message

Solana message signing requires converting text to bytes and handling the response properly for browser compatibility.

```typescript theme={"system"}
function SignSolanaMessage({ signMessage }: { signMessage?: (message: Uint8Array) => Promise<Uint8Array> }) {
  const [signature, setSignature] = useState<string | undefined>();
  const [signError, setSignError] = useState<Error | undefined>();
  const [signPending, setSignPending] = useState(false);

  const handleSignMessage = useCallback(async () => {
    setSignPending(true);
    try {
      if (!signMessage) {
        throw new Error('no Solana signMessage');
      }
      const input = new TextEncoder().encode("Hello from Solana!");
      const signatureBytes = await signMessage(input);
      const signature = btoa(String.fromCharCode(...signatureBytes));
      setSignature(signature);
      setSignError(undefined);
    } catch (e) {
      if (e instanceof Error) {
        setSignError(e);
      }
    } finally {
      setSignPending(false);
    }
  }, [signMessage]);

  return (
    <>
      <Button
        onClick={handleSignMessage}
        disabled={signPending}
        isLoading={signPending}
        className="mb-4"
      >
        Sign Message
      </Button>
      {signError && renderError(signError)}
      {signature && (
        <div className="mt-2 text-xs">
          <div>Signature: {signature}</div>
        </div>
      )}
    </>
  );
}
```

### Send Transaction

Solana transactions require proper setup including blockhash, simulation, and error handling.

```typescript theme={"system"}
import { Transaction, SystemProgram, PublicKey } from '@solana/web3.js';

function SendSolana() {
  const [state, setState] = useState<
    | { status: 'none' }
    | { status: 'pending' }
    | { status: 'error'; error: Error }
    | { status: 'success'; signature: string }
  >({ status: 'none' });

  const { connection: solanaConnection } = useSolanaConnection();
  const { sendTransaction, publicKey } = useSolanaWallet();

  const handleSend = useCallback(async () => {
    setState({ status: 'pending' });
    try {
      if (!publicKey) {
        throw new Error('no Solana publicKey');
      }

      const { blockhash } = await solanaConnection.getLatestBlockhash();
      if (!blockhash) {
        throw new Error('failed to fetch latest Solana blockhash');
      }

      const transaction = new Transaction();
      transaction.add(
        SystemProgram.transfer({
          fromPubkey: publicKey,
          toPubkey: new PublicKey('DESTINATION_ADDRESS_HERE'),
          lamports: 0n, // 0 SOL for demo
        }),
      );
      transaction.recentBlockhash = blockhash;
      transaction.feePayer = publicKey;

      // Simulate first
      const simulation = await solanaConnection.simulateTransaction(transaction);
      if (simulation.value.err) {
        const logs = simulation.value.logs?.join('\n') ?? 'No logs';
        const errDetail = JSON.stringify(simulation.value.err);
        throw new Error(`Simulation failed: ${errDetail}\nLogs:\n${logs}`);
      }
      
      const signature = await sendTransaction(transaction, solanaConnection);
      setState({ status: 'success', signature });
    } catch (e) {
      if (e instanceof Error) {
        setState({ status: 'error', error: e });
      }
    }
  }, [sendTransaction, publicKey, solanaConnection]);

  return (
    <>
      <Button
        onClick={handleSend}
        disabled={state.status === 'pending'}
        isLoading={state.status === 'pending'}
        className="mb-4"
      >
        Send Transaction (sol)
      </Button>
      {state.status === 'error' && renderError(state.error)}
      {state.status === 'success' && (
        <div className="mt-2 text-xs">
          <div>Signature: {state.signature.slice(0, 20)}...</div>
        </div>
      )}
    </>
  );
}
```

## Key Points

* Always check `useHasSolanaProvider()` before rendering Solana UI
* Use `TextEncoder` and `btoa` for browser-compatible message signing
* Simulate transactions before sending to catch errors early
* Import Solana hooks from `@solana/wallet-adapter-react` not `@farcaster/mini-app-solana`
* Replace placeholder addresses with real addresses for your app

### Custom Program Interactions

For calling your own Solana programs, you'll need to serialize instruction data and handle program-derived addresses.

```typescript theme={"system"}
import { 
  TransactionInstruction, 
  SYSVAR_RENT_PUBKEY,
  SystemProgram 
} from '@solana/web3.js';
import * as borsh from 'borsh';

class InstructionData {
  instruction: number;
  amount: number;
  
  constructor(props: { instruction: number; amount: number }) {
    this.instruction = props.instruction;
    this.amount = props.amount;
  }
}

const instructionSchema = new Map([
  [InstructionData, { 
    kind: 'struct', 
    fields: [
      ['instruction', 'u8'],
      ['amount', 'u64']
    ] 
  }]
]);

async function callCustomProgram(programId: string, instruction: number, amount: number) {
  if (!publicKey) throw new Error('Wallet not connected');
  
  // Serialize instruction data
  const instructionData = new InstructionData({ instruction, amount });
  const serializedData = borsh.serialize(instructionSchema, instructionData);
  
  // Create program-derived address (if needed)
  const [programDataAccount] = await PublicKey.findProgramAddress(
    [Buffer.from('your-seed'), publicKey.toBuffer()],
    new PublicKey(programId)
  );
  
  const transaction = new Transaction();
  transaction.add(
    new TransactionInstruction({
      keys: [
        { pubkey: publicKey, isSigner: true, isWritable: false },
        { pubkey: programDataAccount, isSigner: false, isWritable: true },
        { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
        { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
      ],
      programId: new PublicKey(programId),
      data: Buffer.from(serializedData),
    })
  );
  
  // ... rest of transaction setup and sending
}
```

For advanced contract interactions, token transfers, and error handling patterns, see the template's Demo.tsx component.
