use std::collections::HashMap;
use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{
to_json_binary, Addr, Binary, Coin, ContractInfoResponse, Decimal, Deps, Env, MessageInfo,
Timestamp,
};
use cw_ownable::{Action, Ownership};
use cw_utils::Expiration;
use serde::Serialize;
use url::Url;
use crate::error::Cw721ContractError;
use crate::execute::{assert_creator, assert_minter};
use crate::state::{
Attribute, CollectionExtension, CollectionExtensionAttributes, CollectionInfo, NftInfo, Trait,
ATTRIBUTE_BANNER_URL, ATTRIBUTE_DESCRIPTION, ATTRIBUTE_EXPLICIT_CONTENT,
ATTRIBUTE_EXTERNAL_LINK, ATTRIBUTE_IMAGE, ATTRIBUTE_ROYALTY_INFO, ATTRIBUTE_START_TRADING_TIME,
CREATOR, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_ROYALTY_SHARE_DELTA_PCT, MAX_ROYALTY_SHARE_PCT,
MINTER,
};
use crate::traits::{Cw721CustomMsg, Cw721State, FromAttributesState, ToAttributesState};
use crate::NftExtension;
use crate::{traits::StateFactory, Approval, RoyaltyInfo};
#[cw_serde]
pub enum Cw721ExecuteMsg<
TNftExtensionMsg,
TCollectionExtensionMsg,
TExtensionMsg,
> {
#[deprecated(since = "0.19.0", note = "Please use UpdateMinterOwnership instead")]
UpdateOwnership(Action),
UpdateMinterOwnership(Action),
UpdateCreatorOwnership(Action),
UpdateCollectionInfo {
collection_info: CollectionInfoMsg<TCollectionExtensionMsg>,
},
TransferNft {
recipient: String,
token_id: String,
},
SendNft {
contract: String,
token_id: String,
msg: Binary,
},
Approve {
spender: String,
token_id: String,
expires: Option<Expiration>,
},
Revoke {
spender: String,
token_id: String,
},
ApproveAll {
operator: String,
expires: Option<Expiration>,
},
RevokeAll {
operator: String,
},
Mint {
token_id: String,
owner: String,
token_uri: Option<String>,
extension: TNftExtensionMsg,
},
Burn {
token_id: String,
},
AddMinter {
minter: String,
},
RemoveMinter {
minter: String,
},
UpdateExtension {
msg: TExtensionMsg,
},
UpdateNftInfo {
token_id: String,
token_uri: Option<String>,
extension: TNftExtensionMsg,
},
SetWithdrawAddress {
address: String,
},
RemoveWithdrawAddress {},
WithdrawFunds {
amount: Coin,
},
}
#[cw_serde]
pub struct Cw721InstantiateMsg<TCollectionExtensionMsg> {
pub name: String,
pub symbol: String,
pub collection_info_extension: TCollectionExtensionMsg,
pub minter: Option<String>,
pub creator: Option<String>,
pub withdraw_address: Option<String>,
}
#[cw_serde]
#[derive(QueryResponses)]
pub enum Cw721QueryMsg<
TNftExtension,
TCollectionExtension,
TExtensionQueryMsg,
> {
#[returns(OwnerOfResponse)]
OwnerOf {
token_id: String,
include_expired: Option<bool>,
},
#[returns(ApprovalResponse)]
Approval {
token_id: String,
spender: String,
include_expired: Option<bool>,
},
#[returns(ApprovalsResponse)]
Approvals {
token_id: String,
include_expired: Option<bool>,
},
#[returns(OperatorResponse)]
Operator {
owner: String,
operator: String,
include_expired: Option<bool>,
},
#[returns(OperatorsResponse)]
AllOperators {
owner: String,
include_expired: Option<bool>,
start_after: Option<String>,
limit: Option<u32>,
},
#[returns(NumTokensResponse)]
NumTokens {},
#[deprecated(
since = "0.19.0",
note = "Please use GetCollectionInfoAndExtension instead"
)]
#[returns(CollectionInfoAndExtensionResponse<TCollectionExtension>)]
ContractInfo {},
#[returns(ConfigResponse<TCollectionExtension>)]
GetConfig {},
#[returns(CollectionInfoAndExtensionResponse<TCollectionExtension>)]
GetCollectionInfoAndExtension {},
#[returns(AllInfoResponse)]
GetAllInfo {},
#[returns(CollectionExtensionAttributes)]
GetCollectionExtensionAttributes {},
#[deprecated(since = "0.19.0", note = "Please use GetMinterOwnership instead")]
#[returns(Ownership<Addr>)]
Ownership {},
#[deprecated(since = "0.19.0", note = "Please use GetMinterOwnership instead")]
#[returns(MinterResponse)]
Minter {},
#[returns(Ownership<Addr>)]
GetMinterOwnership {},
#[returns(Ownership<Addr>)]
GetCreatorOwnership {},
#[returns(AdditionalMintersResponse)]
GetAdditionalMinters {
start_after: Option<String>,
limit: Option<u32>,
},
#[returns(NftInfoResponse<TNftExtension>)]
NftInfo { token_id: String },
#[returns(Option<NftInfoResponse<TNftExtension>>)]
GetNftByExtension {
extension: TNftExtension,
start_after: Option<String>,
limit: Option<u32>,
},
#[returns(AllNftInfoResponse<TNftExtension>)]
AllNftInfo {
token_id: String,
include_expired: Option<bool>,
},
#[returns(TokensResponse)]
Tokens {
owner: String,
start_after: Option<String>,
limit: Option<u32>,
},
#[returns(TokensResponse)]
AllTokens {
start_after: Option<String>,
limit: Option<u32>,
},
#[returns(())]
Extension { msg: TExtensionQueryMsg },
#[returns(())]
GetCollectionExtension { msg: TCollectionExtension },
#[returns(Option<String>)]
GetWithdrawAddress {},
}
#[cw_serde]
pub enum Cw721MigrateMsg {
WithUpdate {
minter: Option<String>,
creator: Option<String>,
},
}
#[cw_serde]
pub struct CollectionInfoMsg<TCollectionExtensionMsg> {
pub name: Option<String>,
pub symbol: Option<String>,
pub extension: TCollectionExtensionMsg,
}
#[cw_serde]
pub struct AttributeMsg {
pub attr_type: AttributeType,
pub key: String,
pub value: String,
pub data: Option<HashMap<String, String>>,
}
impl AttributeMsg {
pub fn string_value(&self) -> Result<String, Cw721ContractError> {
Ok(self.value.clone())
}
pub fn u64_value(&self) -> Result<u64, Cw721ContractError> {
Ok(self.value.parse::<u64>()?)
}
pub fn bool_value(&self) -> Result<bool, Cw721ContractError> {
Ok(self.value.parse::<bool>()?)
}
pub fn decimal_value(&self) -> Result<Decimal, Cw721ContractError> {
Ok(self.value.parse::<Decimal>()?)
}
pub fn timestamp_value(&self) -> Result<Timestamp, Cw721ContractError> {
let nanos = self.u64_value()?;
Ok(Timestamp::from_nanos(nanos))
}
pub fn addr_value(&self) -> Result<Addr, Cw721ContractError> {
Ok(Addr::unchecked(self.string_value()?))
}
}
impl AttributeMsg {
pub fn from(&self) -> Result<Attribute, Cw721ContractError> {
let value = match self.attr_type {
AttributeType::String => to_json_binary(&self.string_value()?)?,
AttributeType::U64 => to_json_binary(&self.u64_value()?)?,
AttributeType::Boolean => to_json_binary(&self.bool_value()?)?,
AttributeType::Decimal => to_json_binary(&self.decimal_value()?)?,
AttributeType::Timestamp => to_json_binary(&self.timestamp_value()?)?,
AttributeType::Addr => to_json_binary(&self.addr_value()?)?,
AttributeType::Custom => {
return Err(Cw721ContractError::UnsupportedCustomAttributeType {
key: self.key.clone(),
value: self.value.clone(),
});
}
};
let attribute = Attribute {
key: self.key.clone(),
value,
};
Ok(attribute)
}
}
#[cw_serde]
pub enum AttributeType {
String,
U64,
Boolean,
Timestamp,
Addr,
Decimal,
Custom,
}
#[cw_serde]
pub struct CollectionExtensionMsg<TRoyaltyInfoResponse> {
pub description: Option<String>,
pub image: Option<String>,
pub external_link: Option<String>,
pub banner_url: Option<String>,
pub explicit_content: Option<bool>,
pub start_trading_time: Option<Timestamp>,
pub royalty_info: Option<TRoyaltyInfoResponse>,
}
impl<TRoyaltyInfoResponse> Cw721CustomMsg for CollectionExtensionMsg<TRoyaltyInfoResponse> where
TRoyaltyInfoResponse: Cw721CustomMsg
{
}
impl StateFactory<CollectionExtension<RoyaltyInfo>>
for CollectionExtensionMsg<RoyaltyInfoResponse>
{
fn create(
&self,
deps: Deps,
env: &Env,
info: Option<&MessageInfo>,
current: Option<&CollectionExtension<RoyaltyInfo>>,
) -> Result<CollectionExtension<RoyaltyInfo>, Cw721ContractError> {
self.validate(deps, env, info, current)?;
match current {
Some(current) => {
let mut updated = current.clone();
if let Some(description) = &self.description {
updated.description.clone_from(description);
}
if let Some(image) = &self.image {
updated.image.clone_from(image)
}
if let Some(external_link) = &self.external_link {
updated.external_link = Some(external_link.clone());
}
if let Some(banner_url) = &self.banner_url {
updated.banner_url = Some(banner_url.clone());
}
if let Some(explicit_content) = self.explicit_content {
updated.explicit_content = Some(explicit_content);
}
if let Some(start_trading_time) = self.start_trading_time {
updated.start_trading_time = Some(start_trading_time);
}
if let Some(royalty_info_response) = &self.royalty_info {
match current.royalty_info.clone() {
Some(current_royalty_info) => {
updated.royalty_info = Some(royalty_info_response.create(
deps,
env,
info,
Some(¤t_royalty_info),
)?);
}
None => {
updated.royalty_info =
Some(royalty_info_response.create(deps, env, info, None)?);
}
}
}
Ok(updated)
}
None => {
let royalty_info = match &self.royalty_info {
Some(royalty_info) => Some(royalty_info.create(deps, env, info, None)?),
None => None,
};
let new = CollectionExtension {
description: self.description.clone().unwrap_or_default(),
image: self.image.clone().unwrap_or_default(),
external_link: self.external_link.clone(),
banner_url: self.banner_url.clone(),
explicit_content: self.explicit_content,
start_trading_time: self.start_trading_time,
royalty_info,
};
Ok(new)
}
}
}
fn validate(
&self,
deps: Deps,
_env: &Env,
info: Option<&MessageInfo>,
_current: Option<&CollectionExtension<RoyaltyInfo>>,
) -> Result<(), Cw721ContractError> {
let sender = info.map(|i| &i.sender);
let minter_initialized = MINTER.item.may_load(deps.storage)?;
if self.start_trading_time.is_some()
&& minter_initialized.is_some()
&& sender.is_some()
&& MINTER.assert_owner(deps.storage, sender.unwrap()).is_err()
&& MINTER.item.exists(deps.storage)
{
return Err(Cw721ContractError::NotMinter {});
}
let creator_initialized = CREATOR.item.may_load(deps.storage)?;
if (self.description.is_some()
|| self.image.is_some()
|| self.external_link.is_some()
|| self.banner_url.is_some()
|| self.explicit_content.is_some()
|| self.royalty_info.is_some())
&& sender.is_some()
&& creator_initialized.is_some()
&& CREATOR.assert_owner(deps.storage, sender.unwrap()).is_err()
{
return Err(Cw721ContractError::NotCreator {});
}
if let Some(description) = &self.description {
if description.is_empty() {
return Err(Cw721ContractError::CollectionDescriptionEmpty {});
}
if description.len() > MAX_COLLECTION_DESCRIPTION_LENGTH as usize {
return Err(Cw721ContractError::CollectionDescriptionTooLong {
max_length: MAX_COLLECTION_DESCRIPTION_LENGTH,
});
}
}
if let Some(image) = &self.image {
Url::parse(image)?;
}
if let Some(external_link) = &self.external_link {
Url::parse(external_link)?;
}
if let Some(banner_url) = &self.banner_url {
Url::parse(banner_url)?;
}
Ok(())
}
}
#[cw_serde]
pub struct RoyaltyInfoResponse {
pub payment_address: String,
pub share: Decimal,
}
impl Cw721CustomMsg for RoyaltyInfoResponse {}
impl StateFactory<RoyaltyInfo> for RoyaltyInfoResponse {
fn create(
&self,
deps: Deps,
env: &Env,
info: Option<&MessageInfo>,
current: Option<&RoyaltyInfo>,
) -> Result<RoyaltyInfo, Cw721ContractError> {
self.validate(deps, env, info, current)?;
match current {
Some(current) => {
let mut updated = current.clone();
updated.payment_address = Addr::unchecked(self.payment_address.as_str()); updated.share = self.share;
Ok(updated)
}
None => {
let new = RoyaltyInfo {
payment_address: Addr::unchecked(self.payment_address.as_str()), share: self.share,
};
Ok(new)
}
}
}
fn validate(
&self,
deps: Deps,
_env: &Env,
_info: Option<&MessageInfo>,
current: Option<&RoyaltyInfo>,
) -> Result<(), Cw721ContractError> {
if let Some(current_royalty_info) = current {
if current_royalty_info.share < self.share {
let share_delta = self.share.abs_diff(current_royalty_info.share);
if share_delta > Decimal::percent(MAX_ROYALTY_SHARE_DELTA_PCT) {
return Err(Cw721ContractError::InvalidRoyalties(format!(
"Share increase cannot be greater than {MAX_ROYALTY_SHARE_DELTA_PCT}%"
)));
}
}
}
if self.share > Decimal::percent(MAX_ROYALTY_SHARE_PCT) {
return Err(Cw721ContractError::InvalidRoyalties(format!(
"Share cannot be greater than {MAX_ROYALTY_SHARE_PCT}%"
)));
}
deps.api.addr_validate(self.payment_address.as_str())?;
Ok(())
}
}
impl From<RoyaltyInfo> for RoyaltyInfoResponse {
fn from(royalty_info: RoyaltyInfo) -> Self {
Self {
payment_address: royalty_info.payment_address.to_string(),
share: royalty_info.share,
}
}
}
#[cw_serde]
pub struct ConfigResponse<TCollectionExtension> {
pub num_tokens: u64,
pub minter_ownership: Ownership<Addr>,
pub creator_ownership: Ownership<Addr>,
pub withdraw_address: Option<String>,
pub collection_info: CollectionInfo,
pub collection_extension: TCollectionExtension,
pub contract_info: ContractInfoResponse,
}
#[cw_serde]
pub struct CollectionInfoAndExtensionResponse<TCollectionExtension> {
pub name: String,
pub symbol: String,
pub extension: TCollectionExtension,
pub updated_at: Timestamp,
}
#[cw_serde]
pub struct AllInfoResponse {
pub contract_info: ContractInfoResponse,
pub collection_info: CollectionInfo,
pub collection_extension: CollectionExtensionAttributes,
pub num_tokens: u64,
}
impl<T> From<CollectionInfoAndExtensionResponse<T>> for CollectionInfo {
fn from(response: CollectionInfoAndExtensionResponse<T>) -> Self {
CollectionInfo {
name: response.name,
symbol: response.symbol,
updated_at: response.updated_at,
}
}
}
impl<TCollectionExtension, TCollectionExtensionMsg>
StateFactory<CollectionInfoAndExtensionResponse<TCollectionExtension>>
for CollectionInfoMsg<TCollectionExtensionMsg>
where
TCollectionExtension: Cw721State,
TCollectionExtensionMsg: Cw721CustomMsg + StateFactory<TCollectionExtension>,
{
fn create(
&self,
deps: Deps,
env: &Env,
info: Option<&MessageInfo>,
current: Option<&CollectionInfoAndExtensionResponse<TCollectionExtension>>,
) -> Result<CollectionInfoAndExtensionResponse<TCollectionExtension>, Cw721ContractError> {
self.validate(deps, env, info, current)?;
match current {
Some(current) => {
let mut updated = current.clone();
if let Some(name) = &self.name {
updated.name.clone_from(name);
}
if let Some(symbol) = &self.symbol {
updated.symbol.clone_from(symbol);
}
let current_extension = current.extension.clone();
let updated_extension =
self.extension
.create(deps, env, info, Some(¤t_extension))?;
updated.extension = updated_extension;
Ok(updated)
}
None => {
let extension = self.extension.create(deps, env, info, None)?;
let new = CollectionInfoAndExtensionResponse {
name: self.name.clone().unwrap(),
symbol: self.symbol.clone().unwrap(),
extension,
updated_at: env.block.time,
};
Ok(new)
}
}
}
fn validate(
&self,
deps: Deps,
_env: &Env,
info: Option<&MessageInfo>,
_current: Option<&CollectionInfoAndExtensionResponse<TCollectionExtension>>,
) -> Result<(), Cw721ContractError> {
if self.name.is_some() && self.name.clone().unwrap().is_empty() {
return Err(Cw721ContractError::CollectionNameEmpty {});
}
if self.symbol.is_some() && self.symbol.clone().unwrap().is_empty() {
return Err(Cw721ContractError::CollectionSymbolEmpty {});
}
let creator_initialized = CREATOR.item.may_load(deps.storage)?;
if (self.name.is_some() || self.symbol.is_some())
&& creator_initialized.is_some()
&& info.is_some()
&& CREATOR
.assert_owner(deps.storage, &info.unwrap().sender)
.is_err()
{
return Err(Cw721ContractError::NotCreator {});
}
Ok(())
}
}
impl<TRoyaltyInfo> ToAttributesState for CollectionExtension<TRoyaltyInfo>
where
TRoyaltyInfo: Serialize,
{
fn to_attributes_state(&self) -> Result<Vec<Attribute>, Cw721ContractError> {
let attributes = vec![
Attribute {
key: ATTRIBUTE_DESCRIPTION.to_string(),
value: to_json_binary(&self.description)?,
},
Attribute {
key: ATTRIBUTE_IMAGE.to_string(),
value: to_json_binary(&self.image)?,
},
Attribute {
key: ATTRIBUTE_EXTERNAL_LINK.to_string(),
value: to_json_binary(&self.external_link.clone())?,
},
Attribute {
key: ATTRIBUTE_BANNER_URL.to_string(),
value: to_json_binary(&self.banner_url.clone())?,
},
Attribute {
key: ATTRIBUTE_EXPLICIT_CONTENT.to_string(),
value: to_json_binary(&self.explicit_content)?,
},
Attribute {
key: ATTRIBUTE_START_TRADING_TIME.to_string(),
value: to_json_binary(&self.start_trading_time)?,
},
Attribute {
key: ATTRIBUTE_ROYALTY_INFO.to_string(),
value: to_json_binary(&self.royalty_info)?,
},
];
Ok(attributes)
}
}
impl<TRoyaltyInfo> FromAttributesState for CollectionExtension<TRoyaltyInfo>
where
TRoyaltyInfo: ToAttributesState + FromAttributesState,
{
fn from_attributes_state(attributes: &[Attribute]) -> Result<Self, Cw721ContractError> {
let description = attributes
.iter()
.find(|attr| attr.key == ATTRIBUTE_DESCRIPTION)
.ok_or_else(|| Cw721ContractError::AttributeMissing("description".to_string()))?
.value::<String>()?;
let image = attributes
.iter()
.find(|attr| attr.key == ATTRIBUTE_IMAGE)
.ok_or_else(|| Cw721ContractError::AttributeMissing("image".to_string()))?
.value::<String>()?;
let external_link = attributes
.iter()
.find(|attr| attr.key == ATTRIBUTE_EXTERNAL_LINK)
.ok_or_else(|| Cw721ContractError::AttributeMissing("external link".to_string()))?
.value::<Option<String>>()?;
let banner_url = attributes
.iter()
.find(|attr| attr.key == ATTRIBUTE_BANNER_URL)
.and_then(|attr| attr.value::<Option<String>>().ok())
.unwrap_or(None);
let explicit_content = attributes
.iter()
.find(|attr| attr.key == ATTRIBUTE_EXPLICIT_CONTENT)
.ok_or_else(|| Cw721ContractError::AttributeMissing("explicit content".to_string()))?
.value::<Option<bool>>()?;
let start_trading_time = attributes
.iter()
.find(|attr| attr.key == ATTRIBUTE_START_TRADING_TIME)
.ok_or_else(|| Cw721ContractError::AttributeMissing("start trading time".to_string()))?
.value::<Option<Timestamp>>()?;
let royalty_info = attributes
.iter()
.find(|attr| attr.key == ATTRIBUTE_ROYALTY_INFO)
.ok_or_else(|| Cw721ContractError::AttributeMissing("royalty info".to_string()))?
.value::<Option<RoyaltyInfo>>()?;
let royalty_info = if royalty_info.is_some() {
Some(FromAttributesState::from_attributes_state(attributes)?)
} else {
None
};
Ok(CollectionExtension {
description,
image,
external_link,
banner_url,
explicit_content,
start_trading_time,
royalty_info,
})
}
}
#[cw_serde]
pub struct OwnerOfResponse {
pub owner: String,
pub approvals: Vec<Approval>,
}
#[cw_serde]
pub struct ApprovalResponse {
pub approval: Approval,
}
#[cw_serde]
pub struct ApprovalsResponse {
pub approvals: Vec<Approval>,
}
#[cw_serde]
pub struct OperatorResponse {
pub approval: Approval,
}
#[cw_serde]
pub struct OperatorsResponse {
pub operators: Vec<Approval>,
}
#[cw_serde]
pub struct NumTokensResponse {
pub count: u64,
}
#[cw_serde]
pub struct NftInfoResponse<TNftExtension> {
pub token_uri: Option<String>,
pub extension: TNftExtension,
}
#[cw_serde]
pub struct AllNftInfoResponse<TNftExtension> {
pub access: OwnerOfResponse,
pub info: NftInfoResponse<TNftExtension>,
}
#[cw_serde]
pub struct TokensResponse {
pub tokens: Vec<String>,
}
#[cw_serde]
pub struct MinterResponse {
pub minter: Option<String>,
}
#[cw_serde]
pub struct AdditionalMintersResponse {
pub minters: Vec<String>,
}
#[cw_serde]
pub struct NftInfoMsg<TNftExtensionMsg> {
pub owner: String,
pub approvals: Vec<Approval>,
pub token_uri: Option<String>,
pub extension: TNftExtensionMsg,
}
impl<TNftExtension, TNftExtensionMsg> StateFactory<NftInfo<TNftExtension>>
for NftInfoMsg<TNftExtensionMsg>
where
TNftExtension: Cw721State,
TNftExtensionMsg: Cw721CustomMsg + StateFactory<TNftExtension>,
{
fn create(
&self,
deps: Deps,
env: &Env,
info: Option<&MessageInfo>,
optional_current: Option<&NftInfo<TNftExtension>>,
) -> Result<NftInfo<TNftExtension>, Cw721ContractError> {
self.validate(deps, env, info, optional_current)?;
match optional_current {
Some(current) => {
let mut updated = current.clone();
if self.token_uri.is_some() {
updated.token_uri = empty_as_none(self.token_uri.clone());
}
let current_extension = optional_current.map(|c| &c.extension);
updated.extension = self.extension.create(deps, env, info, current_extension)?;
Ok(updated)
}
None => {
let extension = self.extension.create(deps, env, info, None)?;
let token_uri = empty_as_none(self.token_uri.clone());
Ok(NftInfo {
owner: Addr::unchecked(&self.owner), approvals: vec![],
token_uri,
extension,
})
}
}
}
fn validate(
&self,
deps: Deps,
_env: &Env,
info: Option<&MessageInfo>,
current: Option<&NftInfo<TNftExtension>>,
) -> Result<(), Cw721ContractError> {
let info = info.ok_or(Cw721ContractError::NoInfo)?;
if current.is_none() {
assert_minter(deps.storage, &info.sender)?;
} else {
assert_creator(deps.storage, &info.sender)?;
}
let token_uri = empty_as_none(self.token_uri.clone());
if let Some(token_uri) = token_uri {
Url::parse(token_uri.as_str())?;
}
deps.api.addr_validate(&self.owner)?;
Ok(())
}
}
#[cw_serde]
#[derive(Default)]
pub struct NftExtensionMsg {
pub image: Option<String>,
pub image_data: Option<String>,
pub external_url: Option<String>,
pub description: Option<String>,
pub name: Option<String>,
pub attributes: Option<Vec<Trait>>,
pub background_color: Option<String>,
pub animation_url: Option<String>,
pub youtube_url: Option<String>,
}
impl Cw721CustomMsg for NftExtensionMsg {}
impl From<NftExtension> for NftExtensionMsg {
fn from(extension: NftExtension) -> Self {
NftExtensionMsg {
image: extension.image,
image_data: extension.image_data,
external_url: extension.external_url,
description: extension.description,
name: extension.name,
attributes: extension.attributes,
background_color: extension.background_color,
animation_url: extension.animation_url,
youtube_url: extension.youtube_url,
}
}
}
impl StateFactory<NftExtension> for NftExtensionMsg {
fn create(
&self,
deps: Deps,
env: &Env,
info: Option<&MessageInfo>,
current: Option<&NftExtension>,
) -> Result<NftExtension, Cw721ContractError> {
self.validate(deps, env, info, current)?;
match current {
Some(current) => {
let mut updated = current.clone();
if self.image.is_some() {
updated.image = empty_as_none(self.image.clone());
}
if self.image_data.is_some() {
updated.image_data = empty_as_none(self.image_data.clone());
}
if self.external_url.is_some() {
updated.external_url = empty_as_none(self.external_url.clone());
}
if self.description.is_some() {
updated.description = empty_as_none(self.description.clone());
}
if self.name.is_some() {
updated.name = empty_as_none(self.name.clone());
}
if self.attributes.is_some() {
updated.attributes = match self.attributes.clone() {
Some(attributes) => Some(attributes.create(deps, env, info, None)?),
None => None,
};
}
if self.background_color.is_some() {
updated.background_color = empty_as_none(self.background_color.clone())
}
if self.animation_url.is_some() {
updated.animation_url = empty_as_none(self.animation_url.clone());
}
if self.youtube_url.is_some() {
updated.youtube_url = empty_as_none(self.youtube_url.clone());
}
Ok(updated)
}
None => {
let mut new_metadata: NftExtension = self.clone().into();
if self.attributes.is_some() {
new_metadata.attributes = match self.attributes.clone() {
Some(attributes) => Some(attributes.create(deps, env, info, None)?),
None => None,
};
}
Ok(new_metadata)
}
}
}
fn validate(
&self,
deps: Deps,
_env: &Env,
info: Option<&MessageInfo>,
current: Option<&NftExtension>,
) -> Result<(), Cw721ContractError> {
if current.is_none() {
let info = info.ok_or(Cw721ContractError::NoInfo)?;
let minter_check = assert_minter(deps.storage, &info.sender);
let creator_check = assert_creator(deps.storage, &info.sender);
if minter_check.is_err() && creator_check.is_err() {
return Err(Cw721ContractError::NotMinterOrCreator {});
}
} else {
let info = info.ok_or(Cw721ContractError::NoInfo)?;
assert_creator(deps.storage, &info.sender)?;
}
let image = empty_as_none(self.image.clone());
if let Some(image) = &image {
Url::parse(image)?;
}
let external_url = empty_as_none(self.external_url.clone());
if let Some(url) = &external_url {
Url::parse(url)?;
}
let animation_url = empty_as_none(self.animation_url.clone());
if let Some(animation_url) = &animation_url {
Url::parse(animation_url)?;
}
let youtube_url = empty_as_none(self.youtube_url.clone());
if let Some(youtube_url) = &youtube_url {
Url::parse(youtube_url)?;
}
Ok(())
}
}
pub fn empty_as_none(value: Option<String>) -> Option<String> {
value.filter(|v| !v.is_empty())
}
impl<TMsg, TState> StateFactory<Option<TState>> for Option<TMsg>
where
TState: Cw721State,
TMsg: Cw721CustomMsg + StateFactory<TState>,
{
fn create(
&self,
deps: Deps,
env: &Env,
info: Option<&MessageInfo>,
current: Option<&Option<TState>>,
) -> Result<Option<TState>, Cw721ContractError> {
if self.is_none() {
return Ok(None);
}
let msg = self.clone().unwrap();
let current = current.and_then(|c| c.as_ref());
let created_or_updated = msg.create(deps, env, info, current)?;
Ok(Some(created_or_updated))
}
fn validate(
&self,
deps: Deps,
env: &Env,
info: Option<&MessageInfo>,
current: Option<&Option<TState>>,
) -> Result<(), Cw721ContractError> {
if self.is_none() {
return Ok(());
}
let msg = self.clone().unwrap();
let current = current.and_then(|c| c.as_ref());
msg.validate(deps, env, info, current)
}
}