关于 integration-privy
面向 Solana 移动应用的集成技能,说明如何将 Privy 用户身份和服务端可验证会话与 Mobile Wallet Adapter 钱包连接结合。Privy 管理用户和会话,私钥及签名仍由钱包应用处理;该流程仅支持 Android 开发构建。
主要功能
- 01通过 SIWS 交换登录会话
- 02连接 Privy 用户与钱包
- 03获取服务端验证用访问令牌
- 04配置 Expo 原生依赖与 Provider
- 05同时退出 Privy 和钱包
适用场景
- 01为跨设备用户建立稳定身份
- 02为后端请求提供可验证会话
- 03在钱包连接后添加 Privy 登录
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ solana-mobile/ solana-mobile-skills --skill integration-privy安装后在 Claude Code 中输入/integration-privy确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/setup.mdsiws.mdtroubleshooting.md详细介绍
Privy 在 Solana 移动端
Privy 负责用户:一个持久的账户标识符,以及后端可以验证的 JWT。Mobile Wallet Adapter 负责密钥。在此配置中,Privy 不会进行任何签名——所有签名仍由钱包应用生成。
Sign-In-With-Solana 将两者连接起来。Privy 生成消息,MWA 对其签名,Privy 再用该签名换取会话。
如果应用需要跨设备稳定的用户记录、可由服务器验证的会话,或钱包以外的登录方式,可以使用此方案。只需要连接地址的应用不需要 Privy——单独使用 solana-mobile-wallet skill 即可。
仅支持 Android,且仅支持开发构建。 MWA 不支持 iOS,也无法在 Expo Go 中运行,这限制了整个集成方案。
开始之前
| 要求 | 来源 |
|---|---|
可正常运行的 useMobileWallet() | solana-mobile-wallet skill |
| Android 开发构建 | solana-mobile skill |
| Privy app ID 和 client ID | Privy 控制面板 — 第 1 步 |
第 1 步:创建 Privy app
请先完成此步骤。其中两个值是编译时环境变量,控制面板中的一个开关则决定登录是否能够正常工作。
- 在 https://dashboard.privy.io 登录,并在组织概览中点击 New app
- 为应用命名,选择 Mobile app,创建应用并保存 App ID
- 在 User management > Authentication 下的 External wallets 卡片中,启用 SVM (Solana) wallets
- 在 App settings > Clients 下,将应用标识符设为
app.json中的expo.android.package值,然后保存 Client ID
SVM wallets 开关很容易被漏掉,而且之后很难排查——关闭时,即使钱包签名正确,login 也会拒绝所有 SIWS 尝试。应用标识符也很重要,因为 Privy 会将调用应用的包名与客户端进行比对。
EXPO_PUBLIC_PRIVY_APP_ID=your-privy-app-id
EXPO_PUBLIC_PRIVY_CLIENT_ID=your-privy-client-id
这两个值都是公开的客户端标识符,因此使用 EXPO_PUBLIC_ 是正确的。Privy app secret 绝不能放在移动应用中——任何带有 EXPO_PUBLIC_ 前缀的内容都能从已发布的程序包中读取。此 secret 仅供服务器代码使用。
第 2 步:安装并配置
npx expo install @privy-io/expo @privy-io/expo-native-extensions
@privy-io/expo 有一长串 peer 依赖项,且会随版本变化——包括 passkeys、secure store、web browser、crypto、viem。请根据你选择的版本所要求的内容进行安装,不要照搬其他地方的依赖列表,包括这里的列表。
需要完成三项原生配置,而 SDK 遇到每种配置缺失时会在不同位置报错:
- 在入口模块中加载 crypto 和 text-encoding polyfill,并确保在其他任何内容之前加载
- 在
app.json的 plugins 中配置expo-secure-store和expo-web-browser - 覆盖 Metro resolver,让
jose解析到其浏览器构建版本
每项配置的完整内容及验证方法请见:references/setup.md。
完成此步骤后,请原生重新构建(npx expo run:android)——重新加载 JS 不会载入新的原生模块。
第 3 步:挂载 providers
import { PrivyProvider } from '@privy-io/expo'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { type AppIdentity, createSolanaDevnet, MobileWalletProvider } from '@wallet-ui/react-native-kit'
import type { ReactNode } from 'react'
const cluster = createSolanaDevnet()
const identity: AppIdentity = { name: 'My App', uri: 'myapp://myapp' }
const privyAppId = process.env.EXPO_PUBLIC_PRIVY_APP_ID
const privyClientId = process.env.EXPO_PUBLIC_PRIVY_CLIENT_ID
const queryClient = new QueryClient()
export function AppProviders({ children }: { children: ReactNode }) {
if (!privyAppId || !privyClientId) {
throw new Error('Missing Privy environment variables')
}
return (
<QueryClientProvider client={queryClient}>
<PrivyProvider appId={privyAppId} clientId={privyClientId}>
<MobileWalletProvider cluster={cluster} identity={identity}>
{children}
</MobileWalletProvider>
</PrivyProvider>
</QueryClientProvider>
)
}
PrivyProvider 和 MobileWalletProvider 互不依赖,因此两者的嵌套顺序不限——但两者都必须位于所有屏幕上层;如果下方的 hooks 用于查询和变更,还需要将 QueryClientProvider 放在两者上层。
缺少环境变量时抛出错误是有意为之。未定义的值会作为格式错误的 app ID 传给 Privy,直到很久之后才会表现为不明确的初始化错误。
在类型定义中,clientId 对 PrivyProviderProps 来说是可选的,但这里容易造成误解:Privy 的移动端文档将其视为必填项,而且控制面板会为每个移动客户端单独生成一个。请传入此值。
步骤 4:等待 isReady
usePrivy() 返回的状态在 SDK 完成读取存储的令牌之前没有意义:
| 值 | 类型 | 说明 |
|---|---|---|
isReady | boolean | 在其值为 true 之前,其他所有状态都只是暂定的 |
user | User | null | 未认证时为 null,而不是 undefined |
error | Error | null | 初始化失败,通常与存储访问有关 |
logout | () => Promise<void> | 无人登录时不执行任何操作 |
getAccessToken | () => Promise<string | null> | 每次请求时调用;切勿缓存结果 |
const { error, isReady, user } = usePrivy()
if (!isReady) return <Loading />
if (error) return <ErrorCard message={error.message} />
如果在 isReady 为 false 时渲染已登出的状态,已认证用户每次冷启动时都会短暂看到登录屏幕。
步骤 5:使用 SIWS 登录
整个集成只需按这个顺序执行:生成、签名、交换。
import { useLoginWithSiws } from '@privy-io/expo'
import type { Address } from '@solana/kit'
import { useMutation } from '@tanstack/react-query'
import { fromUint8Array, useMobileWallet } from '@wallet-ui/react-native-kit'
const siwsDomain = 'myapp.com'
const siwsUri = 'myapp://privy-login'
export function usePrivySignInMutation(address: Address) {
const { generateMessage, login } = useLoginWithSiws()
const { signMessages } = useMobileWallet()
return useMutation({
mutationFn: async () => {
const { message } = await generateMessage({
from: { domain: siwsDomain, uri: siwsUri },
wallet: { address: address.toString() },
})
const signedPayload = await signMessages(new TextEncoder().encode(message))
await login({ message, signature: fromUint8Array(signedPayload) })
},
})
}
只有在钱包已连接后才能调用它——useMobileWallet().account 必须已定义,否则 signMessages 会自行触发授权。
三个编码细节决定了这是否可行:
- 传入
account.address,它是 base58。account.addressBase64也存在,但它是 MWA 的线路格式,Privy 不接受这种格式。Privy 自己的示例花了三行将 base64 转为 base58,因为它直接驱动底层协议;@wallet-ui/react-native-kit已经为你完成了转换。 fromUint8Array生成的是 base64,而不是 base58。 它是js-base64的重新导出。Privy 在这里需要 base64 字符串;base58 会导致验证失败。- 不要截取字节。
signMessages返回的是 MWA 的已签名载荷,而不是单独的 64 字节签名。将其整体编码为 base64 并传入——模板和 Privy 的示例都正是这样做的。
from.domain 是 RFC 3986 authority:纯主机名,不带 scheme 和路径。from.uri 是完整 URI,通常是应用的深层链接。两者都应保持稳定——它们会嵌入用户在钱包中看到的签名消息。
将钱包关联到现有账户,以及在服务端验证会话: references/siws.md。
步骤 6:同时退出两者
const { logout } = usePrivy()
const { disconnect } = useMobileWallet()
await logout()
await disconnect()
只执行其中一个会让应用处于半退出状态。单独调用 disconnect() 会保留一个没有钱包的有效 Privy 会话;单独调用 logout() 则会让钱包保持已授权状态,并在下次尝试时静默重新登录。
哪一方负责什么
| 事项 | 负责方 |
|---|---|
| 私钥和签名 | 通过 MWA 使用的钱包应用 |
| 已连接地址 | useMobileWallet().account |
| 跨设备的用户身份 | usePrivy().user |
| 可在服务端验证的会话 | usePrivy().getAccessToken() |
| 发送交易 | useMobileWallet().sendTransactions |
此配置中没有 Privy 签名器。用户登录 Privy 和通过 MWA 连接是两件相互独立的事,UI 必须处理所有组合状态——其中最实用的是“已连接但未登录”,登录按钮就应放在这种状态下。
参考资料
- references/setup.md — polyfill、Metro 配置、
app.json插件、环境变量,以及如何验证每项是否生效 - references/siws.md — 深入介绍 SIWS 交换、关联其他钱包、服务端令牌验证,以及不使用 Wallet UI 的底层协议变体
- references/troubleshooting.md — Privy 特有的问题及其成因
这里的模式遵循
expo-kit-privy,
这是一个完整的可运行应用。如果本文内容有歧义,请查看该项目:
npx solana-mobile@latest create /tmp/reference-app --template expo-kit-privy --skip-install
相关技能
solana-mobile-wallet— MWA 连接、签名和发送,本项目以此为基础solana-mobile— 开发构建、模拟器和工具链检查seeker-genesis-token— 不使用 Privy 在服务端验证 SIWS,适用于 JWT 过于繁琐的场景
链接
- Privy Solana MWA 配方:https://docs.privy.io/recipes/solana/adding-solana-mwa
- Privy Expo SIWS 登录:https://docs.privy.io/guide/expo/authentication/siws
- Privy 控制面板:https://dashboard.privy.io