Skip to main content

Introduction

This guide will help you use the Phantom redirect-based connect feature on mobile. Instead of deeplinking into the Phantom in-app browser, your users can enjoy a redirect-based connection, where they are automatically redirected between the app and their wallet to facilitate the connect, sign and additional method calls.

Why redirect events are necessary

Unlike browser-extension wallets (like MetaMask or Phantom browser extension), Phantom on mobile operates via deep links. When you request a signature:
  1. Your app opens Phantom via a deep link (phantom://)
  2. User approves or rejects in the Phantom app
  3. Phantom redirects back to your app
On mobile web browsers, this redirect opens a new tab, which means:
  • The original JavaScript execution context is lost
  • Any Promise awaiting the signature result never resolves
  • Your app loses track of the pending operation
This is fundamentally different from browser-extension wallets where everything happens in the same page context via injected providers. To handle this, you must use event listeners to capture the redirect results.

Setup

mobileExperience prop

Make sure you set mobileExperience to redirect on your DynamicContextProvider: With just this code, your users will connect with the redirect-based approach. If you want to learn how to make method calls (like signAndSendTransaction) and access the result, read on.

Handling redirect events

When using redirect-based signing on mobile, Phantom opens via deep link and redirects back after the user approves or rejects. Because the original Promise context is lost, you must use event listeners to capture the results. In React Native, use the client.wallets.phantom event emitter to subscribe to redirect results:
import { FC, useEffect } from 'react';
import { Alert } from 'react-native';
import { useMyDynamicClient } from './useMyDynamicClient';

export const PhantomEventHandler: FC = () => {
  const { client } = useMyDynamicClient();

  useEffect(() => {
    const handleSignMessage = ({ signature, errorCode, errorMessage }) => {
      if (errorCode) {
        Alert.alert('Sign Message Failed', errorMessage || errorCode);
      } else {
        Alert.alert('Message Signed', `Signature: ${signature?.slice(0, 20)}...`);
      }
    };

    const handleSignTransaction = ({ transaction, errorCode, errorMessage }) => {
      if (errorCode) {
        console.error('Transaction signing failed:', errorMessage);
        return;
      }
      console.log('Signed transaction:', transaction);
    };

    const handleSignAndSendTransaction = ({ signature, errorCode, errorMessage }) => {
      if (errorCode) {
        console.error('Transaction failed:', errorMessage);
        return;
      }
      console.log('Transaction sent, signature:', signature);
    };

    const handleSignAllTransactions = ({ transactions, errorCode, errorMessage }) => {
      if (errorCode) {
        console.error('Batch signing failed:', errorMessage);
        return;
      }
      console.log('Signed transactions:', transactions);
    };

    // Subscribe to events
    client.wallets.phantom.on('signMessageResult', handleSignMessage);
    client.wallets.phantom.on('signTransactionResult', handleSignTransaction);
    client.wallets.phantom.on('signAndSendTransactionResult', handleSignAndSendTransaction);
    client.wallets.phantom.on('signAllTransactionsResult', handleSignAllTransactions);

    // Cleanup on unmount
    return () => {
      client.wallets.phantom.off('signMessageResult', handleSignMessage);
      client.wallets.phantom.off('signTransactionResult', handleSignTransaction);
      client.wallets.phantom.off('signAndSendTransactionResult', handleSignAndSendTransaction);
      client.wallets.phantom.off('signAllTransactionsResult', handleSignAllTransactions);
    };
  }, [client]);

  return null;
};
Mount this component at the root of your app so it can capture redirect results when Phantom redirects back.

Available events

Event / CallbackPayloadDescription
signMessageResult / onSignMessage{ signature?, message?, requestId?, errorCode?, errorMessage? }Fired after signing a message.
signTransactionResult / onSignTransaction{ transaction?, requestId?, errorCode?, errorMessage? }Fired after signing a single transaction.
signAndSendTransactionResult / onSignAndSendTransaction{ signature?, transaction?, requestId?, errorCode?, errorMessage? }Fired after signing and broadcasting a transaction.
signAllTransactionsResult / onSignAllTransactions{ transactions?, requestId?, errorCode?, errorMessage? }Fired after signing multiple transactions.
All callbacks/events are optional. The events are persisted and re-emitted when the app regains focus, ensuring you always receive the result even if the app was backgrounded.