Skip to main content

Expo Quickstart

The Privy Expo SDK is a react-native library client for Privy that allows you to add secure authentication, non-custodial embedded wallets, and powerful user management into your application.

note

The Privy Expo SDK is under continued development. We are rapidly shipping features and improvements to our developer interface.

Feature Support Matrix
FeatureSupported?
Sign In w/ Emailโœ…
Sign In w/ SMSโœ…
Sign In w/ Social Providersโœ…
Sign In w/ Wallets (SIWE)๐Ÿ•’
Embedded Wallet Creationโœ…
Embedded Wallet Creation with Passwordsโœ…
Embedded Wallet Recoveryโœ…
Embedded Wallet Signatures & Transactions**โœ…
Setting password on existing embedded wallet๐Ÿ•’

** The Privy Expo SDK ships an EIP-1193Provider interface to request signatures/transactions from the embedded wallet.

Starter Projectsโ€‹

For examples of the Privy Expo SDK in action, see our starter repos on Github.

Project typerepo
Expoprivy-io/expo-starter
Bare expoprivy-io/expo-bare-starter

Setupโ€‹

Installationโ€‹

Install the Privy Expo SDK along with its peer dependencies using npm:

npx expo install expo-application expo-constants expo-linking expo-secure-store react-native-webview @privy-io/expo

Install polyfills which account for APIs required by the Privy Expo SDK that are not available in the React Native environment.

npm i --save react-native-get-random-values @ethersproject/shims

These must be installed and imported as early as possible in your application.

import 'react-native-get-random-values'
import '@ethersproject/shims'

// Your root component

Install babel plugins (required for upstream dependencies)

npm i --save-dev @babel/plugin-transform-class-properties @babel/plugin-transform-private-methods @babel/plugin-transform-flow-strip-types

Add to your babel config (babel.config.js or .babelrc)

{
// babel config...
"plugins": [
["@babel/plugin-transform-class-properties", { "loose": true }],
["@babel/plugin-transform-private-methods", { "loose": true }],
"@babel/plugin-transform-flow-strip-types"
]
}

Initializationโ€‹

Initialize the Privy client with an object containing the following fields:

  • appId: your Privy App ID from the Privy console
// Import required polyfils first
import 'react-native-get-random-values';
import '@ethersproject/shims';

import React from 'react';

// Import the PrivyProvider
import {PrivyProvider} from '@privy-io/expo';

// Your components
import {HomeScreen} from './Homescreen';

export default function App() {
return (
// Render the PrivyProvider with your app ID
<PrivyProvider appId={'insert-your-privy-app-id'}>
<HomeScreen />
</PrivyProvider>
);
}

That's it! You can now use the Privy Expo SDK to securely authenticate and provision embedded wallets for your users. ๐ŸŽ‰

App Configurationโ€‹

Allowed application IDsโ€‹

A Privy application can be configured to restrict which mobile apps can use it's client-side App ID (similar to allowed domains for web apps). For this, we'll use the unique value that identifies your app in the Apple App Store or Google Play Store.

  1. Copy your ID from app.config.js or app.json.

    {
    // other config
    "ios": {
    "bundleIdentifier": "com.myorg.app" // <โ”
    }, // โ”‚
    // โ”œโ”€ your app ID
    "android": { // โ”‚
    "package": "com.myorg.app" // <โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
    }
    }
  2. Add your ID in the privy console in the Configuration section.

    allowed app ids field in privy console

  • Multiple app IDs can be added
  • An empty list will mean all requests from mobile apps are denied.
  • For development only, if you are using expo go, enter host.exp.Exponent to allow requests. (You can also add a wildcard character (*) to accept all requests, but this is not recommended)

Allowed url schemesโ€‹

When a url is used to launch your app at the end of an OAuth login flow, it will contain a custom scheme like myapp://oauth-callback. This scheme is specified in your app config, and should be added in the Privy Console for increased security.

  1. Copy your app's url scheme from app.json or app.config.ts.

  2. Add your url scheme in the Privy Console.

    allowed app ids field in privy console

  • Multiple schemes can be added
  • An empty list will mean all url schemes (other than https and http) will be rejected.
  • For development only if you are using Expo Go, enter exp which is the custom scheme for the Expo Go app. (You can also add a wildcard character (*) to accept all schemes, but this is not recommended)

Authenticationโ€‹

The Privy Expo SDK allows you to log users in either via:

  • one-time passcode (OTP) sent to their email address or phone number.
  • a variety of OAuth providers (apple, google, etc..)

