cw721_base_updatable/
query.rs

1use serde::de::DeserializeOwned;
2use serde::Serialize;
3
4use cosmwasm_std::{to_binary, Addr, Binary, BlockInfo, Deps, Env, Order, StdError, StdResult};
5
6use cw721_updatable::{
7    AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, CustomMsg,
8    Cw721Query, Expiration, NftInfoResponse, NumTokensResponse, OperatorsResponse, OwnerOfResponse,
9    TokensResponse,
10};
11use cw_storage_plus::Bound;
12use cw_utils::maybe_addr;
13
14use crate::msg::{MinterResponse, QueryMsg};
15use crate::state::{Approval, Cw721Contract, TokenInfo};
16
17const DEFAULT_LIMIT: u32 = 10;
18const MAX_LIMIT: u32 = 100;
19
20impl<'a, T, C, E, Q> Cw721Query<T> for Cw721Contract<'a, T, C, E, Q>
21where
22    T: Serialize + DeserializeOwned + Clone,
23    C: CustomMsg,
24    E: CustomMsg,
25    Q: CustomMsg,
26{
27    fn contract_info(&self, deps: Deps) -> StdResult<ContractInfoResponse> {
28        self.contract_info.load(deps.storage)
29    }
30
31    fn num_tokens(&self, deps: Deps) -> StdResult<NumTokensResponse> {
32        let count = self.token_count(deps.storage)?;
33        Ok(NumTokensResponse { count })
34    }
35
36    fn nft_info(&self, deps: Deps, token_id: String) -> StdResult<NftInfoResponse<T>> {
37        let info = self.tokens.load(deps.storage, &token_id)?;
38        Ok(NftInfoResponse {
39            token_uri: info.token_uri,
40            extension: info.extension,
41        })
42    }
43
44    fn owner_of(
45        &self,
46        deps: Deps,
47        env: Env,
48        token_id: String,
49        include_expired: bool,
50    ) -> StdResult<OwnerOfResponse> {
51        let info = self.tokens.load(deps.storage, &token_id)?;
52        Ok(OwnerOfResponse {
53            owner: info.owner.to_string(),
54            approvals: humanize_approvals(&env.block, &info, include_expired),
55        })
56    }
57
58    /// operators returns all operators owner given access to
59    fn operators(
60        &self,
61        deps: Deps,
62        env: Env,
63        owner: String,
64        include_expired: bool,
65        start_after: Option<String>,
66        limit: Option<u32>,
67    ) -> StdResult<OperatorsResponse> {
68        let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
69        let start_addr = maybe_addr(deps.api, start_after)?;
70        let start = start_addr.as_ref().map(Bound::exclusive);
71
72        let owner_addr = deps.api.addr_validate(&owner)?;
73        let res: StdResult<Vec<_>> = self
74            .operators
75            .prefix(&owner_addr)
76            .range(deps.storage, start, None, Order::Ascending)
77            .filter(|r| {
78                include_expired || r.is_err() || !r.as_ref().unwrap().1.is_expired(&env.block)
79            })
80            .take(limit)
81            .map(parse_approval)
82            .collect();
83        Ok(OperatorsResponse { operators: res? })
84    }
85
86    fn approval(
87        &self,
88        deps: Deps,
89        env: Env,
90        token_id: String,
91        spender: String,
92        include_expired: bool,
93    ) -> StdResult<ApprovalResponse> {
94        let token = self.tokens.load(deps.storage, &token_id)?;
95
96        // token owner has absolute approval
97        if token.owner == spender {
98            let approval = cw721_updatable::Approval {
99                spender: token.owner.to_string(),
100                expires: Expiration::Never {},
101            };
102            return Ok(ApprovalResponse { approval });
103        }
104
105        let filtered: Vec<_> = token
106            .approvals
107            .into_iter()
108            .filter(|t| t.spender == spender)
109            .filter(|t| include_expired || !t.is_expired(&env.block))
110            .map(|a| cw721_updatable::Approval {
111                spender: a.spender.into_string(),
112                expires: a.expires,
113            })
114            .collect();
115
116        if filtered.is_empty() {
117            return Err(StdError::not_found("Approval not found"));
118        }
119        // we expect only one item
120        let approval = filtered[0].clone();
121
122        Ok(ApprovalResponse { approval })
123    }
124
125    /// approvals returns all approvals owner given access to
126    fn approvals(
127        &self,
128        deps: Deps,
129        env: Env,
130        token_id: String,
131        include_expired: bool,
132    ) -> StdResult<ApprovalsResponse> {
133        let token = self.tokens.load(deps.storage, &token_id)?;
134        let approvals: Vec<_> = token
135            .approvals
136            .into_iter()
137            .filter(|t| include_expired || !t.is_expired(&env.block))
138            .map(|a| cw721_updatable::Approval {
139                spender: a.spender.into_string(),
140                expires: a.expires,
141            })
142            .collect();
143
144        Ok(ApprovalsResponse { approvals })
145    }
146
147    fn tokens(
148        &self,
149        deps: Deps,
150        owner: String,
151        start_after: Option<String>,
152        limit: Option<u32>,
153    ) -> StdResult<TokensResponse> {
154        let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
155        let start = start_after.map(|s| Bound::ExclusiveRaw(s.into()));
156
157        let owner_addr = deps.api.addr_validate(&owner)?;
158        let tokens: Vec<String> = self
159            .tokens
160            .idx
161            .owner
162            .prefix(owner_addr)
163            .keys(deps.storage, start, None, Order::Ascending)
164            .take(limit)
165            .collect::<StdResult<Vec<_>>>()?;
166
167        Ok(TokensResponse { tokens })
168    }
169
170    fn all_tokens(
171        &self,
172        deps: Deps,
173        start_after: Option<String>,
174        limit: Option<u32>,
175    ) -> StdResult<TokensResponse> {
176        let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
177        let start = start_after.map(|s| Bound::ExclusiveRaw(s.into()));
178
179        let tokens: StdResult<Vec<String>> = self
180            .tokens
181            .range(deps.storage, start, None, Order::Ascending)
182            .take(limit)
183            .map(|item| item.map(|(k, _)| k))
184            .collect();
185
186        Ok(TokensResponse { tokens: tokens? })
187    }
188
189    fn all_nft_info(
190        &self,
191        deps: Deps,
192        env: Env,
193        token_id: String,
194        include_expired: bool,
195    ) -> StdResult<AllNftInfoResponse<T>> {
196        let info = self.tokens.load(deps.storage, &token_id)?;
197        Ok(AllNftInfoResponse {
198            access: OwnerOfResponse {
199                owner: info.owner.to_string(),
200                approvals: humanize_approvals(&env.block, &info, include_expired),
201            },
202            info: NftInfoResponse {
203                token_uri: info.token_uri,
204                extension: info.extension,
205            },
206        })
207    }
208}
209
210impl<'a, T, C, E, Q> Cw721Contract<'a, T, C, E, Q>
211where
212    T: Serialize + DeserializeOwned + Clone,
213    C: CustomMsg,
214    E: CustomMsg,
215    Q: CustomMsg,
216{
217    pub fn minter(&self, deps: Deps) -> StdResult<MinterResponse> {
218        let minter_addr = self.minter.load(deps.storage)?;
219        Ok(MinterResponse {
220            minter: minter_addr.to_string(),
221        })
222    }
223
224    pub fn query(&self, deps: Deps, env: Env, msg: QueryMsg<Q>) -> StdResult<Binary> {
225        match msg {
226            QueryMsg::Minter {} => to_binary(&self.minter(deps)?),
227            QueryMsg::ContractInfo {} => to_binary(&self.contract_info(deps)?),
228            QueryMsg::NftInfo { token_id } => to_binary(&self.nft_info(deps, token_id)?),
229            QueryMsg::OwnerOf {
230                token_id,
231                include_expired,
232            } => {
233                to_binary(&self.owner_of(deps, env, token_id, include_expired.unwrap_or(false))?)
234            }
235            QueryMsg::AllNftInfo {
236                token_id,
237                include_expired,
238            } => to_binary(&self.all_nft_info(
239                deps,
240                env,
241                token_id,
242                include_expired.unwrap_or(false),
243            )?),
244            QueryMsg::AllOperators {
245                owner,
246                include_expired,
247                start_after,
248                limit,
249            } => to_binary(&self.operators(
250                deps,
251                env,
252                owner,
253                include_expired.unwrap_or(false),
254                start_after,
255                limit,
256            )?),
257            QueryMsg::NumTokens {} => to_binary(&self.num_tokens(deps)?),
258            QueryMsg::Tokens {
259                owner,
260                start_after,
261                limit,
262            } => to_binary(&self.tokens(deps, owner, start_after, limit)?),
263            QueryMsg::AllTokens { start_after, limit } => {
264                to_binary(&self.all_tokens(deps, start_after, limit)?)
265            }
266            QueryMsg::Approval {
267                token_id,
268                spender,
269                include_expired,
270            } => to_binary(&self.approval(
271                deps,
272                env,
273                token_id,
274                spender,
275                include_expired.unwrap_or(false),
276            )?),
277            QueryMsg::Approvals {
278                token_id,
279                include_expired,
280            } => {
281                to_binary(&self.approvals(deps, env, token_id, include_expired.unwrap_or(false))?)
282            }
283            QueryMsg::Extension { msg: _ } => Ok(Binary::default()),
284        }
285    }
286}
287
288fn parse_approval(item: StdResult<(Addr, Expiration)>) -> StdResult<cw721_updatable::Approval> {
289    item.map(|(spender, expires)| cw721_updatable::Approval {
290        spender: spender.to_string(),
291        expires,
292    })
293}
294
295fn humanize_approvals<T>(
296    block: &BlockInfo,
297    info: &TokenInfo<T>,
298    include_expired: bool,
299) -> Vec<cw721_updatable::Approval> {
300    info.approvals
301        .iter()
302        .filter(|apr| include_expired || !apr.is_expired(block))
303        .map(humanize_approval)
304        .collect()
305}
306
307fn humanize_approval(approval: &Approval) -> cw721_updatable::Approval {
308    cw721_updatable::Approval {
309        spender: approval.spender.to_string(),
310        expires: approval.expires,
311    }
312}