Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
# MLSA Cards DApp
Play. Learn. Collect. Mint.

A full-stack NFT collectible card game where users solve problems to earn points, purchase digital cards, and mint them as ERC-721 NFTs on the blockchain.
A full-stack Web3-powered gamified learning platform where users solve domain-based challenges, earn points, unlock collectible cards, and mint them as ERC-721 NFTs with verifiable on-chain ownership.
Developed as a collaborative MLSA project, showcasing secure authentication, blockchain integration, and real-world full-stack architecture.

## 🎯 Project Vision

MLSA Cards aims to bridge learning and ownership by transforming skill acquisition into an engaging, collectible-driven experience.

- 1.Instead of passive learning, users:

- 2.actively solve problems

- 3.earn verifiable rewards

- 4.gain permanent digital ownership of achievements via NFTs.

## 📑 Table of Contents
- [Project Vision](#-project-vision)
- [What Can Users Do?](#-what-can-users-do)
- [Core Features](#-core-features)
- [UI & User Experience](#-ui--user-experience)
- [- [Tech Stack](#-tech-stack)]
- [Why This Project Stands Out (MLSA Impact)](#-why-this-project-stands-out-mlsa-impact)

## 🎮 About

Expand All @@ -10,6 +33,21 @@ MLSA Cards is a gamified learning platform that combines education with blockcha
- **Collect Cards**: Purchase unique skill cards from the store using earned points
- **Mint NFTs**: Convert owned cards into blockchain-backed NFTs with permanent ownership
- **Hybrid Authentication**: Login with Google OAuth for easy access or MetaMask for Web3 natives
## 🎮 What Can Users Do?
🧠 Solve Problems
- Answer curated questions across multiple categories (Programming, CS, Web Dev, AI/ML, Science, Geography, etc.)

⭐ Earn & Track Points
- Real-time scoring, accuracy tracking, streaks, and level progression

🃏 Collect Skill Cards
- Spend earned points to purchase collectible cards with defined rarity

🎨 Mint NFTs
- Convert owned cards into ERC-721 NFTs backed by blockchain verification

🔐 Hybrid Authentication
- Login via Google OAuth or MetaMask with optional wallet linking

## ✨ Features

Expand Down Expand Up @@ -37,6 +75,16 @@ MLSA Cards is a gamified learning platform that combines education with blockcha
- IPFS metadata storage via Pinata
- ERC-721 compliant smart contract
- Transaction history and on-chain verification

## 🖥 UI & User Experience

- Designed using Figma and implemented with a cslean, responsive UI.
- Key interfaces include:
- Authentication (Login / Signup)
- User Dashboard (stats, points, cards, level)
- Quiz Interface (category-based questions)
- Card Marketplace
- NFT Collection with blockchain links

## 🛠 Tech Stack

Expand Down Expand Up @@ -65,7 +113,7 @@ MLSA Cards is a gamified learning platform that combines education with blockcha
## 📋 Prerequisites

- **Node.js** 18+ and npm
- **Python** 3.10+
- **Python** Python 3.10–3.11 required. Python 3.13 not supported.
- **MetaMask** browser extension
- **Google OAuth Credentials** (for OAuth login)
- **Pinata Account** (for IPFS storage)
Expand Down Expand Up @@ -323,3 +371,10 @@ This project is licensed under the MIT License.
- Verify redirect URI matches exactly: `http://localhost:8000/auth/google/callback`
- Check CLIENT_ID and CLIENT_SECRET are correct
- Ensure OAuth consent screen is configured
## 🌟 Why This Project Stands Out (MLSA Impact)

- Demonstrates real-world Web3 integration
- Implements secure hybrid authentication
- Uses production-style backend architecture
- Encourages learning through gamification
- Built via collaborative, contribution-based development
3 changes: 3 additions & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ httpx==0.27.0
pydantic==2.6.3
requests==2.31.0
pytest==8.1.1
itsdangerous
sqlalchemy
authlib
44 changes: 40 additions & 4 deletions backend/static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ let signer;
let wallet;
let token;
let currentChainId;
let providerEventsBound = false;

const networkNames = {
"31337": "Hardhat (Localhost)",
Expand All @@ -40,6 +41,43 @@ function showStatus(message, type = "info") {
statusEl.appendChild(badge);
}

function applyNetworkDisplay(chainIdValue) {
currentChainId = chainIdValue;
chainIdEl.textContent = currentChainId;
networkNameEl.textContent = networkNames[currentChainId] || "Unknown Network";
}

function bindProviderEvents() {
if (!window.ethereum || providerEventsBound) return;
// Keep UI in sync with MetaMask changes.
window.ethereum.on("chainChanged", (chainIdHex) => {
const parsed = parseInt(chainIdHex, 16).toString();
applyNetworkDisplay(parsed);
// Full reload guarantees fresh provider/signer/token state.
window.location.reload();
});

window.ethereum.on("accountsChanged", (accounts) => {
if (!accounts || !accounts.length) {
wallet = undefined;
walletEl.textContent = "Not connected";
signinBtn.disabled = true;
mintBtn.disabled = true;
showStatus("Please connect MetaMask", "error");
return;
}
wallet = accounts[0];
walletEl.textContent = wallet;
// Token remains tied to the prior account; require fresh sign-in.
token = undefined;
signinBtn.disabled = false;
mintBtn.disabled = true;
showStatus("Account changed. Please sign in again.", "info");
});

providerEventsBound = true;
}

function updateMintPreview() {
const name = nameInput.value;
const desc = descInput.value;
Expand All @@ -60,17 +98,15 @@ async function connect() {
showStatus("MetaMask not found", "error");
return;
}
bindProviderEvents();
provider = new ethers.BrowserProvider(window.ethereum);
const accounts = await provider.send("eth_requestAccounts", []);
wallet = accounts[0];
signer = await provider.getSigner();

const network = await provider.getNetwork();
currentChainId = network.chainId.toString();

applyNetworkDisplay(network.chainId.toString());
walletEl.textContent = wallet;
chainIdEl.textContent = currentChainId;
networkNameEl.textContent = networkNames[currentChainId] || "Unknown Network";
connectionInfoEl.style.display = "grid";
signinBtn.disabled = false;
showStatus("Wallet connected", "connected");
Expand Down
6 changes: 4 additions & 2 deletions smart-contracts/scripts/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ async function main() {

const GameCollectible = await hre.ethers.getContractFactory("GameCollectible");
const gameCollectible = await GameCollectible.deploy(name, symbol, baseURI);
await gameCollectible.deployed();
await gameCollectible.waitForDeployment();

console.log("GameCollectible deployed to:", gameCollectible.address);

const address = await gameCollectible.getAddress();
console.log("GameCollectible deployed to:", address);
}

main().catch((error) => {
Expand Down