Phantom dApp Integration - Implementation Summary
What Was Changed
★ The Core Problem
The old implementation generated new Phantom encryption keys on every browser session using nacl.box.keyPair(). Phantom expected a stable, persistent encryption identity. When keys changed, Phantom rejected the connection.
★ The Solution
Switched from localStorage-based key generation to environment-configured stable keys. Keys are now:
- Generated once and stored in environment variables
- Loaded at build/runtime (never regenerated)
- Different for each environment (devnet vs mainnet)
- Validated early with clear error messages
Files Modified
1. .env (Development Configuration)
VITE_PUBLIC_PHANTOM_DAPP_PUBLIC_KEY=your_devnet_public_key_here
PHANTOM_DAPP_SECRET_KEY=your_devnet_secret_key_here
→ Generate these with: node scripts/generate-phantomkeypair.js (in smartbets-protocol)
2. .env.mainnet (Production Configuration)
# ⚠️ CRITICAL: Use DIFFERENT keys than devnet
# ⚠️ NEVER regenerate these keys once deployed
VITE_PUBLIC_PHANTOM_DAPP_PUBLIC_KEY=your_mainnet_public_key_here
PHANTOM_DAPP_SECRET_KEY=your_mainnet_secret_key_here
3. src/lib/phantomDeeplink.ts (Core Logic)
Removed:
- LocalStorage keypair fallback
- Dynamic key generation on each session
- No validation for mainnet
Added:
- Environment variable loading:
import.meta.env.VITE_PUBLIC_PHANTOM_DAPP_PUBLIC_KEY - Strict error handling with helpful messages
- Mainnet validation: Prevents deployment without keys
- Module-level validation on import
Key Function:
export const getDappKeyPair = (): nacl.BoxKeyPair => {
const publicKeyB58 = import.meta.env.VITE_PUBLIC_PHANTOM_DAPP_PUBLIC_KEY;
const secretKeyB58 = import.meta.env.PHANTOM_DAPP_SECRET_KEY;
if (!publicKeyB58 || !secretKeyB58) {
throw new Error('Missing Phantom dApp keys - see PHANTOM_SETUP.md');
}
return {
publicKey: bs58.decode(publicKeyB58),
secretKey: bs58.decode(secretKeyB58),
};
};
Documentation Added
1. PHANTOM_SETUP.md
Purpose: Complete integration guide for developers
Contains:
- Step-by-step setup instructions
- Key generation walkthrough
- Code explanation (before/after)
- Deployment instructions per platform
- Troubleshooting guide
- Key rotation procedures
Read this if: You're setting up Phantom for the first time or need detailed explanations.
2. PHANTOM_DEPLOYMENT_CHECKLIST.md
Purpose: Quick reference for CI/CD and deployment pipelines
Contains:
- Pre-deployment checks
- Build verification
- Environment variable setup per platform
- Post-deployment testing
- Rollback procedures
- Safety reminders
Read this if: You're deploying to production and need a checklist.
3. PHANTOM_INTEGRATION_SUMMARY.md (this file)
Purpose: Quick overview of changes and how to use them
Quick Start
For Developers
# 1. Generate keys (one-time)
cd ../smartbets-protocol
node scripts/generate-phantomkeypair.js
# 2. Add keys to .env
# Edit gta-frontend/.env and add the keys above
# 3. Build and test
cd ../gta-frontend
npm run build:dev
npm run preview:dev
# 4. Test Phantom connection in browser
# Should work without any localStorage manipulation
For CI/CD Pipelines
# Vercel/Netlify: Add environment variables in settings
VITE_PUBLIC_PHANTOM_DAPP_PUBLIC_KEY: <from-secrets>
PHANTOM_DAPP_SECRET_KEY: <from-secrets>
# GitHub Actions
- name: Build GTA Frontend
env:
VITE_PUBLIC_PHANTOM_DAPP_PUBLIC_KEY: ${{ secrets.PHANTOM_PUBLIC_KEY }}
PHANTOM_DAPP_SECRET_KEY: ${{ secrets.PHANTOM_SECRET_KEY }}
run: npm run build:mainnet
Architecture Changes
Before (❌ Broken on Mainnet)
Browser Session 1 Browser Session 2
↓ ↓
Generate Key #1 → localStorage Generate Key #2 → localStorage
↓ ↓
Phantom: Is this dApp #1? Phantom: Is this dApp #2?
↓ ↓
"Encryption key mismatch" ← Keys don't match!
After (✅ Stable Identity)
Dev Environment Prod Environment
↓ ↓
.env vars → Build → Mainnet deployment
↓ ↓
Key #1 (stable) Key #2 (stable)
↓ ↓
All sessions use same key → Phantom recognizes dApp
Key Points
✅ Do This
- Generate separate keypairs for devnet and mainnet
- Store secret keys in CI/CD secrets, not version control
- Use environment variables for all Phantom keys
- Rebuild when rotating keys (keys are baked into build)
❌ Don't Do This
- Don't commit secret keys to git
- Don't regenerate keys after mainnet deployment (unless absolutely necessary)
- Don't mix devnet and mainnet keys
- Don't use localStorage for persistent keys
🚨 Critical for Mainnet
Mainnet validation prevents deployment without keys:
if (VITE_SOLANA_CLUSTER === 'mainnet-beta' && !publicKey) {
throw Error('Mainnet requires Phantom dApp keys');
}
Validation & Error Handling
Module Load Time (Immediate Validation)
Import phantomDeeplink.ts
↓
Check: Is mainnet? And missing keys?
↓
YES → Throw clear error with setup instructions
NO → Continue (devnet or keys present)
Function Call Time (Runtime Validation)
Call getDappKeyPair()
↓
Check: Both keys present?
↓
NO → Throw detailed error
YES → Decode base58 and return
Testing Checklist
Before pushing to mainnet, verify:
- Keys are different for devnet and mainnet
-
npm run build:mainnetsucceeds - Public key is embedded in bundle
- Secret key is NOT in bundle (check with
grep) - Clear error if keys missing (test by removing from .env)
- Phantom connection works end-to-end:
- Connect → No error
- Sign message → No error
- Signature verified ✓
Maintenance
Key Rotation (Mainnet)
Only if necessary (security breach, rebranding, etc.):
- Generate new keys
- Update CI/CD secrets
- Redeploy with new keys
- Notify users: "Please reconnect wallet"
- Users remove from Phantom Trusted Apps
- Users reconnect with new key
Regular Checks
- Monthly: Verify secrets are in place
- After incidents: Verify key integrity
- Before deployments: Check keys match environment
Platform-Specific Setup
Vercel
- Project Settings → Environment Variables
- Add:
VITE_PUBLIC_PHANTOM_DAPP_PUBLIC_KEY - Add:
PHANTOM_DAPP_SECRET_KEY - Push to trigger rebuild
Netlify
- Site Settings → Build & Deploy → Environment
- Add variables (same names)
- Redeploy
Self-Hosted
export VITE_PUBLIC_PHANTOM_DAPP_PUBLIC_KEY=<key>
export PHANTOM_DAPP_SECRET_KEY=<key>
npm run build:mainnet
FAQ
Q: Do I need to update anything else?
A: No. All Phantom connection/signing logic already uses getDappKeyPair(). No other changes needed.
Q: Can existing sessions connect with new keys? A: No. After key rotation, users must disconnect and reconnect from Phantom.
Q: What if I lose the secret key? A: Generate new keys and rotate (expensive). Keep backups in secure vault.
Q: Does this affect WalletConnect? A: No. This is Phantom-specific. WalletConnect has its own security model.
Q: Can I preview/test mainnet locally?
A: Yes: npm run build:mainnet && npm run preview:mainnet (requires mainnet keys in .env)
Related Files
See these for more details:
PHANTOM_SETUP.md- Complete integration guidePHANTOM_DEPLOYMENT_CHECKLIST.md- Deployment checklistsrc/lib/phantomDeeplink.ts- Core implementation.env&.env.mainnet- Configuration files
Support
If issues arise:
- Check error message in browser console
- Verify keys in environment variables
- Clear Phantom session (Settings → Trusted Apps → Remove)
- Rebuild and redeploy
- Test again
See PHANTOM_SETUP.md Troubleshooting section for common issues.