Skip to content

Commit

Permalink
Merge pull request #219 from starknet-id/ayush/bountive
Browse files Browse the repository at this point in the history
feat: add bountive quest
  • Loading branch information
Th0rgal authored May 29, 2024
2 parents b9970cc + 9c04427 commit ead556f
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 1 deletion.
42 changes: 42 additions & 0 deletions config.template.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ staking_contract="0xXXXXXXXXXXXX"
api_endpoint = "xxxxxx"
[quests.nimbora]
contract = "0xXXXXXXXXXXXX"
[quests.bountive]
contract = "0xXXXXXXXXXXXX"

[rhino]
api_endpoint = "xxxxx"
Expand Down Expand Up @@ -1129,3 +1131,43 @@ options = [
"Yes",
]
correct_answers = [*]

[quizzes.bountive]
name = "Jackpot Journey quiz"
desc = "Bountive an innovative solution to save & invest your digital assets"
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 Bountive, enjoy the experience, and earn an exclusive NFT reward by testing your knowledge about Starknet Ecosystem projects!"

[[quizzes.bountive.questions]]
kind = "text_choice"
layout = "default"
question = "What is our protocol for?"
options = [
"Trading Cryptocurrencies on Starknet",
"Hosting Decentralized Auctions on Starknet",
"Decentralized no-loss prize savings protocol on Starknet",
"Creating NFT Marketplaces on Starknet",
]
correct_answers = [*]

[[quizzes.bountive.questions]]
kind = "text_choice"
layout = "default"
question = "What raffles are not available on Bountive?"
options = [
"Core Raffles",
"Scam Raffles",
"Sponsored Raffles",
"Event Raffles",
]
correct_answers = [*]

[[quizzes.bountive.questions]]
kind = "text_choice"
layout = "default"
question = "What is the killer feature of Bountive?"
options = [
"High Interest Rates",
"No-loss",
"Advanced Trading Tools"
]
correct_answers = [*]
3 changes: 2 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ pub_struct!(Clone, Deserialize; Quests {
carbonable: Contract,
hashstack: TokenAndContract,
haiko: PublicApi,
nimbora: Contract
nimbora: Contract,
bountive: Contract
});

pub_struct!(Clone, Deserialize; Twitter {
Expand Down
109 changes: 109 additions & 0 deletions src/endpoints/quests/bountive/claimable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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 = 30;
const TASK_IDS: &[u32] = &[145, 146, 147, 148];
const LAST_TASK: u32 = TASK_IDS[3];
const NFT_LEVEL: u32 = 42;

#[derive(Deserialize)]
pub struct ClaimableQuery {
addr: FieldElement,
}

#[route(
get,
"/quests/bountive/claimable",
crate::endpoints::quests::bountive::claimable
)]
pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<ClaimableQuery>,
) -> impl IntoResponse {
let collection = state
.db
.collection::<CompletedTaskDocument>("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()),
}
}
4 changes: 4 additions & 0 deletions src/endpoints/quests/bountive/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod verify_twitter_fw;
pub mod verify_twitter_rt;
pub mod verify_deposit;
pub mod claimable;
60 changes: 60 additions & 0 deletions src/endpoints/quests/bountive/verify_deposit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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/bountive/verify_deposit",
crate::endpoints::quests::bountive::verify_deposit
)]
pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<VerifyQuery>,
) -> impl IntoResponse {
let task_id = 147;
let addr = &query.addr;
let calldata = vec![*addr];

let call_result = state
.provider
.call(
FunctionCall {
contract_address: state.conf.quests.bountive.contract,
entry_point_selector: selector!("shares"),
calldata,
},
BlockId::Tag(BlockTag::Latest),
)
.await;


match call_result {
Ok(result) => {
if result[0] == FieldElement::ZERO {
get_error("You didn't invest on bountive.".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)),
}
}
30 changes: 30 additions & 0 deletions src/endpoints/quests/bountive/verify_twitter_fw.rs
Original file line number Diff line number Diff line change
@@ -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/bountive/verify_twitter_fw",
crate::endpoints::quests::bountive::verify_twitter_fw
)]
pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<VerifyQuery>,
) -> impl IntoResponse {
let task_id = 146;
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)),
}
}
30 changes: 30 additions & 0 deletions src/endpoints/quests/bountive/verify_twitter_rt.rs
Original file line number Diff line number Diff line change
@@ -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/bountive/verify_twitter_rt",
crate::endpoints::quests::bountive::verify_twitter_rt
)]
pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<VerifyQuery>,
) -> impl IntoResponse {
let task_id = 148;
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)),
}
}
1 change: 1 addition & 0 deletions src/endpoints/quests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ pub mod rango;
pub mod nimbora;
pub mod hashstack;
pub mod haiko;
pub mod bountive;
10 changes: 10 additions & 0 deletions src/endpoints/quests/uri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,16 @@ pub async fn handler(
}),
).into_response(),

Some(42) => (
StatusCode::OK,
Json(TokenURI {
name: "Bountive Jackpot Journey NFT ".into(),
description: "A Bountive Jackpot Journey won for successfully finishing the Quest".into(),
image: format!("{}/bountive/bountiveJackpot.webp", state.conf.variables.app_link),
attributes: None,
}),
).into_response(),


_ => get_error("Error, this level is not correct".into()),
}
Expand Down
1 change: 1 addition & 0 deletions src/endpoints/quests/verify_quiz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ fn get_task_id(quiz_name: &str) -> Option<u32> {
"nimbora" => Some(89),
"nostra2" => Some(132),
"haiko" => Some(140),
"bountive" => Some(145),
_ => None,
}
}
Expand Down

0 comments on commit ead556f

Please sign in to comment.