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 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 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 let approval = filtered[0].clone();
121
122 Ok(ApprovalResponse { approval })
123 }
124
125 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}