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}