spawn_nft/
lib.rs

1use std::collections::{HashMap, HashSet};
2use wasm_bindgen::prelude::*;
3use serde::{Serialize, Deserialize};
4use web_sys::console;
5
6/// A highly optimized library for managing NFTs (Non-Fungible Tokens) in Ethereum-compatible WebAssembly (WASM).
7#[wasm_bindgen]
8pub struct NFTManager {
9    owner: String,
10    nft_classes: HashMap<String, NFTClass>,
11    tokens: HashMap<String, Token>,
12    approvals: HashMap<String, HashSet<String>>, // Mapping for approval management
13}
14
15/// Structure for storing NFT class information
16#[derive(Serialize, Deserialize, Clone)]
17pub struct NFTClass {
18    metadata: String,
19    total_issuance: u32,
20    owner: String,
21}
22
23/// Structure for storing individual NFT token information
24#[derive(Serialize, Deserialize, Clone)]
25pub struct Token {
26    metadata: String,
27    owner: String,
28    approved: Option<String>, // Optional approved account for transfer
29}
30
31#[wasm_bindgen]
32impl NFTManager {
33    /// Initialize a new NFTManager
34    #[wasm_bindgen(constructor)]
35    pub fn new(owner: &str) -> NFTManager {
36        console::log_1(&format!("NFT Manager initialized with owner: {}", owner).into());
37        NFTManager {
38            owner: owner.to_string(),
39            nft_classes: HashMap::new(),
40            tokens: HashMap::new(),
41            approvals: HashMap::new(),
42        }
43    }
44
45    /// Internal helper function to check if the current caller is the contract owner.
46    fn is_owner(&self, caller: &str) -> Result<bool, String> {
47        if caller == self.owner {
48            Ok(true)
49        } else {
50            Err("Caller is not the contract owner".into())
51        }
52    }
53
54    /// Internal helper function to check if the current caller is the owner of the class.
55    fn is_owner_of_class(&self, class_id: &str, caller: &str) -> Result<bool, String> {
56        if let Some(class) = self.nft_classes.get(class_id) {
57            if class.owner == caller {
58                Ok(true)
59            } else {
60                Err("Caller is not the class owner".into())
61            }
62        } else {
63            Err("Class not found".into())
64        }
65    }
66
67    /// Create a new NFT class. Only the contract owner can create classes.
68    pub fn create_class(&mut self, class_id: &str, metadata: &str, caller: &str) -> Result<(), String> {
69        if self.is_owner(caller)? {
70            let class = NFTClass {
71                metadata: metadata.to_string(),
72                total_issuance: 0,
73                owner: caller.to_string(),
74            };
75
76            self.nft_classes.insert(class_id.to_string(), class);
77            console::log_1(&format!("Class created with ID: {}", class_id).into());
78            Ok(())
79        } else {
80            Err("Caller is not authorized to create classes".into())
81        }
82    }
83
84    /// Mint a new token within an NFT class. Only the owner of the class can mint.
85    pub fn mint(&mut self, class_id: &str, token_id: &str, metadata: &str, caller: &str) -> Result<(), String> {
86        if self.is_owner_of_class(class_id, caller)? {
87            let token = Token {
88                metadata: metadata.to_string(),
89                owner: caller.to_string(),
90                approved: None,
91            };
92
93            if let Some(class) = self.nft_classes.get_mut(class_id) {
94                class.total_issuance += 1;
95                self.tokens.insert(token_id.to_string(), token);
96                console::log_1(&format!("Token minted with ID: {}", token_id).into());
97                Ok(())
98            } else {
99                Err("Class not found".into())
100            }
101        } else {
102            Err("Caller is not authorized to mint tokens".into())
103        }
104    }
105
106    /// Transfer a token to another owner. Must be the token owner or approved operator.
107    pub fn transfer(&mut self, token_id: &str, new_owner: &str, caller: &str) -> Result<(), String> {
108        // Önce token sahibini öğrenelim
109        let owner = self.get_owner(token_id)?;
110    
111        // Şimdi mutable borçlanmayı gerçekleştirebiliriz
112        if let Some(token) = self.tokens.get_mut(token_id) {
113            // Sahip doğrulaması
114            if owner == caller {
115                token.owner = new_owner.to_string();
116                token.approved = None; // Clear approvals after transfer
117                console::log_1(&format!("Token {} transferred to {}", token_id, new_owner).into());
118                Ok(())
119            } else {
120                Err("Caller is not the token owner".into())
121            }
122        } else {
123            Err("Token not found.".into())
124        }
125    }
126    
127    
128
129    /// Approve another account to transfer the given token.
130    pub fn approve(&mut self, token_id: &str, approved: &str, caller: &str) -> Result<(), String> {
131        if let Some(token) = self.tokens.get_mut(token_id) {
132            if token.owner != caller {
133                console::log_1(&format!("Failed to approve token {}: Caller is not owner", token_id).into());
134                return Err("Only the owner can approve a transfer.".into());
135            }
136
137            token.approved = Some(approved.to_string());
138            self.approvals.entry(token_id.to_string()).or_default().insert(approved.to_string());
139            console::log_1(&format!("Approval set for token {}: {}", token_id, approved).into());
140            Ok(())
141        } else {
142            console::log_1(&format!("Token with ID: {} not found", token_id).into());
143            Err("Token not found.".into())
144        }
145    }
146
147    /// Burn a token (remove it from circulation). Only the token owner can burn the token.
148    pub fn burn(&mut self, token_id: &str, caller: &str) -> Result<(), String> {
149        if let Some(token) = self.tokens.get(token_id) {
150            if token.owner != caller {
151                console::log_1(&format!("Failed to burn token {}: Caller is not owner", token_id).into());
152                return Err("Only the owner can burn the token.".into());
153            }
154        }
155
156        if let Some(_token) = self.tokens.remove(token_id) {
157            console::log_1(&format!("Token with ID: {} burned", token_id).into());
158            Ok(())
159        } else {
160            console::log_1(&format!("Token with ID: {} not found", token_id).into());
161            Err("Token not found.".into())
162        }
163    }
164
165    /// Check the owner of a token.
166    pub fn get_owner(&self, token_id: &str) -> Result<String, String> {
167        if let Some(token) = self.tokens.get(token_id) {
168            Ok(token.owner.clone())
169        } else {
170            console::log_1(&format!("Token with ID: {} not found", token_id).into());
171            Err("Token not found.".into())
172        }
173    }
174
175    /// Check if an address is approved for the given token.
176    pub fn is_approved(&self, token_id: &str, operator: &str) -> bool {
177        if let Some(approvals) = self.approvals.get(token_id) {
178            approvals.contains(operator)
179        } else {
180            false
181        }
182    }
183
184    /// Set a new owner for the NFTManager contract.
185    pub fn transfer_ownership(&mut self, new_owner: &str, caller: &str) -> Result<(), String> {
186        if self.is_owner(caller)? {
187            self.owner = new_owner.to_string();
188            console::log_1(&format!("Ownership transferred to {}", new_owner).into());
189            Ok(())
190        } else {
191            Err("Caller is not authorized to transfer ownership".into())
192        }
193    }
194}