solana-mobile-wallet
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSolana wallets on mobile
移动端Solana钱包
Wallet connection and transaction signing through Mobile Wallet Adapter (MWA), wrapped by
(or on the legacy stack).
@wallet-ui/react-native-kit@wallet-ui/react-native-web3jsMWA 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 skill.
solana-mobile通过Mobile Wallet Adapter(MWA)实现钱包连接与交易签署,由封装(旧技术栈使用)。
@wallet-ui/react-native-kit@wallet-ui/react-native-web3jsMWA在Android上需要开发构建版本,Expo Go无法运行。 如果项目还没有开发构建版本或尚未创建,请从技能开始。
solana-mobileStep 1: pick the stack — do this before writing any code
步骤1:选择技术栈——编写代码前完成此步骤
Write kit code. with is the stack to reach for,
and everything in this file describes it.
@solana/kit@wallet-ui/react-native-kitThere is exactly one reason to write instead: the project already runs on it.
Check first.
@solana/web3.jspackage.json | Do this |
|---|---|
| Kit. This file, plus references/kit.md |
| 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.
编写Kit代码。 搭配是首选技术栈,本文档所有内容均围绕该栈展开。
@solana/kit@wallet-ui/react-native-kit只有一种情况需要使用:项目已基于该库运行。请先查看。
@solana/web3.jspackage.json | 操作建议 |
|---|---|
包含 | 使用Kit。参考本文档及references/kit.md |
应用的Solana客户端为 | 参考references/web3js.md |
| 用户明确要求使用web3.js | 参考references/web3js.md,并说明使用Kit的优势 |
不要在Kit项目中引入web3.js,也不要在同一应用中混合使用两者。它们的提供者属性、Hook返回值和交易构造逻辑均不同,因此基于其中一个栈编写的代码在另一个栈中会静默失败。如果项目完全没有Solana客户端,属于全新构建——请使用Kit。
在现有web3.js应用中添加新钱包功能,不是中途迁移的理由。匹配现有技术栈,若值得迁移可在后续提及。
Step 2: confirm the provider is mounted
步骤2:确认提供者已挂载
useMobileWalletMobileWalletProviderBuild the cluster with the helpers rather than by hand — a also
needs a , which the helpers fill in:
createSolana*SolanaClusterlabeltsx
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>createSolanaDevnetcreateSolanaTestnetcreateSolanaLocalnetcreateSolanaMainneturlThe provider props are , , and optional , , .
There is no prop and no prop — passing those does nothing.
clusteridentitycachecreateClientchildrenchainendpointEvery field is optional. alone is enough to get started; add as a
real deep link for anything shipping, since wallets display it during authorization and a
placeholder can read as a phishing attempt.
AppIdentitynameuriPut from above the wallet provider — the hook
patterns below are queries and mutations.
QueryClientProvider@tanstack/react-query若没有在上方挂载,会返回空状态。请在根布局或应用提供者模块中查找它。
MobileWalletProvideruseMobileWallet使用辅助函数构建集群,而非手动创建——还需要一个,辅助函数会自动填充该字段:
createSolana*SolanaClusterlabeltsx
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>createSolanaDevnetcreateSolanaTestnetcreateSolanaLocalnetcreateSolanaMainneturl提供者的属性包括、,以及可选的、、。没有属性和属性——传入这些属性不会产生任何效果。
clusteridentitycachecreateClientchildrenchainendpointAppIdentitynameuri将的放在钱包提供者上方——下面的Hook模式属于查询和突变操作。
@tanstack/react-queryQueryClientProviderStep 3: use the hook
步骤3:使用Hook
tsx
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 |
|---|---|---|
| | |
| | All authorized accounts |
| | Opens the wallet picker |
| | |
| | Kit client — use |
| | The active cluster. Put it in query keys |
| | Simplest send path |
| | Note the second argument |
| | Sign without broadcasting |
| | |
| | Sign-in with Solana; can also connect |
| Config and authorization store |
Singular aliases exist for several of these — , ,
— with the same signatures. The templates use the plural forms; either
works, so follow whatever the project already uses.
sendTransactionsignMessagesignTransactionsFour things that trip people up:
- There is no boolean. Derive it:
connected.const connected = !!account - takes
signAndSendTransactionas a second argument. Calling it with only a transaction fails. Get the slot fromminContextSlot, or usegetLatestBlockhash, which handles this for you.sendTransactions(instructions) - The kit hook exposes , not
client.connectiononly exists on the web3.js stack.connection - Include 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.
chain
account.addressAddressaccount.labeltsx
import { useMobileWallet } from '@wallet-ui/react-native-kit'
const { account, connect, disconnect, client } = useMobileWallet()在Kit栈中,该Hook实际返回的内容:
| 返回值 | 类型 | 说明 |
|---|---|---|
| | 断开连接时为 |
| | 所有已授权账户 |
| | 打开钱包选择器 |
| | 断开连接 |
| | Kit客户端——使用 |
| | 当前活跃集群。请将其加入查询键 |
| | 最简单的交易发送方式 |
| | 注意第二个参数 |
| | 签署但不广播交易 |
| | 签署消息 |
| | Solana登录;也可用于连接钱包 |
| 配置和授权存储 |
其中部分返回值存在单数别名——、、——签名相同。模板使用复数形式;两种形式均可,请遵循项目现有规范。
sendTransactionsignMessagesignTransactions容易出错的四点:
- 没有布尔值。需自行推导:
connected。const connected = !!account - 的第二个参数是
signAndSendTransaction。仅传入交易调用会失败。从minContextSlot获取插槽,或使用getLatestBlockhash,该方法会自动处理此问题。sendTransactions(instructions) - Kit Hook暴露的是,而非
client。connection仅存在于web3.js栈中。connection - 所有包含链数据的React Query键中都要包含。否则切换集群时会显示之前网络的缓存余额,看起来像是钱包BUG。
chain
account.addressAddressaccount.labelConnect and disconnect
连接与断开连接
tsx
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 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.
connect()Authorization is cached, so the app reconnects on restart without a new prompt.
tsx
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) {
// 用户关闭钱包选择器会进入此处。不要将其视为崩溃。
console.error(error)
}
}
return (
<Pressable onPress={onPress}>
<Text>{account ? 'Disconnect' : 'Connect Wallet'}</Text>
</Pressable>
)
}始终将包裹在try/catch中——取消钱包选择器会拒绝Promise。取消是正常结果,无需提醒用户;如需区分取消与真实失败,请参考references/kit.md。
connect()授权信息会被缓存,因此应用重启时无需重新提示即可重新连接。
Read chain data
读取链上数据
Use from the hook. There is no need to build a client of your own:
clienttsx
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: builds a request and runs it.
Forget and nothing errors — the data simply never arrives.
client.rpc.someMethod(...).send().send()Balances come back as lamports. Convert deliberately, and never with :
bigintparseFloatts
export function lamportsToSol(lamports: bigint) {
return Number(lamports) / 1e9
}使用Hook返回的,无需自行构建客户端:
clienttsx
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调用是惰性的:构建请求,执行请求。忘记不会报错——只是永远不会返回数据。
client.rpc.someMethod(...).send().send()余额以类型的lamports返回。请谨慎转换,切勿使用:
bigintparseFloatts
export function lamportsToSol(lamports: bigint) {
return Number(lamports) / 1e9
}Send a transaction
发送交易
Build instructions and hand them over. This covers most cases:
tsx
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)sendTransactionsminContextSlotReach for the explicit 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.
pipe构建指令并传入即可,这适用于大多数场景:
tsx
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)sendTransactionsminContextSlot仅当需要控制付费方、特定区块哈希有效期或预检查手续费时,才使用显式的形式。完整示例(包含余额与手续费断言及签名解码):references/kit.md。
pipeReference 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 stack, and a migration sketch
@solana/web3.js - references/troubleshooting.md — connection and signing failures with known causes
When something here is ambiguous, read the template. The patterns in this skill follow
,
which is a complete working app and stays current in a way prose does not:
expo-kit-minimalbash
npx solana-mobile@latest create /tmp/reference-app --template expo-kit-minimal --skip-install- references/kit.md — Kit栈:集群与配置、读取链上数据、交易、Solana登录、消息签署、错误处理
- references/web3js.md — 旧版栈,及迁移概述
@solana/web3.js - references/troubleshooting.md — 已知原因导致的连接与签署失败问题
若本文档内容存在歧义,请参考模板。本技能中的模式遵循,这是一个完整的可运行应用,且会持续更新,比文字文档更及时:
expo-kit-minimalbash
npx solana-mobile@latest create /tmp/reference-app --template expo-kit-minimal --skip-installRelated skills
相关技能
- — project setup, templates, emulators, development builds
solana-mobile - — add Privy accounts and sessions on top of this wallet connection
integration-privy - — verify Seeker device ownership after connecting
seeker-genesis-token - — display
seeker-domainsnames instead of raw addresses.skr
- — 项目搭建、模板、模拟器、开发构建版本
solana-mobile - — 在此钱包连接基础上添加Privy账户与会话
integration-privy - — 连接后验证Seeker设备所有权
seeker-genesis-token - — 显示
seeker-domains域名而非原始地址.skr
Links
链接
- Wallet UI: https://wallet-ui.dev
- MWA docs: https://docs.solanamobile.com/react-native/overview
- Solana Kit: https://www.solanakit.com
- Wallet UI: https://wallet-ui.dev
- MWA文档: https://docs.solanamobile.com/react-native/overview
- Solana Kit: https://www.solanakit.com