Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion TEMPLATES.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ Templates maintained by the Solana community

`gh:solana-foundation/templates/community/phantom-embedded-react-native`

> A minimal Expo starter template for integrating Phantom's embedded user wallets on Solana for mobile apps
> Expo starter for Phantom's embedded wallet on Solana. Google or Apple OAuth, no extension needed.

`solana` `phantom` `wallet` `embedded-wallet` `react-native` `expo` `mobile` `typescript` `oauth` `authentication`

Expand Down
3 changes: 0 additions & 3 deletions community/phantom-embedded-js/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
# Get yours at: https://phantom.com/portal
VITE_PHANTOM_APP_ID=your-app-id-here

# Phantom Connect Auth URL (REQUIRED)
VITE_PHANTOM_AUTH_URL=https://connect.phantom.app

# Authentication Redirect URL (REQUIRED - must be whitelisted in Phantom Portal)
VITE_REDIRECT_URL=http://localhost:5173/

Expand Down
224 changes: 46 additions & 178 deletions community/phantom-embedded-js/README.md
Original file line number Diff line number Diff line change
@@ -1,215 +1,83 @@
# Phantom Embedded JS

A minimal starter template for integrating **Phantom's embedded user wallets** on Solana using vanilla JavaScript/TypeScript. This template demonstrates how to authenticate users with Phantom Connect and display their Solana account information.
A vanilla JavaScript/TypeScript starter for integrating Phantom's embedded wallet SDK. Users can sign in with Google or Apple OAuth—no browser extension required.

## Features
## Quick Start

- **Phantom Connect Authentication** - OAuth-based user authentication with Google and Apple
- **Embedded User Wallet** - No browser extension required
- **Real-time Balance Display** - View SOL balance with auto-refresh every 30 seconds
- **Direct Blockchain Queries** - Uses @solana/web3.js to query Solana RPC directly
- **Copy Address** - One-click address copying
- **Theme Switching** - Toggle between light and dark modes (light mode default)
- **Fully Responsive** - Works seamlessly on mobile and desktop
- **Fast Development** - Built with Vite for instant hot reload
- **Persistent Preferences** - Theme preference saved in localStorage
### 1. Prerequisites

