Fetch User Information
- User by Wallet Address
- Mutual Follows/Followers
- Username Search
- Mutes, Blocks, and Bans
Build Farcaster Mini Apps
Host Farcaster Mini Apps
Fetch Cast Information
Build Bots and Agents
- Create Farcaster Bot or Agent
- Listen for @bot Mentions
- Make Agents Prompt Transactions
Fetch Farcaster Feeds
- Trending Feed on Farcaster
- Channel Feeds
- Feed of Given Farcaster user ID
- Casts by Embed in Farcaster
- How to Use the Neynar Feed API
Onboard New Users
- Create New Farcaster Account
- SIWN: Connect Farcaster Accounts
- Fetch Signers
- Add User Verification
Write Data to Farcaster
- Choose the Right Signer
- Write Data with Managed Signers
- Like & Recast
- Write Casts to Channel
- Auth address signature generation
Filter Spam, Low Quality Data
Run Queries on FC Data
- Choose Among Data Products
- Ingest Farcaster Data
- Indexer Service
- Hosted SQL
Render Farcaster in React
Get Events Via Webhooks
Fetch Notifications
Create Onchain Transactions
Intersect Onchain & Social Data
Write Direct Casts
Get Farcaster Storage Data
Publish Actions on FC Apps
Get Farcaster Data on Base
Explore Network Health
Get Hypersub Subscriptions
Contribute To Development
Build Farcaster Mini Apps
Solana miniapp features
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.
Copy
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.
Copy
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.
Copy
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
andbtoa
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.
Copy
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.
Was this page helpful?
Assistant
Responses are generated using AI and may contain mistakes.