From c8f673157d325da20085482b43c8985d77fed140 Mon Sep 17 00:00:00 2001 From: Ethan Frey <ethanfrey@users.noreply.github.com> Date: Thu, 23 Sep 2021 10:07:05 +0200 Subject: [PATCH 1/6] Start cw721-metadata-uri example --- Cargo.lock | 16 ++++++ contracts/cw721-base/src/msg.rs | 5 +- contracts/cw721-metadata-uri/.cargo/config | 5 ++ contracts/cw721-metadata-uri/Cargo.toml | 39 ++++++++++++++ contracts/cw721-metadata-uri/NOTICE | 14 +++++ .../cw721-metadata-uri/examples/schema.rs | 38 ++++++++++++++ contracts/cw721-metadata-uri/src/lib.rs | 52 +++++++++++++++++++ 7 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 contracts/cw721-metadata-uri/.cargo/config create mode 100644 contracts/cw721-metadata-uri/Cargo.toml create mode 100644 contracts/cw721-metadata-uri/NOTICE create mode 100644 contracts/cw721-metadata-uri/examples/schema.rs create mode 100644 contracts/cw721-metadata-uri/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 285ea43c0..191cfb3a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -656,6 +656,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw721-metadata-uri" +version = "0.9.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.9.0", + "cw0 0.9.0", + "cw2", + "cw721", + "cw721-base", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "der" version = "0.4.1" diff --git a/contracts/cw721-base/src/msg.rs b/contracts/cw721-base/src/msg.rs index a2c5d0aab..750fff6c1 100644 --- a/contracts/cw721-base/src/msg.rs +++ b/contracts/cw721-base/src/msg.rs @@ -1,8 +1,9 @@ -use cosmwasm_std::Binary; -use cw721::Expiration; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use cosmwasm_std::Binary; +use cw721::Expiration; + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InstantiateMsg { /// Name of the NFT contract diff --git a/contracts/cw721-metadata-uri/.cargo/config b/contracts/cw721-metadata-uri/.cargo/config new file mode 100644 index 000000000..7d1a066c8 --- /dev/null +++ b/contracts/cw721-metadata-uri/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +wasm-debug = "build --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/contracts/cw721-metadata-uri/Cargo.toml b/contracts/cw721-metadata-uri/Cargo.toml new file mode 100644 index 000000000..31fa179c3 --- /dev/null +++ b/contracts/cw721-metadata-uri/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "cw721-metadata-uri" +version = "0.9.0" +authors = ["Ethan Frey <ethanfrey@users.noreply.github.com>"] +edition = "2018" +description = "Example extending CW721 NFT with ERC20 like metadata_uri" +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/cw-plus" +homepage = "https://cosmwasm.com" +documentation = "https://docs.cosmwasm.com" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "artifacts/*", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cw0 = { path = "../../packages/cw0", version = "0.9.0" } +cw2 = { path = "../../packages/cw2", version = "0.9.0" } +cw721 = { path = "../../packages/cw721", version = "0.9.0" } +cw721-base = { path = "../cw721-base", version = "0.9.0", features = ["library"] } +cw-storage-plus = { path = "../../packages/storage-plus", version = "0.9.0" } +cosmwasm-std = { version = "0.16.0" } +schemars = "0.8.1" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.23" } + +[dev-dependencies] +cosmwasm-schema = { version = "0.16.0" } diff --git a/contracts/cw721-metadata-uri/NOTICE b/contracts/cw721-metadata-uri/NOTICE new file mode 100644 index 000000000..c03203270 --- /dev/null +++ b/contracts/cw721-metadata-uri/NOTICE @@ -0,0 +1,14 @@ +Cw721_base +Copyright (C) 2020 Confio OÜ + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/contracts/cw721-metadata-uri/examples/schema.rs b/contracts/cw721-metadata-uri/examples/schema.rs new file mode 100644 index 000000000..f9c424023 --- /dev/null +++ b/contracts/cw721-metadata-uri/examples/schema.rs @@ -0,0 +1,38 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; + +use cw721::{ + AllNftInfoResponse, ApprovedForAllResponse, ContractInfoResponse, NftInfoResponse, + NumTokensResponse, OwnerOfResponse, TokensResponse, +}; +use cw721_base::entry::Extension; +use cw721_base::msg::{ExecuteMsg, InstantiateMsg, MinterResponse, QueryMsg}; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema_with_title(&schema_for!(ExecuteMsg<Extension>), &out_dir, "ExecuteMsg"); + export_schema(&schema_for!(QueryMsg), &out_dir); + export_schema_with_title( + &schema_for!(AllNftInfoResponse<Extension>), + &out_dir, + "AllNftInfoResponse", + ); + export_schema(&schema_for!(ApprovedForAllResponse), &out_dir); + export_schema(&schema_for!(ContractInfoResponse), &out_dir); + export_schema(&schema_for!(MinterResponse), &out_dir); + export_schema_with_title( + &schema_for!(NftInfoResponse<Extension>), + &out_dir, + "NftInfoResponse", + ); + export_schema(&schema_for!(NumTokensResponse), &out_dir); + export_schema(&schema_for!(OwnerOfResponse), &out_dir); + export_schema(&schema_for!(TokensResponse), &out_dir); +} diff --git a/contracts/cw721-metadata-uri/src/lib.rs b/contracts/cw721-metadata-uri/src/lib.rs new file mode 100644 index 000000000..aec41f9da --- /dev/null +++ b/contracts/cw721-metadata-uri/src/lib.rs @@ -0,0 +1,52 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +pub use cw721_base::{ + ContractError, ExecuteMsg, InstantiateMsg, MintMsg, MinterResponse, QueryMsg, +}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Extension { + pub metadata_uri: String, +} + +#[cfg(not(feature = "library"))] +pub mod entry { + use super::*; + + pub use cw721_base::Cw721Contract; + + use cosmwasm_std::entry_point; + use cosmwasm_std::{Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult}; + + // This is a simple type to let us handle empty extensions + + // This makes a conscious choice on the various generics used by the contract + #[entry_point] + pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, + ) -> StdResult<Response> { + let tract = Cw721Contract::<Extension, Empty>::default(); + tract.instantiate(deps, env, info, msg) + } + + #[entry_point] + pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg<Extension>, + ) -> Result<Response, ContractError> { + let tract = Cw721Contract::<Extension, Empty>::default(); + tract.execute(deps, env, info, msg) + } + + #[entry_point] + pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> { + let tract = Cw721Contract::<Extension, Empty>::default(); + tract.query(deps, env, msg) + } +} From 07b904f3b3bc9b0e09243dada0d66a43c916ff1e Mon Sep 17 00:00:00 2001 From: Ethan Frey <ethanfrey@users.noreply.github.com> Date: Thu, 23 Sep 2021 10:19:21 +0200 Subject: [PATCH 2/6] Cleanup and generate schema --- contracts/cw721-base/src/lib.rs | 20 +- .../cw721-metadata-uri/examples/schema.rs | 5 +- .../schema/all_nft_info_response.json | 172 ++++++++++ .../schema/approved_for_all_response.json | 97 ++++++ .../schema/contract_info_response.json | 17 + .../schema/execute_msg.json | 306 ++++++++++++++++++ .../schema/instantiate_msg.json | 24 ++ .../schema/minter_response.json | 14 + .../schema/nft_info_response.json | 48 +++ .../schema/num_tokens_response.json | 15 + .../schema/owner_of_response.json | 103 ++++++ .../cw721-metadata-uri/schema/query_msg.json | 227 +++++++++++++ .../schema/tokens_response.json | 17 + contracts/cw721-metadata-uri/src/lib.rs | 23 +- 14 files changed, 1062 insertions(+), 26 deletions(-) create mode 100644 contracts/cw721-metadata-uri/schema/all_nft_info_response.json create mode 100644 contracts/cw721-metadata-uri/schema/approved_for_all_response.json create mode 100644 contracts/cw721-metadata-uri/schema/contract_info_response.json create mode 100644 contracts/cw721-metadata-uri/schema/execute_msg.json create mode 100644 contracts/cw721-metadata-uri/schema/instantiate_msg.json create mode 100644 contracts/cw721-metadata-uri/schema/minter_response.json create mode 100644 contracts/cw721-metadata-uri/schema/nft_info_response.json create mode 100644 contracts/cw721-metadata-uri/schema/num_tokens_response.json create mode 100644 contracts/cw721-metadata-uri/schema/owner_of_response.json create mode 100644 contracts/cw721-metadata-uri/schema/query_msg.json create mode 100644 contracts/cw721-metadata-uri/schema/tokens_response.json diff --git a/contracts/cw721-base/src/lib.rs b/contracts/cw721-base/src/lib.rs index e5a6e35ce..9d9a4ab8e 100644 --- a/contracts/cw721-base/src/lib.rs +++ b/contracts/cw721-base/src/lib.rs @@ -9,10 +9,10 @@ pub use crate::error::ContractError; pub use crate::msg::{ExecuteMsg, InstantiateMsg, MintMsg, MinterResponse, QueryMsg}; pub use crate::state::Cw721Contract; +#[cfg(not(feature = "library"))] pub mod entry { use super::*; - #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult}; @@ -20,31 +20,31 @@ pub mod entry { pub type Extension = Option<Empty>; // This makes a conscious choice on the various generics used by the contract - #[cfg_attr(not(feature = "library"), entry_point)] + #[entry_point] pub fn instantiate( deps: DepsMut, env: Env, info: MessageInfo, - msg: msg::InstantiateMsg, + msg: InstantiateMsg, ) -> StdResult<Response> { - let tract = state::Cw721Contract::<Extension, Empty>::default(); + let tract = Cw721Contract::<Extension, Empty>::default(); tract.instantiate(deps, env, info, msg) } - #[cfg_attr(not(feature = "library"), entry_point)] + #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, - msg: msg::ExecuteMsg<Extension>, + msg: ExecuteMsg<Extension>, ) -> Result<Response, ContractError> { - let tract = state::Cw721Contract::<Extension, Empty>::default(); + let tract = Cw721Contract::<Extension, Empty>::default(); tract.execute(deps, env, info, msg) } - #[cfg_attr(not(feature = "library"), entry_point)] - pub fn query(deps: Deps, env: Env, msg: msg::QueryMsg) -> StdResult<Binary> { - let tract = state::Cw721Contract::<Extension, Empty>::default(); + #[entry_point] + pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> { + let tract = Cw721Contract::<Extension, Empty>::default(); tract.query(deps, env, msg) } } diff --git a/contracts/cw721-metadata-uri/examples/schema.rs b/contracts/cw721-metadata-uri/examples/schema.rs index f9c424023..305267994 100644 --- a/contracts/cw721-metadata-uri/examples/schema.rs +++ b/contracts/cw721-metadata-uri/examples/schema.rs @@ -7,8 +7,7 @@ use cw721::{ AllNftInfoResponse, ApprovedForAllResponse, ContractInfoResponse, NftInfoResponse, NumTokensResponse, OwnerOfResponse, TokensResponse, }; -use cw721_base::entry::Extension; -use cw721_base::msg::{ExecuteMsg, InstantiateMsg, MinterResponse, QueryMsg}; +use cw721_metadata_uri::{ExecuteMsg, Extension, InstantiateMsg, MinterResponse, QueryMsg}; fn main() { let mut out_dir = current_dir().unwrap(); @@ -17,7 +16,7 @@ fn main() { remove_schemas(&out_dir).unwrap(); export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema_with_title(&schema_for!(ExecuteMsg<Extension>), &out_dir, "ExecuteMsg"); + export_schema_with_title(&schema_for!(ExecuteMsg), &out_dir, "ExecuteMsg"); export_schema(&schema_for!(QueryMsg), &out_dir); export_schema_with_title( &schema_for!(AllNftInfoResponse<Extension>), diff --git a/contracts/cw721-metadata-uri/schema/all_nft_info_response.json b/contracts/cw721-metadata-uri/schema/all_nft_info_response.json new file mode 100644 index 000000000..3fab93565 --- /dev/null +++ b/contracts/cw721-metadata-uri/schema/all_nft_info_response.json @@ -0,0 +1,172 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllNftInfoResponse", + "type": "object", + "required": [ + "access", + "info" + ], + "properties": { + "access": { + "description": "Who can transfer the token", + "allOf": [ + { + "$ref": "#/definitions/OwnerOfResponse" + } + ] + }, + "info": { + "description": "Data on the token itself,", + "allOf": [ + { + "$ref": "#/definitions/NftInfoResponse_for_Extension" + } + ] + } + }, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "anyOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Extension": { + "type": "object", + "required": [ + "metadata_uri" + ], + "properties": { + "metadata_uri": { + "type": "string" + } + } + }, + "NftInfoResponse_for_Extension": { + "type": "object", + "required": [ + "description", + "extension", + "name" + ], + "properties": { + "description": { + "description": "Describes the asset to which this NFT represents", + "type": "string" + }, + "extension": { + "description": "You can add any custom metadata here when you extend cw721-base", + "allOf": [ + { + "$ref": "#/definitions/Extension" + } + ] + }, + "image": { + "description": "\"A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive. TODO: Use https://docs.rs/url_serde for type-safety", + "type": [ + "string", + "null" + ] + }, + "name": { + "description": "Identifies the asset to which this NFT represents", + "type": "string" + } + } + }, + "OwnerOfResponse": { + "type": "object", + "required": [ + "approvals", + "owner" + ], + "properties": { + "approvals": { + "description": "If set this address is approved to transfer/send the token as well", + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + }, + "owner": { + "description": "Owner of the token", + "type": "string" + } + } + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw721-metadata-uri/schema/approved_for_all_response.json b/contracts/cw721-metadata-uri/schema/approved_for_all_response.json new file mode 100644 index 000000000..f405e3581 --- /dev/null +++ b/contracts/cw721-metadata-uri/schema/approved_for_all_response.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ApprovedForAllResponse", + "type": "object", + "required": [ + "operators" + ], + "properties": { + "operators": { + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + } + }, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "anyOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw721-metadata-uri/schema/contract_info_response.json b/contracts/cw721-metadata-uri/schema/contract_info_response.json new file mode 100644 index 000000000..a16712589 --- /dev/null +++ b/contracts/cw721-metadata-uri/schema/contract_info_response.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractInfoResponse", + "type": "object", + "required": [ + "name", + "symbol" + ], + "properties": { + "name": { + "type": "string" + }, + "symbol": { + "type": "string" + } + } +} diff --git a/contracts/cw721-metadata-uri/schema/execute_msg.json b/contracts/cw721-metadata-uri/schema/execute_msg.json new file mode 100644 index 000000000..68630375f --- /dev/null +++ b/contracts/cw721-metadata-uri/schema/execute_msg.json @@ -0,0 +1,306 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This is like Cw721ExecuteMsg but we add a Mint command for an owner to make this stand-alone. You will likely want to remove mint and use other control logic in any contract that inherits this.", + "anyOf": [ + { + "description": "Transfer is a base message to move a token to another account without triggering actions", + "type": "object", + "required": [ + "transfer_nft" + ], + "properties": { + "transfer_nft": { + "type": "object", + "required": [ + "recipient", + "token_id" + ], + "properties": { + "recipient": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Send is a base message to transfer a token to a contract and trigger an action on the receiving contract.", + "type": "object", + "required": [ + "send_nft" + ], + "properties": { + "send_nft": { + "type": "object", + "required": [ + "contract", + "msg", + "token_id" + ], + "properties": { + "contract": { + "type": "string" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Allows operator to transfer / send the token from the owner's account. If expiration is set, then this allowance has a time/height limit", + "type": "object", + "required": [ + "approve" + ], + "properties": { + "approve": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Remove previously granted Approval", + "type": "object", + "required": [ + "revoke" + ], + "properties": { + "revoke": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", + "type": "object", + "required": [ + "approve_all" + ], + "properties": { + "approve_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "operator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Remove previously granted ApproveAll permission", + "type": "object", + "required": [ + "revoke_all" + ], + "properties": { + "revoke_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "operator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Mint a new NFT, can only be called by the contract minter", + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "$ref": "#/definitions/MintMsg_for_Extension" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec<u8> to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec<u8>", + "type": "string" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "anyOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Extension": { + "type": "object", + "required": [ + "metadata_uri" + ], + "properties": { + "metadata_uri": { + "type": "string" + } + } + }, + "MintMsg_for_Extension": { + "type": "object", + "required": [ + "extension", + "name", + "owner", + "token_id" + ], + "properties": { + "description": { + "description": "Describes the asset to which this NFT represents (may be empty)", + "type": [ + "string", + "null" + ] + }, + "extension": { + "description": "Any custom extension used by this contract", + "allOf": [ + { + "$ref": "#/definitions/Extension" + } + ] + }, + "image": { + "description": "A URI pointing to an image representing the asset", + "type": [ + "string", + "null" + ] + }, + "name": { + "description": "Identifies the asset to which this NFT represents", + "type": "string" + }, + "owner": { + "description": "The owner of the newly minter NFT", + "type": "string" + }, + "token_id": { + "description": "Unique ID of the NFT", + "type": "string" + } + } + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw721-metadata-uri/schema/instantiate_msg.json b/contracts/cw721-metadata-uri/schema/instantiate_msg.json new file mode 100644 index 000000000..b024c82c1 --- /dev/null +++ b/contracts/cw721-metadata-uri/schema/instantiate_msg.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "minter", + "name", + "symbol" + ], + "properties": { + "minter": { + "description": "The minter is the only one who can create new NFTs. This is designed for a base NFT that is controlled by an external program or contract. You will likely replace this with custom logic in custom NFTs", + "type": "string" + }, + "name": { + "description": "Name of the NFT contract", + "type": "string" + }, + "symbol": { + "description": "Symbol of the NFT contract", + "type": "string" + } + } +} diff --git a/contracts/cw721-metadata-uri/schema/minter_response.json b/contracts/cw721-metadata-uri/schema/minter_response.json new file mode 100644 index 000000000..a20e0d767 --- /dev/null +++ b/contracts/cw721-metadata-uri/schema/minter_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MinterResponse", + "description": "Shows who can mint these tokens", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "string" + } + } +} diff --git a/contracts/cw721-metadata-uri/schema/nft_info_response.json b/contracts/cw721-metadata-uri/schema/nft_info_response.json new file mode 100644 index 000000000..bc8d6da58 --- /dev/null +++ b/contracts/cw721-metadata-uri/schema/nft_info_response.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NftInfoResponse", + "type": "object", + "required": [ + "description", + "extension", + "name" + ], + "properties": { + "description": { + "description": "Describes the asset to which this NFT represents", + "type": "string" + }, + "extension": { + "description": "You can add any custom metadata here when you extend cw721-base", + "allOf": [ + { + "$ref": "#/definitions/Extension" + } + ] + }, + "image": { + "description": "\"A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive. TODO: Use https://docs.rs/url_serde for type-safety", + "type": [ + "string", + "null" + ] + }, + "name": { + "description": "Identifies the asset to which this NFT represents", + "type": "string" + } + }, + "definitions": { + "Extension": { + "type": "object", + "required": [ + "metadata_uri" + ], + "properties": { + "metadata_uri": { + "type": "string" + } + } + } + } +} diff --git a/contracts/cw721-metadata-uri/schema/num_tokens_response.json b/contracts/cw721-metadata-uri/schema/num_tokens_response.json new file mode 100644 index 000000000..4647c23aa --- /dev/null +++ b/contracts/cw721-metadata-uri/schema/num_tokens_response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NumTokensResponse", + "type": "object", + "required": [ + "count" + ], + "properties": { + "count": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } +} diff --git a/contracts/cw721-metadata-uri/schema/owner_of_response.json b/contracts/cw721-metadata-uri/schema/owner_of_response.json new file mode 100644 index 000000000..2bd34b01b --- /dev/null +++ b/contracts/cw721-metadata-uri/schema/owner_of_response.json @@ -0,0 +1,103 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OwnerOfResponse", + "type": "object", + "required": [ + "approvals", + "owner" + ], + "properties": { + "approvals": { + "description": "If set this address is approved to transfer/send the token as well", + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + }, + "owner": { + "description": "Owner of the token", + "type": "string" + } + }, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "anyOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw721-metadata-uri/schema/query_msg.json b/contracts/cw721-metadata-uri/schema/query_msg.json new file mode 100644 index 000000000..471924bf0 --- /dev/null +++ b/contracts/cw721-metadata-uri/schema/query_msg.json @@ -0,0 +1,227 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "anyOf": [ + { + "description": "Return the owner of the given token, error if token does not exist Return type: OwnerOfResponse", + "type": "object", + "required": [ + "owner_of" + ], + "properties": { + "owner_of": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired approvals, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "List all operators that can access all of the owner's tokens Return type: `ApprovedForAllResponse`", + "type": "object", + "required": [ + "approved_for_all" + ], + "properties": { + "approved_for_all": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired items, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Total number of tokens issued", + "type": "object", + "required": [ + "num_tokens" + ], + "properties": { + "num_tokens": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Returns top-level metadata about the contract: `ContractInfoResponse`", + "type": "object", + "required": [ + "contract_info" + ], + "properties": { + "contract_info": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* but directly from the contract: `NftInfoResponse`", + "type": "object", + "required": [ + "nft_info" + ], + "properties": { + "nft_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization for clients: `AllNftInfo`", + "type": "object", + "required": [ + "all_nft_info" + ], + "properties": { + "all_nft_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired approvals, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset. Return type: TokensResponse.", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract. Return type: TokensResponse.", + "type": "object", + "required": [ + "all_tokens" + ], + "properties": { + "all_tokens": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/cw721-metadata-uri/schema/tokens_response.json b/contracts/cw721-metadata-uri/schema/tokens_response.json new file mode 100644 index 000000000..b8e3d75b5 --- /dev/null +++ b/contracts/cw721-metadata-uri/schema/tokens_response.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokensResponse", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_from` in future queries to achieve pagination.", + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/contracts/cw721-metadata-uri/src/lib.rs b/contracts/cw721-metadata-uri/src/lib.rs index aec41f9da..9111d4a20 100644 --- a/contracts/cw721-metadata-uri/src/lib.rs +++ b/contracts/cw721-metadata-uri/src/lib.rs @@ -1,23 +1,23 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -pub use cw721_base::{ - ContractError, ExecuteMsg, InstantiateMsg, MintMsg, MinterResponse, QueryMsg, -}; +use cosmwasm_std::Empty; +pub use cw721_base::{ContractError, InstantiateMsg, MintMsg, MinterResponse, QueryMsg}; +pub type ExecuteMsg = cw721_base::ExecuteMsg<Extension>; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Extension { pub metadata_uri: String, } +pub type Cw721MetadataContract<'a> = cw721_base::Cw721Contract<'a, Extension, Empty>; + #[cfg(not(feature = "library"))] pub mod entry { use super::*; - pub use cw721_base::Cw721Contract; - use cosmwasm_std::entry_point; - use cosmwasm_std::{Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult}; + use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; // This is a simple type to let us handle empty extensions @@ -29,8 +29,7 @@ pub mod entry { info: MessageInfo, msg: InstantiateMsg, ) -> StdResult<Response> { - let tract = Cw721Contract::<Extension, Empty>::default(); - tract.instantiate(deps, env, info, msg) + Cw721MetadataContract::default().instantiate(deps, env, info, msg) } #[entry_point] @@ -38,15 +37,13 @@ pub mod entry { deps: DepsMut, env: Env, info: MessageInfo, - msg: ExecuteMsg<Extension>, + msg: ExecuteMsg, ) -> Result<Response, ContractError> { - let tract = Cw721Contract::<Extension, Empty>::default(); - tract.execute(deps, env, info, msg) + Cw721MetadataContract::default().execute(deps, env, info, msg) } #[entry_point] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> { - let tract = Cw721Contract::<Extension, Empty>::default(); - tract.query(deps, env, msg) + Cw721MetadataContract::default().query(deps, env, msg) } } From 1e0d338e74ed39b63918bf158ad21a2422da5157 Mon Sep 17 00:00:00 2001 From: Ethan Frey <ethanfrey@users.noreply.github.com> Date: Thu, 23 Sep 2021 10:24:22 +0200 Subject: [PATCH 3/6] Add cw721-metadata-uri to CI --- .circleci/config.yml | 38 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 8 ++++++++ 2 files changed, 46 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 54e1c520f..9b631ef55 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,6 +17,7 @@ workflows: - contract_cw20_staking - contract_cw20_merkle_airdrop - contract_cw721_base + - contract_cw721_metadata_uri - contract_cw1155_base - package_controllers - package_cw0 @@ -548,6 +549,43 @@ jobs: - target key: cargocache-cw721-base-rust:1.53.0-{{ checksum "~/project/Cargo.lock" }} + + contract_cw721_metadata_uri: + docker: + - image: rust:1.53.0 + working_directory: ~/project/contracts/cw721-metadata-uri + steps: + - checkout: + path: ~/project + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - cargocache-cw721-metadata-uri-rust:1.53.0-{{ checksum "~/project/Cargo.lock" }} + - run: + name: Unit Tests + environment: + RUST_BACKTRACE: 1 + command: cargo unit-test --locked + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target + key: cargocache-cw721-metadata-uri-rust:1.53.0-{{ checksum "~/project/Cargo.lock" }} + contract_cw1155_base: docker: - image: rust:1.53.0 diff --git a/Cargo.toml b/Cargo.toml index 7990e6df8..5928d6e51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,10 @@ incremental = false codegen-units = 1 incremental = false +[profile.release.package.cw20-merkle-airdrop] +codegen-units = 1 +incremental = false + [profile.release.package.cw20-staking] codegen-units = 1 incremental = false @@ -53,6 +57,10 @@ incremental = false codegen-units = 1 incremental = false +[profile.release.package.cw721-metadata-uri] +codegen-units = 1 +incremental = false + [profile.release.package.cw1155-base] codegen-units = 1 incremental = false From 62a193e5b6c758f8ad1ed3a1142b54989e4414c4 Mon Sep 17 00:00:00 2001 From: Ethan Frey <ethanfrey@users.noreply.github.com> Date: Thu, 23 Sep 2021 10:41:01 +0200 Subject: [PATCH 4/6] Fix linting issue --- contracts/cw721-base/examples/schema.rs | 3 +-- contracts/cw721-base/src/contract_tests.rs | 5 +++-- contracts/cw721-base/src/lib.rs | 8 +++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/contracts/cw721-base/examples/schema.rs b/contracts/cw721-base/examples/schema.rs index f9c424023..a5ca2036e 100644 --- a/contracts/cw721-base/examples/schema.rs +++ b/contracts/cw721-base/examples/schema.rs @@ -7,8 +7,7 @@ use cw721::{ AllNftInfoResponse, ApprovedForAllResponse, ContractInfoResponse, NftInfoResponse, NumTokensResponse, OwnerOfResponse, TokensResponse, }; -use cw721_base::entry::Extension; -use cw721_base::msg::{ExecuteMsg, InstantiateMsg, MinterResponse, QueryMsg}; +use cw721_base::{Extension, ExecuteMsg, InstantiateMsg, MinterResponse, QueryMsg}; fn main() { let mut out_dir = current_dir().unwrap(); diff --git a/contracts/cw721-base/src/contract_tests.rs b/contracts/cw721-base/src/contract_tests.rs index 83630a2ff..5416b561a 100644 --- a/contracts/cw721-base/src/contract_tests.rs +++ b/contracts/cw721-base/src/contract_tests.rs @@ -7,8 +7,9 @@ use cw721::{ NftInfoResponse, OwnerOfResponse, }; -use crate::entry::Extension; -use crate::{ContractError, Cw721Contract, ExecuteMsg, InstantiateMsg, MintMsg, QueryMsg}; +use crate::{ + ContractError, Cw721Contract, ExecuteMsg, Extension, InstantiateMsg, MintMsg, QueryMsg, +}; const MINTER: &str = "merlin"; const CONTRACT_NAME: &str = "Magic Power"; diff --git a/contracts/cw721-base/src/lib.rs b/contracts/cw721-base/src/lib.rs index 9d9a4ab8e..cc92699e1 100644 --- a/contracts/cw721-base/src/lib.rs +++ b/contracts/cw721-base/src/lib.rs @@ -8,16 +8,18 @@ pub mod state; pub use crate::error::ContractError; pub use crate::msg::{ExecuteMsg, InstantiateMsg, MintMsg, MinterResponse, QueryMsg}; pub use crate::state::Cw721Contract; +use cosmwasm_std::Empty; + +// This is a simple type to let us handle empty extensions +pub type Extension = Option<Empty>; #[cfg(not(feature = "library"))] pub mod entry { use super::*; use cosmwasm_std::entry_point; - use cosmwasm_std::{Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult}; + use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; - // This is a simple type to let us handle empty extensions - pub type Extension = Option<Empty>; // This makes a conscious choice on the various generics used by the contract #[entry_point] From aead0d9ebe446031349e884d6c98e32b4f1cf7a8 Mon Sep 17 00:00:00 2001 From: Ethan Frey <ethanfrey@users.noreply.github.com> Date: Thu, 23 Sep 2021 10:52:29 +0200 Subject: [PATCH 5/6] Add test showing Metadata Extension working --- contracts/cw721-base/examples/schema.rs | 2 +- contracts/cw721-base/src/lib.rs | 1 - contracts/cw721-metadata-uri/src/lib.rs | 50 ++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/contracts/cw721-base/examples/schema.rs b/contracts/cw721-base/examples/schema.rs index a5ca2036e..1b7b115d0 100644 --- a/contracts/cw721-base/examples/schema.rs +++ b/contracts/cw721-base/examples/schema.rs @@ -7,7 +7,7 @@ use cw721::{ AllNftInfoResponse, ApprovedForAllResponse, ContractInfoResponse, NftInfoResponse, NumTokensResponse, OwnerOfResponse, TokensResponse, }; -use cw721_base::{Extension, ExecuteMsg, InstantiateMsg, MinterResponse, QueryMsg}; +use cw721_base::{ExecuteMsg, Extension, InstantiateMsg, MinterResponse, QueryMsg}; fn main() { let mut out_dir = current_dir().unwrap(); diff --git a/contracts/cw721-base/src/lib.rs b/contracts/cw721-base/src/lib.rs index cc92699e1..4d635f277 100644 --- a/contracts/cw721-base/src/lib.rs +++ b/contracts/cw721-base/src/lib.rs @@ -20,7 +20,6 @@ pub mod entry { use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; - // This makes a conscious choice on the various generics used by the contract #[entry_point] pub fn instantiate( diff --git a/contracts/cw721-metadata-uri/src/lib.rs b/contracts/cw721-metadata-uri/src/lib.rs index 9111d4a20..d987700c1 100644 --- a/contracts/cw721-metadata-uri/src/lib.rs +++ b/contracts/cw721-metadata-uri/src/lib.rs @@ -3,7 +3,6 @@ use serde::{Deserialize, Serialize}; use cosmwasm_std::Empty; pub use cw721_base::{ContractError, InstantiateMsg, MintMsg, MinterResponse, QueryMsg}; -pub type ExecuteMsg = cw721_base::ExecuteMsg<Extension>; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Extension { @@ -11,6 +10,7 @@ pub struct Extension { } pub type Cw721MetadataContract<'a> = cw721_base::Cw721Contract<'a, Extension, Empty>; +pub type ExecuteMsg = cw721_base::ExecuteMsg<Extension>; #[cfg(not(feature = "library"))] pub mod entry { @@ -47,3 +47,51 @@ pub mod entry { Cw721MetadataContract::default().query(deps, env, msg) } } + +#[cfg(test)] +mod tests { + use super::*; + + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cw721::Cw721Query; + + const CREATOR: &str = "creator"; + + #[test] + fn use_metadata_extension() { + let mut deps = mock_dependencies(&[]); + let contract = Cw721MetadataContract::default(); + + let info = mock_info(CREATOR, &[]); + let init_msg = InstantiateMsg { + name: "SpaceShips".to_string(), + symbol: "SPACE".to_string(), + minter: CREATOR.to_string(), + }; + contract + .instantiate(deps.as_mut(), mock_env(), info.clone(), init_msg) + .unwrap(); + + let token_id = "Enterprise"; + let mint_msg = MintMsg { + token_id: token_id.to_string(), + owner: "john".to_string(), + name: "Starship USS Enterprise".to_string(), + description: Some("Spaceship with Warp Drive".into()), + image: None, + extension: Extension { + metadata_uri: "http://starships.example.com/Starship/Enterprise.json".into(), + }, + }; + let exec_msg = ExecuteMsg::Mint(mint_msg.clone()); + contract + .execute(deps.as_mut(), mock_env(), info, exec_msg) + .unwrap(); + + let res = contract.nft_info(deps.as_ref(), token_id.into()).unwrap(); + assert_eq!(res.name, mint_msg.name); + assert_eq!(res.description, mint_msg.description.unwrap()); + assert_eq!(res.image, mint_msg.image); + assert_eq!(res.extension, mint_msg.extension); + } +} From 749dfc7623e61beb209853c3fb0ed459019e0e2a Mon Sep 17 00:00:00 2001 From: Ethan Frey <ethanfrey@users.noreply.github.com> Date: Thu, 23 Sep 2021 12:41:08 +0200 Subject: [PATCH 6/6] Add README to explain purpose/usage --- contracts/cw721-metadata-uri/NOTICE | 2 +- contracts/cw721-metadata-uri/README.md | 37 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 contracts/cw721-metadata-uri/README.md diff --git a/contracts/cw721-metadata-uri/NOTICE b/contracts/cw721-metadata-uri/NOTICE index c03203270..f0a440e12 100644 --- a/contracts/cw721-metadata-uri/NOTICE +++ b/contracts/cw721-metadata-uri/NOTICE @@ -1,4 +1,4 @@ -Cw721_base +Cw721_metadata_uri Copyright (C) 2020 Confio OÜ Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/contracts/cw721-metadata-uri/README.md b/contracts/cw721-metadata-uri/README.md new file mode 100644 index 000000000..9d6cb3e6d --- /dev/null +++ b/contracts/cw721-metadata-uri/README.md @@ -0,0 +1,37 @@ +# CW721 Metadata URI + +In Ethereum, the ERC721 standard includes a metadata_uri field to store all metadata offchain. +With CW721-Base in CosmWasm, we allow you to store any data on chain you wish, using a generic `extension: T`. + +In order to provide better "out of the box" compatibility for people migrating from the Ethereum ecosystem, +and to demonstrate how to use the extension ability, we have created this simple contract. There is no business +logic here, but looking at `lib.rs` will show you how do define custom data that is included when minting and +available in all queries. + +In particular, here we define: + +```rust +pub struct Extension { + pub metadata_uri: String, +} +``` + +This means when you query `NftInfo{name: "Enterprise"}`, you will get something like: + +```json +{ + "name": "Enterprise", + "description": "USS Starship Enterprise", + "image": null, + "extension": { + "metadata_uri": "http://starships.example.com/Starships/Enterprise.json" + } +} +``` + +Please look at the test code for an example usage in Rust. + +## Notice + +Feel free to use this contract out of the box, or as inspiration for further customization of cw721-base. +We will not be adding new features or business logic here.