Skip to main content

cw721/
traits.rs

1use std::fmt::Debug;
2
3use cosmwasm_std::{
4    to_json_binary, Addr, Api, Binary, Coin, CosmosMsg, CustomMsg, Deps, DepsMut, Empty, Env,
5    MessageInfo, QuerierWrapper, Response, StdResult, Storage, WasmMsg, WasmQuery,
6};
7use cw_ownable::{Action, Ownership};
8use cw_utils::Expiration;
9use schemars::JsonSchema;
10use serde::{de::DeserializeOwned, Serialize};
11
12#[allow(deprecated)]
13use crate::{
14    error::Cw721ContractError,
15    execute::{
16        add_additional_minter, approve, approve_all, burn_nft, initialize_creator,
17        initialize_minter, instantiate, instantiate_with_version, migrate, mint,
18        remove_additional_minter, remove_withdraw_address, revoke, revoke_all, send_nft,
19        set_withdraw_address, transfer_nft, update_collection_info, update_creator_ownership,
20        update_minter_ownership, update_nft_info, withdraw_funds,
21    },
22    msg::{
23        AdditionalMintersResponse, AllNftInfoResponse, ApprovalResponse, ApprovalsResponse,
24        CollectionInfoAndExtensionResponse, CollectionInfoMsg, Cw721ExecuteMsg,
25        Cw721InstantiateMsg, Cw721MigrateMsg, Cw721QueryMsg, MinterResponse, NftInfoResponse,
26        NumTokensResponse, OperatorResponse, OperatorsResponse, OwnerOfResponse, TokensResponse,
27    },
28    query::{
29        query_additional_minters, query_all_nft_info, query_all_tokens, query_approval,
30        query_approvals, query_collection_extension_attributes, query_collection_info,
31        query_collection_info_and_extension, query_creator_ownership, query_minter,
32        query_minter_ownership, query_nft_info, query_num_tokens, query_operator, query_operators,
33        query_owner_of, query_tokens, query_withdraw_address,
34    },
35    state::CollectionInfo,
36    Attribute,
37};
38use crate::{
39    msg::{AllInfoResponse, ConfigResponse},
40    query::{query_all_info, query_config, query_nft_by_extension},
41    Approval,
42};
43
44/// This is an exact copy of `CustomMsg`, since implementing a trait for a type from another crate is not possible.
45///
46/// Possible:
47/// `impl<T> Cw721CustomMsg for Option<T> where T: Cw721CustomMsg {}`
48///
49/// Not possible:
50/// `impl<T> CustomMsg for Option<T> where T: CustomMsg {}`
51///
52/// This will be removed once the `CustomMsg` trait is moved to the `cosmwasm_std` crate: https://github.com/CosmWasm/cosmwasm/issues/2056
53pub trait Cw721CustomMsg: Serialize + Clone + Debug + PartialEq + JsonSchema {}
54
55pub trait Cw721State: Serialize + DeserializeOwned + Clone + Debug {}
56
57impl Cw721State for Empty {}
58impl<T> Cw721State for Option<T> where T: Cw721State {}
59
60impl Cw721CustomMsg for Empty {}
61impl<T> Cw721CustomMsg for Option<T> where T: Cw721CustomMsg {}
62
63/// e.g. for checking whether an NFT has specific traits (metadata).
64pub trait Contains {
65    fn contains(&self, other: &Self) -> bool;
66}
67
68pub trait StateFactory<TState> {
69    fn create(
70        &self,
71        deps: Deps,
72        env: &Env,
73        info: Option<&MessageInfo>,
74        current: Option<&TState>,
75    ) -> Result<TState, Cw721ContractError>;
76    fn validate(
77        &self,
78        deps: Deps,
79        env: &Env,
80        info: Option<&MessageInfo>,
81        current: Option<&TState>,
82    ) -> Result<(), Cw721ContractError>;
83}
84
85impl StateFactory<Empty> for Empty {
86    fn create(
87        &self,
88        _deps: Deps,
89        _env: &Env,
90        _info: Option<&MessageInfo>,
91        _current: Option<&Empty>,
92    ) -> Result<Empty, Cw721ContractError> {
93        Ok(Empty {})
94    }
95
96    fn validate(
97        &self,
98        _deps: Deps,
99        _env: &Env,
100        _info: Option<&MessageInfo>,
101        _current: Option<&Empty>,
102    ) -> Result<(), Cw721ContractError> {
103        Ok(())
104    }
105}
106
107pub trait ToAttributesState {
108    fn to_attributes_state(&self) -> Result<Vec<Attribute>, Cw721ContractError>;
109}
110
111impl<T> ToAttributesState for Option<T>
112where
113    T: ToAttributesState,
114{
115    fn to_attributes_state(&self) -> Result<Vec<Attribute>, Cw721ContractError> {
116        match self {
117            Some(inner) => inner.to_attributes_state(),
118            None => Ok(vec![]),
119        }
120    }
121}
122
123pub trait FromAttributesState: Sized {
124    fn from_attributes_state(value: &[Attribute]) -> Result<Self, Cw721ContractError>;
125}
126
127impl<T> FromAttributesState for Option<T>
128where
129    T: FromAttributesState,
130{
131    fn from_attributes_state(value: &[Attribute]) -> Result<Self, Cw721ContractError> {
132        if value.is_empty() {
133            Ok(None)
134        } else {
135            T::from_attributes_state(value).map(Some)
136        }
137    }
138}
139
140/// Trait with generic onchain nft and collection extensions used to execute the contract logic and contains default implementations for all messages.
141pub trait Cw721Execute<
142    // NftInfo extension (onchain metadata).
143    TNftExtension,
144    // NftInfo extension msg for onchain metadata.
145    TNftExtensionMsg,
146    // CollectionInfo extension (onchain attributes).
147    TCollectionExtension,
148    // CollectionInfo extension msg for onchain collection attributes.
149    TCollectionExtensionMsg,
150    // Custom extension msg for custom contract logic. Default implementation is a no-op.
151    TExtensionMsg,
152    // Defines for `CosmosMsg::Custom<T>` in response. Barely used, so `Empty` can be used.
153    TCustomResponseMsg,
154> where
155    TNftExtension: Cw721State,
156    TNftExtensionMsg: Cw721CustomMsg + StateFactory<TNftExtension>,
157    TCollectionExtension: Cw721State + ToAttributesState + FromAttributesState,
158    TCollectionExtensionMsg: Cw721CustomMsg + StateFactory<TCollectionExtension>,
159    TCustomResponseMsg: CustomMsg,
160{
161    fn instantiate_with_version(
162        &self,
163        deps: DepsMut,
164        env: &Env,
165        info: &MessageInfo,
166        msg: Cw721InstantiateMsg<TCollectionExtensionMsg>,
167        contract_name: &str,
168        contract_version: &str,
169    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
170        instantiate_with_version(deps, env, info, msg, contract_name, contract_version)
171    }
172
173    fn instantiate(
174        &self,
175        deps: DepsMut,
176        env: &Env,
177        info: &MessageInfo,
178        msg: Cw721InstantiateMsg<TCollectionExtensionMsg>,
179    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
180        instantiate(deps, env, info, msg)
181    }
182
183    fn execute(
184        &self,
185        deps: DepsMut,
186        env: &Env,
187        info: &MessageInfo,
188        msg: Cw721ExecuteMsg<TNftExtensionMsg, TCollectionExtensionMsg, TExtensionMsg>,
189    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
190        match msg {
191            Cw721ExecuteMsg::UpdateCollectionInfo { collection_info } => {
192                self.update_collection_info(deps, info.into(), env, collection_info)
193            }
194            Cw721ExecuteMsg::Mint {
195                token_id,
196                owner,
197                token_uri,
198                extension,
199            } => self.mint(deps, env, info, token_id, owner, token_uri, extension),
200            Cw721ExecuteMsg::Approve {
201                spender,
202                token_id,
203                expires,
204            } => self.approve(deps, env, info, spender, token_id, expires),
205            Cw721ExecuteMsg::Revoke { spender, token_id } => {
206                self.revoke(deps, env, info, spender, token_id)
207            }
208            Cw721ExecuteMsg::ApproveAll { operator, expires } => {
209                self.approve_all(deps, env, info, operator, expires)
210            }
211            Cw721ExecuteMsg::RevokeAll { operator } => self.revoke_all(deps, env, info, operator),
212            Cw721ExecuteMsg::TransferNft {
213                recipient,
214                token_id,
215            } => self.transfer_nft(deps, env, info, recipient, token_id),
216            Cw721ExecuteMsg::SendNft {
217                contract,
218                token_id,
219                msg,
220            } => self.send_nft(deps, env, info, contract, token_id, msg),
221            Cw721ExecuteMsg::Burn { token_id } => self.burn_nft(deps, env, info, token_id),
222            Cw721ExecuteMsg::AddMinter { minter } => self.add_additional_minter(deps, info, minter),
223            Cw721ExecuteMsg::RemoveMinter { minter } => {
224                self.remove_additional_minter(deps, info, minter)
225            }
226            #[allow(deprecated)]
227            Cw721ExecuteMsg::UpdateOwnership(action) => {
228                self.update_minter_ownership(deps, env, info, action)
229            }
230            Cw721ExecuteMsg::UpdateMinterOwnership(action) => {
231                self.update_minter_ownership(deps, env, info, action)
232            }
233            Cw721ExecuteMsg::UpdateCreatorOwnership(action) => {
234                self.update_creator_ownership(deps, env, info, action)
235            }
236            #[allow(deprecated)]
237            Cw721ExecuteMsg::UpdateExtension { msg } => {
238                self.execute_extension(deps, env, info, msg)
239            }
240            Cw721ExecuteMsg::UpdateNftInfo {
241                token_id,
242                token_uri,
243                extension,
244            } => self.update_nft_info(deps, env, info, token_id, token_uri, extension),
245            Cw721ExecuteMsg::SetWithdrawAddress { address } => {
246                self.set_withdraw_address(deps, &info.sender, address)
247            }
248            Cw721ExecuteMsg::RemoveWithdrawAddress {} => {
249                self.remove_withdraw_address(deps.storage, &info.sender)
250            }
251            Cw721ExecuteMsg::WithdrawFunds { amount } => self.withdraw_funds(deps.storage, &amount),
252        }
253    }
254
255    fn migrate(
256        &self,
257        deps: DepsMut,
258        env: Env,
259        msg: Cw721MigrateMsg,
260        contract_name: &str,
261        contract_version: &str,
262    ) -> Result<Response, Cw721ContractError> {
263        migrate(deps, env, msg, contract_name, contract_version)
264    }
265
266    // ------- ERC721-based functions -------
267    fn transfer_nft(
268        &self,
269        deps: DepsMut,
270        env: &Env,
271        info: &MessageInfo,
272        recipient: String,
273        token_id: String,
274    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
275        transfer_nft::<TNftExtension>(deps, env, info, &recipient, &token_id)?;
276
277        Ok(Response::new()
278            .add_attribute("action", "transfer_nft")
279            .add_attribute("sender", info.sender.to_string())
280            .add_attribute("recipient", recipient)
281            .add_attribute("token_id", token_id))
282    }
283
284    fn send_nft(
285        &self,
286        deps: DepsMut,
287        env: &Env,
288        info: &MessageInfo,
289        contract: String,
290        token_id: String,
291        msg: Binary,
292    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
293        send_nft::<TNftExtension, TCustomResponseMsg>(deps, env, info, contract, token_id, msg)
294    }
295
296    fn approve(
297        &self,
298        deps: DepsMut,
299        env: &Env,
300        info: &MessageInfo,
301        spender: String,
302        token_id: String,
303        expires: Option<Expiration>,
304    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
305        approve::<TNftExtension, TCustomResponseMsg>(deps, env, info, spender, token_id, expires)
306    }
307
308    fn revoke(
309        &self,
310        deps: DepsMut,
311        env: &Env,
312        info: &MessageInfo,
313        spender: String,
314        token_id: String,
315    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
316        revoke::<TNftExtension, TCustomResponseMsg>(deps, env, info, spender, token_id)
317    }
318
319    fn approve_all(
320        &self,
321        deps: DepsMut,
322        env: &Env,
323        info: &MessageInfo,
324        operator: String,
325        expires: Option<Expiration>,
326    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
327        approve_all::<TCustomResponseMsg>(deps, env, info, operator, expires)
328    }
329
330    fn revoke_all(
331        &self,
332        deps: DepsMut,
333        _env: &Env,
334        info: &MessageInfo,
335        operator: String,
336    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
337        revoke_all::<TCustomResponseMsg>(deps, _env, info, operator)
338    }
339
340    fn burn_nft(
341        &self,
342        deps: DepsMut,
343        env: &Env,
344        info: &MessageInfo,
345        token_id: String,
346    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
347        burn_nft::<TCustomResponseMsg>(deps, env, info, token_id)
348    }
349
350    fn add_additional_minter(
351        &self,
352        deps: DepsMut,
353        info: &MessageInfo,
354        minter: String,
355    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
356        add_additional_minter::<TCustomResponseMsg>(deps, info, minter)
357    }
358
359    fn remove_additional_minter(
360        &self,
361        deps: DepsMut,
362        info: &MessageInfo,
363        minter: String,
364    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
365        remove_additional_minter::<TCustomResponseMsg>(deps, info, minter)
366    }
367
368    // ------- opionated cw721 functions -------
369    fn initialize_creator(
370        &self,
371        storage: &mut dyn Storage,
372        api: &dyn Api,
373        creator: Option<&str>,
374    ) -> StdResult<Ownership<Addr>> {
375        initialize_creator(storage, api, creator)
376    }
377
378    fn initialize_minter(
379        &self,
380        storage: &mut dyn Storage,
381        api: &dyn Api,
382        minter: Option<&str>,
383    ) -> StdResult<Ownership<Addr>> {
384        initialize_minter(storage, api, minter)
385    }
386
387    fn update_collection_info(
388        &self,
389        deps: DepsMut,
390        info: Option<&MessageInfo>,
391        env: &Env,
392        msg: CollectionInfoMsg<TCollectionExtensionMsg>,
393    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
394        update_collection_info::<TCollectionExtension, TCollectionExtensionMsg, TCustomResponseMsg>(
395            deps, info, env, msg,
396        )
397    }
398
399    #[allow(clippy::too_many_arguments)]
400    fn mint(
401        &self,
402        deps: DepsMut,
403        env: &Env,
404        info: &MessageInfo,
405        token_id: String,
406        owner: String,
407        token_uri: Option<String>,
408        extension: TNftExtensionMsg,
409    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
410        mint::<TNftExtension, TNftExtensionMsg, TCustomResponseMsg>(
411            deps, env, info, token_id, owner, token_uri, extension,
412        )
413    }
414
415    fn update_minter_ownership(
416        &self,
417        deps: DepsMut,
418        env: &Env,
419        info: &MessageInfo,
420        action: Action,
421    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
422        update_minter_ownership::<TCustomResponseMsg>(deps, env, info, action)
423    }
424
425    fn update_creator_ownership(
426        &self,
427        deps: DepsMut,
428        env: &Env,
429        info: &MessageInfo,
430        action: Action,
431    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
432        update_creator_ownership::<TCustomResponseMsg>(deps, env, info, action)
433    }
434
435    /// Custom msg execution. This is a no-op in default implementation.
436    fn execute_extension(
437        &self,
438        _deps: DepsMut,
439        _env: &Env,
440        _info: &MessageInfo,
441        _msg: TExtensionMsg,
442    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
443        Ok(Response::default())
444    }
445
446    /// The creator is the only one eligible to update NFT's token uri and onchain metadata (`NftInfo.extension`).
447    /// NOTE: approvals and owner are not affected by this call, since they belong to the NFT owner.
448    fn update_nft_info(
449        &self,
450        deps: DepsMut,
451        env: &Env,
452        info: &MessageInfo,
453        token_id: String,
454        token_uri: Option<String>,
455        msg: TNftExtensionMsg,
456    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
457        update_nft_info::<TNftExtension, TNftExtensionMsg, TCustomResponseMsg>(
458            deps,
459            env,
460            info.into(),
461            token_id,
462            token_uri,
463            msg,
464        )
465    }
466
467    fn set_withdraw_address(
468        &self,
469        deps: DepsMut,
470        sender: &Addr,
471        address: String,
472    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
473        set_withdraw_address::<TCustomResponseMsg>(deps, sender, address)
474    }
475
476    fn remove_withdraw_address(
477        &self,
478        storage: &mut dyn Storage,
479        sender: &Addr,
480    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
481        remove_withdraw_address::<TCustomResponseMsg>(storage, sender)
482    }
483
484    fn withdraw_funds(
485        &self,
486        storage: &mut dyn Storage,
487        amount: &Coin,
488    ) -> Result<Response<TCustomResponseMsg>, Cw721ContractError> {
489        withdraw_funds::<TCustomResponseMsg>(storage, amount)
490    }
491}
492
493/// Trait with generic onchain nft and collection extensions used to query the contract state and contains default implementations for all queries.
494pub trait Cw721Query<
495    // NftInfo extension (onchain metadata).
496    TNftExtension,
497    // CollectionInfo extension (onchain attributes).
498    TCollectionExtension,
499    // Custom query msg for custom contract logic. Default implementation returns an empty binary.
500    TExtensionQueryMsg,
501> where
502    TNftExtension: Cw721State + Contains,
503    TCollectionExtension: Cw721State + FromAttributesState,
504    TExtensionQueryMsg: Cw721CustomMsg,
505{
506    fn query(
507        &self,
508        deps: Deps,
509        env: &Env,
510        msg: Cw721QueryMsg<TNftExtension, TCollectionExtension, TExtensionQueryMsg>,
511    ) -> Result<Binary, Cw721ContractError> {
512        match msg {
513            #[allow(deprecated)]
514            Cw721QueryMsg::Minter {} => Ok(to_json_binary(&self.query_minter(deps.storage)?)?),
515            #[allow(deprecated)]
516            Cw721QueryMsg::ContractInfo {} => Ok(to_json_binary(
517                &self.query_collection_info_and_extension(deps)?,
518            )?),
519            Cw721QueryMsg::GetConfig {} => Ok(to_json_binary(
520                &self.query_all_collection_info(deps, env.contract.address.to_string())?,
521            )?),
522            Cw721QueryMsg::GetCollectionInfoAndExtension {} => Ok(to_json_binary(
523                &self.query_collection_info_and_extension(deps)?,
524            )?),
525            Cw721QueryMsg::GetAllInfo {} => Ok(to_json_binary(&self.query_all_info(deps, env)?)?),
526            Cw721QueryMsg::GetCollectionExtensionAttributes {} => Ok(to_json_binary(
527                &self.query_collection_extension_attributes(deps)?,
528            )?),
529            Cw721QueryMsg::NftInfo { token_id } => Ok(to_json_binary(
530                &self.query_nft_info(deps.storage, token_id)?,
531            )?),
532            Cw721QueryMsg::GetNftByExtension {
533                extension,
534                start_after,
535                limit,
536            } => Ok(to_json_binary(&self.query_nft_by_extension(
537                deps.storage,
538                extension,
539                start_after,
540                limit,
541            )?)?),
542            Cw721QueryMsg::OwnerOf {
543                token_id,
544                include_expired,
545            } => Ok(to_json_binary(&self.query_owner_of(
546                deps,
547                env,
548                token_id,
549                include_expired.unwrap_or(false),
550            )?)?),
551            Cw721QueryMsg::AllNftInfo {
552                token_id,
553                include_expired,
554            } => Ok(to_json_binary(&self.query_all_nft_info(
555                deps,
556                env,
557                token_id,
558                include_expired.unwrap_or(false),
559            )?)?),
560            Cw721QueryMsg::Operator {
561                owner,
562                operator,
563                include_expired,
564            } => Ok(to_json_binary(&self.query_operator(
565                deps,
566                env,
567                owner,
568                operator,
569                include_expired.unwrap_or(false),
570            )?)?),
571            Cw721QueryMsg::AllOperators {
572                owner,
573                include_expired,
574                start_after,
575                limit,
576            } => Ok(to_json_binary(&self.query_operators(
577                deps,
578                env,
579                owner,
580                include_expired.unwrap_or(false),
581                start_after,
582                limit,
583            )?)?),
584            Cw721QueryMsg::NumTokens {} => {
585                Ok(to_json_binary(&self.query_num_tokens(deps.storage)?)?)
586            }
587            Cw721QueryMsg::Tokens {
588                owner,
589                start_after,
590                limit,
591            } => Ok(to_json_binary(&self.query_tokens(
592                deps,
593                env,
594                owner,
595                start_after,
596                limit,
597            )?)?),
598            Cw721QueryMsg::AllTokens { start_after, limit } => Ok(to_json_binary(
599                &self.query_all_tokens(deps, env, start_after, limit)?,
600            )?),
601            Cw721QueryMsg::Approval {
602                token_id,
603                spender,
604                include_expired,
605            } => Ok(to_json_binary(&self.query_approval(
606                deps,
607                env,
608                token_id,
609                spender,
610                include_expired.unwrap_or(false),
611            )?)?),
612            Cw721QueryMsg::Approvals {
613                token_id,
614                include_expired,
615            } => Ok(to_json_binary(&self.query_approvals(
616                deps,
617                env,
618                token_id,
619                include_expired.unwrap_or(false),
620            )?)?),
621            #[allow(deprecated)]
622            Cw721QueryMsg::Ownership {} => {
623                Ok(to_json_binary(&self.query_minter_ownership(deps.storage)?)?)
624            }
625            Cw721QueryMsg::GetMinterOwnership {} => {
626                Ok(to_json_binary(&self.query_minter_ownership(deps.storage)?)?)
627            }
628            Cw721QueryMsg::GetCreatorOwnership {} => Ok(to_json_binary(
629                &self.query_creator_ownership(deps.storage)?,
630            )?),
631            Cw721QueryMsg::GetAdditionalMinters { start_after, limit } => Ok(to_json_binary(
632                &self.query_additional_minters(deps, start_after, limit)?,
633            )?),
634            Cw721QueryMsg::Extension { msg } => self.query_extension(deps, env, msg),
635            Cw721QueryMsg::GetCollectionExtension { msg } => {
636                self.query_custom_collection_extension(deps, env, msg)
637            }
638            Cw721QueryMsg::GetWithdrawAddress {} => {
639                Ok(to_json_binary(&self.query_withdraw_address(deps)?)?)
640            }
641        }
642    }
643
644    #[deprecated(since = "0.19.0", note = "Please use query_minter_ownership instead")]
645    /// Deprecated: use query_minter_ownership instead! Will be removed in next release!
646    fn query_minter(&self, storage: &dyn Storage) -> StdResult<MinterResponse> {
647        #[allow(deprecated)]
648        query_minter(storage)
649    }
650
651    fn query_minter_ownership(&self, storage: &dyn Storage) -> StdResult<Ownership<Addr>> {
652        query_minter_ownership(storage)
653    }
654
655    fn query_creator_ownership(&self, storage: &dyn Storage) -> StdResult<Ownership<Addr>> {
656        query_creator_ownership(storage)
657    }
658
659    fn query_additional_minters(
660        &self,
661        deps: Deps,
662        start_after: Option<String>,
663        limit: Option<u32>,
664    ) -> StdResult<AdditionalMintersResponse> {
665        query_additional_minters(deps, start_after, limit)
666    }
667
668    fn query_collection_info(&self, deps: Deps) -> StdResult<CollectionInfo> {
669        query_collection_info(deps.storage)
670    }
671
672    fn query_collection_extension_attributes(&self, deps: Deps) -> StdResult<Vec<Attribute>> {
673        query_collection_extension_attributes(deps)
674    }
675
676    fn query_all_collection_info(
677        &self,
678        deps: Deps,
679        contract_addr: impl Into<String>,
680    ) -> Result<ConfigResponse<TCollectionExtension>, Cw721ContractError>
681    where
682        TCollectionExtension: FromAttributesState,
683    {
684        query_config(deps, contract_addr)
685    }
686
687    fn query_collection_info_and_extension(
688        &self,
689        deps: Deps,
690    ) -> Result<CollectionInfoAndExtensionResponse<TCollectionExtension>, Cw721ContractError>
691    where
692        TCollectionExtension: FromAttributesState,
693    {
694        query_collection_info_and_extension(deps)
695    }
696
697    fn query_all_info(&self, deps: Deps, env: &Env) -> StdResult<AllInfoResponse> {
698        query_all_info(deps, env)
699    }
700
701    fn query_num_tokens(&self, storage: &dyn Storage) -> StdResult<NumTokensResponse> {
702        query_num_tokens(storage)
703    }
704
705    fn query_nft_info(
706        &self,
707        storage: &dyn Storage,
708        token_id: String,
709    ) -> StdResult<NftInfoResponse<TNftExtension>> {
710        query_nft_info::<TNftExtension>(storage, token_id)
711    }
712
713    fn query_nft_by_extension(
714        &self,
715        storage: &dyn Storage,
716        extension: TNftExtension,
717        start_after: Option<String>,
718        limit: Option<u32>,
719    ) -> StdResult<Option<Vec<NftInfoResponse<TNftExtension>>>> {
720        query_nft_by_extension::<TNftExtension>(storage, extension, start_after, limit)
721    }
722
723    fn query_owner_of(
724        &self,
725        deps: Deps,
726        env: &Env,
727        token_id: String,
728        include_expired_approval: bool,
729    ) -> StdResult<OwnerOfResponse> {
730        query_owner_of(deps, env, token_id, include_expired_approval)
731    }
732
733    /// operator returns the approval status of an operator for a given owner if exists
734    fn query_operator(
735        &self,
736        deps: Deps,
737        env: &Env,
738        owner: String,
739        operator: String,
740        include_expired_approval: bool,
741    ) -> StdResult<OperatorResponse> {
742        query_operator(deps, env, owner, operator, include_expired_approval)
743    }
744
745    /// operators returns all operators owner given access to
746    fn query_operators(
747        &self,
748        deps: Deps,
749        env: &Env,
750        owner: String,
751        include_expired_approval: bool,
752        start_after: Option<String>,
753        limit: Option<u32>,
754    ) -> StdResult<OperatorsResponse> {
755        query_operators(
756            deps,
757            env,
758            owner,
759            include_expired_approval,
760            start_after,
761            limit,
762        )
763    }
764
765    fn query_approval(
766        &self,
767        deps: Deps,
768        env: &Env,
769        token_id: String,
770        spender: String,
771        include_expired_approval: bool,
772    ) -> StdResult<ApprovalResponse> {
773        query_approval(
774            deps,
775            env,
776            token_id,
777            deps.api.addr_validate(&spender)?,
778            include_expired_approval,
779        )
780    }
781
782    /// approvals returns all approvals owner given access to
783    fn query_approvals(
784        &self,
785        deps: Deps,
786        env: &Env,
787        token_id: String,
788        include_expired_approval: bool,
789    ) -> StdResult<ApprovalsResponse> {
790        query_approvals(deps, env, token_id, include_expired_approval)
791    }
792
793    fn query_tokens(
794        &self,
795        deps: Deps,
796        _env: &Env,
797        owner: String,
798        start_after: Option<String>,
799        limit: Option<u32>,
800    ) -> StdResult<TokensResponse> {
801        query_tokens(deps, _env, owner, start_after, limit)
802    }
803
804    fn query_all_tokens(
805        &self,
806        deps: Deps,
807        _env: &Env,
808        start_after: Option<String>,
809        limit: Option<u32>,
810    ) -> StdResult<TokensResponse> {
811        query_all_tokens(deps, _env, start_after, limit)
812    }
813
814    fn query_all_nft_info(
815        &self,
816        deps: Deps,
817        env: &Env,
818        token_id: String,
819        include_expired_approval: bool,
820    ) -> StdResult<AllNftInfoResponse<TNftExtension>> {
821        query_all_nft_info::<TNftExtension>(deps, env, token_id, include_expired_approval)
822    }
823
824    /// Custom msg query. Default implementation returns an empty binary.
825    fn query_extension(
826        &self,
827        _deps: Deps,
828        _env: &Env,
829        _msg: TExtensionQueryMsg,
830    ) -> Result<Binary, Cw721ContractError> {
831        Ok(Binary::default())
832    }
833
834    /// use GetCollectionInfoAndExtension instead.
835    /// No-op / empty extension query returning empty binary, needed for inferring type parameter during compile
836    ///
837    /// Note: it may be extended in case there are use cases e.g. for specific NFT metadata query.
838    fn query_custom_collection_extension(
839        &self,
840        _deps: Deps,
841        _env: &Env,
842        _msg: TCollectionExtension,
843    ) -> Result<Binary, Cw721ContractError> {
844        Ok(Binary::default())
845    }
846
847    fn query_withdraw_address(&self, deps: Deps) -> StdResult<Option<String>> {
848        query_withdraw_address(deps)
849    }
850}
851
852/// Generic trait with onchain nft and collection extensions used to call query and execute messages for a given CW721 addr.
853pub trait Cw721Calls<
854    TNftExtension,
855    TNftExtensionMsg,
856    TCollectionExtension,
857    TCollectionExtensionMsg,
858    TExtensionMsg,
859    TExtensionQueryMsg,
860> where
861    TNftExtensionMsg: Cw721CustomMsg,
862    TNftExtension: Cw721State,
863    TCollectionExtension: Cw721State,
864    TCollectionExtensionMsg: Cw721CustomMsg,
865    TExtensionMsg: Cw721CustomMsg,
866    TExtensionQueryMsg: Cw721CustomMsg,
867{
868    /// Returns the CW721 address.
869    fn addr(&self) -> Addr;
870
871    /// Executes the CW721 contract with the given message.
872    fn call(
873        &self,
874        msg: Cw721ExecuteMsg<TNftExtensionMsg, TCollectionExtensionMsg, TExtensionMsg>,
875    ) -> StdResult<CosmosMsg> {
876        let msg = to_json_binary(&msg)?;
877        Ok(WasmMsg::Execute {
878            contract_addr: self.addr().into(),
879            msg,
880            funds: vec![],
881        }
882        .into())
883    }
884
885    /// Queries the CW721 contract with the given message.
886    fn query<T: DeserializeOwned>(
887        &self,
888        querier: &QuerierWrapper,
889        req: Cw721QueryMsg<TNftExtension, TCollectionExtension, TExtensionQueryMsg>,
890    ) -> StdResult<T> {
891        let query = WasmQuery::Smart {
892            contract_addr: self.addr().into(),
893            msg: to_json_binary(&req)?,
894        }
895        .into();
896        querier.query(&query)
897    }
898
899    /*** queries ***/
900    fn owner_of<T: Into<String>>(
901        &self,
902        querier: &QuerierWrapper,
903        token_id: T,
904        include_expired: bool,
905    ) -> StdResult<OwnerOfResponse> {
906        let req = Cw721QueryMsg::OwnerOf {
907            token_id: token_id.into(),
908            include_expired: Some(include_expired),
909        };
910        self.query(querier, req)
911    }
912
913    fn approval<T: Into<String>>(
914        &self,
915        querier: &QuerierWrapper,
916        token_id: T,
917        spender: T,
918        include_expired: Option<bool>,
919    ) -> StdResult<ApprovalResponse> {
920        let req = Cw721QueryMsg::Approval {
921            token_id: token_id.into(),
922            spender: spender.into(),
923            include_expired,
924        };
925        let res: ApprovalResponse = self.query(querier, req)?;
926        Ok(res)
927    }
928
929    fn approvals<T: Into<String>>(
930        &self,
931        querier: &QuerierWrapper,
932        token_id: T,
933        include_expired: Option<bool>,
934    ) -> StdResult<ApprovalsResponse> {
935        let req = Cw721QueryMsg::Approvals {
936            token_id: token_id.into(),
937            include_expired,
938        };
939        let res: ApprovalsResponse = self.query(querier, req)?;
940        Ok(res)
941    }
942
943    fn all_operators<T: Into<String>>(
944        &self,
945        querier: &QuerierWrapper,
946        owner: T,
947        include_expired: bool,
948        start_after: Option<String>,
949        limit: Option<u32>,
950    ) -> StdResult<Vec<Approval>> {
951        let req = Cw721QueryMsg::AllOperators {
952            owner: owner.into(),
953            include_expired: Some(include_expired),
954            start_after,
955            limit,
956        };
957        let res: OperatorsResponse = self.query(querier, req)?;
958        Ok(res.operators)
959    }
960
961    fn num_tokens(&self, querier: &QuerierWrapper) -> StdResult<u64> {
962        let req = Cw721QueryMsg::NumTokens {};
963        let res: NumTokensResponse = self.query(querier, req)?;
964        Ok(res.count)
965    }
966
967    /// This is a helper to get the metadata and extension data in one call
968    fn config<U: DeserializeOwned>(
969        &self,
970        querier: &QuerierWrapper,
971    ) -> StdResult<ConfigResponse<U>> {
972        let req = Cw721QueryMsg::GetConfig {};
973        self.query(querier, req)
974    }
975
976    /// This is a helper to get the metadata and extension data in one call
977    fn collection_info<U: DeserializeOwned>(
978        &self,
979        querier: &QuerierWrapper,
980    ) -> StdResult<CollectionInfoAndExtensionResponse<U>> {
981        let req = Cw721QueryMsg::GetCollectionInfoAndExtension {};
982        self.query(querier, req)
983    }
984
985    /// With NFT onchain metadata
986    fn nft_info<T: Into<String>, U: DeserializeOwned>(
987        &self,
988        querier: &QuerierWrapper,
989        token_id: T,
990    ) -> StdResult<NftInfoResponse<U>> {
991        let req = Cw721QueryMsg::NftInfo {
992            token_id: token_id.into(),
993        };
994        self.query(querier, req)
995    }
996
997    /// With NFT onchain metadata
998    fn all_nft_info<T: Into<String>, U: DeserializeOwned>(
999        &self,
1000        querier: &QuerierWrapper,
1001        token_id: T,
1002        include_expired: bool,
1003    ) -> StdResult<AllNftInfoResponse<U>> {
1004        let req = Cw721QueryMsg::AllNftInfo {
1005            token_id: token_id.into(),
1006            include_expired: Some(include_expired),
1007        };
1008        self.query(querier, req)
1009    }
1010
1011    /// With enumerable extension
1012    fn tokens<T: Into<String>>(
1013        &self,
1014        querier: &QuerierWrapper,
1015        owner: T,
1016        start_after: Option<String>,
1017        limit: Option<u32>,
1018    ) -> StdResult<TokensResponse> {
1019        let req = Cw721QueryMsg::Tokens {
1020            owner: owner.into(),
1021            start_after,
1022            limit,
1023        };
1024        self.query(querier, req)
1025    }
1026
1027    /// With enumerable extension
1028    fn all_tokens(
1029        &self,
1030        querier: &QuerierWrapper,
1031        start_after: Option<String>,
1032        limit: Option<u32>,
1033    ) -> StdResult<TokensResponse> {
1034        let req = Cw721QueryMsg::AllTokens { start_after, limit };
1035        self.query(querier, req)
1036    }
1037
1038    /// returns true if the contract supports the metadata extension
1039    fn has_metadata(&self, querier: &QuerierWrapper) -> bool {
1040        self.collection_info::<Empty>(querier).is_ok()
1041    }
1042
1043    /// returns true if the contract supports the enumerable extension
1044    fn has_enumerable(&self, querier: &QuerierWrapper) -> bool {
1045        self.tokens(querier, self.addr(), None, Some(1)).is_ok()
1046    }
1047}