diff --git a/Cargo.lock b/Cargo.lock
index 1191295..136b6a7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1021,6 +1021,7 @@ dependencies = [
"cw-hooks",
"dao-interface",
"dao-voting 2.4.0",
+ "query_auth 0.1.0",
"schemars 0.8.16",
"secret-cosmwasm-std",
"secret-cw2",
@@ -1066,10 +1067,13 @@ dependencies = [
"dao-pre-propose-base",
"dao-proposal-single",
"dao-voting 2.4.0",
+ "query_auth 0.1.0",
"secret-cosmwasm-std",
"secret-cw2",
"secret-multi-test",
"secret-utils 0.13.4",
+ "shade-protocol",
+ "snip20-reference-impl",
]
[[package]]
@@ -1154,7 +1158,6 @@ dependencies = [
"dao-hooks",
"dao-interface",
"dao-pre-propose-base",
- "dao-pre-propose-single",
"dao-testing",
"dao-voting 2.4.0",
"dao-voting-cw4",
@@ -1328,8 +1331,8 @@ dependencies = [
"serde",
"shade-protocol",
"snip721-controllers",
- "snip721-reference-impl",
"snip721-roles",
+ "snip721-roles-impl",
"thiserror",
]
@@ -2713,7 +2716,6 @@ dependencies = [
"base64 0.21.5",
"bincode2",
"cosmwasm-schema 1.5.0",
- "dao-snip721-extensions",
"primitive-types",
"schemars 0.8.16",
"secret-cosmwasm-std",
@@ -2741,10 +2743,27 @@ dependencies = [
"secret-utils 0.13.4",
"serde",
"shade-protocol",
- "snip721-reference-impl",
+ "snip721-roles-impl",
"thiserror",
]
+[[package]]
+name = "snip721-roles-impl"
+version = "1.0.0"
+dependencies = [
+ "base64 0.21.5",
+ "bincode2",
+ "cosmwasm-schema 1.1.11",
+ "dao-snip721-extensions",
+ "primitive-types",
+ "schemars 0.8.16",
+ "secret-cosmwasm-std",
+ "secret-cosmwasm-storage",
+ "secret-toolkit",
+ "serde",
+ "shade-protocol",
+]
+
[[package]]
name = "spki"
version = "0.6.0"
diff --git a/Cargo.toml b/Cargo.toml
index 947d4ae..5647795 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -144,6 +144,7 @@ serde = { version = "1.0.158", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.21" ,default-features = false}
cosmwasm-schema = { git = "https://github.com/scrtlabs/cosmwasm/", branch = "secret",default-features = false }
snip20-reference-impl = { path = "./contracts/external/snip20-reference-impl/",default-features = false }
+snip721-roles-impl = { path = "./contracts/external/snip721-roles-impl/" ,default-features = false}
snip721-reference-impl = { path = "./contracts/external/snip721-reference-impl/" ,default-features = false}
cosmos-sdk-proto = { version = "0.20.0", default-features = false }
shade-protocol = { git = "https://github.com/securesecrets/shade", rev = "lend-v1",features = [
diff --git a/contracts/external/snip721-reference-impl/.circleci/config.yml b/contracts/external/snip721-reference-impl/.circleci/config.yml
new file mode 100644
index 0000000..fc7fb91
--- /dev/null
+++ b/contracts/external/snip721-reference-impl/.circleci/config.yml
@@ -0,0 +1,52 @@
+version: 2.1
+
+jobs:
+ build:
+ docker:
+ - image: rust:1.65
+ steps:
+ - checkout
+ - run:
+ name: Version information
+ command: rustc --version; cargo --version; rustup --version
+ - restore_cache:
+ keys:
+ - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }}
+ - run:
+ name: Add wasm32 target
+ command: rustup target add wasm32-unknown-unknown
+ - run:
+ name: Build
+ command: cargo wasm --locked
+ - run:
+ name: Unit tests
+ env: RUST_BACKTRACE=1
+ command: cargo unit-test --locked
+ - run:
+ name: Integration tests
+ command: cargo integration-test --locked
+ - run:
+ name: Format source code
+ command: cargo fmt
+ - run:
+ name: Build and run schema generator
+ command: cargo schema --locked
+ - run:
+ name: Ensure checked-in source code and 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/debug/.fingerprint
+ - target/debug/build
+ - target/debug/deps
+ - target/wasm32-unknown-unknown/release/.fingerprint
+ - target/wasm32-unknown-unknown/release/build
+ - target/wasm32-unknown-unknown/release/deps
+ key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }}
diff --git a/contracts/external/snip721-reference-impl/.github/workflows/Basic.yml b/contracts/external/snip721-reference-impl/.github/workflows/Basic.yml
new file mode 100644
index 0000000..c95b5e0
--- /dev/null
+++ b/contracts/external/snip721-reference-impl/.github/workflows/Basic.yml
@@ -0,0 +1,81 @@
+# Based on https://github.com/actions-rs/example/blob/master/.github/workflows/quickstart.yml
+
+on: [push, pull_request]
+
+name: Basic
+
+jobs:
+
+ test:
+ name: Test Suite
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v2
+
+ - name: Install stable toolchain
+ uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: 1.65.0
+ target: wasm32-unknown-unknown
+ override: true
+
+ - name: Run unit tests
+ uses: actions-rs/cargo@v1
+ with:
+ command: unit-test
+ args: --locked
+ env:
+ RUST_BACKTRACE: 1
+
+ - name: Compile WASM contract
+ uses: actions-rs/cargo@v1
+ with:
+ command: wasm
+ args: --locked
+ env:
+ RUSTFLAGS: "-C link-arg=-s"
+
+ - name: Run integration tests
+ uses: actions-rs/cargo@v1
+ with:
+ command: integration-test
+ args: --locked
+
+
+ lints:
+ name: Lints
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v2
+
+ - name: Install stable toolchain
+ uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: 1.65.0
+ override: true
+ components: rustfmt, clippy
+
+ - name: Run cargo fmt
+ uses: actions-rs/cargo@v1
+ with:
+ command: fmt
+ args: --all -- --check
+
+ - name: Run cargo clippy
+ uses: actions-rs/cargo@v1
+ with:
+ command: clippy
+ args: -- -D warnings
+
+ # TODO: we should check
+ # CHANGES_IN_REPO=$(git status --porcelain)
+ # after this, but I don't know how
+ - name: Generate Schema
+ uses: actions-rs/cargo@v1
+ with:
+ command: schema
+ args: --locked
diff --git a/contracts/external/snip721-reference-impl/Cargo.toml b/contracts/external/snip721-reference-impl/Cargo.toml
index 1262898..2b5750c 100644
--- a/contracts/external/snip721-reference-impl/Cargo.toml
+++ b/contracts/external/snip721-reference-impl/Cargo.toml
@@ -21,15 +21,14 @@ crate-type = ["cdylib", "rlib"]
backtraces = ["cosmwasm-std/backtraces"]
[dependencies]
-cosmwasm-std = {workspace=true }
-secret-toolkit = {workspace=true }
-cosmwasm-storage = {workspace=true }
-schemars = {workspace=true }
-serde = {workspace=true }
+cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.1.11" }
+secret-toolkit = { version = "0.10.0", default-features = false, features = ["storage", "serialization", "utils", "permit", "viewing-key", "crypto"] }
+cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.1.10" }
+schemars = "0.8.12"
+serde = { version = "1.0.190", default-features = false, features = ["derive"] }
bincode2 = "2.0.1"
base64 = "0.21.2"
primitive-types = { version = "0.12.2", default-features = false }
-dao-snip721-extensions ={ workspace = true }
[dev-dependencies]
diff --git a/contracts/external/snip721-reference-impl/LICENSE.md b/contracts/external/snip721-reference-impl/LICENSE.md
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/contracts/external/snip721-reference-impl/LICENSE.md
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/contracts/external/snip721-reference-impl/NOTICE.md b/contracts/external/snip721-reference-impl/NOTICE.md
new file mode 100644
index 0000000..be103bb
--- /dev/null
+++ b/contracts/external/snip721-reference-impl/NOTICE.md
@@ -0,0 +1,10 @@
+Copyright 2020 Bill Wincer
+
+Licensed under the GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007;
+you may not use these files except in compliance with the [License](https://github.com/baedrik/snip721-reference-impl/blob/master/LICENSE.md).
+
+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/external/snip721-reference-impl/README.md b/contracts/external/snip721-reference-impl/README.md
new file mode 100755
index 0000000..55abaf5
--- /dev/null
+++ b/contracts/external/snip721-reference-impl/README.md
@@ -0,0 +1,2781 @@
+# SNIP-721 Reference Implementation
+***NOTE***
+I'm making the code available early for people who are interested, but I still need to write the specification for SNIP-723 (which includes some miscellaneous improvements like a BatchNftDossier query, a NumTokensOfOwner query to retrieve the count of tokens owned by one address and in which the querier has permission know the tokens' ownership, and adding a token's unwrapped status to the NftDossier response). I also still need to update this README with documentation of the additions.
+
+
+
+This is a reference implementation of the [SNIP-721 specification](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-721.md) and [SNIP-722 specification](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md). It not only implements the base requirements of SNIP-721 and SNIP-722, but it also includes additional functionality that may be helpful for many use cases. As SNIP-721 is a superset of the [CW-721 specification](https://github.com/CosmWasm/cw-nfts/tree/main/packages/cw721), this implementation is CW-721 compliant; however, because CW-721 does not support privacy, a number of the CW-721-compliant functions may not return all the information a CW-721 implementation would. For example, the [OwnerOf](#ownerof) query will not display the approvals for a token unless the token owner has supplied his address and viewing key. In order to strive for CW-721 compliance, a number of queries that require authentication use optional parameters that the CW-721 counterpart does not have. If the optional authentication parameters are not supplied, the responses will only display information that the token owner has made public.
+
+### Terms
+- __Message__ - This is an on-chain interface. It is triggered by sending a transaction, and receiving an on-chain response which is read by the client. Messages are authenticated both by the blockchain, and by the secret enclave.
+- __Query__ - This is an off-chain interface. Queries are done by returning data that a node has locally, and are not public. Query responses are returned immediately, and do not have to wait for blocks.
+- __Cosmos Message Sender__ - The account that is found under the `sender` field in a standard Cosmos SDK message. This is also the signer of the message.
+
+### Padding
+Users may want to enforce constant length messages to avoid leaking data. To support this functionality, every message includes an optional `padding` field. This optional `padding` field is ignored during message processing.
+
+### Requests
+Requests should be sent as base64 encoded JSON. Future versions of Secret Network may add support for other formats as well, but at this time we recommend usage of JSON only. For this reason the parameter descriptions specify the JSON type which must be used. In addition, request parameters will include in parentheses a CosmWasm (or other) underlying type that this value must conform to. E.g. a recipient address is sent as a string, but must also be parsed to a bech32 address.
+
+### Responses
+Message responses will be JSON encoded in the `data` field of the Cosmos response, rather than in the `logs`, except in the case of MintNft, BatchMintNft, and MintNftClones messages, where the token ID(s) will be returned in both the `data` and `logs` fields. This is because minting may frequently be done by a contract, and `data` fields of responses from callback messages do not get forwarded to the sender of the initial message.
+
+* [Instantiating The Token Contract](#Instantiating-The-Token-Contract)
+* Messages
+ * [MintNft](#MintNft)
+ * [BatchMintNft](#BatchMintNft)
+ * [MintNftClones](#MintNftClones)
+ * [SetMetadata](#setmetadata)
+ * [SetRoyaltyInfo](#setroyaltyinfo)
+ * [Reveal](#reveal)
+ * [MakeOwnershipPrivate](#MakeOwnershipPrivate)
+ * [SetGlobalApproval](#setglobal)
+ * [SetWhitelistedApproval](#setwhitelisted)
+ * [Approve](#Approve)
+ * [Revoke](#Revoke)
+ * [ApproveAll](#ApproveAll)
+ * [RevokeAll](#RevokeAll)
+ * [TransferNft](#TransferNft)
+ * [BatchTransferNft](#BatchTransferNft)
+ * [SendNft](#sendnft)
+ * [BatchSendNft](#batchsend)
+ * [BurnNft](#BurnNft)
+ * [BatchBurnNft](#BatchBurnNft)
+ * [CreateViewingKey](#CreateViewingKey)
+ * [SetViewingKey](#SetViewingKey)
+ * [AddMinters](#AddMinters)
+ * [RemoveMinters](#RemoveMinters)
+ * [SetMinters](#SetMinters)
+ * [SetContractStatus](#SetContractStatus)
+ * [ChangeAdmin](#ChangeAdmin)
+ * [RegisterReceiveNft](#registerreceive)
+ * [RevokePermit](#RevokePermit)
+* Queries
+ * [ContractInfo](#ContractInfo)
+ * [ContractConfig](#ContractConfig)
+ * [Minters](#Minters)
+ * [RegisteredCodeHash](#RegisteredCodeHash)
+ * [NumTokens](#NumTokens)
+ * [AllTokens](#AllTokens)
+ * [IsUnwrapped](#IsUnwrapped)
+ * [IsTransferable](#istransferable)
+ * [OwnerOf](#ownerof)
+ * [NftInfo](#nftinfo)
+ * [AllNftInfo](#AllNftInfo)
+ * [PrivateMetadata](#PrivateMetadata)
+ * [NftDossier](#nftdossier)
+ * [RoyaltyInfo](#royaltyquery)
+ * [TokenApprovals](#TokenApprovals)
+ * [ApprovedForAll](#ApprovedForAll)
+ * [InventoryApprovals](#inventoryapprovals)
+ * [Tokens](#Tokens)
+ * [VerifyTransferApproval](#verifyapproval)
+ * [ImplementsTokenSubtype](#ImplementsTokenSubtype)
+ * [ImplementsNonTransferableTokens](#implementsnontransferabletokens)
+ * [TransactionHistory](#TransactionHistory)
+ * [WithPermit](#WithPermit)
+* [Receiver Interface](#receiver)
+ * [ReceiveNft](#receivenft)
+ * [BatchReceiveNft](#batchreceivenft)
+
+# Instantiating The Token Contract
+##### Request
+```
+{
+ “name”: “name_of_the_token”,
+ “symbol”: “token_symbol”,
+ “admin”: “optional_admin_address”,
+ “entropy”: “string_used_as_entropy_when_generating_random_viewing_keys”,
+ "royalty_info": {
+ "decimal_places_in_rates": 4,
+ "royalties": [
+ {
+ "recipient": "address_that_should_be_paid_this_royalty",
+ "rate": 100,
+ },
+ {
+ "...": "..."
+ }
+ ],
+ },
+ “config”: {
+ “public_token_supply”: true | false,
+ “public_owner”: true | false,
+ “enable_sealed_metadata”: true | false,
+ “unwrapped_metadata_is_private”: true | false,
+ “minter_may_update_metadata”: true | false,
+ “owner_may_update_metadata”: true | false,
+ “enable_burn”: true | false
+ },
+ “post_init_callback”: {
+ “msg”: “base64_encoded_Binary_representing_the_msg_to_perform_after_initialization”,
+ “contract_address”: “address_of_the_contract_being_called_after_initialization”,
+ “code_hash”: “code_hash_of_the_contract_being_called_after_initialization”,
+ “send”: [
+ {
+ “denom”: “denom_string_for_native_coin_being_sent_with_this_message”,
+ “amount”: “amount_of_native_coin_being_sent”
+ },
+ {
+ "...": "..."
+ }
+ ]
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|--------------------|--------------------------------------------------------|---------------------------------------------------------------------|----------|--------------------|
+| name | string | Name of the token contract | no | |
+| symbol | string | Token contract symbol | no | |
+| admin | string (Addr) | Address to be given admin authority | yes | env.message.sender |
+| entropy | string | String used as entropy when generating random viewing keys | no | |
+| royalty_info | [RoyaltyInfo (see below)](#royaltyinfo) | Default RoyaltyInfo for the contract | yes | nothing |
+| config | [Config (see below)](#config) | Privacy configuration for the contract | yes | defined below |
+| post_init_callback | [PostInitCallback (see below)](#postinitcallback) | Information used to perform a callback message after initialization | yes | nothing |
+
+The contract's default RoyaltyInfo is the RoyaltyInfo that will be assigned to any token that is minted without explicitly defining its own RoyaltyInfo. It should be noted that default RoyaltyInfo only applies to new tokens minted while the default is in effect, and will not alter the royalties for any existing NFTs. This is because a token creator should not be able to sell a token with only 1% advertised royalty, and then change it to 100% once it is purchased. If a [SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) non-transferable token is minted, it will not inherit default royalties because non-transferable tokens can never be transferred as part of a sale, rendering royalties meaningless.
+
+### RoyaltyInfo
+RoyaltyInfo is used to define royalties to be paid when an NFT is sold. This implementation will only display a token's royalty recipient addresses if the querier has permission to transfer the token, and it will only display the contract's default royalty recipient addresses if the querier is an authorized minter.
+```
+{
+ "decimal_places_in_rates": 4,
+ "royalties": [
+ {
+ "recipient": "address_that_should_be_paid_this_royalty_(optional_in_query_responses)",
+ "rate": 100,
+ },
+ {
+ "...": "..."
+ }
+ ]
+}
+```
+| Name | Type | Description | Optional |
+|-------------------------|------------------------------------------|-----------------------------------------------------------------------------------------------------|----------|
+| decimal_places_in_rates | number (u8) | The number of decimal places used for all rates in `royalties` (e.g. 2 decimals for whole percents) | no |
+| royalties | array of [Royalty (see below)](#royalty) | List of royalties to be paid upon sale | no |
+
+### Royalty
+Royalty defines a payment address and a royalty rate to be paid when an NFT is sold. This implementation will only display a token's royalty recipient addresses if the querier has permission to transfer the token, and it will only display the contract's default royalty recipient addresses if the querier is an authorized minter.
+```
+{
+ "recipient": "address_that_should_be_paid_this_royalty_(optional_in_query_responses)",
+ "rate": 100,
+}
+```
+| Name | Type | Description | Optional in Messages | Optional in Query Responses |
+|-----------|--------------------|------------------------------------------------------------------------------------------------------------------|----------------------|-----------------------------|
+| recipient | string (Addr) | The address that should be paid this royalty | no | yes |
+| rate | number (u16) | The royalty rate to be paid using the number of decimals specified in the `RoyaltyInfo` containing this `Royalty`| no | no |
+
+### Config
+Config is the privacy configuration for the contract.
+* `public_token_supply` - This config value indicates whether the token IDs and the number of tokens controlled by the contract are public. If the token supply is private, only minters can view the token IDs and number of tokens controlled by the contract (default: False)
+* `public_owner` - This config value indicates whether token ownership is public or private by default. Regardless of this setting a user has the ability to change whether the ownership of their tokens is public or private (default: False)
+* `enable_sealed_metadata` - This config value indicates whether sealed metadata should be enabled. If sealed metadata is enabled, the private metadata of a newly minted token is not viewable by anyone, not even the owner, until the owner calls the [Reveal](#reveal) message. When Reveal is called, the sealed metadata is irreversibly unwrapped and moved to the public metadata (as default). If `unwrapped_metadata_is_private` is set to true, the sealed metadata will remain as private metadata after unwrapping, but the owner (and anyone the owner has whitelisted) will now be able to see it. Anyone will be able to query the token to know whether it has been unwrapped. This simulates buying/selling a wrapped card that no one knows which card it is until it is unwrapped. If sealed metadata is not enabled, all tokens are considered unwrapped when minted (default: False)
+* `unwrapped_metadata_is_private` - This config value indicates if the [Reveal](#reveal) message should keep the sealed metadata private after unwrapping. This config value is ignored if sealed metadata is not enabled (default: False)
+* `minter_may_update_metadata` - This config value indicates whether a minter is permitted to update a token's metadata (default: True)
+* `owner_may_update_metadata` - This config value indicates whether the owner of a token is permitted to update a token's metadata (default: False)
+* `enable_burn` - This config value indicates whether burn functionality is enabled. [SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) non-transferable tokens can always be burned even when burning is disabled. This is because an owner must have a way to dispose of an unwanted, non-transferable token (default: False)
+```
+{
+ “public_token_supply”: true | false,
+ “public_owner”: true | false,
+ “enable_sealed_metadata”: true | false,
+ “unwrapped_metadata_is_private”: true | false,
+ “minter_may_update_metadata”: true | false,
+ “owner_may_update_metadata”: true | false,
+ “enable_burn”: true | false
+}
+```
+| Name | Type | Optional | Value If Omitted |
+|-------------------------------|------|----------|------------------|
+| public_token_supply | bool | yes | false |
+| public_owner | bool | yes | false |
+| enable_sealed_metadata | bool | yes | false |
+| unwrapped_metadata_is_private | bool | yes | false |
+| minter_may_update_metadata | bool | yes | true |
+| owner_may_update_metadata | bool | yes | false |
+| enable_burn | bool | yes | false |
+
+### PostInitCallback
+The PostInitCallback object is used to have the token contract execute an optional callback message after the contract has initialized. This can be useful if another contract is instantiating this token contract and needs the token contract to inform the creating contract of the address it has been given.
+```
+{
+ “msg”: “base64_encoded_Binary_representing_the_msg_to_perform_after_initialization”,
+ “contract_address”: “address_of_the_contract_being_called_after_initialization”,
+ “code_hash”: “code_hash_of_the_contract_being_called_after_initialization”,
+ “send”: [
+ {
+ “denom”: “denom_string_for_native_coin_being_sent_with_this_message”,
+ “amount”: “amount_of_native_coin_being_sent”
+ },
+ {
+ "...": "..."
+ }
+ ]
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|------------------|---------------------------------------|-------------------------------------------------------------------------------------------------------|----------|------------------|
+| msg | string (base64 encoded Binary) | Base64 encoded Binary representation of the callback message to perform after contract initialization | no | |
+| contract_address | string (Addr) | Address of the contract to call after initialization | no | |
+| code_hash | string | A 32-byte hex encoded string, with the code hash of the contract to call after initialization | no | |
+| send | array of [Coin (see below)](#coin) | List of native Coin amounts to send with the callback message | no | |
+
+#### Coin
+Coin is the payment to send with the post-init callback message. Although `send` is not an optional field of the [PostInitCallback](#postinitcallback), because it is an array, you can just use `[]` to not send any payment with the callback message
+```
+{
+ “denom”: “denom_string_for_native_coin_being_sent_with_this_message”,
+ “amount”: “amount_of_native_coin_being_sent”
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|--------|------------------|---------------------------------------------------------------------------|----------|------------------|
+| denom | string | The denomination of the native Coin (uscrt for SCRT) | no | |
+| amount | string (Uint128) | The amount of the native Coin to send with the PostInitCallback message | no | |
+
+# Messages
+## MintNft
+MintNft mints a single token. Only an authorized minting address my execute MintNft.
+
+[SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) adds the ability to optionally mint non-transferable tokens, which are NFTs that can never have a different owner than the address it was minted to.
+
+##### Request
+```
+{
+ "mint_nft": {
+ "token_id": "optional_ID_of_new_token",
+ "owner": "optional_address_the_new_token_will_be_minted_to",
+ "public_metadata": {
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+ },
+ "private_metadata": {
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+ },
+ "serial_number": {
+ "mint_run": 3,
+ "serial_number": 67,
+ "quantity_minted_this_run": 1000,
+ },
+ "royalty_info": {
+ "decimal_places_in_rates": 4,
+ "royalties": [
+ {
+ "recipient": "address_that_should_be_paid_this_royalty",
+ "rate": 100,
+ },
+ {
+ "...": "..."
+ }
+ ],
+ },
+ "transferable": true | false,
+ "memo": "optional_memo_for_the_mint_tx",
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|------------------|-------------------------------------------|-----------------------------------------------------------------------------------------------|----------|----------------------|
+| token_id | string | Identifier for the token to be minted | yes | minting order number |
+| owner | string (Addr) | Address of the owner of the minted token | yes | env.message.sender |
+| public_metadata | [Metadata (see below)](#metadata) | The metadata that is publicly viewable | yes | nothing |
+| private_metadata | [Metadata (see below)](#metadata) | The metadata that is viewable only by the token owner and addresses the owner has whitelisted | yes | nothing |
+| serial_number | [SerialNumber (see below)](#serialnumber) | The SerialNumber for this token | yes | nothing |
+| royalty_info | [RoyaltyInfo (see above)](#royaltyinfo) | RoyaltyInfo for this token | yes | default RoyaltyInfo |
+| transferable | bool | True if the minted token should be transferable | yes | true |
+| memo | string | `memo` for the mint tx that is only viewable by addresses involved in the mint (minter, owner)| yes | nothing |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+Setting royalties for a non-transferable token has no purpose, because it can never be transferred as part of a sale, so this implementation will not store any RoyaltyInfo for non-transferable tokens.
+
+##### Response
+```
+{
+ "mint_nft": {
+ "token_id": "ID_of_minted_token",
+ }
+}
+```
+The ID of the minted token will also be returned in a LogAttribute with the key `minted`.
+
+### Metadata
+This is the metadata for a token that follows CW-721 metadata specification, which is based on ERC721 Metadata JSON Schema. This implementation will throw an error if both `token_uri` and `extension` are provided.
+```
+{
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------|-------------------------------------|--------------------------------------------------------------------------------------|----------|----------------------|
+| token_uri | string | Uri pointing to off-chain JSON metadata | yes | nothing |
+| extension | [Extension (see below)](#extension) | Data structure defining on-chain metadata | yes | nothing |
+This implementation will throw an error if both `token_uri` and `extension` are provided.
+
+### Extension
+This is an on-chain metadata extension struct that conforms to the Stashh metadata standard (which in turn implements https://docs.opensea.io/docs/metadata-standards). Urls should be prefixed with `http://`, `https://`, `ipfs://`, or `ar://`. Feel free to add/delete any fields as necessary.
+
+[SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) adds an optional `token_subtype` field in Extension to be used for Badges/POAPs. This field is intended to be used by applications in order to differentiate NFTs that are used as Badges/POAPs so that they can be displayed as such because they will be used for things like trophies, achievements, proof of attendence, etc...
+```
+{
+ "image": "optional_image_url",
+ "image_data": "optional_raw_svg_image_data",
+ "external_url": "optional_url_to_view_token_on_your_site",
+ "description": "optional_token_description",
+ "name": "optional_token_name",
+ "attributes": [
+ {
+ "display_type": "optional_display_format_for_numerical_traits",
+ "trait_type": "optional_name_of_the_trait",
+ "value": "trait value",
+ "max_value": "optional_max_value_for_numerical_traits"
+ },
+ {
+ "...": "...",
+ },
+ ],
+ "background_color": "optional_six-character_hexadecimal_background_color_(without_pre-pended_`#`)",
+ "animation_url": "optional_url_to_multimedia_file",
+ "youtube_url": "optional_url_to_a_YouTube_video",
+ "media": [
+ {
+ "file_type": "optional_file_type",
+ "extension": "optional_file_extension",
+ "authentication": {
+ "key": "optional_decryption_key_or_password",
+ "user": "optional_username_for_authentication"
+ },
+ "url": "url_pointing_to_the_multimedia_file"
+ },
+ {
+ "...": "...",
+ },
+ ],
+ "protected_attributes": [ "list", "of_attributes", "whose_types", "are_public", "but_values", "are_private" ],
+ "token_subtype": "badge"
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|----------------------|----------------------------------------------|--------------------------------------------------------------------------------------|----------|----------------------|
+| image | string | Url to the token's image | yes | nothing |
+| image_data | string | Raw SVG image data that should only be used if there is no `image` field | yes | nothing |
+| external_url | string | Url to view the token on your site | yes | nothing |
+| description | string | Text description of the token | yes | nothing |
+| name | string | Name of the token | yes | nothing |
+| attributes | array of [Trait (see below)](#trait) | Token's attributes | yes | nothing |
+| background_color | string | Background color represented as a six-character hexadecimal without a pre-pended # | yes | nothing |
+| animation_url | string | Url to a multimedia file | yes | nothing |
+| youtube_url | string | Url to a YouTube video | yes | nothing |
+| media | array of [MediaFile (see below)](#mediafile) | List of multimedia files using Stashh specifications | yes | nothing |
+| protected_attributes | array of string | List of attributes whose types are public but whose values are private | yes | nothing |
+| token_subtype | string | token subtype used to signify what the NFT is used for, such as "badge" | yes | nothing |
+
+### Trait
+Trait describes a token attribute as defined in https://docs.opensea.io/docs/metadata-standards.
+```
+{
+ "display_type": "optional_display_format_for_numerical_traits",
+ "trait_type": "optional_name_of_the_trait",
+ "value": "trait value",
+ "max_value": "optional_max_value_for_numerical_traits"
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|--------------|--------|--------------------------------------------------------------------------------------|----------|----------------------|
+| display_type | string | Display format for numerical traits | yes | nothing |
+| trait_type | string | Name of the trait | yes | nothing |
+| value | string | Trait value | no | |
+| max_value | string | Maximum value for this numerical trait | yes | nothing |
+
+### MediaFile
+MediaFile is the data structure used by Stashh to reference off-chain multimedia files. It allows for hosted files to be encrypted or authenticated with basic authentication, and for the decryption key or username/password to also be included in the on-chain private metadata. Urls should be prefixed with `http://`, `https://`, `ipfs://`, or `ar://`.
+```
+{
+ "file_type": "optional_file_type",
+ "extension": "optional_file_extension",
+ "authentication": {
+ "key": "optional_decryption_key_or_password",
+ "user": "optional_username_for_authentication"
+ },
+ "url": "url_pointing_to_the_multimedia_file"
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|----------------|-----------------------------------------------|---------------------------------------------------------------------------------------------|----------|----------------------|
+| file_type | string | File type. Stashh currently uses: "image", "video", "audio", "text", "font", "application" | yes | nothing |
+| extension | string | File extension | yes | nothing |
+| authentication | [Authentication (see below)](#authentication) | Credentials or decryption key for a protected file | yes | nothing |
+| url | string | Url to the multimedia file | no | |
+
+### Authentication
+Authentication is used to provide the decryption key or username/password for protected files.
+```
+{
+ "key": "optional_decryption_key_or_password",
+ "user": "optional_username_for_authentication"
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|------|--------|--------------------------------------------------------------------------------------|----------|----------------------|
+| key | string | Decryption key or password | yes | nothing |
+| user | string | Username for basic authentication | yes | nothing |
+
+### SerialNumber
+SerialNumber is used to serialize identical NFTs.
+```
+{
+ "mint_run": 3,
+ "serial_number": 67,
+ "quantity_minted_this_run": 1000,
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|--------------------------|--------------|-----------------------------------------------------------------------------------------------------|----------|----------------------|
+| mint_run | number (u32) | The mint run this token was minted in. This represents batches of NFTs released at the same time | yes | nothing |
+| serial_number | number (u32) | The serial number of this token | no | |
+| quantity_minted_this_run | number (u32) | The number of tokens minted in this mint run. | yes | nothing |
+
+A mint run is a group of NFTs released at the same time. So, for example, if a creator decided to make 100 copies, they would all be part of mint run number 1. If they sell well and the creator wants to rerelease that NFT, he could make 100 more copies that would all be part of mint run number 2. The combination of mint_run, serial_number, and quantity_minted_this_run is used to indicate, for example, that this token was number 67 of 1000 minted in mint run number 3.
+
+## BatchMintNft
+BatchMintNft mints a list of tokens. Only an authorized minting address my execute BatchMintNft.
+
+[SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) adds the ability to optionally mint non-transferable tokens, which are NFTs that can never have a different owner than the address it was minted to.
+
+##### Request
+```
+{
+ "batch_mint_nft": {
+ "mints": [
+ {
+ "token_id": "optional_ID_of_new_token",
+ "owner": "optional_address_the_new_token_will_be_minted_to",
+ "public_metadata": {
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+ },
+ "private_metadata": {
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+ },
+ "serial_number": {
+ "mint_run": 3,
+ "serial_number": 67,
+ "quantity_minted_this_run": 1000,
+ },
+ "royalty_info": {
+ "decimal_places_in_rates": 4,
+ "royalties": [
+ {
+ "recipient": "address_that_should_be_paid_this_royalty",
+ "rate": 100,
+ },
+ {
+ "...": "..."
+ }
+ ],
+ },
+ "transferable": true | false,
+ "memo": "optional_memo_for_the_mint_tx"
+ },
+ {
+ "...": "..."
+ }
+ ],
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|---------|---------------------------------------|------------------------------------------------------------------------|----------|------------------|
+| mints | array of [Mint (see below)](#mint) | A list of all the mint operations to perform | no | |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "batch_mint_nft": {
+ "token_ids": [
+ "IDs", "of", "tokens", "that", "were", "minted", "..."
+ ]
+ }
+}
+```
+The IDs of the minted tokens will also be returned in a LogAttribute with the key `minted`.
+
+### Mint
+The Mint object defines the data necessary to mint one token.
+```
+{
+ "token_id": "optional_ID_of_new_token",
+ "owner": "optional_address_the_new_token_will_be_minted_to",
+ "public_metadata": {
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+ },
+ "private_metadata": {
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+ },
+ "serial_number": {
+ "mint_run": 3,
+ "serial_number": 67,
+ "quantity_minted_this_run": 1000,
+ },
+ "royalty_info": {
+ "decimal_places_in_rates": 4,
+ "royalties": [
+ {
+ "recipient": "address_that_should_be_paid_this_royalty",
+ "rate": 100,
+ },
+ {
+ "...": "..."
+ }
+ ],
+ },
+ "transferable": true | false,
+ "memo": "optional_memo_for_the_mint_tx"
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|------------------|-------------------------------------------|------------------------------------------------------------------------------------------------|----------|----------------------|
+| token_id | string | Identifier for the token to be minted | yes | minting order number |
+| owner | string (Addr) | Address of the owner of the minted token | yes | env.message.sender |
+| public_metadata | [Metadata (see above)](#metadata) | The metadata that is publicly viewable | yes | nothing |
+| private_metadata | [Metadata (see above)](#metadata) | The metadata that is viewable only by the token owner and addresses the owner has whitelisted | yes | nothing |
+| serial_number | [SerialNumber (see above)](#serialnumber) | The SerialNumber for this token | yes | nothing |
+| royalty_info | [RoyaltyInfo (see above)](#royaltyinfo) | RoyaltyInfo for this token | yes | default RoyaltyInfo |
+| transferable | bool | True if the minted token should be transferable | yes | true |
+| memo | string | `memo` for the mint tx that is only viewable by addresses involved in the mint (minter, owner) | yes | nothing |
+
+Setting royalties for a non-transferable token has no purpose, because it can never be transferred as part of a sale, so this implementation will not store any RoyaltyInfo for non-transferable tokens.
+
+## MintNftClones
+MintNftClones mints copies of an NFT, giving each one a [MintRunInfo](#mintruninfo) that indicates its serial number and the number of identical NFTs minted with it. If the optional `mint_run_id` is provided, the contract will also indicate which mint run these tokens were minted in, where the first use of the `mint_run_id` will be mint run number 1, the second time MintNftClones is called with that `mint_run_id` will be mint run number 2, etc... If no `mint_run_id` is provided, the MintRunInfo will not include a `mint_run`.
+
+[SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) adds the ability to optionally mint non-transferable tokens, which are NFTs that can never have a different owner than the address it was minted to.
+
+##### Request
+```
+{
+ "mint_nft_clones": {
+ "mint_run_id": "optional_ID_used_to_track_mint_run_numbers_over_multiple_calls",
+ "quantity": 100,
+ "owner": "optional_address_the_new_tokens_will_be_minted_to",
+ "public_metadata": {
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+ },
+ "private_metadata": {
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+ },
+ "royalty_info": {
+ "decimal_places_in_rates": 4,
+ "royalties": [
+ {
+ "recipient": "address_that_should_be_paid_this_royalty",
+ "rate": 100,
+ },
+ {
+ "...": "..."
+ }
+ ],
+ },
+ "transferable": true | false,
+ "memo": "optional_memo_for_the_mint_tx",
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|------------------|-----------------------------------------|----------------------------------------------------------------------------------------------------------|----------|---------------------|
+| mint_run_id | string | Identifier used to track the number of mint runs these clones have had over multiple MintNftClones calls | yes | nothing |
+| quantity | number (u32) | Number of clones to mint in this run | no | |
+| owner | string (Addr) | Address of the owner of the minted tokens | yes | env.message.sender |
+| public_metadata | [Metadata (see above)](#metadata) | The metadata that is publicly viewable | yes | nothing |
+| private_metadata | [Metadata (see above)](#metadata) | The metadata that is viewable only by the token owner and addresses the owner has whitelisted | yes | nothing |
+| royalty_info | [RoyaltyInfo (see above)](#royaltyinfo) | RoyaltyInfo for these tokens | yes | default RoyaltyInfo |
+| transferable | bool | True if the minted token should be transferable | yes | true |
+| memo | string | `memo` for the mint tx that is only viewable by addresses involved in the mint (minter, owner) | yes | nothing |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+Setting royalties for a non-transferable token has no purpose, because it can never be transferred as part of a sale, so this implementation will not store any RoyaltyInfo for non-transferable tokens.
+
+##### Response
+```
+{
+ "mint_nft_clones": {
+ "first_minted": "token_id_of_the_first_minted_token",
+ "last_minted": "token_id_of_the_last_minted_token"
+ }
+}
+```
+The IDs of the minted tokens will also be returned in LogAttributes with the keys `first_minted` and `last_minted`. Because the token IDs are sequential, the IDs of the other minted tokens are easily inferred.
+
+## SetMetadata
+SetMetadata will set the public and/or private metadata to the corresponding input if the message sender is either the token owner or an approved minter and they have been given this power by the configuration value chosen during instantiation. The private metadata of a [sealed](#enablesealed) token may not be altered until after it has been unwrapped.
+
+##### Request
+```
+{
+ "set_metadata": {
+ "token_id": "ID_of_token_whose_metadata_should_be_updated",
+ "public_metadata": {
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+ },
+ "private_metadata": {
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+ },
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|------------------|--------------------------------------|------------------------------------------------------------------------|----------|------------------|
+| token_id | string | ID of the token whose metadata should be updated | no | |
+| public_metadata | [Metadata (see above)](#metadata) | The new public metadata for the token | yes | nothing |
+| private_metadata | [Metadata (see above)](#metadata) | The new private metadata for the token | yes | nothing |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "set_metadata": {
+ "status": "success"
+ }
+}
+```
+
+## SetRoyaltyInfo
+If a token_id is supplied, SetRoyaltyInfo will update the specified token's RoyaltyInfo to the input. If no RoyaltyInfo is provided, it will delete the RoyaltyInfo and replace it with the contract's default RoyaltyInfo (if there is one). If no token_id is provided, SetRoyaltyInfo will update the contract's default RoyaltyInfo to the input, or delete it if no RoyaltyInfo is provided.
+Only an authorized minter may update the contract's default RoyaltyInfo.
+Only a token's creator may update its RoyaltyInfo, and only if they are also the current owner.
+
+This implementation will throw an error if trying to set the royalty of a [SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) non-transferable token, because they can never be transferred as part of a sale.
+
+##### Request
+```
+{
+ "set_royalty_info": {
+ "token_id": "optional_ID_of_token_whose_royalty_info_should_be_updated",
+ "royalty_info": {
+ "decimal_places_in_rates": 4,
+ "royalties": [
+ {
+ "recipient": "address_that_should_be_paid_this_royalty",
+ "rate": 100,
+ },
+ {
+ "...": "..."
+ }
+ ],
+ },
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|--------------|-----------------------------------------|------------------------------------------------------------------------|----------|------------------|
+| token_id | string | Optional ID of the token whose RoyaltyInfo should be updated | yes | see above |
+| royalty_info | [RoyaltyInfo (see above)](#royaltyinfo) | The new RoyaltyInfo | yes | see above |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "set_royalty_info": {
+ "status": "success"
+ }
+}
+```
+
+## Reveal
+Reveal unwraps the [sealed](#enablesealed) private metadata, irreversibly marking the token as unwrapped. If the `unwrapped_metadata_is_private` [configuration value](#unwrapprivate) is true, the formerly sealed metadata will remain private, otherwise it will be made public.
+
+##### Request
+```
+{
+ "reveal": {
+ "token_id": "ID_of_the_token_to_unwrap",
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|----------|----------------------|------------------------------------------------------------------------|----------|------------------|
+| token_id | string | ID of the token to unwrap | no | |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "reveal": {
+ "status": "success"
+ }
+}
+```
+
+## MakeOwnershipPrivate
+MakeOwnershipPrivate is used when the token contract was instantiated with the `public_owner` configuration value set to true. It allows an address to make all of its tokens have private ownership by default. The owner may still use [SetGlobalApproval](#setglobal) or [SetWhitelistedApproval](#setwhitelisted) to make ownership public as desired.
+
+##### Request
+```
+{
+ "make_ownership_private": {
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|----------|----------------------|------------------------------------------------------------------------|----------|------------------|
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "make_ownership_private": {
+ "status": "success"
+ }
+}
+```
+
+## SetGlobalApproval
+The owner of a token can use SetGlobalApproval to make ownership and/or private metadata viewable by everyone. This can be set for a single token or for an owner's entire inventory of tokens by choosing the appropriate [AccessLevel](#accesslevel). SetGlobalApproval can also be used to revoke any global approval previously granted.
+
+##### Request
+```
+{
+ "set_global_approval": {
+ "token_id": "optional_ID_of_the_token_to_grant_or_revoke_approval_on",
+ "view_owner": "approve_token" | "all" | "revoke_token" | "none",
+ "view_private_metadata": "approve_token" | "all" | "revoke_token" | "none",
+ "expires": "never" | {"at_height": 999999} | {"at_time":999999},
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------------|--------------------------------------------|----------------------------------------------------------------------------------------------------|----------|------------------|
+| token_id | string | If supplying either `approve_token` or `revoke_token` access, the token whose privacy is being set | yes | nothing |
+| view_owner | [AccessLevel (see below)](#accesslevel) | Grant or revoke everyone's permission to view the ownership of a token/inventory | yes | nothing |
+| view_private_metadata | [AccessLevel (see below)](#accesslevel) | Grant or revoke everyone's permission to view the private metadata of a token/inventory | yes | nothing |
+| expires | [Expiration (see below)](#expiration) | Expiration of any approval granted in this message. Can be a blockheight, time, or never | yes | "never" |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "set_global_approval": {
+ "status": "success"
+ }
+}
+```
+### AccessLevel
+AccessLevel determines the type of access being granted or revoked to the specified address in a [SetWhitelistedApproval](#setwhitelisted) message or to everyone in a [SetGlobalApproval](#setglobal) message. Inventory-wide approval and token-specific approval are mutually exclusive levels of access. The levels are:
+* `"approve_token"` - grant approval only on the token specified in the message
+* `"revoke_token"` - revoke a previous approval on the specified token
+* `"all"` - grant approval for all tokens in the message signer's inventory. This approval will also apply to any tokens the signer acquires after granting `all` approval
+* `"none"` - revoke any approval (both token and inventory-wide) previously granted to the specified address (or for everyone if using SetGlobalApproval)
+
+If the message signer grants an address (or everyone in the case of SetGlobalApproval) `all` (inventory-wide) approval, it will remove any individual token approvals previously granted to that address (or granted to everyone in the case of SetGlobalApproval), and grant that address `all` (inventory-wide) approval. If an address (or everyone in the case of SetGlobalApproval) already has `all` approval, and the message signer grants it `approve_token` approval, if the expiration of the new `approve_token` approval is the same as the expiration of the previous `all` approval, it will just leave the `all` approval in place. If the expirations are different, it will grant `approve_token` approval with the specified expiration for the input token, and all other tokens will be changed to `approve_token` approvals with the expiration of the previous `all` approval, and the `all` (inventory-wide) approval will be removed. If the message signer applies `revoke_token` access to an address that currently has inventory-wide approval, it will remove the inventory-wide approval, and create `approve_token` approvals for that address on every token in the signer's inventory EXCEPT the token specified with the `revoke_token` message. In other words, it will only revoke the approval on that single token.
+
+### Expiration
+The Expiration object is used to set an expiration for any approvals granted in the message. Expiration can be set to a specified blockheight, a time in seconds since epoch 01/01/1970, or "never". Values for blockheight and time are specified as a u64. If no expiration is given, it will default to "never".
+
+One should be aware that the current blockheight and time is not available to a query on Secret Network at this moment, but there are plans to make the BlockInfo available to queries in a future hardfork. To get around this limitation, the contract saves the BlockInfo every time a message is executed, and uses the blockheight and time of the last message execution to check viewing permission expiration during a query. Therefore it is possible that a whitelisted address may be able to view the owner or metadata of a token past its approval expiration if no one executed any contract message since before the expiration. However, because transferring/burning a token is executing a message, it does have the current blockheight and time available and can not occur if transfer approval has expired.
+
+* `"never"` - the approval will never expire
+* `{"at_time": 1700000000}` - the approval will expire 1700000000 seconds after 01/01/1970 (time value is u64)
+* `{"at_height": 3000000}` - the approval will expire at blockheight 3000000 (height value is u64)
+
+## SetWhitelistedApproval
+The owner of a token can use SetWhitelistedApproval to grant an address permission to view ownership, view private metadata, and/or to transfer a single token or every token in the owner's inventory. SetWhitelistedApproval can also be used to revoke any approval previously granted to the address.
+
+##### Request
+```
+{
+ "set_whitelisted_approval": {
+ "address": "address_being_granted_or_revoked_approval",
+ "token_id": "optional_ID_of_the_token_to_grant_or_revoke_approval_on",
+ "view_owner": "approve_token" | "all" | "revoke_token" | "none",
+ "view_private_metadata": "approve_token" | "all" | "revoke_token" | "none",
+ "transfer": "approve_token" | "all" | "revoke_token" | "none",
+ "expires": "never" | {"at_height": 999999} | {"at_time":999999},
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------------|--------------------------------------------|----------------------------------------------------------------------------------------------------|----------|------------------|
+| address | string (Addr) | Address to grant or revoke approval to/from | no | |
+| token_id | string | If supplying either `approve_token` or `revoke_token` access, the token whose privacy is being set | yes | nothing |
+| view_owner | [AccessLevel (see above)](#accesslevel) | Grant or revoke the address' permission to view the ownership of a token/inventory | yes | nothing |
+| view_private_metadata | [AccessLevel (see above)](#accesslevel) | Grant or revoke the address' permission to view the private metadata of a token/inventory | yes | nothing |
+| transfer | [AccessLevel (see above)](#accesslevel) | Grant or revoke the address' permission to transfer a token/inventory | yes | nothing |
+| expires | [Expiration (see above)](#expiration) | The expiration of any approval granted in this message. Can be a blockheight, time, or never | yes | "never" |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "set_whitelisted_approval": {
+ "status": "success"
+ }
+}
+```
+
+## Approve
+Approve is used to grant an address permission to transfer a single token. This can only be performed by the token's owner or, in compliance with CW-721, an address that has inventory-wide approval to transfer the owner's tokens. Approve is provided to maintain compliance with CW-721, but the owner can use [SetWhitelistedApproval](#setwhitelisted) to accomplish the same thing if specifying a `token_id` and `approve_token` [AccessLevel](#accesslevel) for `transfer`.
+
+##### Request
+```
+{
+ "approve": {
+ "spender": "address_being_granted_approval_to_transfer_the_specified_token",
+ "token_id": "ID_of_the_token_that_can_now_be_transferred_by_the_spender",
+ "expires": "never" | {"at_height": 999999} | {"at_time":999999},
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------------|------------------------------------------|------------------------------------------------------------------------------------------------------|----------|------------------|
+| spender | string (Addr) | Address being granted approval to transfer the token | no | |
+| token_id | string | ID of the token that the spender can now transfer | no | |
+| expires | [Expiration (see above)](#expiration) | The expiration of this token transfer approval. Can be a blockheight, time, or never | yes | "never" |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "approve": {
+ "status": "success"
+ }
+}
+```
+
+## Revoke
+Revoke is used to revoke from an address the permission to transfer this single token. This can only be performed by the token's owner or, in compliance with CW-721, an address that has inventory-wide approval to transfer the owner's tokens (referred to as an operator later). However, one operator may not revoke transfer permission of even one single token away from another operator. Revoke is provided to maintain compliance with CW-721, but the owner can use [SetWhitelistedApproval](#setwhitelisted) to accomplish the same thing if specifying a `token_id` and `revoke_token` [AccessLevel](#accesslevel) for `transfer`.
+
+##### Request
+```
+{
+ "revoke": {
+ "spender": "address_being_revoked_approval_to_transfer_the_specified_token",
+ "token_id": "ID_of_the_token_that_can_no_longer_be_transferred_by_the_spender",
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------------|-------------------------|------------------------------------------------------------------------------------------------------|----------|------------------|
+| spender | string (Addr) | Address no longer permitted to transfer the token | no | |
+| token_id | string | ID of the token that the spender can no longer transfer | no | |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "revoke": {
+ "status": "success"
+ }
+}
+```
+
+## ApproveAll
+ApproveAll is used to grant an address permission to transfer all the tokens in the message sender's inventory. This will include the ability to transfer any tokens the sender acquires after granting this inventory-wide approval. This also gives the address the ability to grant another address the approval to transfer a single token. ApproveAll is provided to maintain compliance with CW-721, but the message sender can use [SetWhitelistedApproval](#setwhitelisted) to accomplish the same thing by using `all` [AccessLevel](#accesslevel) for `transfer`.
+
+##### Request
+```
+{
+ "approve_all": {
+ "operator": "address_being_granted_inventory-wide_approval_to_transfer_tokens",
+ "expires": "never" | {"at_height": 999999} | {"at_time":999999},
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------------|------------------------------------------|------------------------------------------------------------------------------------------------------|----------|------------------|
+| operator | string (Addr) | Address being granted approval to transfer all of the message sender's tokens | no | |
+| expires | [Expiration (see above)](#expiration) | The expiration of this inventory-wide transfer approval. Can be a blockheight, time, or never | yes | "never" |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "approve_all": {
+ "status": "success"
+ }
+}
+```
+
+## RevokeAll
+RevokeAll is used to revoke all transfer approvals granted to an address. RevokeAll is provided to maintain compliance with CW-721, but the message sender can use [SetWhitelistedApproval](#setwhitelisted) to accomplish the same thing by using `none` [AccessLevel](#accesslevel) for `transfer`.
+
+##### Request
+```
+{
+ "revoke_all": {
+ "operator": "address_being_revoked_all_approvals_to_transfer_tokens",
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------------|-------------------------|------------------------------------------------------------------------------------------------------|----------|------------------|
+| operator | string (Addr) | Address being revoked all approvals to transfer the message sender's tokens | no | |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "revoke_all": {
+ "status": "success"
+ }
+}
+```
+
+## TransferNft
+TransferNft is used to transfer ownership of the token to the `recipient` address. This requires a valid `token_id` and the message sender must either be the owner or an address with valid transfer approval. If the `recipient` address is the same as the current owner, the contract will throw an error. If the token is transferred to a new owner, its single-token approvals will be cleared.
+
+This implementation will throw an error if trying to transfer a [SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) non-transferable token.
+
+##### Request
+```
+{
+ "transfer_nft": {
+ "recipient": "address_receiving_the_token",
+ "token_id": "ID_of_the_token_being_transferred",
+ "memo": "optional_memo_for_the_transfer_tx",
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------|--------------------|-------------------------------------------------------------------------------------------------------------------------------------|----------|------------------|
+| recipient | string (Addr) | Address receiving the token | no | |
+| token_id | string | Identifier of the token to be transferred | no | |
+| memo | string | `memo` for the transfer transaction that is only viewable by addresses involved in the transfer (recipient, sender, previous owner) | yes | nothing |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "transfer_nft": {
+ "status": "success"
+ }
+}
+```
+
+## BatchTransferNft
+BatchTransferNft is used to perform multiple token transfers. The message sender may specify a list of tokens to transfer to one `recipient` address in each [Transfer](#transfer) object, and any `memo` provided will be applied to every token transferred in that one `Transfer` object. The message sender may provide multiple `Transfer` objects to perform transfers to multiple addresses, providing a different `memo` for each address if desired. Each individual transfer of a token will show separately in transaction histories. The message sender must have permission to transfer all the tokens listed (either by being the owner or being granted transfer approval) and every listed `token_id` must be valid. A contract may use the [VerifyTransferApproval](#verifyapproval) query to verify that it has permission to transfer all the tokens.
+
+If the message sender does not have permission to transfer any one of the listed tokens, the entire message will fail (no tokens will be transferred) and the error will provide the ID of the first token encountered in which the sender does not have the required permission. If any token transfer involves a `recipient` address that is the same as its current owner, the contract will throw an error. Any token that is transferred to a new owner will have its single-token approvals cleared.
+
+This implementation will throw an error if trying to transfer a [SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) non-transferable token.
+
+##### Request
+```
+{
+ "batch_transfer_nft": {
+ "transfers": [
+ {
+ "recipient": "address_receiving_the_tokens",
+ "token_ids": [
+ "list", "of", "token", "IDs", "to", "transfer"
+ ],
+ "memo": "optional_memo_applied_to_the_transfer_tx_for_every_token_listed_in_this_Transfer_object"
+ },
+ {
+ "...": "..."
+ }
+ ],
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------|-----------------------------------------------|------------------------------------------------------------------------------------------------------|----------|------------------|
+| transfers | array of [Transfer (see below)](#transfer) | List of `Transfer` objects to process | no | |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "batch_transfer_nft": {
+ "status": "success"
+ }
+}
+```
+
+### Transfer
+The Transfer object provides a list of tokens to transfer to one `recipient` address, as well as an optional `memo` that would be included with every logged token transfer.
+```
+{
+ "recipient": "address_receiving_the_tokens",
+ "token_ids": [
+ "list", "of", "token", "IDs", "to", "transfer", "..."
+ ],
+ "memo": "optional_memo_applied_to_the_transfer_tx_for_every_token_listed_in_this_Transfer_object"
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------|--------------------|-------------------------------------------------------------------------------------------------------------------------------------|----------|------------------|
+| recipient | string (Addr) | Address receiving the listed tokens | no | |
+| token_ids | array of string | List of token IDs to transfer to the `recipient` | no | |
+| memo | string | `memo` for the transfer transactions that is only viewable by addresses involved in the transfer (recipient, sender, previous owner)| yes | nothing |
+
+## SendNft
+SendNft is used to transfer ownership of the token to the `contract` address, and then call the recipient's [BatchReceiveNft](#batchreceivenft) (or [ReceiveNft](#receivenft)) if the recipient contract has registered its receiver interface with the NFT contract or if its [ReceiverInfo](#receiverinfo) is provided. If the recipient contract registered (or if the `ReceiverInfo` indicates) that it implements BatchReceiveNft, a BatchReceiveNft callback will be performed with only the single token ID in the `token_ids` array.
+
+While SendNft keeps the `contract` field name in order to maintain CW-721 compliance, Secret Network does not have the same limitations as Cosmos, and it is possible to use SendNft to transfer token ownership to a personal address (not a contract) or to a contract that does not implement any [Receiver Interface](#receiver).
+
+SendNft requires a valid `token_id` and the message sender must either be the owner or an address with valid transfer approval. If the recipient address is the same as the current owner, the contract will throw an error. If the token is transferred to a new owner, its single-token approvals will be cleared. If the BatchReceiveNft (or ReceiveNft) callback fails, the entire transaction will be reverted (even the transfer will not take place).
+
+This implementation will throw an error if trying to send a [SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) non-transferable token.
+
+##### Request
+```
+{
+ "send_nft": {
+ "contract": "address_receiving_the_token",
+ "receiver_info": {
+ "recipient_code_hash": "code_hash_of_the_recipient_contract",
+ "also_implements_batch_receive_nft": true | false,
+ },
+ "token_id": "ID_of_the_token_being_transferred",
+ "msg": "optional_base64_encoded_Binary_message_sent_with_the_BatchReceiveNft_callback",
+ "memo": "optional_memo_for_the_transfer_tx",
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|---------------|-------------------------------------------|--------------------------------------------------------------------------------------------------------|----------|------------------|
+| contract | string (Addr) | Address receiving the token | no | |
+| receiver_info | [ReceiverInfo (see below)](#receiverinfo) | Code hash and BatchReceiveNft implementation status of the recipient contract | yes | nothing |
+| token_id | string | Identifier of the token to be transferred | no | |
+| msg | string (base64 encoded Binary) | `msg` included when calling the recipient contract's BatchReceiveNft (or ReceiveNft) | yes | nothing |
+| memo | string | `memo` for the tx that is only viewable by addresses involved (recipient, sender, previous owner) | yes | nothing |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "send_nft": {
+ "status": "success"
+ }
+}
+```
+### ReceiverInfo
+The optional ReceiverInfo object may be used to provide the code hash of the contract receiving tokens from either [SendNft](#sendnft) or [BatchSendNft](#batchsend). It may also optionally indicate whether the recipient contract implements [BatchReceiveNft](#batchreceivenft) in addition to [ReceiveNft](#receivenft). If the `also_implements_batch_receive_nft` field is not provided, it defaults to `false`.
+```
+{
+ "recipient_code_hash": "code_hash_of_the_recipient_contract",
+ "also_implements_batch_receive_nft": true | false,
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------------------------|--------|------------------------------------------------------------------------------------------------------------------------|----------|------------------|
+| recipient_code_hash | string | Code hash of the recipient contract | no | |
+| also_implements_batch_receive_nft | bool | True if the recipient contract implements [BatchReceiveNft](#batchreceivenft) in addition to [ReceiveNft](#receivenft) | yes | false |
+
+## BatchSendNft
+BatchSendNft is used to perform multiple token transfers, and then call the recipient contracts' [BatchReceiveNft](#batchreceivenft) (or [ReceiveNft](#receivenft)) if they have registered their receiver interface with the NFT contract or if their [ReceiverInfo](#receiverinfo) is provided. The message sender may specify a list of tokens to send to one recipient address in each [Send](#send) object, and any `memo` or `msg` provided will be applied to every token transferred in that one `Send` object. If the list of transferred tokens belonged to multiple previous owners, a separate BatchReceiveNft callback will be performed for each of the previous owners. If the contract only implements ReceiveNft, one ReceiveNft will be performed for every sent token. Therefore it is highly recommended to implement BatchReceiveNft if there is the possibility of being sent multiple tokens at one time. This will significantly reduce gas costs.
+
+The message sender may provide multiple [Send](#send) objects to perform sends to multiple addresses, providing a different `memo` and `msg` for each address if desired. Each individual transfer of a token will show separately in transaction histories. The message sender must have permission to transfer all the tokens listed (either by being the owner or being granted transfer approval) and every token ID must be valid. A contract may use the [VerifyTransferApproval](#verifyapproval) query to verify that it has permission to transfer all the tokens. If the message sender does not have permission to transfer any one of the listed tokens, the entire message will fail (no tokens will be transferred) and the error will provide the ID of the first token encountered in which the sender does not have the required permission. If any token transfer involves a recipient address that is the same as its current owner, the contract will throw an error. Any token that is transferred to a new owner will have its single-token approvals cleared.
+If any BatchReceiveNft (or ReceiveNft) callback fails, the entire transaction will be reverted (even the transfers will not take place).
+
+This implementation will throw an error if trying to send a [SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) non-transferable token.
+
+##### Request
+```
+{
+ "batch_send_nft": {
+ "sends": [
+ {
+ "contract": "address_receiving_the_tokens",
+ "receiver_info": {
+ "recipient_code_hash": "code_hash_of_the_recipient_contract",
+ "also_implements_batch_receive_nft": true | false,
+ },
+ "token_ids": [
+ "list", "of", "token", "IDs", "to", "transfer", "..."
+ ],
+ "msg": "optional_base64_encoded_Binary_message_sent_with_every_BatchReceiveNft_callback_made_for_this_one_Send_object",
+ "memo": "optional_memo_applied_to_the_transfer_tx_for_every_token_listed_in_this_Send_object"
+ },
+ {
+ "...": "..."
+ }
+ ],
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------|---------------------------------------|--------------------------------------------------------------------------------------------------------------------|----------|------------------|
+| sends | array of [Send (see below)](#send) | List of `Send` objects to process | no | |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "batch_send_nft": {
+ "status": "success"
+ }
+}
+```
+
+### Send
+The Send object provides a list of tokens to transfer to one recipient address, optionally provides the [ReceiverInfo](#receiverinfo) of the recipient contract, as well as an optional `memo` that would be included with every logged token transfer, and an optional `msg` that would be included with every [BatchReceiveNft](#batchreceivenft) or [ReceiveNft](#receivenft) callback made as a result of this Send object. While Send keeps the `contract` field name in order be consistent with CW-721 specification, Secret Network does not have the same limitations as Cosmos, and it is possible to use [BatchSendNft](#batchsend) to transfer token ownership to a personal address (not a contract) or to a contract that does not implement any [Receiver Interface](#receiver).
+```
+{
+ "contract": "address_receiving_the_tokens",
+ "receiver_info": {
+ "recipient_code_hash": "code_hash_of_the_recipient_contract",
+ "also_implements_batch_receive_nft": true | false,
+ },
+ "token_ids": [
+ "list", "of", "token", "IDs", "to", "transfer", "..."
+ ],
+ "msg": "optional_base64_encoded_Binary_message_sent_with_every_BatchReceiveNft_callback_made_for_this_one_Send_object",
+ "memo": "optional_memo_applied_to_the_transfer_tx_for_every_token_listed_in_this_Send_object"
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|---------------|-------------------------------------------|--------------------------------------------------------------------------------------------------------|----------|------------------|
+| contract | string (Addr) | Address receiving the token | no | |
+| receiver_info | [ReceiverInfo (see above)](#receiverinfo) | Code hash and BatchReceiveNft implementation status of the recipient contract | yes | nothing |
+| token_ids | array of string | List of token IDs to send to the recipient | no | |
+| msg | string (base64 encoded Binary) | `msg` included when calling the recipient contract's BatchReceiveNft (or ReceiveNft) | yes | nothing |
+| memo | string | `memo` for the tx that is only viewable by addresses involved (recipient, sender, previous owner) | yes | nothing |
+
+## BurnNft
+BurnNft is used to burn a single token, providing an optional `memo` to include in the burn's transaction history if desired. If the contract has not enabled burn functionality using the init configuration `enable_burn`, BurnNft will result in an error, unless the token being burned is a [SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) non-transferable token. This is because an owner should always be able to dispose of an unwanted, non-transferable token. Only the token owner and anyone else with valid transfer approval may burn this token.
+
+##### Request
+```
+{
+ "burn_nft": {
+ "token_id": "ID_of_the_token_to_burn",
+ "memo": "optional_memo_for_the_burn_tx",
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------|--------------------|-------------------------------------------------------------------------------------------------------------------------------------|----------|------------------|
+| token_id | string | Identifier of the token to burn | no | |
+| memo | string | `memo` for the burn tx that is only viewable by addresses involved in the burn (message sender and previous owner if different) | yes | nothing |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "burn_nft": {
+ "status": "success"
+ }
+}
+```
+
+## BatchBurnNft
+BatchBurnNft is used to burn multiple tokens. The message sender may specify a list of tokens to burn in each [Burn](#burn) object, and any `memo` provided will be applied to every token burned in that one `Burn` object. The message sender will usually list every token to be burned in one `Burn` object, but if a different `memo` is needed for different tokens being burned, multiple `Burn` objects may be listed. Each individual burning of a token will show separately in transaction histories. The message sender must have permission to transfer/burn all the tokens listed (either by being the owner or being granted transfer approval). A contract may use the [VerifyTransferApproval](#verifyapproval) query to verify that it has permission to transfer/burn all the tokens. If the message sender does not have permission to transfer/burn any one of the listed tokens, the entire message will fail (no tokens will be burned) and the error will provide the ID of the first token encountered in which the sender does not have the required permission.
+
+A [SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) non-transferable token can always be burned even if burn functionality has been disabled using the init configuration. This is because an owner should always be able to dispose of an unwanted, non-transferable token.
+
+##### Request
+```
+{
+ "batch_burn_nft": {
+ "burns": [
+ {
+ "token_ids": [
+ "list", "of", "token", "IDs", "to", "burn"
+ ],
+ "memo": "optional_memo_applied_to_the_burn_tx_for_every_token_listed_in_this_Burn_object"
+ },
+ {
+ "...": "..."
+ }
+ ],
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------|---------------------------------------|--------------------------------------------------------------------------------------------------------------------|----------|------------------|
+| burns | array of [Burn (see below)](#burn) | List of `Burn` objects to process | no | |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "batch_burn_nft": {
+ "status": "success"
+ }
+}
+```
+
+### Burn
+The Burn object provides a list of tokens to burn, as well as an optional `memo` that would be included with every token burn transaction history.
+```
+{
+ "token_ids": [
+ "list", "of", "token", "IDs", "to", "burn", "..."
+ ],
+ "memo": "optional_memo_applied_to_the_burn_tx_for_every_token_listed_in_this_Burn_object"
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------|-----------------|----------------------------------------------------------------------------------------------------------------------------------|----------|------------------|
+| token_ids | array of string | List of token IDs to burn | no | |
+| memo | string | `memo` for the burn txs that is only viewable by addresses involved in the burn (message sender and previous owner if different) | yes | nothing |
+
+## CreateViewingKey
+CreateViewingKey is used to generate a random viewing key to be used to authenticate account-specific queries. The `entropy` field is a client-supplied string used as part of the entropy supplied to the rng that creates the viewing key.
+
+##### Request
+```
+{
+ "create_viewing_key": {
+ "entropy": "string_used_as_part_of_the_entropy_supplied_to_the_rng",
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------|--------|--------------------------------------------------------------------------------------------------------------------|----------|------------------|
+| entropy | string | String used as part of the entropy supplied to the rng that generates the random viewing key | no | |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "viewing_key": {
+ "key": "the_created_viewing_key"
+ }
+}
+```
+
+## SetViewingKey
+SetViewingKey is used to set the viewing key to a predefined string. It will replace any key that currently exists. It would be best for users to call CreateViewingKey to ensure a strong key, but this function is provided so that contracts can also utilize viewing keys.
+
+##### Request
+```
+{
+ "set_viewing_key": {
+ "key": "the_new_viewing_key",
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------|--------|--------------------------------------------------------------------------------------------------------------------|----------|------------------|
+| key | string | The new viewing key for the message sender | no | |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "viewing_key": {
+ "key": "the_message_sender's_viewing_key"
+ }
+}
+```
+
+## AddMinters
+AddMinters will add the provided addresses to the list of authorized minters. This can only be called by the admin address.
+
+##### Request
+```
+{
+ "add_minters": {
+ "minters": [
+ "list", "of", "addresses", "to", "add", "to", "the", "list", "of", "minters", "..."
+ ],
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|---------|-----------------------------|--------------------------------------------------------------------------------------------------------------------|----------|------------------|
+| minters | array of string (Addr) | The list of addresses to add to the list of authorized minters | no | |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "add_minters": {
+ "status": "success"
+ }
+}
+```
+
+## RemoveMinters
+RemoveMinters will remove the provided addresses from the list of authorized minters. This can only be called by the admin address.
+
+##### Request
+```
+{
+ "remove_minters": {
+ "minters": [
+ "list", "of", "addresses", "to", "remove", "from", "the", "list", "of", "minters", "..."
+ ],
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|---------|-----------------------------|--------------------------------------------------------------------------------------------------------------------|----------|------------------|
+| minters | array of string (Addr) | The list of addresses to remove from the list of authorized minters | no | |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "remove_minters": {
+ "status": "success"
+ }
+}
+```
+
+## SetMinters
+SetMinters will precisely define the list of authorized minters. This can only be called by the admin address.
+
+##### Request
+```
+{
+ "set_minters": {
+ "minters": [
+ "list", "of", "addresses", "that", "have", "minting", "authority", "..."
+ ],
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|---------|-----------------------------|---------------------------------------------------------------------------------------------|----------|------------------|
+| minters | array of string (Addr) | The list of addresses to are allowed to mint | no | |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "set_minters": {
+ "status": "success"
+ }
+}
+```
+
+## SetContractStatus
+SetContractStatus allows the contract admin to define which messages the contract will execute. This can only be called by the admin address.
+
+##### Request
+```
+{
+ "set_contract_status": {
+ "level": "normal" | "stop_transactions" | "stop_all",
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|---------|--------------------------------------------------|---------------------------------------------------------------------------------------------|----------|------------------|
+| level | [ContractStatus (see below)](#contractstatus) | The level that defines which messages the contract will execute | no | |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "set_contract_status": {
+ "status": "success"
+ }
+}
+```
+### ContractStatus
+ContractStatus indicates which messages the contract will execute. The possible values are:
+* `"normal"` - the contract will execute all messages
+* `"stop_transactions"` - the contract will not allow any minting, burning, sending, or transferring of tokens
+* `"stop_all"` - the contract will only execute a SetContractStatus message
+
+## ChangeAdmin
+ChangeAdmin will allow the current admin to transfer admin privileges to another address (which will be the only admin address). This can only be called by the current admin address.
+
+##### Request
+```
+{
+ "change_admin": {
+ "address": "address_of_the_new_contract_admin",
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|---------|--------------------|---------------------------------------------------------------------------------------------|----------|------------------|
+| address | string (Addr) | Address of the new contract admin | no | |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "change_admin": {
+ "status": "success"
+ }
+}
+```
+
+## RegisterReceiveNft
+A contract will use RegisterReceiveNft to notify the NFT contract that it implements ReceiveNft and possibly also BatchReceiveNft [(see below)](#receiver). This enables the NFT contract to call the registered contract whenever it is Sent a token (or tokens). In order to comply with CW-721, ReceiveNft only informs the recipient contract that it has been sent a single token, and it only informs the recipient contract who the token's previous owner was, not who sent the token (which may be different addresses) despite calling the previous owner `sender` ([see below](#cwsender)). BatchReceiveNft, on the other hand, can be used to inform a contract that it was sent multiple tokens, and notifies the recipient of both, the token's previous owner and the sender. If a contract implements BatchReceiveNft, the NFT contract will always call BatchReceiveNft even if there is only one token being sent, in which case the `token_ids` array will only have one element.
+
+##### Request
+```
+{
+ "register_receive_nft": {
+ "code_hash": "code_hash_of_the_contract_implementing_a_receiver_interface",
+ "also_implements_batch_receive_nft": true | false,
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------------------------|--------|----------------------------------------------------------------------------------------------------------------------------|----------|------------------|
+| code_hash | string | A 32-byte hex encoded string, with the code hash of the message sender, which is a contract that implements a receiver | no | |
+| also_implements_batch_receive_nft | bool | true if the message sender contract also implements BatchReceiveNft so it can be informed that it was sent a list of tokens| yes | false |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "register_receive_nft": {
+ "status": "success"
+ }
+}
+```
+
+## RevokePermit
+RevokePermit allows a user to disable the use of a permit for authenticated queries.
+
+##### Request
+```
+{
+ "revoke_permit": {
+ "permit_name": "name_of_the_permit_that_is_no_longer_valid",
+ "padding": "optional_ignored_string_that_can_be_used_to_maintain_constant_message_length"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------------------------|--------|---------------------------------------------------------------------------|----------|------------------|
+| permit_name | string | name of the permit that is no longer valid | no | |
+| padding | string | An ignored string that can be used to maintain constant message length | yes | nothing |
+
+##### Response
+```
+{
+ "revoke_permit": {
+ "status": "success"
+ }
+}
+```
+
+# Queries
+Queries are off-chain requests, that are not cryptographically validated; therefore, this contract utilizes viewing keys to authenticate address-specific queries. It makes viewing key validation resource intensive in order to combat offline brute-force attempts to guess viewing keys. Also, even if a user has not set a viewing key, it will perform the same resource intensive processing to prevent an attacker from knowing that a key has not been set and provides the same error response whether there is no viewing key or if the input key does not match.
+
+Any query that inquires about a specific token will return an error if the input token ID does not exist. If the token supply is public, the error will indicate that the token does not exist. If the token supply is private, the query will return the same error response whether the token does not exist or the querier does not have permission to view the requested information.
+
+One should be aware that the current blockheight and time is not available to a query on Secret Network at this moment, but there are plans to make the BlockInfo available to queries in a future hardfork. To get around this limitation, the contract saves the BlockInfo every time a message is executed, and uses the blockheight and time of the last message execution to check viewing approval expiration during a query. Therefore it is possible that a whitelisted address may be able to view the owner or metadata of a token past its approval expiration if no one executed any contract message since before the expiration. However, because transferring/burning a token is executing a message, it does have the current blockheight and time available and can enforce exact expiration.
+
+## ContractInfo
+ContractInfo returns the contract's name and symbol. This query is not authenticated.
+
+##### Request
+```
+{
+ "contract_info": {}
+}
+```
+##### Response
+```
+{
+ "contract_info": {
+ "name": "contract_name",
+ "symbol": "contract_symbol"
+ }
+}
+```
+
+## ContractConfig
+ContractConfig returns the configuration values that were selected when the contract was instantiated. See [Config](#config) for an explanation of the configuration options. ContractConfig also returns whether non-transferable tokens and token subtypes are implemented. This query is not authenticated.
+
+##### Request
+```
+{
+ "contract_config": {}
+}
+```
+##### Response
+```
+{
+ "contract_config": {
+ “token_supply_is_public”: true | false,
+ “owner_is_public”: true | false,
+ “sealed_metadata_is_enabled”: true | false,
+ “unwrapped_metadata_is_private”: true | false,
+ “minter_may_update_metadata”: true | false,
+ “owner_may_update_metadata”: true | false,
+ “burn_is_enabled”: true | false,
+ "implements_non_transferable_tokens": true | false,
+ "implements_token_subtype": true | false
+ }
+}
+```
+| Name | Type | Description | Optional |
+|------------------------------------|------|--------------------------------------------------------------------------------------------|----------|
+| token_supply_is_public | bool | True if token IDs and the number of tokens controlled by the contract are public | no |
+| owner_is_public | bool | True if newly minted coins have public ownership as default | no |
+| sealed_metadata_is_enabled | bool | True if newly minted coins have sealed metadata | no |
+| unwrapped_metadata_is_private | bool | True if sealed metadata remains private after unwrapping | no |
+| minter_may_update_metadata | bool | True if authorized minters may alter a token's metadata | no |
+| owner_may_update_metadata | bool | True if a token owner may alter its metadata | no |
+| burn_is_enabled | bool | True if burn functionality is enabled | no |
+| implements_non_transferable_tokens | bool | True if the contract implements non-transferable tokens | no |
+| implements_token_subtype | bool | True if the contract implements token subtypes | no |
+
+## Minters
+Minters returns the list of addresses that are authorized to mint tokens. This query is not authenticated.
+
+##### Request
+```
+{
+ "minters": {}
+}
+```
+##### Response
+```
+{
+ "minters": {
+ “minters”: [
+ "list", "of", "authorized", "minters", "..."
+ ]
+ }
+}
+```
+| Name | Type | Description | Optional |
+|---------|-----------------------------|------------------------------------------|----------|
+| minters | array of string (Addr) | List of addresses with minting authority | no |
+
+## RegisteredCodeHash
+RegisteredCodeHash will display the code hash of the specified contract if it has registered its [receiver interface](#receiver) and will indicate whether the contract implements [BatchReceiveNft](#batchreceivenft).
+
+##### Request
+```
+{
+ "registered_code_hash": {
+ "contract": "address_of_the_contract_whose_registration_is_being_queried"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|----------|--------------------|-----------------------------------------------------------------------|----------|------------------|
+| contract | string (Addr) | The address of the contract whose registration info is being queried | no | |
+
+##### Response
+```
+{
+ "registered_code_hash": {
+ "code_hash": "code_hash_of_the_registered_contract",
+ "also_implements_batch_receive_nft": true | false
+ }
+}
+```
+| Name | Type | Description | Optional |
+|------------------------------------|--------|------------------------------------------------------------------------------|----------|
+| code_hash | string | A 32-byte hex encoded string, with the code hash of the registered contract | yes |
+| also_implements_batch_receive_nft | bool | True if the registered contract also implements BatchReceiveNft | no |
+
+## NumTokens
+NumTokens returns the number of tokens controlled by the contract. If the contract's token supply is private, only an authenticated minter's address will be allowed to perform this query.
+
+##### Request
+```
+{
+ "num_tokens": {
+ "viewer": {
+ "address": "address_of_the_querier_if_supplying_optional_ViewerInfo",
+ "viewing_key": "viewer's_key_if_supplying_optional_ViewerInfo"
+ }
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|--------|---------------------------------------|---------------------------------------------------------------------|----------|------------------|
+| viewer | [ViewerInfo (see below)](#viewerinfo) | The address and viewing key performing this query | yes | nothing |
+
+##### Response
+```
+{
+ "num_tokens": {
+ "count": 99999
+ }
+}
+```
+| Name | Type | Description | Optional |
+|---------|--------------|----------------------------------------------|----------|
+| count | number (u32) | Number of tokens controlled by this contract | no |
+
+### ViewerInfo
+The ViewerInfo object provides the address and viewing key of the querier. It is optionally provided in queries where public responses and address-specific responses will differ.
+```
+{
+ "address": "address_of_the_querier_if_supplying_optional_ViewerInfo",
+ "viewing_key": "viewer's_key_if_supplying_optional_ViewerInfo"
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-------------|--------------------|-----------------------------------------------------------------------------------------------------------------------|----------|------------------|
+| address | string (Addr) | Address performing the query | no | |
+| viewing_key | string | The querying address' viewing key | no | |
+
+## AllTokens
+AllTokens returns an optionally paginated list of all the token IDs controlled by the contract. If the contract's token supply is private, only an authenticated minter's address will be allowed to perform this query. When paginating, supply the last token ID received in a response as the `start_after` token ID of the next query to continue listing where the previous query stopped.
+
+##### Request
+```
+{
+ "all_tokens": {
+ "viewer": {
+ "address": "address_of_the_querier_if_supplying_optional_ViewerInfo",
+ "viewing_key": "viewer's_key_if_supplying_optional_ViewerInfo"
+ },
+ "start_after": "optionally_display_only_token_ids_that_come_after_this_one_in_the_list",
+ "limit": 10
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-------------|---------------------------------------|------------------------------------------------------------------------------------------|----------|------------------|
+| viewer | [ViewerInfo (see above)](#viewerinfo) | The address and viewing key performing this query | yes | nothing |
+| start_after | string | Results will only list token IDs that come after this token ID in the list | yes | nothing |
+| limit | number (u32) | Number of token IDs to return | yes | 300 |
+
+##### Response
+```
+{
+ "token_list": {
+ "tokens": [
+ "list", "of", "token", "IDs", "controlled", "by", "the", "contract", "..."
+ ]
+ }
+}
+```
+| Name | Type | Description | Optional |
+|---------|-----------------|----------------------------------------------------------------------|----------|
+| tokens | array of string | A list of token IDs controlled by this contract | no |
+
+## IsUnwrapped
+IsUnwrapped indicates whether the token has been unwrapped. If [sealed metadata](#enablesealed) is not enabled, all tokens are considered to be unwrapped. This query is not authenticated.
+
+##### Request
+```
+{
+ "is_unwrapped": {
+ "token_id": "ID_of_the_token_whose_unwrapped_status_is_being_queried"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-------------|--------|------------------------------------------------------------------------------------------|----------|------------------|
+| token_id | string | The ID of the token whose unwrapped status is being queried | no | |
+
+##### Response
+```
+{
+ "is_unwrapped": {
+ "token_is_unwrapped": true | false
+ }
+}
+```
+| Name | Type | Description | Optional |
+|---------------------|------|---------------------------------------------------------------------------------------|----------|
+| token_is_unwrapped | bool | True if the token is unwrapped (or [sealed metadata](#enablesealed) is not enabled) | no |
+
+## IsTransferable
+IsTransferable is a [SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) query that indicates whether the token is transferable. This query is not authenticated.
+
+##### Request
+```
+{
+ "is_transferable": {
+ "token_id": "ID_of_the_token_whose_transferability_is_being_queried"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-------------|--------|------------------------------------------------------------------------------------------|----------|------------------|
+| token_id | string | The ID of the token whose transferability is being queried | no | |
+
+##### Response
+```
+{
+ "is_transferable": {
+ "token_is_transferable": true | false
+ }
+}
+```
+| Name | Type | Description | Optional |
+|------------------------|------|-------------------------------------------------------------------------|----------|
+| token_is_transferable | bool | True if the token is transferable | no |
+
+## OwnerOf
+OwnerOf returns the owner of the specified token if the querier is the owner or has been granted permission to view the owner. If the querier is the owner, OwnerOf will also display all the addresses that have been given transfer permission. The transfer approval list is provided as part of CW-721 compliance; however, the token owner is advised to use [NftDossier](#nftdossier) for a more complete list that includes view_owner and view_private_metadata approvals (which CW-721 is not capable of keeping private). If no [viewer](#viewerinfo) is provided, OwnerOf will only display the owner if ownership is public for this token.
+
+##### Request
+```
+{
+ "owner_of": {
+ "token_id": "ID_of_the_token_being_queried",
+ "viewer": {
+ "address": "address_of_the_querier_if_supplying_optional_ViewerInfo",
+ "viewing_key": "viewer's_key_if_supplying_optional_ViewerInfo"
+ },
+ "include_expired": true | false
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------|---------------------------------------|-----------------------------------------------------------------------|----------|------------------|
+| token_id | string | ID of the token being queried | no | |
+| viewer | [ViewerInfo (see above)](#viewerinfo) | The address and viewing key performing this query | yes | nothing |
+| include_expired | bool | True if expired transfer approvals should be included in the response | yes | false |
+
+##### Response
+```
+{
+ "owner_of": {
+ "owner": "address_of_the_token_owner",
+ "approvals": [
+ {
+ "spender": "address_with_transfer_approval",
+ "expires": "never" | {"at_height": 999999} | {"at_time":999999}
+ },
+ {
+ "...": "..."
+ }
+ ]
+ }
+}
+```
+| Name | Type | Description | Optional |
+|-----------|------------------------------------------------------|----------------------------------------------------------|----------|
+| owner | string (Addr) | Address of the token's owner | no |
+| approvals | array of [Cw721Approval (see below)](#cw721approval) | List of approvals to transfer this token | no |
+
+### Cw721Approval
+The Cw721Approval object is used to display CW-721-style approvals which are limited to only permission to transfer, as CW-721 does not enable ownership or metadata privacy.
+```
+{
+ "spender": "address_with_transfer_approval",
+ "expires": "never" | {"at_height": 999999} | {"at_time":999999}
+}
+```
+| Name | Type | Description | Optional |
+|---------|---------------------------------------|---------------------------------------------------------------------------------|----------|
+| spender | string (Addr) | Address whitelisted to transfer a token | no |
+| expires | [Expiration (see above)](#expiration) | The expiration of this transfer approval. Can be a blockheight, time, or never | no |
+
+## NftInfo
+NftInfo returns the public metadata of a token. It follows CW-721 specification, which is based on ERC-721 Metadata JSON Schema. At most, one of the fields `token_uri` OR `extension` will be defined.
+
+##### Request
+```
+{
+ "nft_info": {
+ "token_id": "ID_of_the_token_being_queried"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------|---------------------------------------|-----------------------------------------------------------------------|----------|------------------|
+| token_id | string | ID of the token being queried | no | |
+
+##### Response
+```
+{
+ "nft_info": {
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+ }
+}
+```
+| Name | Type | Description | Optional |
+|-----------|-------------------------------------|--------------------------------------------------------------------------------------|----------|
+| token_uri | string | Uri pointing to off-chain JSON metadata | yes |
+| extension | [Extension (see above)](#extension) | Data structure defining on-chain metadata | yes |
+At most, one of the fields `token_uri` OR `extension` will be defined.
+
+## AllNftInfo
+AllNftInfo displays the result of both [OwnerOf](#ownerof) and [NftInfo](#nftinfo) in a single query. This is provided for CW-721 compliance, but for more complete information about a token, use [NftDossier](#nftdossier), which will include private metadata and view_owner and view_private_metadata approvals if the querier is permitted to view this information.
+
+##### Request
+```
+{
+ "all_nft_info": {
+ "token_id": "ID_of_the_token_being_queried",
+ "viewer": {
+ "address": "address_of_the_querier_if_supplying_optional_ViewerInfo",
+ "viewing_key": "viewer's_key_if_supplying_optional_ViewerInfo"
+ },
+ "include_expired": true | false
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------|---------------------------------------|-----------------------------------------------------------------------|----------|------------------|
+| token_id | string | ID of the token being queried | no | |
+| viewer | [ViewerInfo (see above)](#viewerinfo) | The address and viewing key performing this query | yes | nothing |
+| include_expired | bool | True if expired transfer approvals should be included in the response | yes | false |
+
+##### Response
+```
+{
+ "all_nft_info": {
+ "access": {
+ "owner": "address_of_the_token_owner",
+ "approvals": [
+ {
+ "spender": "address_with_transfer_approval",
+ "expires": "never" | {"at_height": 999999} | {"at_time":999999}
+ },
+ {
+ "...": "..."
+ }
+ ]
+ },
+ "info": {
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+ }
+ }
+}
+```
+| Name | Type | Description | Optional |
+|-------------|---------------------------------------------------|-----------------------------------------------------------------------------|----------|
+| access | [Cw721OwnerOfResponse (see below)](#cw721ownerof) | The token's owner and its transfer approvals if permitted to view this info | no |
+| info | [Metadata (see above)](#metadata) | The token's public metadata | yes |
+
+### Cw721OwnerOfResponse
+The Cw721OwnerOfResponse object is used to display a token's owner if the querier has view_owner permission, and the token's transfer approvals if the querier is the token's owner.
+```
+{
+ "owner": "address_of_the_token_owner",
+ "approvals": [
+ {
+ "spender": "address_with_transfer_approval",
+ "expires": "never" | {"at_height": 999999} | {"at_time":999999}
+ },
+ {
+ "...": "..."
+ }
+ ]
+}
+```
+| Name | Type | Description | Optional |
+|-----------|------------------------------------------------------|----------------------------------------------------------|----------|
+| owner | string (Addr) | Address of the token's owner | yes |
+| approvals | array of [Cw721Approval (see above)](#cw721approval) | List of approvals to transfer this token | no |
+
+## PrivateMetadata
+PrivateMetadata returns the private metadata of a token if the querier is permitted to view it. It follows CW-721 metadata specification, which is based on ERC-721 Metadata JSON Schema. At most, one of the fields `token_uri` OR `extension` will be defined. If the metadata is [sealed](#enablesealed), no one is permitted to view it until it has been unwrapped with [Reveal](#reveal). If no [viewer](#viewerinfo) is provided, PrivateMetadata will only display the private metadata if the private metadata is public for this token.
+
+##### Request
+```
+{
+ "private_metadata": {
+ "token_id": "ID_of_the_token_being_queried",
+ "viewer": {
+ "address": "address_of_the_querier_if_supplying_optional_ViewerInfo",
+ "viewing_key": "viewer's_key_if_supplying_optional_ViewerInfo"
+ },
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------|---------------------------------------|-----------------------------------------------------------------------|----------|------------------|
+| token_id | string | ID of the token being queried | no | |
+| viewer | [ViewerInfo (see above)](#viewerinfo) | The address and viewing key performing this query | yes | nothing |
+
+##### Response
+```
+{
+ "private_metadata": {
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+ }
+}
+```
+| Name | Type | Description | Optional |
+|-----------|-------------------------------------|--------------------------------------------------------------------------------------|----------|
+| token_uri | string | Uri pointing to off-chain JSON metadata | yes |
+| extension | [Extension (see above)](#extension) | Data structure defining on-chain metadata | yes |
+At most, one of the fields `token_uri` OR `extension` will be defined.
+
+## NftDossier
+NftDossier returns all the information about a token that the viewer is permitted to view. If no [viewer](#viewerinfo) is provided, NftDossier will only display the information that has been made public. The response may include the owner, the public metadata, the private metadata, the reason the private metadata is not viewable, the royalty information, the mint run information, whether the token is transferable, whether ownership is public, whether the private metadata is public, and (if the querier is the owner,) the approvals for this token as well as the inventory-wide approvals for the owner. This implementation will only display a token's royalty recipient addresses if the querier has permission to transfer the token.
+
+[SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) adds a `transferable` field to the NftDossier response.
+
+SNIP-723 (specification to be written) adds an `unwrapped` field which is false if private metadata for this token is sealed.
+
+##### Request
+```
+{
+ "nft_dossier": {
+ "token_id": "ID_of_the_token_being_queried",
+ "viewer": {
+ "address": "address_of_the_querier_if_supplying_optional_ViewerInfo",
+ "viewing_key": "viewer's_key_if_supplying_optional_ViewerInfo"
+ },
+ "include_expired": true | false
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------|---------------------------------------|-----------------------------------------------------------------------|----------|------------------|
+| token_id | string | ID of the token being queried | no | |
+| viewer | [ViewerInfo (see above)](#viewerinfo) | The address and viewing key performing this query | yes | nothing |
+| include_expired | bool | True if expired approvals should be included in the response | yes | false |
+
+##### Response
+```
+{
+ "nft_dossier": {
+ "owner": "address_of_the_token_owner",
+ "public_metadata": {
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+ },
+ "private_metadata": {
+ "token_uri": "optional_uri_pointing_to_off-chain_JSON_metadata",
+ "extension": {
+ "...": "..."
+ }
+ },
+ "display_private_metadata_error": "optional_error_describing_why_private_metadata_is_not_viewable_if_applicable",
+ "royalty_info": {
+ "decimal_places_in_rates": 4,
+ "royalties": [
+ {
+ "recipient": "optional_address_that_should_be_paid_this_royalty",
+ "rate": 100,
+ },
+ {
+ "...": "..."
+ }
+ ],
+ },
+ "mint_run_info": {
+ "collection_creator": "optional_address_that_instantiated_this_contract",
+ "token_creator": "optional_address_that_minted_this_token",
+ "time_of_minting": 999999,
+ "mint_run": 3,
+ "serial_number": 67,
+ "quantity_minted_this_run": 1000,
+ },
+ "transferable": true | false,
+ "unwrapped": true | false,
+ "owner_is_public": true | false,
+ "public_ownership_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "private_metadata_is_public": true | false,
+ "private_metadata_is_public_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "token_approvals": [
+ {
+ "address": "whitelisted_address",
+ "view_owner_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "view_private_metadata_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "transfer_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ },
+ {
+ "...": "..."
+ }
+ ],
+ "inventory_approvals": [
+ {
+ "address": "whitelisted_address",
+ "view_owner_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "view_private_metadata_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "transfer_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ },
+ {
+ "...": "..."
+ }
+ ]
+ }
+}
+```
+| Name | Type | Description | Optional |
+|---------------------------------------|-------------------------------------------------------|----------------------------------------------------------------------------------------|----------|
+| owner | string (Addr) | Address of the token's owner | yes |
+| public_metadata | [Metadata (see above)](#metadata) | The token's public metadata | yes |
+| private_metadata | [Metadata (see above)](#metadata) | The token's private metadata | yes |
+| display_private_metadata_error | string | If the private metadata is not displayed, the corresponding error message | yes |
+| royalty_info | [RoyaltyInfo (see above)](#royaltyinfo) | The token's RoyaltyInfo | yes |
+| mint_run_info | [MintRunInfo (see below)](#mintruninfo) | The token's MintRunInfo | yes |
+| transferable | bool | True if this token is transferable | no* |
+| unwrapped | bool | False if this token's private metadata is sealed | no* |
+| owner_is_public | bool | True if ownership is public for this token | no |
+| public_ownership_expiration | [Expiration (see above)](#expiration) | When public ownership expires for this token. Can be a blockheight, time, or never | yes |
+| private_metadata_is_public | bool | True if private metadata is public for this token | no |
+| private_metadata_is_public_expiration | [Expiration (see above)](#expiration) | When public display of private metadata expires. Can be a blockheight, time, or never | yes |
+| token_approvals | array of [Snip721Approval (see below)](#snipapproval) | List of approvals for this token | yes |
+| inventory_approvals | array of [Snip721Approval (see below)](#snipapproval) | List of inventory-wide approvals for the token's owner | yes |
+
+The `transferable` field is mandatory for [SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) compliant contracts, but because SNIP-722 is an optional extension to SNIP-721, any NftDossier response that does not include the field can be considered to come from a contract that only implements transferable tokens (considered equivalent to `transferable` = true).
+The `unwrapped` field is mandatory for SNIP-723 (specification to be written) compliant contracts, but because SNIP-723 is an optional extension to SNIP-721, an NftDossier response might not include the field. In this case, the `display_private_metadata_error` field might indicate that the private metadata is sealed if the querier has permission to normally view private metadata. Or an [IsUnwrapped](#IsUnwrapped) query may be performed to learn the token's sealed status.
+
+### MintRunInfo
+MintRunInfo contains information about the minting of this token.
+```
+{
+ "collection_creator": "optional_address_that_instantiated_this_contract",
+ "token_creator": "optional_address_that_minted_this_token",
+ "time_of_minting": 999999,
+ "mint_run": 3,
+ "serial_number": 67,
+ "quantity_minted_this_run": 1000,
+}
+```
+| Name | Type | Description | Optional |
+|--------------------------|--------------------|-----------------------------------------------------------------------------------------------------------|----------|
+| collection_creator | string (Addr) | The address that instantiated this contract | yes |
+| token_creator | string (Addr) | The address that minted this token | yes |
+| time_of_minting | number (u64) | The number of seconds since 01/01/1970 that this token was minted | yes |
+| mint_run | number (u32) | The mint run this token was minted in. This represents batches of NFTs released at the same time | yes |
+| serial_number | number (u32) | The serial number of this token | yes |
+| quantity_minted_this_run | number (u32) | The number of tokens minted in this mint run. | yes |
+
+A mint run is a group of NFTs released at the same time. So, for example, if a creator decided to make 100 copies, they would all be part of mint run number 1. If they sell well and the creator wants to rerelease that NFT, he could make 100 more copies that would all be part of mint run number 2. The combination of mint_run, serial_number, and quantity_minted_this_run is used to indicate, for example, that this token was number 67 of 1000 minted in mint run number 3.
+
+### Snip721Approval
+The Snip721Approval object is used to display all the approvals (and their expirations) that have been granted to a whitelisted address. The expiration field will be null if the whitelisted address does not have that corresponding permission type.
+```
+{
+ "address": "whitelisted_address",
+ "view_owner_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "view_private_metadata_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "transfer_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+}
+```
+| Name | Type | Description | Optional |
+|----------------------------------|---------------------------------------|---------------------------------------------------------------------------------------------|----------|
+| address | string (Addr) | The whitelisted address | no |
+| view_owner_expiration | [Expiration (see above)](#expiration) | The expiration for view_owner permission. Can be a blockheight, time, or never | yes |
+| view_private_metadata_expiration | [Expiration (see above)](#expiration) | The expiration for view__private_metadata permission. Can be a blockheight, time, or never | yes |
+| transfer_expiration | [Expiration (see above)](#expiration) | The expiration for transfer permission. Can be a blockheight, time, or never | yes |
+
+## RoyaltyInfo (query)
+If a `token_id` is provided in the request, RoyaltyInfo returns the royalty information for that token. This implementation will only display a token's royalty recipient addresses if the querier has permission to transfer the token. If no `token_id` is requested, RoyaltyInfo displays the default royalty information for the contract. This implementation will only display the contract's default royalty recipient addresses if the querier is an authorized minter.
+
+##### Request
+```
+{
+ "royalty_info": {
+ "token_id": "optional_ID_of_the_token_being_queried",
+ "viewer": {
+ "address": "address_of_the_querier_if_supplying_optional_ViewerInfo",
+ "viewing_key": "viewer's_key_if_supplying_optional_ViewerInfo"
+ },
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------|---------------------------------------|-----------------------------------------------------------------------|----------|--------------------------------------|
+| token_id | string | ID of the token being queried | yes | query contract's default RoyaltyInfo |
+| viewer | [ViewerInfo (see above)](#viewerinfo) | The address and viewing key performing this query | yes | nothing |
+
+##### Response
+```
+{
+ "royalty_info": {
+ "decimal_places_in_rates": 4,
+ "royalties": [
+ {
+ "recipient": "optional_address_that_should_be_paid_this_royalty",
+ "rate": 100,
+ },
+ {
+ "...": "..."
+ }
+ ],
+ }
+}
+```
+| Name | Type | Description | Optional |
+|--------------|-----------------------------------------|----------------------------------------------------------------------------------------|----------|
+| royalty_info | [RoyaltyInfo (see above)](#royaltyinfo) | The token or default RoyaltyInfo as per the request | yes |
+
+## TokenApprovals
+TokenApprovals returns whether the owner and private metadata of a token is public, and lists all the approvals specific to this token. Only the token's owner may perform TokenApprovals.
+
+##### Request
+```
+{
+ "token_approvals": {
+ "token_id": "ID_of_the_token_being_queried",
+ "viewing_key": "the_token_owner's_viewing_key",
+ "include_expired": true | false
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------|--------|-----------------------------------------------------------------------|----------|------------------|
+| token_id | string | ID of the token being queried | no | |
+| viewing_key | string | The token owner's viewing key | no | |
+| include_expired | bool | True if expired approvals should be included in the response | yes | false |
+
+##### Response
+```
+{
+ "token_approvals": {
+ "owner_is_public": true | false,
+ "public_ownership_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "private_metadata_is_public": true | false,
+ "private_metadata_is_public_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "token_approvals": [
+ {
+ "address": "whitelisted_address",
+ "view_owner_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "view_private_metadata_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "transfer_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ },
+ {
+ "...": "..."
+ }
+ ],
+ }
+}
+```
+| Name | Type | Description | Optional |
+|---------------------------------------|-------------------------------------------------------|----------------------------------------------------------------------------------------|----------|
+| owner_is_public | bool | True if ownership is public for this token | no |
+| public_ownership_expiration | [Expiration (see above)](#expiration) | When public ownership expires for this token. Can be a blockheight, time, or never | yes |
+| private_metadata_is_public | bool | True if private metadata is public for this token | no |
+| private_metadata_is_public_expiration | [Expiration (see above)](#expiration) | When public display of private metadata expires. Can be a blockheight, time, or never | yes |
+| token_approvals | array of [Snip721Approval (see above)](#snipapproval) | List of approvals for this token | no |
+
+## ApprovedForAll
+ApprovedForAll displays all the addresses that have approval to transfer all of the specified owner's tokens. This is provided to comply with CW-721 specification, but because approvals are private on Secret Network, if the `owner`'s viewing key is not provided, no approvals will be displayed. For a more complete list of inventory-wide approvals, the owner should use [InventoryApprovals](#inventoryapprovals) which also includes view_owner and view_private_metadata approvals.
+
+##### Request
+```
+{
+ "approved_for_all": {
+ "owner": "address_whose_approvals_are_being_queried",
+ "viewing_key": "owner's_viewing_key"
+ "include_expired": true | false
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------|--------------------|------------------------------------------------------------------------------------------|----------|------------------|
+| owner | string (Addr) | The address whose approvals are being queried | no | |
+| viewing_key | string | The owner's viewing key | yes | nothing |
+| include_expired | bool | True if expired approvals should be included in the response | yes | false |
+
+##### Response
+```
+{
+ "approved_for_all": {
+ "operators": [
+ {
+ "spender": "address_with_transfer_approval",
+ "expires": "never" | {"at_height": 999999} | {"at_time":999999}
+ },
+ {
+ "...": "..."
+ }
+ ]
+ }
+}
+```
+| Name | Type | Description | Optional |
+|-----------|------------------------------------------------------|----------------------------------------------------------|----------|
+| operators | array of [Cw721Approval (see above)](#cw721approval) | List of approvals to transfer all of the owner's tokens | no |
+
+## InventoryApprovals
+InventoryApprovals returns whether all the address' tokens have public ownership and/or public display of private metadata, and lists all the inventory-wide approvals the address has granted. Only the viewing key for this specified address will be accepted.
+
+##### Request
+```
+{
+ "inventory_approvals": {
+ "address": "address_whose_approvals_are_being_queried",
+ "viewing_key": "the_viewing_key_associated_with_this_address",
+ "include_expired": true | false
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------------|--------------------|-----------------------------------------------------------------------|----------|------------------|
+| address | string (Addr) | The address whose inventory-wide approvals are being queried | no | |
+| viewing_key | string | The viewing key associated with this address | no | |
+| include_expired | bool | True if expired approvals should be included in the response | yes | false |
+
+##### Response
+```
+{
+ "inventory_approvals": {
+ "owner_is_public": true | false,
+ "public_ownership_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "private_metadata_is_public": true | false,
+ "private_metadata_is_public_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "inventory_approvals": [
+ {
+ "address": "whitelisted_address",
+ "view_owner_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "view_private_metadata_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ "transfer_expiration": "never" | {"at_height": 999999} | {"at_time":999999},
+ },
+ {
+ "...": "..."
+ }
+ ],
+ }
+}
+```
+| Name | Type | Description | Optional |
+|---------------------------------------|-------------------------------------------------------|----------------------------------------------------------------------------------------|----------|
+| owner_is_public | bool | True if ownership is public for all of this address' tokens | no |
+| public_ownership_expiration | [Expiration (see above)](#expiration) | When public ownership expires for all tokens. Can be a blockheight, time, or never | yes |
+| private_metadata_is_public | bool | True if private metadata is public for all of this address' tokens | no |
+| private_metadata_is_public_expiration | [Expiration (see above)](#expiration) | When public display of private metadata expires. Can be a blockheight, time, or never | yes |
+| inventory_approvals | array of [Snip721Approval (see above)](#snipapproval) | List of inventory-wide approvals for this address | no |
+
+## Tokens
+Tokens displays an optionally paginated list of all the token IDs that belong to the specified `owner`. It will only display the owner's tokens on which the querier has view_owner permission. If no viewing key is provided, it will only display the owner's tokens that have public ownership. When paginating, supply the last token ID received in a response as the `start_after` string of the next query to continue listing where the previous query stopped.
+
+##### Request
+```
+{
+ "tokens": {
+ "owner": "address_whose_inventory_is_being_queried",
+ "viewer": "address_of_the_querier_if_different_from_owner",
+ "viewing_key": "querier's_viewing_key"
+ "start_after": "optionally_display_only_token_ids_that_come_after_this_one_in_the_list",
+ "limit": 10
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-------------|--------------------|------------------------------------------------------------------------------------------|----------|------------------|
+| owner | string (Addr) | The address whose inventory is being queried | no | |
+| viewer | string (Addr) | The querier's address if different from the `owner` | yes | nothing |
+| viewing_key | string | The querier's viewing key | yes | nothing |
+| start_after | string | Results will only list token IDs that come after this token ID in the list | yes | nothing |
+| limit | number (u32) | Number of token IDs to return | yes | 30 |
+
+##### Response
+```
+{
+ "token_list": {
+ "tokens": [
+ "list", "of", "the", "owner's", "tokens", "..."
+ ]
+ }
+}
+```
+| Name | Type | Description | Optional |
+|---------|-----------------|----------------------------------------------------------------------|----------|
+| tokens | array of string | A list of token IDs owned by the specified `owner` | no |
+
+## VerifyTransferApproval
+VerifyTransferApproval will verify that the specified address has approval to transfer the entire provided list of tokens. As explained [above](#queryblockinfo), queries may experience a delay in revealing expired approvals, so it is possible that a transfer attempt will still fail even after being verified by VerifyTransferApproval. If the address does not have transfer approval on all the tokens, the response will indicate the first token encountered that can not be transferred by the address.
+
+Because the intent of VerifyTransferApproval is to provide contracts a way to know before-hand whether an attempt to transfer tokens will fail, this implementation will consider any [SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) non-transferable token as unapproved for transfer.
+
+##### Request
+```
+{
+ "verify_transfer_approval": {
+ "token_ids": [
+ "list", "of", "tokens", "to", "check", "for", "transfer", "approval", "..."
+ ],
+ "address": "address_to_use_for_approval_checking",
+ "viewing_key": "address'_viewing_key"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-------------|--------------------|------------------------------------------------------------------------------------------|----------|------------------|
+| token_ids | array of string | List of tokens to check for the address' transfer approval | no | |
+| address | string (Addr) | Address being checked for transfer approval | no | |
+| viewing_key | string | The address' viewing key | no | |
+
+##### Response
+```
+{
+ "verify_transfer_approval": {
+ "approved_for_all": true | false,
+ "first_unapproved_token": "token_id"
+ }
+}
+```
+| Name | Type | Description | Optional |
+|------------------------|--------|-----------------------------------------------------------------------------------|----------|
+| approved_for_all | bool | True if the `address` has transfer approval on all the `token_ids` | no |
+| first_unapproved_token | string | The first token in the list that the `address` does not have approval to transfer | yes |
+
+## ImplementsTokenSubtype
+ImplementsTokenSubtype is a [SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) query which indicates whether the contract implements the `token_subtype` Extension field. Because legacy SNIP-721 contracts do not implement this query and do not implement token subtypes, any use of this query should always check for an error response, and if the response is an error, it can be considered that the contract does not implement subtypes. Because message parsing ignores input fields that a contract does not expect, this query should be used before attempting a message that uses the `token_subtype` [Extension](#extension) field. If the message is sent to a SNIP-721 contract that does not implement `token_subtype`, that field will just be ignored and the resulting NFT will still be created/updated, but without a `token_subtype`.
+
+##### Request
+```
+{
+ "implements_token_subtype": {}
+}
+```
+##### Response
+```
+{
+ "implements_token_subtype": {
+ "is_enabled": true | false
+ }
+}
+```
+| Name | Type | Description | Optional |
+|-------------|------|-------------------------------------------------------------------------|----------|
+| is_enabled | bool | True if the contract implements token subtypes | no |
+
+## ImplementsNonTransferableTokens
+ImplementsNonTransferableTokens is a [SNIP-722](https://github.com/baedrik/snip-722-spec/blob/master/SNIP-722.md) query which indicates whether the contract implements non-transferable tokens. Because legacy SNIP-721 contracts do not implement this query and do not implement non-transferable tokens, any use of this query should always check for an error response, and if the response is an error, it can be considered that the contract does not implement non-transferable tokens. Because message parsing ignores input fields that a contract does not expect, this query should be used before attempting to mint a non-transferable token. If the message is sent to a SNIP-721 contract that does not implement non-transferable tokens, the `transferable` field will just be ignored and the resulting NFT will still be created, but will always be transferable.
+
+##### Request
+```
+{
+ "implements_non_transferable_tokens": {}
+}
+```
+##### Response
+```
+{
+ "implements_non_transferable_tokens": {
+ "is_enabled": true | false
+ }
+}
+```
+| Name | Type | Description | Optional |
+|-------------|------|-------------------------------------------------------------------------|----------|
+| is_enabled | bool | True if the contract implements non-transferable tokens | no |
+
+## TransactionHistory
+TransactionHistory displays an optionally paginated list of transactions (mint, burn, and transfer) in reverse chronological order that involve the specified address.
+
+##### Request
+```
+{
+ "transaction_history": {
+ "address": "address_whose_tx_history_is_being_queried",
+ "viewing_key": "address'_viewing_key"
+ "page": "optional_page_to_display",
+ "page_size": 10
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-------------|--------------------|-----------------------------------------------------------------------------------------------------------------------|----------|------------------|
+| address | string (Addr) | The address whose transaction history is being queried | no | |
+| viewing_key | string | The address' viewing key | no | |
+| page | number (u32) | The page number to display, where the first transaction shown skips the `page` * `page_size` most recent transactions | yes | 0 |
+| page_size | number (u32) | Number of transactions to return | yes | 30 |
+
+##### Response
+```
+{
+ "transaction_history": {
+ "total": 99,
+ "txs": [
+ {
+ "tx_id": 9999,
+ "block_height": 999999,
+ "block_time": 1610000012,
+ "token_id": "token_involved_in_the_tx",
+ "action": {
+ "transfer": {
+ "from": "previous_owner_of_the_token",
+ "sender": "address_that_sent_the_token_if_different_than_the_previous_owner",
+ "recipient": "new_owner_of_the_token"
+ }
+ },
+ "memo": "optional_memo_for_the_tx"
+ },
+ {
+ "tx_id": 9998,
+ "block_height": 999998,
+ "block_time": 1610000006,
+ "token_id": "token_involved_in_the_tx",
+ "action": {
+ "mint": {
+ "minter": "address_that_minted_the_token",
+ "recipient": "owner_of_the_newly_minted_token"
+ }
+ },
+ "memo": "optional_memo_for_the_tx"
+ },
+ {
+ "tx_id": 9997,
+ "block_height": 999997,
+ "block_time": 1610000000,
+ "token_id": "token_involved_in_the_tx",
+ "action": {
+ "burn": {
+ "owner": "previous_owner_of_the_token",
+ "burner": "address_that_burned_the_token_if_different_than_the_previous_owner",
+ }
+ },
+ "memo": "optional_memo_for_the_tx"
+ },
+ {
+ "...": "..."
+ }
+ ],
+ }
+}
+```
+| Name | Type | Description | Optional |
+|-------|--------------------------------|----------------------------------------------------------------------------------------|----------|
+| total | number (u64) | The total number of transactions that involve the specified address | no |
+| txs | array of [Tx (see below)](#tx) | List of transactions in reverse chronological order that involve the specified address | no |
+
+### Tx
+The Tx object contains all the information pertaining to a [mint](#txmint), [burn](#txburn), or [transfer](#txxfer) transaction.
+```
+{
+ "tx_id": 9999,
+ "block_height": 999999,
+ "block_time": 1610000000,
+ "token_id": "token_involved_in_the_tx",
+ "action": { TxAction::Transfer | TxAction::Mint | TxAction::Burn },
+ "memo": "optional_memo_for_the_tx"
+}
+```
+| Name | Type | Description | Optional |
+|--------------|-----------------------------------|-------------------------------------------------------------------------------------------|----------|
+| tx_id | number (u64) | The transaction identifier | no |
+| block_height | number (u64) | The number of the block that contains the transaction | no |
+| block_time | number (u64) | The time in seconds since 01/01/1970 of the block that contains the transaction | no |
+| token_id | string | The token involved in the transaction | no |
+| action | [TxAction (see below)](#txaction) | The type of transaction and the information specific to that type | no |
+| memo | string | `memo` for the transaction that is only viewable by addresses involved in the transaction | yes |
+
+### TxAction
+The TxAction object defines the type of transaction and holds the information specific to that type.
+
+* TxAction::Mint
+```
+{
+ "minter": "address_that_minted_the_token",
+ "recipient": "owner_of_the_newly_minted_token"
+}
+
+```
+| Name | Type | Description | Optional |
+|-----------|--------------------|--------------------------------------------------------------------------------|----------|
+| minter | string (Addr) | The address that minted the token | no |
+| recipient | string (Addr) | The address of the newly minted token's owner | no |
+
+* TxAction::Transfer
+```
+{
+ "from": "previous_owner_of_the_token",
+ "sender": "address_that_sent_the_token_if_different_than_the_previous_owner",
+ "recipient": "new_owner_of_the_token"
+}
+
+```
+| Name | Type | Description | Optional |
+|-----------|--------------------|--------------------------------------------------------------------------------|----------|
+| from | string (Addr) | The previous owner of the token | no |
+| sender | string (Addr) | The address that sent the token if different than the previous owner | yes |
+| recipient | string (Addr) | The new owner of the token | no |
+
+* TxAction::Burn
+```
+{
+ "owner": "previous_owner_of_the_token",
+ "burner": "address_that_burned_the_token_if_different_than_the_previous_owner",
+}
+
+```
+| Name | Type | Description | Optional |
+|-----------|--------------------|--------------------------------------------------------------------------------|----------|
+| owner | string (Addr) | The previous owner of the token | no |
+| burner | string (Addr) | The address that burned the token if different than the previous owner | yes |
+
+## WithPermit
+SNIP-721 contracts may optionally implement query permits as specified in [SNIP-24](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-24.md). They are an improvement over viewing keys in that permits allow a user to query private information without first needing to send a transaction to set or create a viewing key (see [here](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-24.md#Rationale) for more details).
+
+Because SNIP-721s already provide whitelisting functionality for approving other addresses to view private information, SNIP-721 permits typically use the `owner` permission type to authenticate the query to display all the private information that the address of the creator of the permit is authorized to see. So, it is generally advised that you never give SNIP-721 permits with `owner` permission to anyone. If you need someone to view private information of a token you own, you should whitelist their address, and they will then use a permit they create themselves to view only what you have approved. This eliminates the need to provide them a permit, eliminates the need to track permit names in order to later revoke viewing permission, and provides an easy way to query the network to see everyone that currently has viewing approval. That said, contract developers are not limited, and may choose, if appropriate for their use-case, to implement permits that have more granular permissions that users are meant to share with others.
+
+WithPermit wraps permit queries in the [same manner](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-24.md#WithPermit) as SNIP-24.
+
+##### Request
+```
+{
+ "with_permit": {
+ "permit": {
+ "params": {
+ "permit_name": "some_name",
+ "allowed_tokens": ["collection_address_1", "collection_address_2", "..."],
+ "chain_id": "some_chain_id",
+ "permissions": ["owner"]
+ },
+ "signature": {
+ "pub_key": {
+ "type": "tendermint/PubKeySecp256k1",
+ "value": "33_bytes_of_secp256k1_pubkey_as_base64"
+ },
+ "signature": "64_bytes_of_secp256k1_signature_as_base64"
+ }
+ },
+ "query": {
+ "QueryWithPermit_variant_defined_below": { "...": "..." }
+ }
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|--------|---------------------------------------------------------------------------------------|-----------------------------------------------|----------|------------------|
+| permit | [Permit](https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-24.md#WithPermit) | A permit following SNIP-24 standard | no | |
+| query | [QueryWithPermit (see below)](#QueryWithPermit) | The query to perform and its input parameters | no | |
+
+#### QueryWithPermit
+QueryWithPermit is an enum whose variants correlate with all SNIP-721 queries that require authentication. The input parameters are the same as the corresponding query other than the absence of [ViewerInfo](#viewerinfo) and viewing keys because the permit supplied with the `WithPermit` query provides both the address and authentication.
+
+* NumTokens ([corresponding query](#NumTokens))
+##### WithPermit `query` Parameter
+```
+"query": {
+ "num_tokens": {}
+}
+```
+* AllTokens ([corresponding query](#AllTokens))
+##### WithPermit `query` Parameter
+```
+"query": {
+ "all_tokens": {
+ "start_after": "optionally_display_only_token_ids_that_come_after_this_one_in_the_list",
+ "limit": 10
+ }
+}
+```
+* OwnerOf ([corresponding query](#OwnerOf))
+##### WithPermit `query` Parameter
+```
+"query": {
+ "owner_of": {
+ "token_id": "ID_of_the_token_being_queried",
+ "include_expired": true | false
+ }
+}
+```
+* AllNftInfo ([corresponding query](#allnftinfo))
+##### WithPermit `query` Parameter
+```
+"query": {
+ "all_nft_info": {
+ "token_id": "ID_of_the_token_being_queried",
+ "include_expired": true | false
+ }
+}
+```
+* PrivateMetadata ([corresponding query](#PrivateMetadata))
+##### WithPermit `query` Parameter
+```
+"query": {
+ "private_metadata": {
+ "token_id": "ID_of_the_token_being_queried",
+ }
+}
+```
+* NftDossier ([corresponding query](#NftDossier))
+##### WithPermit `query` Parameter
+```
+"query": {
+ "nft_dossier": {
+ "token_id": "ID_of_the_token_being_queried",
+ "include_expired": true | false
+ }
+}
+```
+* BatchNftDossier ([corresponding query](#BatchNftDossier))
+##### WithPermit `query` Parameter
+```
+"query": {
+ "batch_nft_dossier": {
+ "token_ids": ["ID_of", "the_tokens", "being_queried"],
+ "include_expired": true | false
+ }
+}
+```
+* RoyaltyInfo ([corresponding query](#royaltyquery))
+##### WithPermit `query` Parameter
+```
+"query": {
+ "royalty_info": {
+ "token_id": "optional_ID_of_the_token_being_queried",
+ }
+}
+```
+* TokenApprovals ([corresponding query](#TokenApprovals))
+##### WithPermit `query` Parameter
+```
+"query": {
+ "token_approvals": {
+ "token_id": "ID_of_the_token_being_queried",
+ "include_expired": true | false
+ }
+}
+```
+* ApprovedForAll ([corresponding query](#ApprovedForAll))
+##### WithPermit `query` Parameter
+```
+"query": {
+ "approved_for_all": {
+ "include_expired": true | false
+ }
+}
+```
+* InventoryApprovals ([corresponding query](#InventoryApprovals))
+##### WithPermit `query` Parameter
+```
+"query": {
+ "inventory_approvals": {
+ "include_expired": true | false
+ }
+}
+```
+* NumTokensOfOwner ([corresponding query](#NumTokensOfOwner))
+##### WithPermit `query` Parameter
+```
+"query": {
+ "num_tokens_of_owner": {
+ "owner": "address_whose_token_count_is_being_queried",
+ }
+}
+```
+* Tokens ([corresponding query](#tokens))
+##### WithPermit `query` Parameter
+```
+"query": {
+ "tokens": {
+ "owner": "address_whose_inventory_is_being_queried",
+ "start_after": "optionally_display_only_token_ids_that_come_after_this_one_in_the_list",
+ "limit": 10
+ }
+}
+```
+* VerifyTransferApproval ([corresponding query](#VerifyTransferApproval))
+##### WithPermit `query` Parameter
+```
+"query": {
+ "verify_transfer_approval": {
+ "token_ids": [
+ "list", "of", "tokens", "to", "check", "for", "transfer", "approval", "..."
+ ],
+ }
+}
+```
+* TransactionHistory ([corresponding query](#TransactionHistory))
+##### WithPermit `query` Parameter
+```
+"query": {
+ "transaction_history": {
+ "page": "optional_page_to_display",
+ "page_size": 10
+ }
+}
+```
+
+# Receiver Interface
+When the token contract executes [SendNft](#sendnft) and [BatchSendNft](#batchsend) messages, it will perform a callback to the receiving contract's receiver interface if the contract had registered its code hash using [RegisterReceiveNft](#registerreceive). [BatchReceiveNft](#batchreceivenft) is preferred over [ReceiveNft](#receivenft), because ReceiveNft does not allow the recipient to know who sent the token, only its previous owner, and ReceiveNft can only process one token. So it is inefficient when sending multiple tokens to the same contract (a deck of game cards for instance). ReceiveNft primarily exists just to maintain CW-721 compliance, and if the receiving contract registered that it implements BatchReceiveNft, BatchReceiveNft will be called, even when there is only one token_id in the message.
+
+Also, it should be noted that the CW-721 `sender` field is inaccurately named, because it is used to hold the address the token came from, not the address that sent it (which is not always the same). The name is reluctantly kept in [ReceiveNft](#receivenft) to maintain CW-721 compliance, but BatchReceiveNft uses `sender` to hold the sending address (which matches both its true role and its SNIP-20 Receive counterpart). Any contract that is implementing both Receiver Interfaces must be sure that the ReceiveNft `sender` field is actually processed like a BatchReceiveNft `from` field. Again, apologies for any confusion caused by propagating inaccuracies, but because [InterNFT](https://internft.org) is planning on using CW-721 standards, compliance with CW-721 might be necessary.
+
+## ReceiveNft
+ReceiveNft may be a HandleMsg variant of any contract that wants to implement a receiver interface. [BatchReceiveNft](#batchreceivenft), which is more informative and more efficient, is preferred over ReceiveNft.
+```
+{
+ "receive_nft": {
+ "sender": "address_of_the_previous_owner_of_the_token",
+ "token_id": "ID_of_the_sent_token",
+ "msg": "optional_base64_encoded_Binary_message_used_to_control_receiving_logic"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|----------|--------------------------------|--------------------------------------------------------------------------------------------------------|----------|------------------|
+| sender | string (Addr) | Address of the token's previous owner ([see above](#cwsender) about this inaccurate naming convention) | no | |
+| token_id | string | ID of the sent token | no | |
+| msg | string (base64 encoded Binary) | Msg used to control receiving logic | yes | nothing |
+
+## BatchReceiveNft
+BatchReceiveNft may be a HandleMsg variant of any contract that wants to implement a receiver interface. BatchReceiveNft, which is more informative and more efficient, is preferred over [ReceiveNft](#receivenft).
+```
+{
+ "batch_receive_nft": {
+ "sender": "address_that_sent_the_tokens",
+ "from": "address_of_the_previous_owner_of_the_tokens",
+ "token_ids": [
+ "list", "of", "tokens", "sent", "..."
+ ],
+ "msg": "optional_base64_encoded_Binary_message_used_to_control_receiving_logic"
+ }
+}
+```
+| Name | Type | Description | Optional | Value If Omitted |
+|-----------|--------------------------------|--------------------------------------------------------------------------------------------------------------------------|----------|------------------|
+| sender | string (Addr) | Address that sent the tokens (this field has no ReceiveNft equivalent, [see above](#cwsender)) | no | |
+| from | string (Addr) | Address of the tokens' previous owner (this field is equivalent to the ReceiveNft `sender` field, [see above](#cwsender))| no | |
+| token_ids | array of string | List of the tokens sent | no | |
+| msg | string (base64 encoded Binary) | Msg used to control receiving logic | yes | nothing |
diff --git a/contracts/external/snip721-reference-impl/rustfmt.toml b/contracts/external/snip721-reference-impl/rustfmt.toml
new file mode 100644
index 0000000..11a85e6
--- /dev/null
+++ b/contracts/external/snip721-reference-impl/rustfmt.toml
@@ -0,0 +1,15 @@
+# stable
+newline_style = "unix"
+hard_tabs = false
+tab_spaces = 4
+
+# unstable... should we require `rustup run nightly cargo fmt` ?
+# or just update the style guide when they are stable?
+#fn_single_line = true
+#format_code_in_doc_comments = true
+#overflow_delimited_expr = true
+#reorder_impl_items = true
+#struct_field_align_threshold = 20
+#struct_lit_single_line = true
+#report_todo = "Always"
+
diff --git a/contracts/external/snip721-reference-impl/src/contract.rs b/contracts/external/snip721-reference-impl/src/contract.rs
index dc74ec1..fba3973 100644
--- a/contracts/external/snip721-reference-impl/src/contract.rs
+++ b/contracts/external/snip721-reference-impl/src/contract.rs
@@ -16,6 +16,8 @@ use secret_toolkit::{
viewing_key::{ViewingKey, ViewingKeyStore},
};
+use crate::expiration::Expiration;
+use crate::inventory::{Inventory, InventoryIter};
use crate::mint_run::{SerialNumber, StoredMintRunInfo};
use crate::msg::{
AccessLevel, BatchNftDossierElement, Burn, ContractStatus, Cw721Approval, Cw721OwnerOfResponse,
@@ -33,14 +35,6 @@ use crate::state::{
PREFIX_ROYALTY_INFO, VIEWING_KEY_ERR_MSG,
};
use crate::token::{Metadata, Token};
-use crate::{
- expiration::Expiration,
- msg::{InstantiateResponse, Minters, NftInfo, NumTokens, OwnerOf},
-};
-use crate::{
- inventory::{Inventory, InventoryIter},
- msg::ContractInfo,
-};
/// pad handle responses and log attributes to blocks of 256 bytes to prevent leaking info based on
/// response size
@@ -62,7 +56,7 @@ pub const ID_BLOCK_SIZE: u32 = 64;
#[entry_point]
pub fn instantiate(
deps: DepsMut,
- env: Env,
+ _env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult {
@@ -128,12 +122,7 @@ pub fn instantiate(
} else {
Vec::new()
};
- Ok(Response::new()
- .add_messages(messages)
- .set_data(to_binary(&InstantiateResponse {
- contract_address: env.contract.address,
- code_hash: env.contract.code_hash,
- })?))
+ Ok(Response::new().add_messages(messages))
}
///////////////////////////////////// Handle //////////////////////////////////////
@@ -1967,7 +1956,7 @@ pub fn query_contract_creator(deps: Deps) -> StdResult {
pub fn query_contract_info(storage: &dyn Storage) -> StdResult {
let config: Config = load(storage, CONFIG_KEY)?;
- to_binary(&ContractInfo {
+ to_binary(&QueryAnswer::ContractInfo {
name: config.name,
symbol: config.symbol,
})
@@ -2073,7 +2062,7 @@ pub fn query_config(storage: &dyn Storage) -> StdResult {
pub fn query_minters(deps: Deps) -> StdResult {
let minters: Vec = may_load(deps.storage, MINTERS_KEY)?.unwrap_or_default();
- to_binary(&Minters {
+ to_binary(&QueryAnswer::Minters {
minters: minters
.iter()
.map(|m| deps.api.addr_humanize(m))
@@ -2096,7 +2085,7 @@ pub fn query_num_tokens(
// authenticate permission to view token supply
check_view_supply(deps, viewer, from_permit)?;
let config: Config = load(deps.storage, CONFIG_KEY)?;
- to_binary(&NumTokens {
+ to_binary(&QueryAnswer::NumTokens {
count: config.token_cnt,
})
}
@@ -2169,7 +2158,7 @@ pub fn query_owner_of(
let (may_owner, approvals, _idx) =
process_cw721_owner_of(deps, block, token_id, viewer, include_expired, from_permit)?;
if let Some(owner) = may_owner {
- return to_binary(&OwnerOf { owner, approvals });
+ return to_binary(&QueryAnswer::OwnerOf { owner, approvals });
}
Err(StdError::generic_err(format!(
"You are not authorized to view the owner of token {}",
@@ -2193,7 +2182,7 @@ pub fn query_nft_info(storage: &dyn Storage, token_id: &str) -> StdResult = Vec::new();
for id in token_ids.into_iter() {
// cargo fmt creates the and_then block, but clippy doesn't like it
- #[allow(clippy::blocks_in_conditions)]
+ #[allow(clippy::blocks_in_if_conditions)]
if get_token_if_permitted(
deps,
block,
@@ -4108,7 +4096,6 @@ pub struct CacheReceiverInfo {
#[allow(clippy::too_many_arguments)]
fn receiver_callback_msgs(
deps: &mut DepsMut,
- _env: Env,
contract_human: &str,
contract: &CanonicalAddr,
receiver_info: Option,
@@ -4410,7 +4397,6 @@ fn send_list(
// get BatchReceiveNft and ReceiveNft msgs for all the tokens sent in this Send
messages.extend(receiver_callback_msgs(
&mut deps,
- env.clone(),
&send.contract,
&contract_raw,
send.receiver_info,
diff --git a/contracts/external/snip721-reference-impl/src/msg.rs b/contracts/external/snip721-reference-impl/src/msg.rs
index 51c9ed7..984d0af 100644
--- a/contracts/external/snip721-reference-impl/src/msg.rs
+++ b/contracts/external/snip721-reference-impl/src/msg.rs
@@ -11,7 +11,7 @@ use crate::royalties::{DisplayRoyaltyInfo, RoyaltyInfo};
use crate::token::{Extension, Metadata};
/// Instantiation message
-#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
+#[derive(Serialize, Deserialize, JsonSchema)]
pub struct InstantiateMsg {
/// name of token contract
pub name: String,
@@ -33,12 +33,6 @@ pub struct InstantiateMsg {
pub post_init_callback: Option,
}
-#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)]
-pub struct InstantiateResponse {
- pub contract_address: Addr,
- pub code_hash: String,
-}
-
/// This type represents optional configuration values.
/// All values are optional and have defaults which are more private by default,
/// but can be overridden if necessary
@@ -105,7 +99,7 @@ pub struct PostInstantiateCallback {
pub send: Vec,
}
-#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
+#[derive(Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
/// mint new token
@@ -413,7 +407,7 @@ pub enum ExecuteMsg {
}
/// permission access level
-#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
+#[derive(Serialize, Deserialize, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum AccessLevel {
/// approve permission only for the specified token
@@ -890,29 +884,6 @@ pub struct BatchNftDossierElement {
pub inventory_approvals: Option>,
}
-#[derive(Serialize, Deserialize, JsonSchema, Debug)]
-pub struct Minters {
- pub minters: Vec,
-}
-
-#[derive(Serialize, Deserialize, JsonSchema, Debug)]
-pub struct NftInfo {
- pub token_uri: Option,
- pub extension: Option,
-}
-
-#[derive(Serialize, Deserialize, JsonSchema, Debug)]
-pub struct OwnerOf {
- pub owner: Addr,
- pub approvals: Vec,
-}
-
-#[derive(Serialize, Deserialize, JsonSchema, Debug)]
-pub struct ContractInfo {
- pub name: String,
- pub symbol: String,
-}
-
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum QueryAnswer {
@@ -1025,11 +996,6 @@ pub enum QueryAnswer {
},
}
-#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
-pub struct NumTokens {
- pub count: u32,
-}
-
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum ResponseStatus {
diff --git a/contracts/external/snip721-reference-impl/src/token.rs b/contracts/external/snip721-reference-impl/src/token.rs
index 848c8ab..35226fd 100644
--- a/contracts/external/snip721-reference-impl/src/token.rs
+++ b/contracts/external/snip721-reference-impl/src/token.rs
@@ -63,11 +63,6 @@ pub struct Extension {
/// token subtypes used by Stashh for display groupings (primarily used for badges, which are specified
/// by using "badge" as the token_subtype)
pub token_subtype: Option,
-
- /// Optional on-chain role for this member, can be used by other contracts to enforce permissions
- pub role: Option,
- /// The voting weight of this role
- pub weight: u64,
}
/// attribute trait
diff --git a/contracts/external/snip721-reference-impl/src/unittest_handles.rs b/contracts/external/snip721-reference-impl/src/unittest_handles.rs
index 4f140f8..a30b246 100644
--- a/contracts/external/snip721-reference-impl/src/unittest_handles.rs
+++ b/contracts/external/snip721-reference-impl/src/unittest_handles.rs
@@ -546,7 +546,7 @@ mod tests {
"Init failed: {}",
init_result.err().unwrap()
);
- let pub_expect = Metadata {
+ let pub_expect = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("MyNFT".to_string()),
@@ -554,8 +554,8 @@ mod tests {
image: Some("uri".to_string()),
..Extension::default()
}),
- };
- let priv_expect = Metadata {
+ });
+ let priv_expect = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("MyNFTpriv".to_string()),
@@ -563,12 +563,12 @@ mod tests {
image: Some("privuri".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("MyNFT".to_string()),
owner: Some("alice".to_string()),
- public_metadata: Some(pub_expect.clone()),
- private_metadata: Some(priv_expect.clone()),
+ public_metadata: pub_expect.clone(),
+ private_metadata: priv_expect.clone(),
royalty_info: None,
serial_number: None,
transferable: None,
@@ -601,10 +601,10 @@ mod tests {
// verify the token metadata
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &token_key).unwrap();
- assert_eq!(pub_meta, pub_expect);
+ assert_eq!(pub_meta, pub_expect.unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &token_key).unwrap();
- assert_eq!(priv_meta, priv_expect);
+ assert_eq!(priv_meta, priv_expect.unwrap());
// verify token is in owner list
assert!(Inventory::owns(&deps.storage, &alice_raw, 0).unwrap());
// verify mint tx was logged to both parties
@@ -661,7 +661,7 @@ mod tests {
assert!(error.contains("Token ID MyNFT is already in use"));
// test minting without specifying recipient or id
- let pub_expect = Metadata {
+ let pub_expect = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("AdminNFT".to_string()),
@@ -669,11 +669,11 @@ mod tests {
image: None,
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: None,
owner: None,
- public_metadata: Some(pub_expect.clone()),
+ public_metadata: pub_expect.clone(),
private_metadata: None,
royalty_info: None,
serial_number: None,
@@ -714,7 +714,7 @@ mod tests {
// verify metadata
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &token_key).unwrap();
- assert_eq!(pub_meta, pub_expect);
+ assert_eq!(pub_meta, pub_expect.unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &token_key).unwrap();
assert!(priv_meta.is_none());
@@ -1017,7 +1017,7 @@ mod tests {
mock_info("admin", &[]),
execute_msg,
);
- let set_expect = Metadata {
+ let set_expect = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("New Name".to_string()),
@@ -1025,10 +1025,10 @@ mod tests {
image: Some("new uri".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::SetMetadata {
token_id: "MyNFT".to_string(),
- public_metadata: Some(set_expect.clone()),
+ public_metadata: set_expect.clone(),
private_metadata: None,
padding: None,
};
@@ -1040,7 +1040,7 @@ mod tests {
);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &0u32.to_le_bytes()).unwrap();
- assert_eq!(pub_meta, set_expect);
+ assert_eq!(pub_meta, set_expect.unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &0u32.to_le_bytes()).unwrap();
assert!(priv_meta.is_none());
@@ -1240,7 +1240,7 @@ mod tests {
assert!(error.contains("Metadata can not have BOTH token_uri AND extension"));
// sanity check, minter changing metadata after owner unwrapped
- let set_pub = Metadata {
+ let set_pub = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("New Name Pub".to_string()),
@@ -1248,8 +1248,8 @@ mod tests {
image: Some("new uri pub".to_string()),
..Extension::default()
}),
- };
- let set_priv = Metadata {
+ });
+ let set_priv = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("New Name Priv".to_string()),
@@ -1257,11 +1257,11 @@ mod tests {
image: Some("new uri priv".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::SetMetadata {
token_id: "MyNFT".to_string(),
- public_metadata: Some(set_pub.clone()),
- private_metadata: Some(set_priv.clone()),
+ public_metadata: set_pub.clone(),
+ private_metadata: set_priv.clone(),
padding: None,
};
let _handle_result = execute(
@@ -1272,10 +1272,10 @@ mod tests {
);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &token_key).unwrap();
- assert_eq!(pub_meta, set_pub);
+ assert_eq!(pub_meta, set_pub.unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &token_key).unwrap();
- assert_eq!(priv_meta, set_priv);
+ assert_eq!(priv_meta, set_priv.unwrap());
// test setting metadata when status prevents it
let execute_msg = ExecuteMsg::SetContractStatus {
@@ -1355,7 +1355,7 @@ mod tests {
"Init failed: {}",
init_result.err().unwrap()
);
- let pub_expect = Metadata {
+ let pub_expect = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("MyNFT".to_string()),
@@ -1363,11 +1363,11 @@ mod tests {
image: Some("uri".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("MyNFT".to_string()),
owner: Some("alice".to_string()),
- public_metadata: Some(pub_expect.clone()),
+ public_metadata: pub_expect.clone(),
private_metadata: None,
royalty_info: None,
serial_number: None,
@@ -1381,7 +1381,7 @@ mod tests {
mock_info("admin", &[]),
execute_msg,
);
- let priv_expect = Metadata {
+ let priv_expect = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("New Name".to_string()),
@@ -1389,11 +1389,11 @@ mod tests {
image: Some("new uri".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::SetMetadata {
token_id: "MyNFT".to_string(),
public_metadata: None,
- private_metadata: Some(priv_expect.clone()),
+ private_metadata: priv_expect.clone(),
padding: None,
};
let _handle_result = execute(
@@ -1404,10 +1404,10 @@ mod tests {
);
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &token_key).unwrap();
- assert_eq!(priv_meta, priv_expect);
+ assert_eq!(priv_meta, priv_expect.unwrap());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &token_key).unwrap();
- assert_eq!(pub_meta, pub_expect);
+ assert_eq!(pub_meta, pub_expect.unwrap());
}
// test Reveal
@@ -1549,7 +1549,7 @@ mod tests {
init_result.err().unwrap()
);
- let seal_meta = Metadata {
+ let seal_meta = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("MySealedNFT".to_string()),
@@ -1557,11 +1557,11 @@ mod tests {
image: Some("sealed_uri".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("MyNFT".to_string()),
owner: Some("alice".to_string()),
- private_metadata: Some(seal_meta.clone()),
+ private_metadata: seal_meta.clone(),
public_metadata: None,
royalty_info: None,
serial_number: None,
@@ -1610,7 +1610,7 @@ mod tests {
assert!(priv_meta.is_none());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &token_key).unwrap();
- assert_eq!(pub_meta, seal_meta.clone());
+ assert_eq!(pub_meta, seal_meta.clone().unwrap());
// test trying to unwrap token that has already been unwrapped
let execute_msg = ExecuteMsg::Reveal {
@@ -1637,7 +1637,7 @@ mod tests {
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("MyNFT".to_string()),
owner: Some("alice".to_string()),
- private_metadata: Some(seal_meta.clone()),
+ private_metadata: seal_meta.clone(),
public_metadata: None,
royalty_info: None,
serial_number: None,
@@ -1663,7 +1663,7 @@ mod tests {
);
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &token_key).unwrap();
- assert_eq!(priv_meta, seal_meta);
+ assert_eq!(priv_meta, seal_meta.unwrap());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Option = may_load(&pub_store, &token_key).unwrap();
assert!(pub_meta.is_none());
@@ -1726,7 +1726,7 @@ mod tests {
let error = extract_error_msg(handle_result);
assert!(error.contains("You do not own token NFT1"));
- let pub1 = Metadata {
+ let pub1 = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("My1".to_string()),
@@ -1734,11 +1734,11 @@ mod tests {
image: Some("URI 1".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("NFT1".to_string()),
owner: Some("alice".to_string()),
- public_metadata: Some(pub1.clone()),
+ public_metadata: pub1.clone(),
private_metadata: None,
royalty_info: None,
serial_number: None,
@@ -1752,7 +1752,7 @@ mod tests {
mock_info("admin", &[]),
execute_msg,
);
- let pub2: Metadata = Metadata {
+ let pub2 = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("My2".to_string()),
@@ -1760,11 +1760,11 @@ mod tests {
image: Some("URI 2".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("NFT2".to_string()),
owner: Some("alice".to_string()),
- public_metadata: Some(pub2.clone()),
+ public_metadata: pub2.clone(),
private_metadata: None,
royalty_info: None,
serial_number: None,
@@ -1778,7 +1778,7 @@ mod tests {
mock_info("admin", &[]),
execute_msg,
); // test burn when status prevents it
- let pub3 = Metadata {
+ let pub3 = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("My3".to_string()),
@@ -1786,11 +1786,11 @@ mod tests {
image: Some("URI 3".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("NFT3".to_string()),
owner: Some("alice".to_string()),
- public_metadata: Some(pub3.clone()),
+ public_metadata: pub3.clone(),
private_metadata: None,
royalty_info: None,
serial_number: None,
@@ -1804,7 +1804,7 @@ mod tests {
mock_info("admin", &[]),
execute_msg,
);
- let pub4 = Metadata {
+ let pub4 = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("My4".to_string()),
@@ -1812,11 +1812,11 @@ mod tests {
image: Some("URI 4".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("NFT4".to_string()),
owner: Some("alice".to_string()),
- public_metadata: Some(pub4.clone()),
+ public_metadata: pub4.clone(),
private_metadata: None,
royalty_info: None,
serial_number: None,
@@ -1980,7 +1980,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft1_key).unwrap();
- assert_eq!(pub_meta, pub1.clone());
+ assert_eq!(pub_meta, pub1.clone().unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft1_key).unwrap();
assert!(priv_meta.is_none());
@@ -2041,7 +2041,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft1_key).unwrap();
- assert_eq!(pub_meta, pub1.clone());
+ assert_eq!(pub_meta, pub1.clone().unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft1_key).unwrap();
assert!(priv_meta.is_none());
@@ -2115,7 +2115,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft2_key).unwrap();
- assert_eq!(pub_meta, pub2.clone());
+ assert_eq!(pub_meta, pub2.clone().unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft2_key).unwrap();
assert!(priv_meta.is_none());
@@ -2176,7 +2176,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft2_key).unwrap();
- assert_eq!(pub_meta, pub2.clone());
+ assert_eq!(pub_meta, pub2.clone().unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft2_key).unwrap();
assert!(priv_meta.is_none());
@@ -2371,7 +2371,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft2_key).unwrap();
- assert_eq!(pub_meta, pub2);
+ assert_eq!(pub_meta, pub2.unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft2_key).unwrap();
assert!(priv_meta.is_none());
@@ -2477,7 +2477,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft1_key).unwrap();
- assert_eq!(pub_meta, pub1.clone());
+ assert_eq!(pub_meta, pub1.clone().unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft1_key).unwrap();
assert!(priv_meta.is_none());
@@ -2514,7 +2514,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft3_key).unwrap();
- assert_eq!(pub_meta, pub3.clone());
+ assert_eq!(pub_meta, pub3.clone().unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft3_key).unwrap();
assert!(priv_meta.is_none());
@@ -2622,7 +2622,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft1_key).unwrap();
- assert_eq!(pub_meta, pub1.clone());
+ assert_eq!(pub_meta, pub1.clone().unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft1_key).unwrap();
assert!(priv_meta.is_none());
@@ -2649,7 +2649,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft3_key).unwrap();
- assert_eq!(pub_meta, pub3.clone());
+ assert_eq!(pub_meta, pub3.clone().unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft3_key).unwrap();
assert!(priv_meta.is_none());
@@ -2732,7 +2732,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft4_key).unwrap();
- assert_eq!(pub_meta, pub4.clone());
+ assert_eq!(pub_meta, pub4.clone().unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft4_key).unwrap();
assert!(priv_meta.is_none());
@@ -2846,7 +2846,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft3_key).unwrap();
- assert_eq!(pub_meta, pub3.clone());
+ assert_eq!(pub_meta, pub3.clone().unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft3_key).unwrap();
assert!(priv_meta.is_none());
@@ -2954,7 +2954,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft3_key).unwrap();
- assert_eq!(pub_meta, pub3.clone());
+ assert_eq!(pub_meta, pub3.clone().unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft3_key).unwrap();
assert!(priv_meta.is_none());
@@ -3071,7 +3071,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft4_key).unwrap();
- assert_eq!(pub_meta, pub4);
+ assert_eq!(pub_meta, pub4.unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft3_key).unwrap();
assert!(priv_meta.is_none());
@@ -3207,7 +3207,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft1_key).unwrap();
- assert_eq!(pub_meta, pub1);
+ assert_eq!(pub_meta, pub1.unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft1_key).unwrap();
assert!(priv_meta.is_none());
@@ -3256,7 +3256,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft3_key).unwrap();
- assert_eq!(pub_meta, pub3.clone());
+ assert_eq!(pub_meta, pub3.clone().unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft3_key).unwrap();
assert!(priv_meta.is_none());
@@ -3729,7 +3729,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft3_key).unwrap();
- assert_eq!(pub_meta, pub3.clone());
+ assert_eq!(pub_meta, pub3.clone().unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft3_key).unwrap();
assert!(priv_meta.is_none());
@@ -3778,7 +3778,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft3_key).unwrap();
- assert_eq!(pub_meta, pub3);
+ assert_eq!(pub_meta, pub3.unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft3_key).unwrap();
assert!(priv_meta.is_none());
@@ -3845,7 +3845,7 @@ mod tests {
error.contains("Not authorized to grant/revoke transfer permission for token MyNFT")
);
- let priv_expect = Metadata {
+ let priv_expect = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("MyNFT".to_string()),
@@ -3853,11 +3853,11 @@ mod tests {
image: Some("uri".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("MyNFT".to_string()),
owner: Some("alice".to_string()),
- private_metadata: Some(priv_expect.clone()),
+ private_metadata: priv_expect.clone(),
public_metadata: None,
royalty_info: None,
serial_number: None,
@@ -4081,7 +4081,7 @@ mod tests {
assert!(token.unwrapped);
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &tok_key).unwrap();
- assert_eq!(priv_meta, priv_expect.clone());
+ assert_eq!(priv_meta, priv_expect.clone().unwrap());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Option = may_load(&pub_store, &tok_key).unwrap();
assert!(pub_meta.is_none());
@@ -4143,7 +4143,7 @@ mod tests {
assert!(token.unwrapped);
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &tok_key).unwrap();
- assert_eq!(priv_meta, priv_expect);
+ assert_eq!(priv_meta, priv_expect.unwrap());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Option = may_load(&pub_store, &tok_key).unwrap();
assert!(pub_meta.is_none());
@@ -4171,7 +4171,7 @@ mod tests {
// used to test auto-setting individual token permissions when only one token
// of many is approved with a different expiration than an operator's expiration
- let priv2 = Metadata {
+ let priv2 = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("MyNFT2".to_string()),
@@ -4179,11 +4179,11 @@ mod tests {
image: Some("uri2".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("MyNFT2".to_string()),
owner: Some("alice".to_string()),
- private_metadata: Some(priv2.clone()),
+ private_metadata: priv2.clone(),
public_metadata: None,
royalty_info: None,
serial_number: None,
@@ -4310,7 +4310,7 @@ mod tests {
assert!(token.unwrapped);
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &tok2_key).unwrap();
- assert_eq!(priv_meta, priv2);
+ assert_eq!(priv_meta, priv2.unwrap());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Option = may_load(&pub_store, &tok2_key).unwrap();
assert!(pub_meta.is_none());
@@ -4392,7 +4392,7 @@ mod tests {
error.contains("Not authorized to grant/revoke transfer permission for token MyNFT")
);
- let priv_expect = Metadata {
+ let priv_expect = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("MyNFT".to_string()),
@@ -4400,11 +4400,11 @@ mod tests {
image: Some("uri".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("MyNFT".to_string()),
owner: Some("alice".to_string()),
- private_metadata: Some(priv_expect.clone()),
+ private_metadata: priv_expect.clone(),
public_metadata: None,
royalty_info: None,
serial_number: None,
@@ -4604,7 +4604,7 @@ mod tests {
assert!(token.permissions.is_empty());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &tok_key).unwrap();
- assert_eq!(priv_meta, priv_expect.clone());
+ assert_eq!(priv_meta, priv_expect.clone().unwrap());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Option = may_load(&pub_store, &tok_key).unwrap();
assert!(pub_meta.is_none());
@@ -4693,7 +4693,7 @@ mod tests {
assert!(token.unwrapped);
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &tok_key).unwrap();
- assert_eq!(priv_meta, priv_expect.clone());
+ assert_eq!(priv_meta, priv_expect.clone().unwrap());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Option = may_load(&pub_store, &tok_key).unwrap();
assert!(pub_meta.is_none());
@@ -4727,7 +4727,7 @@ mod tests {
assert!(token.unwrapped);
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &tok_key).unwrap();
- assert_eq!(priv_meta, priv_expect.clone());
+ assert_eq!(priv_meta, priv_expect.clone().unwrap());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Option = may_load(&pub_store, &tok_key).unwrap();
assert!(pub_meta.is_none());
@@ -4772,7 +4772,7 @@ mod tests {
assert!(token.unwrapped);
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &tok_key).unwrap();
- assert_eq!(priv_meta, priv_expect);
+ assert_eq!(priv_meta, priv_expect.unwrap());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Option = may_load(&pub_store, &tok_key).unwrap();
assert!(pub_meta.is_none());
@@ -4794,7 +4794,7 @@ mod tests {
mock_info("admin", &[]),
execute_msg,
);
- let priv2 = Metadata {
+ let priv2 = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("MyNFT2".to_string()),
@@ -4802,11 +4802,11 @@ mod tests {
image: Some("uri2".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("MyNFT2".to_string()),
owner: Some("alice".to_string()),
- private_metadata: Some(priv2.clone()),
+ private_metadata: priv2.clone(),
public_metadata: None,
royalty_info: None,
serial_number: None,
@@ -4921,7 +4921,7 @@ mod tests {
assert!(token.unwrapped);
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &tok2_key).unwrap();
- assert_eq!(priv_meta, priv2);
+ assert_eq!(priv_meta, priv2.unwrap());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Option = may_load(&pub_store, &tok2_key).unwrap();
assert!(pub_meta.is_none());
@@ -5327,7 +5327,7 @@ mod tests {
mock_info("admin", &[]),
execute_msg,
);
- let priv3 = Metadata {
+ let priv3 = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("MyNFT3".to_string()),
@@ -5335,8 +5335,8 @@ mod tests {
image: Some("privuri3".to_string()),
..Extension::default()
}),
- };
- let pub3 = Metadata {
+ });
+ let pub3 = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("MyNFT3".to_string()),
@@ -5344,12 +5344,12 @@ mod tests {
image: Some("puburi3".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("MyNFT3".to_string()),
owner: Some("alice".to_string()),
- private_metadata: Some(priv3.clone()),
- public_metadata: Some(pub3.clone()),
+ private_metadata: priv3.clone(),
+ public_metadata: pub3.clone(),
royalty_info: None,
serial_number: None,
transferable: None,
@@ -5428,10 +5428,10 @@ mod tests {
assert!(token.unwrapped);
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &tok3_key).unwrap();
- assert_eq!(priv_meta, priv3);
+ assert_eq!(priv_meta, priv3.unwrap());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &tok3_key).unwrap();
- assert_eq!(pub_meta, pub3);
+ assert_eq!(pub_meta, pub3.unwrap());
// confirm the MyNFT2 metadata has been deleted from storage
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &tok2_key).unwrap();
@@ -6872,7 +6872,7 @@ mod tests {
let error = extract_error_msg(handle_result);
assert!(error.contains("You are not authorized to perform this action on token MyNFT"));
- let priv1 = Metadata {
+ let priv1 = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("MyNFT".to_string()),
@@ -6880,8 +6880,8 @@ mod tests {
image: Some("privuri".to_string()),
..Extension::default()
}),
- };
- let pub1 = Metadata {
+ });
+ let pub1 = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("MyNFT".to_string()),
@@ -6889,12 +6889,12 @@ mod tests {
image: Some("puburi".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("MyNFT".to_string()),
owner: Some("alice".to_string()),
- private_metadata: Some(priv1.clone()),
- public_metadata: Some(pub1.clone()),
+ private_metadata: priv1.clone(),
+ public_metadata: pub1.clone(),
royalty_info: None,
serial_number: None,
transferable: None,
@@ -7164,10 +7164,10 @@ mod tests {
// confirm the metadata is intact
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &tok_key).unwrap();
- assert_eq!(priv_meta, priv1.clone());
+ assert_eq!(priv_meta, priv1.clone().unwrap());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &tok_key).unwrap();
- assert_eq!(pub_meta, pub1.clone());
+ assert_eq!(pub_meta, pub1.clone().unwrap());
// confirm the tx was logged to all involved parties
let (txs, total) = get_txs(&deps.api, &deps.storage, &alice_raw, 0, 1).unwrap();
assert_eq!(total, 2);
@@ -7256,10 +7256,10 @@ mod tests {
// confirm the metadata is intact
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &tok_key).unwrap();
- assert_eq!(priv_meta, priv1);
+ assert_eq!(priv_meta, priv1.unwrap());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &tok_key).unwrap();
- assert_eq!(pub_meta, pub1);
+ assert_eq!(pub_meta, pub1.unwrap());
// confirm the tx was logged to all involved parties
let (txs, total) = get_txs(&deps.api, &deps.storage, &charlie_raw, 0, 10).unwrap();
assert_eq!(total, 1);
@@ -8589,7 +8589,7 @@ mod tests {
let error = extract_error_msg(handle_result);
assert!(error.contains("You are not authorized to perform this action on token MyNFT"));
- let priv1 = Metadata {
+ let priv1 = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("MyNFT".to_string()),
@@ -8597,8 +8597,8 @@ mod tests {
image: Some("privuri".to_string()),
..Extension::default()
}),
- };
- let pub1 = Metadata {
+ });
+ let pub1 = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("MyNFT".to_string()),
@@ -8606,12 +8606,12 @@ mod tests {
image: Some("puburi".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("MyNFT".to_string()),
owner: Some("alice".to_string()),
- private_metadata: Some(priv1.clone()),
- public_metadata: Some(pub1.clone()),
+ private_metadata: priv1.clone(),
+ public_metadata: pub1.clone(),
royalty_info: None,
serial_number: None,
transferable: None,
@@ -8862,10 +8862,10 @@ mod tests {
// confirm the metadata is intact
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &tok_key).unwrap();
- assert_eq!(priv_meta, priv1.clone());
+ assert_eq!(priv_meta, priv1.clone().unwrap());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &tok_key).unwrap();
- assert_eq!(pub_meta, pub1.clone());
+ assert_eq!(pub_meta, pub1.clone().unwrap());
// confirm the tx was logged to all involved parties
let (txs, total) = get_txs(&deps.api, &deps.storage, &alice_raw, 0, 1).unwrap();
assert_eq!(total, 2);
@@ -8978,10 +8978,10 @@ mod tests {
// confirm the metadata is intact
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Metadata = load(&priv_store, &tok_key).unwrap();
- assert_eq!(priv_meta, priv1);
+ assert_eq!(priv_meta, priv1.unwrap());
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &tok_key).unwrap();
- assert_eq!(pub_meta, pub1);
+ assert_eq!(pub_meta, pub1.unwrap());
// confirm the tx was logged to all involved parties
let (txs, total) = get_txs(&deps.api, &deps.storage, &charlie_raw, 0, 10).unwrap();
assert_eq!(total, 1);
@@ -11523,7 +11523,7 @@ mod tests {
let error = extract_error_msg(handle_result);
assert!(error.contains("You do not own token NFT1"));
- let pub1 = Metadata {
+ let pub1 = Some(Metadata {
token_uri: None,
extension: Some(Extension {
name: Some("My1".to_string()),
@@ -11531,11 +11531,11 @@ mod tests {
image: Some("URI 1".to_string()),
..Extension::default()
}),
- };
+ });
let execute_msg = ExecuteMsg::MintNft {
token_id: Some("NFT1".to_string()),
owner: Some("alice".to_string()),
- public_metadata: Some(pub1.clone()),
+ public_metadata: pub1.clone(),
private_metadata: None,
royalty_info: None,
serial_number: None,
@@ -11682,7 +11682,7 @@ mod tests {
assert!(token.unwrapped);
let pub_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PUB_META);
let pub_meta: Metadata = load(&pub_store, &nft1_key).unwrap();
- assert_eq!(pub_meta, pub1);
+ assert_eq!(pub_meta, pub1.unwrap());
let priv_store = ReadonlyPrefixedStorage::new(&deps.storage, PREFIX_PRIV_META);
let priv_meta: Option = may_load(&priv_store, &nft1_key).unwrap();
assert!(priv_meta.is_none());
diff --git a/contracts/external/snip721-roles-impl/.cargo/config b/contracts/external/snip721-roles-impl/.cargo/config
new file mode 100644
index 0000000..2a01f1d
--- /dev/null
+++ b/contracts/external/snip721-roles-impl/.cargo/config
@@ -0,0 +1,5 @@
+[alias]
+wasm = "build --release --target wasm32-unknown-unknown"
+unit-test = "test --lib"
+integration-test = "test --test integration"
+schema = "run --example schema"
diff --git a/contracts/external/snip721-roles-impl/.gitignore b/contracts/external/snip721-roles-impl/.gitignore
new file mode 100644
index 0000000..ea00b0b
--- /dev/null
+++ b/contracts/external/snip721-roles-impl/.gitignore
@@ -0,0 +1,21 @@
+# Build results
+/target
+contract.wasm
+contract.wasm.gz
+
+# Binaries
+*.wasm
+*.wasm.gz
+
+# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327)
+.cargo-ok
+
+# Text file backups
+**/*.rs.bk
+
+# macOS
+.DS_Store
+
+# IDEs
+*.iml
+.idea
diff --git a/contracts/external/snip721-roles-impl/Cargo.toml b/contracts/external/snip721-roles-impl/Cargo.toml
new file mode 100644
index 0000000..9628222
--- /dev/null
+++ b/contracts/external/snip721-roles-impl/Cargo.toml
@@ -0,0 +1,38 @@
+[package]
+name = "snip721-roles-impl"
+version = "1.0.0"
+authors = ["bill wincer"]
+edition = "2021"
+
+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.
+ "contract.wasm",
+ "hash.txt",
+]
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[features]
+# for quicker tests, cargo test --lib
+# for more explicit tests, cargo test --features=backtraces
+backtraces = ["cosmwasm-std/backtraces"]
+
+[dependencies]
+cosmwasm-std = {workspace=true }
+secret-toolkit = {workspace=true }
+cosmwasm-storage = {workspace=true }
+schemars = {workspace=true }
+serde = {workspace=true }
+bincode2 = "2.0.1"
+base64 = "0.21.2"
+primitive-types = { version = "0.12.2", default-features = false }
+dao-snip721-extensions ={ workspace = true }
+shade-protocol ={ workspace = true }
+cosmwasm-schema ={ workspace = true }
+
+
+[dev-dependencies]
+cosmwasm-schema = { workspace = true }
diff --git a/contracts/external/snip721-roles-impl/Makefile b/contracts/external/snip721-roles-impl/Makefile
new file mode 100644
index 0000000..13c5cd1
--- /dev/null
+++ b/contracts/external/snip721-roles-impl/Makefile
@@ -0,0 +1,66 @@
+SECRETCLI = docker exec -it secretdev /usr/bin/secretcli
+
+.PHONY: all
+all: clippy test
+
+.PHONY: check
+check:
+ cargo check
+
+.PHONY: clippy
+clippy:
+ cargo clippy
+
+.PHONY: test
+test: unit-test integration-test
+
+.PHONY: unit-test
+unit-test:
+ cargo test
+
+.PHONY: integration-test
+integration-test: compile-optimized
+ cargo test --test '*'
+
+.PHONY: list-code
+list-code:
+ $(SECRETCLI) query compute list-code
+
+.PHONY: compile _compile
+compile: _compile contract.wasm.gz
+_compile:
+ cargo build --target wasm32-unknown-unknown --locked
+ cp ./target/wasm32-unknown-unknown/debug/*.wasm ./contract.wasm
+
+.PHONY: compile-optimized _compile-optimized
+compile-optimized: _compile-optimized contract.wasm.gz
+_compile-optimized:
+ RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown --locked
+ @# The following line is not necessary, may work only on linux (extra size optimization)
+ wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm -o ./contract.wasm
+
+.PHONY: compile-optimized-reproducible
+compile-optimized-reproducible:
+ docker run --rm -v "$$(pwd)":/contract \
+ --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/code/target \
+ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
+ enigmampc/secret-contract-optimizer:1.0.10
+
+contract.wasm.gz: contract.wasm
+ cat ./contract.wasm | gzip -9 > ./contract.wasm.gz
+
+.PHONY: start-server
+start-server: # CTRL+C to stop
+ docker run -it --rm \
+ -p 26657:26657 -p 26656:26656 -p 1317:1317 \
+ -v $$(pwd):/root/code \
+ --name secretdev enigmampc/secret-network-sw-dev:latest
+
+.PHONY: schema
+schema:
+ cargo run --example schema
+
+.PHONY: clean
+clean:
+ cargo clean
+ rm -f ./contract.wasm ./contract.wasm.gz
diff --git a/contracts/external/snip721-roles-impl/src/contract.rs b/contracts/external/snip721-roles-impl/src/contract.rs
new file mode 100644
index 0000000..d9adaf7
--- /dev/null
+++ b/contracts/external/snip721-roles-impl/src/contract.rs
@@ -0,0 +1,5202 @@
+/// This contract implements SNIP-721 standard:
+/// https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-721.md
+use std::collections::HashSet;
+
+use base64::{engine::general_purpose, Engine as _};
+use cosmwasm_std::{
+ attr, to_binary, Addr, Api, Binary, BlockInfo, CanonicalAddr, CosmosMsg, Deps, DepsMut, Env,
+ MessageInfo, Response, StdError, StdResult, Storage, WasmMsg,
+};
+use cosmwasm_storage::{PrefixedStorage, ReadonlyPrefixedStorage};
+use primitive_types::U256;
+use schemars::JsonSchema;
+use secret_toolkit::{
+ crypto::sha_256,
+ permit::{validate, Permit, RevokedPermits},
+ utils::{pad_handle_result, pad_query_result},
+ viewing_key::{ViewingKey, ViewingKeyStore},
+};
+use serde::{Deserialize, Serialize};
+
+use crate::msg::{
+ AccessLevel, BatchNftDossierElement, Burn, ContractStatus, Cw721Approval, Cw721OwnerOfResponse,
+ ExecuteAnswer, ExecuteMsg, InstantiateMsg, Mint, QueryAnswer, QueryMsg, QueryWithPermit,
+ ReceiverInfo, ResponseStatus::Success, Send, Snip721Approval, Transfer, ViewerInfo,
+};
+use crate::receiver::{batch_receive_nft_msg, receive_nft_msg};
+use crate::royalties::{RoyaltyInfo, StoredRoyaltyInfo};
+use crate::state::{
+ get_txs, json_may_load, json_save, load, may_load, remove, save, store_burn, store_mint,
+ store_transfer, AuthList, Config, Permission, PermissionType, ReceiveRegistration, CONFIG_KEY,
+ CREATOR_KEY, DEFAULT_ROYALTY_KEY, MINTERS_KEY, PREFIX_ALL_PERMISSIONS, PREFIX_AUTHLIST,
+ PREFIX_INFOS, PREFIX_MAP_TO_ID, PREFIX_MAP_TO_INDEX, PREFIX_MINT_RUN, PREFIX_MINT_RUN_NUM,
+ PREFIX_OWNER_PRIV, PREFIX_PRIV_META, PREFIX_PUB_META, PREFIX_RECEIVERS, PREFIX_REVOKED_PERMITS,
+ PREFIX_ROYALTY_INFO, VIEWING_KEY_ERR_MSG,
+};
+use crate::token::{Metadata, Token};
+use crate::{
+ expiration::Expiration,
+ msg::{InstantiateResponse, Minters, NftInfo, NumTokens, OwnerOf},
+};
+use crate::{
+ inventory::{Inventory, InventoryIter},
+ msg::ContractInfo,
+};
+use crate::{
+ mint_run::{SerialNumber, StoredMintRunInfo},
+ state::Snip721Contract,
+};
+
+// enum used to return correct response from SetWhitelistedApproval
+pub enum SetAppResp {
+ SetWhitelistedApproval,
+ ApproveAll,
+ RevokeAll,
+}
+
+// table of bools used to alter AuthLists properly
+#[derive(Default)]
+pub struct AlterAuthTable {
+ // true if the specified token index should be added to an AuthList for that PermissionType
+ pub add: [bool; 3],
+ // true if all but the specified token index should be added to an AuthList for that PermType
+ pub full: [bool; 3],
+ // true if the specified token index should be removed from an AuthList for that PermType
+ pub remove: [bool; 3],
+ // true if the AuthList should be cleared for that Permission Type
+ pub clear: [bool; 3],
+ // true if there is at least one true in the table
+ pub has_update: bool,
+}
+
+// table of bools used to alter a permission list appropriately
+#[derive(Default)]
+pub struct AlterPermTable {
+ // true if the address should be added to the permission list for that PermissionType
+ pub add: [bool; 3],
+ // true if the address should be removed from the permission list for that PermissionType
+ pub remove: [bool; 3],
+ // true if there is at least one true in the table
+ pub has_update: bool,
+}
+
+// bundled info needed when setting accesses
+pub struct ProcessAccInfo {
+ // the input token or a default
+ pub token: Token,
+ // input token's mint index or a default
+ pub idx: u32,
+ // true if there was an input token
+ pub token_given: bool,
+ // the accesses being set
+ pub accesses: [Option; 3],
+ // optional expiration
+ pub expires: Option,
+ // true if this is an operator trying to set permissions
+ pub from_oper: bool,
+}
+
+// a receiver, their code hash, and whether they implement BatchReceiveNft
+pub struct CacheReceiverInfo {
+ // the contract address
+ pub contract: CanonicalAddr,
+ // the contract's registration info
+ pub registration: ReceiveRegistration,
+}
+
+// bundled info when prepping an authenticated token query
+pub struct TokenQueryInfo {
+ // querier's address
+ viewer_raw: Option,
+ // error message String
+ err_msg: String,
+ // the requested token
+ token: Token,
+ // the requested token's index
+ idx: u32,
+ // true if the contract has public ownership
+ owner_is_public: bool,
+}
+
+// permission type info
+pub struct PermissionTypeInfo {
+ // index for view owner permission
+ pub view_owner_idx: usize,
+ // index for view private metadata permission
+ pub view_meta_idx: usize,
+ // index for transfer permission
+ pub transfer_idx: usize,
+ // number of permission types
+ pub num_types: usize,
+}
+
+// an owner's inventory and the tokens they lost in this tx
+pub struct InventoryUpdate {
+ // owner's inventory
+ pub inventory: Inventory,
+ // the list of lost tokens
+ pub remove: HashSet,
+}
+// list of tokens sent from one previous owner
+pub struct SendFrom {
+ // the owner's address
+ pub owner: CanonicalAddr,
+ // the tokens that were sent
+ pub token_ids: Vec,
+}
+// used to cache owner information for dossier_list()
+pub struct OwnerInfo {
+ // the owner's address
+ pub owner: CanonicalAddr,
+ // the view_owner privacy override
+ pub owner_is_public: bool,
+ // inventory approvals
+ pub inventory_approvals: Vec,
+ // expiration for global view_owner approval if applicable
+ pub view_owner_exp: Option,
+ // expiration for global view_private_metadata approval if applicable
+ pub view_meta_exp: Option,
+}
+
+/// pad handle responses and log attributes to blocks of 256 bytes to prevent leaking info based on
+/// response size
+pub const BLOCK_SIZE: usize = 256;
+/// max number of token ids to keep in id list block
+pub const ID_BLOCK_SIZE: u32 = 64;
+
+////////////////////////////////////// Init ///////////////////////////////////////
+/// Returns InitResult
+///
+/// Initializes the contract
+///
+/// # Arguments
+///
+/// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+/// * `env` - Env of contract's environment
+/// * `info` - contract execution info for authorization - identity of the call, and payment.
+/// * `msg` - InitMsg passed in with the instantiation message
+
+impl
+ Snip721Contract
+where
+ QueryExt: JsonSchema,
+ MetadataExt: Clone + Serialize + for<'a> Deserialize<'a>,
+{
+ pub fn instantiate(
+ &self,
+ deps: DepsMut,
+ env: Env,
+ info: MessageInfo,
+ msg: InstantiateMsg,
+ ) -> StdResult {
+ self.query_auth
+ .save(deps.storage, &msg.query_auth.into_valid(deps.api)?)?;
+ let creator_raw = deps.api.addr_canonicalize(info.sender.as_str())?;
+ save(deps.storage, CREATOR_KEY, &creator_raw)?;
+ let admin_raw = msg
+ .admin
+ .map(|a| {
+ deps.api
+ .addr_canonicalize(deps.api.addr_validate(&a)?.as_str())
+ })
+ .transpose()?
+ .unwrap_or(creator_raw);
+ let prng_seed = sha_256(
+ general_purpose::STANDARD
+ .encode(msg.entropy.as_str())
+ .as_bytes(),
+ );
+ ViewingKey::set_seed(deps.storage, &prng_seed);
+
+ let init_config = msg.config.unwrap_or_default();
+
+ let config = Config {
+ name: msg.name,
+ symbol: msg.symbol,
+ admin: admin_raw.clone(),
+ mint_cnt: 0,
+ tx_cnt: 0,
+ token_cnt: 0,
+ status: ContractStatus::Normal.to_u8(),
+ token_supply_is_public: init_config.public_token_supply.unwrap_or(false),
+ owner_is_public: init_config.public_owner.unwrap_or(false),
+ sealed_metadata_is_enabled: init_config.enable_sealed_metadata.unwrap_or(false),
+ unwrap_to_private: init_config.unwrapped_metadata_is_private.unwrap_or(false),
+ minter_may_update_metadata: init_config.minter_may_update_metadata.unwrap_or(true),
+ owner_may_update_metadata: init_config.owner_may_update_metadata.unwrap_or(false),
+ burn_is_enabled: init_config.enable_burn.unwrap_or(false),
+ };
+
+ let minters = vec![admin_raw];
+ save(deps.storage, CONFIG_KEY, &config)?;
+ save(deps.storage, MINTERS_KEY, &minters)?;
+
+ if msg.royalty_info.is_some() {
+ self.store_royalties(
+ deps.storage,
+ deps.api,
+ msg.royalty_info.as_ref(),
+ None,
+ DEFAULT_ROYALTY_KEY,
+ )?;
+ }
+
+ // perform the post init callback if needed
+ let messages: Vec = if let Some(callback) = msg.post_init_callback {
+ let execute = WasmMsg::Execute {
+ msg: callback.msg,
+ contract_addr: callback.contract_address,
+ code_hash: callback.code_hash,
+ funds: callback.send,
+ };
+ vec![execute.into()]
+ } else {
+ Vec::new()
+ };
+ Ok(Response::new()
+ .add_messages(messages)
+ .set_data(to_binary(&InstantiateResponse {
+ contract_address: env.contract.address,
+ code_hash: env.contract.code_hash,
+ })?))
+ }
+
+ ///////////////////////////////////// Handle //////////////////////////////////////
+ /// Returns StdResult
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `env` - Env of contract's environment
+ /// * `info` - contract execution info for authorization - identity of the call, and payment.
+ /// * `msg` - HandleMsg passed in with the execute message
+ pub fn execute(
+ &self,
+ deps: DepsMut,
+ env: Env,
+ info: MessageInfo,
+ msg: ExecuteMsg,
+ ) -> StdResult {
+ let mut config: Config = load(deps.storage, CONFIG_KEY)?;
+
+ let response = match msg {
+ ExecuteMsg::MintNft {
+ token_id,
+ owner,
+ public_metadata,
+ private_metadata,
+ serial_number,
+ royalty_info,
+ transferable,
+ memo,
+ extension,
+ ..
+ } => self.mint(
+ deps,
+ &env,
+ &info.sender,
+ &mut config,
+ ContractStatus::Normal.to_u8(),
+ token_id,
+ owner,
+ public_metadata,
+ private_metadata,
+ serial_number,
+ royalty_info,
+ transferable,
+ memo,
+ extension,
+ ),
+ ExecuteMsg::BatchMintNft { mints, .. } => self.batch_mint(
+ deps,
+ &env,
+ &info.sender,
+ &mut config,
+ ContractStatus::Normal.to_u8(),
+ mints,
+ ),
+ ExecuteMsg::MintNftClones {
+ mint_run_id,
+ quantity,
+ owner,
+ public_metadata,
+ private_metadata,
+ royalty_info,
+ memo,
+ extension,
+ ..
+ } => self.mint_clones(
+ deps,
+ &env,
+ &info.sender,
+ &mut config,
+ ContractStatus::Normal.to_u8(),
+ mint_run_id.as_ref(),
+ quantity,
+ owner,
+ public_metadata,
+ private_metadata,
+ royalty_info,
+ memo,
+ extension,
+ ),
+ ExecuteMsg::SetMetadata {
+ token_id,
+ public_metadata,
+ private_metadata,
+ ..
+ } => self.set_metadata(
+ deps,
+ &info.sender,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ &token_id,
+ public_metadata,
+ private_metadata,
+ ),
+ ExecuteMsg::SetRoyaltyInfo {
+ token_id,
+ royalty_info,
+ ..
+ } => self.set_royalty_info(
+ deps,
+ &info.sender,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ token_id.as_deref(),
+ royalty_info.as_ref(),
+ ),
+ ExecuteMsg::Reveal { token_id, .. } => self.reveal(
+ deps,
+ &info.sender,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ &token_id,
+ ),
+ ExecuteMsg::MakeOwnershipPrivate { .. } => self.make_owner_private(
+ deps,
+ &info.sender,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ ),
+ ExecuteMsg::SetGlobalApproval {
+ token_id,
+ view_owner,
+ view_private_metadata,
+ expires,
+ ..
+ } => self.set_global_approval(
+ deps,
+ &env,
+ &info.sender,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ token_id,
+ view_owner,
+ view_private_metadata,
+ expires,
+ ),
+ ExecuteMsg::SetWhitelistedApproval {
+ address,
+ token_id,
+ view_owner,
+ view_private_metadata,
+ transfer,
+ expires,
+ ..
+ } => self.set_whitelisted_approval(
+ deps,
+ &env,
+ &info.sender,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ &address,
+ token_id,
+ view_owner,
+ view_private_metadata,
+ transfer,
+ expires,
+ SetAppResp::SetWhitelistedApproval,
+ ),
+ ExecuteMsg::Approve {
+ spender,
+ token_id,
+ expires,
+ ..
+ } => self.approve_revoke(
+ deps,
+ &env,
+ &info.sender,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ &spender,
+ &token_id,
+ expires,
+ true,
+ ),
+ ExecuteMsg::Revoke {
+ spender, token_id, ..
+ } => self.approve_revoke(
+ deps,
+ &env,
+ &info.sender,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ &spender,
+ &token_id,
+ None,
+ false,
+ ),
+ ExecuteMsg::ApproveAll {
+ operator, expires, ..
+ } => self.set_whitelisted_approval(
+ deps,
+ &env,
+ &info.sender,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ &operator,
+ None,
+ None,
+ None,
+ Some(AccessLevel::All),
+ expires,
+ SetAppResp::ApproveAll,
+ ),
+ ExecuteMsg::RevokeAll { operator, .. } => self.set_whitelisted_approval(
+ deps,
+ &env,
+ &info.sender,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ &operator,
+ None,
+ None,
+ None,
+ Some(AccessLevel::None),
+ None,
+ SetAppResp::RevokeAll,
+ ),
+ ExecuteMsg::TransferNft {
+ recipient,
+ token_id,
+ memo,
+ ..
+ } => self.transfer_nft(
+ deps,
+ &env,
+ &info.sender,
+ &mut config,
+ ContractStatus::Normal.to_u8(),
+ recipient,
+ token_id,
+ memo,
+ ),
+ ExecuteMsg::BatchTransferNft { transfers, .. } => self.batch_transfer_nft(
+ deps,
+ &env,
+ &info.sender,
+ &mut config,
+ ContractStatus::Normal.to_u8(),
+ transfers,
+ ),
+ ExecuteMsg::SendNft {
+ contract,
+ receiver_info,
+ token_id,
+ msg,
+ memo,
+ ..
+ } => self.send_nft(
+ deps,
+ &env,
+ &info.sender,
+ &mut config,
+ ContractStatus::Normal.to_u8(),
+ contract,
+ receiver_info,
+ token_id,
+ msg,
+ memo,
+ ),
+ ExecuteMsg::BatchSendNft { sends, .. } => self.batch_send_nft(
+ deps,
+ &env,
+ &info.sender,
+ &mut config,
+ ContractStatus::Normal.to_u8(),
+ sends,
+ ),
+ ExecuteMsg::RegisterReceiveNft {
+ code_hash,
+ also_implements_batch_receive_nft,
+ ..
+ } => self.register_receive_nft(
+ deps,
+ &info.sender,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ code_hash,
+ also_implements_batch_receive_nft,
+ ),
+ ExecuteMsg::BurnNft { token_id, memo, .. } => self.burn_nft(
+ deps,
+ &env,
+ &info.sender,
+ &mut config,
+ ContractStatus::Normal.to_u8(),
+ token_id,
+ memo,
+ ),
+ ExecuteMsg::BatchBurnNft { burns, .. } => self.batch_burn_nft(
+ deps,
+ &env,
+ &info.sender,
+ &mut config,
+ ContractStatus::Normal.to_u8(),
+ burns,
+ ),
+ ExecuteMsg::CreateViewingKey { entropy, .. } => self.create_key(
+ deps,
+ &env,
+ &info,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ &entropy,
+ ),
+ ExecuteMsg::SetViewingKey { key, .. } => self.set_key(
+ deps,
+ &info.sender,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ key,
+ ),
+ ExecuteMsg::AddMinters { minters, .. } => self.add_minters(
+ deps,
+ &info.sender,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ &minters,
+ ),
+ ExecuteMsg::RemoveMinters { minters, .. } => self.remove_minters(
+ deps,
+ &info.sender,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ &minters,
+ ),
+ ExecuteMsg::SetMinters { minters, .. } => self.set_minters(
+ deps,
+ &info.sender,
+ &config,
+ ContractStatus::StopTransactions.to_u8(),
+ &minters,
+ ),
+ ExecuteMsg::ChangeAdmin { address, .. } => self.change_admin(
+ deps,
+ &info.sender,
+ &mut config,
+ ContractStatus::StopTransactions.to_u8(),
+ &address,
+ ),
+ ExecuteMsg::SetContractStatus { level, .. } => {
+ self.set_contract_status(deps, &info.sender, &mut config, level)
+ }
+ ExecuteMsg::RevokePermit { permit_name, .. } => {
+ self.revoke_permit(deps.storage, &info.sender, &permit_name)
+ }
+ ExecuteMsg::Extension { msg: _ } => Ok(Response::default()),
+ };
+ pad_handle_result(response, BLOCK_SIZE)
+ }
+
+ /// Returns StdResult
+ ///
+ /// mint a new token
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `env` - a reference to the Env of contract's environment
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a mutable reference to the Config
+ /// * `priority` - u8 representation of highest status level this action is permitted at
+ /// * `token_id` - optional token id, if not specified, use token index
+ /// * `owner` - optional owner of this token, if not specified, use the minter's address
+ /// * `public_metadata` - optional public metadata viewable by everyone
+ /// * `private_metadata` - optional private metadata viewable only by owner and whitelist
+ /// * `serial_number` - optional serial number information for this token
+ /// * `royalty_info` - optional royalties information for this token
+ /// * `transferable` - optionally true if this token is transferable
+ /// * `memo` - optional memo for the mint tx
+ #[allow(clippy::too_many_arguments)]
+ pub fn mint(
+ &self,
+ deps: DepsMut,
+ env: &Env,
+ sender: &Addr,
+ config: &mut Config,
+ priority: u8,
+ token_id: Option,
+ owner: Option,
+ public_metadata: Option,
+ private_metadata: Option,
+ serial_number: Option,
+ royalty_info: Option,
+ transferable: Option,
+ memo: Option,
+ extension: MetadataExt,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ let minters: Vec = may_load(deps.storage, MINTERS_KEY)?.unwrap_or_default();
+ if !minters.contains(&sender_raw) {
+ return Err(StdError::generic_err(
+ "Only designated minters are allowed to mint",
+ ));
+ }
+ let mints = vec![Mint {
+ token_id,
+ owner,
+ public_metadata,
+ private_metadata,
+ serial_number,
+ royalty_info,
+ transferable,
+ memo,
+ extension,
+ }];
+ let mut minted = self.mint_list(deps, env, config, &sender_raw, mints)?;
+ let minted_str = minted.pop().unwrap_or_default();
+ Ok(Response::new()
+ .add_attributes(vec![attr("minted", &minted_str)])
+ .set_data(to_binary(&ExecuteAnswer::MintNft {
+ token_id: minted_str,
+ })?))
+ }
+
+ /// Returns StdResult
+ ///
+ /// mints many tokens
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `env` - a reference to the Env of contract's environment
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a mutable reference to the Config
+ /// * `priority` - u8 representation of highest ContractStatus level this action is permitted
+ /// * `mints` - the list of mints to perform
+ pub fn batch_mint(
+ &self,
+ deps: DepsMut,
+ env: &Env,
+ sender: &Addr,
+ config: &mut Config,
+ priority: u8,
+ mints: Vec>,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ let minters: Vec = may_load(deps.storage, MINTERS_KEY)?.unwrap_or_default();
+ if !minters.contains(&sender_raw) {
+ return Err(StdError::generic_err(
+ "Only designated minters are allowed to mint",
+ ));
+ }
+ let minted = self.mint_list(deps, env, config, &sender_raw, mints)?;
+ Ok(Response::new()
+ .add_attributes(vec![attr("minted", format!("{:?}", &minted))])
+ .set_data(to_binary(&ExecuteAnswer::BatchMintNft {
+ token_ids: minted,
+ })?))
+ }
+
+ /// Returns StdResult
+ ///
+ /// mints clones of a token
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `env` - a reference to the Env of contract's environment
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a mutable reference to the Config
+ /// * `priority` - u8 representation of highest status level this action is permitted at
+ /// * `mint_run_id` - optional id used to track subsequent mint runs
+ /// * `quantity` - number of clones to mint
+ /// * `owner` - optional owner of this token, if not specified, use the minter's address
+ /// * `public_metadata` - optional public metadata viewable by everyone
+ /// * `private_metadata` - optional private metadata viewable only by owner and whitelist
+ /// * `royalty_info` - optional royalties information for these clones
+ /// * `memo` - optional memo for the mint txs
+ #[allow(clippy::too_many_arguments)]
+ pub fn mint_clones(
+ &self,
+ deps: DepsMut,
+ env: &Env,
+ sender: &Addr,
+ config: &mut Config,
+ priority: u8,
+ mint_run_id: Option<&String>,
+ quantity: u32,
+ owner: Option,
+ public_metadata: Option,
+ private_metadata: Option,
+ royalty_info: Option,
+ memo: Option,
+ extension: MetadataExt,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ let minters: Vec = may_load(deps.storage, MINTERS_KEY)?.unwrap_or_default();
+ if !minters.contains(&sender_raw) {
+ return Err(StdError::generic_err(
+ "Only designated minters are allowed to mint",
+ ));
+ }
+ if quantity == 0 {
+ return Err(StdError::generic_err("Quantity can not be zero"));
+ }
+ let mint_run = mint_run_id
+ .map(|i| {
+ let key = i.as_bytes();
+ let mut run_store = PrefixedStorage::new(deps.storage, PREFIX_MINT_RUN_NUM);
+ let last_num: u32 = may_load(&run_store, key)?.unwrap_or(0);
+ let this_num: u32 = last_num.checked_add(1).ok_or_else(|| {
+ StdError::generic_err(format!(
+ "Mint run ID {} has already reached its maximum possible value",
+ i
+ ))
+ })?;
+ save(&mut run_store, key, &this_num)?;
+ Ok::(this_num)
+ })
+ .transpose()?;
+ let mut serial_number = SerialNumber {
+ mint_run,
+ serial_number: 1,
+ quantity_minted_this_run: Some(quantity),
+ };
+ let mut mints: Vec> = Vec::new();
+ for _ in 0..quantity {
+ mints.push(Mint {
+ token_id: None,
+ owner: owner.clone(),
+ public_metadata: public_metadata.clone(),
+ private_metadata: private_metadata.clone(),
+ serial_number: Some(serial_number.clone()),
+ royalty_info: royalty_info.clone(),
+ transferable: Some(true),
+ memo: memo.clone(),
+ extension: extension.clone(),
+ });
+ serial_number.serial_number += 1;
+ }
+ let mut minted = self.mint_list(deps, env, config, &sender_raw, mints)?;
+ // if mint_list did not error, there must be at least one token id
+ let first_minted = minted
+ .first()
+ .ok_or_else(|| StdError::generic_err("List of minted tokens is empty"))?
+ .clone();
+ let last_minted = minted
+ .pop()
+ .ok_or_else(|| StdError::generic_err("List of minted tokens is empty"))?;
+
+ Ok(Response::new()
+ .add_attributes(vec![
+ attr("first_minted", &first_minted),
+ attr("last_minted", &last_minted),
+ ])
+ .set_data(to_binary(&ExecuteAnswer::MintNftClones {
+ first_minted,
+ last_minted,
+ })?))
+ }
+
+ /// Returns StdResult
+ ///
+ /// sets new public and/or private metadata
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a reference to the Config
+ /// * `priority` - u8 representation of highest status level this action is permitted at
+ /// * `token_id` - token id String slice of token whose metadata should be updated
+ /// * `public_metadata` - the optional new public metadata viewable by everyone
+ /// * `private_metadata` - the optional new private metadata viewable by everyone
+ #[allow(clippy::too_many_arguments)]
+ pub fn set_metadata(
+ &self,
+ deps: DepsMut,
+ sender: &Addr,
+ config: &Config,
+ priority: u8,
+ token_id: &str,
+ public_metadata: Option,
+ private_metadata: Option,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let custom_err = format!("Not authorized to update metadata of token {}", token_id);
+ // if token supply is private, don't leak that the token id does not exist
+ // instead just say they are not authorized for that token
+ let opt_err = if config.token_supply_is_public {
+ None
+ } else {
+ Some(&*custom_err)
+ };
+ let (token, idx) = self.get_token(deps.storage, token_id, opt_err)?;
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ if !(token.owner == sender_raw && config.owner_may_update_metadata) {
+ let minters: Vec =
+ may_load(deps.storage, MINTERS_KEY)?.unwrap_or_default();
+ if !(minters.contains(&sender_raw) && config.minter_may_update_metadata) {
+ return Err(StdError::generic_err(custom_err));
+ }
+ }
+ if let Some(public) = public_metadata {
+ self.set_metadata_impl(deps.storage, &token, idx, PREFIX_PUB_META, &public)?;
+ }
+ if let Some(private) = private_metadata {
+ self.set_metadata_impl(deps.storage, &token, idx, PREFIX_PRIV_META, &private)?;
+ }
+ Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetMetadata { status: Success })?))
+ }
+
+ /// Returns StdResult
+ ///
+ /// sets new royalty information for a specified token or if no token ID is provided, sets new
+ /// royalty information as the contract's default
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a reference to the Config
+ /// * `priority` - u8 representation of highest status level this action is permitted at
+ /// * `token_id` - optional token id String slice of token whose royalty info should be updated
+ /// * `royalty_info` - a optional reference to the new RoyaltyInfo
+ pub fn set_royalty_info(
+ &self,
+ deps: DepsMut,
+ sender: &Addr,
+ config: &Config,
+ priority: u8,
+ token_id: Option<&str>,
+ royalty_info: Option<&RoyaltyInfo>,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ // set a token's royalties
+ if let Some(id) = token_id {
+ let custom_err = "A token's RoyaltyInfo may only be set by the token creator when they are also the token owner";
+ // if token supply is private, don't leak that the token id does not exist
+ // instead just say they are not authorized for that token
+ let opt_err = if config.token_supply_is_public {
+ None
+ } else {
+ Some(custom_err)
+ };
+ let (token, idx) = self.get_token(deps.storage, id, opt_err)?;
+ if !token.transferable {
+ return Err(StdError::generic_err(
+ "Non-transferable tokens can not be sold, so royalties are meaningless",
+ ));
+ }
+ let token_key = idx.to_le_bytes();
+ let run_store = ReadonlyPrefixedStorage::new(deps.storage, PREFIX_MINT_RUN);
+ let mint_run: StoredMintRunInfo = load(&run_store, &token_key)?;
+ if sender_raw != mint_run.token_creator || sender_raw != token.owner {
+ return Err(StdError::generic_err(custom_err));
+ }
+ let default_roy = royalty_info.as_ref().map_or_else(
+ || may_load::(deps.storage, DEFAULT_ROYALTY_KEY),
+ |_r| Ok(None),
+ )?;
+ let mut roy_store = PrefixedStorage::new(deps.storage, PREFIX_ROYALTY_INFO);
+ self.store_royalties(
+ &mut roy_store,
+ deps.api,
+ royalty_info,
+ default_roy.as_ref(),
+ &token_key,
+ )?;
+ // set default royalty
+ } else {
+ let minters: Vec =
+ may_load(deps.storage, MINTERS_KEY)?.unwrap_or_default();
+ if !minters.contains(&sender_raw) {
+ return Err(StdError::generic_err(
+ "Only designated minters can set default royalties for the contract",
+ ));
+ }
+ self.store_royalties(
+ deps.storage,
+ deps.api,
+ royalty_info,
+ None,
+ DEFAULT_ROYALTY_KEY,
+ )?;
+ };
+
+ Ok(
+ Response::new().set_data(to_binary(&ExecuteAnswer::SetRoyaltyInfo {
+ status: Success,
+ })?),
+ )
+ }
+
+ /// Returns StdResult
+ ///
+ /// makes the sealed private metadata public
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a reference to the Config
+ /// * `priority` - u8 representation of highest status level this action is permitted at
+ /// * `token_id` - token id String slice of token whose metadata should be updated
+ pub fn reveal(
+ &self,
+ deps: DepsMut,
+ sender: &Addr,
+ config: &Config,
+ priority: u8,
+ token_id: &str,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ if !config.sealed_metadata_is_enabled {
+ return Err(StdError::generic_err(
+ "Sealed metadata functionality is not enabled for this contract",
+ ));
+ }
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ let custom_err = format!("You do not own token {}", token_id);
+ // if token supply is private, don't leak that the token id does not exist
+ // instead just say they do not own that token
+ let opt_err = if config.token_supply_is_public {
+ None
+ } else {
+ Some(&*custom_err)
+ };
+ let (mut token, idx) = self.get_token(deps.storage, token_id, opt_err)?;
+ if token.unwrapped {
+ return Err(StdError::generic_err(
+ "This token has already been unwrapped",
+ ));
+ }
+ if token.owner != sender_raw {
+ return Err(StdError::generic_err(custom_err));
+ }
+ token.unwrapped = true;
+ let token_key = idx.to_le_bytes();
+ let mut info_store = PrefixedStorage::new(deps.storage, PREFIX_INFOS);
+ json_save(&mut info_store, &token_key, &token)?;
+ if !config.unwrap_to_private {
+ let mut priv_store = PrefixedStorage::new(deps.storage, PREFIX_PRIV_META);
+ let may_priv: Option = may_load(&priv_store, &token_key)?;
+ if let Some(metadata) = may_priv {
+ remove(&mut priv_store, &token_key);
+ let mut pub_store = PrefixedStorage::new(deps.storage, PREFIX_PUB_META);
+ save(&mut pub_store, &token_key, &metadata)?;
+ }
+ }
+ Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Reveal { status: Success })?))
+ }
+
+ /// Returns StdResult
+ ///
+ /// grants/revokes trasfer permission on a token
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `env` - a reference to the Env of contract's environment
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a reference to the Config
+ /// * `priority` - u8 representation of highest status level this action is permitted at
+ /// * `spender` - a reference to the address being granted permission
+ /// * `token_id` - string slice of the token id to grant permission to
+ /// * `expires` - optional Expiration for this approval
+ /// * `is_approve` - true if this is an Approve call
+ #[allow(clippy::too_many_arguments)]
+ pub fn approve_revoke(
+ &self,
+ deps: DepsMut,
+ env: &Env,
+ sender: &Addr,
+ config: &Config,
+ priority: u8,
+ spender: &str,
+ token_id: &str,
+ expires: Option,
+ is_approve: bool,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let address_raw = deps
+ .api
+ .addr_canonicalize(deps.api.addr_validate(spender)?.as_str())?;
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ let custom_err = format!(
+ "Not authorized to grant/revoke transfer permission for token {}",
+ token_id
+ );
+ // if token supply is private, don't leak that the token id does not exist
+ // instead just say they are not authorized for that token
+ let opt_err = if config.token_supply_is_public {
+ None
+ } else {
+ Some(&*custom_err)
+ };
+ let (token, idx) = self.get_token(deps.storage, token_id, opt_err)?;
+ let mut all_perm: Option> = None;
+ let mut from_oper = false;
+ let transfer_idx = PermissionType::Transfer.to_usize();
+ // if not called by the owner, check if message sender has operator status
+ if token.owner != sender_raw {
+ let all_store = ReadonlyPrefixedStorage::new(deps.storage, PREFIX_ALL_PERMISSIONS);
+ let may_list: Option> =
+ json_may_load(&all_store, token.owner.as_slice())?;
+ if let Some(list) = may_list.clone() {
+ if let Some(perm) = list.iter().find(|&p| p.address == sender_raw) {
+ if let Some(exp) = perm.expirations[transfer_idx] {
+ if exp.is_expired(&env.block) {
+ return Err(StdError::generic_err(format!(
+ "Transfer authority for all tokens of {} has expired",
+ &deps.api.addr_humanize(&token.owner)?
+ )));
+ } else {
+ from_oper = true;
+ }
+ }
+ }
+ }
+ if !from_oper {
+ return Err(StdError::generic_err(custom_err));
+ }
+ all_perm = may_list;
+ }
+ let mut accesses: [Option; 3] = [None, None, None];
+ let response: ExecuteAnswer;
+ if is_approve {
+ accesses[transfer_idx] = Some(AccessLevel::ApproveToken);
+ response = ExecuteAnswer::Approve { status: Success };
+ } else {
+ accesses[transfer_idx] = Some(AccessLevel::RevokeToken);
+ response = ExecuteAnswer::Revoke { status: Success };
+ }
+ let owner = token.owner.clone();
+ let mut proc_info = ProcessAccInfo {
+ token,
+ idx,
+ token_given: true,
+ accesses,
+ expires,
+ from_oper,
+ };
+ self.process_accesses(
+ deps.storage,
+ env,
+ &address_raw,
+ &owner,
+ &mut proc_info,
+ all_perm,
+ )?;
+ let res = Response::new().set_data(to_binary(&response)?);
+ Ok(res)
+ }
+
+ /// Returns StdResult
+ ///
+ /// makes an address' token ownership private
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a reference to the Config
+ /// * `priority` - u8 representation of highest status level this action is permitted at
+ pub fn make_owner_private(
+ &self,
+ deps: DepsMut,
+ sender: &Addr,
+ config: &Config,
+ priority: u8,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ // only need to do this if the contract has public ownership
+ if config.owner_is_public {
+ let mut priv_store = PrefixedStorage::new(deps.storage, PREFIX_OWNER_PRIV);
+ save(&mut priv_store, sender_raw.as_slice(), &false)?
+ }
+ Ok(
+ Response::new().set_data(to_binary(&ExecuteAnswer::MakeOwnershipPrivate {
+ status: Success,
+ })?),
+ )
+ }
+
+ /// Returns StdResult
+ ///
+ /// adds/revokes access for everyone
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `env` - a reference to the Env of contract's environment
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a reference to the Config
+ /// * `priority` - u8 representation of highest status level this action is permitted at
+ /// * `token_id` - optional token id to apply approvals to
+ /// * `view_owner` - optional access level for viewing token ownership
+ /// * `view_private_metadata` - optional access level for viewing private metadata
+ /// * `expires` - optional Expiration for this approval
+ #[allow(clippy::too_many_arguments)]
+ pub fn set_global_approval(
+ &self,
+ deps: DepsMut,
+ env: &Env,
+ sender: &Addr,
+ config: &Config,
+ priority: u8,
+ token_id: Option,
+ view_owner: Option,
+ view_private_metadata: Option,
+ expires: Option,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let token_given: bool;
+ // use this "address" to represent global permission
+ let global_raw = CanonicalAddr(Binary::from(b"public"));
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ let mut custom_err = String::new();
+ let (token, idx) = if let Some(id) = token_id {
+ token_given = true;
+ custom_err = format!("You do not own token {}", id);
+ // if token supply is private, don't leak that the token id does not exist
+ // instead just say they do not own that token
+ let opt_err = if config.token_supply_is_public {
+ None
+ } else {
+ Some(&*custom_err)
+ };
+ self.get_token(deps.storage, &id, opt_err)?
+ } else {
+ token_given = false;
+ (
+ Token {
+ owner: sender_raw.clone(),
+ permissions: Vec::new(),
+ unwrapped: false,
+ transferable: true,
+ },
+ 0,
+ )
+ };
+ // if trying to set token permissions when you are not the owner
+ if token_given && token.owner != sender_raw {
+ return Err(StdError::generic_err(custom_err));
+ }
+ let mut accesses: [Option; 3] = [None, None, None];
+ accesses[PermissionType::ViewOwner.to_usize()] = view_owner;
+ accesses[PermissionType::ViewMetadata.to_usize()] = view_private_metadata;
+ let mut proc_info = ProcessAccInfo {
+ token,
+ idx,
+ token_given,
+ accesses,
+ expires,
+ from_oper: false,
+ };
+ self.process_accesses(
+ deps.storage,
+ env,
+ &global_raw,
+ &sender_raw,
+ &mut proc_info,
+ None,
+ )?;
+ Ok(
+ Response::new().set_data(to_binary(&ExecuteAnswer::SetGlobalApproval {
+ status: Success,
+ })?),
+ )
+ }
+
+ /// Returns StdResult
+ ///
+ /// sets specified permissions for an address
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `env` - a reference to the Env of contract's environment
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a reference to the Config
+ /// * `priority` - u8 representation of highest status level this action is permitted at
+ /// * `address` - a reference to the address being granted permission
+ /// * `token_id` - optional token id to apply approvals to
+ /// * `view_owner` - optional access level for viewing token ownership
+ /// * `view_private_metadata` - optional access level for viewing private metadata
+ /// * `transfer` - optional access level for transferring tokens
+ /// * `expires` - optional Expiration for this approval
+ /// * `response_type` - which response to return for SetWhitelistedApproval, ApproveAll, or RevokeAll
+ #[allow(clippy::too_many_arguments)]
+ pub fn set_whitelisted_approval(
+ &self,
+ deps: DepsMut,
+ env: &Env,
+ sender: &Addr,
+ config: &Config,
+ priority: u8,
+ address: &str,
+ token_id: Option,
+ view_owner: Option,
+ view_private_metadata: Option,
+ transfer: Option,
+ expires: Option,
+ response_type: SetAppResp,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let token_given: bool;
+ let address_raw = deps
+ .api
+ .addr_canonicalize(deps.api.addr_validate(address)?.as_str())?;
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ let mut custom_err = String::new();
+ let (token, idx) = if let Some(id) = token_id {
+ token_given = true;
+ custom_err = format!("You do not own token {}", id);
+ // if token supply is private, don't leak that the token id does not exist
+ // instead just say they do not own that token
+ let opt_err = if config.token_supply_is_public {
+ None
+ } else {
+ Some(&*custom_err)
+ };
+ self.get_token(deps.storage, &id, opt_err)?
+ } else {
+ token_given = false;
+ (
+ Token {
+ owner: sender_raw.clone(),
+ permissions: Vec::new(),
+ unwrapped: false,
+ transferable: true,
+ },
+ 0,
+ )
+ };
+ // if trying to set token permissions when you are not the owner
+ if token_given && token.owner != sender_raw {
+ return Err(StdError::generic_err(custom_err));
+ }
+ let mut accesses: [Option; 3] = [None, None, None];
+ accesses[PermissionType::ViewOwner.to_usize()] = view_owner;
+ accesses[PermissionType::ViewMetadata.to_usize()] = view_private_metadata;
+ accesses[PermissionType::Transfer.to_usize()] = transfer;
+ let mut proc_info = ProcessAccInfo {
+ token,
+ idx,
+ token_given,
+ accesses,
+ expires,
+ from_oper: false,
+ };
+ self.process_accesses(
+ deps.storage,
+ env,
+ &address_raw,
+ &sender_raw,
+ &mut proc_info,
+ None,
+ )?;
+ let response = match response_type {
+ SetAppResp::SetWhitelistedApproval => {
+ ExecuteAnswer::SetWhitelistedApproval { status: Success }
+ }
+ SetAppResp::ApproveAll => ExecuteAnswer::ApproveAll { status: Success },
+ SetAppResp::RevokeAll => ExecuteAnswer::RevokeAll { status: Success },
+ };
+ let res = Response::new().set_data(to_binary(&response)?);
+ Ok(res)
+ }
+
+ /// Returns StdResult
+ ///
+ /// burns many tokens
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `env` - a reference to the Env of contract's environment
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a mutable reference to the Config
+ /// * `priority` - u8 representation of highest ContractStatus level this action is permitted
+ /// * `burns` - the list of burns to perform
+ pub fn batch_burn_nft(
+ &self,
+ deps: DepsMut,
+ env: &Env,
+ sender: &Addr,
+ config: &mut Config,
+ priority: u8,
+ burns: Vec,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ self.burn_list(deps, &env.block, config, &sender_raw, burns)?;
+ let res =
+ Response::new().set_data(to_binary(&ExecuteAnswer::BatchBurnNft { status: Success })?);
+ Ok(res)
+ }
+
+ /// Returns StdResult
+ ///
+ /// burns a token
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `env` - a reference to the Env of contract's environment
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a mutable reference to the Config
+ /// * `priority` - u8 representation of highest ContractStatus level this action is permitted
+ /// * `token_id` - token id String of token to be burnt
+ /// * `memo` - optional memo for the burn tx
+ #[allow(clippy::too_many_arguments)]
+ fn burn_nft(
+ &self,
+ deps: DepsMut,
+ env: &Env,
+ sender: &Addr,
+ config: &mut Config,
+ priority: u8,
+ token_id: String,
+ memo: Option,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ let burns = vec![Burn {
+ token_ids: vec![token_id],
+ memo,
+ }];
+ self.burn_list(deps, &env.block, config, &sender_raw, burns)?;
+ let res = Response::new().set_data(to_binary(&ExecuteAnswer::BurnNft { status: Success })?);
+ Ok(res)
+ }
+
+ /// Returns StdResult
+ ///
+ /// transfer many tokens
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `env` - a reference to the Env of contract's environment
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a mutable reference to the Config
+ /// * `priority` - u8 representation of highest ContractStatus level this action is permitted
+ /// * `transfers` - list of transfers to perform
+ pub fn batch_transfer_nft(
+ &self,
+ deps: DepsMut,
+ env: &Env,
+ sender: &Addr,
+ config: &mut Config,
+ priority: u8,
+ transfers: Vec,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let _m = self.send_list(deps, env, sender, config, Some(transfers), None)?;
+
+ let res = Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransferNft {
+ status: Success,
+ })?);
+ Ok(res)
+ }
+
+ /// Returns StdResult
+ ///
+ /// transfer a token
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `env` - a reference to the Env of contract's environment
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a mutable reference to the Config
+ /// * `priority` - u8 representation of highest ContractStatus level this action is permitted
+ /// * `recipient` - the address receiving the token
+ /// * `token_id` - token id String of token to be transferred
+ /// * `memo` - optional memo for the mint tx
+ #[allow(clippy::too_many_arguments)]
+ pub fn transfer_nft(
+ &self,
+ deps: DepsMut,
+ env: &Env,
+ sender: &Addr,
+ config: &mut Config,
+ priority: u8,
+ recipient: String,
+ token_id: String,
+ memo: Option,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let transfers = Some(vec![Transfer {
+ recipient,
+ token_ids: vec![token_id],
+ memo,
+ }]);
+ let _m = self.send_list(deps, env, sender, config, transfers, None)?;
+
+ let res =
+ Response::new().set_data(to_binary(&ExecuteAnswer::TransferNft { status: Success })?);
+ Ok(res)
+ }
+
+ /// Returns StdResult
+ ///
+ /// sends tokens to contracts, and calls those contracts' ReceiveNft. Will error if any
+ /// contract has not registered its ReceiveNft
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `env` - a reference to the Env of contract's environment
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a mutable reference to the Config
+ /// * `priority` - u8 representation of highest ContractStatus level this action is permitted
+ /// * `sends` - list of SendNfts to perform
+ fn batch_send_nft(
+ &self,
+ deps: DepsMut,
+ env: &Env,
+ sender: &Addr,
+ config: &mut Config,
+ priority: u8,
+ sends: Vec,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let messages = self.send_list(deps, env, sender, config, None, Some(sends))?;
+
+ let res = Response::new()
+ .add_messages(messages)
+ .set_data(to_binary(&ExecuteAnswer::BatchSendNft { status: Success })?);
+ Ok(res)
+ }
+
+ /// Returns StdResult
+ ///
+ /// sends a token to a contract, and calls that contract's ReceiveNft. Will error if the
+ /// contract has not registered its ReceiveNft
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `env` - a reference to the Env of contract's environment
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a mutable reference to the Config
+ /// * `priority` - u8 representation of highest ContractStatus level this action is permitted
+ /// * `contract` - the address of the contract receiving the token
+ /// * `receiver_info` - optional code hash and BatchReceiveNft implementation status of
+ /// the recipient contract
+ /// * `token_id` - ID String of the token that was sent
+ /// * `msg` - optional msg used to control ReceiveNft logic
+ /// * `memo` - optional memo for the mint tx
+ #[allow(clippy::too_many_arguments)]
+ fn send_nft(
+ &self,
+ deps: DepsMut,
+ env: &Env,
+ sender: &Addr,
+ config: &mut Config,
+ priority: u8,
+ contract: String,
+ receiver_info: Option,
+ token_id: String,
+ msg: Option,
+ memo: Option,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let sends = Some(vec![Send {
+ contract,
+ receiver_info,
+ token_ids: vec![token_id],
+ msg,
+ memo,
+ }]);
+ let messages = self.send_list(deps, env, sender, config, None, sends)?;
+
+ let res = Response::new()
+ .add_messages(messages)
+ .set_data(to_binary(&ExecuteAnswer::SendNft { status: Success })?);
+ Ok(res)
+ }
+
+ /// Returns StdResult
+ ///
+ /// registers a contract's ReceiveNft
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a reference to the Config
+ /// * `priority` - u8 representation of highest ContractStatus level this action is permitted
+ /// * `code_hash` - code hash String of the registering contract
+ /// * `impl_batch` - optionally true if the contract also implements BatchReceiveNft
+ pub fn register_receive_nft(
+ &self,
+ deps: DepsMut,
+ sender: &Addr,
+ config: &Config,
+ priority: u8,
+ code_hash: String,
+ impl_batch: Option,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ let regrec = ReceiveRegistration {
+ code_hash,
+ impl_batch: impl_batch.unwrap_or(false),
+ };
+ let mut store = PrefixedStorage::new(deps.storage, PREFIX_RECEIVERS);
+ save(&mut store, sender_raw.as_slice(), ®rec)?;
+ let res = Response::new().set_data(to_binary(&ExecuteAnswer::RegisterReceiveNft {
+ status: Success,
+ })?);
+ Ok(res)
+ }
+
+ /// Returns StdResult
+ ///
+ /// creates a viewing key
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `env` - a reference to the Env of contract's environment
+ /// * `info` - contract execution info for authorization - identity of the call, and payment.
+ /// * `config` - a reference to the Config
+ /// * `priority` - u8 representation of highest ContractStatus level this action is permitted
+ /// * `entropy` - string slice of the input String to be used as entropy in randomization
+ pub fn create_key(
+ &self,
+ deps: DepsMut,
+ env: &Env,
+ info: &MessageInfo,
+ config: &Config,
+ priority: u8,
+ entropy: &str,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let key = ViewingKey::create(
+ deps.storage,
+ info,
+ env,
+ info.sender.as_str(),
+ entropy.as_ref(),
+ );
+
+ Ok(Response::new().set_data(to_binary(&ExecuteAnswer::ViewingKey { key })?))
+ }
+
+ /// Returns StdResult
+ ///
+ /// sets the viewing key to the input String
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a reference to the Config
+ /// * `priority` - u8 representation of highest ContractStatus level this action is permitted
+ /// * `key` - String to be used as the viewing key
+ pub fn set_key(
+ &self,
+ deps: DepsMut,
+ sender: &Addr,
+ config: &Config,
+ priority: u8,
+ key: String,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ ViewingKey::set(deps.storage, sender.as_str(), &key);
+ Ok(Response::new().set_data(to_binary(&ExecuteAnswer::ViewingKey { key })?))
+ }
+
+ /// Returns StdResult
+ ///
+ /// add a list of minters
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a reference to the Config
+ /// * `priority` - u8 representation of highest ContractStatus level this action is permitted
+ /// * `new_minters` - list of minter addresses to add
+ pub fn add_minters(
+ &self,
+ deps: DepsMut,
+ sender: &Addr,
+ config: &Config,
+ priority: u8,
+ new_minters: &[String],
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ if config.admin != sender_raw {
+ return Err(StdError::generic_err(
+ "This is an admin command and can only be run from the admin address",
+ ));
+ }
+ let mut minters: Vec =
+ may_load(deps.storage, MINTERS_KEY)?.unwrap_or_default();
+ let mut update = false;
+ for minter in new_minters {
+ let minter_raw = deps
+ .api
+ .addr_canonicalize(deps.api.addr_validate(minter)?.as_str())?;
+ if !minters.contains(&minter_raw) {
+ minters.push(minter_raw);
+ update = true;
+ }
+ }
+ // only save if the list changed
+ if update {
+ save(deps.storage, MINTERS_KEY, &minters)?;
+ }
+ Ok(Response::new().set_data(to_binary(&ExecuteAnswer::AddMinters { status: Success })?))
+ }
+
+ /// Returns StdResult
+ ///
+ /// remove a list of minters
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a reference to the Config
+ /// * `priority` - u8 representation of highest ContractStatus level this action is permitted
+ /// * `no_minters` - list of minter addresses to remove
+ pub fn remove_minters(
+ &self,
+ deps: DepsMut,
+ sender: &Addr,
+ config: &Config,
+ priority: u8,
+ no_minters: &[String],
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ if config.admin != sender_raw {
+ return Err(StdError::generic_err(
+ "This is an admin command and can only be run from the admin address",
+ ));
+ }
+ let may_minters: Option> = may_load(deps.storage, MINTERS_KEY)?;
+ if let Some(mut minters) = may_minters {
+ let old_len = minters.len();
+ let no_raw: Vec = no_minters
+ .iter()
+ .map(|x| {
+ deps.api
+ .addr_canonicalize(deps.api.addr_validate(x)?.as_str())
+ })
+ .collect::>>()?;
+ minters.retain(|m| !no_raw.contains(m));
+ let new_len = minters.len();
+ if new_len > 0 {
+ if old_len != new_len {
+ save(deps.storage, MINTERS_KEY, &minters)?;
+ }
+ } else {
+ remove(deps.storage, MINTERS_KEY);
+ }
+ }
+ Ok(
+ Response::new().set_data(to_binary(&ExecuteAnswer::RemoveMinters {
+ status: Success,
+ })?),
+ )
+ }
+
+ /// Returns StdResult
+ ///
+ /// define the exact list of minters
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a reference to the Config
+ /// * `priority` - u8 representation of highest ContractStatus level this action is permitted
+ /// * `human_minters` - exact list of minter addresses
+ pub fn set_minters(
+ &self,
+ deps: DepsMut,
+ sender: &Addr,
+ config: &Config,
+ priority: u8,
+ human_minters: &[String],
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ if config.admin != sender_raw {
+ return Err(StdError::generic_err(
+ "This is an admin command and can only be run from the admin address",
+ ));
+ }
+ // remove duplicates from the minters list
+ let minters_raw: Vec = human_minters
+ .iter()
+ .map(|x| {
+ deps.api
+ .addr_canonicalize(deps.api.addr_validate(x)?.as_str())
+ })
+ .collect::>>()?;
+ let mut sortable: Vec<&[u8]> = minters_raw.iter().map(|x| x.as_slice()).collect();
+ sortable.sort_unstable();
+ sortable.dedup();
+ let minters: Vec = sortable
+ .iter()
+ .map(|x| CanonicalAddr(Binary(x.to_vec())))
+ .collect();
+ if minters.is_empty() {
+ remove(deps.storage, MINTERS_KEY);
+ } else {
+ save(deps.storage, MINTERS_KEY, &minters)?;
+ }
+ Ok(Response::new().set_data(to_binary(&ExecuteAnswer::SetMinters { status: Success })?))
+ }
+
+ /// Returns StdResult
+ ///
+ /// change the admin address
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a mutable reference to the Config
+ /// * `priority` - u8 representation of highest ContractStatus level this action is permitted
+ /// * `address` - new admin address
+ pub fn change_admin(
+ &self,
+ deps: DepsMut,
+ sender: &Addr,
+ config: &mut Config,
+ priority: u8,
+ address: &str,
+ ) -> StdResult {
+ self.check_status(config.status, priority)?;
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ if config.admin != sender_raw {
+ return Err(StdError::generic_err(
+ "This is an admin command and can only be run from the admin address",
+ ));
+ }
+ let new_admin = deps
+ .api
+ .addr_canonicalize(deps.api.addr_validate(address)?.as_str())?;
+ if new_admin != config.admin {
+ config.admin = new_admin;
+ save(deps.storage, CONFIG_KEY, &config)?;
+ }
+ Ok(Response::new().set_data(to_binary(&ExecuteAnswer::ChangeAdmin { status: Success })?))
+ }
+
+ /// Returns StdResult
+ ///
+ /// set the contract status level
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - mutable reference to Extern containing all the contract's external dependencies
+ /// * `sender` - a reference to the message sender address
+ /// * `config` - a mutable reference to the Config
+ /// * `level` - new ContractStatus
+ pub fn set_contract_status(
+ &self,
+ deps: DepsMut,
+ sender: &Addr,
+ config: &mut Config,
+ level: ContractStatus,
+ ) -> StdResult {
+ let sender_raw = deps.api.addr_canonicalize(sender.as_str())?;
+ if config.admin != sender_raw {
+ return Err(StdError::generic_err(
+ "This is an admin command and can only be run from the admin address",
+ ));
+ }
+ let new_status = level.to_u8();
+ if config.status != new_status {
+ config.status = new_status;
+ save(deps.storage, CONFIG_KEY, &config)?;
+ }
+ Ok(
+ Response::new().set_data(to_binary(&ExecuteAnswer::SetContractStatus {
+ status: Success,
+ })?),
+ )
+ }
+
+ /// Returns StdResult
+ ///
+ /// revoke the ability to use a specified permit
+ ///
+ /// # Arguments
+ ///
+ /// * `storage` - mutable reference to the contract's storage
+ /// * `sender` - a reference to the message sender address
+ /// * `permit_name` - string slice of the name of the permit to revoke
+ fn revoke_permit(
+ &self,
+ storage: &mut dyn Storage,
+ sender: &Addr,
+ permit_name: &str,
+ ) -> StdResult {
+ RevokedPermits::revoke_permit(
+ storage,
+ PREFIX_REVOKED_PERMITS,
+ sender.as_str(),
+ permit_name,
+ );
+
+ Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RevokePermit { status: Success })?))
+ }
+
+ /////////////////////////////////////// Query /////////////////////////////////////
+ /// Returns StdResult
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - reference to Extern containing all the contract's external dependencies
+ /// * `env` - Env of contract's environment
+ /// * `msg` - QueryMsg passed in with the query call
+ pub fn query(&self, deps: Deps, env: Env, msg: QueryMsg) -> StdResult {
+ let response = match msg {
+ QueryMsg::ContractInfo {} => self.query_contract_info(deps.storage),
+ QueryMsg::ContractCreator {} => self.query_contract_creator(deps),
+ QueryMsg::RoyaltyInfo { token_id, viewer } => {
+ self.query_royalty(deps, &env.block, token_id.as_deref(), viewer, None)
+ }
+ QueryMsg::ContractConfig {} => self.query_config(deps.storage),
+ QueryMsg::Minters {} => self.query_minters(deps),
+ QueryMsg::NumTokens { viewer } => self.query_num_tokens(deps, viewer, None),
+ QueryMsg::AllTokens {
+ viewer,
+ start_after,
+ limit,
+ } => self.query_all_tokens(deps, viewer, start_after.as_deref(), limit, None),
+ QueryMsg::OwnerOf {
+ token_id,
+ viewer,
+ include_expired,
+ } => self.query_owner_of(deps, &env.block, &token_id, viewer, include_expired, None),
+ QueryMsg::NftInfo { token_id } => self.query_nft_info(deps.storage, &token_id),
+ QueryMsg::PrivateMetadata { token_id, viewer } => {
+ self.query_private_meta(deps, &env.block, &token_id, viewer, None)
+ }
+ QueryMsg::AllNftInfo {
+ token_id,
+ viewer,
+ include_expired,
+ } => {
+ self.query_all_nft_info(deps, &env.block, &token_id, viewer, include_expired, None)
+ }
+ QueryMsg::NftDossier {
+ token_id,
+ viewer,
+ include_expired,
+ } => self.query_nft_dossier(deps, &env.block, token_id, viewer, include_expired, None),
+ QueryMsg::BatchNftDossier {
+ token_ids,
+ viewer,
+ include_expired,
+ } => self.query_batch_nft_dossier(
+ deps,
+ &env.block,
+ token_ids,
+ viewer,
+ include_expired,
+ None,
+ ),
+ QueryMsg::TokenApprovals {
+ token_id,
+ viewing_key,
+ include_expired,
+ } => self.query_token_approvals(
+ deps,
+ &env.block,
+ &token_id,
+ Some(&viewing_key),
+ include_expired,
+ None,
+ ),
+ QueryMsg::InventoryApprovals {
+ address,
+ viewing_key,
+ include_expired,
+ } => {
+ let viewer = Some(ViewerInfo {
+ address,
+ viewing_key,
+ });
+ self.query_inventory_approvals(deps, &env.block, viewer, include_expired, None)
+ }
+ QueryMsg::ApprovedForAll {
+ owner,
+ viewing_key,
+ include_expired,
+ } => self.query_approved_for_all(
+ deps,
+ &env.block,
+ Some(&owner),
+ viewing_key.as_deref(),
+ include_expired,
+ None,
+ ),
+ QueryMsg::Tokens {
+ owner,
+ viewer,
+ viewing_key,
+ start_after,
+ limit,
+ } => self.query_tokens(
+ deps,
+ &env.block,
+ &owner,
+ viewer.as_deref(),
+ viewing_key.as_deref(),
+ start_after.as_deref(),
+ limit,
+ None,
+ ),
+ QueryMsg::NumTokensOfOwner {
+ owner,
+ viewer,
+ viewing_key,
+ } => self.query_num_owner_tokens(
+ deps,
+ &env.block,
+ &owner,
+ viewer.as_deref(),
+ viewing_key.as_deref(),
+ None,
+ ),
+ QueryMsg::VerifyTransferApproval {
+ token_ids,
+ address,
+ viewing_key,
+ } => {
+ let viewer = Some(ViewerInfo {
+ address,
+ viewing_key,
+ });
+ self.query_verify_approval(deps, &env.block, token_ids, viewer, None)
+ }
+ QueryMsg::IsUnwrapped { token_id } => self.query_is_unwrapped(deps.storage, &token_id),
+ QueryMsg::IsTransferable { token_id } => {
+ self.query_is_transferable(deps.storage, &token_id)
+ }
+ QueryMsg::ImplementsNonTransferableTokens {} => {
+ to_binary(&QueryAnswer::ImplementsNonTransferableTokens { is_enabled: true })
+ }
+ QueryMsg::ImplementsTokenSubtype {} => {
+ to_binary(&QueryAnswer::ImplementsTokenSubtype { is_enabled: true })
+ }
+ QueryMsg::TransactionHistory {
+ address,
+ viewing_key,
+ page,
+ page_size,
+ } => {
+ let viewer = Some(ViewerInfo {
+ address,
+ viewing_key,
+ });
+ self.query_transactions(deps, viewer, page, page_size, None)
+ }
+ QueryMsg::RegisteredCodeHash { contract } => self.query_code_hash(deps, &contract),
+ QueryMsg::WithPermit { permit, query } => {
+ self.permit_queries(deps, &env, permit, query)
+ }
+ QueryMsg::QueryExtension { .. } => Ok(Binary::default()),
+ };
+ pad_query_result(response, BLOCK_SIZE)
+ }
+
+ /// Returns StdResult from validating a permit and then using its creator's address when
+ /// performing the specified query
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - a reference to Extern containing all the contract's external dependencies
+ /// * `env` - a reference to the Env of contract's environment
+ /// * `permit` - the permit used to authentic the query
+ /// * `query` - the query to perform
+ pub fn permit_queries(
+ &self,
+ deps: Deps,
+ env: &Env,
+ permit: Permit,
+ query: QueryWithPermit,
+ ) -> StdResult {
+ let querier = deps.api.addr_canonicalize(
+ deps.api
+ .addr_validate(&validate(
+ deps,
+ PREFIX_REVOKED_PERMITS,
+ &permit,
+ env.contract.address.to_string(),
+ Some("secret"),
+ )?)?
+ .as_str(),
+ )?;
+ if !permit.check_permission(&secret_toolkit::permit::TokenPermissions::Owner) {
+ return Err(StdError::generic_err(format!(
+ "Owner permission is required for SNIP-721 queries, got permissions {:?}",
+ permit.params.permissions
+ )));
+ }
+ let block = &env.block;
+ // permit validated, process query
+ match query {
+ QueryWithPermit::RoyaltyInfo { token_id } => {
+ self.query_royalty(deps, block, token_id.as_deref(), None, Some(querier))
+ }
+ QueryWithPermit::PrivateMetadata { token_id } => {
+ self.query_private_meta(deps, block, &token_id, None, Some(querier))
+ }
+ QueryWithPermit::NftDossier {
+ token_id,
+ include_expired,
+ } => {
+ self.query_nft_dossier(deps, block, token_id, None, include_expired, Some(querier))
+ }
+ QueryWithPermit::BatchNftDossier {
+ token_ids,
+ include_expired,
+ } => self.query_batch_nft_dossier(
+ deps,
+ block,
+ token_ids,
+ None,
+ include_expired,
+ Some(querier),
+ ),
+ QueryWithPermit::OwnerOf {
+ token_id,
+ include_expired,
+ } => self.query_owner_of(deps, block, &token_id, None, include_expired, Some(querier)),
+ QueryWithPermit::AllNftInfo {
+ token_id,
+ include_expired,
+ } => self.query_all_nft_info(
+ deps,
+ block,
+ &token_id,
+ None,
+ include_expired,
+ Some(querier),
+ ),
+ QueryWithPermit::InventoryApprovals { include_expired } => {
+ self.query_inventory_approvals(deps, block, None, include_expired, Some(querier))
+ }
+ QueryWithPermit::VerifyTransferApproval { token_ids } => {
+ self.query_verify_approval(deps, block, token_ids, None, Some(querier))
+ }
+ QueryWithPermit::TransactionHistory { page, page_size } => {
+ self.query_transactions(deps, None, page, page_size, Some(querier))
+ }
+ QueryWithPermit::NumTokens {} => self.query_num_tokens(deps, None, Some(querier)),
+ QueryWithPermit::AllTokens { start_after, limit } => {
+ self.query_all_tokens(deps, None, start_after.as_deref(), limit, Some(querier))
+ }
+ QueryWithPermit::TokenApprovals {
+ token_id,
+ include_expired,
+ } => self.query_token_approvals(
+ deps,
+ block,
+ &token_id,
+ None,
+ include_expired,
+ Some(querier),
+ ),
+ QueryWithPermit::ApprovedForAll { include_expired } => {
+ self.query_approved_for_all(deps, block, None, None, include_expired, Some(querier))
+ }
+ QueryWithPermit::Tokens {
+ owner,
+ start_after,
+ limit,
+ } => self.query_tokens(
+ deps,
+ block,
+ &owner,
+ None,
+ None,
+ start_after.as_deref(),
+ limit,
+ Some(querier),
+ ),
+ QueryWithPermit::NumTokensOfOwner { owner } => {
+ self.query_num_owner_tokens(deps, block, &owner, None, None, Some(querier))
+ }
+ }
+ }
+
+ /// Returns StdResult displaying the contract's creator
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - a reference to Extern containing all the contract's external dependencies
+ pub fn query_contract_creator(&self, deps: Deps) -> StdResult {
+ let creator_raw: CanonicalAddr = load(deps.storage, CREATOR_KEY)?;
+ to_binary(&QueryAnswer::ContractCreator {
+ creator: Some(deps.api.addr_humanize(&creator_raw)?),
+ })
+ }
+
+ /// Returns StdResult displaying the contract's name and symbol
+ ///
+ /// # Arguments
+ ///
+ /// * `storage` - a reference to the contract's storage
+ pub fn query_contract_info(&self, storage: &dyn Storage) -> StdResult {
+ let config: Config = load(storage, CONFIG_KEY)?;
+
+ to_binary(&ContractInfo {
+ name: config.name,
+ symbol: config.symbol,
+ })
+ }
+
+ /// Returns StdResult displaying either a token's royalty information or the contract's
+ /// default royalty information if no token_id is specified
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - a reference to Extern containing all the contract's external dependencies
+ /// * `block` - a reference to the BlockInfo
+ /// * `token_id` - optional token id whose RoyaltyInfo is being requested
+ /// * `viewer` - optional address and key making an authenticated query request
+ /// * `from_permit` - address derived from an Owner permit, if applicable
+ pub fn query_royalty(
+ &self,
+ deps: Deps,
+ block: &BlockInfo,
+ token_id: Option<&str>,
+ viewer: Option,
+ from_permit: Option,
+ ) -> StdResult {
+ let viewer_raw = self.get_querier(deps, viewer, from_permit)?;
+ let (royalty, hide_addr) = if let Some(id) = token_id {
+ // if the token id was found
+ if let Ok((token, idx)) = self.get_token(deps.storage, id, None) {
+ let hide_addr = self
+ .check_perm_core(
+ deps,
+ block,
+ &token,
+ id,
+ viewer_raw.as_ref(),
+ token.owner.as_slice(),
+ PermissionType::Transfer.to_usize(),
+ &mut Vec::new(),
+ "",
+ )
+ .is_err();
+ // get the royalty information if present
+ let roy_store = ReadonlyPrefixedStorage::new(deps.storage, PREFIX_ROYALTY_INFO);
+ (
+ may_load::(&roy_store, &idx.to_le_bytes())?,
+ hide_addr,
+ )
+ // token id not found
+ } else {
+ let config: Config = load(deps.storage, CONFIG_KEY)?;
+ let minters: Vec =
+ may_load(deps.storage, MINTERS_KEY)?.unwrap_or_default();
+ let is_minter = viewer_raw.map(|v| minters.contains(&v)).unwrap_or(false);
+ // if minter querying or the token supply is public, let them know the token does not exist
+ if config.token_supply_is_public || is_minter {
+ return Err(StdError::generic_err(format!("Token ID: {} not found", id)));
+ }
+ // token supply is private and querier is not a minter so just show the default without addresses
+ (
+ may_load::(deps.storage, DEFAULT_ROYALTY_KEY)?,
+ true,
+ )
+ }
+ // no id specified, so get the default
+ } else {
+ let minters: Vec =
+ may_load(deps.storage, MINTERS_KEY)?.unwrap_or_default();
+ // only let minters view default royalty addresses
+ (
+ may_load::(deps.storage, DEFAULT_ROYALTY_KEY)?,
+ viewer_raw.map(|v| !minters.contains(&v)).unwrap_or(true),
+ )
+ };
+ to_binary(&QueryAnswer::RoyaltyInfo {
+ royalty_info: royalty
+ .map(|s| s.to_human(deps.api, hide_addr))
+ .transpose()?,
+ })
+ }
+
+ /// Returns StdResult displaying the contract's configuration
+ ///
+ /// # Arguments
+ ///
+ /// * `storage` - a reference to the contract's storage
+ pub fn query_config(&self, storage: &dyn Storage) -> StdResult {
+ let config: Config = load(storage, CONFIG_KEY)?;
+
+ to_binary(&QueryAnswer::ContractConfig {
+ token_supply_is_public: config.token_supply_is_public,
+ owner_is_public: config.owner_is_public,
+ sealed_metadata_is_enabled: config.sealed_metadata_is_enabled,
+ unwrapped_metadata_is_private: config.unwrap_to_private,
+ minter_may_update_metadata: config.minter_may_update_metadata,
+ owner_may_update_metadata: config.owner_may_update_metadata,
+ burn_is_enabled: config.burn_is_enabled,
+ implements_non_transferable_tokens: true,
+ implements_token_subtype: true,
+ })
+ }
+
+ /// Returns StdResult displaying the list of authorized minters
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - a reference to Extern containing all the contract's external dependencies
+ pub fn query_minters(&self, deps: Deps) -> StdResult {
+ let minters: Vec = may_load(deps.storage, MINTERS_KEY)?.unwrap_or_default();
+
+ to_binary(&Minters {
+ minters: minters
+ .iter()
+ .map(|m| deps.api.addr_humanize(m))
+ .collect::>>()?,
+ })
+ }
+
+ /// Returns StdResult displaying the number of tokens the contract controls
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - a reference to Extern containing all the contract's external dependencies
+ /// * `viewer` - optional address and key making an authenticated query request
+ /// * `from_permit` - address derived from an Owner permit, if applicable
+ pub fn query_num_tokens(
+ &self,
+ deps: Deps,
+ viewer: Option,
+ from_permit: Option,
+ ) -> StdResult {
+ // authenticate permission to view token supply
+ self.check_view_supply(deps, viewer, from_permit)?;
+ let config: Config = load(deps.storage, CONFIG_KEY)?;
+ to_binary(&NumTokens {
+ count: config.token_cnt,
+ })
+ }
+
+ /// Returns StdResult displaying the list of tokens that the contract controls
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - a reference to Extern containing all the contract's external dependencies
+ /// * `viewer` - optional address and key making an authenticated query request
+ /// * `start_after` - optionally only display token ids that come after this one
+ /// * `limit` - optional max number of tokens to display
+ /// * `from_permit` - address derived from an Owner permit, if applicable
+ pub fn query_all_tokens(
+ &self,
+ deps: Deps,
+ viewer: Option,
+ start_after: Option<&str>,
+ limit: Option,
+ from_permit: Option,
+ ) -> StdResult {
+ // authenticate permission to view token supply
+ self.check_view_supply(deps, viewer, from_permit)?;
+ let mut i = start_after.map_or_else(
+ || Ok(0),
+ |id| {
+ let map2idx = ReadonlyPrefixedStorage::new(deps.storage, PREFIX_MAP_TO_INDEX);
+ let idx: u32 = may_load(&map2idx, id.as_bytes())?
+ .ok_or_else(|| StdError::generic_err(format!("Token ID: {} not found", id)))?;
+ idx.checked_add(1).ok_or_else(|| {
+ StdError::generic_err("This token was the last one the contract could mint")
+ })
+ },
+ )?;
+ let cut_off = limit.unwrap_or(300);
+ let config: Config = load(deps.storage, CONFIG_KEY)?;
+ let mut tokens = Vec::new();
+ let mut count = 0u32;
+ let map2id = ReadonlyPrefixedStorage::new(deps.storage, PREFIX_MAP_TO_ID);
+ while count < cut_off && i < config.mint_cnt {
+ if let Some(id) = may_load::(&map2id, &i.to_le_bytes())? {
+ tokens.push(id);
+ // will hit gas ceiling before the count overflows
+ count += 1;
+ }
+ // i can't overflow if it was less than a u32
+ i += 1;
+ }
+ to_binary(&QueryAnswer::TokenList { tokens })
+ }
+
+ /// Returns StdResult displaying the owner of the input token if the requester is authorized
+ /// to view it and the transfer approvals on this token if the owner is querying
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - a reference to Extern containing all the contract's external dependencies
+ /// * `block` - a reference to the BlockInfo
+ /// * `token_id` - string slice of the token id
+ /// * `viewer` - optional address and key making an authenticated query request
+ /// * `include_expired` - optionally true if the Approval lists should include expired Approvals
+ /// * `from_permit` - address derived from an Owner permit, if applicable
+ pub fn query_owner_of(
+ &self,
+ deps: Deps,
+ block: &BlockInfo,
+ token_id: &str,
+ viewer: Option,
+ include_expired: Option,
+ from_permit: Option,
+ ) -> StdResult {
+ let (may_owner, approvals, _idx) = self.process_cw721_owner_of(
+ deps,
+ block,
+ token_id,
+ viewer,
+ include_expired,
+ from_permit,
+ )?;
+ if let Some(owner) = may_owner {
+ return to_binary(&OwnerOf { owner, approvals });
+ }
+ Err(StdError::generic_err(format!(
+ "You are not authorized to view the owner of token {}",
+ token_id
+ )))
+ }
+
+ /// Returns StdResult displaying the public metadata of a token
+ ///
+ /// # Arguments
+ ///
+ /// * `storage` - a reference to the contract's storage
+ /// * `token_id` - string slice of the token id
+ pub fn query_nft_info(&self, storage: &dyn Storage, token_id: &str) -> StdResult {
+ let map2idx = ReadonlyPrefixedStorage::new(storage, PREFIX_MAP_TO_INDEX);
+ let may_idx: Option = may_load(&map2idx, token_id.as_bytes())?;
+ // if token id was found
+ if let Some(idx) = may_idx {
+ let meta_store = ReadonlyPrefixedStorage::new(storage, PREFIX_PUB_META);
+ let metadata_info = self
+ .token_extension_info
+ .get(storage, &token_id.to_string());
+ let meta: Metadata = may_load(&meta_store, &idx.to_le_bytes())?.unwrap_or(Metadata {
+ token_uri: None,
+ extension: None,
+ });
+ return to_binary(&NftInfo {
+ token_uri: meta.token_uri,
+ extension: meta.extension,
+ metadata_extension: metadata_info.unwrap(),
+ });
+ }
+ let config: Config = load(storage, CONFIG_KEY)?;
+ // token id wasn't found
+ // if the token supply is public, let them know the token does not exist
+ if config.token_supply_is_public {
+ return Err(StdError::generic_err(format!(
+ "Token ID: {} not found",
+ token_id
+ )));
+ }
+ // otherwise, just return empty metadata
+ to_binary(&QueryAnswer::NftInfo {
+ token_uri: None,
+ extension: None,
+ })
+ }
+
+ /// Returns StdResult displaying the private metadata of a token if permitted to
+ /// view it
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - a reference to Extern containing all the contract's external dependencies
+ /// * `block` - a reference to the BlockInfo
+ /// * `token_id` - string slice of the token id
+ /// * `viewer` - optional address and key making an authenticated query request
+ /// * `from_permit` - address derived from an Owner permit, if applicable
+ pub fn query_private_meta(
+ &self,
+ deps: Deps,
+ block: &BlockInfo,
+ token_id: &str,
+ viewer: Option,
+ from_permit: Option,
+ ) -> StdResult {
+ let prep_info = self.query_token_prep(deps, token_id, viewer, from_permit)?;
+ self.check_perm_core(
+ deps,
+ block,
+ &prep_info.token,
+ token_id,
+ prep_info.viewer_raw.as_ref(),
+ prep_info.token.owner.as_slice(),
+ PermissionType::ViewMetadata.to_usize(),
+ &mut Vec::new(),
+ &prep_info.err_msg,
+ )?;
+ // don't display if private metadata is sealed
+ if !prep_info.token.unwrapped {
+ return Err(StdError::generic_err(
+ "Sealed metadata must be unwrapped by calling Reveal before it can be viewed",
+ ));
+ }
+ let meta_store = ReadonlyPrefixedStorage::new(deps.storage, PREFIX_PRIV_META);
+ let meta: Metadata =
+ may_load(&meta_store, &prep_info.idx.to_le_bytes())?.unwrap_or(Metadata {
+ token_uri: None,
+ extension: None,
+ });
+ to_binary(&QueryAnswer::PrivateMetadata {
+ token_uri: meta.token_uri,
+ extension: meta.extension,
+ })
+ }
+
+ /// Returns StdResult displaying response of both the OwnerOf and NftInfo queries
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - a reference to Extern containing all the contract's external dependencies
+ /// * `block` - a reference to the BlockInfo
+ /// * `token_id` - string slice of the token id
+ /// * `viewer` - optional address and key making an authenticated query request
+ /// * `include_expired` - optionally true if the Approval lists should include expired Approvals
+ /// * `from_permit` - address derived from an Owner permit, if applicable
+ pub fn query_all_nft_info(
+ &self,
+ deps: Deps,
+ block: &BlockInfo,
+ token_id: &str,
+ viewer: Option,
+ include_expired: Option,
+ from_permit: Option,
+ ) -> StdResult {
+ let (owner, approvals, idx) = self.process_cw721_owner_of(
+ deps,
+ block,
+ token_id,
+ viewer,
+ include_expired,
+ from_permit,
+ )?;
+ let meta_store = ReadonlyPrefixedStorage::new(deps.storage, PREFIX_PUB_META);
+ let info: Option = may_load(&meta_store, &idx.to_le_bytes())?;
+ let access = Cw721OwnerOfResponse { owner, approvals };
+ to_binary(&QueryAnswer::AllNftInfo { access, info })
+ }
+
+ /// Returns StdResult displaying all the token information the querier is permitted to
+ /// view. This may include the owner, the public metadata, the private metadata, royalty
+ /// information, mint run information, whether the token is unwrapped, whether the token is
+ /// transferable, and the token and inventory approvals
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - a reference to Extern containing all the contract's external dependencies
+ /// * `block` - a reference to the BlockInfo
+ /// * `token_id` - the token id
+ /// * `viewer` - optional address and key making an authenticated query request
+ /// * `include_expired` - optionally true if the Approval lists should include expired Approvals
+ /// * `from_permit` - address derived from an Owner permit, if applicable
+ pub fn query_nft_dossier(
+ &self,
+ deps: Deps,
+ block: &BlockInfo,
+ token_id: String,
+ viewer: Option,
+ include_expired: Option,
+ from_permit: Option,
+ ) -> StdResult {
+ let dossier = self
+ .dossier_list(
+ deps,
+ block,
+ vec![token_id],
+ viewer,
+ include_expired,
+ from_permit,
+ )?
+ .pop()
+ .ok_or_else(|| {
+ StdError::generic_err("NftDossier can never return an empty dossier list")
+ })?;
+
+ to_binary(&QueryAnswer::NftDossier {
+ owner: dossier.owner,
+ public_metadata: dossier.public_metadata,
+ private_metadata: dossier.private_metadata,
+ royalty_info: dossier.royalty_info,
+ mint_run_info: dossier.mint_run_info,
+ transferable: dossier.transferable,
+ unwrapped: dossier.unwrapped,
+ display_private_metadata_error: dossier.display_private_metadata_error,
+ owner_is_public: dossier.owner_is_public,
+ public_ownership_expiration: dossier.public_ownership_expiration,
+ private_metadata_is_public: dossier.private_metadata_is_public,
+ private_metadata_is_public_expiration: dossier.private_metadata_is_public_expiration,
+ token_approvals: dossier.token_approvals,
+ inventory_approvals: dossier.inventory_approvals,
+ })
+ }
+
+ /// Returns StdResult displaying all the token information the querier is permitted to
+ /// view of multiple tokens. This may include the owner, the public metadata, the private metadata,
+ /// royalty information, mint run information, whether the token is unwrapped, whether the token is
+ /// transferable, and the token and inventory approvals
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - a reference to Extern containing all the contract's external dependencies
+ /// * `block` - a reference to the BlockInfo
+ /// * `token_ids` - list of token ids whose info should be retrieved
+ /// * `viewer` - optional address and key making an authenticated query request
+ /// * `include_expired` - optionally true if the Approval lists should include expired Approvals
+ /// * `from_permit` - address derived from an Owner permit, if applicable
+ pub fn query_batch_nft_dossier(
+ &self,
+ deps: Deps,
+ block: &BlockInfo,
+ token_ids: Vec,
+ viewer: Option,
+ include_expired: Option,
+ from_permit: Option,
+ ) -> StdResult {
+ let nft_dossiers =
+ self.dossier_list(deps, block, token_ids, viewer, include_expired, from_permit)?;
+
+ to_binary(&QueryAnswer::BatchNftDossier { nft_dossiers })
+ }
+
+ /// Returns StdResult displaying the approvals in place for a specified token
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - a reference to Extern containing all the contract's external dependencies
+ /// * `block` - a reference to the BlockInfo
+ /// * `token_id` - string slice of the token id
+ /// * `viewing_key` - the optional token owner's viewing key
+ /// * `include_expired` - optionally true if the Approval lists should include expired Approvals
+ /// * `from_permit` - address derived from an Owner permit, if applicable
+ pub fn query_token_approvals(
+ &self,
+ deps: Deps,
+ block: &BlockInfo,
+ token_id: &str,
+ viewing_key: Option<&str>,
+ include_expired: Option,
+ from_permit: Option,
+ ) -> StdResult {
+ let config: Config = load(deps.storage, CONFIG_KEY)?;
+ let custom_err = format!(
+ "You are not authorized to view approvals for token {}",
+ token_id
+ );
+ // if token supply is private, don't leak that the token id does not exist
+ // instead just say they are not authorized for that token
+ let opt_err = if config.token_supply_is_public {
+ None
+ } else {
+ Some(&*custom_err)
+ };
+ let (mut token, _idx) = self.get_token(deps.storage, token_id, opt_err)?;
+ // verify that the querier is the token owner
+ if let Some(pmt) = from_permit {
+ if pmt != token.owner {
+ return Err(StdError::generic_err(custom_err));
+ }
+ } else {
+ let key = viewing_key.ok_or_else(|| {
+ StdError::generic_err("This is being called incorrectly if there is no viewing key")
+ })?;
+ let owner_addr = deps.api.addr_humanize(&token.owner)?;
+ ViewingKey::check(deps.storage, owner_addr.as_str(), key)
+ .map_err(|_| StdError::generic_err(VIEWING_KEY_ERR_MSG))?;
+ }
+ let owner_slice = token.owner.as_slice();
+ let own_priv_store = ReadonlyPrefixedStorage::new(deps.storage, PREFIX_OWNER_PRIV);
+ let global_pass: bool =
+ may_load(&own_priv_store, owner_slice)?.unwrap_or(config.owner_is_public);
+ let perm_type_info = PermissionTypeInfo {
+ view_owner_idx: PermissionType::ViewOwner.to_usize(),
+ view_meta_idx: PermissionType::ViewMetadata.to_usize(),
+ transfer_idx: PermissionType::Transfer.to_usize(),
+ num_types: PermissionType::Transfer.num_types(),
+ };
+ let incl_exp = include_expired.unwrap_or(false);
+ let (token_approvals, token_owner_exp, token_meta_exp) = self.gen_snip721_approvals(
+ deps.api,
+ block,
+ &mut token.permissions,
+ incl_exp,
+ &perm_type_info,
+ )?;
+ let all_store = ReadonlyPrefixedStorage::new(deps.storage, PREFIX_ALL_PERMISSIONS);
+ let mut all_perm: Vec =
+ json_may_load(&all_store, owner_slice)?.unwrap_or_default();
+ let (_inventory_approv, all_owner_exp, all_meta_exp) =
+ self.gen_snip721_approvals(deps.api, block, &mut all_perm, incl_exp, &perm_type_info)?;
+ // determine if ownership is public
+ let (public_ownership_expiration, owner_is_public) = if global_pass {
+ (Some(Expiration::Never), true)
+ } else if token_owner_exp.is_some() {
+ (token_owner_exp, true)
+ } else {
+ (all_owner_exp, all_owner_exp.is_some())
+ };
+ // determine if private metadata is public
+ let (private_metadata_is_public_expiration, private_metadata_is_public) =
+ if token_meta_exp.is_some() {
+ (token_meta_exp, true)
+ } else {
+ (all_meta_exp, all_meta_exp.is_some())
+ };
+ to_binary(&QueryAnswer::TokenApprovals {
+ owner_is_public,
+ public_ownership_expiration,
+ private_metadata_is_public,
+ private_metadata_is_public_expiration,
+ token_approvals,
+ })
+ }
+
+ /// Returns StdResult displaying the inventory-wide approvals for a specified address
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - a reference to Extern containing all the contract's external dependencies
+ /// * `block` - a reference to the BlockInfo
+ /// * `viewer` - optional address and key making an authenticated query request
+ /// * `include_expired` - optionally true if the Approval lists should include expired Approvals
+ /// * `from_permit` - address derived from an Owner permit, if applicable
+ pub fn query_inventory_approvals(
+ &self,
+ deps: Deps,
+ block: &BlockInfo,
+ viewer: Option,
+ include_expired: Option,
+ from_permit: Option,
+ ) -> StdResult {
+ let owner_raw = self
+ .get_querier(deps, viewer, from_permit)?
+ .ok_or_else(|| {
+ StdError::generic_err(
+ "This is being called incorrectly if there is no querier address",
+ )
+ })?;
+ let owner_slice = owner_raw.as_slice();
+ let own_priv_store = ReadonlyPrefixedStorage::new(deps.storage, PREFIX_OWNER_PRIV);
+ let config: Config = load(deps.storage, CONFIG_KEY)?;
+ let global_pass: bool =
+ may_load(&own_priv_store, owner_slice)?.unwrap_or(config.owner_is_public);
+ let all_store = ReadonlyPrefixedStorage::new(deps.storage, PREFIX_ALL_PERMISSIONS);
+ let mut all_perm: Vec =
+ json_may_load(&all_store, owner_slice)?.unwrap_or_default();
+ let perm_type_info = PermissionTypeInfo {
+ view_owner_idx: PermissionType::ViewOwner.to_usize(),
+ view_meta_idx: PermissionType::ViewMetadata.to_usize(),
+ transfer_idx: PermissionType::Transfer.to_usize(),
+ num_types: PermissionType::Transfer.num_types(),
+ };
+ let (
+ inventory_approvals,
+ mut public_ownership_expiration,
+ private_metadata_is_public_expiration,
+ ) = self.gen_snip721_approvals(
+ deps.api,
+ block,
+ &mut all_perm,
+ include_expired.unwrap_or(false),
+ &perm_type_info,
+ )?;
+ let owner_is_public = if global_pass {
+ public_ownership_expiration = Some(Expiration::Never);
+ true
+ } else {
+ public_ownership_expiration.is_some()
+ };
+ let private_metadata_is_public = private_metadata_is_public_expiration.is_some();
+ to_binary(&QueryAnswer::InventoryApprovals {
+ owner_is_public,
+ public_ownership_expiration,
+ private_metadata_is_public,
+ private_metadata_is_public_expiration,
+ inventory_approvals,
+ })
+ }
+
+ /// Returns StdResult displaying the list of all addresses that have approval to transfer
+ /// all of the owner's tokens. Only the owner's viewing key will be accepted for this query
+ ///
+ /// # Arguments
+ ///
+ /// * `deps` - a reference to Extern containing all the contract's external dependencies
+ /// * `block` - a reference to the BlockInfo
+ /// * `owner` - an optional reference to the address whose transfer ALL list should be displayed
+ /// * `viewing_key` - optional owner's viewing key
+ /// * `include_expired` - optionally true if the Approval list should include expired Approvals
+ /// * `from_permit` - address derived from an Owner permit, if applicable
+ pub fn query_approved_for_all(
+ &self,
+ deps: Deps,
+ block: &BlockInfo,
+ owner: Option<&str>,
+ viewing_key: Option<&str>,
+ include_expired: Option,
+ from_permit: Option,
+ ) -> StdResult {
+ // get the address whose approvals are being queried
+ let owner_raw = if let Some(pmt) = from_permit {
+ pmt
+ } else {
+ let owner_addr = deps.api.addr_validate(owner.ok_or_else(|| {
+ StdError::generic_err(
+ "This is being called incorrectly if there is no owner address",
+ )
+ })?)?;
+ let raw = deps.api.addr_canonicalize(owner_addr.as_str())?;
+ if let Some(key) = viewing_key {
+ ViewingKey::check(deps.storage, owner_addr.as_str(), key)
+ .map_err(|_| StdError::generic_err(VIEWING_KEY_ERR_MSG))?;
+ // didn't supply a viewing key so just return an empty list of approvals
+ } else {
+ return to_binary(&QueryAnswer::ApprovedForAll {
+ operators: Vec::new(),
+ });
+ }
+ raw
+ };
+ let mut operators: Vec = Vec::new();
+ let all_store = ReadonlyPrefixedStorage::new(deps.storage, PREFIX_ALL_PERMISSIONS);
+ let all_perm: Vec