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 |
| 参考references/web3js.md |
| 用户明确要求使用web3.js | 参考references/web3js.md,并说明使用Kit技术栈的优势 |
不要在Kit项目中引入web3.js,也不要在同一应用中混合使用两种技术栈。它们的提供者属性、钩子返回值和交易构造逻辑均不同,因此一种技术栈的代码在另一种中会静默失败。如果项目完全没有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将中的放在钱包提供者的上级——以下的钩子模式均基于查询和突变实现。
@tanstack/react-queryQueryClientProviderStep 3: use the hook
步骤3:使用钩子
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技术栈中,该钩子实际返回的内容如下:
| 返回值 | 类型 | 说明 |
|---|---|---|
| | 断开连接时为 |
| | 所有已授权的账户 |
| | 打开钱包选择器 |
| | 断开连接 |
| | Kit客户端——使用 |
| | 当前活跃的集群。请将其加入查询键中 |
| | 最简单的交易发送方式 |
| | 注意第二个参数 |
| | 签署交易但不广播 |
| | 签署消息 |
| | Solana登录;也可用于连接钱包 |
| 配置和授权存储 |
其中部分方法存在单数别名——、、——签名相同。模板中使用复数形式;两种形式均可,请遵循项目现有代码风格。
sendTransactionsignMessagesignTransactions容易出错的四点:
- 没有布尔值。需要自行推导:
connected。const connected = !!account - 的第二个参数是
signAndSendTransaction。仅传入交易调用该方法会失败。从minContextSlot获取插槽,或使用getLatestBlockhash,该方法会自动处理此问题。sendTransactions(instructions) - Kit钩子暴露的是,而非
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
}使用钩子返回的。无需自行构建客户端:
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