Learn to Earn Demo (#388)

* demo

* more features

* results screen

* quiz questions

* mobile styling

* quiz component

* add nosana quiz

* shuffle quiz questions

* hook up endpoint to quiz

* completed badge and quiz ids

* add step quiz

* hide claim button when quiz is completed

* handle not connected/no account for rewards points

* quiz fixes

* renames

---------

Co-authored-by: Adrian Brzeziński <a.brzezinski94@gmail.com>
This commit is contained in:
saml33 2024-03-04 00:26:11 +11:00 committed by GitHub
parent e872814625
commit d6d2648d97
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 1010 additions and 1 deletions

19
apis/quiz.ts Normal file
View File

@ -0,0 +1,19 @@
export const getCompletedQuizzes = async (wallet: string) => {
try {
const result = await fetch(
`https://api.mngo.cloud/data/v4/user-data/all-completed-quizzes?wallet-pk=${wallet}`,
)
const solved = await result.json()
return solved?.length
? (solved as {
wallet_pk: string
mango_account: string
quiz_id: number
points: number
}[])
: []
} catch (e) {
console.log(e)
return []
}
}

View File

@ -276,7 +276,7 @@ const UserSetupModal = ({
src="/logos/logo-mark.svg"
alt="next"
/>
<div className="absolute left-0 top-0 z-10 flex h-1.5 w-full flex-grow bg-th-bkg-3">
<div className="absolute left-0 top-0 z-10 flex h-1.5 w-full grow bg-th-bkg-3">
<div
style={{
width: `${(showSetupStep / 3) * 100}%`,

406
components/quiz/Quiz.tsx Normal file
View File

@ -0,0 +1,406 @@
import { Quiz as QuizType, QuizQuestion } from 'utils/quiz'
import { useState } from 'react'
import Button, { IconButton } from '@components/shared/Button'
import { useRouter } from 'next/router'
import {
ArrowLeftIcon,
CheckCircleIcon,
ChevronDownIcon,
InformationCircleIcon,
XCircleIcon,
} from '@heroicons/react/20/solid'
import { Disclosure } from '@headlessui/react'
import Image from 'next/image'
import { useWallet } from '@solana/wallet-adapter-react'
import useMangoAccount from 'hooks/useMangoAccount'
import { bs58 } from '@project-serum/anchor/dist/cjs/utils/bytes'
import { useQueryClient } from '@tanstack/react-query'
import { useCompletedQuizzes } from 'hooks/useQuiz'
import { notify } from 'utils/notifications'
type RESULT = {
correctAnswers: number
wrongAnswers: QuizQuestion[]
}
const DEFAULT_RESULT = {
correctAnswers: 0,
wrongAnswers: [],
}
const Quiz = ({ quiz }: { quiz: QuizType }) => {
const router = useRouter()
const queryClient = useQueryClient()
const { connected, publicKey, signMessage } = useWallet()
const { mangoAccountAddress } = useMangoAccount()
const { data: solved } = useCompletedQuizzes(publicKey?.toBase58())
const [currentQuestion, setCurrentQuestion] = useState(0)
const [answerIndex, setAnswerIndex] = useState<number | null>(null)
const [isCorrectAnswer, setIsCorrectAnswer] = useState<boolean | null>(null)
const [result, setResult] = useState<RESULT>(DEFAULT_RESULT)
const [showIntro, setShowIntro] = useState(true)
const [showResult, setShowResult] = useState(false)
const { questions, intro } = quiz
const { question, choices, description, correctAnswer } =
questions[currentQuestion]
const handleAnswer = (answer: string, index: number) => {
setAnswerIndex(index)
if (answer === correctAnswer) {
setIsCorrectAnswer(true)
} else {
setIsCorrectAnswer(false)
}
}
const handleNext = () => {
setAnswerIndex(null)
setResult((prev) =>
isCorrectAnswer
? {
...prev,
correctAnswers: prev.correctAnswers + 1,
}
: {
...prev,
wrongAnswers: [...prev.wrongAnswers, questions[currentQuestion]],
},
)
if (currentQuestion !== questions.length - 1) {
setCurrentQuestion((prev) => prev + 1)
} else {
setCurrentQuestion(0)
setShowResult(true)
}
}
const handleTryAgain = () => {
setResult(DEFAULT_RESULT)
setShowResult(false)
}
const getResultsHeadingText = (score: number) => {
if (!score) {
return 'Whoops 😲'
} else if (score < 50) {
return 'Try Again'
} else if (score < 100) {
return 'Almost There...'
} else return 'Congratulations 🎉'
}
const completeQuiz = async () => {
const message = new TextEncoder().encode(mangoAccountAddress)
const signature = await signMessage!(message)
const rawResponse = await fetch(
'https://api.mngo.cloud/data/v4/user-data/complete-quiz',
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
wallet_pk: publicKey?.toBase58(),
quiz_id: quiz.id,
mango_account: mangoAccountAddress,
signature: bs58.encode(signature),
}),
},
)
await rawResponse.json()
queryClient.invalidateQueries(['completed-quizzes', publicKey?.toBase58()])
notify({
type: 'success',
title: 'Well done!',
description: 'Well done! 50 Rewards Points are on the way',
})
router.push('/learn', undefined, { shallow: true })
}
const canClaimPoints = connected && mangoAccountAddress
return (
<>
<div className="flex h-12 w-full items-center justify-between bg-th-bkg-2 px-4 md:px-6">
<div className="flex items-center">
<IconButton
className="text-th-fgd-3"
hideBg
size="medium"
onClick={() => router.push('/learn', undefined, { shallow: true })}
>
<ArrowLeftIcon className="h-5 w-5" />
</IconButton>
<p className="text-th-fgd-2">{quiz.name} Quiz</p>
</div>
{showIntro || showResult ? null : (
<div className="rounded-full bg-th-bkg-1 px-3 py-1 font-mono">
<span>{currentQuestion + 1}</span>
<span>/{quiz.questions.length}</span>
</div>
)}
</div>
<div className="flex h-0.5 w-full grow bg-th-bkg-1">
<div
style={{
width:
showIntro || showResult
? '0%'
: `${((currentQuestion + 1) / quiz.questions.length) * 100}%`,
opacity: showResult ? 0 : 100,
}}
className="flex bg-th-active transition-all duration-700 ease-out"
/>
</div>
<div className="w-full px-4">
<div className="mx-auto mt-12 w-full max-w-xl rounded-xl bg-th-bkg-2 p-8">
{showIntro ? (
<>
{quiz.imagePath ? (
<Image
className="mx-auto mb-3"
src={quiz.imagePath}
height={48}
width={48}
alt="Quiz Image"
/>
) : null}
<h2 className="mb-2">{intro.title}</h2>
<p className="text-base">{intro.description}</p>
{intro?.docs ? (
<a className="mt-2 block text-base" href={intro.docs.url}>
{intro.docs.linkText}
</a>
) : null}
<Button
className="mt-6"
onClick={() => setShowIntro(false)}
size="large"
>
Let&apos;s Go
</Button>
<div className="mx-auto mt-6 w-max rounded-full border border-th-fgd-4 px-3 py-1">
<p className="text-th-fgd-2">
{!connected
? 'Connect wallet to earn rewards points'
: solved?.find((x) => x.quiz_id === quiz.id)
? 'Rewards Points Claimed'
: mangoAccountAddress
? `Score ${quiz.questions.length}/${quiz.questions.length} to earn rewards points`
: 'Create a Mango Account to earn rewards points'}
</p>
</div>
</>
) : !showResult ? (
<>
<h2 className="leading-tight">{question}</h2>
{description ? <p className="mt-2"></p> : null}
<div className="space-y-1 pt-6">
{choices.map((choice, index) => (
<button
className={`flex w-full items-center justify-between rounded-md bg-th-bkg-3 p-3 text-th-fgd-1 ${
answerIndex === index
? 'border border-th-active'
: 'border border-transparent md:hover:bg-th-bkg-4'
}`}
key={choice}
onClick={() => handleAnswer(choice, index)}
>
<div
className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs ${
answerIndex === index
? 'bg-th-active text-th-bkg-1'
: 'bg-th-bkg-1 text-th-fgd-3'
}`}
>
<span className="font-bold">
{String.fromCharCode(97 + index).toUpperCase()}
</span>
</div>
<span className="mx-6 text-base">{choice}</span>
<div className="h-6 w-6" />
</button>
))}
</div>
<div className="mt-8 flex justify-end">
<Button disabled={answerIndex === null} onClick={handleNext}>
{currentQuestion === questions.length - 1 ? 'Finish' : 'Next'}
</Button>
</div>
</>
) : (
<>
<h2 className="mb-4">
{getResultsHeadingText(
(result.correctAnswers / questions.length) * 100,
)}
</h2>
<p>You scored</p>
<span className="font-display text-5xl text-th-fgd-1">
{((result.correctAnswers / questions.length) * 100).toFixed()}%
</span>
{result.correctAnswers !== questions.length ? (
<div className="mx-auto mt-2 w-max rounded-full border border-th-fgd-4 px-3 py-1">
<p className="text-th-fgd-2">
Try again to earn rewards points.
</p>
</div>
) : null}
<div className="my-6 border-b border-th-bkg-4">
<div className="flex justify-between border-t border-th-bkg-4 p-4">
<div className="flex items-center">
<CheckCircleIcon className="mr-1 h-4 w-4 text-th-success" />
<p>Correct Answers</p>
</div>
<p className="font-mono text-th-fgd-1">
{result.correctAnswers}
</p>
</div>
{result.wrongAnswers?.length ? (
<Disclosure>
{({ open }) => (
<>
<Disclosure.Button
className={`w-full border-t border-th-bkg-4 p-4 text-left focus:outline-none`}
>
<div className="flex items-center justify-between">
<div className="flex items-center">
<XCircleIcon className="mr-1 h-4 w-4 text-th-error" />
<p>Wrong Answers</p>
</div>
<div className="flex items-center space-x-2">
<p className="font-mono text-th-fgd-1">
{result.wrongAnswers.length}
</p>
<ChevronDownIcon
className={`${
open ? 'rotate-180' : 'rotate-0'
} h-6 w-6 shrink-0 text-th-fgd-3`}
/>
</div>
</div>
</Disclosure.Button>
<Disclosure.Panel className="pb-2">
{result.wrongAnswers.map((answer) => (
<div className="mb-2" key={answer.question}>
<Disclosure>
{({ open }) => (
<>
<div
className={`flex items-center justify-between rounded-lg bg-th-bkg-1 p-4 text-left ${
open ? 'rounded-b-none' : ''
}`}
>
<div className="flex items-start">
<XCircleIcon className="mr-1 mt-0.5 h-4 w-4 shrink-0 text-th-error" />
<p className="font-bold text-th-fgd-1">
{answer.question}
</p>
</div>
<Disclosure.Button className="ml-4">
<span className="whitespace-nowrap text-xs">
{open
? 'Hide Answer'
: 'Reveal Answer'}
</span>
</Disclosure.Button>
</div>
<Disclosure.Panel className="rounded-b-lg bg-th-bkg-1 p-4">
{answer.explanation ? (
<div className="mb-4 rounded-lg bg-th-up-muted p-4 text-left">
<div className="flex items-start">
<InformationCircleIcon className="mr-1 mt-0.5 h-4 w-4 shrink-0 text-th-fgd-1" />
<p className="text-th-fgd-1">
{answer.explanation}
</p>
</div>
</div>
) : null}
{answer.choices.map((choice, index) => (
<div
key={choice}
className={`mb-2 flex w-full items-center justify-between rounded-md p-3 text-th-fgd-1 ${
answer.correctAnswer === choice
? 'border border-th-success'
: 'border border-th-bkg-4'
}`}
>
<div
className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs ${
answer.correctAnswer === choice
? 'bg-th-success text-th-bkg-1'
: 'bg-th-bkg-2 text-th-fgd-3'
}`}
>
<span className="font-bold">
{String.fromCharCode(
97 + index,
).toUpperCase()}
</span>
</div>
<span className="mx-6">{choice}</span>
<div className="h-6 w-6" />
</div>
))}
</Disclosure.Panel>
</>
)}
</Disclosure>
</div>
))}
</Disclosure.Panel>
</>
)}
</Disclosure>
) : (
<div className="flex justify-between border-t border-th-bkg-4 p-4">
<div className="flex items-center">
<XCircleIcon className="mr-1 h-4 w-4 text-th-error" />
<p>Wrong Answers</p>
</div>
<p className="font-mono text-th-fgd-1">0</p>
</div>
)}
</div>
<div className="flex justify-center space-x-3">
{solved?.find((x) => x.quiz_id === quiz.id) ||
!canClaimPoints ? (
<Button
onClick={() =>
router.push('/learn', undefined, { shallow: true })
}
size="large"
>
Exit
</Button>
) : result.correctAnswers === questions.length ? (
<Button onClick={completeQuiz} size="large">
Claim Rewards Points
</Button>
) : (
<>
<Button onClick={handleTryAgain} secondary size="large">
Try Again
</Button>
<Button
onClick={() =>
router.push('/learn', undefined, { shallow: true })
}
size="large"
>
Exit
</Button>
</>
)}
</div>
</>
)}
</div>
</div>
</>
)
}
export default Quiz

