diff --git a/apps/web/content/docs/ar/frontend/meta.json b/apps/web/content/docs/ar/frontend/meta.json index 7d528af9d..464709650 100644 --- a/apps/web/content/docs/ar/frontend/meta.json +++ b/apps/web/content/docs/ar/frontend/meta.json @@ -1,4 +1,4 @@ { "title": "الواجهة الأمامية", - "pages": ["kit"] + "pages": ["nextjs-solana"] } \ No newline at end of file diff --git a/apps/web/content/docs/de/frontend/meta.json b/apps/web/content/docs/de/frontend/meta.json index de12c1e30..71218677c 100644 --- a/apps/web/content/docs/de/frontend/meta.json +++ b/apps/web/content/docs/de/frontend/meta.json @@ -1,4 +1,4 @@ { "title": "Frontend", - "pages": ["kit"] + "pages": ["nextjs-solana"] } \ No newline at end of file diff --git a/apps/web/content/docs/el/frontend/meta.json b/apps/web/content/docs/el/frontend/meta.json index de12c1e30..71218677c 100644 --- a/apps/web/content/docs/el/frontend/meta.json +++ b/apps/web/content/docs/el/frontend/meta.json @@ -1,4 +1,4 @@ { "title": "Frontend", - "pages": ["kit"] + "pages": ["nextjs-solana"] } \ No newline at end of file diff --git a/apps/web/content/docs/en/clients/official/javascript.mdx b/apps/web/content/docs/en/clients/official/javascript.mdx index 97cb77ab3..fde6e8779 100644 --- a/apps/web/content/docs/en/clients/official/javascript.mdx +++ b/apps/web/content/docs/en/clients/official/javascript.mdx @@ -6,6 +6,26 @@ description: h1: JavaScript/TypeScript SDK for Solana --- +## Solana Client + +`@solana/client` is the headless Solana runtime that bundles RPC, wallets, transactions, and subscriptions in one store. + +| Package | Description | GitHub | +| --------------- | ---------------------------- | ---------------------------------------------------------------------------------------- | +| @solana/client | Headless client runtime | [Source](https://github.com/solana-foundation/framework-kit/tree/main/packages/client) | + +- [Guide: @solana/client overview](/docs/frontend/client) + +## Solana React Hooks + +`@solana/react-hooks` layers a React provider and hooks on top of `@solana/client`, reusing the same runtime and cache. + +| Package | Description | GitHub | +| -------------------- | ------------------------------- | --------------------------------------------------------------------------------------------- | +| @solana/react-hooks | React provider + hooks for UIs | [Source](https://github.com/solana-foundation/framework-kit/tree/main/packages/react-hooks) | + +- [Guide: @solana/react-hooks](/docs/frontend/react-hooks) + ## Solana Kit `@solana/kit` is the recommended TypeScript SDK for building on Solana. diff --git a/apps/web/content/docs/en/frontend/client.mdx b/apps/web/content/docs/en/frontend/client.mdx new file mode 100644 index 000000000..04605887e --- /dev/null +++ b/apps/web/content/docs/en/frontend/client.mdx @@ -0,0 +1,104 @@ +--- +title: "@solana/client" +description: Build lean Solana frontends with the headless Solana client runtime. +--- + +`@solana/client` keeps the runtime surface lean. One store, one RPC stack, and a wallet registry power +the different helpers so the same instance can back CLIs, scripts, or full UIs. Actions, watchers, and +helpers all share cache, subscriptions, and wallet sessions through that single client. + + +When you are building a purely React experience it is usually faster to start with +[`@solana/react-hooks`](/docs/frontend/react-hooks). The hooks package wraps this same client runtime and +exposes ready-made hooks so you only drop to the headless client when you need extra control. + + +## Install + +```terminal +$ npm install @solana/client +``` + +Use any package manager; the client runs in browsers, workers, React, Svelte, or server-side runtimes. + +## Create a client once + +Choose Wallet Standard connectors (auto discovery is the fastest way to start), then create the client. +This single object exposes the store, actions, watchers, and helpers. + +```ts +import { autoDiscover, createClient } from "@solana/client"; + +const client = createClient({ + endpoint: "https://api.devnet.solana.com", + websocketEndpoint: "wss://api.devnet.solana.com", + walletConnectors: autoDiscover(), +}); + +await client.actions.connectWallet("wallet-standard:phantom"); +const balance = await client.actions.fetchBalance("Fke...address"); +console.log(balance.lamports?.toString()); +``` + +The client store tracks cluster config, subscriptions, pending transactions, and wallet sessions. You can +provide your own Zustand store if you need persistence or multi-tab coordination. + +## Wallet orchestration + +Connectors encapsulate Wallet Standard metadata plus connect/disconnect logic. Register the built-in +`phantom()`, `solflare()`, `backpack()`, or `autoDiscover()` helpers, or wrap custom providers with +`createWalletStandardConnector`. All wallet actions (connect, disconnect, sign, send) flow through the +client registry so every consumer stays in sync. + +## Actions, watchers, and helpers + +- **Actions** wrap common RPC reads and writes while updating the store (e.g., `fetchAccount`, + `requestAirdrop`, `setCluster`). +- **Watchers** multiplex websocket subscriptions, stream updates into the store, and give you abort + handles for cleanup. +- **Helpers** expose higher-level flows such as SOL transfers, SPL token helpers, signature polling, and + transaction pools. + +```ts +const abortWatcher = client.watchers.watchBalance( + { address: "Fke...address" }, + (lamports) => { + console.log("live balance", lamports.toString()); + }, +); + +// Later when the component unmounts or the flow ends +abortWatcher.abort(); +``` + +## Transaction helper pattern + +The transaction helper owns blockhash refresh, fee payer resolution, and signing. You can prepare, +inspect, and send with whatever UX you prefer. + +```ts +const prepared = await client.helpers.transaction.prepare({ + authority: client.store.getState().wallet.session!, + instructions: [instructionA, instructionB], +}); + +await client.helpers.transaction.simulate(prepared, { commitment: "processed" }); +const signature = await client.helpers.transaction.send(prepared); +console.log("submitted", signature.toString()); +``` + +Use `prepareAndSend` for a pre-built flow (simulation plus logging) or call `sign` / `toWire` to collect +signatures manually before relaying the wire format yourself. + +## Common patterns for Solana devs + +- **Headless state machines**: Run the client inside API routes, scripts, or workers to reuse the same + wallet + RPC orchestration logic that powers your UI. +- **Realtime dashboards**: Combine watchers (balances, accounts, signatures) with your preferred UI + library; the client handles websocket fan-out and cache invalidation. +- **Custom stores**: Inject your own Zustand store to hydrate from IndexedDB/localStorage, mirror state to + server sessions, or coordinate between browser tabs. +- **Bridge to React hooks**: Pass a configured client instance to `@solana/react-hooks` when you want + ergonomic hooks on top of the same runtime. +- **Testability**: The single client interface can be mocked in unit tests, making it easy to simulate + RPC responses or wallet sessions without a browser wallet present. diff --git a/apps/web/content/docs/en/frontend/index.mdx b/apps/web/content/docs/en/frontend/index.mdx new file mode 100644 index 000000000..cd6ca0aa2 --- /dev/null +++ b/apps/web/content/docs/en/frontend/index.mdx @@ -0,0 +1,44 @@ +--- +title: Frontend +description: Learn about building interfaces for Solana Programs. +--- + +Frontend development on Solana involves working with Programs, wallets and +popular JavaScript frameworks like React. Interacting with these components +requires handling connection, transaction creation and reading from Solana +Accounts. + +To help with this work, a variety Solana Client libraries are available in different frameworks. + +## Main libraries + + + + + + - Simple Solana client bundling RPC, wallets, transactions + - Includes built-in state store, actions, watchers, connectors + + + - Complete hooks for wallets, balances, transfers, signatures, queries + - React provider hooks wrapping `@solana/client` runtime state + + + - Web3.js compatible toolkit to simplify upgrading. + - Newer internals relying on a mix of web3.js and kit. + + + - Low level Solana SDK powering the other Solana libraries like `@solana/react-hooks` + - Fully tree-shakable, uses modern web standards, and powers the runtime + + + + + + Many Solana ecosystem projects still rely on the deprecated `@solana/web3.js`. + Prefer `@solana/web3-compat` to simplify your migration path. + + +- [@solana/client guide](/docs/frontend/client): lean, headless runtime for RPC, wallets, and transactions. +- [@solana/react-hooks guide](/docs/frontend/react-hooks): React hooks layered on the same client runtime. +- [@solana/web3-compat guide](/docs/frontend/web3-compat): compatibility layer to migrate from `@solana/web3.js` to Kit powered stacks. diff --git a/apps/web/content/docs/en/frontend/kit.mdx b/apps/web/content/docs/en/frontend/kit.mdx deleted file mode 100644 index 5414724c5..000000000 --- a/apps/web/content/docs/en/frontend/kit.mdx +++ /dev/null @@ -1,678 +0,0 @@ ---- -title: Next.js - Solana Kit -seoTitle: Solana wallet integration with Next.js and Solana Kit -description: Set up Solana wallet integration in Next.js with Solana Kit. ---- - -Set up a minimal Solana wallet integration in Next.js with Solana Kit. - -This guide provides a minimal example of implementing Solana wallet -functionality in a Next.js application using `@solana/kit`. You'll create a -connect wallet button and a component to send transactions. - -![Nextjs Kit Application](/assets/docs/frontend/nextjs-kit.gif) - -For a more comprehensive example of using `@solana/kit` in a React application, -refer to the -[React App Example](https://github.com/anza-xyz/kit/tree/main/examples/react-app) -in the Solana Kit repository. - -## Resources - -- [Solana Kit Documentation](https://www.solanakit.com/docs) -- [Solana Kit GitHub Repository](https://github.com/anza-xyz/kit) - -## Prerequisites - -- Install [Node.js](https://nodejs.org/en/download) - -## Create Next.js Project - -Create a new Next.js project with [shadcn](https://ui.shadcn.com/) for ui -components and install required Solana dependencies. - -```terminal -$ npx shadcn@latest init -``` - -Navigate to your project directory: - -```terminal -$ cd -``` - -### Install UI Components - -Install the following shadcn ui components: - -```terminal -$ npx shadcn@latest add button dropdown-menu avatar -``` - -### Install Solana Dependencies - -Install the following Solana dependencies: - -```terminal -$ npm install @solana/kit @solana/react @wallet-standard/core @wallet-standard/react @solana-program/memo -``` - -## Implementation Walkthrough - -Follow the steps below and copy the provided code to your project. - - - -## !!steps 1. Create Solana Context - -First, create a React Context that manages the entire wallet state for the -application. - -Create `components/solana-provider.tsx` and add the provided code. This provider -component will: - -- Connect to Solana's devnet RPC endpoints -- Filter for available Solana wallets installed in the user's browser -- Track which wallet and account is currently connected -- Provide wallet state to child components - - - -```tsx !! title="components/solana-provider.tsx" -"use client"; - -import React, { createContext, useContext, useState, useMemo } from "react"; -import { - useWallets, - type UiWallet, - type UiWalletAccount -} from "@wallet-standard/react"; -import { createSolanaRpc, createSolanaRpcSubscriptions } from "@solana/kit"; -import { StandardConnect } from "@wallet-standard/core"; - -// Create RPC connection -const RPC_ENDPOINT = "https://api.devnet.solana.com"; -const WS_ENDPOINT = "wss://api.devnet.solana.com"; -const chain = "solana:devnet"; -const rpc = createSolanaRpc(RPC_ENDPOINT); -const ws = createSolanaRpcSubscriptions(WS_ENDPOINT); - -interface SolanaContextState { - // RPC - rpc: ReturnType; - ws: ReturnType; - chain: typeof chain; - - // Wallet State - wallets: UiWallet[]; - selectedWallet: UiWallet | null; - selectedAccount: UiWalletAccount | null; - isConnected: boolean; - - // Wallet Actions - setWalletAndAccount: ( - wallet: UiWallet | null, - account: UiWalletAccount | null - ) => void; -} - -const SolanaContext = createContext(undefined); - -export function useSolana() { - const context = useContext(SolanaContext); - if (!context) { - throw new Error("useSolana must be used within a SolanaProvider"); - } - return context; -} - -export function SolanaProvider({ children }: { children: React.ReactNode }) { - const allWallets = useWallets(); - - // Filter for Solana wallets only that support signAndSendTransaction - const wallets = useMemo(() => { - return allWallets.filter( - (wallet) => - wallet.chains?.some((c) => c.startsWith("solana:")) && - wallet.features.includes(StandardConnect) && - wallet.features.includes("solana:signAndSendTransaction") - ); - }, [allWallets]); - - // State management - const [selectedWallet, setSelectedWallet] = useState(null); - const [selectedAccount, setSelectedAccount] = - useState(null); - - // Check if connected (account must exist in the wallet's accounts) - const isConnected = useMemo(() => { - if (!selectedAccount || !selectedWallet) return false; - - // Find the wallet and check if it still has this account - const currentWallet = wallets.find((w) => w.name === selectedWallet.name); - return !!( - currentWallet && - currentWallet.accounts.some( - (acc) => acc.address === selectedAccount.address - ) - ); - }, [selectedAccount, selectedWallet, wallets]); - - const setWalletAndAccount = ( - wallet: UiWallet | null, - account: UiWalletAccount | null - ) => { - setSelectedWallet(wallet); - setSelectedAccount(account); - }; - - // Create context value - const contextValue = useMemo( - () => ({ - // Static RPC values - rpc, - ws, - chain, - - // Dynamic wallet values - wallets, - selectedWallet, - selectedAccount, - isConnected, - setWalletAndAccount - }), - [wallets, selectedWallet, selectedAccount, isConnected] - ); - - return ( - - {children} - - ); -} -``` - -```tsx !! title="app/layout.tsx" -// default layout -``` - -```tsx !! title="app/page.tsx" -// default page -``` - -```tsx !! title="components/ui/" -// shadcn ui components -``` - -```json !! title="package.json" -{ - "name": "my-app", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev --turbopack", - "build": "next build --turbopack", - "start": "next start", - "lint": "eslint" - }, - "dependencies": { - "@radix-ui/react-avatar": "^1.1.10", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-slot": "^1.2.3", - "@solana-program/memo": "^0.8.0", - "@solana/kit": "^3.0.3", - "@solana/react": "^3.0.3", - "@wallet-standard/core": "^1.1.1", - "@wallet-standard/react": "^1.0.1", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "lucide-react": "^0.544.0", - "next": "15.5.4", - "react": "19.1.0", - "react-dom": "19.1.0", - "tailwind-merge": "^3.3.1" - }, - "devDependencies": { - "@eslint/eslintrc": "^3", - "@tailwindcss/postcss": "^4", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "eslint": "^9", - "eslint-config-next": "15.5.4", - "tailwindcss": "^4", - "tw-animate-css": "^1.4.0", - "typescript": "^5" - } -} -``` - -## !!steps 2. Update Layout - -Next, wrap the entire Next.js application with the Solana provider. - -Update `app/layout.tsx` with the provided code. This step: - -- Imports the `SolanaProvider` component -- Wraps the application's children components with the `SolanaProvider` -- Ensures all pages and components have access to wallet functionality - - - -```tsx !! title="app/layout.tsx" -import { SolanaProvider } from "@/components/solana-provider"; -import "./globals.css"; - -export default function RootLayout({ - children -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - - {children} - - - ); -} -``` - -```tsx !! title="app/page.tsx" -// default page -``` - -## !!steps 3. Create Wallet Connect Button - -Now build the button to connect and disconnect wallets. - -Create `components/wallet-connect-button.tsx` and add the provided code. This -dropdown button: - -- Shows available wallets when clicked -- Handles the wallet connection flow using the Wallet Standard - - - -```tsx !! title="components/wallet-connect-button.tsx" -"use client"; - -import { useState } from "react"; -import { useSolana } from "@/components/solana-provider"; -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger -} from "@/components/ui/dropdown-menu"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { ChevronDown, Wallet, LogOut } from "lucide-react"; -import { - useConnect, - useDisconnect, - type UiWallet -} from "@wallet-standard/react"; - -function truncateAddress(address: string): string { - return `${address.slice(0, 4)}...${address.slice(-4)}`; -} - -function WalletIcon({ - wallet, - className -}: { - wallet: UiWallet; - className?: string; -}) { - return ( - - {wallet.icon && ( - - )} - {wallet.name.slice(0, 2).toUpperCase()} - - ); -} - -function WalletMenuItem({ - wallet, - onConnect -}: { - wallet: UiWallet; - onConnect: () => void; -}) { - const { setWalletAndAccount } = useSolana(); - const [isConnecting, connect] = useConnect(wallet); - - const handleConnect = async () => { - if (isConnecting) return; - - try { - const accounts = await connect(); - - if (accounts && accounts.length > 0) { - const account = accounts[0]; - setWalletAndAccount(wallet, account); - onConnect(); - } - } catch (err) { - console.error(`Failed to connect ${wallet.name}:`, err); - } - }; - - return ( - - ); -} - -function DisconnectButton({ - wallet, - onDisconnect -}: { - wallet: UiWallet; - onDisconnect: () => void; -}) { - const { setWalletAndAccount } = useSolana(); - const [isDisconnecting, disconnect] = useDisconnect(wallet); - - const handleDisconnect = async () => { - try { - await disconnect(); - setWalletAndAccount(null, null); - onDisconnect(); - } catch (err) { - console.error("Failed to disconnect wallet:", err); - } - }; - - return ( - - - Disconnect - - ); -} - -export function WalletConnectButton() { - const { wallets, selectedWallet, selectedAccount, isConnected } = useSolana(); - - const [dropdownOpen, setDropdownOpen] = useState(false); - - return ( - - - - - - - {wallets.length === 0 ? ( -

