From 624d20815aa8ce1e5783e1d7e90242a870580b42 Mon Sep 17 00:00:00 2001 From: Iris Date: Wed, 11 Oct 2023 14:37:18 +0200 Subject: [PATCH 1/6] feat: add gigabrain quiz --- config.template.toml | 137 ++++++++++++++++++ src/endpoints/quests/gigabrain/claimable.rs | 101 +++++++++++++ src/endpoints/quests/gigabrain/mod.rs | 2 + src/endpoints/quests/gigabrain/verify_quiz.rs | 42 ++++++ src/endpoints/quests/mod.rs | 1 + src/endpoints/quests/uri.rs | 11 ++ src/main.rs | 8 + 7 files changed, 302 insertions(+) create mode 100644 src/endpoints/quests/gigabrain/claimable.rs create mode 100644 src/endpoints/quests/gigabrain/mod.rs create mode 100644 src/endpoints/quests/gigabrain/verify_quiz.rs diff --git a/config.template.toml b/config.template.toml index 73917546..d297f1f4 100644 --- a/config.template.toml +++ b/config.template.toml @@ -174,4 +174,141 @@ options = [ "Liquidity providers assume the counter-position to users' options trades", "Liquidity providers enforce exposure limits" ] +correct_answers = [*] + +[quizzes.gigabrain] +name = "Starknet Giga Brain Quiz" +desc = "Starknet Quest Quiz Rounds, a quiz series designed to make Starknet ecosystem knowledge accessible and enjoyable for all. Test your knowledge, have fun, and earn exclusive NFT rewards by testing your Starknet related topics." +intro = "Starknet Quest Quiz Rounds, a quiz series designed to make Starknet ecosystem knowledge accessible and enjoyable for all. Test your knowledge, have fun, and earn exclusive NFT rewards by testing your Starknet related topics." + +[[quizzes.gigabrain.questions]] +kind = "text_choice" +layout = "default" +question = "What is Starknet?" +options = [ + "Starknet is a zkEVM scaling solution.", + "Starknet is a Layer 1 blockchain separate from Ethereum.", + "An Ethereum Layer 2 secured by STARK proofs.", + "Starknet is a permissioned blockchain primarily used for private business logic." +] +correct_answers = [*] + +[[quizzes.gigabrain.questions]] +kind = "text_choice" +layout = "default" +question = "What are STARKs?" +options = [ + "STARKs is a cryptographic solution decreasing blockspace.", + "STARKs are a mathematical mechanism to provide proof of integrity in computer tasks.", + "STARKs are a widely adopted consensus algorithm for blockchain networks.", + "STARKs are a collection of Smart contract execution models." +] +correct_answers = [*] + +[[quizzes.gigabrain.questions]] +kind = "text_choice" +layout = "default" +question = "What are the differences between SNARKs and STARKs in the context of rollups?" +options = [ + "SNARKs are more scalable and require no trusted setup, while STARKs are vulnerable to quantum computers", + "SNARKs require a trusted setup and are less scalable, while STARKs are secure against quantum computers and offer more scalability.", + "SNARKs and STARKs are two names for the same cryptographic technique with no differences.", + "SNARKs are faster to generate and verify than STARKs, but STARKs are more widely adopted in the blockchain industry." +] +correct_answers = [*] + +[[quizzes.gigabrain.questions]] +kind = "text_choice" +layout = "default" +question = "Which one of these 4 options plays a crucial in Starknets architecture? " +options = [ + "Miner", + "Executor", + "Sequencer", + "Interpreter" +] +correct_answers = [*] + +[[quizzes.gigabrain.questions]] +kind = "text_choice" +layout = "default" +question = "What is Cairo?" +options = [ + "Cairo is a low-level assembly language.", + "Cairo is a provable programming language that permits to deploy smart contracts on Starknet.", + "Cairo is a blockchain platform for deploying smart contracts.", + "Cairo is a Rust-based virtual machine." +] +correct_answers = [*] + +[[quizzes.gigabrain.questions]] +kind = "text_choice" +layout = "default" +question = "What is the primary role of the Starknet Foundation within the ecosystem?" +options = [ + "To serve as a mission-driven non-profit organization.", + "To maintain Starknet as a permissioned infrastructure.", + "To advance decentralization goals, liveness, censorship-resistance, transparency, and creativity within Starknet.", + "To make decisions regarding protocol updates and dispute resolution informally without any defined process." +] +correct_answers = [*] + +[[quizzes.gigabrain.questions]] +kind = "text_choice" +layout = "default" +question = "How does the voting process work on Starknet?" +options = [ + "Voting takes place on the Mainnet, and a proposal requires a minimum quorum to be valid.", + "Voting has a period where voters can examine a proposal and then vote via Snapshot.", + "The Foundation does the voting process.", + "Voting power was distributed solely among all DEFI Platforms." +] +correct_answers = [*] + +[[quizzes.gigabrain.questions]] +kind = "text_choice" +layout = "default" +question = "What will the STRK token be used for?" +options = [ + "Transaction fees, staking, and voting in Starknet governance.", + "Solely for paying transaction fees in Ether (ETH).", + "To secure the network and ensure its liveness.", + "To support the development of non-native tokens within Starknet." +] +correct_answers = [*] + +[[quizzes.gigabrain.questions]] +kind = "text_choice" +layout = "default" +question = "What are the five committees established by the Starknet Foundation?" +options = [ + "Infrastructure Committee, Innovation Committee, Community Committee, Funding Committee, Security Committee", + "Development Committee, Compliance Committee, Stakeholder Committee, Advisory Committee, Network Committee ", + "Provisions Committee, Early Adopter Grants Committee, Developer Partnerships Committee, Governance Committee, Ecosystem Onboarding Committee", + "Protocol Committee, Foundation Committee, Security Committee, Validators Committee, Stakeholder Engagement Committee" +] +correct_answers = [*] + +[[quizzes.gigabrain.questions]] +kind = "text_choice" +layout = "default" +question = "What does SNIP stand for in the context of Starknet?" +options = [ + "Starknet Networking and Infrastructure Protocol", + "Starknet Improvement Protocol", + "Starknet Node Integration Platform", + "Starknet Security and Identity Protocol" +] +correct_answers = [*] + +[[quizzes.gigabrain.questions]] +kind = "text_choice" +layout = "default" +question = "What was the Quantum Leap upgrade and what did it achieve?" +options = [ + "A Starknet upgrade which achieved a 10X increase in Tps.", + "An upgrade of Starknet Beta V1.0.0, and introduced a new scripting language.", + "An upgrade of Starknet V.0.11.9 and it achieved security enhancements.", + "Starknet upgraded which achieved 10x transaction latency." +] correct_answers = [*] \ No newline at end of file diff --git a/src/endpoints/quests/gigabrain/claimable.rs b/src/endpoints/quests/gigabrain/claimable.rs new file mode 100644 index 00000000..5e8690de --- /dev/null +++ b/src/endpoints/quests/gigabrain/claimable.rs @@ -0,0 +1,101 @@ +use crate::models::{AppState, CompletedTaskDocument, Reward, RewardResponse}; +use crate::utils::{get_error, get_nft}; +use axum::{ + extract::{Query, State}, + http::StatusCode, + response::IntoResponse, + Json, +}; +use futures::StreamExt; +use mongodb::bson::doc; +use serde::Deserialize; +use starknet::{ + core::types::FieldElement, + signers::{LocalWallet, SigningKey}, +}; +use std::sync::Arc; + +const QUEST_ID: u32 = 13; +const TASK_IDS: &[u32] = &[51]; +const LAST_TASK: u32 = TASK_IDS[0]; +const NFT_LEVEL: u32 = 19; + +#[derive(Deserialize)] +pub struct ClaimableQuery { + addr: FieldElement, +} + +pub async fn handler( + State(state): State>, + Query(query): Query, +) -> impl IntoResponse { + let collection = state + .db + .collection::("completed_tasks"); + + let pipeline = vec![ + doc! { + "$match": { + "address": &query.addr.to_string(), + "task_id": { "$in": TASK_IDS }, + }, + }, + doc! { + "$lookup": { + "from": "tasks", + "localField": "task_id", + "foreignField": "id", + "as": "task", + }, + }, + doc! { + "$match": { + "task.quest_id": QUEST_ID, + }, + }, + doc! { + "$group": { + "_id": "$address", + "completed_tasks": { "$push": "$task_id" }, + }, + }, + doc! { + "$match": { + "completed_tasks": { "$all": TASK_IDS }, + }, + }, + ]; + + let completed_tasks = collection.aggregate(pipeline, None).await; + match completed_tasks { + Ok(mut tasks_cursor) => { + if tasks_cursor.next().await.is_none() { + return get_error("User hasn't completed all tasks".into()); + } + + let signer = LocalWallet::from(SigningKey::from_secret_scalar( + state.conf.nft_contract.private_key, + )); + + let mut rewards = vec![]; + + let Ok((token_id, sig)) = get_nft(QUEST_ID, LAST_TASK, &query.addr, NFT_LEVEL, &signer).await else { + return get_error("Signature failed".into()); + }; + + rewards.push(Reward { + task_id: LAST_TASK, + nft_contract: state.conf.nft_contract.address.clone(), + token_id: token_id.to_string(), + sig: (sig.r, sig.s), + }); + + if rewards.is_empty() { + get_error("No rewards found for this user".into()) + } else { + (StatusCode::OK, Json(RewardResponse { rewards })).into_response() + } + } + Err(_) => get_error("Error querying rewards".into()), + } +} diff --git a/src/endpoints/quests/gigabrain/mod.rs b/src/endpoints/quests/gigabrain/mod.rs new file mode 100644 index 00000000..0d8a27d8 --- /dev/null +++ b/src/endpoints/quests/gigabrain/mod.rs @@ -0,0 +1,2 @@ +pub mod claimable; +pub mod verify_quiz; diff --git a/src/endpoints/quests/gigabrain/verify_quiz.rs b/src/endpoints/quests/gigabrain/verify_quiz.rs new file mode 100644 index 00000000..3bc7c1aa --- /dev/null +++ b/src/endpoints/quests/gigabrain/verify_quiz.rs @@ -0,0 +1,42 @@ +use std::sync::Arc; + +use crate::{ + common::verify_quiz::verify_quiz, + models::{AppState, VerifyQuizQuery}, + utils::{get_error, CompletedTasksTrait}, +}; +use axum::{extract::State, http::StatusCode, response::IntoResponse, Json}; +use serde_json::json; +use starknet::core::types::FieldElement; + +pub async fn handler( + State(state): State>, + body: Json, +) -> impl IntoResponse { + let task_id = 51; + if body.addr == FieldElement::ZERO { + return get_error("Please connect your wallet first".to_string()); + } + + let user_answers_numbers: Result>, _> = body + .user_answers_list + .iter() + .map(|inner_list| { + inner_list + .iter() + .map(|s| s.parse::()) + .collect::, _>>() + }) + .collect(); + + match user_answers_numbers { + Ok(responses) => match verify_quiz(&state.conf, body.addr, &body.quiz_name, &responses) { + true => match state.upsert_completed_task(body.addr, task_id).await { + Ok(_) => (StatusCode::OK, Json(json!({"res": true}))).into_response(), + Err(e) => get_error(format!("{}", e)), + }, + false => get_error("Incorrect answers".to_string()), + }, + Err(e) => get_error(format!("{}", e)), + } +} diff --git a/src/endpoints/quests/mod.rs b/src/endpoints/quests/mod.rs index 87a32336..1f319293 100644 --- a/src/endpoints/quests/mod.rs +++ b/src/endpoints/quests/mod.rs @@ -3,6 +3,7 @@ pub mod braavos; pub mod carmine; pub mod contract_uri; pub mod ekubo; +pub mod gigabrain; pub mod jediswap; pub mod morphine; pub mod myswap; diff --git a/src/endpoints/quests/uri.rs b/src/endpoints/quests/uri.rs index aa13386d..bcbbc673 100644 --- a/src/endpoints/quests/uri.rs +++ b/src/endpoints/quests/uri.rs @@ -219,6 +219,17 @@ pub async fn handler( ) .into_response(), + Some(19) => ( + StatusCode::OK, + Json(TokenURI { + name: "Starknet Giga Brain NFT".into(), + description: "A Starknet NFT won for successfuly responding to the Starknet Giga Brain quiz.".into(), + image: format!("{}/starknet/gigabrain.webp", state.conf.variables.app_link), + attributes: None, + }), + ) + .into_response(), + _ => get_error("Error, this level is not correct".into()), } } diff --git a/src/main.rs b/src/main.rs index 92435e32..ff166115 100644 --- a/src/main.rs +++ b/src/main.rs @@ -238,6 +238,14 @@ async fn main() { "/quests/myswap/claimable", get(endpoints::quests::myswap::claimable::handler), ) + .route( + "/quests/gigabrain/verify_quiz", + post(endpoints::quests::gigabrain::verify_quiz::handler), + ) + .route( + "/quests/gigabrain/claimable", + get(endpoints::quests::gigabrain::claimable::handler), + ) .route( "/quests/braavos/starknetid/verify_has_domain", get(endpoints::quests::braavos::starknetid::verify_has_domain::handler), From 149d23de0ae57b7c4ed6af60c851864d1c9ce82c Mon Sep 17 00:00:00 2001 From: Iris Date: Thu, 12 Oct 2023 10:06:15 +0200 Subject: [PATCH 2/6] fix: task ids in Braavos quests --- src/endpoints/quests/braavos/starknetid/verify_has_domain.rs | 2 +- .../quests/braavos/starknetid/verify_twitter_fw_braavos.rs | 2 +- .../quests/braavos/starknetid/verify_twitter_fw_sid.rs | 2 +- src/endpoints/quests/braavos/starknetid/verify_twitter_fw_sq.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/endpoints/quests/braavos/starknetid/verify_has_domain.rs b/src/endpoints/quests/braavos/starknetid/verify_has_domain.rs index 92631c2e..a1862950 100644 --- a/src/endpoints/quests/braavos/starknetid/verify_has_domain.rs +++ b/src/endpoints/quests/braavos/starknetid/verify_has_domain.rs @@ -12,5 +12,5 @@ pub async fn handler( State(state): State>, Query(query): Query, ) -> impl IntoResponse { - verify_has_root_or_braavos_domain(state, &query.addr, 100).await + verify_has_root_or_braavos_domain(state, &query.addr, 46).await } diff --git a/src/endpoints/quests/braavos/starknetid/verify_twitter_fw_braavos.rs b/src/endpoints/quests/braavos/starknetid/verify_twitter_fw_braavos.rs index e1979680..c1139a3f 100644 --- a/src/endpoints/quests/braavos/starknetid/verify_twitter_fw_braavos.rs +++ b/src/endpoints/quests/braavos/starknetid/verify_twitter_fw_braavos.rs @@ -16,7 +16,7 @@ pub async fn handler( State(state): State>, Query(query): Query, ) -> impl IntoResponse { - let task_id = 10; + let task_id = 47; match state.upsert_completed_task(query.addr, task_id).await { Ok(_) => (StatusCode::OK, Json(json!({"res": true}))).into_response(), Err(e) => get_error(format!("{}", e)), diff --git a/src/endpoints/quests/braavos/starknetid/verify_twitter_fw_sid.rs b/src/endpoints/quests/braavos/starknetid/verify_twitter_fw_sid.rs index e1979680..4a03e3d8 100644 --- a/src/endpoints/quests/braavos/starknetid/verify_twitter_fw_sid.rs +++ b/src/endpoints/quests/braavos/starknetid/verify_twitter_fw_sid.rs @@ -16,7 +16,7 @@ pub async fn handler( State(state): State>, Query(query): Query, ) -> impl IntoResponse { - let task_id = 10; + let task_id = 49; match state.upsert_completed_task(query.addr, task_id).await { Ok(_) => (StatusCode::OK, Json(json!({"res": true}))).into_response(), Err(e) => get_error(format!("{}", e)), diff --git a/src/endpoints/quests/braavos/starknetid/verify_twitter_fw_sq.rs b/src/endpoints/quests/braavos/starknetid/verify_twitter_fw_sq.rs index e1979680..37f5074f 100644 --- a/src/endpoints/quests/braavos/starknetid/verify_twitter_fw_sq.rs +++ b/src/endpoints/quests/braavos/starknetid/verify_twitter_fw_sq.rs @@ -16,7 +16,7 @@ pub async fn handler( State(state): State>, Query(query): Query, ) -> impl IntoResponse { - let task_id = 10; + let task_id = 48; match state.upsert_completed_task(query.addr, task_id).await { Ok(_) => (StatusCode::OK, Json(json!({"res": true}))).into_response(), Err(e) => get_error(format!("{}", e)), From ced8f81bd2b77c521629207caca20f080f0dd0b2 Mon Sep 17 00:00:00 2001 From: Iris Date: Thu, 12 Oct 2023 17:11:45 +0200 Subject: [PATCH 3/6] ref: remove twitter tasks --- src/endpoints/quests/avnu/claimable.rs | 4 +- src/endpoints/quests/avnu/mod.rs | 1 - .../quests/avnu/verify_twitter_rt.rs | 24 ----- src/endpoints/quests/mod.rs | 2 +- src/endpoints/quests/sithswap/claimable.rs | 4 +- src/endpoints/quests/sithswap/mod.rs | 2 - .../quests/sithswap/verify_twitter_fw.rs | 28 ----- .../quests/sithswap/verify_twitter_rt.rs | 28 ----- src/endpoints/quests/starknet/aa/claimable.rs | 101 ++++++++++++++++++ .../quests/{gigabrain => starknet/aa}/mod.rs | 0 .../{gigabrain => starknet/aa}/verify_quiz.rs | 0 .../{ => starknet}/gigabrain/claimable.rs | 0 .../quests/starknet/gigabrain/mod.rs | 2 + .../quests/starknet/gigabrain/verify_quiz.rs | 42 ++++++++ src/endpoints/quests/starknet/mod.rs | 2 + src/endpoints/quests/zklend/claimable.rs | 4 +- src/endpoints/quests/zklend/mod.rs | 2 - .../quests/zklend/verify_twitter_fw.rs | 24 ----- .../quests/zklend/verify_twitter_rt.rs | 24 ----- src/main.rs | 36 +++---- 20 files changed, 166 insertions(+), 164 deletions(-) delete mode 100644 src/endpoints/quests/avnu/verify_twitter_rt.rs delete mode 100644 src/endpoints/quests/sithswap/verify_twitter_fw.rs delete mode 100644 src/endpoints/quests/sithswap/verify_twitter_rt.rs create mode 100644 src/endpoints/quests/starknet/aa/claimable.rs rename src/endpoints/quests/{gigabrain => starknet/aa}/mod.rs (100%) rename src/endpoints/quests/{gigabrain => starknet/aa}/verify_quiz.rs (100%) rename src/endpoints/quests/{ => starknet}/gigabrain/claimable.rs (100%) create mode 100644 src/endpoints/quests/starknet/gigabrain/mod.rs create mode 100644 src/endpoints/quests/starknet/gigabrain/verify_quiz.rs create mode 100644 src/endpoints/quests/starknet/mod.rs delete mode 100644 src/endpoints/quests/zklend/verify_twitter_fw.rs delete mode 100644 src/endpoints/quests/zklend/verify_twitter_rt.rs diff --git a/src/endpoints/quests/avnu/claimable.rs b/src/endpoints/quests/avnu/claimable.rs index 9a94c2a7..0e8a7dfc 100644 --- a/src/endpoints/quests/avnu/claimable.rs +++ b/src/endpoints/quests/avnu/claimable.rs @@ -16,8 +16,8 @@ use starknet::{ use std::sync::Arc; const QUEST_ID: u32 = 3; -const TASK_IDS: &[u32] = &[12, 13, 15]; -const LAST_TASK: u32 = TASK_IDS[2]; +const TASK_IDS: &[u32] = &[12, 13]; +const LAST_TASK: u32 = TASK_IDS[1]; const NFT_LEVEL: u32 = 6; #[derive(Deserialize)] diff --git a/src/endpoints/quests/avnu/mod.rs b/src/endpoints/quests/avnu/mod.rs index 76171942..1c19b08c 100644 --- a/src/endpoints/quests/avnu/mod.rs +++ b/src/endpoints/quests/avnu/mod.rs @@ -1,4 +1,3 @@ pub mod claimable; pub mod discord_fw_callback; pub mod verify_swap; -pub mod verify_twitter_rt; diff --git a/src/endpoints/quests/avnu/verify_twitter_rt.rs b/src/endpoints/quests/avnu/verify_twitter_rt.rs deleted file mode 100644 index a2bb7dc7..00000000 --- a/src/endpoints/quests/avnu/verify_twitter_rt.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::sync::Arc; - -use crate::{ - models::{AppState, VerifyQuery}, - utils::{get_error, CompletedTasksTrait}, -}; -use axum::{ - extract::{Query, State}, - http::StatusCode, - response::IntoResponse, - Json, -}; -use serde_json::json; - -pub async fn handler( - State(state): State>, - Query(query): Query, -) -> impl IntoResponse { - let task_id = 15; - match state.upsert_completed_task(query.addr, task_id).await { - Ok(_) => (StatusCode::OK, Json(json!({"res": true}))).into_response(), - Err(e) => get_error(format!("{}", e)), - } -} diff --git a/src/endpoints/quests/mod.rs b/src/endpoints/quests/mod.rs index 1f319293..2a796d96 100644 --- a/src/endpoints/quests/mod.rs +++ b/src/endpoints/quests/mod.rs @@ -3,12 +3,12 @@ pub mod braavos; pub mod carmine; pub mod contract_uri; pub mod ekubo; -pub mod gigabrain; pub mod jediswap; pub mod morphine; pub mod myswap; pub mod orbiter; pub mod sithswap; +pub mod starknet; pub mod starknetid; pub mod tribe; pub mod uri; diff --git a/src/endpoints/quests/sithswap/claimable.rs b/src/endpoints/quests/sithswap/claimable.rs index df5a3af4..85087d3c 100644 --- a/src/endpoints/quests/sithswap/claimable.rs +++ b/src/endpoints/quests/sithswap/claimable.rs @@ -16,8 +16,8 @@ use starknet::{ use std::sync::Arc; const QUEST_ID: u32 = 5; -const TASK_IDS: &[u32] = &[20, 21, 22]; -const LAST_TASK: u32 = TASK_IDS[2]; +const TASK_IDS: &[u32] = &[20]; +const LAST_TASK: u32 = TASK_IDS[0]; const NFT_LEVEL: u32 = 7; #[derive(Deserialize)] diff --git a/src/endpoints/quests/sithswap/mod.rs b/src/endpoints/quests/sithswap/mod.rs index ee2c0dee..4e642f70 100644 --- a/src/endpoints/quests/sithswap/mod.rs +++ b/src/endpoints/quests/sithswap/mod.rs @@ -1,4 +1,2 @@ pub mod claimable; pub mod verify_added_liquidity; -pub mod verify_twitter_fw; -pub mod verify_twitter_rt; diff --git a/src/endpoints/quests/sithswap/verify_twitter_fw.rs b/src/endpoints/quests/sithswap/verify_twitter_fw.rs deleted file mode 100644 index eb0cbc0c..00000000 --- a/src/endpoints/quests/sithswap/verify_twitter_fw.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::sync::Arc; - -use crate::{ - models::{AppState, VerifyQuery}, - utils::{get_error, CompletedTasksTrait}, -}; -use axum::{ - extract::{Query, State}, - http::StatusCode, - response::IntoResponse, - Json, -}; -use serde_json::json; -use starknet::core::types::FieldElement; - -pub async fn handler( - State(state): State>, - Query(query): Query, -) -> impl IntoResponse { - let task_id = 21; - if query.addr == FieldElement::ZERO { - return get_error("Please connect your wallet first".to_string()); - } - match state.upsert_completed_task(query.addr, task_id).await { - Ok(_) => (StatusCode::OK, Json(json!({"res": true}))).into_response(), - Err(e) => get_error(format!("{}", e)), - } -} diff --git a/src/endpoints/quests/sithswap/verify_twitter_rt.rs b/src/endpoints/quests/sithswap/verify_twitter_rt.rs deleted file mode 100644 index 643a5b0f..00000000 --- a/src/endpoints/quests/sithswap/verify_twitter_rt.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::sync::Arc; - -use crate::{ - models::{AppState, VerifyQuery}, - utils::{get_error, CompletedTasksTrait}, -}; -use axum::{ - extract::{Query, State}, - http::StatusCode, - response::IntoResponse, - Json, -}; -use serde_json::json; -use starknet::core::types::FieldElement; - -pub async fn handler( - State(state): State>, - Query(query): Query, -) -> impl IntoResponse { - let task_id = 22; - if query.addr == FieldElement::ZERO { - return get_error("Please connect your wallet first".to_string()); - } - match state.upsert_completed_task(query.addr, task_id).await { - Ok(_) => (StatusCode::OK, Json(json!({"res": true}))).into_response(), - Err(e) => get_error(format!("{}", e)), - } -} diff --git a/src/endpoints/quests/starknet/aa/claimable.rs b/src/endpoints/quests/starknet/aa/claimable.rs new file mode 100644 index 00000000..9e7dc441 --- /dev/null +++ b/src/endpoints/quests/starknet/aa/claimable.rs @@ -0,0 +1,101 @@ +use crate::models::{AppState, CompletedTaskDocument, Reward, RewardResponse}; +use crate::utils::{get_error, get_nft}; +use axum::{ + extract::{Query, State}, + http::StatusCode, + response::IntoResponse, + Json, +}; +use futures::StreamExt; +use mongodb::bson::doc; +use serde::Deserialize; +use starknet::{ + core::types::FieldElement, + signers::{LocalWallet, SigningKey}, +}; +use std::sync::Arc; + +const QUEST_ID: u32 = 14; +const TASK_IDS: &[u32] = &[52]; +const LAST_TASK: u32 = TASK_IDS[0]; +const NFT_LEVEL: u32 = 19; + +#[derive(Deserialize)] +pub struct ClaimableQuery { + addr: FieldElement, +} + +pub async fn handler( + State(state): State>, + Query(query): Query, +) -> impl IntoResponse { + let collection = state + .db + .collection::("completed_tasks"); + + let pipeline = vec![ + doc! { + "$match": { + "address": &query.addr.to_string(), + "task_id": { "$in": TASK_IDS }, + }, + }, + doc! { + "$lookup": { + "from": "tasks", + "localField": "task_id", + "foreignField": "id", + "as": "task", + }, + }, + doc! { + "$match": { + "task.quest_id": QUEST_ID, + }, + }, + doc! { + "$group": { + "_id": "$address", + "completed_tasks": { "$push": "$task_id" }, + }, + }, + doc! { + "$match": { + "completed_tasks": { "$all": TASK_IDS }, + }, + }, + ]; + + let completed_tasks = collection.aggregate(pipeline, None).await; + match completed_tasks { + Ok(mut tasks_cursor) => { + if tasks_cursor.next().await.is_none() { + return get_error("User hasn't completed all tasks".into()); + } + + let signer = LocalWallet::from(SigningKey::from_secret_scalar( + state.conf.nft_contract.private_key, + )); + + let mut rewards = vec![]; + + let Ok((token_id, sig)) = get_nft(QUEST_ID, LAST_TASK, &query.addr, NFT_LEVEL, &signer).await else { + return get_error("Signature failed".into()); + }; + + rewards.push(Reward { + task_id: LAST_TASK, + nft_contract: state.conf.nft_contract.address.clone(), + token_id: token_id.to_string(), + sig: (sig.r, sig.s), + }); + + if rewards.is_empty() { + get_error("No rewards found for this user".into()) + } else { + (StatusCode::OK, Json(RewardResponse { rewards })).into_response() + } + } + Err(_) => get_error("Error querying rewards".into()), + } +} diff --git a/src/endpoints/quests/gigabrain/mod.rs b/src/endpoints/quests/starknet/aa/mod.rs similarity index 100% rename from src/endpoints/quests/gigabrain/mod.rs rename to src/endpoints/quests/starknet/aa/mod.rs diff --git a/src/endpoints/quests/gigabrain/verify_quiz.rs b/src/endpoints/quests/starknet/aa/verify_quiz.rs similarity index 100% rename from src/endpoints/quests/gigabrain/verify_quiz.rs rename to src/endpoints/quests/starknet/aa/verify_quiz.rs diff --git a/src/endpoints/quests/gigabrain/claimable.rs b/src/endpoints/quests/starknet/gigabrain/claimable.rs similarity index 100% rename from src/endpoints/quests/gigabrain/claimable.rs rename to src/endpoints/quests/starknet/gigabrain/claimable.rs diff --git a/src/endpoints/quests/starknet/gigabrain/mod.rs b/src/endpoints/quests/starknet/gigabrain/mod.rs new file mode 100644 index 00000000..0d8a27d8 --- /dev/null +++ b/src/endpoints/quests/starknet/gigabrain/mod.rs @@ -0,0 +1,2 @@ +pub mod claimable; +pub mod verify_quiz; diff --git a/src/endpoints/quests/starknet/gigabrain/verify_quiz.rs b/src/endpoints/quests/starknet/gigabrain/verify_quiz.rs new file mode 100644 index 00000000..3bc7c1aa --- /dev/null +++ b/src/endpoints/quests/starknet/gigabrain/verify_quiz.rs @@ -0,0 +1,42 @@ +use std::sync::Arc; + +use crate::{ + common::verify_quiz::verify_quiz, + models::{AppState, VerifyQuizQuery}, + utils::{get_error, CompletedTasksTrait}, +}; +use axum::{extract::State, http::StatusCode, response::IntoResponse, Json}; +use serde_json::json; +use starknet::core::types::FieldElement; + +pub async fn handler( + State(state): State>, + body: Json, +) -> impl IntoResponse { + let task_id = 51; + if body.addr == FieldElement::ZERO { + return get_error("Please connect your wallet first".to_string()); + } + + let user_answers_numbers: Result>, _> = body + .user_answers_list + .iter() + .map(|inner_list| { + inner_list + .iter() + .map(|s| s.parse::()) + .collect::, _>>() + }) + .collect(); + + match user_answers_numbers { + Ok(responses) => match verify_quiz(&state.conf, body.addr, &body.quiz_name, &responses) { + true => match state.upsert_completed_task(body.addr, task_id).await { + Ok(_) => (StatusCode::OK, Json(json!({"res": true}))).into_response(), + Err(e) => get_error(format!("{}", e)), + }, + false => get_error("Incorrect answers".to_string()), + }, + Err(e) => get_error(format!("{}", e)), + } +} diff --git a/src/endpoints/quests/starknet/mod.rs b/src/endpoints/quests/starknet/mod.rs new file mode 100644 index 00000000..df9ef845 --- /dev/null +++ b/src/endpoints/quests/starknet/mod.rs @@ -0,0 +1,2 @@ +pub mod aa; +pub mod gigabrain; diff --git a/src/endpoints/quests/zklend/claimable.rs b/src/endpoints/quests/zklend/claimable.rs index cfc9d2cc..4c9ef537 100644 --- a/src/endpoints/quests/zklend/claimable.rs +++ b/src/endpoints/quests/zklend/claimable.rs @@ -16,8 +16,8 @@ use starknet::{ use std::sync::Arc; const QUEST_ID: u32 = 6; -const TASK_IDS: &[u32] = &[24, 26, 27]; -const LAST_TASK: u32 = TASK_IDS[2]; +const TASK_IDS: &[u32] = &[24]; +const LAST_TASK: u32 = TASK_IDS[0]; const NFT_LEVEL: u32 = 8; #[derive(Deserialize)] diff --git a/src/endpoints/quests/zklend/mod.rs b/src/endpoints/quests/zklend/mod.rs index 6d586aaf..ffee90a8 100644 --- a/src/endpoints/quests/zklend/mod.rs +++ b/src/endpoints/quests/zklend/mod.rs @@ -1,4 +1,2 @@ pub mod claimable; pub mod verify_borrow; -pub mod verify_twitter_fw; -pub mod verify_twitter_rt; diff --git a/src/endpoints/quests/zklend/verify_twitter_fw.rs b/src/endpoints/quests/zklend/verify_twitter_fw.rs deleted file mode 100644 index 195f79ec..00000000 --- a/src/endpoints/quests/zklend/verify_twitter_fw.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::sync::Arc; - -use crate::{ - models::{AppState, VerifyQuery}, - utils::{get_error, CompletedTasksTrait}, -}; -use axum::{ - extract::{Query, State}, - http::StatusCode, - response::IntoResponse, - Json, -}; -use serde_json::json; - -pub async fn handler( - State(state): State>, - Query(query): Query, -) -> impl IntoResponse { - let task_id = 26; - match state.upsert_completed_task(query.addr, task_id).await { - Ok(_) => (StatusCode::OK, Json(json!({"res": true}))).into_response(), - Err(e) => get_error(format!("{}", e)), - } -} diff --git a/src/endpoints/quests/zklend/verify_twitter_rt.rs b/src/endpoints/quests/zklend/verify_twitter_rt.rs deleted file mode 100644 index 2c8a2da8..00000000 --- a/src/endpoints/quests/zklend/verify_twitter_rt.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::sync::Arc; - -use crate::{ - models::{AppState, VerifyQuery}, - utils::{get_error, CompletedTasksTrait}, -}; -use axum::{ - extract::{Query, State}, - http::StatusCode, - response::IntoResponse, - Json, -}; -use serde_json::json; - -pub async fn handler( - State(state): State>, - Query(query): Query, -) -> impl IntoResponse { - let task_id = 27; - match state.upsert_completed_task(query.addr, task_id).await { - Ok(_) => (StatusCode::OK, Json(json!({"res": true}))).into_response(), - Err(e) => get_error(format!("{}", e)), - } -} diff --git a/src/main.rs b/src/main.rs index ff166115..c386d58c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -106,22 +106,10 @@ async fn main() { "/quests/zklend/verify_borrow", get(endpoints::quests::zklend::verify_borrow::handler), ) - .route( - "/quests/zklend/verify_twitter_fw", - get(endpoints::quests::zklend::verify_twitter_fw::handler), - ) - .route( - "/quests/zklend/verify_twitter_rt", - get(endpoints::quests::zklend::verify_twitter_rt::handler), - ) .route( "/quests/zklend/claimable", get(endpoints::quests::zklend::claimable::handler), ) - .route( - "/quests/avnu/verify_twitter_rt", - get(endpoints::quests::avnu::verify_twitter_rt::handler), - ) .route( "/quests/avnu/discord_fw_callback", get(endpoints::quests::avnu::discord_fw_callback::handler), @@ -150,14 +138,6 @@ async fn main() { "/quests/tribe/claimable", get(endpoints::quests::tribe::claimable::handler), ) - .route( - "/quests/sithswap/verify_twitter_fw", - get(endpoints::quests::sithswap::verify_twitter_fw::handler), - ) - .route( - "/quests/sithswap/verify_twitter_rt", - get(endpoints::quests::sithswap::verify_twitter_rt::handler), - ) .route( "/quests/sithswap/verify_added_liquidity", get(endpoints::quests::sithswap::verify_added_liquidity::handler), @@ -239,12 +219,20 @@ async fn main() { get(endpoints::quests::myswap::claimable::handler), ) .route( - "/quests/gigabrain/verify_quiz", - post(endpoints::quests::gigabrain::verify_quiz::handler), + "/quests/starknet/gigabrain/verify_quiz", + post(endpoints::quests::starknet::gigabrain::verify_quiz::handler), + ) + .route( + "/quests/starknet/gigabrain/claimable", + get(endpoints::quests::starknet::gigabrain::claimable::handler), + ) + .route( + "/quests/starknet/aa/verify_quiz", + post(endpoints::quests::starknet::aa::verify_quiz::handler), ) .route( - "/quests/gigabrain/claimable", - get(endpoints::quests::gigabrain::claimable::handler), + "/quests/starknet/aa/claimable", + get(endpoints::quests::starknet::aa::claimable::handler), ) .route( "/quests/braavos/starknetid/verify_has_domain", From 26a1b3f4897bed2439ec2ff1b72e928c5b0d89c6 Mon Sep 17 00:00:00 2001 From: Iris Date: Thu, 12 Oct 2023 17:14:04 +0200 Subject: [PATCH 4/6] feat: add AA quiz NFT uri --- src/endpoints/quests/starknet/aa/claimable.rs | 2 +- src/endpoints/quests/uri.rs | 31 +++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/endpoints/quests/starknet/aa/claimable.rs b/src/endpoints/quests/starknet/aa/claimable.rs index 9e7dc441..1d3a78e7 100644 --- a/src/endpoints/quests/starknet/aa/claimable.rs +++ b/src/endpoints/quests/starknet/aa/claimable.rs @@ -18,7 +18,7 @@ use std::sync::Arc; const QUEST_ID: u32 = 14; const TASK_IDS: &[u32] = &[52]; const LAST_TASK: u32 = TASK_IDS[0]; -const NFT_LEVEL: u32 = 19; +const NFT_LEVEL: u32 = 20; #[derive(Deserialize)] pub struct ClaimableQuery { diff --git a/src/endpoints/quests/uri.rs b/src/endpoints/quests/uri.rs index bcbbc673..b54dc063 100644 --- a/src/endpoints/quests/uri.rs +++ b/src/endpoints/quests/uri.rs @@ -219,16 +219,27 @@ pub async fn handler( ) .into_response(), - Some(19) => ( - StatusCode::OK, - Json(TokenURI { - name: "Starknet Giga Brain NFT".into(), - description: "A Starknet NFT won for successfuly responding to the Starknet Giga Brain quiz.".into(), - image: format!("{}/starknet/gigabrain.webp", state.conf.variables.app_link), - attributes: None, - }), - ) - .into_response(), + Some(19) => ( + StatusCode::OK, + Json(TokenURI { + name: "Starknet Giga Brain NFT".into(), + description: "A Starknet NFT won for successfuly responding to the Starknet Giga Brain quiz.".into(), + image: format!("{}/starknet/gigabrain.webp", state.conf.variables.app_link), + attributes: None, + }), + ) + .into_response(), + + Some(20) => ( + StatusCode::OK, + Json(TokenURI { + name: "".into(), + description: "A Starknet NFT won for successfuly responding ".into(), + image: format!("{}/starknet/aa.webp", state.conf.variables.app_link), + attributes: None, + }), + ) + .into_response(), _ => get_error("Error, this level is not correct".into()), } From 21d053b049803b221eb91f713a1b8d190cbfde47 Mon Sep 17 00:00:00 2001 From: Iris Date: Thu, 12 Oct 2023 17:34:59 +0200 Subject: [PATCH 5/6] fix: update NFT texts --- src/endpoints/quests/uri.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/endpoints/quests/uri.rs b/src/endpoints/quests/uri.rs index b54dc063..191685bf 100644 --- a/src/endpoints/quests/uri.rs +++ b/src/endpoints/quests/uri.rs @@ -223,7 +223,7 @@ pub async fn handler( StatusCode::OK, Json(TokenURI { name: "Starknet Giga Brain NFT".into(), - description: "A Starknet NFT won for successfuly responding to the Starknet Giga Brain quiz.".into(), + description: "A Starknet Giga Brain NFT won for successfuly responding to a quiz.".into(), image: format!("{}/starknet/gigabrain.webp", state.conf.variables.app_link), attributes: None, }), @@ -233,8 +233,8 @@ pub async fn handler( Some(20) => ( StatusCode::OK, Json(TokenURI { - name: "".into(), - description: "A Starknet NFT won for successfuly responding ".into(), + name: "Account Abstraction Mastery NFT".into(), + description: "An Account Abstraction Mastery NFT won for successfully responding to a quiz.".into(), image: format!("{}/starknet/aa.webp", state.conf.variables.app_link), attributes: None, }), From f966c7be7722a7d3eae382d6a024e4c77d84726c Mon Sep 17 00:00:00 2001 From: Iris Date: Thu, 12 Oct 2023 18:37:02 +0200 Subject: [PATCH 6/6] ref: update starknet quizzes --- config.template.toml | 137 ++++++++++++++++++ .../starknet/{aa => aa_mastery}/claimable.rs | 0 .../quests/starknet/{aa => aa_mastery}/mod.rs | 0 .../{aa => aa_mastery}/verify_quiz.rs | 2 +- src/endpoints/quests/starknet/mod.rs | 2 +- src/main.rs | 8 +- 6 files changed, 143 insertions(+), 6 deletions(-) rename src/endpoints/quests/starknet/{aa => aa_mastery}/claimable.rs (100%) rename src/endpoints/quests/starknet/{aa => aa_mastery}/mod.rs (100%) rename src/endpoints/quests/starknet/{aa => aa_mastery}/verify_quiz.rs (98%) diff --git a/config.template.toml b/config.template.toml index d297f1f4..23be6d7f 100644 --- a/config.template.toml +++ b/config.template.toml @@ -311,4 +311,141 @@ options = [ "An upgrade of Starknet V.0.11.9 and it achieved security enhancements.", "Starknet upgraded which achieved 10x transaction latency." ] +correct_answers = [*] + +[quizzes.aa_mastery] +name = "Starknet AA Mastery Quiz" +desc = "Take part in our Starknet quiz to test your knowledge, and you'll have a chance to win an exclusive Account Abstraction Mastery NFT as your reward." +intro = "Starknet Quest Quiz Rounds, a quiz series designed to make Starknet ecosystem knowledge accessible and enjoyable for all. Test your knowledge, have fun, and earn exclusive NFT rewards by testing your Starknet related topics." + +[[quizzes.aa_mastery.questions]] +kind = "text_choice" +layout = "default" +question = "In the context of Ethereum, what do EOA wallets stand for and what are they?" +options = [ + "Ethereum Operational Accounts, used for mining ETH.", + "Ethereum On-chain Authentication, used for DApps.", + "External Ownership Addresses, used for tracking network upgrades.", + "Externally Owned Accounts, your conventional wallets for sending and receiving ETH." +] +correct_answers = [*] + +[[quizzes.aa_mastery.questions]] +kind = "text_choice" +layout = "default" +question = "What is the primary disadvantage associated with Externally Owned Accounts (EOAs) in Ethereum?" +options = [ + "Their adaptability and customization options for user requirements are limited.", + "They employ a 6-digit password as a means of private key protection.", + "Initiating transactions doesn't necessitate the use of private keys.", + "They lack flexibility and are pre-determined by the protocol, leading to poor UX." +] +correct_answers = [*] + +[[quizzes.aa_mastery.questions]] +kind = "text_choice" +layout = "default" +question = "What exactly is a Smart contract wallet in the context of Starknet?" +options = [ + "A wallet that can lock control of user funds to smart contracts.", + "A wallet describing the complexity of blockchain smart contracts.", + "A wallet which is a smart contract that is highly customizable, allowing users to define its functionality as they wish.", + "A blockchain wallet consensus algorithm used for securing transactions." +] +correct_answers = [*] + +[[quizzes.aa_mastery.questions]] +kind = "text_choice" +layout = "default" +question = "What are the main 2 smart contract wallets on Starknet? " +options = [ + "Phanton, Trust Wallet", + "Argent, Braavos", + "MetaMask, Ledger", + "OKX, Binance " +] +correct_answers = [*] + +[[quizzes.aa_mastery.questions]] +kind = "text_choice" +layout = "default" +question = "According to Starknet: What is Signature Abstraction in the context of Smart Contract Wallets?" +options = [ + "Signature Abstraction ensures comfortable user experiences by allowing multiple simultaneous transactions.", + "Signature Abstraction enables the use of any custom logic for account control or any type of device for Signatures.", + "Signature Abstraction enforces the complete ordering of transactions for security.", + "Signature Abstraction restricts customization of account permissions." +] +correct_answers = [*] + +[[quizzes.aa_mastery.questions]] +kind = "text_choice" +layout = "default" +question = "According to Starknet: What is Fee Abstraction in the context of Smart Contract Wallets?" +options = [ + "Fee Abstraction ensures comfortable user experiences by allowing multiple simultaneous transactions.", + "Fee Abstraction restricts users to using only the network's native token for transaction fees.", + "Fee Abstraction allows different tokens to be used as payment for transaction fees, not limited to the native token.", + "Fee Abstraction enforces complete ordering of transactions for security. " +] +correct_answers = [*] + +[[quizzes.aa_mastery.questions]] +kind = "text_choice" +layout = "default" +question = "According to Starknet: What is Nonce Abstraction in the context of Smart Contract Wallets?" +options = [ + "Nonce Abstraction ensures comfortable user experiences by allowing multiple simultaneous transactions.", + "Nonce Abstraction restricts users from sending multiple independent transactions simultaneously.", + "Nonce Abstraction enforces the complete ordering of transactions for security.", + "Nonce Abstraction allows customization of protection mechanism, permitting 2fa or 3fa." +] +correct_answers = [*] + +[[quizzes.aa_mastery.questions]] +kind = "text_choice" +layout = "default" +question = "What does the concept of 'Multicall' offer in the context of Smart Contract Wallets." +options = [ + "It enables you to execute multiple transactions simultaneously, increasing gas fees.", + "It allows you to bundle multiple transactions into one transaction, simplifying on-chain interactions.", + "It provides a method to recover lost seed phrases for wallet security.", + "It automates the inspection of each transaction, ensuring user control." +] +correct_answers = [*] + +[[quizzes.aa_mastery.questions]] +kind = "text_choice" +layout = "default" +question = "How do 'Session keys' benefit users interacting on Starknet?" +options = [ + "They allow users to bypass transaction signing and use DApps/games without any signature.", + "They eliminate the need for social recovery methods in DApps.", + "Session keys enhance user experiences by eliminating the requirement to sign transactions repeatedly when using DApps or games. They also enable users to grant signature allowances to protocols.", + "They provide unlimited access to a Dapp's functions without restrictions." +] +correct_answers = [*] + +[[quizzes.aa_mastery.questions]] +kind = "text_choice" +layout = "default" +question = "How does Braavos Hardware Signer Work?" +options = [ + "It uses a special security chip in mobile devices like iPhones or Android phones to create and protect private keys.", + "The chip generates a unique identifier (UID) that is used to encrypt and safeguard keys.", + "Transactions are approved using biometric authentication like fingerprint or face recognition directly within the app.", + "It relies on a complex cryptographic algorithm called NIST-P256 for security." +] +correct_answers = [*] + +[[quizzes.aa_mastery.questions]] +kind = "text_choice" +layout = "default" +question = "What is the Argent Web Wallet, and what features does it offer?" +options = [ + "A physical wallet for storing cryptocurrency with a biometric lock.", + "A self-custodial smart wallet with seed phrases on the web with various features, such as 2FA.", + "A browser extension for secure internet browsing.", + "An online shopping platform for buying digital goods." +] correct_answers = [*] \ No newline at end of file diff --git a/src/endpoints/quests/starknet/aa/claimable.rs b/src/endpoints/quests/starknet/aa_mastery/claimable.rs similarity index 100% rename from src/endpoints/quests/starknet/aa/claimable.rs rename to src/endpoints/quests/starknet/aa_mastery/claimable.rs diff --git a/src/endpoints/quests/starknet/aa/mod.rs b/src/endpoints/quests/starknet/aa_mastery/mod.rs similarity index 100% rename from src/endpoints/quests/starknet/aa/mod.rs rename to src/endpoints/quests/starknet/aa_mastery/mod.rs diff --git a/src/endpoints/quests/starknet/aa/verify_quiz.rs b/src/endpoints/quests/starknet/aa_mastery/verify_quiz.rs similarity index 98% rename from src/endpoints/quests/starknet/aa/verify_quiz.rs rename to src/endpoints/quests/starknet/aa_mastery/verify_quiz.rs index 3bc7c1aa..762939c4 100644 --- a/src/endpoints/quests/starknet/aa/verify_quiz.rs +++ b/src/endpoints/quests/starknet/aa_mastery/verify_quiz.rs @@ -13,7 +13,7 @@ pub async fn handler( State(state): State>, body: Json, ) -> impl IntoResponse { - let task_id = 51; + let task_id = 52; if body.addr == FieldElement::ZERO { return get_error("Please connect your wallet first".to_string()); } diff --git a/src/endpoints/quests/starknet/mod.rs b/src/endpoints/quests/starknet/mod.rs index df9ef845..ea0bbb34 100644 --- a/src/endpoints/quests/starknet/mod.rs +++ b/src/endpoints/quests/starknet/mod.rs @@ -1,2 +1,2 @@ -pub mod aa; +pub mod aa_mastery; pub mod gigabrain; diff --git a/src/main.rs b/src/main.rs index c386d58c..efced134 100644 --- a/src/main.rs +++ b/src/main.rs @@ -227,12 +227,12 @@ async fn main() { get(endpoints::quests::starknet::gigabrain::claimable::handler), ) .route( - "/quests/starknet/aa/verify_quiz", - post(endpoints::quests::starknet::aa::verify_quiz::handler), + "/quests/starknet/aa_mastery/verify_quiz", + post(endpoints::quests::starknet::aa_mastery::verify_quiz::handler), ) .route( - "/quests/starknet/aa/claimable", - get(endpoints::quests::starknet::aa::claimable::handler), + "/quests/starknet/aa_mastery/claimable", + get(endpoints::quests::starknet::aa_mastery::claimable::handler), ) .route( "/quests/braavos/starknetid/verify_has_domain",