## Getting Started
- **Node.js 18+**
- **Phantom Portal App ID** — Register at [phantom.com/portal](https://phantom.com/portal/) and add:
- `http://localhost:5173` as an allowed origin URL
- `http://localhost:5173/` as an allowed redirect URL

### Prerequisites

- Node.js 18+ or Bun
- pnpm, npm, or yarn
- A Phantom App ID from [Phantom Developer Portal](https://phantom.com/portal)

### Installation
### 2. Create and configure

```bash
# Create from template
npx create-solana-dapp my-app --template phantom-embedded-js

# Navigate to project
cd my-app

# Install dependencies
pnpm install

# Configure environment variables
npx create-solana-dapp@latest <your-app-name> --template phantom-embedded-js
cd <your-app-name>
cp .env.example .env
# Edit .env and add your Phantom App ID

# Run development server
pnpm dev
```

The app will open at [http://localhost:5173](http://localhost:5173)

### Environment Setup

Edit the `.env` file and add your Phantom App ID:
Add your App ID to `.env`:

```env
# Your App ID from Phantom Portal (REQUIRED)
VITE_PHANTOM_APP_ID=your-app-id-here

# Phantom Connect Auth URL (optional - defaults to https://connect.phantom.app)
VITE_PHANTOM_AUTH_URL=https://connect.phantom.app

# Authentication Redirect URL (must be whitelisted in Phantom Portal)
VITE_PHANTOM_APP_ID=your-app-id-from-portal
VITE_REDIRECT_URL=http://localhost:5173/

# Solana RPC Endpoint (optional - defaults to mainnet)
VITE_SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
```

## Configuration

### Get Your Phantom App ID

1. Visit [phantom.com/portal](https://phantom.com/portal) to create an app and get your App ID
2. Whitelist your redirect URLs in the Phantom Portal:
- **Development**: `http://localhost:5173/`
- **Production**: `https://yourdomain.com/` (your production domain URL)
3. Add your App ID to `.env` file

**Best Practices**:

- Always whitelist both your development and production domain URLs
- Keep your App ID secure and never commit `.env` to version control

## Project Structure

```
phantom-embedded-js/
├── src/
│ ├── main.ts # App entry point & orchestration
│ ├── phantom.ts # Phantom SDK wrapper & authentication
│ ├── solana.ts # Solana utilities (balance, formatting)
│ ├── ui.ts # UI management
│ └── styles.css # Styling with theme support
├── index.html # HTML structure
├── package.json # Dependencies & metadata
└── .env.example # Environment template
```

## How It Works

### Authentication Flow

1. User selects Google or Apple sign-in
2. Phantom Connect handles OAuth authentication
3. User is redirected back to your app with authentication data
4. App shows loading state while processing authentication
5. App displays wallet address and SOL balance
6. Balance auto-refreshes every 30 seconds

### Balance Queries
### 3. Run

The Phantom SDK doesn't provide balance queries. This template uses `@solana/web3.js` to query the Solana blockchain directly:

```typescript
import { Connection, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js'

const connection = new Connection(rpcUrl, 'confirmed')
const publicKey = new PublicKey(address)
const lamports = await connection.getBalance(publicKey)
const sol = lamports / LAMPORTS_PER_SOL
```bash
pnpm dev
```

## Customization
Open [http://localhost:5173](http://localhost:5173).

### Styling
## What's in This Template

Customize colors and theme in `src/styles.css`:

```css
:root {
--phantom-purple: #ab9ff2;
--bg-page: #fafafa;
--text-primary: #1a1a1a;
}

[data-theme='dark'] {
--bg-page: #1a1a1a;
--text-primary: #ffffff;
}
```

### Environment Variables

| Variable | Required | Default | Description |
| ----------------------- | -------- | ------------------------------------- | ---------------------------------------------------------- |
| `VITE_PHANTOM_APP_ID` | Yes | - | App ID from Phantom Portal |
| `VITE_REDIRECT_URL` | Yes | - | OAuth redirect URL (must be whitelisted in Phantom Portal) |
| `VITE_PHANTOM_AUTH_URL` | No | `https://connect.phantom.app` | Phantom Connect URL |
| `VITE_SOLANA_RPC_URL` | No | `https://api.mainnet-beta.solana.com` | Solana RPC endpoint (defaults to mainnet) |

## Troubleshooting

### Missing App ID Error

Create a `.env` file with your Phantom App ID from [phantom.com/portal](https://phantom.com/portal), then restart the dev server.

### Authentication Fails

Ensure your redirect URL is whitelisted in Phantom Portal. The URL must match exactly:

- For development: `http://localhost:5173/`
- For production: Your domain URL (e.g., `https://yourdomain.com/`)
- Whitelist both development and production URLs
- Include trailing slash

### Balance Fetch Error or CORS

**Wrong RPC URL**: Make sure you're using a Solana RPC endpoint, not Phantom API.

**Correct (Mainnet):**

```env
VITE_SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
├── src/
│ ├── main.ts # App entry point & session handling
│ ├── phantom.ts # SDK wrapper (connect, disconnect, sign, send)
│ ├── solana.ts # Balance fetching via @solana/web3.js
│ ├── ui.ts # UI state management
│ └── styles.css # Styles with light/dark theme support
├── index.html
└── .env.example
```

**Wrong:**

```env
VITE_SOLANA_RPC_URL=https://api.phantom.app # Don't use this!
```
The template is pre-configured with:

**Recommended RPC Providers** (better reliability than public endpoints):
- Google and Apple OAuth providers
- Solana address type enabled
- Message signing and transaction sending examples
- Light/dark theme toggle

- [Helius](https://helius.dev)
- [Alchemy](https://www.alchemy.com/solana)
- [QuickNode](https://www.quicknode.com/chains/sol)
## Common Issues

## Building for Production
**"Invalid redirect URL"** — Your `.env` redirect URL must exactly match what's in Phantom Portal (including trailing slash).

```bash
# Build
pnpm build
**"Missing App ID"** — Ensure `VITE_PHANTOM_APP_ID` is set in `.env` and restart the dev server.

# Preview
pnpm preview
```
**Balance fetch error** — Make sure `VITE_SOLANA_RPC_URL` points to a valid Solana RPC endpoint. For production, use a dedicated provider like [Helius](https://helius.dev) or [QuickNode](https://quicknode.com).

**Deployment Checklist:**
## Deployment

- Update `VITE_REDIRECT_URL` to your production domain URL
- Whitelist production URL in Phantom Portal (e.g., `https://yourdomain.com/`)
- Use a reliable Solana RPC endpoint (Helius, Alchemy, or QuickNode recommended)
- Set environment variables in your hosting platform
- Test authentication flow in production environment
Add these environment variables to your hosting platform:

**Compatible Platforms**: Vercel, Netlify, Cloudflare Pages, GitHub Pages
- `VITE_PHANTOM_APP_ID`
- `VITE_REDIRECT_URL` (update to your production callback URL)
- `VITE_SOLANA_RPC_URL` (optional, defaults to mainnet-beta)

## Resources
Remember to add your production redirect URL to Phantom Portal.

- [Phantom Browser SDK Docs](https://docs.phantom.com/sdks/browser-sdk) - Official SDK documentation
- [Phantom Developer Portal](https://phantom.com/portal) - Get your App ID
- [Solana Web3.js Docs](https://github.com/solana-foundation/solana-web3.js) - Solana JavaScript SDK
- [Solana Cookbook](https://solanacookbook.com/) - Development recipes
## Learn More

## Support
- [Phantom SDK Documentation](https://docs.phantom.com/wallet-sdks-overview) — Full API reference and examples
- [Phantom Portal](https://phantom.com/portal/) — Manage your app settings
- [@phantom/browser-sdk](https://www.npmjs.com/package/@phantom/browser-sdk) — Package details and changelog

**Need help?** Visit [docs.phantom.com](https://docs.phantom.com) for documentation and support.
## License

**Found a bug?** Contact Phantom support at [docs.phantom.com](https://docs.phantom.com).
MIT
2 changes: 1 addition & 1 deletion community/phantom-embedded-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
}
},
"dependencies": {
"@phantom/browser-sdk": "1.0.0-beta.21",
"@phantom/browser-sdk": "1.0.7",
"@solana/web3.js": "^1.95.0"
},
"devDependencies": {
Expand Down
5 changes: 1 addition & 4 deletions community/phantom-embedded-js/src/phantom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ let connectedAddress: string | null = null
export function initializeSDK(): BrowserSDK {
const appId = import.meta.env.VITE_PHANTOM_APP_ID
const redirectUrl = import.meta.env.VITE_REDIRECT_URL
const authUrl = import.meta.env.VITE_PHANTOM_AUTH_URL

if (!appId || appId === 'your-app-id-here') {
throw new Error(
Expand All @@ -20,12 +19,10 @@ export function initializeSDK(): BrowserSDK {
}

sdk = new BrowserSDK({
providerType: 'embedded',
embeddedWalletType: 'user-wallet',
providers: ['google', 'apple'],
addressTypes: [AddressType.solana],
appId: appId,
authOptions: {
authUrl: `${authUrl}/login`,
redirectUrl: redirectUrl,
},
})
Expand Down
11 changes: 10 additions & 1 deletion community/phantom-embedded-react-native/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@
"web": {
"favicon": "./assets/favicon.png"
},
"plugins": ["expo-router", "expo-secure-store", "expo-web-browser"]
"plugins": [
"expo-router",
[
"expo-secure-store",
{
"faceIDPermission": "Allow $(PRODUCT_NAME) to access Face ID biometric data."
}
],
"expo-web-browser"
]
}
}
2 changes: 1 addition & 1 deletion community/phantom-embedded-react-native/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { colors } from '@/lib/theme'
* Root layout component that wraps the entire app
* Configures PhantomProvider for embedded user wallets
* Supports both Solana and Ethereum chains (multi-chain)
* Updated for SDK v1.0.0-beta.26 with modal support
* Updated for SDK v1.0.5 with latest Expo SDK 54
*/
export default function RootLayout() {
const appId = process.env.EXPO_PUBLIC_PHANTOM_APP_ID || ''
Expand Down
4 changes: 2 additions & 2 deletions community/phantom-embedded-react-native/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { colors } from '@/lib/theme'
/**
* Home screen - displays welcome message and connect button
* This is the entry point of the app where users initiate Phantom Connect
* Updated for SDK v1.0.0-beta.26 with modal support
* Updated for SDK v1.0.7 with latest Expo SDK 54
*/
export default function HomeScreen() {
/**
* Opens the Phantom documentation in the browser
*/
const openDocs = () => {
Linking.openURL('https://docs.phantom.app')
Linking.openURL('https://docs.phantom.com')
}

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'
import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator, Alert } from 'react-native'
import { useAccounts, useDisconnect, usePhantom } from '@phantom/react-native-sdk'
import { useAccounts, useDisconnect, useModal } from '@phantom/react-native-sdk'
import { useRouter } from 'expo-router'
import { getBalance } from '@/lib/solana'
import { truncateAddress, copyToClipboard } from '@/lib/utils'
Expand All @@ -9,12 +9,12 @@ import { colors } from '@/lib/theme'
/**
* WalletInfo component - Dashboard for connected wallet
* Displays Solana wallet address, SOL balance, and logout functionality
* Updated for SDK v1.0.0-beta.26 with modal support
* Updated for SDK v1.0.2 with useModal hook
*/
export function WalletInfo() {
const { addresses, isConnected } = useAccounts()
const { disconnect, isDisconnecting } = useDisconnect()
const { modal } = usePhantom()
const modal = useModal()
const router = useRouter()
const [balance, setBalance] = useState<number | null>(null)
const [isLoadingBalance, setIsLoadingBalance] = useState(false)
Expand Down
Loading
Loading