16
hooks/useQuiz.ts Normal file
View File

@ -0,0 +1,16 @@
import { useQuery } from '@tanstack/react-query'
import { getCompletedQuizzes } from 'apis/quiz'
export const useCompletedQuizzes = (wallet?: string) => {
return useQuery(
['completed-quizzes', wallet],
() => getCompletedQuizzes(wallet!),
{
cacheTime: 1000 * 60 * 10,
staleTime: 1000 * 60,
retry: 3,
refetchOnWindowFocus: false,
enabled: !!wallet,
},
)
}

140
pages/learn.tsx Normal file
View File

@ -0,0 +1,140 @@
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { NextPage } from 'next'
import { Quiz as QuizType, quizzes } from 'utils/quiz'
import { useMemo } from 'react'
import { CheckCircleIcon, ChevronRightIcon } from '@heroicons/react/20/solid'
import { useRouter } from 'next/router'
import Image from 'next/image'
import { useCompletedQuizzes } from 'hooks/useQuiz'
import Quiz from '@components/quiz/Quiz'
import { useWallet } from '@solana/wallet-adapter-react'
export async function getStaticProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, [
'account',
'close-account',
'common',
'notifications',
'onboarding',
'profile',
'search',
'settings',
])),
// Will be passed to the page component as props
},
}
}
const shuffleQuiz = (quiz: QuizType) => {
for (let i = quiz.questions.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[quiz.questions[i], quiz.questions[j]] = [
quiz.questions[j],
quiz.questions[i],
]
}
return quiz
}
const Learn: NextPage = () => {
// const { t } = useTranslation('common')
const router = useRouter()
const { quiz } = router.query
const quizToShow = useMemo(() => {
if (!quiz) return
const index = quizzes.findIndex((q) => q.slug === quiz)
const result = quizzes.find((q) => q.slug === quiz)
return {
quiz: result,
index,
}
}, [quiz])
return !quizToShow?.quiz ? (
<div className="mx-auto flex max-w-xl flex-col items-center px-4 py-12 text-center">
<h1 className="mb-1">Learn 2 Earn</h1>
<p className="text-base">
Earn rewards points for becoming a quiz master.
</p>
<div className="w-full space-y-2 pt-6">
{[...quizzes].reverse().map((quiz, index) => (
<QuizCard key={index} quiz={quiz} />
))}
</div>
</div>
) : (
<div className="mx-auto flex flex-col items-center pb-12 text-center">
<Quiz quiz={shuffleQuiz(quizToShow.quiz)} />
</div>
)
}
const QuizCard = ({ quiz }: { quiz: QuizType }) => {
const { publicKey } = useWallet()
const { data: solved } = useCompletedQuizzes(publicKey?.toBase58())
const router = useRouter()
const goToQuiz = (quiz: string) => {
const query = { ...router.query, ['quiz']: quiz }
router.push({ pathname: router.pathname, query }, undefined, {
shallow: true,
})
}
return (
<button
className="flex w-full items-center justify-between rounded-xl bg-th-bkg-2 p-4 text-left md:p-6 md:hover:bg-th-bkg-3"
key={quiz.name}
onClick={() => goToQuiz(quiz.slug)}
>
<div>
<div className="flex items-center">
{quiz.imagePath ? (
<Image
className="mr-2.5"
src={quiz.imagePath}
height={40}
width={40}
alt="Quiz Image"
/>
) : null}
<div>
<h3>{quiz.name}</h3>
<p>{quiz.description}</p>
</div>
</div>
<div className="mt-3 w-max rounded-full border border-th-fgd-4 px-3 py-1">
<p className="text-xs">{quiz.questions.length} questions</p>
</div>
</div>
<div className="flex items-center space-x-2 pl-4">
{solved?.find((x) => x.quiz_id === quiz.id) ? (
<div className="flex w-max items-center rounded-full bg-th-up-muted py-1 pl-1.5 pr-3">
<CheckCircleIcon className="mr-1 h-4 w-4 text-th-fgd-1" />
<p className="text-th-fgd-1">Completed</p>
</div>
) : null}
{/* <div className="flex w-max items-center rounded-full bg-th-bkg-1 px-3 py-1">
<p className="mr-0.5">
Earn:{' '}
<span className="font-mono text-th-fgd-1">
{formatNumericValue(quiz.earn.amount)}
</span>
</p>
<Image
alt="Mango Logo"
src="/icons/mngo.svg"
height={16}
width={16}
/>
</div> */}
<ChevronRightIcon className="h-6 w-6" />
</div>
</button>
)
}
export default Learn

