Pular para o conteúdo principal
Superteam Brasil

Connect a Wallet Without wallet-adapter

+20 XP
5/11

Connect a Wallet Without wallet-adapter

You have a published package. Now build the app that uses it — starting with the one thing every dApp needs and most get wrong: connecting a wallet.

Why there is no graded exercise here. This lesson's runtime is a sandbox with no DOM and no module resolution, so a React component and live Wallet Standard discovery cannot be graded. You get an annotated walkthrough instead. The graded rung of this module is the next lesson, where signer selection is pure logic. The lesson after that grades the transaction you assemble.

One client, built from plugins

Kit builds a client by composing plugins. You build it once, at module scope, and share it through a provider:

// client.ts — built once
import { createClient } from "@solana/kit";
import { solanaDevnetRpc } from "@solana/kit-plugin-rpc";
import { walletSigner } from "@solana/kit-plugin-wallet";
import { vaultProgram } from "@your-scope/vault-client";

export const client = createClient()
  .use(solanaDevnetRpc())
  .use(walletSigner())
  .use(vaultProgram());

Install list (verify at authoring — this course pins kit 7.0.0, checked 2026-07-27):

@solana/kit @solana/kit-plugin-rpc @solana/kit-plugin-wallet @solana/react @solana-program/system swr @tanstack/react-query

The last two are not optional decoration: @solana/react peer-depends on swr and @tanstack/react-query, and omitting them produces missing-module errors on the first install — the single most common setup failure.

The stale trap this lesson exists to kill

The old stack — ConnectionProvider, WalletProvider, WalletModalProvider, PhantomWalletAdapter, useWallet() — is what most tutorials and most of your search results still teach. This app uses none of it. Wallet Standard is a browser protocol wallets implement directly, so discovery needs no per-wallet adapter package. If a code sample in this course reaches for WalletProvider, it is wrong.

The surprise: a client is bound to one chain

A Kit client is bound to exactly one chain and one endpoint at build time. Switching clusters — devnet to mainnet — is not a config change you flip; it means rebuilding the client, which in React means a useMemo keyed on the endpoint. This is the first thing that surprises people, so the walkthrough builds it in from the start.

Read the annotated walkthrough next: it publishes the client with ClientProvider, reads it with useClient(), and renders a connect/disconnect control driven by the Wallet Standard hooks — keeping the loading / empty / error states that age well from the old react-patterns material, and rewriting every line of its code.

Annotated Walkthrough: Connect / Disconnect

Read each step and the note under it. You will build this for real when you deploy in lesson 7; here the goal is to recognize every piece.

1. Publish the client to the tree

import { ClientProvider } from "@solana/react";
import { client } from "./client";

export function App() {
  return (
    <ClientProvider client={client}>
      <WalletBar />
    </ClientProvider>
  );
}

ClientProvider puts the one module-scope client on the context. Every component below reads the same instance — you never build a second one per render.

2. Rebuild only when the endpoint changes

import { useMemo } from "react";
import { createClient } from "@solana/kit";
import { solanaDevnetRpc } from "@solana/kit-plugin-rpc";

function useVaultClient(endpoint: string) {
  // Keyed on the endpoint: switching devnet -> mainnet rebuilds the client,
  // because a Kit client is bound to one chain and cannot be re-pointed.
  return useMemo(
    () => createClient().use(solanaDevnetRpc(endpoint)).use(walletSigner()).use(vaultProgram()),
    [endpoint]
  );
}

The useMemo dependency is the whole point: nothing rebuilds on a normal render, but a cluster switch does.

3. Discover, connect, disconnect

import { useWallets, useConnect, useConnectedWallet, useDisconnect } from "@solana/kit-plugin-wallet/react";

function WalletBar() {
  const wallets = useWallets();          // whatever the visitor actually has installed
  const connect = useConnect();
  const connected = useConnectedWallet(); // undefined until one is connected
  const disconnect = useDisconnect();

  if (connected) {
    return (
      <button onClick={() => disconnect()}>
        Disconnect {connected.accounts[0]?.address.slice(0, 4)}…
      </button>
    );
  }

  if (wallets.length === 0) {
    // The EMPTY state — no wallet installed. Do not render a dead button.
    return <p>No Solana wallet found. Install one to continue.</p>;
  }

  return (
    <>
      {wallets.map((w) => (
        <button key={w.name} onClick={() => connect(w)}>Connect {w.name}</button>
      ))}
    </>
  );
}

useWallets() returns whatever Wallet Standard wallets the browser advertises — no adapter package per wallet, no hardcoded Phantom. The three states from react-patterns survive as concepts: connected (show identity + disconnect), empty (no wallet — a real state, not an error), and the error state you add when a connect attempt is rejected.

What is deliberately absent

No ConnectionProvider. No WalletProvider. No PhantomWalletAdapter. No useWallet(). If you paste any of them in from a tutorial, you are on the old stack, and it will not compose with the Kit client you built.

Pergunta 1 de 2

You switch the app from devnet to mainnet by changing the endpoint string, but the app keeps talking to devnet. Predict the cause.

Responda todas as perguntas para desbloquear o AI Partner nesta lição.

AnteriorPróximo

Discussão