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, expand your app in the left navigation, then select Set Up.
- Your App ID appears at the top of the page.
- 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
Install peer dependencies
# For Expo projects
npx expo install expo-secure-store expo-web-browser expo-auth-session expo-router react-native-svg
# For bare React Native projects (additional setup required)
npm install expo-secure-store expo-web-browser expo-auth-session react-native-svg
# 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 } = usePhantom();
if (isConnected) {
return (
<View style={{ padding: 20 }}>
<Text>Connected</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
EVM support for Phantom Connect embedded wallets will go live later in 2026. The following Ethereum methods are currently available for injected provider (Phantom extension) connections only.
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
Provides access to connected wallet information.
const {
addresses, // Array of wallet addresses
isConnected, // Connection status
walletId, // Phantom wallet ID
} = useAccounts();
useSolana
Provides access to Solana-specific operations.
const { solana, isAvailable } = useSolana();
if (isAvailable) {
// Sign a message
const signature = await solana.signMessage("Hello Solana!");
// Sign a transaction (without sending)
const signedTx = await solana.signTransaction(transaction);
// Sign and send a transaction
const result = await solana.signAndSendTransaction(transaction);
}
useEthereum
Provides access to Ethereum-specific operations.
EVM support for Phantom Connect embedded wallets will go live later in 2026.
const { ethereum, isAvailable } = useEthereum();
if (isAvailable) {
// Get accounts
const accounts = await ethereum.getAccounts();
// Sign a personal message
const signature = await ethereum.signPersonalMessage("Hello Ethereum!", accounts[0]);
// Sign a transaction (without sending)
const signedTx = await ethereum.signTransaction(transactionData);
// 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
await ethereum.switchChain("0x89"); // Also accepts hex strings
}
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
Example configuration:
<PhantomProvider
config={{
providers: ["google", "apple"], // Specify enabled providers
appId: "your-app-id",
scheme: "myapp",
addressTypes: [AddressType.solana],
}}
>
<App />
</PhantomProvider>
Example usage:
// Google OAuth
await connect({ provider: "google" });
// Apple ID
await connect({ provider: "apple" });
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>;
Debug configuration
Configure debug logging by passing a debugConfig prop to PhantomProvider:
import { PhantomProvider, AddressType } from "@phantom/react-native-sdk";
function App() {
const debugConfig = {
enabled: true, // Enable debug logging
};
return (
<PhantomProvider
config={{
providers: ["google", "apple"],
appId: "your-app-id",
scheme: "mywalletapp",
addressTypes: [AddressType.solana],
}}
debugConfig={debugConfig}
>
<YourApp />
</PhantomProvider>
);
}
Debug configuration properties:
| Property | Type | Description |
|---|
enabled | boolean | Enable debug logging (default: false) |
What you can do
Starter kits and examples
Mobile-specific templates and examples:
Additional resources