cw721_basic/
query.rs

1use serde::de::DeserializeOwned;
2use serde::Serialize;
3
4use cosmwasm_std::{to_binary, Binary, BlockInfo, Deps, Env, Order, Pair, StdError, StdResult};
5
6use cw0::maybe_addr;
7use cw721::{
8    AllNftInfoResponse, ApprovedForAllResponse, ContractInfoResponse, CustomMsg, Cw721Query,
9    Expiration, NftInfoResponse, NumTokensResponse, OwnerOfResponse, TokensResponse,
10};
11use cw_storage_plus::Bound;
12
13use crate::msg::{MinterResponse, QueryMsg};
14use crate::state::{Approval, Cw721Contract, TokenInfo};
15
16const DEFAULT_LIMIT: u32 = 10;
17const MAX_LIMIT: u32 = 30;
18
19impl<'a, T, C> Cw721Query<T> for Cw721Contract<'a, T, C>
20where
21    T: Serialize + DeserializeOwned + Clone,
22    C: CustomMsg,
23{
24    fn contract_info(&self, deps: Deps) -> StdResult<ContractInfoResponse> {
25        self.contract_info.load(deps.storage)
26    }
27
28    fn num_tokens(&self, deps: Deps) -> StdResult<NumTokensResponse> {
29        let count = self.token_count(deps.storage)?;
30        Ok(NumTokensResponse { count })
31    }
32
33    fn nft_info(&self, deps: Deps, token_id: String) -> StdResult<NftInfoResponse<T>> {
34        let info = self.tokens.load(deps.storage, &token_id)?;
35        Ok(NftInfoResponse {
36            token_uri: info.token_uri,
37            extension: info.extension,
38        })
39    }
40
41    fn owner_of(
42        &self,
43        deps: Deps,
44        env: Env,
45        token_id: String,
46        include_expired: bool,
47    ) -> StdResult<OwnerOfResponse> {
48        let info = self.tokens.load(deps.storage, &token_id)?;
49        Ok(OwnerOfResponse {
50            owner: info.owner.to_string(),
51            approvals: humanize_approvals(&env.block, &info, include_expired),
52        })
53    }
54
55    fn all_approvals(
56        &self,
57        deps: Deps,
58        env: Env,
59        owner: String,
60        include_expired: bool,
61        start_after: Option<String>,
62        limit: Option<u32>,
63    ) -> StdResult<ApprovedForAllResponse> {
64        let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
65        let start_addr = maybe_addr(deps.api, start_after)?;
66        let start = start_addr.map(|addr| Bound::exclusive(addr.as_ref()));
67
68        let owner_addr = deps.api.addr_validate(&owner)?;
69        let res: StdResult<Vec<_>> = self
70            .operators
71            .prefix(&owner_addr)
72            .range(deps.storage, start, None, Order::Ascending)
73            .filter(|r| {
74                include_expired || r.is_err() || !r.as_ref().unwrap().1.is_expired(&env.block)
75            })
76            .take(limit)
77            .map(parse_approval)
78            .collect();
79        Ok(ApprovedForAllResponse { operators: res? })
80    }
81
82    fn tokens(
83        &self,
84        deps: Deps,
85        owner: String,
86        start_after: Option<String>,
87        limit: Option<u32>,
88    ) -> StdResult<TokensResponse> {
89        let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
90        let start = start_after.map(Bound::exclusive);
91
92        let owner_addr = deps.api.addr_validate(&owner)?;
93        let pks: Vec<_> = self
94            .tokens
95            .idx
96            .owner
97            .prefix(owner_addr)
98            .keys(deps.storage, start, None, Order::Ascending)
99            .take(limit)
100            .collect();
101
102        let res: Result<Vec<_>, _> = pks.iter().map(|v| String::from_utf8(v.to_vec())).collect();
103        let tokens = res.map_err(StdError::invalid_utf8)?;
104        Ok(TokensResponse { tokens })
105    }
106
107    fn all_tokens(
108        &self,
109        deps: Deps,
110        start_after: Option<String>,
111        limit: Option<u32>,
112    ) -> StdResult<TokensResponse> {
113        let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
114        let start_addr = maybe_addr(deps.api, start_after)?;
115        let start = start_addr.map(|addr| Bound::exclusive(addr.as_ref()));
116
117        let tokens: StdResult<Vec<String>> = self
118            .tokens
119            .range(deps.storage, start, None, Order::Ascending)
120            .take(limit)
121            .map(|item| item.map(|(k, _)| String::from_utf8_lossy(&k).to_string()))
122            .collect();
123        Ok(TokensResponse { tokens: tokens? })
124    }
125
126    fn all_nft_info(
127        &self,
128        deps: Deps,
129        env: Env,
130        token_id: String,
131        include_expired: bool,
132    ) -> StdResult<AllNftInfoResponse<T>> {
133        let info = self.tokens.load(deps.storage, &token_id)?;
134        Ok(AllNftInfoResponse {
135            access: OwnerOfResponse {
136                owner: info.owner.to_string(),
137                approvals: humanize_approvals(&env.block, &info, include_expired),
138            },
139            info: NftInfoResponse {
140                token_uri: info.token_uri,
141                extension: info.extension,
142            },
143        })
144    }
145}
146
147impl<'a, T, C> Cw721Contract<'a, T, C>
148where
149    T: Serialize + DeserializeOwned + Clone,
150    C: CustomMsg,
151{
152    pub fn minter(&self, deps: Deps) -> StdResult<MinterResponse> {
153        let minter_addr = self.minter.load(deps.storage)?;
154        Ok(MinterResponse {
155            minter: minter_addr.to_string(),
156        })
157    }
158
159    pub fn query(&self, deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
160        match msg {
161            QueryMsg::Minter {} => to_binary(&self.minter(deps)?),
162            QueryMsg::ContractInfo {} => to_binary(&self.contract_info(deps)?),
163            QueryMsg::NftInfo { token_id } => to_binary(&self.nft_info(deps, token_id)?),
164            QueryMsg::OwnerOf {
165                token_id,
166                include_expired,
167            } => {
168                to_binary(&self.owner_of(deps, env, token_id, include_expired.unwrap_or(false))?)
169            }
170            QueryMsg::AllNftInfo {
171                token_id,
172                include_expired,
173            } => to_binary(&self.all_nft_info(
174                deps,
175                env,
176                token_id,
177                include_expired.unwrap_or(false),
178            )?),
179            QueryMsg::ApprovedForAll {
180                owner,
181                include_expired,
182                start_after,
183                limit,
184            } => to_binary(&self.all_approvals(
185                deps,
186                env,
187                owner,
188                include_expired.unwrap_or(false),
189                start_after,
190                limit,
191            )?),
192            QueryMsg::NumTokens {} => to_binary(&self.num_tokens(deps)?),
193            QueryMsg::Tokens {
194                owner,
195                start_after,
196                limit,
197            } => to_binary(&self.tokens(deps, owner, start_after, limit)?),
198            QueryMsg::AllTokens { start_after, limit } => {
199                to_binary(&self.all_tokens(deps, start_after, limit)?)
200            }
201        }
202    }
203}
204
205fn parse_approval(item: StdResult<Pair<Expiration>>) -> StdResult<cw721::Approval> {
206    item.and_then(|(k, expires)| {
207        let spender = String::from_utf8(k)?;
208        Ok(cw721::Approval { spender, expires })
209    })
210}
211
212fn humanize_approvals<T>(
213    block: &BlockInfo,
214    info: &TokenInfo<T>,
215    include_expired: bool,
216) -> Vec<cw721::Approval> {
217    info.approvals
218        .iter()
219        .filter(|apr| include_expired || !apr.is_expired(block))
220        .map(humanize_approval)
221        .collect()
222}
223
224fn humanize_approval(approval: &Approval) -> cw721::Approval {
225    cw721::Approval {
226        spender: approval.spender.to_string(),
227        expires: approval.expires,
228    }
229}