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#[deprecated(since = "0.19.0", note = "Please use query_minter_ownership instead")]
60pub 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
244pub 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
275pub 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 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 let approval = filtered[0].clone();
338
339 Ok(ApprovalResponse { approval })
340}
341
342pub 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}