--- id: 0072f title: gamedev — crypto on-chain (NFT assets, payments, leaderboards firmadas) status: pending priority: medium created: 2026-05-10 tags: [gamedev, crypto, web3, nft] parent_issue: 0072 depends_on: [0072e] --- ## Objetivo Construir las primitivas para juegos crypto-aware: cargar assets desde NFTs del wallet del jugador, gating de contenido por tenencia de tokens, pagos in-game on-chain, y leaderboards verificables (firmados, anti-cheat basico). Toda la logica viaja por el bridge JS de 0072e + funciones backend nuevas. ## Casos de uso | Caso | Descripcion | |---|---| | **NFT skin** | Player conecta wallet, juego carga PFP/skin desde NFT que posee. | | **Token gating** | Modos/niveles desbloqueados solo si wallet tiene cierto token o NFT. | | **In-game shop** | Pagos en stablecoin (USDC) por items. | | **Earn-to-play** | Pagos del juego al jugador (tx outgoing del game treasury). | | **Leaderboard firmada** | Score tagged con firma del jugador, servidor verifica autenticidad. | | **Cross-game inventory** | Inventario portable entre varios juegos del mismo studio (NFT estandar). | ## Funciones a crear ### Lectura on-chain (en JS bridge, expuesto a WASM) ```js window.fnGameBridge.crypto = { async getNFTs(address, contractAddr) // ERC721/1155 listing async getTokenBalance(address, token) // ERC20 balance async getENS(address) // ENS reverse lookup async ownsNFT(address, contract, tokenId) // bool, gating }; ``` Implementacion: usar `viem` (~50KB gz) o llamadas RPC directas (`fetch` a Alchemy/Infura/Public RPC). ### En C++ (registry) `cpp/functions/crypto/nft_loader.{cpp,h,md}` (impure, WASM): ```cpp struct NFTAsset { std::string contract; std::string token_id; std::string name; std::string image_url; // ipfs:// o https:// std::string metadata_json; // raw }; void crypto_list_nfts(const std::string& address, void (*cb)(std::vector, void*), void* user); void crypto_load_nft_image(const NFTAsset& nft, void (*cb)(std::vector, void*), void* user); ``` `cpp/functions/crypto/token_gating.{cpp,h,md}` (impure, WASM): ```cpp struct GateRule { std::string contract; std::string token_id; // "" = any token of contract uint64_t min_balance; }; void crypto_check_gate(const std::string& address, const GateRule& rule, void (*cb)(bool ok), void* user); ``` `cpp/functions/crypto/sign_score.{cpp,h,md}` (impure, WASM): ```cpp // Player firma su score → servidor verifica struct ScoreEntry { std::string game_id; std::string level; uint64_t score; uint64_t timestamp; std::string nonce; }; void crypto_sign_score(const ScoreEntry& s, void (*cb)(std::string sig, void*), void* user); ``` ### Backend (verificacion de firmas) Funciones Go nuevas en `functions/crypto/`: - `eth_verify_signature_go_crypto` (pure) — recover signer address de firma + mensaje. Vendoring: `go-ethereum/crypto`. - `eip712_hash_go_crypto` (pure) — hash de typed data segun EIP-712. - `solana_verify_signature_go_crypto` (pure) — ed25519 verify. Service nuevo en `apps/`: `crypto_leaderboard_api` (tag `service`): - POST `/score` con `ScoreEntry` + firma → verifica → guarda en BD si valido. - GET `/leaderboard?game=X&level=Y` → top scores firmados. - BD: SQLite con tabla `scores` (game_id, level, address, score, signature, timestamp). ### Frontend dashboard `apps/crypto_leaderboard_api/web/` — pagina React/Mantine que consume la API y muestra leaderboards. Usa `@fn_library`. ## NFT image loading Las imagenes de NFTs vienen via: - `ipfs://Qm...` — resolver con gateway publico (`https://ipfs.io/ipfs/`, `https://nftstorage.link`, `https://cloudflare-ipfs.com`). - `https://...` — directo. - `data:image/png;base64,...` — embebido. Funcion: `crypto_resolve_ipfs_url_cpp_crypto` (pure): ```cpp std::string resolve_ipfs(const std::string& uri, const std::vector& gateways = {}); ``` Failover entre gateways si uno falla. ## Cache local NFTs raramente cambian. Cache en localStorage / IndexedDB: ```js window.fnGameBridge.cache = { async getNFT(contract, tokenId) { /* IndexedDB lookup */ }, async putNFT(nft) { /* IndexedDB store */ }, }; ``` C++ no toca cache directamente; el JS bridge gestiona cache con TTL 24h. ## Pagos in-game Patron canonico: ```cpp // Comprar power-up por 1 USDC struct Payment { std::string token_contract; // USDC mainnet std::string to_address; // Game treasury std::string amount_wei; // 1000000 = 1 USDC (6 decimales) std::string memo; // "buy:powerup_001:player_123" }; void crypto_pay(const Payment& p, void (*cb)(std::string tx_hash, void*), void* user); ``` Backend verifica via webhook / polling: 1. Watch transactions on `to_address`. 2. Match `memo` → entregar item al usuario. 3. Servicio nuevo: `crypto_payment_watcher` (tag `service`). ## EIP-712 typed data Para firmas estructuradas (mejor UX que `personal_sign` plano): ```js // JS bridge expone: async signTypedData(domain, types, message) { return await signer.signTypedData(domain, types, message); } ``` Beneficio: MetaMask muestra el contenido legible al usuario, no un blob hex. Reduce phishing. ## Anti-cheat layers Score firmado NO es anti-cheat completo (un cheater puede firmar scores falsos). Layers complementarios: 1. **Sanity bounds** — backend rechaza scores imposibles (ej. > max teorico). 2. **Replay attestation** — guardar inputs del juego junto al score, replay deterministic en backend para validar. 3. **Server-authoritative** para modos competitivos — el juego envia inputs, el server simula. No es para todos los juegos. 4. **Rate limit** por wallet — max N scores/hora. Documentar en `cpp/GAMEDEV.md` que la firma es **autenticacion**, no **integridad del score**. ## Integracion con apps existentes Si una app C++ quiere features crypto: 1. Declara `uses_functions: [bridge_web3_cpp_crypto, nft_loader_cpp_crypto, ...]`. 2. Compila con `-sASYNCIFY` y los `ASYNCIFY_IMPORTS` del bridge. 3. Embebe `web/bridge.js` + `web/crypto.js` en su `shell.html`. ## Tamaño - C++ extra: ~10 KB total (bridge + nft loader + token gating + sign score). - JS host extra: ~50 KB gz (viem) + ~20 KB gz (helpers). **Lazy loaded**, no afecta TTI inicial salvo que el juego conecte wallet en startup (no recomendado). - Backend Go: parte de un service separado, no afecta runtime del juego. ## Standards a soportar | Standard | Para qué | |---|---| | ERC-721 | NFT unicos (skins, characters) | | ERC-1155 | NFT semi-fungibles (items, currency) | | ERC-20 | Tokens fungibles (in-game currency) | | EIP-712 | Typed data signing | | EIP-1193 | Wallet provider interface | | Metaplex (Solana) | NFTs Solana | ## Criterio de exito - [x] `engine_demo` carga PFP NFT del wallet conectado. - [x] Token gating: solo wallets con NFT X pueden entrar al modo Y. - [x] Pago de 1 USDC testnet entrega un item in-game tras confirmacion. - [x] Leaderboard API valida firmas correctamente y rechaza falsificaciones. - [x] Tests: mock provider para validar flujos sin red real. - [x] Documentacion completa en `cpp/GAMEDEV.md` seccion crypto on-chain. ## Riesgos 1. **Gas fees** — pagos pequeños on-chain en mainnet ETH son inviables (gas > tx). Estrategia: L2 (Base, Arbitrum, Polygon) o Solana de default. 2. **IPFS gateway flaky** — failover entre gateways, cache local. 3. **Wallet UX en mobile** — WalletConnect deep links requieren testing real en devices. 4. **Regulacion** — payments crypto pueden caer bajo MiCA (EU) o equivalentes. Documentar disclaimers, no consejo legal aqui. 5. **Phishing** — siempre EIP-712 con domain explicito. Documentar para devs que usen el stack. ## No-objetivos - Wallet integrado en el juego (custodial). Demasiado complejo + responsabilidad legal. - Smart contracts propios — issue separado por juego, no aplica al stack genérico. - Tokenomics / gobernanza — no es problema del runtime.