Phantom Wallet Integration (Solana)
Phantom is the de-facto standard wallet in Solana ecosystem. Its API is injected into window.solana and follows SolanaProvider interface from wallet-standard spec. Integration is straightforward, but there are several places to shoot yourself in the foot.
Connection and Provider Detection
First mistake — check window.solana immediately on page load. Extension injects asynchronously, on fast machines it happens before your JS executes, on slow ones it doesn't. Reliable pattern:
const getProvider = (): PhantomProvider | undefined => {
if ('phantom' in window) {
const provider = (window as any).phantom?.solana;
if (provider?.isPhantom) return provider;
}
return undefined;
};
window.phantom.solana — preferable to window.solana because latter might be intercepted by other wallets (Backpack, Solflare). For supporting multiple wallets, use wallet-adapter from Solana Labs — @solana/wallet-adapter-react, which abstracts all providers via single interface.
Connection, Signing and Transactions
// Connection
const response = await provider.connect();
const publicKey = response.publicKey.toString();
// Message signing (for authentication)
const message = new TextEncoder().encode("Sign in to MyApp");
const { signature } = await provider.signMessage(message, "utf8");
// Send transaction
const transaction = new Transaction().add(/* instruction */);
transaction.feePayer = provider.publicKey;
transaction.recentBlockhash = (
await connection.getLatestBlockhash()
).blockhash;
const { signature: txSig } = await provider.signAndSendTransaction(transaction);
Important moment: signAndSendTransaction sends transaction via Phantom's own RPC. If you want to control RPC endpoint (e.g., use Helius or QuickNode with priority fees), use signTransaction + connection.sendRawTransaction manually.
State and Event Handling
Phantom emits connect, disconnect and accountChanged events. Mandatory to subscribe to accountChanged — user might switch account inside wallet without reconnecting, and your app won't know without explicit listener.
provider.on('accountChanged', (publicKey: PublicKey | null) => {
if (publicKey) {
// Update app state
} else {
// Wallet locked — logout user
provider.connect().catch(() => {});
}
});
For React apps better to wrap this layer in @solana/wallet-adapter-react — it manages lifecycle, memoization and reconnect automatically.







