Discord Activity Integration Implementation Plan
Status: COMPLETE (as of 2026-04-09)
Original plan covered Tasks 1–9 (Discord SDK + badge). Tasks 10–14 were added during implementation to handle wallet connection, production server, and CSP constraints.
Phase 1 — Discord SDK Integration (✅ Done)
Task 1: Install dependencies ✅
@discord/embedded-app-sdk,express,@types/express,tsx,qrcode.reactadded topackage.json
Task 2: Discord environment detection ✅
src/utils/discordEnv.ts—isDiscordActivity()checks forframe_id+instance_idin URL
Task 3: Discord Provider ✅
src/providers/DiscordProvider.tsx— SDK init, OAuth (authorize → token exchange → authenticate), React context- Exposes
useDiscord()with{ sdk, user, channelId, guildId, isReady, error }
Task 4: Express OAuth backend ✅
api/server.ts—POST /tokenexchanges Discord OAuth code for access_token- Uses
DISCORD_CLIENT_ID+DISCORD_CLIENT_SECRET(server-side only)
Task 5: Vite proxy ✅ (updated)
- Originally proxied only
/.proxy/api/* - Updated: now proxies ALL
/.proxy/*→http://localhost:3001(required for session polling and external-url endpoint)
Task 6: Conditional DiscordProvider in main.tsx ✅
src/main.tsxwraps app withDiscordProvideronly whenisDiscordActivity()is true- Added
ErrorBoundaryfor crash display
Task 7: DiscordBadge component ✅
src/components/DiscordBadge.tsx— shows Discord user/channel info, errors, loading state- Returns null in standalone mode
Task 8: Environment variables documentation ✅
.env.examplecreated with all required vars
Task 9: Final build and regression check ✅
Phase 2 — Wallet Connection in Discord (✅ Done)
Discord's iframe blocks Privy popups. Solution: external wallet login page + session token system.
Task 10: External wallet login page ✅
src/components/WalletLoginPage.tsx— Privy login → POST/session { token, address }src/App.tsx— detects?session=TOKENand renders WalletLoginPage instead of PlayerMapView
Task 11: Session store + polling ✅
api/server.ts+api/unified-server.ts:POST /session,GET /session/:token,GET /external-urlsrc/components/PlayerMapView.tsx: generates UUID token, shows QR code, polls/.proxy/session/:tokenevery 2s- Address stored in
localStoragefor persistence across reloads
Task 12: Production unified server ✅
api/unified-server.ts— servesdist/static files + all API routes on port 5173serve:discordscript:tsx api/unified-server.tsEXTERNAL_URLenv var passes Cloudflare tunnel URL to the iframe
Phase 3 — Discord CSP & Network Compatibility (✅ Done)
Discord's iframe CSP blocks all external connections except *.discordsays.com.
Task 13: Fix network check (wagmi/publicClient) ✅
discordPublicClient— static viem client withgetChainId()hardcoded to1155- Transport:
/.proxy/rpc(goes through Discord proxy → our server → RPC) discordUseAccount— mockuseAccounthook returningchain: intuitionMainnet, isConnected: truediscordWalletProxy— minimal wallet client for address display and read operations
Task 14: CSP bypass via fetch monkey-patch ✅
src/main.tsx— before React renders, ifisDiscordActivity():https://mainnet.intuition.sh/v1/graphql→/.proxy/graphqlhttps://rpc.intuition.systems→/.proxy/rpc
api/unified-server.ts+api/server.ts:/graphqland/rpcproxy routes added- This allows
player-map's internal API calls (positions, atomDetails) to work inside Discord
Current Architecture Summary
Discord iframe
├─ window.fetch intercepted → external URLs → /.proxy/* → unified-server → real endpoints
├─ DiscordProvider (SDK OAuth)
└─ PlayerMapView
├─ discordAddress (from session polling)
├─ discordPublicClient (viem, transport /.proxy/rpc, getChainId=1155)
└─ discordWalletProxy (fake wallet client, read-only)
External browser (wallet login)
└─ WalletLoginPage
└─ Privy login → POST /session → unified-server stores address
unified-server (port 5173)
├─ Serves dist/ (SPA)
├─ POST /token (Discord OAuth)
├─ POST/GET /session[/:token] (wallet session store)
├─ GET /external-url (Cloudflare URL)
├─ */graphql (proxy → mainnet.intuition.sh)
└─ POST /rpc (proxy → rpc.intuition.systems)
Known Limitations / Future Work
- In-memory session store → sessions lost on server restart (consider Redis or file-based)
- Transactions not supported in Discord (wallet proxy throws on writeContract/signTypedData)
- Cloudflare tunnel URL changes each run → must re-update Discord portal URL mapping
- WalletConnect blocked by CSP (not needed since Privy handles it on external page)