1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! This module contains the logic for checking achievement eligibility and managing achievements.

use ic_cdk::{query, update};
use candid::Principal;

use crate::state::{get_principal_to_hash_value, update_principal_to_hash, update_principal_to_achievement_status};
use crate::ecdsa::{public_key, build_principals_message, sign, verify};
use crate::storable::{Signature, AchievementStatusEnum, AchievementStatus};

/// Checks if a principal is eligible for an achievement.
///
/// # Arguments
///
/// * `principal` - The principal to check.
/// * `blob` - Additional data for eligibility check.
///
/// # Returns
///
/// * `Result<bool, String>` - `true` if the principal is eligible, `false` otherwise.
/// 
/// # Example
/// 
/// ## IC-reactor usage
/// ```typescript
///     const { data: eligible, call: fetchEligigble }: { data: any, call: any } = useUpdateCall({
///         functionName: "checkAchievementEligibility",
///         args: [
///             identity?.getPrincipal(),
///             []
///         ]
///     })
/// ```
#[update(name = "checkAchievementEligibility")]
async fn check_achievement_eligibility(principal: Principal, blob: Vec<u8>) -> Result<bool, String> {

    // Your conditions for achievement
    // Example of calling another canister

    let example_backend_canister_id = Principal::from_text("aax3a-h4aaa-aaaaa-qaahq-cai").unwrap();

    let is_posted: (bool, ) = ic_cdk::call(example_backend_canister_id, "getPrincipalToIsPosted", (principal,)).await.unwrap();

    Ok(is_posted.0)
}

/// Generates a hash for the caller's identity wallet.
///
/// # Arguments
///
/// * `identity_wallet` - The principal of the identity wallet.
/// * `blob` - Additional data for hash generation.
///
/// # Returns
///
/// * `Result<String, String>` - The result of the hash generation.
///
/// # Example
///
/// ```
/// dfx --identity pa_local_wallet canister call achievement generateHashToIdentityWallet "(principal \"$(dfx --identity pa_identity_wallet identity get-principal)\", vec {})"
///
/// (
///   variant {
///     Ok = "Succesfully generate hash for Identity Wallet. Signature 5ac9cae0bd534ee09eea7bf9ddd85a53ba13efe9a416fb13155b46fa2af2f3f0671b2b79c534a29ade73811098cb947ccbd606b935aa1e0610093eac3b3ddc00"
///   },
/// )
/// ```
///
/// ## IC-reactor usage
/// ```typescript
/// const { call: generateHash }: { call: any} = useUpdateCall({
///     functionName: "generateHashToIdentityWallet",
///     args: [
///         Principal.fromText(identity_wallet as string || identity!.getPrincipal()!.toText()),
///         []
///     ]
/// })
/// ```
#[update(name = "generateHashToIdentityWallet")]
async fn generate_hash_to_identity_wallet(identity_wallet: Principal, blob: Vec<u8>) -> Result<String, String> {
    let caller = ic_cdk::api::caller();
    let eligibility = check_achievement_eligibility(caller, blob).await.unwrap();

    if eligibility {
        let message = build_principals_message(caller, identity_wallet);
        let signature = sign(message).await?;

        update_principal_to_hash(caller, Signature(signature.clone().signature_hex))?;

        Ok(String::from(format!("Succesfully generate hash for Identity Wallet. Signature {}", signature.signature_hex)))
    } else {
        Err(String::from("Caller principal is not eligible"))
    }
}

/// Receives an achievement for the caller's identity wallet.
///
/// # Arguments
///
/// * `blob` - Additional data for receiving the achievement.
///
/// # Returns
///
/// * `Result<String, String>` - The result of the achievement reception.
///
/// # Example
///
/// ```
/// dfx --identity pa_identity_wallet canister call reputation_module issueAchievementToIdentityWallet "(principal \"$(dfx canister id achievement)\")"
///
/// (variant { Ok = "Achievement issued" })
/// ```
#[update(name = "receiveAchievementFromIdentityWallet")]
async fn receive_achievement_from_identity_wallet(blob: Vec<u8>) -> Result<String, String> {
    let caller = ic_cdk::api::caller();
    let eligibility = check_achievement_eligibility(caller, blob).await.unwrap();

    if eligibility {
        let allowed_status = AchievementStatusEnum::Allowed;
        update_principal_to_achievement_status(caller, AchievementStatus(allowed_status.to_u8()))?;

        Ok(String::from("Achievement status changed to allowed"))
    } else {
        Err(String::from("Caller principal is not eligible"))
    }
}

/// Receives an achievement for the caller's identity wallet using a hash.
///
/// # Arguments
///
/// * `principal` - The principal of the identity wallet.
///
/// # Returns
///
/// * `Result<String, String>` - The result of the achievement reception.
///
/// # Example
///
/// ```
/// dfx --identity pa_identity_wallet canister call achievement receiveAchievementFromIdentityWalletWithHash "(principal \"$(dfx --identity pa_local_wallet identity get-principal)\")"
///
/// (variant { Ok = "Achievement status changed to allowed" })
/// ```
#[update(name = "receiveAchievementFromIdentityWalletWithHash")]
async fn receive_achievement_from_identity_wallet_with_hash(principal: Principal) -> Result<String, String> {
    let caller = ic_cdk::api::caller();
    let hash = get_principal_to_hash_value(principal)?;
    let public_key = public_key().await?;
    let message = build_principals_message(principal, caller);
    let eligibility = verify(hash.0, message, public_key.public_key_hex).await?;

    if eligibility.is_signature_valid {
        let allowed_status = AchievementStatusEnum::Allowed;
        update_principal_to_achievement_status(caller, AchievementStatus(allowed_status.to_u8()))?;

        Ok(String::from("Achievement status changed to allowed"))
    } else {
        Err(String::from("Principal is not eligible or hash mismatch"))
    }
}