The Phantom Connect React Native SDK provides React hooks for connecting to existing Phantom user wallets in your mobile React Native apps with native transaction support across multiple blockchains.
Features
- Built for React Native: Works in Expo environments with native modules for authentication
- Connection modal: Built-in modal for initiating wallet connections in mobile apps
- System browser authentication: Uses Safari (iOS) and Chrome Custom Tab (Android) for login and redirect handling
- Multi-chain support: Solana (available now), other chains coming soon.
- User wallet integration: Connects to existing Phantom mobile wallets through browser-based OAuth flows.
Security
The Phantom Connect React Native SDK connects to existing Phantom user wallets:
- OAuth authentication with Google, Apple, or custom JWT providers
- PKCE support for secure OAuth flows
- Users maintain full control of their existing wallets
- Integration with Phantom’s secure wallet infrastructure
Prerequisites
- Register your app: Sign up or log in to the Phantom Portal and register your app.
- Obtain your App ID:
- In Phantom Portal, go to your app and open URL Config (left-hand menu). This page shows your allowed origins and redirect URLs.
- Scroll down to the App ID section at the bottom of the page — your App ID is listed there, below the URL configurations.
- Copy the App ID for use in your integration.
- Allowlist your domains and redirect URLs: Add your app’s domains and redirect URLs in the Phantom Portal to enable wallet connections.
Installation
npm install @phantom/react-native-sdk@beta
Install peer dependencies
# For Expo projects
npx expo install expo-secure-store expo-web-browser expo-auth-session expo-router
# Required polyfill for cryptographic operations
npm install react-native-get-random-values
Required polyfill
You must polyfill random byte generation to ensure cryptographic operations work properly. Add this import at the very top of your app’s entry point (before any other imports):
// index.js, App.tsx, or _layout.tsx - MUST be the first import
import "react-native-get-random-values";
import { PhantomProvider } from "@phantom/react-native-sdk";
// ... other imports
The polyfill import must be the first import in your app’s entry point.
Expo projects
Add your custom scheme to app.json:
{
"expo": {
"name": "My Wallet App",
"slug": "my-wallet-app",
"scheme": "mywalletapp",
"plugins": ["expo-router", "expo-secure-store", "expo-web-browser", "expo-auth-session"]
}
}
Quick start
// App.tsx or _layout.tsx (for Expo Router)
import { PhantomProvider, AddressType, darkTheme } from "@phantom/react-native-sdk";
export default function App() {
return (
<PhantomProvider
config={{
providers: ["google", "apple"], // Enabled auth providers for React Native
appId: "your-app-id",
scheme: "mywalletapp",
addressTypes: [AddressType.solana],
authOptions: {
redirectUrl: "mywalletapp://phantom-auth-callback",
},
}}
theme={darkTheme} // Optional: Customize modal appearance
appIcon="https://your-app.com/icon.png" // Optional: Your app icon
appName="Your App Name" // Optional: Your app name
>
<WalletScreen />
</PhantomProvider>
);
}
// WalletScreen.tsx
import { View, Button, Text } from "react-native";
import { useModal, usePhantom } from "@phantom/react-native-sdk";
export function WalletScreen() {
const { open, close, isOpened } = useModal();
const { isConnected, user } = usePhantom();
if (isConnected) {
return (
<View style={{ padding: 20 }}>
<Text>Connected as: {user?.email || "Unknown"}</Text>
</View>
);
}
return (
<View style={{ padding: 20 }}>
<Button title="Connect Wallet" onPress={open} />
</View>
);
}
Use the connection modal (Recommended)
The SDK includes a built-in bottom sheet modal that provides a user-friendly interface for connecting to Phantom. The modal supports multiple authentication methods (Google, Apple) and handles all connection logic automatically.
// WalletScreen.tsx
import React from "react";
import { View, Button, Text } from "react-native";
import { useModal, useAccounts } from "@phantom/react-native-sdk";
export function WalletScreen() {
const modal = useModal();
const { isConnected, addresses } = useAccounts();
if (!isConnected) {
return (
<View style={{ padding: 20 }}>
<Button title="Connect Wallet" onPress={() => modal.open()} />
</View>
);
}
return (
<View style={{ padding: 20 }}>
<Text style={{ fontSize: 18, marginBottom: 10 }}>Wallet Connected</Text>
{addresses.map((addr, index) => (
<Text key={index}>
{addr.addressType}: {addr.address}
</Text>
))}
<Button title="Manage Wallet" onPress={() => modal.open()} />
</View>
);
}
Modal Features:
- Multiple Auth Providers: Google, Apple
- Bottom Sheet UI: Native bottom sheet design for mobile
- Automatic State Management: Shows connect screen when disconnected, wallet management when connected
- Error Handling: Clear error messages displayed in the modal
- Loading States: Visual feedback during connection attempts
Or use hooks directly
// WalletScreen.tsx
import React from "react";
import { View, Button, Text, Alert } from "react-native";
import { useConnect, useAccounts, useSolana, useEthereum, useDisconnect } from "@phantom/react-native-sdk";
export function WalletScreen() {
const { connect, isConnecting, error: connectError } = useConnect();
const { addresses, isConnected } = useAccounts();
const { solana } = useSolana();
const { ethereum } = useEthereum();
const { disconnect } = useDisconnect();
const handleConnect = async () => {
try {
await connect({ provider: "google" });
Alert.alert("Success", "Wallet connected!");
} catch (error) {
Alert.alert("Error", `Failed to connect: ${error.message}`);
}
};
const handleSignSolanaMessage = async () => {
try {
const signature = await solana.signMessage("Hello from Solana!");
Alert.alert("Solana Signed!", `Signature: ${signature.signature.slice(0, 10)}...`);
} catch (error) {
Alert.alert("Error", `Failed to sign: ${error.message}`);
}
};
const handleSignEthereumMessage = async () => {
try {
const accounts = await ethereum.getAccounts();
const signature = await ethereum.signPersonalMessage("Hello from Ethereum!", accounts[0]);
Alert.alert("Ethereum Signed!", `Signature: ${signature.signature.slice(0, 10)}...`);
} catch (error) {
Alert.alert("Error", `Failed to sign: ${error.message}`);
}
};
if (!isConnected) {
return (
<View style={{ padding: 20 }}>
<Button
title={isConnecting ? "Connecting..." : "Connect Wallet"}
onPress={handleConnect}
disabled={isConnecting}
/>
{connectError && <Text style={{ color: "red", marginTop: 10 }}>Error: {connectError.message}</Text>}
</View>
);
}
return (
<View style={{ padding: 20 }}>
<Text style={{ fontSize: 18, marginBottom: 10 }}>Wallet Connected</Text>
{addresses.map((addr, index) => (
<Text key={index}>
{addr.addressType}: {addr.address}
</Text>
))}
<Button title="Sign Solana Message" onPress={handleSignSolanaMessage} />
<Button title="Sign Ethereum Message" onPress={handleSignEthereumMessage} />
<Button title="Disconnect" onPress={disconnect} />
</View>
);
}
Chain-specific operations
Solana operations
import { useSolana } from "@phantom/react-native-sdk";
const { solana } = useSolana();
await solana.signMessage("Hello Solana!");
await solana.signAndSendTransaction(transaction);
Ethereum operations
import { useEthereum } from "@phantom/react-native-sdk";
const { ethereum } = useEthereum();
const accounts = await ethereum.getAccounts();
await ethereum.signPersonalMessage("Hello Ethereum!", accounts[0]);
await ethereum.sendTransaction(transactionData);
Hooks
useModal
Control the connection modal visibility. The modal automatically shows the appropriate content based on connection status.
const modal = useModal();
// Open the modal
modal.open(); // Shows connect options when disconnected, wallet management when connected
// Close the modal
modal.close();
// Check if modal is open
const isOpen = modal.isOpened;
Returns:
open() - Function to open the modal
close() - Function to close the modal
isOpened - Boolean indicating if modal is currently visible
Modal Behavior:
- When disconnected: Shows authentication provider options (Google, Apple)
- When connected: Shows connected wallet addresses and disconnect button
useConnect
Manages wallet connection functionality.
const { connect, isConnecting, error } = useConnect();
// Connect with specific provider (React Native supported providers)
await connect({ provider: "google" }); // Google OAuth
await connect({ provider: "apple" }); // Apple ID
useAccounts hook
Provides access to connected wallet information.
const {
addresses, // Array of wallet addresses
isConnected, // Connection status
walletId, // Phantom wallet ID
} = useAccounts();
useSolana hook
Provides access to Solana-specific operations.
const { solana } = useSolana();
// Sign a message
const signature = await solana.signMessage("Hello Solana!");
// Sign and send a transaction
const result = await solana.signAndSendTransaction(transaction);
useEthereum hook
Provides access to Ethereum-specific operations.
const { ethereum } = useEthereum();
// Get accounts
const accounts = await ethereum.getAccounts();
// Sign a personal message
const signature = await ethereum.signPersonalMessage("Hello Ethereum!", accounts[0]);
// Send a transaction
const result = await ethereum.sendTransaction(transactionData);
// Get current chain ID
const chainId = await ethereum.getChainId();
// Switch to a different EVM network
await ethereum.switchChain(137); // Switch to Polygon
Supported EVM Networks:
| Network | Chain ID | Usage |
|---|
| Ethereum Mainnet | 1 | ethereum.switchChain(1) |
| Ethereum Sepolia | 11155111 | ethereum.switchChain(11155111) |
| Polygon Mainnet | 137 | ethereum.switchChain(137) |
| Polygon Amoy | 80002 | ethereum.switchChain(80002) |
| Base Mainnet | 8453 | ethereum.switchChain(8453) |
| Base Sepolia | 84532 | ethereum.switchChain(84532) |
| Arbitrum One | 42161 | ethereum.switchChain(42161) |
| Arbitrum Sepolia | 421614 | ethereum.switchChain(421614) |
| Monad Mainnet | 143 | ethereum.switchChain(143) |
| Monad Testnet | 10143 | ethereum.switchChain(10143) |
useDisconnect
Manages wallet disconnection.
const { disconnect, isDisconnecting } = useDisconnect();
await disconnect();
Authentication flows
Available Providers
The SDK supports multiple authentication providers that you specify in the providers array:
- Google (
"google") - Google OAuth authentication
- Apple (
"apple") - Apple ID authentication
Note: React Native SDK does not support Phantom Login ("phantom") or injected provider ("injected") authentication. These are only available in the Browser SDK and React SDK for web applications.
Example Configuration:
<PhantomProvider
config={{
providers: ["google", "apple"], // Only google and apple in this example
appId: "your-app-id",
scheme: "myapp",
addressTypes: [AddressType.solana],
}}
>
<App />
</PhantomProvider>
Available Providers
The SDK supports multiple authentication providers that you specify in the providers array:
- Google (
"google") - Google OAuth authentication
- Apple (
"apple") - Apple ID authentication
Note: React Native SDK does not support Phantom Login ("phantom") or injected provider ("injected") authentication. These are only available in the Browser SDK and React SDK for web applications.
Example Configuration:
<PhantomProvider
config={{
providers: ["google", "apple"], // Only google and apple in this example
appId: "your-app-id",
scheme: "myapp",
addressTypes: [AddressType.solana],
}}
>
<App />
</PhantomProvider>
Authentication Process
- User taps Connect Wallet in your app.
- System browser opens (Safari on iOS, Chrome Custom Tab on Android).
- User authenticates with their chosen provider.
- Browser redirects back to your app using the custom scheme.
- SDK automatically processes the authentication result.
- Wallet is connected and ready to use.
Deep link handling
The SDK automatically handles deep link redirects. Ensure your app’s URL scheme is properly configured.
Redirect URL format:
{scheme}://phantom-auth-callback?wallet_id=...&session_id=...
Security considerations
The Phantom Connect React Native SDK uses platform-level secure storage and system browsers during authentication:
Secure storage
- iOS uses Keychain Services with hardware security.
- Android uses Android Keystore with hardware-backed keys.
Secure authentication
- Uses the system browser rather than in-app webviews.
- Verifies redirect origins automatically.
Configuration examples
Basic configuration
import { PhantomProvider, AddressType } from "@phantom/react-native-sdk";
<PhantomProvider
config={{
providers: ["google", "apple"],
appId: "your-app-id",
scheme: "myapp",
addressTypes: [AddressType.solana],
}}
>
<App />
</PhantomProvider>;
Multi-chain configuration
import { PhantomProvider, AddressType, darkTheme } from "@phantom/react-native-sdk";
<PhantomProvider
config={{
providers: ["google", "apple"],
appId: "your-app-id",
scheme: "mycompany-wallet",
addressTypes: [AddressType.solana, AddressType.ethereum],
authOptions: {
redirectUrl: "mycompany-wallet://auth/success",
},
}}
theme={darkTheme}
appIcon="https://your-app.com/icon.png"
appName="Your App"
>
<App />
</PhantomProvider>;
What you can do
Starter kits and examples
Mobile-specific templates and examples:
Additional resources