Skip to content

Commit

Permalink
Merge pull request #113 from starknet-id/feat/add_element_briq_quests
Browse files Browse the repository at this point in the history
feat: add Layerswap quest
  • Loading branch information
Th0rgal authored Oct 25, 2023
2 parents f573cac + b9551ab commit c419ddb
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 2 deletions.
101 changes: 101 additions & 0 deletions src/endpoints/quests/element/layerswap/claimable.rs
Original file line number Diff line number Diff line change
@@ -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 = 18;
const TASK_IDS: &[u32] = &[70, 71, 72];
const LAST_TASK: u32 = TASK_IDS[2];
const NFT_LEVEL: u32 = 24;

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

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/element/layerswap/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod claimable;
pub mod verify_has_bridged;
pub mod verify_twitter_fw;
pub mod verify_twitter_rt;
76 changes: 76 additions & 0 deletions src/endpoints/quests/element/layerswap/verify_has_bridged.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use std::sync::Arc;

use crate::{
models::{AppState, VerifyQuery},
utils::{get_error, to_hex, CompletedTasksTrait},
};
use axum::{
extract::{Query, State},
http::StatusCode,
response::IntoResponse,
Json,
};
use serde::Deserialize;
use serde_json::json;

#[derive(Debug, Deserialize)]
struct LayerswapResponse {
data: Option<Vec<DataEntry>>,
error: Option<LayerswapError>,
}

#[derive(Debug, Deserialize)]
struct DataEntry {
status: String,
}

#[derive(Debug, Deserialize)]
struct LayerswapError {
message: String,
}

pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<VerifyQuery>,
) -> impl IntoResponse {
let task_id = 70;
let url = format!(
"https://bridge-api.layerswap.io/api/explorer/{}",
to_hex(query.addr)
);
let client = reqwest::Client::new();
let response_result = client.get(url).send().await;
match response_result {
Ok(response) => match response.json::<LayerswapResponse>().await {
Ok(res) => {
if let Some(err) = &res.error {
return get_error(format!("Received error from Layerswap: {}", err.message));
}

// Check if there is data and if any entry has "completed" status
if res
.data
.as_ref()
.unwrap_or(&vec![])
.iter()
.any(|entry| entry.status == "completed")
{
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)),
}
} else {
get_error(
"You haven't bridge any ETH or USDC to Starknet using Layerswap."
.to_string(),
)
}
}
Err(e) => get_error(format!(
"Failed to get JSON response while fetching Layerswap data: {}",
e
)),
},
Err(e) => get_error(format!("Failed to fetch Layerswap api: {}", e)),
}
}
28 changes: 28 additions & 0 deletions src/endpoints/quests/element/layerswap/verify_twitter_fw.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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<Arc<AppState>>,
Query(query): Query<VerifyQuery>,
) -> impl IntoResponse {
let task_id = 71;
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)),
}
}
28 changes: 28 additions & 0 deletions src/endpoints/quests/element/layerswap/verify_twitter_rt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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<Arc<AppState>>,
Query(query): Query<VerifyQuery>,
) -> impl IntoResponse {
let task_id = 72;
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)),
}
}
1 change: 1 addition & 0 deletions src/endpoints/quests/element/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod briq;
pub mod element;
pub mod layerswap;
15 changes: 13 additions & 2 deletions src/endpoints/quests/uri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ pub async fn handler(
StatusCode::OK,
Json(TokenURI {
name: "The Element Gemstone".into(),
description: "An Element Gemstone NFT can be won for successfully finishing the Quest".into(),
description: "An Element Gemstone NFT won for successfully finishing the Quest".into(),
image: format!("{}/element/elementGem.webp", state.conf.variables.app_link),
attributes: None,
}),
Expand All @@ -267,13 +267,24 @@ pub async fn handler(
StatusCode::OK,
Json(TokenURI {
name: "The Briq Element Gemstone".into(),
description: "A Briq Element Gemstone NFT can be won for successfully finishing the Quest".into(),
description: "A Briq Element Gemstone NFT won for successfully finishing the Quest".into(),
image: format!("{}/element/briqGem.webp", state.conf.variables.app_link),
attributes: None,
}),
)
.into_response(),

Some(24) => (
StatusCode::OK,
Json(TokenURI {
name: "The Layerswap Element Gemstone".into(),
description: "A Layerswap Element Gemstone NFT won for successfully finishing the Quest".into(),
image: format!("{}/element/layerswapGem.webp", state.conf.variables.app_link),
attributes: None,
}),
)
.into_response(),

_ => get_error("Error, this level is not correct".into()),
}
}
16 changes: 16 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,22 @@ async fn main() {
"/quests/element/briq/claimable",
get(endpoints::quests::element::briq::claimable::handler),
)
.route(
"/quests/element/layerswap/verify_has_bridged",
get(endpoints::quests::element::layerswap::verify_has_bridged::handler),
)
.route(
"/quests/element/layerswap/verify_twitter_fw",
get(endpoints::quests::element::layerswap::verify_twitter_fw::handler),
)
.route(
"/quests/element/layerswap/verify_twitter_rt",
get(endpoints::quests::element::layerswap::verify_twitter_rt::handler),
)
.route(
"/quests/element/layerswap/claimable",
get(endpoints::quests::element::layerswap::claimable::handler),
)
.route(
"/achievements/verify_default",
get(endpoints::achievements::verify_default::handler),
Expand Down

0 comments on commit c419ddb

Please sign in to comment.