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