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_updatable::{ContractInfoResponse, CustomMsg, Cw721Execute, Cw721ReceiveMsg, Expiration};
8
9use crate::error::ContractError;
10use crate::msg::{ExecuteMsg, InstantiateMsg, MintMsg, UpdateMetadataMsg};
11use crate::state::{Approval, Cw721Contract, TokenInfo};
12
13const CONTRACT_NAME: &str = "crates.io:cw721-updatable";
15const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
16
17impl<'a, T, C, E, Q> Cw721Contract<'a, T, C, E, Q>
18where
19 T: Serialize + DeserializeOwned + Clone,
20 C: CustomMsg,
21 E: CustomMsg,
22 Q: CustomMsg,
23{
24 pub fn instantiate(
25 &self,
26 deps: DepsMut,
27 _env: Env,
28 _info: MessageInfo,
29 msg: InstantiateMsg,
30 ) -> StdResult<Response<C>> {
31 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
32
33 let info = ContractInfoResponse {
34 name: msg.name,
35 symbol: msg.symbol,
36 };
37 self.contract_info.save(deps.storage, &info)?;
38 let minter = deps.api.addr_validate(&msg.minter)?;
39 self.minter.save(deps.storage, &minter)?;
40 Ok(Response::default())
41 }
42
43 pub fn execute(
44 &self,
45 deps: DepsMut,
46 env: Env,
47 info: MessageInfo,
48 msg: ExecuteMsg<T, E>,
49 ) -> Result<Response<C>, ContractError> {
50 match msg {
51 ExecuteMsg::Mint(msg) => self.mint(deps, env, info, msg),
52 ExecuteMsg::Approve {
53 spender,
54 token_id,
55 expires,
56 } => self.approve(deps, env, info, spender, token_id, expires),
57 ExecuteMsg::Revoke { spender, token_id } => {
58 self.revoke(deps, env, info, spender, token_id)
59 }
60 ExecuteMsg::ApproveAll { operator, expires } => {
61 self.approve_all(deps, env, info, operator, expires)
62 }
63 ExecuteMsg::RevokeAll { operator } => self.revoke_all(deps, env, info, operator),
64 ExecuteMsg::TransferNft {
65 recipient,
66 token_id,
67 } => self.transfer_nft(deps, env, info, recipient, token_id),
68 ExecuteMsg::SendNft {
69 contract,
70 token_id,
71 msg,
72 } => self.send_nft(deps, env, info, contract, token_id, msg),
73 ExecuteMsg::Burn { token_id } => self.burn(deps, env, info, token_id),
74 ExecuteMsg::Extension { msg: _ } => Ok(Response::default()),
75
76 ExecuteMsg::UpdateMetadata(msg) => self.update_metadata(deps, env, info, msg),
78 }
79 }
80}
81
82impl<'a, T, C, E, Q> Cw721Contract<'a, T, C, E, Q>
84where
85 T: Serialize + DeserializeOwned + Clone,
86 C: CustomMsg,
87 E: CustomMsg,
88 Q: CustomMsg,
89{
90 pub fn mint(
91 &self,
92 deps: DepsMut,
93 _env: Env,
94 info: MessageInfo,
95 msg: MintMsg<T>,
96 ) -> Result<Response<C>, ContractError> {
97 let minter = self.minter.load(deps.storage)?;
98
99 if info.sender != minter {
100 return Err(ContractError::Unauthorized {});
101 }
102
103 let token = TokenInfo {
105 owner: deps.api.addr_validate(&msg.owner)?,
106 approvals: vec![],
107 token_uri: msg.token_uri,
108 extension: msg.extension,
109 };
110 self.tokens
111 .update(deps.storage, &msg.token_id, |old| match old {
112 Some(_) => Err(ContractError::Claimed {}),
113 None => Ok(token),
114 })?;
115
116 self.increment_tokens(deps.storage)?;
117
118 Ok(Response::new()
119 .add_attribute("action", "mint")
120 .add_attribute("minter", info.sender)
121 .add_attribute("owner", msg.owner)
122 .add_attribute("token_id", msg.token_id))
123 }
124
125 fn update_metadata(
127 &self,
128 deps: DepsMut,
129 env: Env,
130 info: MessageInfo,
131 msg: UpdateMetadataMsg<T>,
132 ) -> Result<Response<C>, ContractError> {
133 let token_id = msg.token_id;
134 let metadata = msg.extension;
135
136 let minter = self.minter.load(deps.storage)?;
137 let mut token = self.tokens.load(deps.storage, &token_id)?;
138
139 if info.sender != minter {
141 return Err(ContractError::Unauthorized {});
142 }
143
144 self.check_can_send(deps.as_ref(), &env, &info, &token)?;
146
147 token.extension = metadata;
149
150 token.approvals = vec![];
152
153 self.tokens.save(deps.storage, &token_id, &token)?;
154
155 Ok(Response::new()
156 .add_attribute("action", "update_metadata")
157 .add_attribute("sender", info.sender)
158 .add_attribute("token_id", token_id))
159 }
160}
161
162impl<'a, T, C, E, Q> Cw721Execute<T, C> for Cw721Contract<'a, T, C, E, Q>
163where
164 T: Serialize + DeserializeOwned + Clone,
165 C: CustomMsg,
166 E: CustomMsg,
167 Q: CustomMsg,
168{
169 type Err = ContractError;
170
171 fn transfer_nft(
172 &self,
173 deps: DepsMut,
174 env: Env,
175 info: MessageInfo,
176 recipient: String,
177 token_id: String,
178 ) -> Result<Response<C>, ContractError> {
179 self._transfer_nft(deps, &env, &info, &recipient, &token_id)?;
180
181 Ok(Response::new()
182 .add_attribute("action", "transfer_nft")
183 .add_attribute("sender", info.sender)
184 .add_attribute("recipient", recipient)
185 .add_attribute("token_id", token_id))
186 }
187
188 fn send_nft(
189 &self,
190 deps: DepsMut,
191 env: Env,
192 info: MessageInfo,
193 contract: String,
194 token_id: String,
195 msg: Binary,
196 ) -> Result<Response<C>, ContractError> {
197 self._transfer_nft(deps, &env, &info, &contract, &token_id)?;
199
200 let send = Cw721ReceiveMsg {
201 sender: info.sender.to_string(),
202 token_id: token_id.clone(),
203 msg,
204 };
205
206 Ok(Response::new()
208 .add_message(send.into_cosmos_msg(contract.clone())?)
209 .add_attribute("action", "send_nft")
210 .add_attribute("sender", info.sender)
211 .add_attribute("recipient", contract)
212 .add_attribute("token_id", token_id))
213 }
214
215 fn approve(
216 &self,
217 deps: DepsMut,
218 env: Env,
219 info: MessageInfo,
220 spender: String,
221 token_id: String,
222 expires: Option<Expiration>,
223 ) -> Result<Response<C>, ContractError> {
224 self._update_approvals(deps, &env, &info, &spender, &token_id, true, expires)?;
225
226 Ok(Response::new()
227 .add_attribute("action", "approve")
228 .add_attribute("sender", info.sender)
229 .add_attribute("spender", spender)
230 .add_attribute("token_id", token_id))
231 }
232
233 fn revoke(
234 &self,
235 deps: DepsMut,
236 env: Env,
237 info: MessageInfo,
238 spender: String,
239 token_id: String,
240 ) -> Result<Response<C>, ContractError> {
241 self._update_approvals(deps, &env, &info, &spender, &token_id, false, None)?;
242
243 Ok(Response::new()
244 .add_attribute("action", "revoke")
245 .add_attribute("sender", info.sender)
246 .add_attribute("spender", spender)
247 .add_attribute("token_id", token_id))
248 }
249
250 fn approve_all(
251 &self,
252 deps: DepsMut,
253 env: Env,
254 info: MessageInfo,
255 operator: String,
256 expires: Option<Expiration>,
257 ) -> Result<Response<C>, ContractError> {
258 let expires = expires.unwrap_or_default();
260 if expires.is_expired(&env.block) {
261 return Err(ContractError::Expired {});
262 }
263
264 let operator_addr = deps.api.addr_validate(&operator)?;
266 self.operators
267 .save(deps.storage, (&info.sender, &operator_addr), &expires)?;
268
269 Ok(Response::new()
270 .add_attribute("action", "approve_all")
271 .add_attribute("sender", info.sender)
272 .add_attribute("operator", operator))
273 }
274
275 fn revoke_all(
276 &self,
277 deps: DepsMut,
278 _env: Env,
279 info: MessageInfo,
280 operator: String,
281 ) -> Result<Response<C>, ContractError> {
282 let operator_addr = deps.api.addr_validate(&operator)?;
283 self.operators
284 .remove(deps.storage, (&info.sender, &operator_addr));
285
286 Ok(Response::new()
287 .add_attribute("action", "revoke_all")
288 .add_attribute("sender", info.sender)
289 .add_attribute("operator", operator))
290 }
291
292 fn burn(
293 &self,
294 deps: DepsMut,
295 env: Env,
296 info: MessageInfo,
297 token_id: String,
298 ) -> Result<Response<C>, ContractError> {
299 let token = self.tokens.load(deps.storage, &token_id)?;
300 self.check_can_send(deps.as_ref(), &env, &info, &token)?;
301
302 self.tokens.remove(deps.storage, &token_id)?;
303 self.decrement_tokens(deps.storage)?;
304
305 Ok(Response::new()
306 .add_attribute("action", "burn")
307 .add_attribute("sender", info.sender)
308 .add_attribute("token_id", token_id))
309 }
310}
311
312impl<'a, T, C, E, Q> Cw721Contract<'a, T, C, E, Q>
314where
315 T: Serialize + DeserializeOwned + Clone,
316 C: CustomMsg,
317 E: CustomMsg,
318 Q: CustomMsg,
319{
320 pub fn _transfer_nft(
321 &self,
322 deps: DepsMut,
323 env: &Env,
324 info: &MessageInfo,
325 recipient: &str,
326 token_id: &str,
327 ) -> Result<TokenInfo<T>, ContractError> {
328 let mut token = self.tokens.load(deps.storage, token_id)?;
329 self.check_can_send(deps.as_ref(), env, info, &token)?;
331 token.owner = deps.api.addr_validate(recipient)?;
333 token.approvals = vec![];
334 self.tokens.save(deps.storage, token_id, &token)?;
335 Ok(token)
336 }
337
338 #[allow(clippy::too_many_arguments)]
339 pub fn _update_approvals(
340 &self,
341 deps: DepsMut,
342 env: &Env,
343 info: &MessageInfo,
344 spender: &str,
345 token_id: &str,
346 add: bool,
348 expires: Option<Expiration>,
349 ) -> Result<TokenInfo<T>, ContractError> {
350 let mut token = self.tokens.load(deps.storage, token_id)?;
351 self.check_can_approve(deps.as_ref(), env, info, &token)?;
353
354 let spender_addr = deps.api.addr_validate(spender)?;
356 token.approvals.retain(|apr| apr.spender != spender_addr);
357
358 if add {
360 let expires = expires.unwrap_or_default();
362 if expires.is_expired(&env.block) {
363 return Err(ContractError::Expired {});
364 }
365 let approval = Approval {
366 spender: spender_addr,
367 expires,
368 };
369 token.approvals.push(approval);
370 }
371
372 self.tokens.save(deps.storage, token_id, &token)?;
373
374 Ok(token)
375 }
376
377 pub fn check_can_approve(
379 &self,
380 deps: Deps,
381 env: &Env,
382 info: &MessageInfo,
383 token: &TokenInfo<T>,
384 ) -> Result<(), ContractError> {
385 if token.owner == info.sender {
387 return Ok(());
388 }
389 let op = self
391 .operators
392 .may_load(deps.storage, (&token.owner, &info.sender))?;
393 match op {
394 Some(ex) => {
395 if ex.is_expired(&env.block) {
396 Err(ContractError::Unauthorized {})
397 } else {
398 Ok(())
399 }
400 }
401 None => Err(ContractError::Unauthorized {}),
402 }
403 }
404
405 pub fn check_can_send(
407 &self,
408 deps: Deps,
409 env: &Env,
410 info: &MessageInfo,
411 token: &TokenInfo<T>,
412 ) -> Result<(), ContractError> {
413 if token.owner == info.sender {
415 return Ok(());
416 }
417
418 if token
420 .approvals
421 .iter()
422 .any(|apr| apr.spender == info.sender && !apr.is_expired(&env.block))
423 {
424 return Ok(());
425 }
426
427 let op = self
429 .operators
430 .may_load(deps.storage, (&token.owner, &info.sender))?;
431 match op {
432 Some(ex) => {
433 if ex.is_expired(&env.block) {
434 Err(ContractError::Unauthorized {})
435 } else {
436 Ok(())
437 }
438 }
439 None => Err(ContractError::Unauthorized {}),
440 }
441 }
442}