Solana Frontend Integration with @solana/web3.js
Solana frontend is not EVM. There's no ethers.js, no useContractRead. The account model, wallets via Wallet Adapter, deserialization via Borsh — everything is different. The main trap for developers with EVM background is trying to apply the same patterns. It doesn't work.
Wallet Connection: Wallet Adapter
@solana/wallet-adapter is the standard way to connect Phantom, Solflare, Backpack, and other wallets:
// providers.tsx
import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react'
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui'
import { PhantomWalletAdapter, SolflareWalletAdapter } from '@solana/wallet-adapter-wallets'
import { clusterApiUrl } from '@solana/web3.js'
const wallets = [new PhantomWalletAdapter(), new SolflareWalletAdapter()]
const endpoint = clusterApiUrl('mainnet-beta') // or your RPC
export function SolanaProviders({ children }: { children: React.ReactNode }) {
return (
<ConnectionProvider endpoint={endpoint}>
<WalletProvider wallets={wallets} autoConnect>
<WalletModalProvider>
{children}
</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
)
}
Importing styles is mandatory: import '@solana/wallet-adapter-react-ui/styles.css'. Without it, the connection modal won't display correctly.
Reading Data from the Blockchain
Connection from @solana/web3.js is similar to PublicClient in viem. Key methods:
import { Connection, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js'
const connection = new Connection(
process.env.NEXT_PUBLIC_RPC_URL!,
'confirmed' // commitment level: processed | confirmed | finalized
)
// SOL balance
const balance = await connection.getBalance(new PublicKey(address))
const solBalance = balance / LAMPORTS_PER_SOL
// Account information (program account data)
const accountInfo = await connection.getAccountInfo(new PublicKey(address))
Commitment levels — an important detail often overlooked. processed — fastest, but the transaction might revert. confirmed — confirmed by ~66% stake. finalized — irreversible. For UI typically confirmed, for financial operations — finalized.
Working with SPL Tokens
Most dApps work with SPL tokens, not native SOL. You need @solana/spl-token:
import { getAssociatedTokenAddress, getAccount } from '@solana/spl-token'
import { PublicKey } from '@solana/web3.js'
// Address of the user's token account
const tokenAccount = await getAssociatedTokenAddress(
new PublicKey(mintAddress), // token mint address
new PublicKey(walletAddress) // owner wallet
)
// Balance
try {
const account = await getAccount(connection, tokenAccount)
const balance = Number(account.amount) / 10 ** decimals
} catch (e) {
// TokenAccountNotFoundError — account not created, balance is 0
}
Nuance: token account might not exist if the user has never held this token. This isn't an error — it's just a balance of 0. You need to handle TokenAccountNotFoundError.
Sending Transactions
import { useWallet } from '@solana/wallet-adapter-react'
import { Transaction, SystemProgram, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js'
const { publicKey, sendTransaction } = useWallet()
const handleSend = async () => {
if (!publicKey) return
const { blockhash, lastValidBlockHeight } =
await connection.getLatestBlockhash()
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: publicKey,
toPubkey: new PublicKey(recipient),
lamports: amount * LAMPORTS_PER_SOL,
})
)
transaction.recentBlockhash = blockhash
transaction.feePayer = publicKey
const signature = await sendTransaction(transaction, connection)
// Wait for confirmation
await connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight })
}
sendTransaction from the wallet adapter automatically requests the user's wallet signature.
RPC and Rate Limits
The public clusterApiUrl('mainnet-beta') is only for development — it's heavily rate-limited. For production: Helius, QuickNode, Alchemy (Solana), or your own node. Helius is especially convenient — it provides enhanced API with transaction parsing and webhooks for on-chain events.
Integration takes 2–3 days: setting up wallet adapter, connecting to RPC, basic read/write operations, and handling typical errors (insufficient funds, blockhash expired, simulation failed).







