├── hooks ├── useColorScheme.ts ├── useColorScheme.web.ts └── useThemeColor.ts ├── assets └── images │ ├── icon.png │ ├── favicon.png │ ├── splash.png │ └── adaptive-icon.png ├── expo-env.d.ts ├── babel.config.js ├── entrypoint.js ├── tsconfig.json ├── .gitignore ├── metro.config.js ├── components ├── login │ ├── PrivyUI.tsx │ ├── PasskeyLogin.tsx │ ├── OAuth.tsx │ └── SMS.tsx ├── LoginScreen.tsx ├── walletActions │ ├── EVMWalletActions.tsx │ └── SolanaWalletActions.tsx ├── userManagement │ ├── Wallets.tsx │ ├── UnlinkAccounts.tsx │ └── LinkAccounts.tsx └── UserScreen.tsx ├── app ├── _layout.tsx └── index.tsx ├── constants └── Colors.ts ├── app.json ├── README.md ├── package.json └── scripts └── reset-project.js /hooks/useColorScheme.ts: -------------------------------------------------------------------------------- 1 | export { useColorScheme } from 'react-native'; 2 | -------------------------------------------------------------------------------- /assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privy-io/expo-starter/HEAD/assets/images/icon.png -------------------------------------------------------------------------------- /assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privy-io/expo-starter/HEAD/assets/images/favicon.png -------------------------------------------------------------------------------- /assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privy-io/expo-starter/HEAD/assets/images/splash.png -------------------------------------------------------------------------------- /assets/images/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privy-io/expo-starter/HEAD/assets/images/adaptive-icon.png -------------------------------------------------------------------------------- /expo-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // NOTE: This file should not be edited and should be in your git ignore -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /entrypoint.js: -------------------------------------------------------------------------------- 1 | // entrypoint.js 2 | 3 | // Import required polyfills first 4 | // IMPORTANT: These polyfills must be installed in this order 5 | import "react-native-get-random-values"; 6 | import "@ethersproject/shims"; 7 | import { Buffer } from "buffer"; 8 | global.Buffer = Buffer; 9 | // Then import the expo router 10 | import "expo-router/entry"; 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true, 5 | // Allows us to use conditional/deep imports on published packages 6 | "moduleResolution": "Bundler", 7 | "paths": { 8 | "@/*": [ 9 | "./*" 10 | ] 11 | } 12 | }, 13 | "include": [ 14 | "**/*.ts", 15 | "**/*.tsx", 16 | ".expo/types/**/*.ts", 17 | "expo-env.d.ts" 18 | , "app/index.js" ] 19 | } 20 | -------------------------------------------------------------------------------- /hooks/useColorScheme.web.ts: -------------------------------------------------------------------------------- 1 | // NOTE: The default React Native styling doesn't support server rendering. 2 | // Server rendered styles should not change between the first render of the HTML 3 | // and the first render on the client. Typically, web developers will use CSS media queries 4 | // to render different styles on the client and server, these aren't directly supported in React Native 5 | // but can be achieved using a styling library like Nativewind. 6 | export function useColorScheme() { 7 | return 'light'; 8 | } 9 | -------------------------------------------------------------------------------- /hooks/useThemeColor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Learn more about light and dark modes: 3 | * https://docs.expo.dev/guides/color-schemes/ 4 | */ 5 | 6 | import { useColorScheme } from 'react-native'; 7 | 8 | import { Colors } from '@/constants/Colors'; 9 | 10 | export function useThemeColor( 11 | props: { light?: string; dark?: string }, 12 | colorName: keyof typeof Colors.light & keyof typeof Colors.dark 13 | ) { 14 | const theme = useColorScheme() ?? 'light'; 15 | const colorFromProps = props[theme]; 16 | 17 | if (colorFromProps) { 18 | return colorFromProps; 19 | } else { 20 | return Colors[theme][colorName]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | 11 | # Native 12 | *.orig.* 13 | *.jks 14 | *.p8 15 | *.p12 16 | *.key 17 | *.mobileprovision 18 | 19 | # Metro 20 | .metro-health-check* 21 | 22 | # debug 23 | npm-debug.* 24 | yarn-debug.* 25 | yarn-error.* 26 | 27 | # macOS 28 | .DS_Store 29 | *.pem 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # builds 35 | android/ 36 | ios/ 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | # @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb 41 | # The following patterns were generated by expo-cli 42 | 43 | expo-env.d.ts 44 | # @end expo-cli -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | // Learn more https://docs.expo.io/guides/customizing-metro 2 | const { getDefaultConfig } = require("expo/metro-config"); 3 | 4 | /** @type {import('expo/metro-config').MetroConfig} */ 5 | const config = getDefaultConfig(__dirname); 6 | 7 | const resolveRequestWithPackageExports = (context, moduleName, platform) => { 8 | // Package exports in `jose` are incorrect, so we need to force the browser version 9 | if (moduleName === "jose") { 10 | const ctx = { 11 | ...context, 12 | unstable_conditionNames: ["browser"], 13 | }; 14 | return ctx.resolveRequest(ctx, moduleName, platform); 15 | } 16 | 17 | return context.resolveRequest(context, moduleName, platform); 18 | }; 19 | 20 | config.resolver.resolveRequest = resolveRequestWithPackageExports; 21 | 22 | module.exports = config; 23 | -------------------------------------------------------------------------------- /components/login/PrivyUI.tsx: -------------------------------------------------------------------------------- 1 | import { useLogin } from "@privy-io/expo/ui"; 2 | import { Button, View, Text } from "react-native"; 3 | import { useState } from "react"; 4 | 5 | export default function PrivyUI() { 6 | const [error, setError] = useState(""); 7 | 8 | const { login } = useLogin(); 9 | return ( 10 | 11 | 45 | 46 | ))} 47 | 48 | {error && Error: {error}} 49 | 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "@privy-io/expo-starter", 4 | "slug": "expo-starter", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/images/icon.png", 8 | "scheme": "myapp", 9 | "userInterfaceStyle": "automatic", 10 | "splash": { 11 | "image": "./assets/images/splash.png", 12 | "resizeMode": "contain", 13 | "backgroundColor": "#ffffff" 14 | }, 15 | "ios": { 16 | "usesAppleSignIn": true, 17 | "supportsTablet": true, 18 | "bundleIdentifier": "dev.privy.example", 19 | "associatedDomains": ["webcredentials:"], 20 | "infoPlist": { 21 | "NSAppTransportSecurity": { 22 | "NSAllowsArbitraryLoads": true 23 | } 24 | } 25 | }, 26 | "android": { 27 | "adaptiveIcon": { 28 | "foregroundImage": "./assets/images/adaptive-icon.png", 29 | "backgroundColor": "#ffffff" 30 | }, 31 | "package": "dev.privy.example" 32 | }, 33 | "extra": { 34 | "privyAppId": "", 35 | "privyClientId": "", 36 | "passkeyAssociatedDomain": "https://" 37 | }, 38 | "plugins": [ 39 | "expo-router", 40 | "expo-secure-store", 41 | "expo-apple-authentication", 42 | [ 43 | "expo-build-properties", 44 | { 45 | "ios": { 46 | "deploymentTarget": "17.5" 47 | }, 48 | "android": { 49 | "compileSdkVersion": 35 50 | } 51 | } 52 | ], 53 | "expo-font" 54 | ], 55 | "experiments": { 56 | "typedRoutes": true 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Privy Expo Starter 2 | 3 | This demonstrates a minimal working installation of the privy sdk in a fresh expo app. We recommend reading the [documentation](https://docs.privy.io/guide/expo/dashboard) for a more detailed guide. 4 | 5 | ## Setup 6 | 7 | 1. Install dependencies 8 | 9 | ```sh 10 | yarn 11 | ``` 12 | 13 | 2. Configure an app client in your [Dashboard](https://dashboard.privy.io/apps?page=settings&setting=clients), and add your Privy app ID and app client ID in `app.json` 14 | 15 | ```json 16 | ... 17 | "extra": { 18 | "privyAppId": "", 19 | "privyClientId": "" 20 | } 21 | ... 22 | ``` 23 | 24 | If you are using Expo go, be sure to add `host.exp.Exponent` to Allowed app identifiers under app clients in your [Dashboard]('https://dashboard.privy.io/apps?page=settings&setting=clients') 25 | 26 | 3. Configure your application identifier in `app.json`. This should match the bundle identifier for your app in the app store. 27 | 28 | ```json 29 | ... 30 | "ios": { 31 | "bundleIdentifier": "com.example.myapp" 32 | }, 33 | "android": { 34 | "package": "com.example.myapp" 35 | } 36 | ... 37 | ``` 38 | 39 | 4. If you are making use of passkeys, ensure that you have an [associated website](https://docs.privy.io/guide/expo/setup/passkey#_3-update-native-app-settings) for your application. Once you have this your `app.json` should be updated as follows: 40 | 41 | ```json 42 | ... 43 | "associatedDomains": ["webcredentials:"], 44 | ... 45 | "extra": { 46 | ... 47 | "passkeyAssociatedDomain": "https://" 48 | }, 49 | ... 50 | ``` 51 | 52 | ## Run the app 53 | 54 | ```sh 55 | # expo go 56 | yarn run start 57 | 58 | # ios 59 | yarn run ios 60 | 61 | # android 62 | yarn run android 63 | ``` 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "expo-starter", 3 | "main": "entrypoint.js", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "start": "expo start --dev-client", 7 | "android": "expo run:android", 8 | "ios": "expo run:ios", 9 | "lint": "expo lint" 10 | }, 11 | "dependencies": { 12 | "@ethersproject/shims": "^5.8.0", 13 | "@expo-google-fonts/inter": "^0.3.0", 14 | "@expo/vector-icons": "^14.1.0", 15 | "@privy-io/expo": "^0.58.1", 16 | "@privy-io/expo-native-extensions": "^0.0.5", 17 | "@react-navigation/native": "^7.1.6", 18 | "@solana/web3.js": "^1.98.4", 19 | "buffer": "^6.0.3", 20 | "expo": "~53.0.9", 21 | "expo-apple-authentication": "~7.2.4", 22 | "expo-application": "~6.1.4", 23 | "expo-build-properties": "~0.14.6", 24 | "expo-clipboard": "~7.1.4", 25 | "expo-constants": "~17.1.6", 26 | "expo-crypto": "~14.1.4", 27 | "expo-font": "~13.3.1", 28 | "expo-linking": "~7.1.4", 29 | "expo-router": "~5.0.6", 30 | "expo-secure-store": "~14.2.3", 31 | "expo-splash-screen": "~0.30.8", 32 | "expo-status-bar": "~2.2.3", 33 | "expo-system-ui": "~5.0.7", 34 | "expo-web-browser": "~14.1.6", 35 | "react": "19.0.0", 36 | "react-dom": "19.0.0", 37 | "react-native": "0.79.2", 38 | "react-native-gesture-handler": "~2.24.0", 39 | "react-native-get-random-values": "^1.11.0", 40 | "react-native-passkeys": "^0.3.3", 41 | "react-native-qrcode-styled": "^0.3.3", 42 | "react-native-reanimated": "~3.17.4", 43 | "react-native-safe-area-context": "5.4.0", 44 | "react-native-screens": "~4.10.0", 45 | "react-native-svg": "15.11.2", 46 | "react-native-webview": "13.13.5", 47 | "viem": "^2.31.6" 48 | }, 49 | "resolutions": { 50 | "viem": "2.32.0" 51 | }, 52 | "devDependencies": { 53 | "@babel/core": "^7.25.2", 54 | "@types/react": "~19.0.10", 55 | "eslint": "^9.25.0", 56 | "eslint-config-expo": "~9.2.0", 57 | "typescript": "~5.8.3" 58 | }, 59 | "private": true 60 | } 61 | -------------------------------------------------------------------------------- /components/login/SMS.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { useLoginWithSMS } from "@privy-io/expo"; 3 | import { Button, Text, TextInput, View } from "react-native"; 4 | 5 | export default function SMSLogin() { 6 | const [phone, setPhone] = useState(""); 7 | const [code, setCode] = useState(""); 8 | const { sendCode, loginWithCode } = useLoginWithSMS(); 9 | 10 | return ( 11 | 21 | SMS Login 22 | 31 | 44 | 71 | 72 | ))} 73 | 74 | 98 | 99 | ))} 100 | 101 |