MCPInstall

integration-privy

Add Privy authentication to a Solana Expo Android app on top of Mobile Wallet Adapter, using Sign-In-With-Solana. Use when installing @privy-io/expo, mounting PrivyProvider, logging a user in with useLoginWithSiws, linking a wallet to an existing Privy account, reading the Privy access token from a backend, or debugging a Privy plus MWA setup.

Installs
11.7K
Last 24h
+79
Trending rank
#2316
Security
Pass

Install

Works with every agent the skills CLI supports.

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

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads integration-privy automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

Security audits

Independent scans of the skill's instructions and bundled files.

Gen Agent Trust Hub PassSAFE ·The skill provides legitimate instructions for integrating Privy authentication into Solana-based Expo Android applications. It correctly guides developers through SIWS (Sign-In-With-Solana) flows, dependency management, and security best practices, such as proper JWT validation and secret management.
Socket PassNo alerts
Snyk PassLOW ·Risk: LOW · No issues

SKILL.md

The instructions your agent reads when the skill is triggered.

Privy on Solana mobile

Privy owns the user: a durable account identifier and a JWT a backend can verify. Mobile Wallet Adapter owns the keys. Privy signs nothing in this setup — every signature still comes from the wallet app.

Sign-In-With-Solana joins the two. Privy generates a message, MWA signs it, Privy exchanges the signature for a session.

Reach for this when an app needs a stable user record across devices, a server-verifiable session, or login methods beyond a wallet. An app that only needs a connected address does not need Privy — use the solana-mobile-wallet skill alone.

Android only, and a development build only. MWA has no iOS support and does not run in Expo Go, which caps the whole integration.

Before you start

RequirementWhere it comes from
A working useMobileWallet()solana-mobile-wallet skill
A development build on Androidsolana-mobile skill
A Privy app ID and client IDThe Privy dashboard — step 1

Step 1: create the Privy app

Do this first. Two of these values are compile-time environment variables, and one dashboard toggle decides whether login works at all.

  1. Sign in at https://dashboard.privy.io and click New app on the organization overview
  2. Name it, select Mobile app, create it, and save the App ID
  3. Under User management > Authentication, in the External wallets card, enable SVM (Solana) wallets
  4. Under App settings > Clients, set the app identifier to the expo.android.package value from app.json, and save the Client ID

The SVM wallets toggle is the one that is easy to skip and expensive to debug — while it is off, login rejects every SIWS attempt even though the wallet signed correctly. The app identifier matters because Privy checks the calling app's package name against the client.

EXPO_PUBLIC_PRIVY_APP_ID=your-privy-app-id
EXPO_PUBLIC_PRIVY_CLIENT_ID=your-privy-client-id

Both are public client-side identifiers, so EXPO_PUBLIC_ is correct. The Privy app secret never belongs in a mobile app — anything prefixed EXPO_PUBLIC_ is readable in the shipped bundle. The secret is for server code only.

Step 2: install and configure

npx expo install @privy-io/expo @privy-io/expo-native-extensions

@privy-io/expo carries a long peer dependency list that shifts between releases — passkeys, secure store, web browser, crypto, viem. Install what the version you picked asks for rather than copying a list from anywhere, including from here.

Three pieces of native wiring are required, and the SDK fails in a different place for each:

  • Crypto and text-encoding polyfills, loaded from the entry module before anything else
  • expo-secure-store and expo-web-browser in app.json plugins
  • A Metro resolver override so jose resolves to its browser build

Full contents for each, and how to confirm they took: references/setup.md. Rebuild natively (npx expo run:android) after this step — a JS reload will not pick up the new native modules.

Step 3: mount the 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 and MobileWalletProvider do not depend on each other, so their relative nesting is free — but both must sit above every screen, and QueryClientProvider above both if the hooks below are queries and mutations.

Throwing on missing environment variables is deliberate. Undefined values reach Privy as a malformed app ID and surface much later as an opaque initialization error.

clientId is typed optional in PrivyProviderProps, which is misleading here: Privy's mobile documentation treats it as required, and the dashboard issues one per mobile client. Pass it.

Step 4: wait for isReady

usePrivy() returns state that is meaningless until the SDK finishes reading stored tokens:

