This guide will help you integrate Phantom's SDK to enable users to connect with Google (user wallets) or via the Phantom browser extension/mobile app.
When a user clicks the Phantom button in your existing modal, they will have two options:
- Continue with Google - Creates a non-custodial user wallet authenticated via Google
- Connect via Extension/App - Connects to the user's existing Phantom browser extension or mobile app
Both options provide full wallet functionality including signing transactions, sending funds, and managing accounts.
npm install @phantom/react-sdk@betanpm install @phantom/browser-sdk@betaNote: Make sure to install the latest beta version to get access to the newest features including Google authentication and improved mobile browser support.
- Visit phantom.com/portal
- Create or select your application
- Copy your
appId - Configure your URLs under the URL config section.
- Whitelisted domains
- Whitelisted auth redirect URLs
Important: When using Google authentication, you must configure redirect URLs to handle the OAuth callback. This is critical for the authentication flow to work properly.
Create a callback route in your app (e.g., /auth/callback) that will handle the OAuth redirect:
// Example: /auth/callback route
import { useConnect, usePhantom } from "@phantom/react-sdk";
function AuthCallback() {
const { isConnected, isConnecting } = usePhantom();
const { error: connectError } = useConnect();
// Loading state
if (isConnecting) {
return (
<div>
<div className="spinner" />
<h3>Connecting to your wallet...</h3>
<p>Please wait while we complete authentication.</p>
</div>
);
}
// Success state
if (isConnected) {
return (
<div>
<div className="success-icon">✓</div>
<h3>Authentication Successful</h3>
<p>You are now connected to your wallet.</p>
<button onClick={() => window.location.href = "/"}>
Continue to App
</button>
</div>
);
}
// Error state
if (connectError) {
return (
<div>
<div className="error-icon">✗</div>
<h3>Authentication Failed</h3>
<p>{connectError.message || "An error occurred during authentication."}</p>
<div>
<button onClick={() => window.location.reload()}>Retry</button>
<button onClick={() => window.location.href = "/"}>Go Home</button>
</div>
</div>
);
}
return null;
}Set the redirectUrl in your SDK configuration to point to your callback route:
import { PhantomProvider } from "@phantom/react-sdk";
import { AddressType } from "@phantom/browser-sdk";
function App() {
return (
<PhantomProvider
config={{
providers: ["google", "injected"], // Google auth + extension/app
appId: "your-app-id-from-portal", // Required for Google auth
addressTypes: [AddressType.solana, AddressType.ethereum], // Or just Solana
authOptions: {
// Set your callback URL - this is where users will be redirected after Google auth
redirectUrl: window.location.origin + "/auth/callback"
},
}}
>
<YourApp />
</PhantomProvider>
);
}import { BrowserSDK, AddressType } from "@phantom/browser-sdk";
const sdk = new BrowserSDK({
providers: ["google", "injected"], // Google auth + extension/app
appId: "your-app-id-from-portal", // Required for Google auth
addressTypes: [AddressType.solana, AddressType.ethereum], // Or just Solana
authOptions: {
// Set your callback URL
redirectUrl: window.location.origin + "/auth/callback"
},
});Make sure your routing is configured to handle the callback route:
import { Routes, Route } from "react-router-dom";
import { PhantomProvider } from "@phantom/react-sdk";
import { AuthCallback } from "./AuthCallback";
import { YourMainApp } from "./YourMainApp";
function App() {
const config = {
providers: ["google", "injected"],
appId: "your-app-id",
addressTypes: [AddressType.solana, AddressType.ethereum],
authOptions: {
redirectUrl: window.location.origin + "/auth/callback",
},
};
return (
<Routes>
<Route
path="/auth/callback"
element={
<PhantomProvider config={config}>
<AuthCallback />
</PhantomProvider>
}
/>
<Route
path="/"
element={
<PhantomProvider config={config}>
<YourMainApp />
</PhantomProvider>
}
/>
</Routes>
);
}// app/auth/callback/page.tsx
"use client";
import { PhantomProvider } from "@phantom/react-sdk";
import { AuthCallback } from "@/components/AuthCallback";
export default function CallbackPage() {
const config = {
providers: ["google", "injected"],
appId: process.env.NEXT_PUBLIC_APP_ID!,
addressTypes: [AddressType.solana, AddressType.ethereum],
authOptions: {
redirectUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/auth/callback`,
},
};
return (
<PhantomProvider config={config}>
<AuthCallback />
</PhantomProvider>
);
}-
Loading State: Always show a loading indicator while
isConnectingis true. The SDK handles the OAuth flow automatically when the user returns to your callback URL. -
Error Handling: Check for
connectErrorand display user-friendly error messages. Common errors include:- User cancelled authentication
- Network errors
- Invalid redirect URL configuration
-
Success State: Once
isConnectedis true, you can redirect users to your main app or show a success message. -
URL Matching: The
redirectUrlmust exactly match the URL where your callback component is mounted. Make sure there are no trailing slashes or query parameters that could cause mismatches. -
Environment Variables: For production, use environment variables for your redirect URL:
redirectUrl: process.env.NEXT_PUBLIC_REDIRECT_URL || import.meta.env.VITE_REDIRECT_URL || window.location.origin + "/auth/callback"
When a user clicks your existing Phantom button, you'll want to show them connection options. Here's how to implement it:
import { useConnect, usePhantom } from "@phantom/react-sdk";
function PhantomConnectButton() {
const { connect, isConnecting } = useConnect();
const { isConnected, addresses } = usePhantom();
const handlePhantomClick = async () => {
if (isConnected) {
// Already connected, show wallet info or disconnect
return;
}
// Show connection options modal
// Option 1: Connect with Google
const connectWithGoogle = async () => {
try {
await connect({ provider: "google" });
console.log("Connected with Google:", addresses);
} catch (error) {
console.error("Google connection failed:", error);
}
};
// Option 2: Connect with Extension/App
const connectWithExtension = async () => {
try {
await connect({ provider: "injected" });
console.log("Connected with extension:", addresses);
} catch (error) {
console.error("Extension connection failed:", error);
}
};
// Show your modal with both options
showConnectionModal({
onGoogle: connectWithGoogle,
onExtension: connectWithExtension,
});
};
return (
<button onClick={handlePhantomClick} disabled={isConnecting}>
{isConnecting ? "Connecting..." : "Connect Phantom"}
</button>
);
}import { BrowserSDK, AddressType } from "@phantom/browser-sdk";
const sdk = new BrowserSDK({
providers: ["google", "injected"],
appId: "your-app-id",
addressTypes: [AddressType.solana, AddressType.ethereum],
});
// Handle Phantom button click
async function handlePhantomClick() {
if (sdk.isConnected()) {
// Already connected
return;
}
// Show connection options
const option = await showConnectionModal(); // Your modal implementation
if (option === "google") {
try {
const { addresses } = await sdk.connect({ provider: "google" });
console.log("Connected with Google:", addresses);
} catch (error) {
console.error("Google connection failed:", error);
}
} else if (option === "extension") {
try {
const { addresses } = await sdk.connect({ provider: "injected" });
console.log("Connected with extension:", addresses);
} catch (error) {
console.error("Extension connection failed:", error);
}
}
}- User clicks "Continue with Google"
- SDK redirects to Google OAuth
- User authenticates with Google
- SDK creates a non-custodial wallet (keys encrypted client-side)
- User is redirected back to your app, connected and ready
- User clicks "Connect via Extension/App"
- SDK detects if Phantom extension is installed (desktop) or prompts to open mobile app
- User approves connection in extension/app
- Connection established, wallet ready to use
Once connected, you can use the wallet for all standard operations:
import { useSolana, useEthereum } from "@phantom/react-sdk";
function TransactionExample() {
const { solana } = useSolana();
const { ethereum } = useEthereum();
// Sign and send Solana transaction
const sendSolanaTx = async () => {
const result = await solana.signAndSendTransaction(transaction);
console.log("Transaction sent:", result.signature);
};
// Sign and send Ethereum transaction
const sendEthereumTx = async () => {
const result = await ethereum.sendTransaction({
to: "0x...",
value: "1000000000000000000",
});
console.log("Transaction sent:", result.hash);
};
// Sign messages
const signMessage = async () => {
const signature = await solana.signMessage("Hello Polymarket!");
console.log("Signature:", signature);
};
}// Sign and send Solana transaction
const solanaResult = await sdk.solana.signAndSendTransaction(transaction);
console.log("Transaction sent:", solanaResult.signature);
// Sign and send Ethereum transaction
const ethResult = await sdk.ethereum.sendTransaction({
to: "0x...",
value: "1000000000000000000",
});
console.log("Transaction sent:", ethResult.hash);
// Sign messages
const signature = await sdk.solana.signMessage("Hello Polymarket!");
console.log("Signature:", signature);The SDK automatically attempts to reconnect users who have previously connected:
// React SDK - auto-connect is enabled by default
<PhantomProvider
config={{
providers: ["google", "injected"],
appId: "your-app-id",
addressTypes: [AddressType.solana],
}}
>// Browser SDK
const sdk = new BrowserSDK({
providers: ["google", "injected"],
appId: "your-app-id",
addressTypes: [AddressType.solana],
});
// Manually trigger auto-connect
await sdk.autoConnect();- Show connection status: Use
isConnectedto show wallet status in your UI - Handle loading states: Use
isConnectingorisLoadingto show loading indicators - Error handling: Always wrap connection calls in try-catch blocks
- User experience: Show clear options for Google vs Extension connection
- Mobile optimization: Test on mobile browsers to ensure smooth experience
- Get your
appIdfrom phantom.com/portal - Install the latest beta version of the SDK:
npm install @phantom/react-sdk@beta # or npm install @phantom/browser-sdk@beta - Set up your auth callback route (
/auth/callbackor similar) with proper loading, success, and error states - Configure
redirectUrlin your SDK config to point to your callback route - Update your Phantom button to show connection options (Google vs Extension)
- Test the complete flow:
- Test Google authentication flow end-to-end
- Test Extension/App connection
- Verify redirect handling works correctly
- Test error scenarios (user cancellation, network errors)
- Deploy and monitor connection success rates
If authentication isn't completing:
- Verify your
redirectUrlexactly matches the URL where your callback component is mounted - Check that your callback route is properly configured in your router
- Ensure there are no CORS issues blocking the redirect
- Check browser console for any error messages