Follow the steps below for your desired login flow.

  1. Send an OTP by passing the user's email address to the sendCode method returned from useLoginWithEmail

    import {useLoginWithEmail} from '@privy-io/expo';

    export function LoginScreen() {
    const [email, setEmail] = useState('');

    const {sendCode} = useLoginWithEmail();

    return (
    <View style={styles.container}>
    <Text>Login</Text>

    <TextInput value={email} onChangeText={setEmail} placeholder="Email" inputMode="email" />

    <Button onPress={() => sendCode({ email })}>
    Send Code
    </Button>
    </View>
    );
    }
  2. The user will receive an email with a 6-digit OTP. Prompt for this OTP within your application, then authenticate by passing it to loginWithCode method returned from useLoginWithEmail:

    import {useLoginWithEmail} from '@privy-io/expo';

    export function LoginScreen() {
    const [code, setCode] = useState('');

    const {loginWithCode} = useLoginWithEmail();

    return (
    <View style={styles.container}>
    <Text>Login</Text>

    <TextInput value={code} onChangeText={setCode} placeholder="Code" inputMode="numeric" />

    <Button onPress={() => loginWithCode({ code })}>
    Login
    </Button>
    </View>
    );
    }
  3. If the OTP is correct, loginWithCode will return an object representing the user session, and the user object from usePrivy will be updated, triggering re-renders wherever it is used ๐ŸŽ‰.

tip

After a user is logged in, the same workflow may be followed to link an email to their account using the useLinkWithEmail hook instead.

User Dataโ€‹

To get an object representation of your user's identity data, including their Privy user ID, linked accounts, and embedded wallets, use the Privy client's usePrivy hook:

const {user} = usePrivy();
tip

You may also use this method to determine whether a user is authenticated in your app. If the user returned from usePrivy is truthy, the user is authenticated; otherwise, they are unauthenticated.

Persistenceโ€‹

By default, the Privy Expo SDK makes use of expo-secure-store package to persist sessions after your app is closed.

If you'd rather persist sessions in a different way, you can easily build an adapter and provide it as an optional prop to the <PrivyProvider />

import StorageProvider from 'my-storage-provider'
import type {Storage} from '@privy-io/js-sdk-core'
import {PrivyProvider} from '@privy-io/expo'

import AppContent from './AppContent'

const storage: Storage = {
get: (key) => StorageProvider.getItemAsync(key),
put: (key, val) => StorageProvider.setItemAsync(key, val),
del: (key) => StorageProvider.deleteItemAsync(key),
getKeys: () => StorageProvider.getAllKeys(),
};

export function App() {
return (
<PrivyProvider appId={'my-app-id'} storage={storage}>
<AppContent />
</PrivyProvider>
)
}

Embedded Walletsโ€‹

Waiting for secure contextโ€‹

Privy uses a secure context for embedded wallet communication that needs to be loaded before any read or write operations can be completed.

This is easy to ensure in your application by waiting for the value of isReady which is returned by the usePrivy hook to be true.

import {usePrivy} from '@privy-io/expo'

export function App() {
const {isReady} = usePrivy()

if (!isReady) return <Spinner />

return <AppContent />
}

Creating the walletโ€‹

To create an embedded wallet for your user, use the Privy Expo SDK's useEmbeddedWallet hook.

As an optional parameter to create, you may include a password set by the user as a string. Specifically:

  • If you do include a password, the recovery share of the embedded wallet will be secured by the user's password. This is called password-based recovery.
  • If you do not include a password, the recovery share will be secured by Privy. This is called automatic recovery.
import {useEmbeddedWallet, isNotCreated} from '@privy-io/expo';

const CreateWalletButton = () => {
const wallet = useEmbeddedWallet();

if (isNotCreated(wallet)) {
return <Button onPress={() => wallet.create()}>Create Wallet</Button>;
}

return null;
};
How can my app know if a user already has an embedded wallet?

To determine if the current user already has an embedded wallet, you can either:

  • obtain an object representation of the user from the Privy SDK's usePrivy hook, and check if it includes a wallet with a walletClientType of 'privy', or
  • use the Privy client's useEmbeddedWallet hook, like below:
import {useEmbeddedWallet, isConnected, needsRecovery} from '@privy-io/expo';

