If your Expo mobile app uses Privy, you can implement wallet deeplinking to allow your users to connect their existing mobile wallets (like Phantom) with a seamless experience. This guide will walk you through the steps to set up wallet deeplinking in your Privy Expo app.
0. Setup
This guide assumes that you have already integrated Privy into your React native app. If you have not yet set up the basic integration, please first follow the Privy quickstart.
1. Install required packages
First, make sure you have the necessary dependencies:
npm install @privy-io/expo @privy-io/expo/connectors
2. Import the wallet connector hooks
Import the wallet connector hooks in your component:
import {
useDeeplinkWalletConnector,
usePhantomDeeplinkWalletConnector,
useBackpackDeeplinkWalletConnector
} from '@privy-io/expo/connectors';
Privy Expo provides several hooks for wallet deeplinking:
Hook | Description |
---|
useDeeplinkWalletConnector | A generic wallet deeplinking connector that you can configure for various wallets |
usePhantomDeeplinkWalletConnector | A pre-configured connector specifically for Phantom wallet |
useBackpackDeeplinkWalletConnector | A pre-configured connector specifically for Backpack wallet |
3. Set up the wallet connector
Phantom
Backpack
Other wallet providers
To set up the Phantom wallet connector, use the usePhantomDeeplinkWalletConnector
hook in your component:export default function LoginScreen() {
const {
address,
connect,
disconnect,
isConnected,
signMessage,
signTransaction,
signAllTransactions,
signAndSendTransaction
} = usePhantomDeeplinkWalletConnector({
appUrl: 'https://yourdapp.com',
redirectUri: '/sign-in'
});
// Your component code here
}
Configuration options
The usePhantomDeeplinkWalletConnector
hook accepts the following configuration:Parameter | Type | Description |
---|
appUrl | string | The URL of your app that will be displayed in the wallet app as the requesting dapp, for metadata purposes |
redirectUri | string | The path in your app that the wallet should redirect to after completing an action |
4. Using the connector in your UI
Now you can implement UI components to interact with the wallet:
return (
<View>
<H4>Wallet Deeplinking</H4>
<Text>Connected: {isConnected ? 'true' : 'false'}</Text>
{!isConnected && <Button onPress={() => connect()}>Connect Wallet</Button>}
{isConnected && (
<>
<Text>Address: {address}</Text>
<Button onPress={() => disconnect()}>Disconnect</Button>
<Button onPress={handleSignMsg}>Sign message</Button>
<Button onPress={handleSignTx}>Sign transaction</Button>
<Button onPress={handleSignAndSendTx}>Sign and send transaction</Button>
<Button onPress={handleSignAllTxs}>Sign all transactions</Button>
</>
)}
</View>
);
5. Implementing the wallet functions
Here are examples of how to implement the handler functions for various wallet actions:
// Function to handle message signing
const handleSignMsg = async () => {
try {
const message = 'Hello, Privy Expo!';
const signature = await signMessage(message);
console.log('Message signed:', signature);
} catch (error) {
console.error('Error signing message:', error);
}
};
// Function to handle transaction signing
const handleSignTx = async () => {
try {
// Create a transaction
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: new PublicKey(address),
toPubkey: new PublicKey('DESTINATION_ADDRESS'),
lamports: LAMPORTS_PER_SOL * 0.01
})
);
const signedTx = await signTransaction(transaction);
console.log('Transaction signed:', signedTx);
} catch (error) {
console.error('Error signing transaction:', error);
}
};
// Function to handle signing and sending a transaction
const handleSignAndSendTx = async () => {
try {
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: new PublicKey(address),
toPubkey: new PublicKey('DESTINATION_ADDRESS'),
lamports: LAMPORTS_PER_SOL * 0.01
})
);
const signature = await signAndSendTransaction(transaction);
console.log('Transaction sent:', signature);
} catch (error) {
console.error('Error sending transaction:', error);
}
};
// Function to handle signing multiple transactions
const handleSignAllTxs = async () => {
try {
const transactions = [
new Transaction().add(
SystemProgram.transfer({
fromPubkey: new PublicKey(address),
toPubkey: new PublicKey('DESTINATION_ADDRESS_1'),
lamports: LAMPORTS_PER_SOL * 0.01
})
),
new Transaction().add(
SystemProgram.transfer({
fromPubkey: new PublicKey(address),
toPubkey: new PublicKey('DESTINATION_ADDRESS_2'),
lamports: LAMPORTS_PER_SOL * 0.01
})
)
];
const signedTxs = await signAllTransactions(transactions);
console.log('Transactions signed:', signedTxs);
} catch (error) {
console.error('Error signing transactions:', error);
}
};
The configuration for the generic connector will depend on the specific wallet you’re integrating
with. Refer to each wallet’s documentation for their deeplinking protocol.
How deeplinking works
When a user interacts with your app:
- Your app initiates a connection request using the
connect()
function
- The user is directed to their installed wallet app
- The user approves or denies the action in their wallet
- The wallet redirects back to your app with the result
- Your app updates its state based on the wallet’s response
This flow provides a seamless experience for users, allowing them to interact with your dApp using their preferred wallet without having to switch contexts or manually copy addresses.
That’s it! You’ve successfully integrated wallet deeplinking in your Privy React Native app 🎉
For the best user experience, consider implementing fallbacks for when a user doesn’t have the
wallet installed. You might prompt them to install the wallet or offer them an alternative login
method.