mcpField
I
integration-privy

integration-privy

github.com/solana-mobile开发812.6k 次安装1 周前更新
代码仓库

关于 integration-privy

面向 Solana 移动应用的集成技能,说明如何将 Privy 用户身份和服务端可验证会话与 Mobile Wallet Adapter 钱包连接结合。Privy 管理用户和会话,私钥及签名仍由钱包应用处理;该流程仅支持 Android 开发构建。

主要功能

  1. 01通过 SIWS 交换登录会话
  2. 02连接 Privy 用户与钱包
  3. 03获取服务端验证用访问令牌
  4. 04配置 Expo 原生依赖与 Provider
  5. 05同时退出 Privy 和钱包

适用场景

  1. 01为跨设备用户建立稳定身份
  2. 02为后端请求提供可验证会话
  3. 03在钱包连接后添加 Privy 登录

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/solana-mobile/solana-mobile-skills --skill integration-privy

安装后在 Claude Code 中输入/integration-privy确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
setup.md
siws.md
troubleshooting.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 IDPrivy 控制面板 — 第 1 步

第 1 步:创建 Privy app

请先完成此步骤。其中两个值是编译时环境变量,控制面板中的一个开关则决定登录是否能够正常工作。

  1. 在 https://dashboard.privy.io 登录,并在组织概览中点击 New app
  2. 为应用命名,选择 Mobile app,创建应用并保存 App ID
  3. 在 User management > Authentication 下的 External wallets 卡片中,启用 SVM (Solana) wallets
  4. 在 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 完成读取存储的令牌之前没有意义:

值类型说明
isReadyboolean在其值为 true 之前,其他所有状态都只是暂定的
userUser | null未认证时为 null,而不是 undefined
errorError | 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 会自行触发授权。

三个编码细节决定了这是否可行:

  1. 传入 account.address,它是 base58。 account.addressBase64 也存在,但它是 MWA 的线路格式,Privy 不接受这种格式。Privy 自己的示例花了三行将 base64 转为 base58,因为它直接驱动底层协议;@wallet-ui/react-native-kit 已经为你完成了转换。
  2. fromUint8Array 生成的是 base64,而不是 base58。 它是 js-base64 的重新导出。Privy 在这里需要 base64 字符串;base58 会导致验证失败。
  3. 不要截取字节。 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 过于繁琐的场景

链接

评论

支持 Markdown