- No wallets detected -

- ) : ( - <> - {!isConnected ? ( - <> - Available Wallets - - {wallets.map((wallet, index) => ( - setDropdownOpen(false)} - /> - ))} - - ) : ( - selectedWallet && - selectedAccount && ( - <> - Connected Wallet - -
-
- -
- - {selectedWallet.name} - - - {truncateAddress(selectedAccount.address)} - -
-
-
- - setDropdownOpen(false)} - /> - - ) - )} - - )} -
-
- ); -} -``` - -```tsx !! title="app/page.tsx" -// default page -``` - -## !!steps 4. Create Send Transaction Component - -Create a component that sends a transaction invoking the memo program to add a -message to the translation logs. - -The purpose of this component is to demonstrate how to send transactions with -the connected wallet. - -Create `components/memo-card.tsx` and add the provided code. This component: - -- Allows users to input a message -- Creates a Solana transaction with an instruction invoking the memo program -- Requests the connected wallet to sign and send the transaction -- Displays a link to view the transaction on Solana Explorer - - - -```tsx !! title="components/memo-card.tsx" -"use client"; - -import { useState } from "react"; -import { useSolana } from "@/components/solana-provider"; -import { useWalletAccountTransactionSendingSigner } from "@solana/react"; -import { type UiWalletAccount } from "@wallet-standard/react"; -import { - pipe, - createTransactionMessage, - appendTransactionMessageInstruction, - setTransactionMessageFeePayerSigner, - setTransactionMessageLifetimeUsingBlockhash, - signAndSendTransactionMessageWithSigners, - getBase58Decoder, - type Signature -} from "@solana/kit"; -import { getAddMemoInstruction } from "@solana-program/memo"; - -// Component that only renders when wallet is connected -function ConnectedMemoCard({ account }: { account: UiWalletAccount }) { - const { rpc, chain } = useSolana(); - const [isLoading, setIsLoading] = useState(false); - const [memoText, setMemoText] = useState(""); - const [txSignature, setTxSignature] = useState(""); - - const signer = useWalletAccountTransactionSendingSigner(account, chain); - - const sendMemo = async () => { - if (!signer) return; - - setIsLoading(true); - try { - const { value: latestBlockhash } = await rpc - .getLatestBlockhash({ commitment: "confirmed" }) - .send(); - - const memoInstruction = getAddMemoInstruction({ memo: memoText }); - - const message = pipe( - createTransactionMessage({ version: 0 }), - (m) => setTransactionMessageFeePayerSigner(signer, m), - (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), - (m) => appendTransactionMessageInstruction(memoInstruction, m) - ); - - const signature = await signAndSendTransactionMessageWithSigners(message); - const signatureStr = getBase58Decoder().decode(signature) as Signature; - - setTxSignature(signatureStr); - setMemoText(""); - } catch (error) { - console.error("Memo failed:", error); - } finally { - setIsLoading(false); - } - }; - - return ( -
-
- -