Skip to main content

cw721/
query.rs

1use cosmwasm_std::{
2    Addr, BlockInfo, CustomMsg, Deps, Empty, Env, Order, StdError, StdResult, Storage,
3};
4use cw_ownable::Ownership;
5use cw_storage_plus::Bound;
6use cw_utils::{maybe_addr, Expiration};
7
8use crate::{
9    error::Cw721ContractError,
10    extension::{
11        Cw721BaseExtensions, Cw721EmptyExtensions, Cw721Extensions, Cw721OnchainExtensions,
12    },
13    msg::{
14        AdditionalMintersResponse, AllInfoResponse, AllNftInfoResponse, ApprovalResponse,
15        ApprovalsResponse, CollectionInfoAndExtensionResponse, ConfigResponse, MinterResponse,
16        NftInfoResponse, NumTokensResponse, OperatorResponse, OperatorsResponse, OwnerOfResponse,
17        TokensResponse,
18    },
19    state::{
20        Approval, CollectionExtensionAttributes, CollectionInfo, Cw721Config, NftInfo,
21        ADDITIONAL_MINTERS, CREATOR, MINTER,
22    },
23    traits::{Contains, Cw721CustomMsg, Cw721Query, Cw721State, FromAttributesState},
24    DefaultOptionalCollectionExtension, DefaultOptionalNftExtension,
25    EmptyOptionalCollectionExtension, EmptyOptionalNftExtension,
26};
27
28pub const DEFAULT_LIMIT: u32 = 10;
29pub const MAX_LIMIT: u32 = 1000;
30
31pub fn parse_approval(item: StdResult<(Addr, Expiration)>) -> StdResult<Approval> {
32    item.map(|(spender, expires)| Approval { spender, expires })
33}
34
35pub fn humanize_approvals<TNftExtension>(
36    block: &BlockInfo,
37    nft_info: &NftInfo<TNftExtension>,
38    include_expired_approval: bool,
39) -> Vec<Approval>
40where
41    TNftExtension: Cw721State,
42{
43    nft_info
44        .approvals
45        .iter()
46        .filter(|apr| include_expired_approval || !apr.is_expired(block))
47        .map(humanize_approval)
48        .collect()
49}
50
51pub fn humanize_approval(approval: &Approval) -> Approval {
52    Approval {
53        spender: approval.spender.clone(),
54        expires: approval.expires,
55    }
56}
57
58// --- query helpers ---
59#[deprecated(since = "0.19.0", note = "Please use query_minter_ownership instead")]
60/// Deprecated: use query_minter_ownership instead! Will be removed in next release!
61pub fn query_minter(storage: &dyn Storage) -> StdResult<MinterResponse> {
62    let minter = MINTER
63        .get_ownership(storage)?
64        .owner
65        .map(|a| a.into_string());
66
67    Ok(MinterResponse { minter })
68}
69
70pub fn query_minter_ownership(storage: &dyn Storage) -> StdResult<Ownership<Addr>> {
71    MINTER.get_ownership(storage)
72}
73
74pub fn query_creator_ownership(storage: &dyn Storage) -> StdResult<Ownership<Addr>> {
75    CREATOR.get_ownership(storage)
76}
77
78pub fn query_additional_minters(
79    deps: Deps,
80    start_after: Option<String>,
81    limit: Option<u32>,
82) -> StdResult<AdditionalMintersResponse> {
83    let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
84    let start_addr = maybe_addr(deps.api, start_after)?;
85    let start = start_addr.as_ref().map(Bound::exclusive);
86    let minters = ADDITIONAL_MINTERS
87        .keys(deps.storage, start, None, Order::Ascending)
88        .take(limit)
89        .map(|item| item.map(|addr| addr.to_string()))
90        .collect::<StdResult<Vec<_>>>()?;
91    Ok(AdditionalMintersResponse { minters })
92}
93
94pub fn query_collection_info(storage: &dyn Storage) -> StdResult<CollectionInfo> {
95    let config = Cw721Config::<Option<Empty>>::default();
96    config.collection_info.load(storage)
97}
98
99pub fn query_collection_extension_attributes(
100    deps: Deps,
101) -> StdResult<CollectionExtensionAttributes> {
102    let config = Cw721Config::<Option<Empty>>::default();
103    config
104        .collection_extension
105        .range(deps.storage, None, None, Order::Ascending)
106        .map(|kv| Ok(kv?.1))
107        .collect()
108}
109
110pub fn query_config<TCollectionExtension>(
111    deps: Deps,
112    contract_addr: impl Into<String>,
113) -> Result<ConfigResponse<TCollectionExtension>, Cw721ContractError>
114where
115    TCollectionExtension: Cw721State + FromAttributesState,
116{
117    let collection_info = query_collection_info(deps.storage)?;
118    let attributes = query_collection_extension_attributes(deps)?;
119    let collection_extension = FromAttributesState::from_attributes_state(&attributes)?;
120    let num_tokens = query_num_tokens(deps.storage)?.count;
121    let minter_ownership = query_minter_ownership(deps.storage)?;
122    let creator_ownership = query_creator_ownership(deps.storage)?;
123    let withdraw_address = query_withdraw_address(deps)?;
124    let contract_info = deps.querier.query_wasm_contract_info(contract_addr)?;
125    Ok(ConfigResponse {
126        num_tokens,
127        minter_ownership,
128        creator_ownership,
129        collection_info,
130        collection_extension,
131        withdraw_address,
132        contract_info,
133    })
134}
135pub fn query_collection_info_and_extension<TCollectionExtension>(
136    deps: Deps,
137) -> Result<CollectionInfoAndExtensionResponse<TCollectionExtension>, Cw721ContractError>
138where
139    TCollectionExtension: Cw721State + FromAttributesState,
140{
141    let collection_info = query_collection_info(deps.storage)?;
142    let attributes = query_collection_extension_attributes(deps)?;
143    let extension = FromAttributesState::from_attributes_state(&attributes)?;
144    Ok(CollectionInfoAndExtensionResponse {
145        name: collection_info.name,
146        symbol: collection_info.symbol,
147        updated_at: collection_info.updated_at,
148        extension,
149    })
150}
151
152pub fn query_all_info(deps: Deps, env: &Env) -> StdResult<AllInfoResponse> {
153    let collection_info = query_collection_info(deps.storage)?;
154    let attributes = query_collection_extension_attributes(deps)?;
155    let num_tokens = Cw721Config::<Option<Empty>>::default().token_count(deps.storage)?;
156    let contract_info = deps
157        .querier
158        .query_wasm_contract_info(env.contract.address.clone())?;
159    Ok(AllInfoResponse {
160        collection_info,
161        collection_extension: attributes,
162        num_tokens,
163        contract_info,
164    })
165}
166
167pub fn query_num_tokens(storage: &dyn Storage) -> StdResult<NumTokensResponse> {
168    let count = Cw721Config::<Option<Empty>>::default().token_count(storage)?;
169    Ok(NumTokensResponse { count })
170}
171
172pub fn query_nft_info<TNftExtension>(
173    storage: &dyn Storage,
174    token_id: String,
175) -> StdResult<NftInfoResponse<TNftExtension>>
176where
177    TNftExtension: Cw721State,
178{
179    let info = Cw721Config::<TNftExtension>::default()
180        .nft_info
181        .load(storage, &token_id)?;
182    Ok(NftInfoResponse {
183        token_uri: info.token_uri,
184        extension: info.extension,
185    })
186}
187
188pub fn query_nft_by_extension<TNftExtension>(
189    storage: &dyn Storage,
190    extension: TNftExtension,
191    start_after: Option<String>,
192    limit: Option<u32>,
193) -> StdResult<Option<Vec<NftInfoResponse<TNftExtension>>>>
194where
195    TNftExtension: Cw721State + Contains,
196{
197    let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
198    let start = start_after.map(|s| Bound::ExclusiveRaw(s.into()));
199
200    let nfts: Vec<Option<NftInfo<TNftExtension>>> = Cw721Config::<TNftExtension>::default()
201        .nft_info
202        .range(storage, start, None, Order::Ascending)
203        .take(limit)
204        .map(|kv| {
205            let nft = kv?.1;
206            let result = if nft.extension.contains(&extension) {
207                Some(nft)
208            } else {
209                None
210            };
211            Ok(result)
212        })
213        .collect::<StdResult<_>>()?;
214    let filtered = nfts
215        .iter()
216        .filter_map(|n| n.clone())
217        .map(|n| NftInfoResponse {
218            token_uri: n.token_uri,
219            extension: n.extension,
220        })
221        .collect::<Vec<NftInfoResponse<TNftExtension>>>();
222    if filtered.is_empty() {
223        Ok(None)
224    } else {
225        Ok(Some(filtered))
226    }
227}
228
229pub fn query_owner_of(
230    deps: Deps,
231    env: &Env,
232    token_id: String,
233    include_expired_approval: bool,
234) -> StdResult<OwnerOfResponse> {
235    let nft_info = Cw721Config::<Option<Empty>>::default()
236        .nft_info
237        .load(deps.storage, &token_id)?;
238    Ok(OwnerOfResponse {
239        owner: nft_info.owner.to_string(),
240        approvals: humanize_approvals(&env.block, &nft_info, include_expired_approval),
241    })
242}
243
244/// operator returns the approval status of an operator for a given owner if exists
245pub fn query_operator(
246    deps: Deps,
247    env: &Env,
248    owner: String,
249    operator: String,
250    include_expired_approval: bool,
251) -> StdResult<OperatorResponse> {
252    let owner_addr = deps.api.addr_validate(&owner)?;
253    let operator_addr = deps.api.addr_validate(&operator)?;
254
255    let info = Cw721Config::<Option<Empty>>::default()
256        .operators
257        .may_load(deps.storage, (&owner_addr, &operator_addr))?;
258
259    if let Some(expires) = info {
260        if !include_expired_approval && expires.is_expired(&env.block) {
261            return Err(StdError::not_found("Approval not found"));
262        }
263
264        return Ok(OperatorResponse {
265            approval: Approval {
266                spender: operator_addr,
267                expires,
268            },
269        });
270    }
271
272    Err(StdError::not_found("Approval not found"))
273}
274
275/// operators returns all operators owner given access to
276pub fn query_operators(
277    deps: Deps,
278    env: &Env,
279    owner: String,
280    include_expired_approval: bool,
281    start_after: Option<String>,
282    limit: Option<u32>,
283) -> StdResult<OperatorsResponse> {
284    let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
285    let start_addr = maybe_addr(deps.api, start_after)?;
286    let start = start_addr.as_ref().map(Bound::exclusive);
287
288    let owner_addr = deps.api.addr_validate(&owner)?;
289    let res: StdResult<Vec<_>> = Cw721Config::<Option<Empty>>::default()
290        .operators
291        .prefix(&owner_addr)
292        .range(deps.storage, start, None, Order::Ascending)
293        .filter(|r| {
294            include_expired_approval || r.is_err() || !r.as_ref().unwrap().1.is_expired(&env.block)
295        })
296        .take(limit)
297        .map(parse_approval)
298        .collect();
299    Ok(OperatorsResponse { operators: res? })
300}
301
302pub fn query_approval(
303    deps: Deps,
304    env: &Env,
305    token_id: String,
306    spender: Addr,
307    include_expired_approval: bool,
308) -> StdResult<ApprovalResponse> {
309    let token = Cw721Config::<Option<Empty>>::default()
310        .nft_info
311        .load(deps.storage, &token_id)?;
312
313    // token owner has absolute approval
314    if token.owner == spender {
315        let approval = Approval {
316            spender: token.owner,
317            expires: Expiration::Never {},
318        };
319        return Ok(ApprovalResponse { approval });
320    }
321
322    let filtered: Vec<_> = token
323        .approvals
324        .into_iter()
325        .filter(|t| t.spender == spender)
326        .filter(|t| include_expired_approval || !t.is_expired(&env.block))
327        .map(|a| Approval {
328            spender: a.spender,
329            expires: a.expires,
330        })
331        .collect();
332
333    if filtered.is_empty() {
334        return Err(StdError::not_found("Approval not found"));
335    }
336    // we expect only one item
337    let approval = filtered[0].clone();
338
339    Ok(ApprovalResponse { approval })
340}
341
342/// approvals returns all approvals owner given access to
343pub fn query_approvals(
344    deps: Deps,
345    env: &Env,
346    token_id: String,
347    include_expired_approval: bool,
348) -> StdResult<ApprovalsResponse> {
349    let token = Cw721Config::<Option<Empty>>::default()
350        .nft_info
351        .load(deps.storage, &token_id)?;
352    let approvals: Vec<_> = token
353        .approvals
354        .into_iter()
355        .filter(|t| include_expired_approval || !t.is_expired(&env.block))
356        .map(|a| Approval {
357            spender: a.spender,
358            expires: a.expires,
359        })
360        .collect();
361
362    Ok(ApprovalsResponse { approvals })
363}
364
365pub fn query_tokens(
366    deps: Deps,
367    _env: &Env,
368    owner: String,
369    start_after: Option<String>,
370    limit: Option<u32>,
371) -> StdResult<TokensResponse> {
372    let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
373    let start = start_after.map(|s| Bound::ExclusiveRaw(s.into()));
374
375    let owner_addr = deps.api.addr_validate(&owner)?;
376    let tokens: Vec<String> = Cw721Config::<Option<Empty>>::default()
377        .nft_info
378        .idx
379        .owner
380        .prefix(owner_addr)
381        .keys(deps.storage, start, None, Order::Ascending)
382        .take(limit)
383        .collect::<StdResult<Vec<_>>>()?;
384
385    Ok(TokensResponse { tokens })
386}
387
388pub fn query_all_tokens(
389    deps: Deps,
390    _env: &Env,
391    start_after: Option<String>,
392    limit: Option<u32>,
393) -> StdResult<TokensResponse> {
394    let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
395    let start = start_after.map(|s| Bound::ExclusiveRaw(s.into()));
396
397    let tokens = Cw721Config::<Option<Empty>>::default()
398        .nft_info
399        .range(deps.storage, start, None, Order::Ascending)
400        .take(limit)
401        .map(|item| item.map(|(k, _)| k))
402        .collect::<StdResult<Vec<_>>>()?;
403
404    Ok(TokensResponse { tokens })
405}
406
407pub fn query_all_nft_info<TNftExtension>(
408    deps: Deps,
409    env: &Env,
410    token_id: String,
411    include_expired_approval: bool,
412) -> StdResult<AllNftInfoResponse<TNftExtension>>
413where
414    TNftExtension: Cw721State,
415{
416    let nft_info = Cw721Config::<TNftExtension>::default()
417        .nft_info
418        .load(deps.storage, &token_id)?;
419    Ok(AllNftInfoResponse {
420        access: OwnerOfResponse {
421            owner: nft_info.owner.to_string(),
422            approvals: humanize_approvals(&env.block, &nft_info, include_expired_approval),
423        },
424        info: NftInfoResponse {
425            token_uri: nft_info.token_uri,
426            extension: nft_info.extension,
427        },
428    })
429}
430
431pub fn query_withdraw_address(deps: Deps) -> StdResult<Option<String>> {
432    Cw721Config::<Option<Empty>>::default()
433        .withdraw_address
434        .may_load(deps.storage)
435}
436
437impl Cw721Query<DefaultOptionalNftExtension, DefaultOptionalCollectionExtension, Empty>
438    for Cw721OnchainExtensions<'_>
439{
440}
441
442impl Cw721Query<EmptyOptionalNftExtension, DefaultOptionalCollectionExtension, Empty>
443    for Cw721BaseExtensions<'_>
444{
445}
446
447impl Cw721Query<EmptyOptionalNftExtension, EmptyOptionalCollectionExtension, Empty>
448    for Cw721EmptyExtensions<'_>
449{
450}
451
452impl<
453        TNftExtension,
454        TNftExtensionMsg,
455        TCollectionExtension,
456        TCollectionExtensionMsg,
457        TExtensionMsg,
458        TExtensionQueryMsg,
459        TCustomResponseMsg,
460    > Cw721Query<TNftExtension, TCollectionExtension, TExtensionQueryMsg>
461    for Cw721Extensions<
462        '_,
463        TNftExtension,
464        TNftExtensionMsg,
465        TCollectionExtension,
466        TCollectionExtensionMsg,
467        TExtensionMsg,
468        TExtensionQueryMsg,
469        TCustomResponseMsg,
470    >
471where
472    TNftExtension: Cw721State + Contains,
473    TNftExtensionMsg: Cw721CustomMsg,
474    TCollectionExtension: Cw721State + FromAttributesState,
475    TCollectionExtensionMsg: Cw721CustomMsg,
476    TExtensionQueryMsg: Cw721CustomMsg,
477    TCustomResponseMsg: CustomMsg,
478{
479}