From 7f994ae6fb9a18cf3c0c6b6cf9f26c05506418d4 Mon Sep 17 00:00:00 2001 From: ayushtom Date: Tue, 12 Mar 2024 22:36:38 +0530 Subject: [PATCH 1/5] feat: add nostra quest --- src/config.rs | 8 +- src/endpoints/get_quiz.rs | 2 +- .../nostra/{ => liquidity_quest}/claimable.rs | 2 +- .../discord_fw_callback.rs | 3 +- .../quests/nostra/liquidity_quest/mod.rs | 3 + .../verify_added_liquidity.rs | 3 +- src/endpoints/quests/nostra/mod.rs | 5 +- .../quests/nostra/staking_quest/claimable.rs | 107 ++++++++++++++++++ .../quests/nostra/staking_quest/mod.rs | 3 + .../nostra/staking_quest/verify_stake.rs | 59 ++++++++++ .../nostra/staking_quest/verify_twitter_tw.rs | 30 +++++ src/endpoints/quests/uri.rs | 11 ++ src/endpoints/quests/verify_quiz.rs | 1 + 13 files changed, 229 insertions(+), 8 deletions(-) rename src/endpoints/quests/nostra/{ => liquidity_quest}/claimable.rs (97%) rename src/endpoints/quests/nostra/{ => liquidity_quest}/discord_fw_callback.rs (98%) create mode 100644 src/endpoints/quests/nostra/liquidity_quest/mod.rs rename src/endpoints/quests/nostra/{ => liquidity_quest}/verify_added_liquidity.rs (95%) create mode 100644 src/endpoints/quests/nostra/staking_quest/claimable.rs create mode 100644 src/endpoints/quests/nostra/staking_quest/mod.rs create mode 100644 src/endpoints/quests/nostra/staking_quest/verify_stake.rs create mode 100644 src/endpoints/quests/nostra/staking_quest/verify_twitter_tw.rs diff --git a/src/config.rs b/src/config.rs index 599cd33c..284a5bd4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -36,6 +36,12 @@ pub_struct!(Clone, Deserialize; StarknetId { account_id: String, }); +pub_struct!(Clone, Deserialize; Nostra { + utils_contract: FieldElement, + pairs : Vec, + staking_contract: FieldElement, +}); + pub_struct!(Clone, Deserialize; Pairs { utils_contract: FieldElement, pairs : Vec, @@ -68,7 +74,7 @@ pub_struct!(Clone, Deserialize; Quests { myswap: Contract, braavos: Braavos, element: Element, - nostra: Pairs, + nostra: Nostra, carbonable: Contract, }); diff --git a/src/endpoints/get_quiz.rs b/src/endpoints/get_quiz.rs index 93ba7f44..e7169242 100644 --- a/src/endpoints/get_quiz.rs +++ b/src/endpoints/get_quiz.rs @@ -40,7 +40,7 @@ pub async fn handler( Query(query): Query, ) -> impl IntoResponse { let quizzes_from_config = &state.conf.quizzes; - + println!("{:?}", query.id); match quizzes_from_config.get(&query.id) { Some(quiz) => { let questions: Vec = quiz diff --git a/src/endpoints/quests/nostra/claimable.rs b/src/endpoints/quests/nostra/liquidity_quest/claimable.rs similarity index 97% rename from src/endpoints/quests/nostra/claimable.rs rename to src/endpoints/quests/nostra/liquidity_quest/claimable.rs index 3e9c16de..b1f51590 100644 --- a/src/endpoints/quests/nostra/claimable.rs +++ b/src/endpoints/quests/nostra/liquidity_quest/claimable.rs @@ -29,7 +29,7 @@ pub struct ClaimableQuery { #[route( get, "/quests/nostra/claimable", - crate::endpoints::quests::nostra::claimable + crate::endpoints::quests::nostra::liquidity_quest::claimable )] pub async fn handler( State(state): State>, diff --git a/src/endpoints/quests/nostra/discord_fw_callback.rs b/src/endpoints/quests/nostra/liquidity_quest/discord_fw_callback.rs similarity index 98% rename from src/endpoints/quests/nostra/discord_fw_callback.rs rename to src/endpoints/quests/nostra/liquidity_quest/discord_fw_callback.rs index b1c46b2a..e6c390cf 100644 --- a/src/endpoints/quests/nostra/discord_fw_callback.rs +++ b/src/endpoints/quests/nostra/liquidity_quest/discord_fw_callback.rs @@ -31,7 +31,8 @@ pub struct Guild { #[route( get, "/quests/nostra/discord_fw_callback", - crate::endpoints::quests::nostra::discord_fw_callback + crate::endpoints::quests::nostra::liquidity_quest::discord_fw_callback + )] pub async fn handler( State(state): State>, diff --git a/src/endpoints/quests/nostra/liquidity_quest/mod.rs b/src/endpoints/quests/nostra/liquidity_quest/mod.rs new file mode 100644 index 00000000..57eade9b --- /dev/null +++ b/src/endpoints/quests/nostra/liquidity_quest/mod.rs @@ -0,0 +1,3 @@ +pub mod claimable; +pub mod discord_fw_callback; +pub mod verify_added_liquidity; diff --git a/src/endpoints/quests/nostra/verify_added_liquidity.rs b/src/endpoints/quests/nostra/liquidity_quest/verify_added_liquidity.rs similarity index 95% rename from src/endpoints/quests/nostra/verify_added_liquidity.rs rename to src/endpoints/quests/nostra/liquidity_quest/verify_added_liquidity.rs index 9208fb21..9e4fade1 100644 --- a/src/endpoints/quests/nostra/verify_added_liquidity.rs +++ b/src/endpoints/quests/nostra/liquidity_quest/verify_added_liquidity.rs @@ -21,7 +21,8 @@ use starknet::{ #[route( get, "/quests/nostra/verify_added_liquidity", - crate::endpoints::quests::nostra::verify_added_liquidity + crate::endpoints::quests::nostra::liquidity_quest::verify_added_liquidity + )] pub async fn handler( State(state): State>, diff --git a/src/endpoints/quests/nostra/mod.rs b/src/endpoints/quests/nostra/mod.rs index 57eade9b..0de6c9b1 100644 --- a/src/endpoints/quests/nostra/mod.rs +++ b/src/endpoints/quests/nostra/mod.rs @@ -1,3 +1,2 @@ -pub mod claimable; -pub mod discord_fw_callback; -pub mod verify_added_liquidity; +pub mod liquidity_quest; +pub mod staking_quest; \ No newline at end of file diff --git a/src/endpoints/quests/nostra/staking_quest/claimable.rs b/src/endpoints/quests/nostra/staking_quest/claimable.rs new file mode 100644 index 00000000..deefbda0 --- /dev/null +++ b/src/endpoints/quests/nostra/staking_quest/claimable.rs @@ -0,0 +1,107 @@ +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 axum_auto_routes::route; +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 = 27; +const TASK_IDS: &[u32] = &[132, 133, 134]; +const LAST_TASK: u32 = TASK_IDS[2]; +const NFT_LEVEL: u32 = 39; + +#[derive(Deserialize)] +pub struct ClaimableQuery { + addr: FieldElement, +} + +#[route( + get, + "/quests/nostra/staking_quest/claimable", + crate::endpoints::quests::nostra::staking_quest::claimable +)] +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/nostra/staking_quest/mod.rs b/src/endpoints/quests/nostra/staking_quest/mod.rs new file mode 100644 index 00000000..934bddc0 --- /dev/null +++ b/src/endpoints/quests/nostra/staking_quest/mod.rs @@ -0,0 +1,3 @@ +pub mod claimable; +pub mod verify_twitter_tw; +pub mod verify_stake; diff --git a/src/endpoints/quests/nostra/staking_quest/verify_stake.rs b/src/endpoints/quests/nostra/staking_quest/verify_stake.rs new file mode 100644 index 00000000..0e463280 --- /dev/null +++ b/src/endpoints/quests/nostra/staking_quest/verify_stake.rs @@ -0,0 +1,59 @@ +use std::sync::Arc; + +use crate::{ + models::{AppState, VerifyQuery}, + utils::{get_error, CompletedTasksTrait}, +}; +use axum::{ + extract::{Query, State}, + http::StatusCode, + response::IntoResponse, + Json, +}; +use axum_auto_routes::route; +use serde_json::json; +use starknet::{ + core::types::{BlockId, BlockTag, FieldElement, FunctionCall}, + macros::selector, + providers::Provider, +}; + +#[route( +get, +"/quests/nostra/staking_quest/verify_stake", +crate::endpoints::quests::nostra::staking_quest::verify_stake +)] +pub async fn handler( + State(state): State>, + Query(query): Query, +) -> impl IntoResponse { + let task_id = 133; + let addr = &query.addr; + let calldata = vec![*addr]; + // get starkname from address + let call_result = state + .provider + .call( + FunctionCall { + contract_address: state.conf.quests.nostra.staking_contract, + entry_point_selector: selector!("balanceOf"), + calldata, + }, + BlockId::Tag(BlockTag::Latest), + ) + .await; + + match call_result { + Ok(result) => { + if result[0] < FieldElement::from_dec_str("10").unwrap() { + get_error("You need to stake atleast 10 STRK".to_string()) + } else { + 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)), + } + } + } + Err(e) => get_error(format!("{}", e)), + } +} diff --git a/src/endpoints/quests/nostra/staking_quest/verify_twitter_tw.rs b/src/endpoints/quests/nostra/staking_quest/verify_twitter_tw.rs new file mode 100644 index 00000000..93a8bad7 --- /dev/null +++ b/src/endpoints/quests/nostra/staking_quest/verify_twitter_tw.rs @@ -0,0 +1,30 @@ +use std::sync::Arc; + +use crate::{ + models::{AppState, VerifyQuery}, + utils::{get_error, CompletedTasksTrait}, +}; +use axum::{ + extract::{Query, State}, + http::StatusCode, + response::IntoResponse, + Json, +}; +use axum_auto_routes::route; +use serde_json::json; + +#[route( +get, +"/quests/nostra/staking_quest/verify_twitter_tw", +crate::endpoints::quests::nostra::staking_quest::verify_twitter_tw +)] +pub async fn handler( + State(state): State>, + Query(query): Query, +) -> impl IntoResponse { + let task_id = 134; + 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/uri.rs b/src/endpoints/quests/uri.rs index 49745c43..70f55e2a 100644 --- a/src/endpoints/quests/uri.rs +++ b/src/endpoints/quests/uri.rs @@ -424,6 +424,17 @@ pub async fn handler( }), ).into_response(), + Some(39) => ( + StatusCode::OK, + Json(TokenURI { + name: "Nostra - LaFamiglia Rose".into(), + description: "A Nostra - LaFamiglia Rose NFT won for successfully finishing the Quest".into(), + image: format!("{}/nostra/rose.webp", state.conf.variables.app_link), + attributes: None, + }), + ).into_response(), + + _ => get_error("Error, this level is not correct".into()), } } diff --git a/src/endpoints/quests/verify_quiz.rs b/src/endpoints/quests/verify_quiz.rs index 20ebf784..911a6d8c 100644 --- a/src/endpoints/quests/verify_quiz.rs +++ b/src/endpoints/quests/verify_quiz.rs @@ -33,6 +33,7 @@ fn get_task_id(quiz_name: &str) -> Option { "braavos" => Some(98), "rhino" => Some(100), "nimbora" => Some(89), + "nostra2" => Some(132), _ => None, } } From 010e29cb250314412be1e279b9bee56eea197d3f Mon Sep 17 00:00:00 2001 From: ayushtom Date: Tue, 12 Mar 2024 23:07:55 +0530 Subject: [PATCH 2/5] feat: add precise checks --- .../nostra/staking_quest/verify_stake.rs | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/endpoints/quests/nostra/staking_quest/verify_stake.rs b/src/endpoints/quests/nostra/staking_quest/verify_stake.rs index 0e463280..60b238fd 100644 --- a/src/endpoints/quests/nostra/staking_quest/verify_stake.rs +++ b/src/endpoints/quests/nostra/staking_quest/verify_stake.rs @@ -29,14 +29,35 @@ pub async fn handler( ) -> impl IntoResponse { let task_id = 133; let addr = &query.addr; - let calldata = vec![*addr]; - // get starkname from address + let balance_calldata = vec![*addr]; + let balance_result = state + .provider + .call( + FunctionCall { + contract_address: state.conf.quests.nostra.staking_contract, + entry_point_selector: selector!("balance_of"), + calldata: balance_calldata, + }, + BlockId::Tag(BlockTag::Latest), + ) + .await; + + let user_balance = match balance_result { + Ok(result) => result[0], + Err(e) => return get_error(format!("{}", e)), + }; + + if user_balance == FieldElement::ZERO { + return get_error("You didn't stake any STRK.".to_string()); + } + + let calldata = vec![user_balance]; let call_result = state .provider .call( FunctionCall { contract_address: state.conf.quests.nostra.staking_contract, - entry_point_selector: selector!("balanceOf"), + entry_point_selector: selector!("convert_to_assets"), calldata, }, BlockId::Tag(BlockTag::Latest), From 622666868f6b4bebe0b183165cb6cd637094f383 Mon Sep 17 00:00:00 2001 From: ayushtom Date: Tue, 12 Mar 2024 23:10:12 +0530 Subject: [PATCH 3/5] chore: remove logs --- src/endpoints/get_quiz.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/endpoints/get_quiz.rs b/src/endpoints/get_quiz.rs index e7169242..272a0d1b 100644 --- a/src/endpoints/get_quiz.rs +++ b/src/endpoints/get_quiz.rs @@ -40,7 +40,6 @@ pub async fn handler( Query(query): Query, ) -> impl IntoResponse { let quizzes_from_config = &state.conf.quizzes; - println!("{:?}", query.id); match quizzes_from_config.get(&query.id) { Some(quiz) => { let questions: Vec = quiz From 72941c80c55560357d93cbda5f475e2144112927 Mon Sep 17 00:00:00 2001 From: ayushtom Date: Tue, 12 Mar 2024 23:22:33 +0530 Subject: [PATCH 4/5] feat: add calldata for converting to assets read call --- src/endpoints/quests/nostra/staking_quest/verify_stake.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/endpoints/quests/nostra/staking_quest/verify_stake.rs b/src/endpoints/quests/nostra/staking_quest/verify_stake.rs index 60b238fd..dc19c53d 100644 --- a/src/endpoints/quests/nostra/staking_quest/verify_stake.rs +++ b/src/endpoints/quests/nostra/staking_quest/verify_stake.rs @@ -42,7 +42,7 @@ pub async fn handler( ) .await; - let user_balance = match balance_result { + let user_balance = match &balance_result { Ok(result) => result[0], Err(e) => return get_error(format!("{}", e)), }; @@ -51,14 +51,13 @@ pub async fn handler( return get_error("You didn't stake any STRK.".to_string()); } - let calldata = vec![user_balance]; let call_result = state .provider .call( FunctionCall { contract_address: state.conf.quests.nostra.staking_contract, entry_point_selector: selector!("convert_to_assets"), - calldata, + calldata: balance_result.unwrap().to_vec(), }, BlockId::Tag(BlockTag::Latest), ) From cf0be5c0a926dcf128063cf81fb25d5123652dcb Mon Sep 17 00:00:00 2001 From: ayushtom Date: Tue, 12 Mar 2024 23:45:15 +0530 Subject: [PATCH 5/5] feat: add nostra campaign details --- config.template.toml | 41 ++++++++++++++++++++++++++++++++++++- src/endpoints/quests/uri.rs | 6 +++--- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/config.template.toml b/config.template.toml index e239e308..c633f4d2 100644 --- a/config.template.toml +++ b/config.template.toml @@ -49,6 +49,7 @@ api_key = "xxxxxx" [quests.nostra] utils_contract = "0xXXXXXXXXXXXX" pairs = ["0xXXXXXXXXXXXX"] +staking_contract="0xXXXXXXXXXXXX" [rhino] api_endpoint = "xxxxx" @@ -1045,4 +1046,42 @@ options = [ "LUSD 750", "LUSD 100" ] -correct_answers = [*] \ No newline at end of file +correct_answers = [*] + + +[quizzes.nostra2] +name = "Nostra Quiz" +desc = "Take part in our Quiz to test your knowledge about Nostra, and you'll have a chance to win 250 STRK." +intro = "Starknet Quest Quiz Rounds, a quiz series designed to make Starknet ecosystem knowledge accessible and enjoyable for all. Test your understanding of the workings of Nostra, enjoy the experience, and earn an exclusive NFT reward by testing your knowledge about Starknet Ecosystem projects!" + +[[quizzes.nostra2.questions]] +kind = "text_choice" +layout = "default" +question = "What is nstSTRK?" +options = [ + "The first liquid staking token on Starknet", + "A stablecoin on Nostra Money Market", + "A governance token for Nostra", +] +correct_answers = [*] + +[[quizzes.nostra2.questions]] +kind = "text_choice" +layout = "default" +question = "Will users be able to use their Nostra Staked STRK (nstSTRK) in DeFi protocols on Starknet?" +options = [ + "Yes - those who integrate with nstSTRK", + "No - it’s impossible", +] +correct_answers = [*] + +[[quizzes.nostra2.questions]] +kind = "text_choice" +layout = "default" +question = "What is the total value of idle STRK tokens on Starknet waiting to be staked on Nostra?" +options = [ + "$951 million", + "$100 million", + "$500 million", +] +correct_answers = [*] diff --git a/src/endpoints/quests/uri.rs b/src/endpoints/quests/uri.rs index 70f55e2a..d3000098 100644 --- a/src/endpoints/quests/uri.rs +++ b/src/endpoints/quests/uri.rs @@ -427,9 +427,9 @@ pub async fn handler( Some(39) => ( StatusCode::OK, Json(TokenURI { - name: "Nostra - LaFamiglia Rose".into(), - description: "A Nostra - LaFamiglia Rose NFT won for successfully finishing the Quest".into(), - image: format!("{}/nostra/rose.webp", state.conf.variables.app_link), + name: "Nostra - Mafia Boss Cigar NFT".into(), + description: "A Nostra - Mafia Boss Cigar NFT won for successfully finishing the Quest".into(), + image: format!("{}/nostra/cigar.webp", state.conf.variables.app_link), attributes: None, }), ).into_response(),