ValueTypeNotes
isReadybooleanEverything else is provisional until this is true
userUser | nullnull when unauthenticated — not undefined
errorError | nullInitialization failures, typically storage access
logout() => Promise<void>No-op when nobody is signed in
getAccessToken() => Promise<string | null>Call per request; never cache the result
const { error, isReady, user } = usePrivy()

if (!isReady) return <Loading />
if (error) return <ErrorCard message={error.message} />

Rendering a signed-out state while isReady is false makes an already-authenticated user flash through a login screen on every cold start.

Step 5: sign in with SIWS

The whole integration is this one sequence: generate, sign, exchange.

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) })
    },
  })
}

Call it only once a wallet is connected — useMobileWallet().account must be defined, since signMessages triggers its own authorization otherwise.

Three encoding details decide whether this works:

  1. Pass account.address, which is base58. account.addressBase64 also exists; it is MWA's wire format and Privy will not accept it. Privy's own recipe spends three lines converting base64 to base58 because it drives the raw protocol — @wallet-ui/react-native-kit has already done that conversion for you.
  2. fromUint8Array produces base64, not base58. It is a re-export of js-base64. Privy wants the base64 string here; base58 fails verification.
  3. Do not slice the bytes. signMessages resolves to MWA's signed payload, not a bare 64-byte signature. Base64-encode it whole and hand it over — the template and Privy's recipe both do exactly this.

from.domain is an RFC 3986 authority: a bare host, no scheme and no path. from.uri is a full URI and is normally your app's deep link. Keep both stable — they are embedded in the signed message the user sees in their wallet.

Linking a wallet to an account that already exists, and verifying the session on a server: references/siws.md.

Step 6: sign out of both

const { logout } = usePrivy()
const { disconnect } = useMobileWallet()

await logout()
await disconnect()

Doing one without the other leaves the app in a half-signed-out state. disconnect() alone keeps a live Privy session with no wallet behind it; logout() alone leaves the wallet authorized and re-signs in silently on the next attempt.

Which side owns what

ConcernOwner
Private keys and signingThe wallet app, over MWA
Connected addressuseMobileWallet().account
User identity across devicesusePrivy().user
Server-verifiable sessionusePrivy().getAccessToken()
Sending transactionsuseMobileWallet().sendTransactions

There is no Privy signer in this setup. A user is signed in to Privy and connected over MWA as two independent facts, and the UI has to handle every combination — most usefully "connected but not signed in", which is where the sign-in button belongs.

Reference material

  • references/setup.md — polyfills, Metro config, app.json plugins, environment variables, and how to verify each one landed
  • references/siws.md — the SIWS exchange in depth, linking additional wallets, server-side token verification, and the raw-protocol variant without Wallet UI
  • references/troubleshooting.md — Privy-specific failures and their causes

The patterns here follow expo-kit-privy, a complete working app. Read it when this file is ambiguous:

npx solana-mobile@latest create /tmp/reference-app --template expo-kit-privy --skip-install

Related skills

  • solana-mobile-wallet — MWA connection, signing, and sending, which this builds on
  • solana-mobile — development builds, emulators, toolchain checks
  • seeker-genesis-token — SIWS verified server-side without Privy, when a JWT is overkill

Links

FAQ

What does the integration-privy skill do?

Add Privy authentication to a Solana Expo Android app on top of Mobile Wallet Adapter, using Sign-In-With-Solana. Use when installing @privy-io/expo, mounting PrivyProvider, logging a user in with useLoginWithSiws, linking a wallet to an existing Privy account, reading the Privy access token from a backend, or debugging a Privy plus MWA setup.

How do I install integration-privy?

Run npx skills add https://github.com/solana-mobile/solana-mobile-skills --skill integration-privy in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/integration-privy/ (personal) or .claude/skills/integration-privy/ (project).

Which AI agents can use integration-privy?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is integration-privy safe to install?

Security partners have reviewed it: Gen Agent Trust Hub passed; Socket passed; Snyk passed. As with any skill, read the SKILL.md and bundled scripts before installing.

Who maintains integration-privy?

It is published by solana-mobile in solana-mobile/solana-mobile-skills. It has 11,714 recorded installs.

More from solana-mobile/solana-mobile-skills

View all →

solana-mobile

solana-mobile/solana-mobile-skills

