1use serde::de::DeserializeOwned;
2use serde::Serialize;
3
4use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
5
6use cw2::set_contract_version;
7use cw721::{ContractInfoResponse, CustomMsg, Cw721Execute, Cw721ReceiveMsg, Expiration};
8
9use crate::error::ContractError;
10use crate::msg::{ExecuteMsg, InstantiateMsg, MintMsg};
11use crate::state::{Approval, Cw721Contract, TokenInfo};
12
13const CONTRACT_NAME: &str = "crates.io:cw721-base";
15const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
16
17impl<'a, T, C> Cw721Contract<'a, T, C>
18where
19 T: Serialize + DeserializeOwned + Clone,
20 C: CustomMsg,
21{
22 pub fn instantiate(
23 &self,
24 deps: DepsMut,
25 _env: Env,
26 _info: MessageInfo,
27 msg: InstantiateMsg,
28 ) -> StdResult<Response<C>> {
29 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
30
31 let info = ContractInfoResponse {
32 name: msg.name,
33 symbol: msg.symbol,
34 };
35 self.contract_info.save(deps.storage, &info)?;
36 let minter = deps.api.addr_validate(&msg.minter)?;
37 self.minter.save(deps.storage, &minter)?;
38 Ok(Response::default())
39 }
40
41 pub fn execute(
42 &self,
43 deps: DepsMut,
44 env: Env,
45 info: MessageInfo,
46 msg: ExecuteMsg<T>,
47 ) -> Result<Response<C>, ContractError> {
48 match msg {
49 ExecuteMsg::Mint(msg) => self.mint(deps, env, info, msg),
50 ExecuteMsg::Approve {
51 spender,
52 token_id,
53 expires,
54 } => self.approve(deps, env, info, spender, token_id, expires),
55 ExecuteMsg::Revoke { spender, token_id } => {
56 self.revoke(deps, env, info, spender, token_id)
57 }
58 ExecuteMsg::ApproveAll { operator, expires } => {
59 self.approve_all(deps, env, info, operator, expires)
60 }
61 ExecuteMsg::RevokeAll { operator } => self.revoke_all(deps, env, info, operator),
62 ExecuteMsg::TransferNft {
63 recipient,
64 token_id,
65 } => self.transfer_nft(deps, env, info, recipient, token_id),
66 ExecuteMsg::SendNft {
67 contract,
68 token_id,
69 msg,
70 } => self.send_nft(deps, env, info, contract, token_id, msg),
71 }
72 }
73}
74
75impl<'a, T, C> Cw721Contract<'a, T, C>
77where
78 T: Serialize + DeserializeOwned + Clone,
79 C: CustomMsg,
80{
81 pub fn mint(
82 &self,
83 deps: DepsMut,
84 _env: Env,
85 info: MessageInfo,
86 msg: MintMsg<T>,
87 ) -> Result<Response<C>, ContractError> {
88 let minter = self.minter.load(deps.storage)?;
89
90 if info.sender != minter {
91 return Err(ContractError::Unauthorized {});
92 }
93
94 let token = TokenInfo {
96 owner: deps.api.addr_validate(&msg.owner)?,
97 approvals: vec![],
98 token_uri: msg.token_uri,
99 extension: msg.extension,
100 };
101 self.tokens
102 .update(deps.storage, &msg.token_id, |old| match old {
103 Some(_) => Err(ContractError::Claimed {}),
104 None => Ok(token),
105 })?;
106
107 self.increment_tokens(deps.storage)?;
108
109 Ok(Response::new()
110 .add_attribute("action", "mint")
111 .add_attribute("minter", info.sender)
112 .add_attribute("token_id", msg.token_id))
113 }
114}
115
116impl<'a, T, C> Cw721Execute<T, C> for Cw721Contract<'a, T, C>
117where
118 T: Serialize + DeserializeOwned + Clone,
119 C: CustomMsg,
120{
121 type Err = ContractError;
122
123 fn transfer_nft(
124 &self,
125 deps: DepsMut,
126 env: Env,
127 info: MessageInfo,
128 recipient: String,
129 token_id: String,
130 ) -> Result<Response<C>, ContractError> {
131 self._transfer_nft(deps, &env, &info, &recipient, &token_id)?;
132
133 Ok(Response::new()
134 .add_attribute("action", "transfer_nft")
135 .add_attribute("sender", info.sender)
136 .add_attribute("recipient", recipient)
137 .add_attribute("token_id", token_id))
138 }
139
140 fn send_nft(
141 &self,
142 deps: DepsMut,
143 env: Env,
144 info: MessageInfo,
145 contract: String,
146 token_id: String,
147 msg: Binary,
148 ) -> Result<Response<C>, ContractError> {
149 self._transfer_nft(deps, &env, &info, &contract, &token_id)?;
151
152 let send = Cw721ReceiveMsg {
153 sender: info.sender.to_string(),
154 token_id: token_id.clone(),
155 msg,
156 };
157
158 Ok(Response::new()
160 .add_message(send.into_cosmos_msg(contract.clone())?)
161 .add_attribute("action", "send_nft")
162 .add_attribute("sender", info.sender)
163 .add_attribute("recipient", contract)
164 .add_attribute("token_id", token_id))
165 }
166
167 fn approve(
168 &self,
169 deps: DepsMut,
170 env: Env,
171 info: MessageInfo,
172 spender: String,
173 token_id: String,
174 expires: Option<Expiration>,
175 ) -> Result<Response<C>, ContractError> {
176 self._update_approvals(deps, &env, &info, &spender, &token_id, true, expires)?;
177
178 Ok(Response::new()
179 .add_attribute("action", "approve")
180 .add_attribute("sender", info.sender)
181 .add_attribute("spender", spender)
182 .add_attribute("token_id", token_id))
183 }
184
185 fn revoke(
186 &self,
187 deps: DepsMut,
188 env: Env,
189 info: MessageInfo,
190 spender: String,
191 token_id: String,
192 ) -> Result<Response<C>, ContractError> {
193 self._update_approvals(deps, &env, &info, &spender, &token_id, false, None)?;
194
195 Ok(Response::new()
196 .add_attribute("action", "revoke")
197 .add_attribute("sender", info.sender)
198 .add_attribute("spender", spender)
199 .add_attribute("token_id", token_id))
200 }
201
202 fn approve_all(
203 &self,
204 deps: DepsMut,
205 env: Env,
206 info: MessageInfo,
207 operator: String,
208 expires: Option<Expiration>,
209 ) -> Result<Response<C>, ContractError> {
210 let expires = expires.unwrap_or_default();
212 if expires.is_expired(&env.block) {
213 return Err(ContractError::Expired {});
214 }
215
216 let operator_addr = deps.api.addr_validate(&operator)?;
218 self.operators
219 .save(deps.storage, (&info.sender, &operator_addr), &expires)?;
220
221 Ok(Response::new()
222 .add_attribute("action", "approve_all")
223 .add_attribute("sender", info.sender)
224 .add_attribute("operator", operator))
225 }
226
227 fn revoke_all(
228 &self,
229 deps: DepsMut,
230 _env: Env,
231 info: MessageInfo,
232 operator: String,
233 ) -> Result<Response<C>, ContractError> {
234 let operator_addr = deps.api.addr_validate(&operator)?;
235 self.operators
236 .remove(deps.storage, (&info.sender, &operator_addr));
237
238 Ok(Response::new()
239 .add_attribute("action", "revoke_all")
240 .add_attribute("sender", info.sender)
241 .add_attribute("operator", operator))
242 }
243}
244
245impl<'a, T, C> Cw721Contract<'a, T, C>
247where
248 T: Serialize + DeserializeOwned + Clone,
249 C: CustomMsg,
250{
251 pub fn _transfer_nft(
252 &self,
253 deps: DepsMut,
254 env: &Env,
255 info: &MessageInfo,
256 recipient: &str,
257 token_id: &str,
258 ) -> Result<TokenInfo<T>, ContractError> {
259 let mut token = self.tokens.load(deps.storage, &token_id)?;
260 self.check_can_send(deps.as_ref(), env, info, &token)?;
262 token.owner = deps.api.addr_validate(recipient)?;
264 token.approvals = vec![];
265 self.tokens.save(deps.storage, &token_id, &token)?;
266 Ok(token)
267 }
268
269 #[allow(clippy::too_many_arguments)]
270 pub fn _update_approvals(
271 &self,
272 deps: DepsMut,
273 env: &Env,
274 info: &MessageInfo,
275 spender: &str,
276 token_id: &str,
277 add: bool,
279 expires: Option<Expiration>,
280 ) -> Result<TokenInfo<T>, ContractError> {
281 let mut token = self.tokens.load(deps.storage, &token_id)?;
282 self.check_can_approve(deps.as_ref(), env, info, &token)?;
284
285 let spender_addr = deps.api.addr_validate(spender)?;
287 token.approvals = token
288 .approvals
289 .into_iter()
290 .filter(|apr| apr.spender != spender_addr)
291 .collect();
292
293 if add {
295 let expires = expires.unwrap_or_default();
297 if expires.is_expired(&env.block) {
298 return Err(ContractError::Expired {});
299 }
300 let approval = Approval {
301 spender: spender_addr,
302 expires,
303 };
304 token.approvals.push(approval);
305 }
306
307 self.tokens.save(deps.storage, &token_id, &token)?;
308
309 Ok(token)
310 }
311
312 pub fn check_can_approve(
314 &self,
315 deps: Deps,
316 env: &Env,
317 info: &MessageInfo,
318 token: &TokenInfo<T>,
319 ) -> Result<(), ContractError> {
320 if token.owner == info.sender {
322 return Ok(());
323 }
324 let op = self
326 .operators
327 .may_load(deps.storage, (&token.owner, &info.sender))?;
328 match op {
329 Some(ex) => {
330 if ex.is_expired(&env.block) {
331 Err(ContractError::Unauthorized {})
332 } else {
333 Ok(())
334 }
335 }
336 None => Err(ContractError::Unauthorized {}),
337 }
338 }
339
340 fn check_can_send(
342 &self,
343 deps: Deps,
344 env: &Env,
345 info: &MessageInfo,
346 token: &TokenInfo<T>,
347 ) -> Result<(), ContractError> {
348 if token.owner == info.sender {
350 return Ok(());
351 }
352
353 if token
355 .approvals
356 .iter()
357 .any(|apr| apr.spender == info.sender && !apr.is_expired(&env.block))
358 {
359 return Ok(());
360 }
361
362 let op = self
364 .operators
365 .may_load(deps.storage, (&token.owner, &info.sender))?;
366 match op {
367 Some(ex) => {
368 if ex.is_expired(&env.block) {
369 Err(ContractError::Unauthorized {})
370 } else {
371 Ok(())
372 }
373 }
374 None => Err(ContractError::Unauthorized {}),
375 }
376 }
377}