Skip to main content
Adds support for connecting and signing with Phantom on mobile using Phantom’s deep link redirect protocol. This is the recommended integration method for mobile web apps that want to use Phantom on iOS and Android without relying on browser extension injection. The extension handles the full redirect lifecycle: building encrypted deep links, waiting for Phantom to redirect back to your app, and completing the operation.

Installation

npm install @dynamic-labs-sdk/solana

Usage

Call addPhantomRedirectSolanaExtension once during app initialization, passing the current URL so the extension can detect and complete any in-progress Phantom redirect:
import { createDynamicClient } from '@dynamic-labs-sdk/client';
import { addPhantomRedirectSolanaExtension } from '@dynamic-labs-sdk/solana/phantom-redirect';

const client = createDynamicClient({
  environmentId: 'YOUR_ENVIRONMENT_ID',
});

addPhantomRedirectSolanaExtension(
  { url: new URL(window.location.href) },
  client
);

Parameters

ParameterTypeDescription
urlURL (optional)The current page URL. Used to detect and complete a pending Phantom redirect. Pass new URL(window.location.href) on every page load.
onCloseTab() => void (optional)Callback invoked when the redirect tab should close itself. By default, the SDK calls window.close(). Provide a custom handler if you need different behavior (e.g. navigating to a fallback route).
disableAutoRedirectCompletionboolean (optional)When true, the extension will not automatically detect and complete pending redirects on load. Defaults to false.
clientDynamicClient (optional)The Dynamic client instance. Only required when using multiple clients.

How it works

Phantom’s redirect protocol works as follows:
  1. Your app calls a standard wallet operation (connect, sign, send, disconnect) on the Phantom redirect provider
  2. The SDK builds an encrypted deep link and redirects the user to the Phantom mobile app
  3. The user approves the request inside Phantom
  4. Phantom redirects back to your app. For connect and sign operations, the redirect URL includes an encrypted response. For disconnect, Phantom redirects back with no query parameters.
  5. On the next page load, addPhantomRedirectSolanaExtension detects the redirect, decrypts the response (if present), and completes the operation
Phantom opens the redirect in a new browser tab. The SDK uses cross-tab broadcast messaging to deliver the result back to the original tab where the operation was initiated, then closes the redirect tab automatically via the onCloseTab callback. All payloads between your app and Phantom are encrypted end-to-end using NaCl box encryption with ephemeral key pairs.

Supported operations

Once registered, the Phantom redirect provider supports all standard Solana wallet operations:
  • connect — request wallet connection
  • disconnect — end the wallet session
  • signMessage — sign an arbitrary message
  • signTransaction — sign a transaction
  • signAllTransactions — sign multiple transactions
  • signAndSendTransaction — sign and submit a transaction
These are used via the same functions as any other Solana wallet in Dynamic — see Signing and sending transactions.

Connect and verify on mobile

connectAndVerifyWithWalletProvider is not supported with Phantom redirect on mobile. It will throw a DeeplinkConnectAndVerifyUnsupportedError.
On mobile, connectAndVerifyWithWalletProvider triggers two sequential deep links — one to connect and another to sign for verification. iOS will not honor the second deep link because it is no longer tied to a user gesture. Android may handle this differently, but this restriction applies to all mobile platforms to ensure a stable experience and prevent production-only errors. Instead, use connectWithWalletProvider and verifyWalletAccount as two separate user actions:
import {
  connectWithWalletProvider,
  verifyWalletAccount,
  DeeplinkConnectAndVerifyUnsupportedError,
} from '@dynamic-labs-sdk/client';

// Step 1: Connect the wallet (first user tap)
const walletAccount = await connectWithWalletProvider({
  walletProviderKey: 'phantomsol:deepLink',
  addToDynamicWalletAccounts: false,
});

// Step 2: Verify ownership from a separate user action (e.g. a "Verify Ownership" button)
await verifyWalletAccount({ walletAccount });
Alternatively, you can catch the error from connectAndVerifyWithWalletProvider and fall back to the two-step flow:
import {
  connectAndVerifyWithWalletProvider,
  connectWithWalletProvider,
  verifyWalletAccount,
  DeeplinkConnectAndVerifyUnsupportedError,
} from '@dynamic-labs-sdk/client';

try {
  await connectAndVerifyWithWalletProvider({
    walletProviderKey: key,
  });
} catch (error) {
  if (error instanceof DeeplinkConnectAndVerifyUnsupportedError) {
    // Mobile deep link provider — connect only, then verify separately
    const walletAccount = await connectWithWalletProvider({
      walletProviderKey: key,
      addToDynamicWalletAccounts: false,
    });

    // Present a "Verify Ownership" button to the user, then:
    await verifyWalletAccount({ walletAccount });
  } else {
    throw error;
  }
}
See Connecting and Verifying a Wallet for more details on the connect and verify flow.

Setting a redirect URL

Phantom redirects back to your app after the user approves or rejects a request. Make sure your app handles the redirect URL on the page that calls addPhantomRedirectSolanaExtension, so the extension can complete the pending operation automatically.
// On every page load, pass the current URL
addPhantomRedirectSolanaExtension(
  { url: new URL(window.location.href) },
  client
);

// If Phantom redirected here with a response, the extension completes it automatically.
// If not, it simply registers the provider for future use.

Limitations

  • Requires the user to have the Phantom mobile app installed
  • The redirect flow causes a full page navigation — ensure your app state is restored on return
  • Each operation generates a new ephemeral encryption key pair; sessions do not persist across devices
  • Only supported on Solana
  • connectAndVerifyWithWalletProvider is not supported on mobile — see Connect and verify on mobile above