Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement verify_default #71

Merged
merged 2 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion config.template.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,14 @@ layout = "illustrated_left"
question = "Which number is represented on the image?"
options = ["1", "2"]
correct_answers = [0]
image_for_layout = "/example/one.webp"
image_for_layout = "/example/one.webp"

[starkscan]
api_key = "xxxxxx"


[achievements]
[achievements.braavos]
contract = "0x00057c4b510d66eb1188a7173f31cccee47b9736d40185da8144377b896d5ff3"
[achievements.argent]
contract = "0x01b22f7a9d18754c994ae0ee9adb4628d414232e3ebd748c386ac286f86c3066"
3 changes: 2 additions & 1 deletion src/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod verify_has_nft;
pub mod verify_has_root_domain;
pub mod verify_quiz;
pub mod verify_quiz;
61 changes: 61 additions & 0 deletions src/common/verify_has_nft.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use crate::{config::Config, models::StarkscanQuery, utils::to_hex};
use starknet::core::types::FieldElement;

pub async fn execute_has_nft(
config: &Config,
addr: FieldElement,
contract: FieldElement,
limit: u32,
) -> bool {
let url = format!(
"https://api.starkscan.co/api/v0/nfts?contract_address={}&owner_address={}",
to_hex(contract),
to_hex(addr)
);
let client = reqwest::Client::new();
match client
.get(&url)
.header("accept", "application/json")
.header("x-api-key", config.starkscan.api_key.clone())
.send()
.await
{
Ok(response) => {
match response.text().await {
Ok(text) => {
match serde_json::from_str::<StarkscanQuery>(&text) {
Ok(res) => {
// Remove duplicates
let nft_data = res.data;
let mut unique_nfts: Vec<String> = Vec::new();
for nft in nft_data {
if nft.name.is_some() {
let name = nft.name.unwrap();
if !unique_nfts.contains(&name) {
unique_nfts.push(name);
}
}
}
unique_nfts.len() >= limit as usize
}
Err(e) => {
println!("Failed to deserialize result from Starkscan API: {}", e);
false
}
}
}
Err(e) => {
println!(
"Failed to get JSON response while fetching user NFT data: {}",
e
);
false
}
}
}
Err(e) => {
println!("Failed to fetch user NFTs from API: {}", e);
false
}
}
}
19 changes: 19 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,23 @@ pub_struct!(Clone, Deserialize; Quiz {
questions: Vec<QuizQuestion>,
});

pub_struct!(Clone, Deserialize; Starkscan {
api_key: String,
});

pub_struct!(Clone, Deserialize; Braavos {
contract: FieldElement,
});

pub_struct!(Clone, Deserialize; Argent {
contract: FieldElement,
});

pub_struct!(Clone, Deserialize; Achievements {
braavos: Braavos,
argent: Argent,
});

