use serde::de::DeserializeOwned; use serde::Serialize; use cosmwasm_std::{to_binary, Binary, BlockInfo, Deps, Env, Order, StdError, StdResult}; use cw0::maybe_addr; use cw721::{ AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, CustomMsg, Cw721Query, Expiration, NftInfoResponse, NumTokensResponse, OperatorsResponse, OwnerOfResponse, TokensResponse, }; use cw_storage_plus::Bound; use crate::msg::{MinterResponse, QueryMsg}; use crate::state::{Approval, Cw721Contract, TokenInfo}; const DEFAULT_LIMIT: u32 = 10; const MAX_LIMIT: u32 = 30; impl<'a, T, C> Cw721Query for Cw721Contract<'a, T, C> where T: Serialize + DeserializeOwned + Clone, C: CustomMsg, { fn contract_info(&self, deps: Deps) -> StdResult { self.contract_info.load(deps.storage) } fn num_tokens(&self, deps: Deps) -> StdResult { let count = self.token_count(deps.storage)?; Ok(NumTokensResponse { count }) } fn nft_info(&self, deps: Deps, token_id: String) -> StdResult> { let info = self.tokens.load(deps.storage, &token_id)?; Ok(NftInfoResponse { token_uri: info.token_uri, extension: info.extension, }) } fn owner_of( &self, deps: Deps, env: Env, token_id: String, include_expired: bool, ) -> StdResult { let info = self.tokens.load(deps.storage, &token_id)?; Ok(OwnerOfResponse { owner: info.owner.to_string(), approvals: humanize_approvals(&env.block, &info, include_expired), }) } /// operators returns all operators owner given access to fn operators( &self, deps: Deps, env: Env, owner: String, include_expired: bool, start_after: Option, limit: Option, ) -> StdResult { let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; let start_addr = maybe_addr(deps.api, start_after)?; let start = start_addr.map(|addr| Bound::exclusive(addr.as_ref())); let owner_addr = deps.api.addr_validate(&owner)?; let res: StdResult> = self .operators .prefix(&owner_addr) .range(deps.storage, start, None, Order::Ascending) .filter(|r| { include_expired || r.is_err() || !r.as_ref().unwrap().1.is_expired(&env.block) }) .take(limit) .map(parse_approval) .collect(); Ok(OperatorsResponse { operators: res? }) } fn approval( &self, deps: Deps, env: Env, token_id: String, spender: String, include_expired: bool, ) -> StdResult { let token = self.tokens.load(deps.storage, &token_id)?; let filtered: Vec<_> = token .approvals .into_iter() .filter(|t| t.spender == spender) .filter(|t| include_expired || !t.is_expired(&env.block)) .map(|a| cw721::Approval { spender: a.spender.into_string(), expires: a.expires, }) .collect(); if filtered.is_empty() { return Err(StdError::not_found("Approval not found")); } // we expect only one item let approval = filtered[0].clone(); Ok(ApprovalResponse { approval }) } /// approvals returns all approvals owner given access to fn approvals( &self, deps: Deps, env: Env, token_id: String, include_expired: bool, ) -> StdResult { let token = self.tokens.load(deps.storage, &token_id)?; let approvals: Vec<_> = token .approvals .into_iter() .filter(|t| include_expired || !t.is_expired(&env.block)) .map(|a| cw721::Approval { spender: a.spender.into_string(), expires: a.expires, }) .collect(); Ok(ApprovalsResponse { approvals }) } fn tokens( &self, deps: Deps, owner: String, start_after: Option, limit: Option, ) -> StdResult { let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; let start = start_after.map(Bound::exclusive); let owner_addr = deps.api.addr_validate(&owner)?; let pks: Vec<_> = self .tokens .idx .owner .prefix(owner_addr) .keys(deps.storage, start, None, Order::Ascending) .take(limit) .collect(); let res: Result, _> = pks.iter().map(|v| String::from_utf8(v.to_vec())).collect(); let tokens = res.map_err(StdError::invalid_utf8)?; Ok(TokensResponse { tokens }) } fn all_tokens( &self, deps: Deps, start_after: Option, limit: Option, ) -> StdResult { let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; let start = start_after.map(Bound::exclusive); let tokens: StdResult> = self .tokens .range(deps.storage, start, None, Order::Ascending) .take(limit) .map(|item| item.map(|(k, _)| String::from_utf8_lossy(&k).to_string())) .collect(); Ok(TokensResponse { tokens: tokens? }) } fn all_nft_info( &self, deps: Deps, env: Env, token_id: String, include_expired: bool, ) -> StdResult> { let info = self.tokens.load(deps.storage, &token_id)?; Ok(AllNftInfoResponse { access: OwnerOfResponse { owner: info.owner.to_string(), approvals: humanize_approvals(&env.block, &info, include_expired), }, info: NftInfoResponse { token_uri: info.token_uri, extension: info.extension, }, }) } } impl<'a, T, C> Cw721Contract<'a, T, C> where T: Serialize + DeserializeOwned + Clone, C: CustomMsg, { pub fn minter(&self, deps: Deps) -> StdResult { let minter_addr = self.minter.load(deps.storage)?; Ok(MinterResponse { minter: minter_addr.to_string(), }) } pub fn query(&self, deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Minter {} => to_binary(&self.minter(deps)?), QueryMsg::ContractInfo {} => to_binary(&self.contract_info(deps)?), QueryMsg::NftInfo { token_id } => to_binary(&self.nft_info(deps, token_id)?), QueryMsg::OwnerOf { token_id, include_expired, } => { to_binary(&self.owner_of(deps, env, token_id, include_expired.unwrap_or(false))?) } QueryMsg::AllNftInfo { token_id, include_expired, } => to_binary(&self.all_nft_info( deps, env, token_id, include_expired.unwrap_or(false), )?), QueryMsg::AllOperators { owner, include_expired, start_after, limit, } => to_binary(&self.operators( deps, env, owner, include_expired.unwrap_or(false), start_after, limit, )?), QueryMsg::NumTokens {} => to_binary(&self.num_tokens(deps)?), QueryMsg::Tokens { owner, start_after, limit, } => to_binary(&self.tokens(deps, owner, start_after, limit)?), QueryMsg::AllTokens { start_after, limit } => { to_binary(&self.all_tokens(deps, start_after, limit)?) } QueryMsg::Approval { token_id, spender, include_expired, } => to_binary(&self.approval( deps, env, token_id, spender, include_expired.unwrap_or(false), )?), QueryMsg::Approvals { token_id, include_expired, } => { to_binary(&self.approvals(deps, env, token_id, include_expired.unwrap_or(false))?) } } } } type Record> = (Vec, V); fn parse_approval(item: StdResult>) -> StdResult { item.and_then(|(k, expires)| { let spender = String::from_utf8(k)?; Ok(cw721::Approval { spender, expires }) }) } fn humanize_approvals( block: &BlockInfo, info: &TokenInfo, include_expired: bool, ) -> Vec { info.approvals .iter() .filter(|apr| include_expired || !apr.is_expired(block)) .map(humanize_approval) .collect() } fn humanize_approval(approval: &Approval) -> cw721::Approval { cw721::Approval { spender: approval.spender.to_string(), expires: approval.expires, } }