const Component = () => {
const wallet = useEmbeddedWallet();
const [password, setPassword] = useState('');

if (isConnected(wallet)) {
/* The user's embedded wallet exists and is ready to be used! */
return <View>Wallet Exists</View>;
}

if (needsRecovery(wallet)) {
/*
The user's embedded wallet exists but has never been loaded on this device.
They will need to go through the password recovery flow to use it.
*/
return (
<View>
The user's embedded wallet exists but has never been loaded on this device. They will need
to go through the password recovery flow to use it.
</View>
);
}

return null;
};
Why is wallet.create undefined or throwing a type error?
  • wallet.create will be undefined if the wallet has already been created
  • If your application uses Typescript, isNotCreated refines the type of wallet. So wallet.create will cause a type error outside a check that either isNotCreated is true or wallet.status === 'not-created'

Getting an EIP-1193 providerโ€‹

To enable your app to request signatures and transactions from the embedded wallet, Privy embedded wallets export an EIP-1193 provider. This allows you request signatures and transactions from the wallet via a familiar JSON-RPC API (e.g. personal_sign).

To get an EIP-1193 provider for the embedded wallet, use the Privy Expo SDK's useEmbeddedWallet hook.

import {useEmbeddedWallet, isConnected, needsRecovery} from '@privy-io/expo';

const Component = () => {
const wallet = useEmbeddedWallet();
const [password, setPassword] = useState('');

const signMessage = (provider: EIP1193Provider) => {
// Get the wallet address
const accounts = await provider.request({
method: 'eth_requestAccounts'
});

// Sign message
const message = 'I hereby vote for foobar';
const signature = await provider.request({
method: 'personal_sign',
params: [message, accounts[0]]
});
}

if (isConnected(wallet)) {
/* The user's embedded wallet exists and is ready to be used! */
return <Button onPress={() => { signMessage(wallet.provider) }}>Sign a message</Button>
}

if (needsRecovery(wallet)) {
/*
The user's embedded wallet exists but has never been loaded on this device.
They will need to go through the password recovery flow to use it.
*/
return (
<View>
The user's embedded wallet exists but has never been loaded on this device. They will need
to go through the password recovery flow to use it.
</View>
);
}

return null;
};
info

Users are only required to enter the password for their embedded wallet if both of the following are true:

The useEmbeddedWallet hook's status property helps inform your app whether these conditions are true for the current user's wallet, allowing you to prompt the user for their password only when necessary.

Requesting signatures and transactionsโ€‹

Once you have used isConnected(wallet) or wallet.status === 'connected' to make sure the wallet is connected and get it's EIP-1193 provider, you can use the provider.request method to send JSON-RPC requests that request signatures and transactions from the wallet.

The request method accepts an object with the fields:

  • method (required): the name of the JSON-RPC method as a string, e.g. personal_sign
  • params (optional): an array of arguments for the JSON-RPC method specified by method
// Get address
const accounts = await wallet.provider.request({
method: 'eth_requestAccounts'
});

// Sign message
const message = 'I hereby vote for foobar';
const signature = await wallet.provider.request({
method: 'personal_sign',
params: [message, accounts[0]]
});

Switching networksโ€‹

By default, embedded wallets are connected to the Ethereum mainnet.

To switch the embedded wallet to a different network, send an wallet_switchEthereumChain JSON-RPC request to the wallet's EIP-1193 provider. In the request's params, specify your target chainId as a hexadecimal string.

await wallet.provider.request({
method: 'wallet_switchEthereumChain',
// Replace '0x5' with the chainId of your target network
params: [{chainId: '0x5'}]
});
info

The Privy Expo SDK currently only supports the networks listed here. We are actively adding support for additional networks; please reach out if you need one urgently prioritized!

Integrating with third-party librariesโ€‹

Using the wallet's EIP-1193 provider, you can easily integrate Privy alongside a third-party library like viem or ethers to interface with the embedded wallet. Third-party libraries may require additional shims to be used in a React Native environment.

First, import the necessary methods, objects, and networks from viem:

import {createWalletClient, custom} from 'viem';
// Replace 'mainnet' with your desired network
import {mainnet} from 'viem/chains';

Next, get an EIP-1193 provider for the user's embedded wallet, and switch its network to your desired network:

await wallet.provider.request({
method: 'wallet_switchEthereumChain',
// Replace '0x1' with the chain ID of your desired network
params: [{chainId: '0x1'}]
});

Lastly, initialize a viem Wallet Client from the EIP-1193 provider:

const walletClient = createWalletClient({
// Replace this with your desired network that you imported from viem
chain: mainnet,
transport: custom(wallet.provider)
});

You can now use methods implemented by viem's Wallet Client, including signMessage, signTypedData, and sendTransaction!

Setting a password for an existing walletโ€‹

Coming Soon