Scaffold, configure, and troubleshoot Solana Mobile apps for Android using the solana-mobile CLI, Expo, and React Native. Use when creating a new Solana mobile app, picking a template, adding Solana to an existing Expo app, checking the Android toolchain, managing Android emulators or connected devices, installing a wallet APK for testing, running a local validator the device can reach, or producing a development build.

Mobile11.7K installs

solana-mobile-wallet

solana-mobile/solana-mobile-skills

Connect Solana wallets and sign or send transactions in React Native Expo apps using Mobile Wallet Adapter and Wallet UI. Use when adding a connect wallet button, showing a connected address, disconnecting, signing messages, sign-in with Solana, transferring SOL, or sending any transaction from a Solana mobile app.

Mobile11.7K installs

seeker-genesis-token

solana-mobile/solana-mobile-skills

Verify Seeker device ownership by checking for the Seeker Genesis Token (SGT) with Sign-in-with-Solana and server-side token verification. Use when gating content or rewards to Seeker owners, verifying a user holds an SGT, adding anti-Sybil checks to a Solana mobile app, or implementing one-claim-per-device logic.

Mobile11.7K installs

seeker-domains

solana-mobile/solana-mobile-skills

Resolve and display .skr domain names in Solana mobile apps, in both directions between names and wallet addresses. Use when showing .skr names instead of wallet addresses in profiles, friend lists, or transaction history, resolving a .skr domain to an address, reverse-looking-up an address to a domain, or validating .skr input.

Mobile11.7K installs

seeker-connect

solana-mobile/solana-mobile-skills

Connect a web, React Native, or Kotlin dapp to the Seeker device's built-in wallet with Seeker Connect. Use when adding wallet connection, sign-in with Solana, message signing, or transaction signing to an Expo or React Native app on a Seeker phone, a Kotlin app on it, or a website that runs in its browser, registering the "Seeker Connect" Wallet Standard wallet, setting the Nostr relay for relayDomain, using the seeker-connect-button element or the SeekerConnectButton native view, installing @solana-mobile/seeker-connect-react-native or com.solanamobile:seeker-connect, or debugging SeekerConnectError codes like association-failed.

Mobile7.1K installs

solana-mobile-publishing

solana-mobile/solana-mobile-skills

Build, sign, and publish an Android APK to the Solana dApp Store with the dapp-store CLI. Use when shipping a Solana mobile app to the dApp Store for the first time, releasing an update, signing a release APK, setting up a publisher account and App NFT, or debugging a release build that works fine in debug.

Mobile6.8K installs

Popular mobile skills

View category →

design-mobile-apps

designed-by-ai/skills

Design mobile app screens with Sleek, edit Sleek projects, and implement their designs in React Native or HTML.

Mobile1.1M installs

ios-design

uizze.sh

Design and review iOS UI/UX for iPhone apps in SwiftUI, UIKit, or React Native. Use for mobile app design, screen redesigns, navigation flows, typography, forms, keyboard and safe-area layout, and accessibility checks. Apply Apple Human Interface Guidelines and consult real product references through the optional Uizze connection.

Mobile416.5K installs

vercel-react-native-skills

vercel-labs/agent-skills

React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.

OfficialMobile256.7K installs

animate-expo

emilkowalski/skills

Build animations in React Native and Expo, making the decisions in the order that determines whether they feel right — should it animate, which thread it runs on, which properties, spring or timing, how the gesture hands off, how it degrades. Writes the implementation with Reanimated, Gesture Handler, Expo Router and expo-haptics. Use when animating anything in an Expo app, adding gestures, sheets, screen transitions, press feedback or haptics, or fixing motion that stutters on device. For web animation use animate.

Mobile140.1K installs

xcode-project-setup

firebase/agent-skills

Safely modifies Xcode projects (.pbxproj) to add Swift Packages and link files. Use this skill whenever an iOS project needs dependencies installed (e.g. Firebase, Alamofire).

OfficialMobile131.2K installs

firebase-remote-config-basics

firebase/agent-skills

Manages Firebase Remote Config templates, feature flags, loading strategies, and SDKs (Android, iOS). Use when downloading/deploying remoteconfig JSON templates, managing version history/feature flags, setting in-app defaults, fetchAndActivate(), real-time listeners, or SDK setup. Don't use for Firebase Hosting, Auth, Firestore, Data Connect, Crashlytics, or App Hosting.

OfficialMobile121.7K installs