pub_struct!(Clone, Deserialize; Config {
server: Server,
database: Database,
Expand All @@ -102,6 +119,8 @@ pub_struct!(Clone, Deserialize; Config {
twitter: Twitter,
discord: Discord,
quizzes: HashMap<String, Quiz>,
starkscan: Starkscan,
achievements: Achievements,
});

pub fn load() -> Config {
Expand Down
5 changes: 4 additions & 1 deletion src/endpoints/achievements/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ pub async fn handler(
"_id": 0,
"category_name": "$name",
"category_desc": "$desc",
"category_img_url": "$img_url",
"achievements": {
"id": "$achievement.id",
"name": "$achievement.name",
"short_desc": "$achievement.short_desc",
"title": {
Expand All @@ -78,14 +80,15 @@ pub async fn handler(
},
doc! {
"$group": {
"_id": { "category_name": "$category_name", "category_desc": "$category_desc" },
"_id": { "category_name": "$category_name", "category_desc": "$category_desc", "category_img_url": "$category_img_url" },
"achievements": { "$push": "$achievements" }
}
},
doc! {
"$project": {
"category_name": "$_id.category_name",
"category_desc": "$_id.category_desc",
"category_img_url": "$_id.category_img_url",
"achievements": 1,
"_id": 0
}
Expand Down
41 changes: 30 additions & 11 deletions src/endpoints/achievements/verify_default.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::sync::Arc;

use crate::{
common::verify_has_nft::execute_has_nft,
config::Config,
models::{AchievedDocument, AppState, VerifyAchievementQuery},
utils::{get_error, AchievementsTrait},
};
Expand All @@ -14,6 +16,20 @@ use mongodb::bson::doc;
use serde_json::json;
use starknet::core::types::FieldElement;

fn get_args(config: Config, achievement_id: u32) -> Result<(FieldElement, u32), String> {
match achievement_id {
// ArgentX Xplorer NFTs
1 => Ok((config.achievements.argent.contract, 1)),
2 => Ok((config.achievements.argent.contract, 4)),
3 => Ok((config.achievements.argent.contract, 8)),
// Braavos Journey NFTs
4 => Ok((config.achievements.braavos.contract, 1)),
5 => Ok((config.achievements.braavos.contract, 3)),
6 => Ok((config.achievements.braavos.contract, 6)),
_ => Err("Invalid achievement ID".to_string()),
}
}

pub async fn handler(
State(state): State<Arc<AppState>>,
Query(query): Query<VerifyAchievementQuery>,
Expand All @@ -30,19 +46,22 @@ pub async fn handler(
};
match achieved_collection.find_one(filter, None).await {
Ok(Some(_)) => (StatusCode::OK, Json(json!({"achieved": true}))).into_response(),
Ok(None) => match state.get_achievement(achievement_id).await {
Ok(Some(achievement)) => {
// todo: add verifying logic here
match state
.upsert_completed_achievement(addr, achievement_id)
.await
{
Ok(_) => (StatusCode::OK, Json(json!({"achieved": true}))).into_response(),
Err(e) => get_error(format!("{}", e)),
Ok(None) => match get_args(state.conf.clone(), achievement_id) {
Ok((contract, limit)) => {
let is_achieved = execute_has_nft(&state.conf, addr, contract, limit).await;
if is_achieved {
match state
.upsert_completed_achievement(addr, achievement_id)
.await
{
Ok(_) => (StatusCode::OK, Json(json!({"achieved": true}))).into_response(),
Err(e) => get_error(format!("{}", e)),
}
} else {
(StatusCode::OK, Json(json!({"achieved": false}))).into_response()
}
}
Ok(None) => get_error("Achievement not found".to_string()),
Err(e) => get_error(format!("Error querying achievement : {}", e)),
Err(e) => get_error(e),
},
Err(e) => get_error(format!("Error querying user achievement : {}", e)),
}
Expand Down
34 changes: 34 additions & 0 deletions src/models.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use mongodb::{bson, Database};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use starknet::{core::types::FieldElement, providers::SequencerGatewayProvider};

use crate::config::Config;
Expand Down Expand Up @@ -98,19 +99,52 @@ pub_struct!(Debug, Serialize, Deserialize; AchievementCategoryDocument {
id: u32,
name: String,
desc: String,
img_url: String,
});

pub_struct!(Debug, Serialize, Deserialize; UserAchievements {
category_name: String,
category_desc: String,
category_img_url: String,
achievements: Vec<UserAchievement>,
});

pub_struct!(Debug, Serialize, Deserialize; UserAchievement {
id: u32,
name: String,
short_desc: String,
title: String,
desc: String,
completed: bool,
verify_type: String,
});

pub_struct!(Debug, Serialize, Deserialize; NftBalance {
contract_address: String,
token_id: String,
owner_address: String,
balance: String,
});

pub_struct!(Debug, Serialize, Deserialize; Nft {
nft_id: String,
contract_address: String,
token_id: String,
name: Option<String>,
description: Option<String>,
external_url: Option<String>,
attributes: Option<Value>,
image_url: Option<String>,
image_small_url: Option<String>,
image_medium_url: Option<String>,
animation_url: Option<String>,
minted_by_address: String,
minted_at_transaction_hash: String,
minted_at_timestamp: i64,
balance: Option<NftBalance>,
});

pub_struct!(Debug, Serialize, Deserialize; StarkscanQuery {
next_url: Option<String>,
data: Vec<Nft>,
});