Wallet Integration Key Points
On Solana, users hold keys in a wallet (browser extension, mobile app, or hardware device). Your dApp never receives a private key. It discovers wallets, requests connection, builds unsigned transaction messages, and asks the wallet to sign. The wallet shows a human-readable approval UI; after the user confirms, your app sends the signed payload to RPC and tracks confirmation.
This page is the conceptual map for Wallet Integration - adapter layers, connect and disconnect, transaction and message signing, autoconnect and session UX, hardware and mobile paths, and the security boundaries that keep keys out of your process on Agave 4.1.1-aligned stacks.
Summary
- Wallet integration is a capability bridge: discover a standard wallet, establish an origin-scoped session, sign only what the user approves, then broadcast with your own RPC and UX. Secrets stay inside the wallet.
- Insight: Every user-paid action (swap, mint, transfer, game move) and most off-chain auth (SIWS) depend on correct connect state, fee-payer identity, cluster alignment, and honest previews. Mistakes produce failed txs, wrong-account data, or phishing-grade UX.
- Key Concepts: Wallet Standard, wallet adapter / wallet-ui, connect / disconnect / select, publicKey session, signTransaction / signAllTransactions / signMessage, TransactionSendingSigner bridge, autoConnect, SIWS, cluster match, MWA / hardware timeouts.
- When to Use This Model: Shipping a React or Next.js dApp, wiring
@solana/kit7.0.0 message pipelines to browser signers, designing session UX, supporting Ledger or mobile, or auditing where keys and signatures are allowed to live. - Limitations/Trade-offs: Connect is not a blank check for future spends; each sensitive action needs approval. Adapters differ by product; mobile and hardware add latency and failure modes. Server auth needs separate signature verification, not "connected in React" alone.
- Related Topics: Wallet Basics, Wallet Adapter, Connecting & Disconnecting, Signing Transactions & Messages, Autoconnect & Session UX, Hardware & Mobile Wallets.
Foundations
A Solana wallet is software or hardware that holds keypairs (or seed-derived accounts), tracks cluster context (mainnet, devnet, testnet), and exposes a narrow API: connect, disconnect, sign transactions, and optionally sign arbitrary messages.
The dApp owns product UX, instruction construction, RPC reads, simulation, submission, and confirmation. It must not take seed phrases or private keys if it claims non-custodial signing.
User key material
|
v
Wallet (extension / mobile / hardware)
| connect, signTransaction, signMessage
v
Adapter / Wallet Standard bridge
| React context: publicKey, connected, signers
v
dApp (@solana/kit build + RPC send + UI)Wallet Standard (and the multi-adapter ecosystem around it) is how browsers discover installed wallets without hardcoding one brand. Extensions register; your app lists them for user selection. On mobile, Mobile Wallet Adapter (MWA) and deep links play a similar role with different plumbing.
In React, @solana/wallet-adapter-react (ConnectionProvider, WalletProvider, useWallet, optional modal) is still common. Newer scaffolds may use @wallet-ui/react. Both share the same contract: client-only providers, registered adapters, and hooks for publicKey, connected, connecting, select, connect, disconnect, and signing.
Connection means this origin may request signatures for a chosen account. It is not an on-chain transaction and not server login. publicKey is the client session identity for address-keyed state.
Cluster alignment is mandatory: wallet network, RPC URL, and program IDs must match. Mainnet wallet against devnet RPC yields opaque simulation and send failures. Show a cluster badge and block send on mismatch when you can detect it.
Read-only work (balances, explorers, marketing) uses RPC without connect. Gate only actions that need a fee payer or a user signature.
Mechanics & Interactions
Adapter setup and discovery
Mount providers only on the client ("use client" in Next.js App Router). Register popular wallets so the modal is not empty. Pair adapter signing with @solana/kit 7.0.0 for message construction and RPC.
"use client";
// Conceptual shape - wallets array + autoConnect + modal
<ConnectionProvider endpoint={rpcUrl}>
<WalletProvider wallets={wallets} autoConnect>
<WalletModalProvider>{children}</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>Adapters map Wallet Standard events into React context. useWallet() is the app surface. Log wallet?.adapter.name in support tooling for wallet-specific bugs.
Connect, select, and disconnect
Typical UX: Connect, pick a wallet in a modal, approve in the extension or app, then show a truncated address and disconnect.
| State | Meaning | UI duty |
|---|---|---|
| disconnected | No session | Connect / select wallet |
| connecting | Prompt or autoConnect in flight | Disable double-click; show restoring or connecting |
| connected | publicKey available | Account chip, disconnect, enable sign actions |
| disconnecting / error | Teardown or failure | Friendly retry; clear optimistic UI |
const { connected, connecting, publicKey, disconnect, select } = useWallet();- Prefer a modal picker when multiple wallets may be installed; bare
connect()without a selected adapter is ambiguous. - Disconnect must clear app stores and caches keyed by pubkey so the next account does not inherit portfolio or auth state.
- Connect does not approve future transactions. Each send still needs a fresh wallet approval.
Signing transactions and messages
Your app builds an unsigned message: fee payer = connected pubkey, recent blockhash, instructions (and compute budget as needed). Optionally simulate before the wallet modal. Call the wallet sign API. After signatures return, you still serialize, send via RPC, and confirm.
1. connected publicKey
2. getLatestBlockhash (kit RPC)
3. createTransactionMessage + fee payer + instructions
4. optional simulate
5. wallet.signTransaction / kit sign helpers
6. send + confirmsignMessage covers off-chain bytes (for example Sign-In with Solana). The wallet returns an ed25519 signature. Servers must verify signature, domain binding, and nonce or expiry; never treat "client says connected" as auth.
Common failure modes: fee payer ≠ connected account; stale blockhash after slow hardware or mobile approval (rebuild); oversized txs without ALTs; assuming signTransaction also confirms on-chain (signing is local; send is yours).
Bridge the wallet into kit's TransactionSendingSigner (or equivalent) so the typed message path and adapter stay one pipeline.
Autoconnect and session UX
autoConnect restores the last chosen wallet on mount (often a localStorage preference, never secret keys). That reduces friction for return visits.
| Pattern | When it fits | Risk if misused |
|---|---|---|
| autoConnect + loading gate | Consumer dApps, frequent txs | Flash of "disconnected" UI if you ignore connecting |
| Manual connect only | High-security admin, rare ops | Extra friction for daily users |
| Lazy connect | Marketing sites; connect on first sign intent | Higher conversion than landing-page modals |
| SIWS + server cookie/JWT | Personalized backends, gated APIs | Forged sessions if verification is client-only |
Show restoring session while connecting is true. Do not open the full connect modal on every load. Always expose disconnect. Treat SIWS as a separate layer: adapter reconnect does not re-prove identity to your API until you re-verify a signature or a still-valid server session.
Hardware and mobile
On desktop, Ledger users often connect through a broker wallet that forwards previews to the device. App code stays adapter-driven; latency and multi-step confirmation increase. On mobile, extension injection differs: use MWA, deep links, or injected in-app browsers. Lengthen timeouts. Test on real devices. Seeker and Seed Vault keep the same rule: dApp requests signatures; device holds seeds. Batch instructions so one product action does not mean many device prompts.
Advanced Considerations & Applications
Treat wallet integration as a security boundary, not only UI chrome.
| Boundary | Allowed in dApp | Must stay in wallet / device |
|---|---|---|
| Private keys / seeds | Never | Always |
| Connection preference | localStorage OK | N/A |
| Transaction bytes to sign | Build and display intent | Sign after user review |
| Off-chain auth message | Compose with domain + expiry | Sign; server verifies |
| Session cookies | After SIWS verify | Not a substitute for keys |
- Origin isolation: Connection is per site. Do not teach users to export seeds to "fix" a bad connection.
- Preview honesty: Simulate and summarize token deltas and fee payer before the wallet prompt when you can.
- No secret storage: Only public keys, cluster preference, and UI flags in browser storage.
- Server trust: Verify SIWS (ed25519, domain binding, replay protection) server-side for authenticated APIs.
- Cluster and program IDs: Config must not silently point a mainnet wallet at the wrong cluster or program.
Keep wallet-adapter or wallet-ui versions aligned with kit and your scaffold. Dual-stack (kit reads + adapter sign) is fine if documented; dual send paths are not. Log adapter name, cluster, and truncated pubkey for support. QA two software wallets plus hardware and mobile for consumer apps. Connect modals need focus trap, keyboard nav, and friendly reject copy.
On an Agave 4.1.1-aligned stack (CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1, @solana/kit 7.0.0), exercise connect, reject, disconnect, delayed sign (stale blockhash), and cluster mismatch before mainnet.
Common Misconceptions
- "Connect means the site can spend freely." Connect only opens a session for later prompts. Each transaction still needs user approval.
- "I can store the private key in localStorage for convenience." That turns a non-custodial product into a malware honeypot. Never.
- "signTransaction also lands the transaction." Signing returns signatures; your code must send and confirm.
- "One wallet brand is enough." Users arrive with Phantom, Solflare, Ledger-brokered setups, and mobile wallets. Discover multiple options.
- "autoConnect is a security feature." It is a UX feature. High-security admin UIs may prefer manual connect every time.
- "Connected in React equals logged in on the server." Server sessions need SIWS verification. Client
publicKeyalone is forgeable in requests. - "Mobile is the same API as the extension." Deep links and MWA change session and timeout behavior.
- "Hardware wallets need special instruction bytes from the dApp." Usually the same message is signed; timers and previews must adapt, not a second protocol.
- "RPC network and wallet network can disagree if I retry hard enough." Align cluster badge, RPC, and wallet network first.
- "Disconnect only hides the address chip." Disconnect must drop the adapter session and clear pubkey-keyed application state.
FAQs
What does a Solana wallet do for a dApp?
It holds keys, shows approval UI, and returns signatures. The dApp discovers the wallet, builds messages, requests signatures, and handles RPC send and confirmation.
What is Wallet Standard?
A browser discovery convention so multiple wallets can register and dApps can list them without hardcoding one extension API.
wallet-adapter vs wallet-ui?
@solana/wallet-adapter-react remains widely used. @wallet-ui/react appears in newer scaffolds. Both sit above Wallet Standard-style discovery; pick one stack and keep versions aligned with kit.
When should I prompt the user to connect?
Prefer lazy connect: prompt when the user starts an action that needs signing or a pubkey-specific view. Do not block pure read content. See Connecting & Disconnecting.
What should disconnect clear?
Adapter session plus app caches and stores keyed by the previous publicKey. Leave public marketing data alone.
How do I sign with @solana/kit 7.0.0 and a browser wallet?
Build the message with kit (fee payer, blockhash, instructions), bridge the wallet into a kit-compatible signer, sign, then send via kit RPC. See Signing Transactions & Messages.
signTransaction vs signMessage?
signTransaction (or sign-all) authorizes on-chain transaction bytes. signMessage signs off-chain bytes for auth or attestations. Only signatures return to the page.
Should I enable autoConnect?
Yes for most consumer apps, with a loading gate while connecting is true and a visible disconnect. Prefer manual connect for high-security admin. See Autoconnect & Session UX.
How does SIWS relate to wallet connect?
Connect gives the client a publicKey. SIWS proves control of that key to your backend via a domain-bound message and server-side ed25519 verification. They are not the same layer.
Why do hardware wallet users fail more often?
Longer approvals expire blockhashes; short UI timeouts abort early; multi-prompt flows cause abandonment. Extend timers, rebuild messages, and batch instructions. See Hardware & Mobile Wallets.
How should mobile wallets integrate?
Use MWA and deep-link return paths for your stack (web vs React Native). Test on devices. Do not assume desktop extension injection.
Do I need a wallet to read balances?
No. Public RPC reads work with any address. Require connect only when the user must sign or when you personalize UI around their pubkey.
What is the main security boundary?
Secret key material never enters dApp memory or storage. The dApp may hold public keys, preferences, unsigned messages it built, and signatures returned after user approval.
How do I handle user rejection?
Treat cancel as normal: friendly copy, enable retry, no stack-trace banners. Do not auto-resubmit in a loop.
Related
- Wallet Basics - Wallet Standard, lazy connect, cluster awareness
- Wallet Adapter - provider setup and
useWallethooks - Connecting & Disconnecting - session state and multi-wallet selection
- Signing Transactions & Messages - kit message pipeline and wallet sign
- Autoconnect & Session UX - restore session without surprise modals
- Hardware & Mobile Wallets - Ledger, MWA, timeouts
- Wallet Integration Best Practices - production checklist
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.