Skip to main content

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:mainnet succeeds
  • 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.):

  1. Generate new keys
  2. Update CI/CD secrets
  3. Redeploy with new keys
  4. Notify users: "Please reconnect wallet"
  5. Users remove from Phantom Trusted Apps
  6. 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

  1. Project Settings → Environment Variables
  2. Add: VITE_PUBLIC_PHANTOM_DAPP_PUBLIC_KEY
  3. Add: PHANTOM_DAPP_SECRET_KEY
  4. Push to trigger rebuild

Netlify

  1. Site Settings → Build & Deploy → Environment
  2. Add variables (same names)
  3. 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)


See these for more details:

  • PHANTOM_SETUP.md - Complete integration guide
  • PHANTOM_DEPLOYMENT_CHECKLIST.md - Deployment checklist
  • src/lib/phantomDeeplink.ts - Core implementation
  • .env & .env.mainnet - Configuration files

Support

If issues arise:

  1. Check error message in browser console
  2. Verify keys in environment variables
  3. Clear Phantom session (Settings → Trusted Apps → Remove)
  4. Rebuild and redeploy
  5. Test again

See PHANTOM_SETUP.md Troubleshooting section for common issues.