Rendu depuis le dépôt source en conservant titres, exemples, code, tableaux, liens et images.
Solana wallets on mobile
Wallet connection and transaction signing through Mobile Wallet Adapter (MWA), wrapped by @wallet-ui/react-native-kit (or @wallet-ui/react-native-web3js on the legacy stack).
MWA requires a development build on Android. Expo Go will not work. If the project has no development build yet, or does not exist, start with the solana-mobile skill.
Step 1: pick the stack — do this before writing any code
Write kit code. @solana/kit with @wallet-ui/react-native-kit is the stack to reach for, and everything in this file describes it.
There is exactly one reason to write @solana/web3.js instead: the project already runs on it. Check package.json first.
package.json says | Do this |
|---|---|
@wallet-ui/react-native-kit, or no Solana client yet | Kit. This file, plus references/kit.md |
@wallet-ui/react-native-web3js is the app's Solana client | references/web3js.md |
| The user explicitly asked for web3.js | references/web3js.md, and say why kit would be better |
Do not introduce web3.js into a kit project, or mix the two in one app. Their provider props, hook return values, and transaction construction all differ, so code from one silently fails on the other. If a project has no Solana client at all, that is a new build — use kit.
Adding a new wallet feature to an existing web3.js app is not a reason to migrate mid-task. Match what is there, and mention migration as a follow-up if it seems worth it.
Step 2: confirm the provider is mounted
useMobileWallet returns empty state without MobileWalletProvider above it. Look for it in the root layout or an app-providers module.
Build the cluster with the createSolana* helpers rather than by hand — a SolanaCluster also needs a label, which the helpers fill in:
import {
type AppIdentity,
createSolanaDevnet,
MobileWalletProvider,
type SolanaCluster,
} from '@wallet-ui/react-native-kit'
const identity: AppIdentity = { name: 'My App' }
const cluster: SolanaCluster = createSolanaDevnet({ url: 'https://api.devnet.solana.com' })
<MobileWalletProvider cluster={cluster} identity={identity}>
{children}
</MobileWalletProvider>createSolanaDevnet, createSolanaTestnet, and createSolanaLocalnet take optional props; createSolanaMainnet requires a url, since there is no sensible public default for mainnet.
The provider props are cluster, identity, and optional cache, createClient, children. There is no `chain` prop and no `endpoint` prop — passing those does nothing.
Every AppIdentity field is optional. name alone is enough to get started; add uri as a real deep link for anything shipping, since wallets display it during authorization and a placeholder can read as a phishing attempt.
Put QueryClientProvider from @tanstack/react-query above the wallet provider — the hook patterns below are queries and mutations.
Step 3: use the hook
import { useMobileWallet } from '@wallet-ui/react-native-kit'
const { account, connect, disconnect, client } = useMobileWallet()What the hook actually returns on the kit stack:
| Value | Type | Notes | |
|---|---|---|---|
account | `Account \ | undefined` | undefined when disconnected, not null |
accounts | `Account[] \ | null` | All authorized accounts |
connect | () => Promise<Account> | Opens the wallet picker | |
disconnect | () => Promise<void> | ||
client | Client | Kit client — use client.rpc for RPC calls | |
chain | SolanaClusterId | The active cluster. Put it in query keys | |
sendTransactions | (instructions: Instruction[]) => Promise<string> | Simplest send path | |
signAndSendTransaction | (tx, minContextSlot) => Promise<SignatureBytes> | Note the second argument | |
signTransaction | (tx) => Promise<Transaction> | Sign without broadcasting | |
signMessages | (msg: Uint8Array) => Promise<Uint8Array> | ||
signIn | (payload) => Promise<SignInOutput> | Sign-in with Solana; can also connect | |
identity, store | Config and authorization store |
Singular aliases exist for several of these — sendTransaction, signMessage, signTransactions — with the same signatures. The templates use the plural forms; either works, so follow whatever the project already uses.
Four things that trip people up:
- There is no `connected` boolean. Derive it:
const connected = !!account. - `signAndSendTransaction` takes `minContextSlot` as a second argument. Calling it with
only a transaction fails. Get the slot from getLatestBlockhash, or use sendTransactions(instructions), which handles this for you.
- The kit hook exposes `client`, not `connection`.
connectiononly exists on the
web3.js stack.
- Include `chain` in every React Query key that holds chain data. Otherwise switching
cluster serves the previous network's cached balances, which looks like a wallet bug.
account.address is a kit Address (a branded string), so it interpolates into text directly. account.label is the wallet-supplied name and may be undefined.
Connect and disconnect
import { Pressable, Text } from 'react-native'
import { useMobileWallet } from '@wallet-ui/react-native-kit'
export function ConnectButton() {
const { account, connect, disconnect } = useMobileWallet()
async function onPress() {
try {
if (account) await disconnect()
else await connect()
} catch (error) {
// The user dismissing the wallet picker lands here. Do not treat it as a crash.
console.error(error)
}
}
return (
<Pressable onPress={onPress}>
<Text>{account ? 'Disconnect' : 'Connect Wallet'}</Text>
</Pressable>
)
}Always wrap connect() in try/catch — cancelling the wallet picker rejects the promise. Cancellation is a normal outcome, not an error state worth alarming the user about; see references/kit.md for how to tell cancellation apart from real failures.
Authorization is cached, so the app reconnects on restart without a new prompt.
Read chain data
Use client from the hook. There is no need to build a client of your own:
import type { Address } from '@solana/kit'
import { useQuery } from '@tanstack/react-query'
import { useMobileWallet } from '@wallet-ui/react-native-kit'
export function useGetBalance({ address }: { address: Address }) {
const { chain, client } = useMobileWallet()
return useQuery({
queryFn: () => client.rpc.getBalance(address).send(),
queryKey: ['get-balance', chain, address],
})
}Kit RPC calls are lazy: client.rpc.someMethod(...) builds a request and .send() runs it. Forget .send() and nothing errors — the data simply never arrives.
Balances come back as bigint lamports. Convert deliberately, and never with parseFloat:
export function lamportsToSol(lamports: bigint) {
return Number(lamports) / 1e9
}Send a transaction
Build instructions and hand them over. This covers most cases:
import { getAddMemoInstruction } from '@solana-program/memo'
import type { Instruction } from '@solana/kit'
const { sendTransactions } = useMobileWallet()
const instructions: Instruction[] = [getAddMemoInstruction({ memo: 'gm' })]
const signature = await sendTransactions(instructions)sendTransactions handles blockhash, minContextSlot, fee payer, and signature decoding. It returns as soon as the wallet has submitted, so confirm the signature before treating the transaction as done — references/kit.md has the helper.
Reach for the explicit pipe form only when you need fee-payer control, a specific blockhash lifetime, or a fee pre-check. Full worked example, with the balance-versus-fee assertion and signature decoding: references/kit.md.
Reference material
- references/kit.md — kit stack: clusters and config, reading chain data,
transactions, sign-in with Solana, message signing, error handling
- references/web3js.md — legacy
@solana/web3.jsstack, and a migration
sketch
- references/troubleshooting.md — connection and signing
failures with known causes
When something here is ambiguous, read the template. The patterns in this skill follow `expo-kit-minimal`, which is a complete working app and stays current in a way prose does not:
npx solana-mobile@latest create /tmp/reference-app --template expo-kit-minimal --skip-installRelated skills
solana-mobile— project setup, templates, emulators, development buildsintegration-privy— add Privy accounts and sessions on top of this wallet connectionseeker-genesis-token— verify Seeker device ownership after connectingseeker-domains— display.skrnames instead of raw addresses
Links
- Wallet UI: https://wallet-ui.dev
- MWA docs: https://docs.solanamobile.com/react-native/overview
- Solana Kit: https://www.solanakit.com