428
utils/quiz.ts Normal file
View File

@ -0,0 +1,428 @@
export type Quiz = {
id: number
name: string
description: string
imagePath?: string
intro: {
title: string
description: string
docs?: {
url: string
linkText: string
}
}
questions: QuizQuestion[]
slug: string
}
export type QuizQuestion = {
question: string
description?: string
explanation?: string // explanation of the correct answer
choices: string[]
correctAnswer: string
}
const healthQuiz = [
{
id: 0,
question:
'Which of the following is NOT factored in the account health calculation',
choices: ['Collateral', 'Borrows', 'Perp Positions', 'Account Value'],
explanation:
"All variants of account health are calculated as a weighted sum of an accounts assets minus liabilities. Account value is the notional value of an account's equity",
correctAnswer: 'Account Value',
},
{
id: 1,
question: 'How many types of account health are there?',
choices: ['1', '2', '3', '4'],
explanation:
'There are three types of health: Initial Health is used to check if new positions can be openend, Maintenance Health controls when liquidation starts and Liquidation End Health determines when liquidation ends.',
correctAnswer: '3',
},
{
id: 2,
question: 'Which of the following is NOT a type of account health?',
choices: [
'Initial Health',
'Liquidation End Health',
'Maintenance Health',
'Collateral Health',
],
explanation:
'There are three types of health: Initial Health is used to check if new positions can be openend, Maintenance Health controls when liquidation starts and Liquidation End Health determines when liquidation ends.',
correctAnswer: 'Collateral Health',
},
{
id: 3,
question: 'What is Initial Health used for?',
choices: [
'Liquidations',
'Free Collateral',
'Account Value',
'Funding Rates',
],
explanation:
"Initial Health essentially represents your free collateral. If this value reaches 0 you won't be able to enter new positions or withdraw collateral",
correctAnswer: 'Free Collateral',
},
{
id: 4,
question:
'True or false? Assets and liabilities are weighted equally across health types.',
choices: ['True', 'False'],
explanation:
'Initial Health is weighted more conservatively than Maintenance Health and the weights are dynamicly determined by the risk engine.',
correctAnswer: 'False',
},
{
id: 5,
question: 'What happens when your account health ratio reaches 0%?',
choices: [
"New positions can't be opened",
'Account will be liquidated',
'Account withdrawals will be locked',
'Account will get an airdrop',
],
explanation:
'Your account health ratio percentage is a representation of Maintenance Health. If it reaches 0% your account will be liquidated.',
correctAnswer: 'Account will be liquidated',
},
{
id: 6,
question: 'What is the Stable Price?',
choices: [
'A special price just for stablecoins',
'The average between the spot best bid and ask prices minus the oracle price',
"A safety mechanism that smooths the oracle price when it's changing rapidly",
'The oracle price from 1 hour ago',
],
explanation:
'Stable price is a safety mechanism that limits your ability to enter risky positions when the oracle price is changing rapidly.',
correctAnswer:
"A safety mechanism that smooths the oracle price when it's changing rapidly",
},
{
id: 7,
question: 'How are assets valued for Maintenance Health?',
choices: [
'Maint asset weight multiplied by stable price',
'Maint asset weight multiplied by oracle price',
'Init asset weight multiplied by oracle price',
'Maint asset weight multiplied by the lesser of oracle price and stable price',
],
explanation:
'Maintenance Health determines if an account can be liquidated. The value of assets in this case is the maintenance asset weight multiplied by the oracle price.',
correctAnswer: 'Maint asset weight multiplied by oracle price',
},
{
id: 8,
question: 'How are liabilities valued for Initial Health?',
choices: [
'Maint liability weight multiplied by stable price',
'Maint liability weight multiplied by oracle price',
'Init liability weight multiplied by oracle price',
'Init liability weight multiplied by the greater of oracle price and stable price',
],
explanation:
'Initial Health determines if new positions can be opened. The value of liabilities in this case is the init liability weight multiplied by the greater of oracle price and stable price. Including stable price prevents new overly risky positions.',
correctAnswer:
'Init liability weight multiplied by the greater of oracle price and stable price',
},
]
// const nosanaQuiz = [
// {
// id: 0,
// question: 'What is the primary purpose of the Nosana Network?',
// choices: [
// 'To offer a decentralized cryptocurrency exchange',
// 'To provide a distributed GPU grid for rent',
// 'To serve as a gaming platform',
// 'To act as a digital wallet for cryptocurrencies',
// ],
// correctAnswer: 'To provide a distributed GPU grid for rent',
// },
// {
// id: 1,
// question:
// 'What unique advantage does Nosana offer to AI users and GPU owners?',
// choices: [
// 'Exclusive access to online gaming',
// 'High-speed internet services',
// 'Affordable GPUs for AI users and income for GPU owners',
// 'Free cryptocurrency mining software',
// ],
// correctAnswer: 'Affordable GPUs for AI users and income for GPU owners',
// },
// {
// id: 2,
// question: "What is Nosana's ultimate vision for the future of computing?",
// choices: [
// 'To centralize all computing power under one corporation',
// 'To decentralize essential computations using blockchain technology',
// 'To monopolize the GPU market',
// 'To replace all traditional computing hardware',
// ],
// correctAnswer:
// 'To decentralize essential computations using blockchain technology',
// },
// {
// id: 3,
// question: 'Which blockchain powers the Nosana programs?',
// choices: ['Ethereum', 'Bitcoin', 'Solana', 'Cardano'],
// correctAnswer: 'Solana',
// },
// {
// id: 4,
// question: 'What can users do with the Nosana Token (NOS)?',
// choices: [
// 'Only trade it for other cryptocurrencies',
// 'Use it to run AI workloads on the network',
// 'Use it as a voting right in governance decisions',
// 'Convert it directly into fiat currency',
// ],
// correctAnswer: 'Use it as a voting right in governance decisions',
// },
// {
// id: 5,
// question: 'What is the total supply of Nosana Tokens (NOS)?',
// choices: ['1,000,000', '10,000,000', '50,000,000', '100,000,000'],
// correctAnswer: '100,000,000',
// },
// {
// id: 6,
// question:
// 'How are the mining tokens distributed to nodes within the Nosana Network?',
// choices: [
// 'Equally to all participants',
// 'Based on the computational power provided',
// 'In a linear fashion over 24 months',
// 'As a lump sum at the start of their participation',
// ],
// correctAnswer: 'In a linear fashion over 24 months',
// },
// {
// id: 7,
// question:
// 'What percentage of the total NOS tokens are allocated to team members?',
// choices: ['5%', '10%', '20%', '25%'],
// correctAnswer: '20%',
// },
// {
// id: 8,
// question:
// 'Which of the following is NOT a use case for company tokens within the Nosana Network?',
// choices: [
// 'Marketing',
// 'Engineering',
// 'Business Development',
// 'Personal expenses of team members',
// ],
// correctAnswer: 'Personal expenses of team members',
// },
// {
// id: 9,
// question: 'Over what period are backers tokens released?',
// choices: ['9 months', '12 months', '24 months', '36 months'],
// correctAnswer: '9 months',
// },
// ]
const stepFinanceQuiz = [
{
question:
'What key functionality does Step Finance provide to its users on the Solana blockchain?',
choices: [
'Crypto mining software',
'Portfolio tracking and management',
'Virtual real estate services',
'Social media platform for traders',
],
correctAnswer: 'Portfolio tracking and management',
},
{
question: 'What feature does Step Finance offer for NFT owners?',
choices: [
'Minting NFTs',
'Viewing NFTs in a personal gallery',
'Bidding on NFT auctions',
'Creating NFTs',
],
correctAnswer: 'Viewing NFTs in a personal gallery',
},
{
question:
'Which exchange is integrated with Step Finance for swapping tokens?',
choices: ['Uniswap', 'PancakeSwap', 'Jupiter Exchange', 'SushiSwap'],
correctAnswer: 'Jupiter Exchange',
},
{
question:
'What can STEP token holders do to earn a share of Step protocol revenue?',
choices: [
'Participate in governance voting',
'Stake STEP for xSTEP',
'Trade STEP tokens frequently',
'Hold STEP tokens in a wallet',
],
correctAnswer: 'Stake STEP for xSTEP',
},
{
question: 'How does Step Finance help users find DeFi opportunities?',
choices: [
'By providing a list of top cryptocurrencies',
'Through a dedicated Opportunities Page',
'By offering a crypto news feed',
'Through email alerts',
],
correctAnswer: 'Through a dedicated Opportunities Page',
},
{
question:
'What unique tool does Step Finance provide for tracking transactions?',
choices: [
'Blockchain explorer',
'Transaction history tool',
'A private ledger',
'An external API',
],
correctAnswer: 'Transaction history tool',
},
{
question: 'How can users support charities through Step Finance?',
choices: [
'By mining cryptocurrencies',
'Through direct token swaps',
'By donating USDC directly from the app',
'By staking NFTs',
],
correctAnswer: 'By donating USDC directly from the app',
},
{
question:
'What is the primary utility of the STEP token within the ecosystem?',
choices: [
'To pay transaction fees',
'To be used throughout the ecosystem for various utilities',
'To vote on future Solana validators',
'Solely for speculative trading',
],
correctAnswer: 'To be used throughout the ecosystem for various utilities',
},
{
question:
'Which service is integrated into Step Finance for private messaging?',
choices: ['Signal', 'Telegram', 'Dialect', 'WhatsApp'],
correctAnswer: 'Dialect',
},
{
question:
'What does Step Finance offer to help monitor and manage yield farms and staking rewards?',
choices: [
'DeFi News aggregator',
'Real-time market data dashboard',
'Protocol integrated management dashboard',
'Automated trading bots',
],
correctAnswer: 'Protocol integrated management dashboard',
},
{
question:
'What allows users to settle DEX balances and close token accounts on Step Finance?',
choices: [
'The Step Wallet',
'A specific tool on the Step Dashboard',
'A third-party service',
'Manual transactions only',
],
correctAnswer: 'A specific tool on the Step Dashboard',
},
{
question: 'How does Step Finance enhance its token swapping feature?',
choices: [
'By offering the highest interest rates for swaps',
'Through reward incentives',
'By limiting swaps to high-volume tokens only',
'Using AI to predict the best swap times',
],
correctAnswer: 'Through reward incentives',
},
{
question:
'What feature does Step Finance provide for a more engaging DeFi experience?',
choices: [
'Virtual reality trading rooms',
'An interactive game for earning tokens',
'Detailed token data on SPL tokens',
'Live streams of DeFi events',
],
correctAnswer: 'Detailed token data on SPL tokens',
},
{
question: 'How does Step Finance claim to benefit builders on Solana?',
choices: [
'By offering free advertising space',
'Through funding and project support',
'Providing a platform for token launches',
'Free hosting services for DeFi projects',
],
correctAnswer: 'Through funding and project support',
},
{
question:
'Which tool within Step Finance helps users explore and sort different yield farming APRs, lending pools data, and margin trading platforms?',
choices: [
'The DeFi Analysis Tool',
'The APR Calculator',
'The DeFi Strategy Simulator',
'The Opportunities Page',
],
correctAnswer: 'The Opportunities Page',
},
]
export const quizzes = [
// {
// name: 'Know Nosana',
// id: 1,
// description: 'Think you know Nosana?',
// imagePath: '/icons/nos.svg',
// intro: {
// title: 'Know Nosana',
// description:
// 'Test your knowledge on the basics of the Nosana Network and the NOS token.',
// },
// questions: nosanaQuiz,
// slug: 'nosana',
// },
{
name: 'Account Health',
id: 2,
description: 'Learn how account health works on Mango.',
intro: {
title: 'Health is Wealth',
description:
'Understanding account health is very important. Take a few minutes to check out the Docs before taking the quiz.',
docs: {
url: 'https://docs.mango.markets/mango-markets/health-overview',
linkText: 'Read the Health Docs',
},
},
questions: healthQuiz,
slug: 'health',
},
{
name: 'Step Finance',
id: 3,
description: 'Step up your knowledge on Step Finance',
imagePath: '/icons/step.svg',
intro: {
title: 'Step Finance Quiz',
description: 'How well do you know Step Finance?',
},
questions: stepFinanceQuiz,
slug: 'step-finance',
},
]