300 lines
9.2 KiB
Rust
300 lines
9.2 KiB
Rust
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<T> for Cw721Contract<'a, T, C>
|
|
where
|
|
T: Serialize + DeserializeOwned + Clone,
|
|
C: CustomMsg,
|
|
{
|
|
fn contract_info(&self, deps: Deps) -> StdResult<ContractInfoResponse> {
|
|
self.contract_info.load(deps.storage)
|
|
}
|
|
|
|
fn num_tokens(&self, deps: Deps) -> StdResult<NumTokensResponse> {
|
|
let count = self.token_count(deps.storage)?;
|
|
Ok(NumTokensResponse { count })
|
|
}
|
|
|
|
fn nft_info(&self, deps: Deps, token_id: String) -> StdResult<NftInfoResponse<T>> {
|
|
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<OwnerOfResponse> {
|
|
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<String>,
|
|
limit: Option<u32>,
|
|
) -> StdResult<OperatorsResponse> {
|
|
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<Vec<_>> = 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<ApprovalResponse> {
|
|
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<ApprovalsResponse> {
|
|
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<String>,
|
|
limit: Option<u32>,
|
|
) -> StdResult<TokensResponse> {
|
|
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<Vec<_>, _> = 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<String>,
|
|
limit: Option<u32>,
|
|
) -> StdResult<TokensResponse> {
|
|
let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
|
|
let start = start_after.map(Bound::exclusive);
|
|
|
|
let tokens: StdResult<Vec<String>> = 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<AllNftInfoResponse<T>> {
|
|
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<MinterResponse> {
|
|
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<Binary> {
|
|
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<V = Vec<u8>> = (Vec<u8>, V);
|
|
fn parse_approval(item: StdResult<Record<Expiration>>) -> StdResult<cw721::Approval> {
|
|
item.and_then(|(k, expires)| {
|
|
let spender = String::from_utf8(k)?;
|
|
Ok(cw721::Approval { spender, expires })
|
|
})
|
|
}
|
|
|
|
fn humanize_approvals<T>(
|
|
block: &BlockInfo,
|
|
info: &TokenInfo<T>,
|
|
include_expired: bool,
|
|
) -> Vec<cw721::Approval> {
|
|
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,
|
|
}
|
|
}
|