Week 4 • Kit Foundations
Kit Foundations
0-to-1 foundation for Solana client development with @solana/kit, @solana/client, and @solana/react-hooks.
@solana/web3.js is now a legacy path for new apps. In this module, we build with @solana/kit and modern Solana client packages.
Step 1: 0-to-1 Theory
Primitives
rpc: how your app asks the chain for data.rpcSubscriptions: how your app listens to live chain changes.client runtime: shared state for wallet/session/network.transaction message: the unsigned plan of what the chain should execute.
Mental Model
Solana client apps have three jobs:
- Read chain state (RPC reads).
- Ask a wallet to sign (the app never sees the private key).
- Send the signed bytes and track confirmation.
If those three jobs are mixed across random UI components, beginner projects become impossible to debug.
Invariants
- One app should have one default Solana runtime config.
- Cluster and commitment should be explicit, never hidden.
- UI should not build raw transactions directly.
Quick Checks
- Why do we separate runtime config from UI components?
- Why is one provider better than many providers?
- What breaks if cluster is hardcoded in random files?
Step 2: Real-World Implementation (Code Solution)
Install:
# Install the Solana client packages we use in Week 4.
pnpm add @solana/kit @solana/client @solana/react-hooks @tanstack/react-queryCreate lib/solana/config.ts:
// Centralize network settings so every page uses the same cluster + commitment.
// Put your RPC URLs in NEXT_PUBLIC_* so Next.js can expose them to the browser.
export const SOLANA_RPC = process.env.NEXT_PUBLIC_SOLANA_RPC ?? "https://api.devnet.solana.com";
export const SOLANA_WS = process.env.NEXT_PUBLIC_SOLANA_WS ?? "wss://api.devnet.solana.com";
export const SOLANA_CLUSTER = "devnet" as const;
export const SOLANA_COMMITMENT = "confirmed" as const;Create app/providers.tsx:
"use client";
import React from "react";
import { autoDiscover, createClient } from "@solana/client";
import { SolanaProvider } from "@solana/react-hooks";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { SOLANA_RPC, SOLANA_WS } from "@/lib/solana/config";
// One shared QueryClient keeps async state stable across pages and rerenders.
const queryClient = new QueryClient();
// Create one Solana client for the whole app (do not recreate per page).
const client = createClient({
endpoint: SOLANA_RPC,
websocketEndpoint: SOLANA_WS,
// Auto-discover Wallet Standard compatible wallets installed in the browser.
walletConnectors: autoDiscover(),
});
export function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{/* SolanaProvider exposes wallet + rpc context to @solana/react-hooks */}
<SolanaProvider client={client}>{children}</SolanaProvider>
</QueryClientProvider>
);
}Update app/layout.tsx (core part only):
import { Providers } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{/* Wrap the app once so every page shares the same Solana runtime config. */}
<Providers>{children}</Providers>
</body>
</html>
);
}Expected result:
- app starts with one shared Solana runtime.
Step 3: Mastery Test
- Easy: if websocket URL is wrong, what feature breaks first?
- Medium: if one page uses a second client with a different endpoint, what bug class appears?
- Hard (senior): how would you support runtime endpoint switching without tearing wallet session state?
If you cannot explain the invariants above, do not move to Lesson 2 yet.