Skip to main content
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.

Configure your app scheme

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>
  );
}
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:
NetworkChain IDUsage
Ethereum Mainnet1ethereum.switchChain(1)
Ethereum Sepolia11155111ethereum.switchChain(11155111)
Polygon Mainnet137ethereum.switchChain(137)
Polygon Amoy80002ethereum.switchChain(80002)
Base Mainnet8453ethereum.switchChain(8453)
Base Sepolia84532ethereum.switchChain(84532)
Arbitrum One42161ethereum.switchChain(42161)
Arbitrum Sepolia421614ethereum.switchChain(421614)
Monad Mainnet143ethereum.switchChain(143)
Monad Testnet10143ethereum.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

  1. User taps Connect Wallet in your app.
  2. System browser opens (Safari on iOS, Chrome Custom Tab on Android).
  3. User authenticates with their chosen provider.
  4. Browser redirects back to your app using the custom scheme.
  5. SDK automatically processes the authentication result.
  6. Wallet is connected and ready to use.
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