diff --git a/app/api/generate-certificate/route.ts b/app/api/generate-certificate/route.ts new file mode 100644 index 00000000..66916e68 --- /dev/null +++ b/app/api/generate-certificate/route.ts @@ -0,0 +1,54 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { PDFDocument } from 'pdf-lib'; + +const courseMapping: Record = { + 'avalanche-fundamentals': 'Avalanche Fundamentals', +}; + +function getCourseName(courseId: string): string { + return courseMapping[courseId] || courseId; +} + +export async function POST(req: NextRequest) { + try { + const { courseId, userName } = await req.json(); + if (!courseId || !userName) { return NextResponse.json({ error: 'Missing required fields' }, { status: 400 }); } + const courseName = getCourseName(courseId); + const protocol = req.headers.get('x-forwarded-proto') || 'http'; + const host = req.headers.get('host') || 'localhost:3000'; + const serverUrl = `${protocol}://${host}`; + const templateUrl = `${serverUrl}/certificates/AvalancheAcademy_Certificate.pdf`; + const templateResponse = await fetch(templateUrl); + + if (!templateResponse.ok) { throw new Error(`Failed to fetch template`); } + + const templateArrayBuffer = await templateResponse.arrayBuffer(); + const pdfDoc = await PDFDocument.load(templateArrayBuffer); + const form = pdfDoc.getForm(); + + try { + // fills the form fields in our certificate template + form.getTextField('FullName').setText(userName); + form.getTextField('Class').setText(courseName); + form.getTextField('Awarded').setText(new Date().toLocaleDateString('en-US', { day: 'numeric', month: 'short', year: 'numeric' })); + form.getTextField('Id').setText(Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)); + } catch (error) { + throw new Error('Failed to fill form fields'); + } + + form.flatten(); + const pdfBytes = await pdfDoc.save(); + return new NextResponse(pdfBytes, { + status: 200, + headers: { + 'Content-Type': 'application/pdf', + 'Content-Disposition': `attachment; filename=${courseId}_certificate.pdf`, + }, + }); + } catch (error) { + return NextResponse.json( + { error: 'Failed to generate certificate, contact the Avalanche team.', details: (error as Error).message }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/components/certificates.tsx b/components/certificates.tsx new file mode 100644 index 00000000..b1cec42e --- /dev/null +++ b/components/certificates.tsx @@ -0,0 +1,241 @@ +"use client"; +import React, { useState, useEffect } from 'react'; +import { getQuizResponse } from '@/utils/indexedDB'; +import { buttonVariants } from '@/components/ui/button'; +import { cn } from '@/utils/cn'; +import quizDataImport from '@/components/quizzes/quizData.json'; +import Quiz from '@/components/quizzes/quiz'; +import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; +import { Linkedin, Twitter, Award, Share2 } from 'lucide-react'; + +interface CertificatePageProps { + courseId: string; +} + +interface QuizInfo { + id: string; + chapter: string; + question: string; +} + +interface QuizData { + question: string; + options: string[]; + correctAnswers: number[]; + hint: string; + explanation: string; + chapter: string; +} + +interface Course { + title: string; + quizzes: string[]; +} + +interface QuizDataStructure { + courses: { + [courseId: string]: Course; + }; + quizzes: { + [quizId: string]: QuizData; + }; +} + +const quizData = quizDataImport as QuizDataStructure; + +const CertificatePage: React.FC = ({ courseId }) => { + const [completedQuizzes, setCompletedQuizzes] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [userName, setUserName] = useState(''); + const [isGenerating, setIsGenerating] = useState(false); + const [quizzes, setQuizzes] = useState([]); + + useEffect(() => { + const fetchQuizzes = () => { + const courseQuizzes = quizData.courses[courseId]?.quizzes || []; + const quizzesWithChapters = courseQuizzes.map(quizId => ({ + id: quizId, + chapter: quizData.quizzes[quizId]?.chapter || 'Unknown Chapter', + question: quizData.quizzes[quizId]?.question || '' + })); + setQuizzes(quizzesWithChapters); + }; + + fetchQuizzes(); + }, [courseId]); + + useEffect(() => { + const checkQuizCompletion = async () => { + const completed = await Promise.all( + quizzes.map(async (quiz) => { + const response = await getQuizResponse(quiz.id); + return response && response.isCorrect ? quiz.id : null; + }) + ); + setCompletedQuizzes(completed.filter((id): id is string => id !== null)); + setIsLoading(false); + }; + + if (quizzes.length > 0) { + checkQuizCompletion(); + } + }, [quizzes]); + + const allQuizzesCompleted = completedQuizzes.length === quizzes.length; + + const generateCertificate = async () => { + if (!userName.trim()) { + alert('Please enter your name'); + return; + } + + setIsGenerating(true); + try { + const response = await fetch('/api/generate-certificate', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + courseId, + userName, + }), + }); + + if (!response.ok) { + throw new Error('Failed to generate certificate'); + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = url; + a.download = `${courseId}_certificate.pdf`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + } catch (error) { + console.error('Error generating certificate:', error); + alert('Failed to generate certificate. Please try again.'); + } finally { + setIsGenerating(false); + } + }; + + const chapters = [...new Set(quizzes.map(quiz => quiz.chapter))]; + + const quizzesByChapter = chapters.reduce((acc, chapter) => { + acc[chapter] = quizzes.filter(quiz => quiz.chapter === chapter); + return acc; + }, {} as Record); + + const shareOnLinkedIn = () => { + const url = `https://www.linkedin.com/in/eckardt/edit/forms/certification/new/?isFromA2p=true&name=Avalanche%20Fundamentals&organizationId=19104188&organizationName=Avalanche`; + window.open(url, '_blank'); + }; + + const shareOnTwitter = () => { + const text = `I just completed the ${quizData.courses[courseId].title} course on Avalanche Academy!`; + const url = `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(window.location.href)}`; + window.open(url, '_blank'); + }; + + if (isLoading) { + return
Loading...
; + } + + return ( +
+ {chapters.map((chapter) => { + const chapterQuizzes = quizzesByChapter[chapter]; + const incompleteQuizzes = chapterQuizzes.filter(quiz => !completedQuizzes.includes(quiz.id)); + + if (incompleteQuizzes.length === 0) return null; + + return ( +
+

{chapter}

+ + {incompleteQuizzes.map((quiz) => ( + + + + ))} + +
+ ); + })} + + {allQuizzesCompleted && ( +
+
+ +

Congratulations!

+
+

+ You've completed all quizzes for the {quizData.courses[courseId].title} course. Claim your certificate now! +

+
+ + setUserName(e.target.value)} + className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white" + placeholder="John Doe" + /> +
+ +
+

+ Share your achievement: +

+
+ + +
+
+
+ )} + {!allQuizzesCompleted && ( +
+ + Complete all quizzes to unlock your certificate and share your achievement! +
+ )} +
+ ); +}; + +export default CertificatePage; \ No newline at end of file diff --git a/components/quiz.tsx b/components/quiz.tsx deleted file mode 100644 index c26f7634..00000000 --- a/components/quiz.tsx +++ /dev/null @@ -1,224 +0,0 @@ -"use client"; -import React, { useState, useEffect } from 'react'; -import { v4 as uuidv4 } from 'uuid'; -import { saveQuizResponse, getQuizResponse, resetQuizResponse } from '@/utils/indexedDB'; -import Image from 'next/image'; -import { cn } from '@/utils/cn'; -import { buttonVariants } from '@/components/ui/button'; - -interface QuizProps { - quizId: string; - question: string; - options: string[]; - correctAnswers: number[]; // Changed to number[] to represent indices - hint: string; - explanation: string; -} - -const Quiz: React.FC = ({ - quizId, - question, - options, - correctAnswers = [], - hint, - explanation -}) => { - const [selectedAnswers, setSelectedAnswers] = useState([]); // Changed to number[] - const [isAnswerChecked, setIsAnswerChecked] = useState(false); - const [isCorrect, setIsCorrect] = useState(false); - const [isClient, setIsClient] = useState(false); - - const isSingleAnswer = correctAnswers.length === 1; - - useEffect(() => { - setIsClient(true); - loadSavedResponse(); - }, [quizId]); - - const loadSavedResponse = async () => { - const savedResponse = await getQuizResponse(quizId); - if (savedResponse) { - setSelectedAnswers(savedResponse.selectedAnswers || []); - setIsAnswerChecked(savedResponse.isAnswerChecked || false); - setIsCorrect(savedResponse.isCorrect || false); - } else { - resetQuizState(); - } - }; - - const resetQuizState = () => { - setSelectedAnswers([]); - setIsAnswerChecked(false); - setIsCorrect(false); - }; - - const handleAnswerSelect = (index: number) => { - if (!isAnswerChecked) { - if (isSingleAnswer) { - setSelectedAnswers([index]); - } else { - setSelectedAnswers(prev => - prev.includes(index) - ? prev.filter(a => a !== index) - : [...prev, index] - ); - } - } - }; - - const checkAnswer = async () => { - if (selectedAnswers.length > 0 && correctAnswers.length > 0) { - const correct = isSingleAnswer - ? selectedAnswers[0] === correctAnswers[0] - : selectedAnswers.length === correctAnswers.length && - selectedAnswers.every(answer => correctAnswers.includes(answer)); - setIsCorrect(correct); - setIsAnswerChecked(true); - - await saveQuizResponse(quizId, { - selectedAnswers, - isAnswerChecked: true, - isCorrect: correct, - }); - } - }; - - const handleTryAgain = async () => { - await resetQuizResponse(quizId); - resetQuizState(); - }; - - const renderAnswerFeedback = () => { - if (isAnswerChecked) { - if (isCorrect) { - return ( -
-
- - - - Correct -
-

{explanation}

-
- ); - } else { - return ( -
-
- - - - Not Quite -
-

Hint: {hint}

-
- ); - } - } - return null; - }; - - if (!isClient) { - return
Loading...
; - } - - if (correctAnswers.length === 0) { - return ( -
-

Error: No correct answers provided for this quiz.

-
- ); - } - - return ( -
-
-
-
- Quiz topic -
-

Time for a Quiz!

-

- Wolfie wants to test your knowledge. {isSingleAnswer ? "Select the correct answer." : "Select all correct answers."} -

-
-
-
-

{question}

-
-
- {options.map((option, index) => ( -
handleAnswerSelect(index)} - > - - {isSingleAnswer - ? String.fromCharCode(65 + index) - : (selectedAnswers.includes(index) ? '✓' : '')} - - {option} -
- ))} -
- {renderAnswerFeedback()} -
-
- {!isAnswerChecked ? ( - - ) : ( - !isCorrect && ( - - ) - )} -
-
-
- ); -}; - -export default Quiz; \ No newline at end of file diff --git a/components/QuizProgress.tsx b/components/quizzes/QuizProgress.tsx similarity index 66% rename from components/QuizProgress.tsx rename to components/quizzes/QuizProgress.tsx index d82bc5b0..01c364fe 100644 --- a/components/QuizProgress.tsx +++ b/components/quizzes/QuizProgress.tsx @@ -1,35 +1,41 @@ "use client"; import React, { useState, useEffect } from 'react'; -import { getQuizProgress, isEligibleForCertificate } from '../utils/quizProgress'; +import { getQuizResponse } from '@/utils/indexedDB'; +import quizData from './quizData.json'; -interface QuizProgressProps { - quizIds: string[]; -} - -const QuizProgress: React.FC = ({ quizIds }) => { +const QuizProgress: React.FC = () => { const [progress, setProgress] = useState<{ [quizId: string]: boolean }>({}); const [isLoading, setIsLoading] = useState(true); useEffect(() => { async function loadProgress() { - const quizProgress = await getQuizProgress(quizIds); + const quizIds = Object.keys(quizData.quizzes); + const quizProgress: { [quizId: string]: boolean } = {}; + + for (const quizId of quizIds) { + const response = await getQuizResponse(quizId); + quizProgress[quizId] = response ? response.isCorrect : false; + } + setProgress(quizProgress); setIsLoading(false); } loadProgress(); - }, [quizIds]); + }, []); if (isLoading) { return
Loading progress...
; } - const eligibleForCertificate = isEligibleForCertificate(progress); + const totalQuizzes = Object.keys(quizData.quizzes).length; + const completedQuizzes = Object.values(progress).filter(Boolean).length; + const eligibleForCertificate = completedQuizzes === totalQuizzes; return (

Quiz Progress

    - {quizIds.map((quizId) => ( + {Object.entries(quizData.quizzes).map(([quizId, quizInfo]) => (
  • Quiz {quizId}: {progress[quizId] ? 'Completed' : 'Not completed'} diff --git a/components/quizzes/quiz.tsx b/components/quizzes/quiz.tsx new file mode 100644 index 00000000..a958e544 --- /dev/null +++ b/components/quizzes/quiz.tsx @@ -0,0 +1,216 @@ +"use client"; +import React, { useState, useEffect } from 'react'; +import { v4 as uuidv4 } from 'uuid'; +import { saveQuizResponse, getQuizResponse, resetQuizResponse } from '@/utils/indexedDB'; +import Image from 'next/image'; +import { cn } from '@/utils/cn'; +import { buttonVariants } from '@/components/ui/button'; +import quizData from './quizData.json'; + +interface QuizProps { + quizId: string; +} + +interface QuizData { + question: string; + options: string[]; + correctAnswers: number[]; + hint: string; + explanation: string; +} + +const Quiz: React.FC = ({ quizId }) => { + const [quizInfo, setQuizInfo] = useState(null); + const [selectedAnswers, setSelectedAnswers] = useState([]); + const [isAnswerChecked, setIsAnswerChecked] = useState(false); + const [isCorrect, setIsCorrect] = useState(false); + const [isClient, setIsClient] = useState(false); + + useEffect(() => { + setIsClient(true); + const fetchedQuizInfo = quizData.quizzes[quizId as keyof typeof quizData.quizzes]; + if (fetchedQuizInfo) { + setQuizInfo(fetchedQuizInfo); + } + loadSavedResponse(); + }, [quizId]); + + const loadSavedResponse = async () => { + const savedResponse = await getQuizResponse(quizId); + if (savedResponse) { + setSelectedAnswers(savedResponse.selectedAnswers || []); + setIsAnswerChecked(savedResponse.isAnswerChecked || false); + setIsCorrect(savedResponse.isCorrect || false); + } else { + resetQuizState(); + } + }; + + const resetQuizState = () => { + setSelectedAnswers([]); + setIsAnswerChecked(false); + setIsCorrect(false); + }; + + const handleAnswerSelect = (index: number) => { + if (!isAnswerChecked) { + if (quizInfo && quizInfo.correctAnswers.length === 1) { + setSelectedAnswers([index]); + } else { + setSelectedAnswers(prev => + prev.includes(index) + ? prev.filter(a => a !== index) + : [...prev, index] + ); + } + } + }; + + const checkAnswer = async () => { + if (quizInfo && selectedAnswers.length > 0 && quizInfo.correctAnswers.length > 0) { + const correct = quizInfo.correctAnswers.length === 1 + ? selectedAnswers[0] === quizInfo.correctAnswers[0] + : selectedAnswers.length === quizInfo.correctAnswers.length && + selectedAnswers.every(answer => quizInfo.correctAnswers.includes(answer)); + setIsCorrect(correct); + setIsAnswerChecked(true); + + await saveQuizResponse(quizId, { + selectedAnswers, + isAnswerChecked: true, + isCorrect: correct, + }); + } + }; + + const handleTryAgain = async () => { + await resetQuizResponse(quizId); + resetQuizState(); + }; + + const renderAnswerFeedback = () => { + if (isAnswerChecked && quizInfo) { + if (isCorrect) { + return ( +
    +
    + + + + Correct +
    +

    {quizInfo.explanation}

    +
    + ); + } else { + return ( +
    +
    + + + + Not Quite +
    +

    Hint: {quizInfo.hint}

    +
    + ); + } + } + return null; + }; + + if (!isClient || !quizInfo) { + return
    Loading...
    ; + } + + return ( +
    +
    +
    +
    + Quiz topic +
    +

    Time for a Quiz!

    +

    + Wolfie wants to test your knowledge. {quizInfo.correctAnswers.length === 1 ? "Select the correct answer." : "Select all correct answers."} +

    +
    +
    +
    +

    {quizInfo.question}

    +
    +
    + {quizInfo.options.map((option, index) => ( +
    handleAnswerSelect(index)} + > + + {quizInfo.correctAnswers.length === 1 + ? String.fromCharCode(65 + index) + : (selectedAnswers.includes(index) ? '✓' : '')} + + {option} +
    + ))} +
    + {renderAnswerFeedback()} +
    +
    + {!isAnswerChecked ? ( + + ) : ( + !isCorrect && ( + + ) + )} +
    +
    +
    + ); +}; + +export default Quiz; \ No newline at end of file diff --git a/components/quizzes/quizData.json b/components/quizzes/quizData.json new file mode 100644 index 00000000..3bdd93a1 --- /dev/null +++ b/components/quizzes/quizData.json @@ -0,0 +1,222 @@ +{ + "courses": { + "avalanche-fundamentals": { + "title": "Avalanche Fundamentals", + "quizzes": ["101", "102", "103", "104", "105", "106", "107", "108", "109", "110", "111", "112", "113", "114", "115", "116", "117"] + } + }, + "quizzes": { + "101": { + "question": "What is the underlying principle of the Avalanche Consensus family?", + "options": [ + "Repeated Sub-Sampling", + "Centralized Election", + "Randomly choosing a Validator that decides on the next State", + "Proof of Work" + ], + "correctAnswers": [0], + "hint": "Think about how Avalanche Consensus achieves consensus.", + "explanation": "The underlying principle of the Avalanche Consensus family is Repeated Sub-Sampling. This means that validators repeatedly sample the network to reach consensus.", + "chapter": "Primer on Avalanche Consensus" + }, + "102": { + "question": "What is the role of validators in the event of conflicting transactions?", + "options": [ + "Validators choose the transaction that benefits them the most.", + "Validators automatically reject all conflicting transactions.", + "Validators collectively decide on which of the two conflicting transactions will be accepted by all validators and determine the next state.", + "Validators create a new transaction to resolve the conflict." + ], + "correctAnswers": [2], + "hint": "Think about how validators resolve conflicts in a blockchain network.", + "explanation": "In the event of conflicting transactions, validators have to collectively decide on which of the two conflicting transactions will be accepted by all validators and determine the next state. They do not act based on personal benefits, they don't reject all conflicting transactions, and they don't create new transactions to resolve conflicts.", + "chapter": "Primer on Avalanche Consensus" + }, + "103": { + "question": "What is a Double Spending Attack in the context of blockchain?", + "options": [ + "It is when a user attempts to spend more cryptocurrency than they own by creating multiple transactions that reference the same funds.", + "It is when a user tries to double the amount of cryptocurrency they own through fraudulent transactions.", + "It is when a user performs two transactions at the exact same time to exploit the system.", + "It is when a validator duplicates transactions to increase their validation rewards." + ], + "correctAnswers": [0], + "hint": "Think about how a user would attempt to spend more funds than they hold.", + "explanation": "A Double Spending Attack is when a user attempts to spend more cryptocurrency than they own by creating multiple transactions that reference the same funds. This kind of attack is a threat to the integrity of the blockchain system.", + "chapter": "Primer on Avalanche Consensus" + }, + "104": { + "question": "In the Avalanche Consensus protocol, what determines whether a validator changes its preference?", + "options": [ + "A simple majority of sampled validators", + "An α-majority of sampled validators", + "A unanimous decision from sampled validators", + "The validator's initial random choice" + ], + "correctAnswers": [1], + "hint": "Think about the concept of 'α-majority' mentioned in the chapter.", + "explanation": "Avalanche consensus dictates that a validator changes its preference if an α-majority of the sampled validators agrees on another option. The α-majority is a key concept in the protocol, allowing for flexible decision-making based on the sampled subset of validators.", + "chapter": "Primer on Avalanche Consensus" + }, + "105": { + "question": "When does a validator in Avalanche finalize its decision?", + "options": [ + "After a set number of rounds of querying and getting majority consensus.", + "After the preference is confirmed by the α-majority for β (Decision Threshold) consecutive rounds.", + "As soon as a conflict between transactions arises.", + "When all validators in the system have replied with their preference." + ], + "correctAnswers": [1], + "hint": "Think about the process of finalizing a decision in Avalanche Consensus.", + "explanation": "In Avalanche, a validator finalizes its decision after its preference is confirmed by the α-majority for β (Decision Threshold) consecutive rounds. It's not a single round process, it doesn't happen immediately when a conflict arises, and it doesn't require replies from all validators in the system.", + "chapter": "Primer on Avalanche Consensus" + }, + "106": { + "question": "What is the primary purpose of an L1 within the Avalanche network?", + "options": [ + "Increasing token value", + "Mining cryptocurrency", + "Enabling specialized blockchain use cases" + ], + "correctAnswers": [2], + "hint": "Think about how L1s are designed to be customizable and optimized for specific use cases.", + "explanation": "The primary purpose of an L1 within the Avalanche network is to enable specialized blockchain use cases. Each L1 is designed to be optimized for specific use cases, thereby boosting the network's overall performance.", + "chapter": "Multi-Chain Architecture" + }, + "107": { + "question": "The addition of a new decentralized application (dApp) on a single-chain system causes more competition over the block space of that chain.", + "options": [ + "True", + "False" + ], + "correctAnswers": [0], + "hint": "Think about how each new dApp vies for the same block space in a single-chain system.", + "explanation": "Each new dApp vies for the same block space in a single-chain system, leading to unnecessary overcrowding of the chain. Multi-chain systems alleviate this issue.", + "chapter": "Multi-Chain Architecture" + }, + "108": { + "question": "In case of a security breach on the Ethereum mainnet, all Layer 2 solutions are potentially affected.", + "options": [ + "True", + "False" + ], + "correctAnswers": [0], + "hint": "Think about how Layer 2 solutions delegate security to the Ethereum mainnet.", + "explanation": "Layer 2 solutions delegate security to the Ethereum mainnet. Hence, a security breach on the mainnet could potentially affect all Layer 2 solutions.", + "chapter": "Multi-Chain Architecture" + }, + "109": { + "question": "In the soda dispenser analogy, what does the 'state' of the machine represent? (Select all that apply)", + "options": [ + "The soda flavors", + "The current balance", + "The number of cans available per flavor", + "The location of the machine" + ], + "correctAnswers": [1, 2], + "hint": "Think about what information the machine needs to keep track of to function properly.", + "explanation": "In the soda dispenser analogy, the 'state' of the machine represents the current balance, total revenue, and the number of cans available per brand.", + "chapter": "Virtual Machines & Blockchains" + }, + "110": { + "question": "In the soda dispenser analogy, what of the following are operations of the machine? (Select all that apply)", + "options": [ + "Inserting coins", + "The current balance", + "The location of the machine", + "Selecting a soda flavour" + ], + "correctAnswers": [0, 3], + "hint": "Think about what actions a user can take when interacting with the machine.", + "explanation": "In the soda dispenser analogy, the operations of the machine include inserting coins and selecting a soda flavor.", + "chapter": "Virtual Machines & Blockchains" + }, + "111": { + "question": "What are the advantages of implementing state machines? (Select all that apply)", + "options": [ + "Increased network speed", + "Decreased transaction costs", + "Reproducibility", + "Clear interface" + ], + "correctAnswers": [2, 3], + "hint": "Think about how state machines simplify interactions and ensure consistency.", + "explanation": "State machines, like the virtual machines in a blockchain, have a clear interface that makes it straightforward to interact with them. They are also reproducible, meaning multiple identical instances can be created.", + "chapter": "Virtual Machines & Blockchains" + }, + "112": { + "question": "In blockchain systems, what role do validators play? (Select all that apply)", + "options": [ + "They reach consensus on the sequence in which transactions are carried out", + "They determine the prices of the digital assets", + "They regulate the blockchain's electricity usage", + "They operate one or more instances of the virtual machines" + ], + "correctAnswers": [0, 3], + "hint": "Think about the role validators play in maintaining the blockchain's integrity.", + "explanation": "Validators in blockchain systems operate one or more instances of virtual machines and reach consensus on the sequence in which transactions are carried out.", + "chapter": "Virtual Machines & Blockchains" + }, + "113": { + "question": "How do the validators make sure that they all have the same view on the state?", + "options": [ + "By assigning each validator a unique part of the blockchain to monitor", + "Through the execution of operations on the local instance of the VM by all validators in the same order", + "Through the manual checking of each transaction by a centralized authority" + ], + "correctAnswers": [1], + "hint": "Think about how validators ensure that they all have the same view of the blockchain's state.", + "explanation": "Validators ensure they all have the same view on the state by executing operations on their local instance of the VM in the same order. This ensures consistency across the network.", + "chapter": "Virtual Machines & Blockchains" + }, + "114": { + "question": "What is a Virtual Machine (VM) in the context of blockchain?", + "options": [ + "A decentralized computer that can execute a program in a controlled environment", + "A physical machine that runs a blockchain network", + "A machine that dispenses soda" + ], + "correctAnswers": [0], + "hint": "Think about how a VM operates in a blockchain system.", + "explanation": "A Virtual Machine (VM) in the context of blockchain is like a decentralized computer that can execute a program in a controlled environment. It defines the application-level logic of a blockchain.", + "chapter": "Virtual Machines & Blockchains" + }, + "115": { + "question": "How many VMs can Avalanche run?", + "options": [ + "Only the Avalanche Virtual Machine", + "EVM and AVM", + "There is no limit to how many virtual machines can run on Avalanche. Everyone can create a modified VM catering best to their needs" + ], + "correctAnswers": [2], + "hint": "Think about the flexibility of Avalanche's architecture.", + "explanation": "Avalanche is designed to be highly flexible, allowing for an unlimited number of custom Virtual Machines to be created and run on the network.", + "chapter": "Virtual Machine Customization" + }, + "116": { + "question": "Can a Virtual Machine (VM) be used to create multiple blockchains?", + "options": [ + "Yes, the same VM can be used to create multiple blockchains", + "No, each blockchain requires a unique VM", + "Yes, but only if the blockchains are part of different networks" + ], + "correctAnswers": [0], + "hint": "Think about how a VM can be customized to create different blockchains.", + "explanation": "You can think of a Virtual Machine (VM) as a blueprint for a blockchain, where the same VM can be used to create multiple blockchains. Each of these blockchains adheres to the same rules but remains logically independent from the others.", + "chapter": "Virtual Machine Customization" + }, + "117": { + "question": "How does Avalanche handle the modification of Virtual Machines (VMs)?", + "options": [ + "Customization is challenging and requires a wide consensus among network participants.", + "Customization is not allowed as it can compromise the security of the blockchain.", + "Avalanche offers an easy API for VM developers.", + "Avalanche does not support customization of VMs." + ], + "correctAnswers": [2], + "hint": "Think about how Avalanche allows developers to modify Virtual Machines.", + "explanation": "Unlike one-chain-fits all systems, which requires a wide consensus to make changes, Avalanche allows for straightforward customization of Virtual Machines, making it more adaptable to unique use cases.", + "chapter": "Virtual Machine Customization" + } + } +} \ No newline at end of file diff --git a/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/01-avalanche-consesus-intro.mdx b/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/01-avalanche-consensus-intro.mdx similarity index 57% rename from content/course/avalanche-fundamentals/02-avalanche-consensus-intro/01-avalanche-consesus-intro.mdx rename to content/course/avalanche-fundamentals/02-avalanche-consensus-intro/01-avalanche-consensus-intro.mdx index f0e20f08..773599dd 100644 --- a/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/01-avalanche-consesus-intro.mdx +++ b/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/01-avalanche-consensus-intro.mdx @@ -10,19 +10,7 @@ icon: Book Avalanche Consensus is a novel consensus protocol that is used in the Avalanche network. It is a leaderless, decentralized, and scalable consensus protocol that is used to achieve consensus on the state of the network. - + ## What You Will Learn diff --git a/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/02-consensus-mechanisms.mdx b/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/02-consensus-mechanisms.mdx index 36798162..84b0eb21 100644 --- a/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/02-consensus-mechanisms.mdx +++ b/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/02-consensus-mechanisms.mdx @@ -16,19 +16,7 @@ Consensus is needed to agree on the order of state changes in a blockchain. This ![](/course-images/avalanche-fundamentals/1.png) - + ## Double Spending Attack @@ -53,16 +41,4 @@ Each of the transaction in itself is valid. Alice owns 5 AVAX and should be able Therefore, validators have to collectively come to consensus on which of the two conflicting transactions will be included in the blockchain first, and therefore be accepted by all validators as the next state. To illustrate, we will color the validators preferring Charlie yellow and the validators preferring Bob blue. - + diff --git a/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/03-snowman-consensus.mdx b/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/03-snowman-consensus.mdx index 18a50f40..db262fb0 100644 --- a/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/03-snowman-consensus.mdx +++ b/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/03-snowman-consensus.mdx @@ -24,19 +24,7 @@ Let's set the alpha value to 3 in our example, meaning that we change our prefer From now on you will respond with blue, when another validators queries you for your current preference. - + ## Consecutive Successes @@ -68,16 +56,4 @@ In the common case when a transaction has no conflicts, finalization happens ver Avalanche Consensus guarantees (with high probability based on system parameters) that if any honest validator accepts a transaction, all honest validators will come to the same conclusion. - + diff --git a/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/02-subnet.mdx b/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/02-subnet.mdx index c6abe4f7..eeacc88b 100644 --- a/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/02-subnet.mdx +++ b/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/02-subnet.mdx @@ -16,15 +16,4 @@ Each blockchain belongs to exactly one Avalanche L1, but an Avalanche L1 can val The tailored, application-specific blockchains of Avalanche L1s offer a solution to these challenges and many others, moving away from the monolithic blockchain structure of the past. - \ No newline at end of file + \ No newline at end of file diff --git a/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/03-benefits.mdx b/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/03-benefits.mdx index 12ef86bb..4322fe01 100644 --- a/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/03-benefits.mdx +++ b/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/03-benefits.mdx @@ -31,17 +31,7 @@ While the unified, or monolithic, blockchain design does offer certain advantage Coming back to our highway analogy, we can imagine a scenario where congestion build up on one highway. The other highways are not directly affected. Some cars may choose to take a different highway and the traffic bottleneck may decrease. - + ## Customizability @@ -55,14 +45,4 @@ In our highway analogy, let's think of some travelers having a very unique requi Avalanche isn't alone in the multi-chain landscape. Other blockchain systems utilizing a similar architectural approach include Cosmos, Polkadot, and Polygon CDK. - + diff --git a/content/course/avalanche-fundamentals/05-vms-and-blockchains/02-state-machine.mdx b/content/course/avalanche-fundamentals/05-vms-and-blockchains/02-state-machine.mdx index e257ff51..359cea02 100644 --- a/content/course/avalanche-fundamentals/05-vms-and-blockchains/02-state-machine.mdx +++ b/content/course/avalanche-fundamentals/05-vms-and-blockchains/02-state-machine.mdx @@ -20,19 +20,7 @@ For optimal functionality, this machine must **consistently monitor its state**. ![](/course-images/avalanche-fundamentals/28.png) - + This dispenser permits several **operations**, such as: - Inserting coins @@ -40,19 +28,7 @@ This dispenser permits several **operations**, such as: User operations trigger **state transitions**. For example, when a user inserts a coin, the balance increases. Selecting a soda brand leads to a decrease of the balance by the soda's price and reduces the quantity of that specific brand by one. - + Additionally, certain logic might be incorporated to dictate various operational outcomes based on the current state. For example, the machine checks if the balance is sufficient when a user chooses a soda brand. As a result, the outcome could be different in cases where the balance is adequate compared to instances where it isn't. @@ -65,16 +41,4 @@ Implementing state machines comes with several advantages: **Reproducibility:** With a Virtual Machine blueprint, one can create multiple identical instances. These behave consistently, implying that if you conduct the same operations in the same sequence on two different machines, the state will remain identical. - \ No newline at end of file + \ No newline at end of file diff --git a/content/course/avalanche-fundamentals/05-vms-and-blockchains/03-blockchains.mdx b/content/course/avalanche-fundamentals/05-vms-and-blockchains/03-blockchains.mdx index 11b05414..1375c07d 100644 --- a/content/course/avalanche-fundamentals/05-vms-and-blockchains/03-blockchains.mdx +++ b/content/course/avalanche-fundamentals/05-vms-and-blockchains/03-blockchains.mdx @@ -10,19 +10,7 @@ Let’s look at how VMs work in blockchain. Each validator operates an instance When a user wishes to execute an operation on this distributed soda dispenser, they transmit the transaction to the network. The validators then reach consensus on the sequence in which transactions are carried out. In Avalanche, validators use Avalanche Consensus, which we talked about earlier. - + Next, each validator executes the operation on their instance of the VM independently. Because each instance of our Virtual Machine behaves identically, validators maintain a uniform view of the machine’s state, so they balance and the number of available soda per flavor. @@ -40,14 +28,4 @@ Each of the validators executes the operations on their own machine. After the f This is the fundamental principle of Blockchains in Avalanche: A collective of validators operate identical virtual machines, come to consensus on the order of transactions, execute them, and have a uniform view on the machine's current state. - \ No newline at end of file + \ No newline at end of file diff --git a/content/course/avalanche-fundamentals/05-vms-and-blockchains/04-variety-of-vm.mdx b/content/course/avalanche-fundamentals/05-vms-and-blockchains/04-variety-of-vm.mdx index 4468055b..f0142785 100644 --- a/content/course/avalanche-fundamentals/05-vms-and-blockchains/04-variety-of-vm.mdx +++ b/content/course/avalanche-fundamentals/05-vms-and-blockchains/04-variety-of-vm.mdx @@ -18,15 +18,4 @@ Even though two blockchains may use the same virtual machine, they each have an The number of blockchains a validator can validate is primarily limited by the additional computational resources required to operate the virtual machines of all the chains. - \ No newline at end of file + \ No newline at end of file diff --git a/content/course/avalanche-fundamentals/06-vm-customization/00-vm-customization.mdx b/content/course/avalanche-fundamentals/06-vm-customization/00-vm-customization.mdx index 49afcd37..85391e3c 100644 --- a/content/course/avalanche-fundamentals/06-vm-customization/00-vm-customization.mdx +++ b/content/course/avalanche-fundamentals/06-vm-customization/00-vm-customization.mdx @@ -20,13 +20,4 @@ In Avalanche, every Avalanche L1 has autonomy over their Virtual Machines. Their In this section, we will see the three way to customize Virtual Machines at a high level. In later courses, we will dive deeper and learn how to actually customize each. - \ No newline at end of file + \ No newline at end of file diff --git a/content/course/avalanche-fundamentals/06-vm-customization/01-configuration.mdx b/content/course/avalanche-fundamentals/06-vm-customization/01-configuration.mdx index dadea8fd..ddc3226c 100644 --- a/content/course/avalanche-fundamentals/06-vm-customization/01-configuration.mdx +++ b/content/course/avalanche-fundamentals/06-vm-customization/01-configuration.mdx @@ -14,31 +14,7 @@ This is a massive advantage over one-chain-fits-all systems, where the parameter Using VM Configuration we can easily create EVM-chains for different use cases such as trading a cheap gaming NFT or valuable Real estate on chain. These blockchains may differ in fees (low fees for cheap NFTs, high fees for valuable goods) and security levels (low security for cheap NFTs, high security for valuable goods). - - - + Examples of the configurable parameters of the subnetEVM include: diff --git a/content/course/avalanche-fundamentals/06-vm-customization/02-modification.mdx b/content/course/avalanche-fundamentals/06-vm-customization/02-modification.mdx index 7c25f97a..5aa586c3 100644 --- a/content/course/avalanche-fundamentals/06-vm-customization/02-modification.mdx +++ b/content/course/avalanche-fundamentals/06-vm-customization/02-modification.mdx @@ -12,17 +12,4 @@ The next more powerful way to customize a VM is VM Modification. To meet our req In Avalanche, creating modified VMs is straightforward. The subnetEVM for instance can be customized through the development of plugins as well as precompiles. - - \ No newline at end of file + \ No newline at end of file diff --git a/content/course/avalanche-fundamentals/get-certificate.mdx b/content/course/avalanche-fundamentals/get-certificate.mdx new file mode 100644 index 00000000..bd91f7e3 --- /dev/null +++ b/content/course/avalanche-fundamentals/get-certificate.mdx @@ -0,0 +1,14 @@ +--- +title: Course Completion Certificate +updated: 2024-09-09 +authors: [ashucoder9] +icon: BadgeCheck +--- + +import CertificatePage from '@/components/certificates'; + +You've made it to the end of the course. Let's check your progress and get your certificate. + + + +Thank you for participating in this course. We hope you found it informative and enjoyable! \ No newline at end of file diff --git a/content/course/avalanche-fundamentals/meta.json b/content/course/avalanche-fundamentals/meta.json index ab94a774..3f284f38 100644 --- a/content/course/avalanche-fundamentals/meta.json +++ b/content/course/avalanche-fundamentals/meta.json @@ -12,6 +12,8 @@ "---Virtual Machines & Blockchains---", "...05-vms-and-blockchains", "---Virtual Machine Customization---", - "...06-vm-customization" + "...06-vm-customization", + "---Course Completion---", + "get-certificate" ] } diff --git a/mdx-components.tsx b/mdx-components.tsx index 84cb0495..a290475f 100644 --- a/mdx-components.tsx +++ b/mdx-components.tsx @@ -10,7 +10,7 @@ import { Pre, } from 'fumadocs-ui/components/codeblock'; import type { ReactNode } from 'react'; -import Quiz from '@/components/quiz' +import Quiz from '@/components/quizzes/quiz' import { Popup, PopupContent, PopupTrigger } from 'fumadocs-ui/twoslash/popup'; import YouTube from '@/components/youtube'; import Gallery from '@/components/gallery'; diff --git a/package.json b/package.json index 77fcfe74..45a81796 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "idb": "^8.0.0", "lucide-react": "^0.408.0", "next": "^14.2.4", + "pdf-lib": "^1.17.1", "react": "^18.3.1", "react-dom": "^18.3.1", "tailwindcss-animate": "^1.0.7", diff --git a/public/certificates/AvalancheAcademy_Certificate.pdf b/public/certificates/AvalancheAcademy_Certificate.pdf new file mode 100644 index 00000000..2e6f49b6 Binary files /dev/null and b/public/certificates/AvalancheAcademy_Certificate.pdf differ diff --git a/yarn.lock b/yarn.lock index 2ca1f0b0..ebfde55b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -351,6 +351,20 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@pdf-lib/standard-fonts@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz#8ba691c4421f71662ed07c9a0294b44528af2d7f" + integrity sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA== + dependencies: + pako "^1.0.6" + +"@pdf-lib/upng@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@pdf-lib/upng/-/upng-1.0.1.tgz#7dc9c636271aca007a9df4deaf2dd7e7960280cb" + integrity sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ== + dependencies: + pako "^1.0.10" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -2707,6 +2721,11 @@ package-json-from-dist@^1.0.0: resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== +pako@^1.0.10, pako@^1.0.11, pako@^1.0.6: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + parse-entities@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.1.tgz#4e2a01111fb1c986549b944af39eeda258fc9e4e" @@ -2761,6 +2780,16 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +pdf-lib@^1.17.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/pdf-lib/-/pdf-lib-1.17.1.tgz#9e7dd21261a0c1fb17992580885b39e7d08f451f" + integrity sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw== + dependencies: + "@pdf-lib/standard-fonts" "^1.0.0" + "@pdf-lib/upng" "^1.0.1" + pako "^1.0.11" + tslib "^1.11.1" + periscopic@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/periscopic/-/periscopic-3.1.0.tgz#7e9037bf51c5855bd33b48928828db4afa79d97a" @@ -3132,16 +3161,7 @@ streamsearch@^1.1.0: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -3167,14 +3187,7 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -3319,6 +3332,11 @@ ts-interface-checker@^0.1.9: resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== +tslib@^1.11.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0: version "2.6.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"