Privy VR Lobby
Enter virtual lobbies with gas-free wallet login and seamless social interactions.
The primitive.
The Solana primitive runs at the right moment in the flow and surfaces a clear, verifiable result that game designers can act on without web3 jargon.
Why this primitiveA single-endpoint x402 paywall on Solana Devnet is the smallest possible monetization surface for 'Privy VR Lobby' — one 0.01 USDC signature unlocks the creative payload, settled by PayAI, verifiable on Solana Explorer, with no server-side subscription state to manage.
Required keys.
Add these in your Lovable project under Settings → Secrets before pasting the prompt below.
The build prompt.
Paste into a fresh Lovable project. Make sure all five secrets above are set first. read the build strategy →
Build "Privy VR Lobby" in ONE Lovable message. Single-page demo on Solana Devnet with an x402 paywall.
This prompt is self-contained — do NOT rely on any external Solana or x402 skill beyond what is inlined here.
CONCEPT
Enter virtual lobbies with gas-free wallet login and seamless social interactions.
Discipline: Game Design & Interactive Media (XR social spaces).
Onchain primitive: x402 Paid Endpoint on Solana Devnet. Why this primitive: A single-endpoint x402 paywall on Solana Devnet is the smallest possible monetization surface for 'Privy VR Lobby' — one 0.01 USDC signature unlocks the creative payload, settled by PayAI, verifiable on Solana Explorer, with no server-side subscription state to manage.
5-CREDIT BUDGET (HARD LIMIT — one Lovable message, no follow-ups):
- ONE single-page app (index route only). No Lovable Cloud, no database, no auth flows
beyond Privy drop-in.
- Privy (Google login + embedded Solana wallet on Devnet) is ALWAYS the signing layer.
- Every unlock = 0.01 USDC on Solana Devnet, settled by PayAI's public facilitator.
- No custom Solana program. No Anchor. No Metaplex. No cNFT. Just SPL Token transfer
wrapped in the x402 exact scheme.
REQUIRED SECRETS (Lovable → Project Settings → Secrets — add BEFORE sending this prompt):
- PRIVY_APP_ID From https://dashboard.privy.io. In the SAME Privy app, whitelist BOTH
`https://*.lovable.app` AND your published `https://<slug>.lovable.app`
under Allowed Origins, or the signing iframe is killed by frame-ancestors
CSP and every tx fails with a blank "Something went wrong" modal.
Enable Solana gas sponsorship for cluster `solana:devnet` in the same
dashboard so users never see a fund-wallet prompt for SOL.
- SOLANA_RPC_URL Alchemy Solana Devnet RPC: https://solana-devnet.g.alchemy.com/v2/<KEY>.
The public https://api.devnet.solana.com is rate-limited and blocks
browser CORS on getLatestBlockhash — do NOT use it.
- LOVABLE_API_KEY Auto-provisioned by Lovable. Optional — only if the unlocked payload
needs one AI call per unlock (invoked from a server function, never the
browser).
Expose secrets to the browser in `.env.local` (Vite only ships VITE_* to the client):
```
VITE_PRIVY_APP_ID=<same value as PRIVY_APP_ID secret>
VITE_SOLANA_RPC_URL=<same value as SOLANA_RPC_URL secret>
```
STACK
- React + Vite + TanStack Start single page.
- `@solana/web3.js` + `@solana/spl-token` for tx building. `@privy-io/react-auth` v3+ for auth AND signing.
- Cluster: Devnet ONLY. Never mainnet.
- PayAI SVM facilitator: `https://x402.payai.network/api/solana-devnet/paid-content`.
- Devnet USDC mint: `4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU`, 6 decimals.
- Amount: `"10000"` atomic units = 0.01 USDC per unlock.
- Network id (CAIP-2-ish): `"solana-devnet"` — the string PayAI uses in `accepts[].network`.
- Scheme: `"exact"`.
VITE + BUFFER POLYFILL (4 layers — omit any one and it works in dev but blanks the
published mobile build):
1. `bun add buffer vite-plugin-node-polyfills`.
2. `vite.config.ts`:
```ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { nodePolyfills } from "vite-plugin-node-polyfills";
export default defineConfig({
plugins: [react(), nodePolyfills({ globals: { Buffer: true, global: true }, protocolImports: true })],
optimizeDeps: { esbuildOptions: { define: { global: "globalThis" } } },
resolve: { alias: { "rpc-websockets/dist/lib/client": "rpc-websockets/dist/lib/client.browser.js" } },
});
```
DO NOT add a top-level `define: { global: "globalThis" }` in vite.config — it conflicts with the
plugin's Rollup inject and leaves Buffer undefined in dependency chunks. DO NOT put `import`
statements inside `build.rollupOptions.output.intro` — top-level import inside a chunk wrapper is
illegal and blanks the page.
3. `index.html` — inline BEFORE any bundle loads:
```html
<script type="module">
import { Buffer } from "https://esm.sh/buffer@6";
globalThis.Buffer = globalThis.Buffer || Buffer;
</script>
```
4. `src/polyfills.ts` (imported FIRST in `src/main.tsx`, before anything else):
```ts
import { Buffer } from "buffer";
(globalThis as any).Buffer = (globalThis as any).Buffer || Buffer;
(window as any).Buffer = (window as any).Buffer || Buffer;
```
Then in every file that touches Solana, add `import { Buffer } from "buffer";` at the top — the
plugin cannot inject into your own TypeScript source.
PRIVY (browser auth + Solana signing) — v3, MUST include `solana.rpcs`:
```
bun add @privy-io/react-auth @solana/web3.js @solana/spl-token @solana/kit @solana-program/memo
```
(`@solana-program/memo` is an implicit peer dep of `@privy-io/react-auth/solana`; missing it
crashes the Privy modal at runtime.)
`src/components/PrivyRoot.tsx`:
```tsx
import { PrivyProvider } from "@privy-io/react-auth";
import { toSolanaWalletConnectors } from "@privy-io/react-auth/solana";
import { createSolanaRpc, createSolanaRpcSubscriptions } from "@solana/kit";
const rpcUrl = import.meta.env.VITE_SOLANA_RPC_URL!;
const wsUrl = rpcUrl.replace("https://", "wss://");
export function PrivyRoot({ children }: { children: React.ReactNode }) {
return (
<PrivyProvider
appId={import.meta.env.VITE_PRIVY_APP_ID!}
config={{
loginMethods: ["google", "email"],
embeddedWallets: { solana: { createOnLogin: "users-without-wallets" } },
externalWallets: { solana: { connectors: toSolanaWalletConnectors() } },
solanaClusters: [{ name: "devnet", rpcUrl }],
solana: {
rpcs: {
"solana:devnet": {
rpc: createSolanaRpc(rpcUrl),
rpcSubscriptions: createSolanaRpcSubscriptions(wsUrl),
},
},
},
}}
>{children}</PrivyProvider>
);
}
```
Without the `solana.rpcs` mapping, Privy throws "No RPC configuration found for chain solana:devnet".
Sign the x402 tx with the embedded wallet:
```tsx
import { useSolanaWallets } from "@privy-io/react-auth/solana";
const { wallets } = useSolanaWallets();
const embedded = wallets.find(w => w.walletClientType === "privy");
const signed = await embedded!.signTransaction(tx); // VersionedTransaction
```
X402 PROXY ROUTE (same-origin — the facilitator does NOT send CORS headers, direct
browser fetch throws `TypeError: Failed to fetch` before the 402 arrives):
```ts
// src/routes/api/public/x402-proxy.ts
import { createFileRoute } from "@tanstack/react-router";
const ENDPOINT = "https://x402.payai.network/api/solana-devnet/paid-content";
export const Route = createFileRoute("/api/public/x402-proxy")({
server: {
handlers: {
GET: async ({ request }) => {
const sig = request.headers.get("PAYMENT-SIGNATURE");
const upstream = await fetch(ENDPOINT, {
method: "GET",
headers: sig ? { "PAYMENT-SIGNATURE": sig } : {},
});
const body = await upstream.arrayBuffer();
const out = new Headers();
const ct = upstream.headers.get("content-type");
if (ct) out.set("Content-Type", ct);
const pr = upstream.headers.get("PAYMENT-RESPONSE");
if (pr) out.set("PAYMENT-RESPONSE", pr);
return new Response(body, { status: upstream.status, headers: out });
},
},
},
});
```
The `/api/public/*` prefix bypasses Lovable's published-site auth gate — that's what we want.
Do NOT add auth middleware.
X402 SVM EXACT SCHEME — locked-in tx shape (PayAI rejects anything else):
Exactly 3 instructions in this order, no Memo, compute unit limit ≤ 40_000:
```ts
import { Connection, PublicKey, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } from "@solana/web3.js";
import { createTransferCheckedInstruction, getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID } from "@solana/spl-token";
const conn = new Connection(import.meta.env.VITE_SOLANA_RPC_URL!, "confirmed");
const payer = new PublicKey(embeddedAddress);
const feePayer = new PublicKey(requirement.extra.feePayer); // facilitator's Solana pubkey
const mint = new PublicKey(requirement.asset); // Devnet USDC
const payTo = new PublicKey(requirement.payTo);
const sourceAta = getAssociatedTokenAddressSync(mint, payer, true);
const destAta = getAssociatedTokenAddressSync(mint, payTo, true);
const ixs = [
ComputeBudgetProgram.setComputeUnitLimit({ units: 40_000 }),
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1 }),
createTransferCheckedInstruction(sourceAta, mint, destAta, payer,
BigInt(requirement.amount), 6, [], TOKEN_PROGRAM_ID),
];
const { blockhash } = await conn.getLatestBlockhash("confirmed");
const msg = new TransactionMessage({
payerKey: feePayer,
recentBlockhash: blockhash,
instructions: ixs,
}).compileToV0Message();
const tx = new VersionedTransaction(msg);
```
Common failures if you deviate: extra Memo ix → "invalid_payload"; compute limit > 40_000 →
"invalid_payload"; wrong feePayer → "invalid_signature".
X402 V2 ENVELOPE (base64-encoded into the `PAYMENT-SIGNATURE` header):
```ts
const envelope = {
x402Version: 2,
accepted: requirement, // echo the exact requirement you picked from accepts[]
payload: { transaction: signedTxB64 },
};
const header = btoa(JSON.stringify(envelope));
```
NOT `{ scheme, network, payload }` at top level — that's v1 and PayAI rejects it as
`invalid_payload`.
4-STEP UI FLOW (always log each step visibly):
1. Challenge — `GET /api/public/x402-proxy` → expect 402 → parse `{ x402Version:2, accepts, error }`.
Pick `accepts[]` where `network === "solana-devnet"` && `scheme === "exact"`.
2. Sign — build the tx above, `embedded.signTransaction(tx)`, base64-serialize `tx.serialize()`.
3. Retry — `GET /api/public/x402-proxy` with header `PAYMENT-SIGNATURE: <base64 envelope>`.
4. Settle — on 200, read `PAYMENT-RESPONSE` header, base64-decode → `{ success, transaction, network, payer }`.
Link the tx to `https://explorer.solana.com/tx/${transaction}?cluster=devnet`.
On any non-200 retry, surface the response body verbatim in the flow log — facilitator errors
(`invalid_payload`, `insufficient_funds`, `invalid_signature`) are the only signal you get.
FUND FLOW UX (before unlock):
- Show the user's Privy Solana address prominently with a copy button.
- Link "Get Devnet USDC" to https://faucet.circle.com/ (choose Solana Devnet) — arrival ~10s.
- Link "Get Devnet SOL" to https://faucet.solana.com — only needed for ATA rent on first-ever unlock.
- "Refresh balance" button that polls USDC balance on click; do NOT auto-poll.
CREATIVE PAYLOAD (what the unlock reveals)
The unlocked payload is the working demo of 'Privy VR Lobby': Enter virtual lobbies with gas-free wallet login and seamless social interactions. Build a single card/panel that renders the creative artefact (text, image, audio snippet, JSON, or interactive canvas — pick whatever best fits the XR social spaces discipline). Generate it deterministically client-side, OR make one optional LOVABLE_API_KEY call from a server function per unlock. Keep the payload self-contained — no external accounts, no user uploads, no storage.
TERMINOLOGY (user-facing copy)
- Say "Lovable Cloud", "backend", "sign in", "unlock". Never "Supabase", "wallet extension",
"seed phrase", "gas fee". Sponsored SOL + a 0.01 USDC unlock is the whole story.
DELIVERABLES
- Working demo at `/` — sign in → fund → unlock → see the creative payload.
- 4-step flow log visible during unlock.
- Every settled tx links to Solana Explorer (devnet cluster).
- No follow-up prompts. Ships in one message.
Market sizing.
Indicative figures for hackathon pitches — refine with your own research before raising.