git.delta.rocks / unique-network / refs/commits / 280307ce9910

difftreelog

Merge commit '1f47f964a0fc5eaa160d9f6fc463551ad4dbd74e' into feature/NFTPAR-196_sponsor_setVariableMetadata

Yaroslav Bolyukin2021-03-05parents: #8d3b0d1 #1f47f96.patch.diff
in: master

19 files changed

modified.gitignorediffbeforeafterboth
--- a/.gitignore
+++ b/.gitignore
@@ -10,4 +10,6 @@
 einstein_key_file
 *store_key*.json
 
-/.idea/
\ No newline at end of file
+/.idea/
+
+tests/.vscode
\ No newline at end of file
modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3456,7 +3456,7 @@
 
 [[package]]
 name = "nft-runtime"
-version = "3.0.0"
+version = "2.1.0"
 dependencies = [
  "frame-benchmarking",
  "frame-executive",
@@ -3785,7 +3785,7 @@
 
 [[package]]
 name = "pallet-nft"
-version = "3.0.0"
+version = "2.1.0"
 dependencies = [
  "frame-benchmarking",
  "frame-support",
modifiedREADME.mddiffbeforeafterboth
--- a/README.md
+++ b/README.md
@@ -4,44 +4,55 @@
 
 ## Project Description
 
-The NFT Pallet is the core of NFT functionality. Like ERC-721 standard in Ethereum ecosystem, this pallet provides the basement for creating collections of unique non-divisible things, also called Non Fungible Tokens (NFTs), minting NFT of a given Collection, and managing their ownership.
+The NFT Pallet is the core of NFT functionality. Like ERC-721 standard in Ethereum ecosystem, this pallet provides the
+basement for creating collections of unique non-divisible things, also called Non Fungible Tokens (NFTs), minting NFT of
+a given Collection, and managing their ownership.
 
-The pallet also enables storing NFT properties. Though (according to ERC-721) NFT properties belong to logic of a concrete application that operates a Collection, so purposefully the NFT Tracking Module does not have any knowledge about properties except their byte size leaving application logic out to be controlled by Smart Contracts.
+The pallet also enables storing NFT properties. Though (according to ERC-721) NFT properties belong to logic of a
+concrete application that operates a Collection, so purposefully the NFT Tracking Module does not have any knowledge
+about properties except their byte size leaving application logic out to be controlled by Smart Contracts.
 
 The NFT Chain also provides:
 
-* Smart Contracts Pallet and example smart contract that interacts with NFT Runtime
-* ERC-1155 Functionality (currently PoC as Re-Fungible tokens, i.e. items that are still unique, but that can be split between multiple users)
-* Variety of economic options for dapp producers to choose from to create freemium games and other ways to attract users. As a step one, we implemented an economic model when a collection sponsor can be set to pay for collection Transfer transactions.
+-   Smart Contracts Pallet and example smart contract that interacts with NFT Runtime
+-   ERC-1155 Functionality (currently PoC as Re-Fungible tokens, i.e. items that are still unique, but that can be split
+    between multiple users)
+-   Variety of economic options for dapp producers to choose from to create freemium games and other ways to attract
+    users. As a step one, we implemented an economic model when a collection sponsor can be set to pay for collection
+    Transfer transactions.
 
 Wider NFT Ecosystem (most of it was developed during Hackusama):
-* [SubstraPunks Game hosted on IPFS](https://github.com/usetech-llc/substrapunks)
-* [NFT Wallet and UI](https://uniqueapps.usetech.com/#/nft)
-* [NFT Asset for Unity Framework](https://github.com/usetech-llc/nft_unity)
 
+-   [SubstraPunks Game hosted on IPFS](https://github.com/usetech-llc/substrapunks)
+-   [NFT Wallet and UI](https://uniqueapps.usetech.com/#/nft)
+-   [NFT Asset for Unity Framework](https://github.com/usetech-llc/nft_unity)
+
 Please see our [walk-thorugh instructions](doc/hackusama_walk_through.md) to try everything out!
 
 ## Hackusama Update
 
 During the Kusama Hackaphon the following changes were made:
-* Enabled Smart Contracts Pallet
-* Enabled integration between Smart Contracts and NFT Pallet (required special edition of RC4 Substrate version)
-* Fixed misc. bugs in NFT Pallet
-* Deployed NFT TestNet. Public node available at wss://unique.usetech.com, custom UI types - see below in this README.
-* New Features:
-  * Re-Fungible Token Mode
-  * Off-Chain Schema to store token image URLs
-  * Alternative economic model
-  * White Lists and Public Mint Permission
-* Use example: [SubstraPunks Game](https://github.com/usetech-llc/substrapunks), fully hosted on IPFS and NFT Testnet Blockchain.
 
+-   Enabled Smart Contracts Pallet
+-   Enabled integration between Smart Contracts and NFT Pallet (required special edition of RC4 Substrate version)
+-   Fixed misc. bugs in NFT Pallet
+-   Deployed NFT TestNet. Public node available at wss://unique.usetech.com, custom UI types - see below in this README.
+-   New Features:
+    -   Re-Fungible Token Mode
+    -   Off-Chain Schema to store token image URLs
+    -   Alternative economic model
+    -   White Lists and Public Mint Permission
+-   Use example: [SubstraPunks Game](https://github.com/usetech-llc/substrapunks), fully hosted on IPFS and NFT Testnet
+    Blockchain.
+
 ## Application Development
 
 If you are building an application that operates NFT tokens, use [this document](doc/application_development.md).
 
 ## Building
 
-Building NFT chain requires special versions of Rust and toolchain. We don't use the most recent versions of everything so that we can keep the builds stable.
+Building NFT chain requires special versions of Rust and toolchain. We don't use the most recent versions of everything
+so that we can keep the builds stable.
 
 1. Install Rust:
 
@@ -60,7 +71,7 @@
 rustup default nightly-2021-01-27
 ```
 
-4. Add wasm target for default toolchain:
+4. Add wasm target for nightly toolchain:
 
 ```bash
 rustup target add wasm32-unknown-unknown --toolchain nightly-2021-01-27
@@ -84,11 +95,17 @@
 cargo run -- --dev
 ```
 
-Detailed logs may be shown by running the node with the following environment variables set: `RUST_LOG=debug RUST_BACKTRACE=1 cargo run -- --dev`.
+Detailed logs may be shown by running the node with the following environment variables set:
+`RUST_LOG=debug RUST_BACKTRACE=1 cargo run -- --dev`.
 
-If you want to see the multi-node consensus algorithm in action locally, then you can create a local testnet with two validator nodes for Alice and Bob, who are the initial authorities of the genesis chain that have been endowed with testnet units. Give each node a name and expose them so they are listed on the Polkadot [telemetry site](https://telemetry.polkadot.io/#/Local%20Testnet). You'll need two terminal windows open.
+If you want to see the multi-node consensus algorithm in action locally, then you can create a local testnet with two
+validator nodes for Alice and Bob, who are the initial authorities of the genesis chain that have been endowed with
+testnet units. Give each node a name and expose them so they are listed on the Polkadot
+[telemetry site](https://telemetry.polkadot.io/#/Local%20Testnet). You'll need two terminal windows open.
 
-We'll start Alice's substrate node first on default TCP port 30333 with her chain database stored locally at `/tmp/alice`. The bootnode ID of her node is `QmQZ8TjTqeDj3ciwr93EJ95hxfDsb9pEYDizUAbWpigtQN`, which is generated from the `--node-key` value that we specify below:
+We'll start Alice's substrate node first on default TCP port 30333 with her chain database stored locally at
+`/tmp/alice`. The bootnode ID of her node is `QmQZ8TjTqeDj3ciwr93EJ95hxfDsb9pEYDizUAbWpigtQN`, which is generated from
+the `--node-key` value that we specify below:
 
 ```bash
 cargo run -- \
@@ -100,7 +117,9 @@
   --validator
 ```
 
-In the second terminal, we'll start Bob's substrate node on a different TCP port of 30334, and with his chain database stored locally at `/tmp/bob`. We'll specify a value for the `--bootnodes` option that will connect his node to Alice's bootnode ID on TCP port 30333:
+In the second terminal, we'll start Bob's substrate node on a different TCP port of 30334, and with his chain database
+stored locally at `/tmp/bob`. We'll specify a value for the `--bootnodes` option that will connect his node to Alice's
+bootnode ID on TCP port 30333:
 
 ```bash
 cargo run -- \
@@ -152,4 +171,4 @@
 
 ## Running Integration Tests
 
-See [tests/README.md](./tests/README.md).
\ No newline at end of file
+See [tests/README.md](./tests/README.md).
modifieddoc/application_development.mddiffbeforeafterboth
--- a/doc/application_development.md
+++ b/doc/application_development.md
@@ -2,17 +2,23 @@
 
 ## Architecture
 
-Both centralized and serverless architectures are supported and application creator can decide which one to use depending on how much logic and data they intend to manage off-chain and off-line.
+Both centralized and serverless architectures are supported and application creator can decide which one to use
+depending on how much logic and data they intend to manage off-chain and off-line.
 
 ### Server-based architecture
 
-In server-based architecture the application creator manually creates NFT collections and authorizes server to perform operations on each collection. See NFT palette methods that have admin permission level to understand better what server (or administrators) can do.
+In server-based architecture the application creator manually creates NFT collections and authorizes server to perform
+operations on each collection. See NFT palette methods that have admin permission level to understand better what server
+(or administrators) can do.
 
 ![](server_architecture.png)
 
 ### Serverless architecture
 
-In serverless architecture the application creator does all initialization and token distribution manually. Alternatively, smart contracts may be used in order to implement some distribution mechanics such as token sales or claiming. The dApp has access to all properties of an NFT token, but it does not have any elevated permissions, since it resides on user site. All operations happen on user's behalf signed by user's private key.
+In serverless architecture the application creator does all initialization and token distribution manually.
+Alternatively, smart contracts may be used in order to implement some distribution mechanics such as token sales or
+claiming. The dApp has access to all properties of an NFT token, but it does not have any elevated permissions, since it
+resides on user site. All operations happen on user's behalf signed by user's private key.
 
 ![](serverless_architecture.png)
 
@@ -27,199 +33,252 @@
 #### CreateCollection
 
 ##### Description
-This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.
 
+This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of
+certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both
+addresses can be changed later.
+
 ##### Permissions
-Anyone
 
+-   Anyone
+
 ##### Parameters
-customDataSz: Size of NFT properties data.
 
+-   customDataSz: Size of NFT properties data.
+
 ##### Events
-CollectionCreated
-CollectionID: Globally unique identifier of newly created collection.
-Owner: Collection owner
 
+-   CollectionCreated
+    -   CollectionID: Globally unique identifier of newly created collection.
+    -   Mode: Collection mode
+    -   Owner: Collection owner
+
 #### ChangeCollectionOwner
 
 ##### Description
+
 Change the owner of the collection
 
 ##### Permissions
-Collection Owner
 
+-   Collection Owner
+
 ##### Parameters
-CollectionId
 
+-   CollectionId
+
 #### DestroyCollection
 
 ##### Description
-DANGEROUS: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.
 
+DANGEROUS: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose
+real money.
+
 ##### Permissions
-Collection Owner
 
+-   Collection Owner
+
 ##### Parameters
-CollectionId
 
+-   CollectionId
+
 #### CreateItem
 
 ##### Description
+
 This method creates a concrete instance of NFT Collection created with CreateCollection method.
 
 ##### Permissions
-Collection Owner
-Collection Admin
 
+-   Collection Owner
+-   Collection Admin
+
 ##### Parameters
-CollectionID: ID of the collection
-Properties: Array of bytes that contains NFT properties. Since NFT Module is agnostic of properties’ meaning, it is treated purely as an array of bytes
-Owner: Address, initial owner of the NFT
 
+-   CollectionID: ID of the collection
+-   Properties: Array of bytes that contains NFT properties. Since NFT Module is agnostic of properties’ meaning, it is
+    treated purely as an array of bytes
+-   Owner: Address, initial owner of the NFT
+
 ##### Events
 ItemCreated
 ItemId: Identifier of newly created NFT, which is unique within the Collection, so the NFT is uniquely identified with a pair of values: CollectionId and ItemId.
+Recipient: Address, owner of newly created item
 
 #### BurnItem
 
 ##### Description
+
 This method destroys a concrete instance of NFT.
 
 ##### Permissions
-Collection Owner
-Collection Admin
-Current NFT Owner
 
+-   Collection Owner
+-   Collection Admin
+-   Current NFT Owner
+
 ##### Parameters
-CollectionID: ID of the collection
-ItemID: ID of NFT to burn
 
+-   CollectionID: ID of the collection
+-   ItemID: ID of NFT to burn
+
 ##### Events
-ItemDestroyed
-CollectionID
-ItemId: Identifier of burned NFT
 
+-   ItemDestroyed
+    -   CollectionID
+    -   ItemId: Identifier of burned NFT
 
 #### AddCollectionAdmin
 
 ##### Description
-NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.
 
+NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can
+issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.
+
 This method adds an admin of the Collection.
 
 ##### Permissions
-Collection Owner
-Collection Admin
 
+-   Collection Owner
+-   Collection Admin
+
 ##### Parameters
-CollectionID: ID of the Collection to add admin for
-Admin: Address of new admin to add
 
+-   CollectionID: ID of the Collection to add admin for
+-   Admin: Address of new admin to add
+
 #### RemoveCollectionAdmin
 
 ##### Description
-Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.
 
+Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which
+case only Collection Owner will be able to add an Admin.
+
 ##### Permissions
-Collection Owner
-Collection Admin
 
+-   Collection Owner
+-   Collection Admin
+
 ##### Parameters
-CollectionID: ID of the Collection to remove admin for
-Admin: Address of admin to remove
 
+-   CollectionID: ID of the Collection to remove admin for
+-   Admin: Address of admin to remove
+
 ### Item Ownership and Transfers
+
 This group of methods allows managing NFT ownership.
 
 #### GetOwner
 
 ##### Description
-Return the address of the NFT owner. 
 
+Return the address of the NFT owner.
+
 ##### Permissions
-Anyone
 
+-   Anyone
+
 ##### Parameters
-CollectionId
-ItemId: ID of the NFT
 
+-   CollectionId
+-   ItemId: ID of the NFT
+
 ##### Returns
+
 Owner address
 
 #### BalanceOf
 
 ##### Description
-This method is included for compatibility with ERC-721. Return the total count of NFTs of a given Collection that belong to a given address. 
 
+This method is included for compatibility with ERC-721. Return the total count of NFTs of a given Collection that belong
+to a given address.
+
 ##### Permissions
-Anyone
 
+-   Anyone
+
 ##### Parameters
-CollectionId
-Address to count NFTs for
 
+-   CollectionId
+-   Address to count NFTs for
+
 ##### Returns
+
 Total count of NFTs for Address
 
-
 #### Transfer
 
 ##### Description
+
 Change ownership of the NFT.
 
 ##### Permissions
-Collection Owner
-Collection Admin
-Current NFT owner
 
+-   Collection Owner
+-   Collection Admin
+-   Current NFT owner
+
 ##### Parameters
-Recipient: Address of token recipient
-ClassId: ID of item class
-ItemId: ID of the item
 
+-   Recipient: Address of token recipient
+-   ClassId: ID of item class
+-   ItemId: ID of the item
+
 #### TransferFrom
 
 ##### Description
-Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.
 
+Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method
+executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this
+owner.
+
 ##### Permissions
-Collection Owner
-Collection Admin
-Current NFT owner
-Address approved by current NFT owner
 
+-   Collection Owner
+-   Collection Admin
+-   Current NFT owner
+-   Address approved by current NFT owner
+
 ##### Parameters
-Recipient: Address of token recipient
-ClassId: ID of item class
-ItemId: ID of the item
 
+-   Recipient: Address of token recipient
+-   ClassId: ID of item class
+-   ItemId: ID of the item
 
 #### Approve
 
 ##### Description
+
 Set, change, or remove approved address to transfer the ownership of the NFT.
 
 ##### Permissions
-Collection Owner
-Collection Admin
-Current NFT owner
 
+-   Collection Owner
+-   Collection Admin
+-   Current NFT owner
+
 ##### Parameters
-Approved: Address that is approved to transfer this NFT or zero (if needed to remove approval)
-ClassId: ID of item class
-ItemId: ID of the item
 
+-   Approved: Address that is approved to transfer this NFT or zero (if needed to remove approval)
+-   ClassId: ID of item class
+-   ItemId: ID of the item
+
 #### GetApproved
 
 ##### Description
+
 Get the approved address for a single NFT.
 
 ##### Permissions
-Anyone
 
+-   Anyone
+
 ##### Parameters
-ClassId: ID of item class
-ItemId: ID of the item
 
+-   ClassId: ID of item class
+-   ItemId: ID of the item
+
 ##### Returns
+
 Approved address
modifieddoc/demo_milestone1-2.mddiffbeforeafterboth
--- a/doc/demo_milestone1-2.md
+++ b/doc/demo_milestone1-2.md
@@ -6,28 +6,41 @@
 
 ### A running chain
 
-**Substrate based blockchain node to host NFT Tracking Module created with substrate-up scripts (currently substrate-up only support Substrate 1.0)**
+**Substrate based blockchain node to host NFT Tracking Module created with substrate-up scripts (currently substrate-up
+only support Substrate 1.0)**
 
-We have created chain based on both versions of substrate: 1 and 2. Nonetheless, the substrate 1 is not being widely used and we decided to obsolete this branch of code. The code still exists in [substrate1](https://github.com/usetech-llc/nft_parachain/tree/substrate1) branch, but this delivery document is based on substrate 2 version.
+We have created chain based on both versions of substrate: 1 and 2. Nonetheless, the substrate 1 is not being widely
+used and we decided to obsolete this branch of code. The code still exists in
+[substrate1](https://github.com/usetech-llc/nft_parachain/tree/substrate1) branch, but this delivery document is based
+on substrate 2 version.
 
 The node can be run using docker container:
+
 ```
 docker-compose up -d
 ```
 
 #### NFT Tracking Module
-Open extrinsics tab of the [standard UI](https://polkadot.js.org/apps/#/extrinsics) and find "nft" in the module list. This proves that NFT module is deployed.
 
-#### Balances Module, Other modules included by default 
-The [same UI](https://polkadot.js.org/apps/#/extrinsics) allows verification that all other modules are also installed as needed.
+Open extrinsics tab of the [standard UI](https://polkadot.js.org/apps/#/extrinsics) and find "nft" in the module list.
+This proves that NFT module is deployed.
+
+#### Balances Module, Other modules included by default
+
+The [same UI](https://polkadot.js.org/apps/#/extrinsics) allows verification that all other modules are also installed
+as needed.
 
 #### Configuration of runtime as needed (e.g. for hot module updates)
 
-The `set_code` method worked out of the box and we did not need to perform any additional customizations to upload an updated WASM version of NFT palette.
+The `set_code` method worked out of the box and we did not need to perform any additional customizations to upload an
+updated WASM version of NFT palette.
 
 ### NFT Tracking Module
 
-Before we continue, we need to do some preparations in the UI: Add NFT Data types so that the UI knows how to decode them. Go to [Settings-Developer Tab](https://polkadot.js.org/apps/#/settings/developer) and add following types to the JSON object in there:
+Before we continue, we need to do some preparations in the UI: Add NFT Data types so that the UI knows how to decode
+them. Go to [Settings-Developer Tab](https://polkadot.js.org/apps/#/settings/developer) and add following types to the
+JSON object in there:
+
 ```
 {
   "NftItemType": {
@@ -50,11 +63,16 @@
 
 #### CreateCollection
 
-Before running test, open [chain state tab](https://polkadot.js.org/apps/#/chainstate) and read `nft`.`nextCollectionId` state variable, which shows how many collections were created so far. If you just started the chain, this should equal 0. 
+Before running test, open [chain state tab](https://polkadot.js.org/apps/#/chainstate) and read `nft`.`nextCollectionId`
+state variable, which shows how many collections were created so far. If you just started the chain, this should
+equal 0.
 
-Open extrinsics tab of the [standard UI](https://polkadot.js.org/apps/#/extrinsics) and select ALICE account. Select `nft` module and `createCollection` method. Put 1 in `custom_data_size` and run the transaction. After it is finalized, read the `nft`.`nextCollectionId` state variable. It will be equal 1.
+Open extrinsics tab of the [standard UI](https://polkadot.js.org/apps/#/extrinsics) and select ALICE account. Select
+`nft` module and `createCollection` method. Put 1 in `custom_data_size` and run the transaction. After it is finalized,
+read the `nft`.`nextCollectionId` state variable. It will be equal 1.
 
-Also, read the state variable `nft`.`collection` with ID = 1 (Because everything in NFT Palette is numbered from 1, not from 0). You will see something like this:
+Also, read the state variable `nft`.`collection` with ID = 1 (Because everything in NFT Palette is numbered from 1, not
+from 0). You will see something like this:
 
 ```
 nft.collection: CollectionType
@@ -67,7 +85,11 @@
 
 ### ChangeCollectionOwner
 
-Open extrinsics tab of the [standard UI](https://polkadot.js.org/apps/#/extrinsics). Select `nft` module and `changeCollectionOwner` method. First, try to execute it to change owner to BOB for collection 1 from some different account than ALICE - FERDIE to see that it is not possible because ALICE owns collection 1 and FERDIE is not allowed to give it to BOB. Second, select ALICE as transaction signer and run it again. This time the collection owner changes, which will be reflected if you read the state variable `nft`.`collection` with ID = 1:
+Open extrinsics tab of the [standard UI](https://polkadot.js.org/apps/#/extrinsics). Select `nft` module and
+`changeCollectionOwner` method. First, try to execute it to change owner to BOB for collection 1 from some different
+account than ALICE - FERDIE to see that it is not possible because ALICE owns collection 1 and FERDIE is not allowed to
+give it to BOB. Second, select ALICE as transaction signer and run it again. This time the collection owner changes,
+which will be reflected if you read the state variable `nft`.`collection` with ID = 1:
 
 ```
 nft.collection: CollectionType
@@ -84,7 +106,9 @@
 
 **Note: Before destroying collection, you can see Milestone 2 deliverables to avoid creating collection again**
 
-Run `nft`.`destroyCollection` with collection ID = 1, and then read collection from state. The returned fields will have default values, which indicates that collection does not exsit anymore: 
+Run `nft`.`destroyCollection` with collection ID = 1, and then read collection from state. The returned fields will have
+default values, which indicates that collection does not exsit anymore:
+
 ```
 nft.collection: CollectionType
 {
@@ -108,20 +132,29 @@
 
 **using Polkadot UI from https://substrate-ui.parity.io/**
 
-This document, except we are using Substrate v2, so we can use the newer version of the UI: https://polkadot.js.org/apps/#
+This document, except we are using Substrate v2, so we can use the newer version of the UI:
+https://polkadot.js.org/apps/#
 
 ## Milestone 2
+
 ### NFT Tracking Module
+
 **Note:** If you have destroyed collection, create it again using `nft`.`createCollection` method.
 
-**Note 2:** The order of items in this section is different from the original spec to make the acceptance workflow more natural.
+**Note 2:** The order of items in this section is different from the original spec to make the acceptance workflow more
+natural.
 
-If you did not destroy collection while looking at Milestone 1 deliverables, use collection ID 1 in the further examples, otherwise use collection ID = 2.
+If you did not destroy collection while looking at Milestone 1 deliverables, use collection ID 1 in the further
+examples, otherwise use collection ID = 2.
 
 #### CreateItem
-Before creating an NFT item, let's read ALICE balance for your collection, which indicates how many NFT tokens ALICE owns in this collection. Read the chain state `nft`.`balance(<Collection ID>, ALICE)`, and it will be 0.
 
-Execute extrinsic `nft`.`createItem` from ALICE account. Set properties to `0x01`. Now if you read the chain state `nft`.`balance(<Collection ID>, ALICE)`, it will be equal to 1. Also, you can read chain state `nft`.`itemList(<Collection ID>, 1)`, and it will return data for the token 1:
+Before creating an NFT item, let's read ALICE balance for your collection, which indicates how many NFT tokens ALICE
+owns in this collection. Read the chain state `nft`.`balance(<Collection ID>, ALICE)`, and it will be 0.
+
+Execute extrinsic `nft`.`createItem` from ALICE account. Set properties to `0x01`. Now if you read the chain state
+`nft`.`balance(<Collection ID>, ALICE)`, it will be equal to 1. Also, you can read chain state
+`nft`.`itemList(<Collection ID>, 1)`, and it will return data for the token 1:
 
 ```
 nft.itemList: NftItemType
@@ -133,7 +166,10 @@
 ```
 
 #### GetOwner
-Reading the ownership is done by reading chainstate `nft`.`itemList(<Collection ID>, 1)`. One of the returned fields is Owner:
+
+Reading the ownership is done by reading chainstate `nft`.`itemList(<Collection ID>, 1)`. One of the returned fields is
+Owner:
+
 ```
 nft.itemList: NftItemType
 {
@@ -144,7 +180,9 @@
 ```
 
 #### Transfer
+
 Execute `nft`.`transfer` from ALICE address to transfer token 1 to BOB and check the ownership again:
+
 ```
 nft.itemList: NftItemType
 {
@@ -159,14 +197,18 @@
 #### BalanceOf
 
 Read the chain state `nft`.`balance` for ALICE address and see that she owns 1 token:
+
 ```
 nft.balance: u64
 1
 ```
 
 #### AddCollectionAdmin
-Execute `nft`.`AddCollectionAdmin` from ALICE account and let CHARLIE be an admin. Now you can see that CHARLIE can transfer ALICE's token from ALICE's account to EVE and back. Also, you can read admin list from chain state and see that it is not empty:
 
+Execute `nft`.`AddCollectionAdmin` from ALICE account and let CHARLIE be an admin. Now you can see that CHARLIE can
+transfer ALICE's token from ALICE's account to EVE and back. Also, you can read admin list from chain state and see that
+it is not empty:
+
 ```
 nft.adminList: Vec<AccountId>
 [
@@ -175,15 +217,21 @@
 ```
 
 #### RemoveCollectionAdmin
-Execute `nft`.`RemoveCollectionAdmin` from ALICE account to remove CHARLIE from admins. Now you can see that CHARLIE cannot transfer ALICE's tokens anymore. If you read the chan state `nft`.`adminList`, the response will be empty:
 
+Execute `nft`.`RemoveCollectionAdmin` from ALICE account to remove CHARLIE from admins. Now you can see that CHARLIE
+cannot transfer ALICE's tokens anymore. If you read the chan state `nft`.`adminList`, the response will be empty:
+
 ```
 nft.adminList: Vec<AccountId>
 []
 ```
 
 #### BurnItem
-Execute `nft`.`burnItem` from ALICE account to burn token 1, and then read the chain state `nft`.`itemList(<Collection ID>, 1)`. This time the chain state returns default values in fields because token does not exist anymore. You can also check ALICE balance in chain state, now it is equal 0 again.
+
+Execute `nft`.`burnItem` from ALICE account to burn token 1, and then read the chain state
+`nft`.`itemList(<Collection ID>, 1)`. This time the chain state returns default values in fields because token does not
+exist anymore. You can also check ALICE balance in chain state, now it is equal 0 again.
+
 ```
 nft.itemList: NftItemType
 {
@@ -194,14 +242,15 @@
 ```
 
 ### Economic Model Specification
+
 [Economic Model Specification](economic_model.md)
 
 ### Contracts Module Specification
+
 https://docs.google.com/document/d/1gDtYjPR9C1VZChxEA-xAdQWQyEvMg245XrZR_MpE3cg/edit?usp=sharing
 
 ### Bonus goal
 
 **Basic demo - Cryptopunks representation on the Substrate Chain.**
 
-See this repo, it has running and playing instructions:
-https://github.com/usetech-llc/substrapunks
+See this repo, it has running and playing instructions: https://github.com/usetech-llc/substrapunks
modifieddoc/economic_model.mddiffbeforeafterboth
--- a/doc/economic_model.md
+++ b/doc/economic_model.md
@@ -2,26 +2,33 @@
 
 ## Economy Goals
 
-One of the goals of this specification is to find means to provide for network infrastructure (incentivisation of validators) and development support for NFT Parachain. Transaction fees will be used as the main source of funds. Fees will be distributed among network participants. The distribution of fees will depend on validator contribution to the network (similarly to Polkadot network) and the chosen fee structure (model).
+One of the goals of this specification is to find means to provide for network infrastructure (incentivisation of
+validators) and development support for NFT Parachain. Transaction fees will be used as the main source of funds. Fees
+will be distributed among network participants. The distribution of fees will depend on validator contribution to the
+network (similarly to Polkadot network) and the chosen fee structure (model).
 
 ## Summary
 
-We are going to offer several fee models for our users. Some models will be implemented immediately, some will wait until there is a demand. 
+We are going to offer several fee models for our users. Some models will be implemented immediately, some will wait
+until there is a demand.
 
-The fee models can be setup by Collection and by Smart Contract address. Every Collection or Smart Contract has a fee model associated with it, which determines how transactions are paid. Network users can choose the fee model that better suits their application. The fee model can be changed later as well.
+The fee models can be setup by Collection and by Smart Contract address. Every Collection or Smart Contract has a fee
+model associated with it, which determines how transactions are paid. Network users can choose the fee model that better
+suits their application. The fee model can be changed later as well.
 
 Initially we offer 4 models for consideration:
 
-* User paid fees (conventional blockchain method)
-* Sponsored (same as above, but paid by sponsor)
-* Prepaid plan
-* Resource purchase
+-   User paid fees (conventional blockchain method)
+-   Sponsored (same as above, but paid by sponsor)
+-   Prepaid plan
+-   Resource purchase
 
 ## Token and Accounts
 
 Network Tokens are tracked via standard Balances module, which is included in NFT Parachain.
 
-User Account matches to user address in the network. Among other properties user Account has Balance, which will be used later on.
+User Account matches to user address in the network. Among other properties user Account has Balance, which will be used
+later on.
 
 ## Settings Economic Model
 
@@ -31,171 +38,234 @@
 
 #### Description
 
-Set the collection sponsor address. The sponsorship needs to be confirmed by sending a ConfirmSponsorship transaction from that address. Also, the protection measures need to be in place so that sponsor account cannot be depleted by malicious users. White listing is one of the measured. It can be enabled with SetPublicAccessMode method.
+Set the collection sponsor address. The sponsorship needs to be confirmed by sending a ConfirmSponsorship transaction
+from that address. Also, the protection measures need to be in place so that sponsor account cannot be depleted by
+malicious users. White listing is one of the measured. It can be enabled with SetPublicAccessMode method.
 
 ##### List of transactions
+
 Transactions that can be sponsored:
-* Transfer
-* Approve
-* TransferFrom
-* TransferWithData
-* TransferFromWithData
-* BurnItem
 
+-   Transfer
+-   Approve
+-   TransferFrom
+-   TransferWithData
+-   TransferFromWithData
+-   BurnItem
+
 ##### Rate Limiting
 
 This is a design proposal, actual implementation may change.
 
 Features:
-* Token transactions per day: The timestamp of last transaction performed on a token (NFT only) is associated with token ID in a dictionary. When a next transaction is run, the time is checked against this timestamp and, if less than allowed by rate limit, the fee is paid by caller account instead of the sponsor.
-* Address transactions per day: The timestamp of last transaction performed by an address (only if transaction is in the [list of affected transactions](#list-of-transactions)) is associated with caller address in a dictionary. Further, the logic is similar to limiting by token as above. If only one of the limits (by token or by address) indicates that fee should be paid by the user, the fee is paid by the user.
-* A pallet method (SetCollectionRateLimits) will be added to set these parameters and enable rate limiting.
-* One idea to consider is deposits that must be made by an address in order to become white listed (instead of admin review).
 
+-   Token transactions per day: The timestamp of last transaction performed on a token (NFT only) is associated with
+    token ID in a dictionary. When a next transaction is run, the time is checked against this timestamp and, if less
+    than allowed by rate limit, the fee is paid by caller account instead of the sponsor.
+-   Address transactions per day: The timestamp of last transaction performed by an address (only if transaction is in
+    the [list of affected transactions](#list-of-transactions)) is associated with caller address in a dictionary.
+    Further, the logic is similar to limiting by token as above. If only one of the limits (by token or by address)
+    indicates that fee should be paid by the user, the fee is paid by the user.
+-   A pallet method (SetCollectionRateLimits) will be added to set these parameters and enable rate limiting.
+-   One idea to consider is deposits that must be made by an address in order to become white listed (instead of admin
+    review).
+
 #### Permissions
 
-* Collection Owner
+-   Collection Owner
 
 #### Parameters
 
-* CollectionId: ID of the collection
-* Sponsor: Sponsor address
-* Fee Model ID: ID of selected economic model. Currently, only "Sponsored" mode is supported, which will be ID = 1.
+-   CollectionId: ID of the collection
+-   Sponsor: Sponsor address
+-   Fee Model ID: ID of selected economic model. Currently, only "Sponsored" mode is supported, which will be ID = 1.
 
 ### ConfirmSponsorship
+
 #### Description
+
 Confirm sponsorship of a collection. This call is needed to protect sponsor address from malicious collection creators.
 
 #### Permissions
-* Collection Sponsor only
 
+-   Collection Sponsor only
+
 #### Parameters
-* CollectionId: ID of the collection
 
+-   CollectionId: ID of the collection
+
 ### RemoveCollectionSponsor
+
 #### Description
+
 Switch back to pay-per-own-transaction model.
 
 #### Permissions
-* Collection owner
 
+-   Collection owner
+
 #### Parameters
-* collectionId: ID of the collection
 
+-   collectionId: ID of the collection
+
 ### ClaimContract
+
 #### Description
-Claim the contract ownership for the purpose of sponsoring. Once the contract is claimed, only the contract owner can manage the contract sponsorship. Also, the contract may be claimed only once.
 
+Claim the contract ownership for the purpose of sponsoring. Once the contract is claimed, only the contract owner can
+manage the contract sponsorship. Also, the contract may be claimed only once.
+
 #### Permissions
-* Anyone
 
+-   Anyone
+
 #### Parameters
-* ContractAddress: Address of the contract
-* Sponsor: Sponsor address
 
+-   ContractAddress: Address of the contract
+-   Sponsor: Sponsor address
+
 ### SetContractSponsor
+
 #### Description
-Set the contract sponsor address. The sponsorship needs to be confirmed by sending a ConfirmContractSponsorship transaction from that address. The spam protection measures are similar to collection sponsorship.
 
+Set the contract sponsor address. The sponsorship needs to be confirmed by sending a ConfirmContractSponsorship
+transaction from that address. The spam protection measures are similar to collection sponsorship.
+
 #### Permissions
-* Contract Owner
 
+-   Contract Owner
+
 #### Parameters
-* ContractAddress: Address of the contract
-* Sponsor: Sponsor address
-* Fee Model ID: ID of selected economic model. Currently, only "Sponsored" mode is supported, which will be ID = 1.
 
+-   ContractAddress: Address of the contract
+-   Sponsor: Sponsor address
+-   Fee Model ID: ID of selected economic model. Currently, only "Sponsored" mode is supported, which will be ID = 1.
+
 ### ConfirmContractSponsorship
+
 #### Description
+
 Confirm sponsorship of a contract. This call is needed to protect sponsor address from malicious sponsorship requests.
 
 #### Permissions
-* Contract Sponsor only
 
+-   Contract Sponsor only
+
 #### Parameters
-* ContractAddress: Address of the contract
 
+-   ContractAddress: Address of the contract
+
 ### RemoveContractSponsor
+
 #### Description
+
 Switch back to pay-per-own-transaction model.
 
 #### Permissions
-* Contract owner
 
+-   Contract owner
+
 #### Parameters
-* contractAddress: Address of the contract
 
+-   contractAddress: Address of the contract
+
 ### SetPublicAccessMode
+
 #### Description
-Toggle between normal and white list access for the methods with access for “Anyone”. If White List mode is enabled, AddToWhiteList and RemoveFromWhiteList methods can be called to add to and remove addresses from the white list.
 
-White list mode is the property of collection. If it is turned on, all public operations such as token transfers, for example, which normally have “Anyone” permission, become white listed, i.e. are only available to collection owner, admins, and addresses from the white list. White lists can be helpful for rate limiting of transfers when collection sponsoring is enabled.
+Toggle between normal and white list access for the methods with access for “Anyone”. If White List mode is enabled,
+AddToWhiteList and RemoveFromWhiteList methods can be called to add to and remove addresses from the white list.
+
+White list mode is the property of collection. If it is turned on, all public operations such as token transfers, for
+example, which normally have “Anyone” permission, become white listed, i.e. are only available to collection owner,
+admins, and addresses from the white list. White lists can be helpful for rate limiting of transfers when collection
+sponsoring is enabled.
 
 #### Permissions
-* Collection Owner
 
+-   Collection Owner
+
 #### Parameters
-* CollectionID: ID of the Collection to set access mode for
-* Mode
-    * 0 = Normal
-    * 1 = White list
 
+-   CollectionID: ID of the Collection to set access mode for
+-   Mode
+    -   0 = Normal
+    -   1 = White list
+
 ### AddToWhiteList
+
 #### Description
+
 Add an address to white list.
 
 #### Permissions
-* Collection Owner
-* Collection Admin
 
+-   Collection Owner
+-   Collection Admin
+
 #### Parameters
-* CollectionID: ID of the Collection
-* Address
 
+-   CollectionID: ID of the Collection
+-   Address
+
 ### RemoveFromWhiteList
+
 #### Description
+
 Remove an address from white list.
 
 #### Permissions
-* Collection Owner
-* Collection Admin
 
+-   Collection Owner
+-   Collection Admin
+
 #### Parameters
-* CollectionID: ID of the Collection
-* Address
 
+-   CollectionID: ID of the Collection
+-   Address
+
 ### SetCollectionRateLimits
+
 #### Description
+
 Enable or disable rate limits for a collection
 
 #### Permissions
-* Collection Owner
-* Collection Admin
 
+-   Collection Owner
+-   Collection Admin
+
 #### Parameters
-* CollectionID: ID of the Collection
-* TokenInterval: Number of seconds allowed between sponsored token trasnsactions. 0 means there is no limit.
-* AddressInterval: Number of seconds allowed between sponsored address trasnsactions. 0 means there is no limit.
 
+-   CollectionID: ID of the Collection
+-   TokenInterval: Number of seconds allowed between sponsored token trasnsactions. 0 means there is no limit.
+-   AddressInterval: Number of seconds allowed between sponsored address trasnsactions. 0 means there is no limit.
 
 ## User Paid Fees
 
-This is conventional fee model, when every Account pais for the transactions they sign and send. Transaction fee will gradually increase if the network load is higher to prevent denial of service. The same type of transaction (with the same transaction weight) will result in higher fee if previous block's weight gets close to maximum block weight. The ratio for multiplying fees will be determined empirically. The multiplication will take place until the blocks stop overpopulating, but with certain saturation. If blocks underfill, i.e. block weight is under the certain threshold, then the next block will have lower fees. The lowering will continue until blocks stop underpopulating, with some saturation.
+This is conventional fee model, when every Account pais for the transactions they sign and send. Transaction fee will
+gradually increase if the network load is higher to prevent denial of service. The same type of transaction (with the
+same transaction weight) will result in higher fee if previous block's weight gets close to maximum block weight. The
+ratio for multiplying fees will be determined empirically. The multiplication will take place until the blocks stop
+overpopulating, but with certain saturation. If blocks underfill, i.e. block weight is under the certain threshold, then
+the next block will have lower fees. The lowering will continue until blocks stop underpopulating, with some saturation.
 
 Each module call will have weight annotation that will determine the weight calculation for this call:
 
-* CreateCollection: Fixed
-* ChangeCollectionOwner: Fixed
-* DestroyCollection: Linear of number of items (or owner addresses, depending on the collection type) multiplied by Collection Data Size
-* CreateItem: Linear of Collection Data Size
-* BurnItem: Linear of Collection Data Size
-* AddCollectionAdmin: Fixed
-* RemoveCollectionAdmin: Fixed
-* Transfers and approvals: Fixed
-* Transfers with data: Fixed + linear of data size
-* All other: Fixed
+-   CreateCollection: Fixed
+-   ChangeCollectionOwner: Fixed
+-   DestroyCollection: Linear of number of items (or owner addresses, depending on the collection type) multiplied by
+    Collection Data Size
+-   CreateItem: Linear of Collection Data Size
+-   BurnItem: Linear of Collection Data Size
+-   AddCollectionAdmin: Fixed
+-   RemoveCollectionAdmin: Fixed
+-   Transfers and approvals: Fixed
+-   Transfers with data: Fixed + linear of data size
+-   All other: Fixed
 
-This model is default and will be set for each collection until collection owner decides to change it to some other model.
+This model is default and will be set for each collection until collection owner decides to change it to some other
+model.
 
 ### Fee Distribution
 
@@ -203,30 +273,32 @@
 
 ## Sponsored
 
-The only difference from User Paid Fees model is that collection owner will be paying for their users. The collection owner must have enough balance on his account in order to pay for user transactions.
+The only difference from User Paid Fees model is that collection owner will be paying for their users. The collection
+owner must have enough balance on his account in order to pay for user transactions.
 
 ### Fee Distribution
 
 Same as in User Paid Fees.
 
-
 ## Prepaid Plan
-
-Collection Owner makes regular payments to prepay for some planned network load, i.e. some fixed number of transactions, created items, etc.
 
+Collection Owner makes regular payments to prepay for some planned network load, i.e. some fixed number of transactions,
+created items, etc.
 
 ### Fee Distribution
 
 Same as in User Paid Fees.
 
-
 ## Resource Purchase
 
-Collection Owner purchases some fixed amount of network resource, which can be measured in number of users and their monthly transactions, for example. This is a one time payment. If Collection Owner decides to switch to a different fee model, the resources may be sold back to the system (with some commission).
+Collection Owner purchases some fixed amount of network resource, which can be measured in number of users and their
+monthly transactions, for example. This is a one time payment. If Collection Owner decides to switch to a different fee
+model, the resources may be sold back to the system (with some commission).
 
-Received funds will be converted to DOTs and used in staking (nomination or validation), which will allow to receive the income that will pay for infrastructure and development support.
+Received funds will be converted to DOTs and used in staking (nomination or validation), which will allow to receive the
+income that will pay for infrastructure and development support.
 
 ### Fee Distribution
 
-Fixed percentage of staking income is distributed between validators proportionally to their contribution to the network. The rest is credited to the network owner.
-
+Fixed percentage of staking income is distributed between validators proportionally to their contribution to the
+network. The rest is credited to the network owner.
modifieddoc/hackusama_walk_through.mddiffbeforeafterboth
--- a/doc/hackusama_walk_through.md
+++ b/doc/hackusama_walk_through.md
@@ -8,46 +8,70 @@
 
 ### Deployed NFT TestNet
 
-The TestNet public node is avaiable at wss://unique.usetech.com. It is easy to verify that it functions with the standard AppsUI:
+The TestNet public node is avaiable at wss://unique.usetech.com. It is easy to verify that it functions with the
+standard AppsUI:
 
 1. Open the [Apps UI](https://polkadot.js.org/apps/#)
 2. Click on the network icon in the top left corner
 3. Scroll down the list and input `wss://unique.usetech.com` under "custom endpoint"
-4. After connection is established, copy these [UI Custom Types](https://github.com/usetech-llc/nft_parachain#ui-custom-types) and paste them in [Developer Settings](https://polkadot.js.org/apps/#/settings/developer). Hit "Save" button.
-5. Now the NFT node is connected with AppsUI. 
-6. Go to [Chain State](https://polkadot.js.org/apps/#/chainstate) and verify that NFT pallet is visible and you can read information from it.
-7. For example, select nft -> collection, and enter collection ID 4 (for SubstraPunks) and click "+" button. You will see the collection properties such as UTF-16 encoded collection name and description, UTF-8 encoded token prefix, the size of custom data, etc.
+4. After connection is established, copy these
+   [UI Custom Types](https://github.com/usetech-llc/nft_parachain#ui-custom-types) and paste them in
+   [Developer Settings](https://polkadot.js.org/apps/#/settings/developer). Hit "Save" button.
+5. Now the NFT node is connected with AppsUI.
+6. Go to [Chain State](https://polkadot.js.org/apps/#/chainstate) and verify that NFT pallet is visible and you can read
+   information from it.
+7. For example, select nft -> collection, and enter collection ID 4 (for SubstraPunks) and click "+" button. You will
+   see the collection properties such as UTF-16 encoded collection name and description, UTF-8 encoded token prefix, the
+   size of custom data, etc.
 
 #### Getting some Unique Tokens
 
-1. Install Polkadot{.js} extension if you don't have it already installed. Here are the links for [Chrome](https://chrome.google.com/webstore/detail/polkadot%7Bjs%7D-extension/mopnmbcafieddcagagdcbnhejhlodfdd) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/polkadot-js-extension/) extensions.
+1. Install Polkadot{.js} extension if you don't have it already installed. Here are the links for
+   [Chrome](https://chrome.google.com/webstore/detail/polkadot%7Bjs%7D-extension/mopnmbcafieddcagagdcbnhejhlodfdd) and
+   [Firefox](https://addons.mozilla.org/en-US/firefox/addon/polkadot-js-extension/) extensions.
 2. Create an address
-3. Contact us at [Unique Network Telegram channel](https://t.me/joinchat/DPVt1RwN50Uic_Q9lFcg9A) in order to get some Unique token and some Re-Fungible tokens. Faucet is comming soon, we will update this instructions when it goes live.
+3. Contact us at [Unique Network Telegram channel](https://t.me/joinchat/DPVt1RwN50Uic_Q9lFcg9A) in order to get some
+   Unique token and some Re-Fungible tokens. Faucet is comming soon, we will update this instructions when it goes live.
 
 ### Smart Contracts Pallet
 
-1. Open [Contracts](https://polkadot.js.org/apps/#/contracts) tab. It only appears in the UI when Smart Contracts pallet is included in the runtime.
+1. Open [Contracts](https://polkadot.js.org/apps/#/contracts) tab. It only appears in the UI when Smart Contracts pallet
+   is included in the runtime.
 2. Additionally, we can find an existing smart contract. Click on "Add an existing contract" link.
 3. Input this contract address: `5GdNqKMv4Sszq3SRd3TkXNa6a9ct4D3nXvtTWTFR7rTyccVJ`
 4. Input any contract name, for example `Claim Substrapunks`
-5. Download and drag-and-drop this [metadata.json](https://github.com/usetech-llc/substrapunks/releases/download/v1.0.2/metadata.json) file into contract ABI field.
-6. Click Save. The contract appears in the page. If the contract did not exist at that address, the UI would display an error message: "Unable to find deployed contract code at the specified address".
+5. Download and drag-and-drop this
+   [metadata.json](https://github.com/usetech-llc/substrapunks/releases/download/v1.0.2/metadata.json) file into
+   contract ABI field.
+6. Click Save. The contract appears in the page. If the contract did not exist at that address, the UI would display an
+   error message: "Unable to find deployed contract code at the specified address".
 
 ### Integration Between Smart Contracts and NFT Pallet
 
-This was one of the most challenging parts of the Hackusama for us. The pre-RC4 versions of Substrate did not function properly when we tried to use `ext_dispatch_call` to dispatch a runtime call to NFT pallet from Contracts pallet, and RC4 had the `ext_dispatch_call` already removed to "free space" for some friendlier way of interaction between pallets, that was not yet implemented. Thanks to Alexander Theißen who reverted the removal of `ext_dispatch_call` and created a special branch based on RC4 for us!
+This was one of the most challenging parts of the Hackusama for us. The pre-RC4 versions of Substrate did not function
+properly when we tried to use `ext_dispatch_call` to dispatch a runtime call to NFT pallet from Contracts pallet, and
+RC4 had the `ext_dispatch_call` already removed to "free space" for some friendlier way of interaction between pallets,
+that was not yet implemented. Thanks to Alexander Theißen who reverted the removal of `ext_dispatch_call` and created a
+special branch based on RC4 for us!
 
-The smart contract source code exists in this [repository folder](https://github.com/usetech-llc/nft_parachain/tree/master/smart_contract/ink-types-node-runtime), but the best way to test how the interaction between smart contracts and NFT pallet works is to see it in action using the [SubstraPunks Game Example](https://ipfs-gateway.usetech.com/ipns/QmaMtDqE9nhMX9RQLTpaCboqg7bqkb6Gi67iCKMe8NDpCE/) that uses smart contract to claim free characters. The complete demo of this game will come later, so please bare with us and let's put this item demonstration for a bit later.
+The smart contract source code exists in this
+[repository folder](https://github.com/usetech-llc/nft_parachain/tree/master/smart_contract/ink-types-node-runtime), but
+the best way to test how the interaction between smart contracts and NFT pallet works is to see it in action using the
+[SubstraPunks Game Example](https://ipfs-gateway.usetech.com/ipns/QmaMtDqE9nhMX9RQLTpaCboqg7bqkb6Gi67iCKMe8NDpCE/) that
+uses smart contract to claim free characters. The complete demo of this game will come later, so please bare with us and
+let's put this item demonstration for a bit later.
 
 ### Re-Fungibility support
 
-This feature is best demonstrated in action using our NFT wallet. First, you need to have some Unique and Re-Fungible tokens. [This section](#getting-some-unique-tokens) tells how to get them.
+This feature is best demonstrated in action using our NFT wallet. First, you need to have some Unique and Re-Fungible
+tokens. [This section](#getting-some-unique-tokens) tells how to get them.
 
 1. Open the [NFT Wallet](https://uniqueapps.usetech.com/#/nft)
 2. Search for collection called "Artwork". Search can be done either by collection name or collection ID (which is 2).
 3. Click on "+ Add collection" in search results. The collection will appear under "My collections"
 4. Expand the "Artwork" collection and see that you own a token with a partial balance
-5. Transfer the token to some other address: Click "Transfer token" and enter address and the amount. Amount should be entered as decimal fraction. For example "0.01" to transfer 1/100th part of the token. 
+5. Transfer the token to some other address: Click "Transfer token" and enter address and the amount. Amount should be
+   entered as decimal fraction. For example "0.01" to transfer 1/100th part of the token.
 6. Observe that your balance decreased by the amount you entered.
 
 #### The Hard Way
@@ -84,15 +108,19 @@
       fraction: 1,000
     }
   ],
-  Data: 
+  Data:
 }
 ```
 
-The Owner field contains a list of owners with the fractions they own. The fractions are represented as fixed point decimals. The number of decimal points in this fraction is determined by collection properties. For example, in the Artwork collection this property is equal to 4. The owned fraction is then determined as `fraction` divided by 10^DecimalPoints, i.e. 10,000 in this case.
+The Owner field contains a list of owners with the fractions they own. The fractions are represented as fixed point
+decimals. The number of decimal points in this fraction is determined by collection properties. For example, in the
+Artwork collection this property is equal to 4. The owned fraction is then determined as `fraction` divided by
+10^DecimalPoints, i.e. 10,000 in this case.
 
 5. Staying in Chain State, select "nft" - "colection"
 6. Input "2" and click "+"
-7. Observe the following structure returned. It tells that collection is ReFungible and DecimalPoints field is equal to 4.
+7. Observe the following structure returned. It tells that collection is ReFungible and DecimalPoints field is equal
+   to 4.
 
 ```
 {
@@ -111,21 +139,25 @@
 
 ### Off-Chain Schema to store token image URLs
 
-This feature is best demonstrated in action using our NFT wallet. First, you need to have some Unique and Re-Fungible tokens. [This section](#getting-some-unique-tokens) tells how to get them.
+This feature is best demonstrated in action using our NFT wallet. First, you need to have some Unique and Re-Fungible
+tokens. [This section](#getting-some-unique-tokens) tells how to get them.
 
 1. Open the [NFT Wallet](https://uniqueapps.usetech.com/#/nft)
 2. Search for collection called "Artwork". Search can be done either by collection name or collection ID (which is 2).
 3. Expand "Artwork" collection
-4. Click on the image to zoom in. 
+4. Click on the image to zoom in.
 5. Right-click on the image and select "Open Image in New Tab"
 6. Note the image URL:
+
 ```
 https://ipfs-gateway.usetech.com/ipfs/QmUSv64cUmL2m44QYkUFWmH89qykC8VLPFwjhpeAScjejS/image1.jpg
-``` 
+```
+
 7. Open [Chain State](https://uniqueapps.usetech.com/#/chainstate).
 8. Select "nft" - "colection"
 9. Input "2" and click "+"
 10. Scroll down:
+
 ```
   ],
   TokenPrefix: ARTu0000,
@@ -135,23 +167,32 @@
   UnconfirmedSponsor: 5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM
 }
 ```
-11. Observe the field called `OffchainSchema`. This field defines the URL schema that is used to contruct token URLs based on the token ID. In this particular example, the {id} template variable is replaced with token id 1, which results in the proper image URL.
 
+11. Observe the field called `OffchainSchema`. This field defines the URL schema that is used to contruct token URLs
+    based on the token ID. In this particular example, the {id} template variable is replaced with token id 1, which
+    results in the proper image URL.
+
 ### New economic models
 
-This feature is best demonstrated when you own a SubstraPunk character. Please proceed with the demonstration, and then come back after you claim one.
+This feature is best demonstrated when you own a SubstraPunk character. Please proceed with the demonstration, and then
+come back after you claim one.
 
-1. Create a new address in Polkadot{.js} extension. We need an address that has zero balance in Unique tokens. Let's call it "ZERO BALANCE" address.
+1. Create a new address in Polkadot{.js} extension. We need an address that has zero balance in Unique tokens. Let's
+   call it "ZERO BALANCE" address.
 2. Open the [NFT Wallet](https://uniqueapps.usetech.com/#/nft)
 3. Search for collection called "SubstraPunks" (partial search also works, so you can just type "punk" and hit Enter)
 4. Add collection
 5. Expand substrapunks collection and transfer your PNK token to your "ZERO BALANCE" address.
-6. Now select your "ZERO BALANCE" address in the drop-down list and repeat searching and adding the collection for this address.
+6. Now select your "ZERO BALANCE" address in the drop-down list and repeat searching and adding the collection for this
+   address.
 7. Transfer token back to your main address. Observe that despite the zero balance, the transfer is successful.
 
 ### White Lists and Public Mint Permission
 
-We did not complete these features in time by Hackusama deadline, but because they are important for security of the network, we completed them after the deadline anyway. They can be seen in branch [feature/white_list](https://github.com/usetech-llc/nft_parachain/tree/feature/white_list). Here are the permalinks to essential functions:
+We did not complete these features in time by Hackusama deadline, but because they are important for security of the
+network, we completed them after the deadline anyway. They can be seen in branch
+[feature/white_list](https://github.com/usetech-llc/nft_parachain/tree/feature/white_list). Here are the permalinks to
+essential functions:
 
 [white_lists](https://github.com/usetech-llc/nft_parachain/blob/b7c59f0085ed2bc1922e937adf68ef4174a8ba36/pallets/nft/src/lib.rs#L659)
 
@@ -163,17 +204,18 @@
 
 All features of the NFT Wallet were already demonstrated previously:
 
-* Enables adding favorite collections
-* Shows tokens owned
-* Allows NFT and Re-Fungible transfers
-* Shows Re-Fungible Balances
-* Shows Token Images
+-   Enables adding favorite collections
+-   Shows tokens owned
+-   Allows NFT and Re-Fungible transfers
+-   Shows Re-Fungible Balances
+-   Shows Token Images
 
 ## Our version of AppsUI
 
 The UI was also mainly demonstrated. We only need to show some configuration that happens behind the scenes:
 
 ### Loads appropriate custom API types
+
 1. Open the [NFT Wallet Developer Settings](https://uniqueapps.usetech.com/#/settings/developer)
 2. Observe that Custom UI types are already in place
 
@@ -190,31 +232,41 @@
 In order to see Unity Asset in action, please follow the instructions in this README file:
 
 [Demonstration](https://github.com/usetech-llc/nft_unity/blob/master/src/DemoApplication/readme.md)
-
 
 ## SubstraPunks Game
 
 [Project Description](https://github.com/usetech-llc/substrapunks/blob/master/README.md)
 
-First, you will need some Unique balance. [This section](#getting-some-unique-tokens) tells how to get the TestNet currency.
+First, you will need some Unique balance. [This section](#getting-some-unique-tokens) tells how to get the TestNet
+currency.
 
-1. Open this [SubstraPunks Game](https://ipfs-gateway.usetech.com/ipns/QmaMtDqE9nhMX9RQLTpaCboqg7bqkb6Gi67iCKMe8NDpCE/) IPFS link
-2. Find a character you like that still has red background. Red indicates that the character was not yet claimed by anyone.
+1. Open this [SubstraPunks Game](https://ipfs-gateway.usetech.com/ipns/QmaMtDqE9nhMX9RQLTpaCboqg7bqkb6Gi67iCKMe8NDpCE/)
+   IPFS link
+2. Find a character you like that still has red background. Red indicates that the character was not yet claimed by
+   anyone.
 3. Allow access for Polkadot{.js} to this site
 4. Click "Claim" button
 5. Select the account that has some Unique balance
-6. Click "Claim" again, sign transaction, and wait until it mines. 
+6. Click "Claim" again, sign transaction, and wait until it mines.
 
-Let's make a pause here: You were just demonstrated how the integration between smart contracts and NFT Pallet works. This is what we've been previously talking about [here](#integration-between-smart-contracts-and-nft-pallet)
+Let's make a pause here: You were just demonstrated how the integration between smart contracts and NFT Pallet works.
+This is what we've been previously talking about [here](#integration-between-smart-contracts-and-nft-pallet)
 
-The source code of the generic smart contract is located in [smart_contract](https://github.com/usetech-llc/nft_parachain/tree/dev/smart_contract/ink-types-node-runtime) folder. But to demonstrate smart contracts in practice we created additional [Claim Contract](https://github.com/usetech-llc/substrapunks/tree/master/smart_contract) to implement claiming functionality in this game. 
+The source code of the generic smart contract is located in
+[smart_contract](https://github.com/usetech-llc/nft_parachain/tree/dev/smart_contract/ink-types-node-runtime) folder.
+But to demonstrate smart contracts in practice we created additional
+[Claim Contract](https://github.com/usetech-llc/substrapunks/tree/master/smart_contract) to implement claiming
+functionality in this game.
+
+Here is how Claiming works: Claim Contract owns all characters in the game initially. Player sends a transaction calling
+the method claim:
 
-Here is how Claiming works: Claim Contract owns all characters in the game initially. Player sends a transaction calling the method claim:
 ```
 fn claim(&mut self, collection_id: u64, item_id: u64, new_owner: AccountId)
 ```
 
 Inside the method `claim` there is a call made to NFT pallet to transfer the token that is being claimed to the player:
+
 ```
 runtime_calls::transfer(collection_id, item_id, new_owner);
 ```
@@ -222,6 +274,5 @@
 7. Follow the link to [NFT Wallet](https://uniqueapps.usetech.com/#/nft)
 8. Search for "SubstraPunks" collection, add it
 9. Expand the collection and find your character in there!
-10. Now you can try transfers and test the [economic model](#new-economic-models), the last thing we put off until a SubstraPunks character is claimed.
-
-
+10. Now you can try transfers and test the [economic model](#new-economic-models), the last thing we put off until a
+    SubstraPunks character is claimed.
modifiednode/Cargo.tomldiffbeforeafterboth
--- a/node/Cargo.toml
+++ b/node/Cargo.toml
@@ -12,9 +12,9 @@
 description = 'Substrate node nft'
 edition = '2018'
 homepage = 'https://usetech.com/blockchain'
-license = 'Unlicense'
+license = 'All Rights Reserved'
 name = 'nft'
-repository = 'https://github.com/substrate-developer-hub/nft/'
+repository = 'https://github.com/usetech-llc/nft_private/'
 version = '3.0.0'
 
 [package.metadata.docs.rs]
@@ -29,7 +29,7 @@
 jsonrpc-core = '15.0.0'
 
 # Substrate dependencies
-nft-runtime = { path = '../runtime', version = '3.0.0' }
+nft-runtime = { path = '../runtime', version = '2.1.0' }
 frame-benchmarking = {version = '2.0.1', git = 'https://github.com/usetech-llc/substrate.git', branch = 'unique'}
 frame-benchmarking-cli = {version = '2.0.1', git = 'https://github.com/usetech-llc/substrate.git', branch = 'unique'}
 pallet-transaction-payment-rpc = {version = '2.0.1', git = 'https://github.com/usetech-llc/substrate.git', branch = 'unique'}
modifiednode/src/command.rsdiffbeforeafterboth
--- a/node/src/command.rs
+++ b/node/src/command.rs
@@ -23,7 +23,7 @@
 
 impl SubstrateCli for Cli {
 	fn impl_name() -> String {
-		"Substrate Node".into()
+		"Unique Node".into()
 	}
 
 	fn impl_version() -> String {
modifiedpallets/nft/Cargo.tomldiffbeforeafterboth
--- a/pallets/nft/Cargo.toml
+++ b/pallets/nft/Cargo.toml
@@ -6,7 +6,7 @@
 license = 'Unlicense'
 name = 'pallet-nft'
 repository = 'https://github.com/substrate-developer-hub/nft/'
-version = '3.0.0'
+version = '2.1.0'
 
 [package.metadata.docs.rs]
 targets = ['x86_64-unknown-linux-gnu']
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
before · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18    construct_runtime, decl_event, decl_module, decl_storage, decl_error,19    dispatch::DispatchResult,20    ensure, fail, parameter_types,21    traits::{22        Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23        Randomness, IsSubType,24    },25    weights::{26        constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27        DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28        WeightToFeePolynomial, DispatchClass,29    },30    StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36    traits::{37        DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38    },39    transaction_validity::{40        TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41    },42    FixedPointOperand, FixedU128,43};44use sp_runtime::traits::StaticLookup;45use pallet_contracts::chain_extension::UncheckedFrom;46use transaction_payment::OnChargeTransaction;4748#[cfg(test)]49mod mock;5051#[cfg(test)]52mod tests;5354mod default_weights;5556pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;57pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;58pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;59pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;6061// Structs62// #region6364pub type CollectionId = u32;65pub type TokenId = u32;66pub type DecimalPoints = u8;6768#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]69#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]70pub enum CollectionMode {71    Invalid,72    NFT,73    // decimal points74    Fungible(DecimalPoints),75    ReFungible,76}7778impl Default for CollectionMode {79    fn default() -> Self {80        Self::Invalid81    }82}8384impl Into<u8> for CollectionMode {85    fn into(self) -> u8 {86        match self {87            CollectionMode::Invalid => 0,88            CollectionMode::NFT => 1,89            CollectionMode::Fungible(_) => 2,90            CollectionMode::ReFungible => 3,91        }92    }93}9495#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]96#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]97pub enum AccessMode {98    Normal,99    WhiteList,100}101impl Default for AccessMode {102    fn default() -> Self {103        Self::Normal104    }105}106107#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]108#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]109pub enum SchemaVersion {110    ImageURL,111    Unique,112}113impl Default for SchemaVersion {114    fn default() -> Self {115        Self::ImageURL116    }117}118119#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]120#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]121pub struct Ownership<AccountId> {122    pub owner: AccountId,123    pub fraction: u128,124}125126#[derive(Encode, Decode, Clone, PartialEq)]127#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]128pub struct CollectionType<T: Config> {129    pub owner: T::AccountId,130    pub mode: CollectionMode,131    pub access: AccessMode,132    pub decimal_points: DecimalPoints,133    pub name: Vec<u16>,        // 64 include null escape char134    pub description: Vec<u16>, // 256 include null escape char135    pub token_prefix: Vec<u8>, // 16 include null escape char136    pub mint_mode: bool,137    pub offchain_schema: Vec<u8>,138    pub schema_version: SchemaVersion,139    pub sponsor: T::AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender140    pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.141    pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions 142    pub variable_on_chain_schema: Vec<u8>, //143    pub const_on_chain_schema: Vec<u8>, //144}145146#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]147#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]148pub struct NftItemType<AccountId> {149    pub owner: AccountId,150    pub const_data: Vec<u8>,151    pub variable_data: Vec<u8>,152}153154#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]155#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]156pub struct FungibleItemType {157    pub value: u128,158}159160#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]161#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]162pub struct ReFungibleItemType<AccountId> {163    pub owner: Vec<Ownership<AccountId>>,164    pub const_data: Vec<u8>,165    pub variable_data: Vec<u8>,166}167168// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]169// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]170// pub struct VestingItem<AccountId, Moment> {171//     pub sender: AccountId,172//     pub recipient: AccountId,173//     pub collection_id: CollectionId,174//     pub item_id: TokenId,175//     pub amount: u64,176//     pub vesting_date: Moment,177// }178179#[derive(Encode, Decode, Debug, Clone, PartialEq)]180#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]181pub struct CollectionLimits<BlockNumber: Encode + Decode> {182    pub account_token_ownership_limit: u32,183    pub sponsored_data_size: u32,184    /// None - setVariableMetadata is not sponsored185    /// Some(v) - setVariableMetadata is sponsored 186    ///           if there is v block between txs187    pub sponsored_data_rate_limit: Option<BlockNumber>,188    pub token_limit: u32,189190    // Timeouts for item types in passed blocks191    pub sponsor_transfer_timeout: u32,192    pub owner_can_transfer: bool,193    pub owner_can_destroy: bool,194}195196impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {197    fn default() -> Self {198        Self { 199            account_token_ownership_limit: 10_000_000, 200            token_limit: u32::max_value(),201            sponsored_data_size: u32::max_value(), 202            sponsored_data_rate_limit: None,203            sponsor_transfer_timeout: 14400,204            owner_can_transfer: true,205            owner_can_destroy: true206        }207    }208}209210#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]211#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]212pub struct ChainLimits {213    pub collection_numbers_limit: u32,214    pub account_token_ownership_limit: u32,215    pub collections_admins_limit: u64,216    pub custom_data_limit: u32,217218    // Timeouts for item types in passed blocks219    pub nft_sponsor_transfer_timeout: u32,220    pub fungible_sponsor_transfer_timeout: u32,221    pub refungible_sponsor_transfer_timeout: u32,222223    // Schema limits224    pub offchain_schema_limit: u32,225    pub variable_on_chain_schema_limit: u32,226    pub const_on_chain_schema_limit: u32,227}228229pub trait WeightInfo {230	fn create_collection() -> Weight;231	fn destroy_collection() -> Weight;232	fn add_to_white_list() -> Weight;233	fn remove_from_white_list() -> Weight;234    fn set_public_access_mode() -> Weight;235    fn set_mint_permission() -> Weight;236    fn change_collection_owner() -> Weight;237    fn add_collection_admin() -> Weight;238    fn remove_collection_admin() -> Weight;239    fn set_collection_sponsor() -> Weight;240    fn confirm_sponsorship() -> Weight;241    fn remove_collection_sponsor() -> Weight;242    fn create_item(s: usize) -> Weight;243    fn burn_item() -> Weight;244    fn transfer() -> Weight;245    fn approve() -> Weight;246    fn transfer_from() -> Weight;247    fn set_offchain_schema() -> Weight;248    fn set_const_on_chain_schema() -> Weight;249    fn set_variable_on_chain_schema() -> Weight;250    fn set_variable_meta_data() -> Weight;251    fn enable_contract_sponsoring() -> Weight;252    fn set_schema_version() -> Weight;253    fn set_chain_limits() -> Weight;254    fn set_contract_sponsoring_rate_limit() -> Weight;255    fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;256    fn toggle_contract_white_list() -> Weight;257    fn add_to_contract_white_list() -> Weight;258    fn remove_from_contract_white_list() -> Weight;259    fn set_collection_limits() -> Weight;260}261262#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]263#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]264pub struct CreateNftData {265    pub const_data: Vec<u8>,266    pub variable_data: Vec<u8>,267}268269#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]270#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]271pub struct CreateFungibleData {272    pub value: u128,273}274275#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]276#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]277pub struct CreateReFungibleData {278    pub const_data: Vec<u8>,279    pub variable_data: Vec<u8>,280    pub pieces: u128,281}282283#[derive(Encode, Decode, Debug, Clone, PartialEq)]284#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]285pub enum CreateItemData {286    NFT(CreateNftData),287    Fungible(CreateFungibleData),288    ReFungible(CreateReFungibleData),289}290291impl CreateItemData {292    pub fn len(&self) -> usize {293        let len = match self {294            CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),295            CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),296            _ => 0297        };298        299        return len;300    }301}302303impl From<CreateNftData> for CreateItemData {304    fn from(item: CreateNftData) -> Self {305        CreateItemData::NFT(item)306    }307}308309impl From<CreateReFungibleData> for CreateItemData {310    fn from(item: CreateReFungibleData) -> Self {311        CreateItemData::ReFungible(item)312    }313}314315impl From<CreateFungibleData> for CreateItemData {316    fn from(item: CreateFungibleData) -> Self {317        CreateItemData::Fungible(item)318    }319}320321322decl_error! {323	/// Error for non-fungible-token module.324	pub enum Error for Module<T: Config> {325        /// Total collections bound exceeded.326        TotalCollectionsLimitExceeded,327		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.328        CollectionDecimalPointLimitExceeded, 329        /// Collection name can not be longer than 63 char.330        CollectionNameLimitExceeded, 331        /// Collection description can not be longer than 255 char.332        CollectionDescriptionLimitExceeded, 333        /// Token prefix can not be longer than 15 char.334        CollectionTokenPrefixLimitExceeded,335        /// This collection does not exist.336        CollectionNotFound,337        /// Item not exists.338        TokenNotFound,339        /// Admin not found340        AdminNotFound,341        /// Arithmetic calculation overflow.342        NumOverflow,       343        /// Account already has admin role.344        AlreadyAdmin,  345        /// You do not own this collection.346        NoPermission,347        /// This address is not set as sponsor, use setCollectionSponsor first.348        ConfirmUnsetSponsorFail,349        /// Collection is not in mint mode.350        PublicMintingNotAllowed,351        /// Sender parameter and item owner must be equal.352        MustBeTokenOwner,353        /// Item balance not enough.354        TokenValueTooLow,355        /// Size of item is too large.356        NftSizeLimitExceeded,357        /// No approve found358        ApproveNotFound,359        /// Requested value more than approved.360        TokenValueNotEnough,361        /// Only approved addresses can call this method.362        ApproveRequired,363        /// Address is not in white list.364        AddresNotInWhiteList,365        /// Number of collection admins bound exceeded.366        CollectionAdminsLimitExceeded,367        /// Owned tokens by a single address bound exceeded.368        AddressOwnershipLimitExceeded,369        /// Length of items properties must be greater than 0.370        EmptyArgument,371        /// const_data exceeded data limit.372        TokenConstDataLimitExceeded,373        /// variable_data exceeded data limit.374        TokenVariableDataLimitExceeded,375        /// Not NFT item data used to mint in NFT collection.376        NotNftDataUsedToMintNftCollectionToken,377        /// Not Fungible item data used to mint in Fungible collection.378        NotFungibleDataUsedToMintFungibleCollectionToken,379        /// Not Re Fungible item data used to mint in Re Fungible collection.380        NotReFungibleDataUsedToMintReFungibleCollectionToken,381        /// Unexpected collection type.382        UnexpectedCollectionType,383        /// Can't store metadata in fungible tokens.384        CantStoreMetadataInFungibleTokens,385        /// Collection token limit exceeded386        CollectionTokenLimitExceeded,387        /// Account token limit exceeded per collection388        AccountTokenLimitExceeded,389        /// Collection limit bounds per collection exceeded390        CollectionLimitBoundsExceeded,391        /// Schema data size limit bound exceeded392        SchemaDataLimitExceeded,393        /// Maximum refungibility exceeded394        WrongRefungiblePieces395	}396}397398pub trait Config: system::Config + Sized + transaction_payment::Config + pallet_contracts::Config {399    type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;400401    /// Weight information for extrinsics in this pallet.402	type WeightInfo: WeightInfo;403}404405#[cfg(feature = "runtime-benchmarks")]406mod benchmarking;407408// #endregion409410decl_storage! {411    trait Store for Module<T: Config> as Nft {412413        // Private members414        NextCollectionID: CollectionId;415        CreatedCollectionCount: u32;416        ChainVersion: u64;417        ItemListIndex: map hasher(identity) CollectionId => TokenId;418419        // Chain limits struct420        pub ChainLimit get(fn chain_limit) config(): ChainLimits;421422        // Bound counters423        CollectionCount: u32;424        pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;425426        // Basic collections427        pub Collection get(fn collection) config(): map hasher(identity) CollectionId => Option<CollectionType<T>> = None;428        pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;429        pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;430431        /// Balance owner per collection map432        pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;433434        /// second parameter: item id + owner account id + spender account id435        pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;436437        /// Item collections438        pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;439        pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;440        pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;441442        /// Index list443        pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;444445        /// Tokens transfer baskets446        pub CreateItemBasket get(fn create_item_basket): map hasher(twox_64_concat) (CollectionId, T::AccountId) => T::BlockNumber;447        pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;448        pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;449        pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;450451        /// Variable metadata sponsoring452        pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Option<T::BlockNumber> = None;453454        // Contract Sponsorship and Ownership455        pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;456        pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;457        pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;458        pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;459        pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 460        pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 461    }462    add_extra_genesis {463        build(|config: &GenesisConfig<T>| {464            // Modification of storage465            for (_num, _c) in &config.collection {466                <Module<T>>::init_collection(_c);467            }468469            for (_num, _c, _i) in &config.nft_item_id {470                <Module<T>>::init_nft_token(*_c, _i);471            }472473            for (collection_id, account_id, fungible_item) in &config.fungible_item_id {474                <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);475            }476477            for (_num, _c, _i) in &config.refungible_item_id {478                <Module<T>>::init_refungible_token(*_c, _i);479            }480        })481    }482}483484decl_event!(485    pub enum Event<T>486    where487        AccountId = <T as system::Config>::AccountId,488    {489        /// New collection was created490        /// 491        /// # Arguments492        /// 493        /// * collection_id: Globally unique identifier of newly created collection.494        /// 495        /// * mode: [CollectionMode] converted into u8.496        /// 497        /// * account_id: Collection owner.498        Created(CollectionId, u8, AccountId),499500        /// New item was created.501        /// 502        /// # Arguments503        /// 504        /// * collection_id: Id of the collection where item was created.505        /// 506        /// * item_id: Id of an item. Unique within the collection.507        ItemCreated(CollectionId, TokenId),508509        /// Collection item was burned.510        /// 511        /// # Arguments512        /// 513        /// collection_id.514        /// 515        /// item_id: Identifier of burned NFT.516        ItemDestroyed(CollectionId, TokenId),517    }518);519520decl_module! {521    pub struct Module<T: Config> for enum Call 522    where 523        origin: T::Origin524    {525        fn deposit_event() = default;526        type Error = Error<T>;527528        fn on_initialize(now: T::BlockNumber) -> Weight {529530            if ChainVersion::get() < 2531            {532                let value = NextCollectionID::get();533                CreatedCollectionCount::put(value);534                ChainVersion::put(2);535            }536537            0538        }539540        /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.541        /// 542        /// # Permissions543        /// 544        /// * Anyone.545        /// 546        /// # Arguments547        /// 548        /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.549        /// 550        /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.551        /// 552        /// * token_prefix: UTF-8 string with token prefix.553        /// 554        /// * mode: [CollectionMode] collection type and type dependent data.555        // returns collection ID556        #[weight = <T as Config>::WeightInfo::create_collection()]557        pub fn create_collection(origin,558                                 collection_name: Vec<u16>,559                                 collection_description: Vec<u16>,560                                 token_prefix: Vec<u8>,561                                 mode: CollectionMode) -> DispatchResult {562563            // Anyone can create a collection564            let who = ensure_signed(origin)?;565566            let decimal_points = match mode {567                CollectionMode::Fungible(points) => points,568                _ => 0569            };570571            // bound Total number of collections572            ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);573574            // check params575            ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);576            ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);577            ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);578            ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);579580            // Generate next collection ID581            let next_id = CreatedCollectionCount::get()582                .checked_add(1)583                .ok_or(Error::<T>::NumOverflow)?;584585            // bound counter586            let total = CollectionCount::get()587                .checked_add(1)588                .ok_or(Error::<T>::NumOverflow)?;589590            CreatedCollectionCount::put(next_id);591            CollectionCount::put(total);592593            // Create new collection594            let new_collection = CollectionType {595                owner: who.clone(),596                name: collection_name,597                mode: mode.clone(),598                mint_mode: false,599                access: AccessMode::Normal,600                description: collection_description,601                decimal_points: decimal_points,602                token_prefix: token_prefix,603                offchain_schema: Vec::new(),604                schema_version: SchemaVersion::ImageURL,605                sponsor: T::AccountId::default(),606                sponsor_confirmed: false,607                variable_on_chain_schema: Vec::new(),608                const_on_chain_schema: Vec::new(),609                limits: CollectionLimits::default()610            };611612            // Add new collection to map613            <Collection<T>>::insert(next_id, new_collection);614615            // call event616            Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));617618            Ok(())619        }620621        /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.622        /// 623        /// # Permissions624        /// 625        /// * Collection Owner.626        /// 627        /// # Arguments628        /// 629        /// * collection_id: collection to destroy.630        #[weight = <T as Config>::WeightInfo::destroy_collection()]631        pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {632633            let sender = ensure_signed(origin)?;634            Self::check_owner_permissions(collection_id, sender)?;635636            <AddressTokens<T>>::remove_prefix(collection_id);637            <Allowances<T>>::remove_prefix(collection_id);638            <Balance<T>>::remove_prefix(collection_id);639            <ItemListIndex>::remove(collection_id);640            <AdminList<T>>::remove(collection_id);641            <Collection<T>>::remove(collection_id);642            <WhiteList<T>>::remove_prefix(collection_id);643644            <NftItemList<T>>::remove_prefix(collection_id);645            <FungibleItemList<T>>::remove_prefix(collection_id);646            <ReFungibleItemList<T>>::remove_prefix(collection_id);647648            <NftTransferBasket<T>>::remove_prefix(collection_id);649            <FungibleTransferBasket<T>>::remove_prefix(collection_id);650            <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);651652            <VariableMetaDataBasket<T>>::remove_prefix(collection_id);653654            if CollectionCount::get() > 0655            {656                // bound couter657                let total = CollectionCount::get()658                    .checked_sub(1)659                    .ok_or(Error::<T>::NumOverflow)?;660661                CollectionCount::put(total);662            }663664            Ok(())665        }666667        /// Add an address to white list.668        /// 669        /// # Permissions670        /// 671        /// * Collection Owner672        /// * Collection Admin673        /// 674        /// # Arguments675        /// 676        /// * collection_id.677        /// 678        /// * address.679        #[weight = <T as Config>::WeightInfo::add_to_white_list()]680        pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{681682            let sender = ensure_signed(origin)?;683            Self::check_owner_or_admin_permissions(collection_id, sender)?;684685            <WhiteList<T>>::insert(collection_id, address, true);686            687            Ok(())688        }689690        /// Remove an address from white list.691        /// 692        /// # Permissions693        /// 694        /// * Collection Owner695        /// * Collection Admin696        /// 697        /// # Arguments698        /// 699        /// * collection_id.700        /// 701        /// * address.702        #[weight = <T as Config>::WeightInfo::remove_from_white_list()]703        pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{704705            let sender = ensure_signed(origin)?;706            Self::check_owner_or_admin_permissions(collection_id, sender)?;707708            <WhiteList<T>>::remove(collection_id, address);709710            Ok(())711        }712713        /// Toggle between normal and white list access for the methods with access for `Anyone`.714        /// 715        /// # Permissions716        /// 717        /// * Collection Owner.718        /// 719        /// # Arguments720        /// 721        /// * collection_id.722        /// 723        /// * mode: [AccessMode]724        #[weight = <T as Config>::WeightInfo::set_public_access_mode()]725        pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult726        {727            let sender = ensure_signed(origin)?;728729            Self::check_owner_permissions(collection_id, sender)?;730            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();731            target_collection.access = mode;732            <Collection<T>>::insert(collection_id, target_collection);733734            Ok(())735        }736737        /// Allows Anyone to create tokens if:738        /// * White List is enabled, and739        /// * Address is added to white list, and740        /// * This method was called with True parameter741        /// 742        /// # Permissions743        /// * Collection Owner744        ///745        /// # Arguments746        /// 747        /// * collection_id.748        /// 749        /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.750        #[weight = <T as Config>::WeightInfo::set_mint_permission()]751        pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult752        {753            let sender = ensure_signed(origin)?;754755            Self::check_owner_permissions(collection_id, sender)?;756            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();757            target_collection.mint_mode = mint_permission;758            <Collection<T>>::insert(collection_id, target_collection);759760            Ok(())761        }762763        /// Change the owner of the collection.764        /// 765        /// # Permissions766        /// 767        /// * Collection Owner.768        /// 769        /// # Arguments770        /// 771        /// * collection_id.772        /// 773        /// * new_owner.774        #[weight = <T as Config>::WeightInfo::change_collection_owner()]775        pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {776777            let sender = ensure_signed(origin)?;778            Self::check_owner_permissions(collection_id, sender)?;779            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();780            target_collection.owner = new_owner;781            <Collection<T>>::insert(collection_id, target_collection);782783            Ok(())784        }785786        /// Adds an admin of the Collection.787        /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership. 788        /// 789        /// # Permissions790        /// 791        /// * Collection Owner.792        /// * Collection Admin.793        /// 794        /// # Arguments795        /// 796        /// * collection_id: ID of the Collection to add admin for.797        /// 798        /// * new_admin_id: Address of new admin to add.799        #[weight = <T as Config>::WeightInfo::add_collection_admin()]800        pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {801802            let sender = ensure_signed(origin)?;803            Self::check_owner_or_admin_permissions(collection_id, sender)?;804            let mut admin_arr: Vec<T::AccountId> = Vec::new();805806            if <AdminList<T>>::contains_key(collection_id)807            {808                admin_arr = <AdminList<T>>::get(collection_id);809                ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);810            }811812            // Number of collection admins813            ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);814815            admin_arr.push(new_admin_id);816            <AdminList<T>>::insert(collection_id, admin_arr);817818            Ok(())819        }820821        /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.822        ///823        /// # Permissions824        /// 825        /// * Collection Owner.826        /// * Collection Admin.827        /// 828        /// # Arguments829        /// 830        /// * collection_id: ID of the Collection to remove admin for.831        /// 832        /// * account_id: Address of admin to remove.833        #[weight = <T as Config>::WeightInfo::remove_collection_admin()]834        pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {835836            let sender = ensure_signed(origin)?;837            Self::check_owner_or_admin_permissions(collection_id, sender)?;838            ensure!(<AdminList<T>>::contains_key(collection_id), Error::<T>::AdminNotFound);839840            let mut admin_arr = <AdminList<T>>::get(collection_id);841            admin_arr.retain(|i| *i != account_id);842            <AdminList<T>>::insert(collection_id, admin_arr);843844            Ok(())845        }846847        /// # Permissions848        /// 849        /// * Collection Owner850        /// 851        /// # Arguments852        /// 853        /// * collection_id.854        /// 855        /// * new_sponsor.856        #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]857        pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {858859            let sender = ensure_signed(origin)?;860861            let mut target_collection = <Collection<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?;862            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);863864            target_collection.sponsor = new_sponsor;865            target_collection.sponsor_confirmed = false;866            <Collection<T>>::insert(collection_id, target_collection);867868            Ok(())869        }870871        /// # Permissions872        /// 873        /// * Sponsor.874        /// 875        /// # Arguments876        /// 877        /// * collection_id.878        #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]879        pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {880881            let sender = ensure_signed(origin)?;882883            let mut target_collection = <Collection<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?;884            ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);885886            target_collection.sponsor_confirmed = true;887            <Collection<T>>::insert(collection_id, target_collection);888889            Ok(())890        }891892        /// Switch back to pay-per-own-transaction model.893        ///894        /// # Permissions895        ///896        /// * Collection owner.897        /// 898        /// # Arguments899        /// 900        /// * collection_id.901        #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]902        pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {903904            let sender = ensure_signed(origin)?;905906            let mut target_collection = <Collection<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?;907            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);908909            target_collection.sponsor = T::AccountId::default();910            target_collection.sponsor_confirmed = false;911            <Collection<T>>::insert(collection_id, target_collection);912913            Ok(())914        }915916        /// This method creates a concrete instance of NFT Collection created with CreateCollection method.917        /// 918        /// # Permissions919        /// 920        /// * Collection Owner.921        /// * Collection Admin.922        /// * Anyone if923        ///     * White List is enabled, and924        ///     * Address is added to white list, and925        ///     * MintPermission is enabled (see SetMintPermission method)926        /// 927        /// # Arguments928        /// 929        /// * collection_id: ID of the collection.930        /// 931        /// * owner: Address, initial owner of the NFT.932        ///933        /// * data: Token data to store on chain.934        // #[weight =935        // (130_000_000 as Weight)936        // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))937        // .saturating_add(RocksDbWeight::get().reads(10 as Weight))938        // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]939940        #[weight = <T as Config>::WeightInfo::create_item(data.len())]941        pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {942943            let sender = ensure_signed(origin)?;944945            Self::collection_exists(collection_id)?;946947            let target_collection = <Collection<T>>::get(collection_id).unwrap();948949            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;950            Self::validate_create_item_args(&target_collection, &data)?;951            Self::create_item_no_validation(collection_id, owner, data)?;952953            Ok(())954        }955956        /// This method creates multiple instances of NFT Collection created with CreateCollection method.957        /// 958        /// # Permissions959        /// 960        /// * Collection Owner.961        /// * Collection Admin.962        /// * Anyone if963        ///     * White List is enabled, and964        ///     * Address is added to white list, and965        ///     * MintPermission is enabled (see SetMintPermission method)966        /// 967        /// # Arguments968        /// 969        /// * collection_id: ID of the collection.970        /// 971        /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].972        /// 973        /// * owner: Address, initial owner of the NFT.974        #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()975                               .map(|data| { data.len() })976                               .sum())]977        pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {978979            ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);980            let sender = ensure_signed(origin)?;981982            Self::collection_exists(collection_id)?;983            let target_collection = <Collection<T>>::get(collection_id).unwrap();984985            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;986987            for data in &items_data {988                Self::validate_create_item_args(&target_collection, data)?;989            }990            for data in &items_data {991                Self::create_item_no_validation(collection_id, owner.clone(), data.clone())?;992            }993994            Ok(())995        }996997        /// Destroys a concrete instance of NFT.998        /// 999        /// # Permissions1000        /// 1001        /// * Collection Owner.1002        /// * Collection Admin.1003        /// * Current NFT Owner.1004        /// 1005        /// # Arguments1006        /// 1007        /// * collection_id: ID of the collection.1008        /// 1009        /// * item_id: ID of NFT to burn.1010        #[weight = <T as Config>::WeightInfo::burn_item()]1011        pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10121013            let sender = ensure_signed(origin)?;1014            Self::collection_exists(collection_id)?;10151016            // Transfer permissions check1017            let target_collection = <Collection<T>>::get(collection_id).unwrap();1018            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1019                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1020                Error::<T>::NoPermission);10211022            if target_collection.access == AccessMode::WhiteList {1023                Self::check_white_list(collection_id, &sender)?;1024            }10251026            match target_collection.mode1027            {1028                CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1029                CollectionMode::Fungible(_)  => Self::burn_fungible_item(&sender, collection_id, value)?,1030                CollectionMode::ReFungible  => Self::burn_refungible_item(collection_id, item_id, &sender)?,1031                _ => ()1032            };10331034            // call event1035            Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10361037            Ok(())1038        }10391040        /// Change ownership of the token.1041        /// 1042        /// # Permissions1043        /// 1044        /// * Collection Owner1045        /// * Collection Admin1046        /// * Current NFT owner1047        ///1048        /// # Arguments1049        /// 1050        /// * recipient: Address of token recipient.1051        /// 1052        /// * collection_id.1053        /// 1054        /// * item_id: ID of the item1055        ///     * Non-Fungible Mode: Required.1056        ///     * Fungible Mode: Ignored.1057        ///     * Re-Fungible Mode: Required.1058        /// 1059        /// * value: Amount to transfer.1060        ///     * Non-Fungible Mode: Ignored1061        ///     * Fungible Mode: Must specify transferred amount1062        ///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1063        #[weight = <T as Config>::WeightInfo::transfer()]1064        pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1065            let sender = ensure_signed(origin)?;1066            Self::transfer_internal(sender, recipient, collection_id, item_id, value)1067        }10681069        /// Set, change, or remove approved address to transfer the ownership of the NFT.1070        /// 1071        /// # Permissions1072        /// 1073        /// * Collection Owner1074        /// * Collection Admin1075        /// * Current NFT owner1076        /// 1077        /// # Arguments1078        /// 1079        /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1080        /// 1081        /// * collection_id.1082        /// 1083        /// * item_id: ID of the item.1084        #[weight = <T as Config>::WeightInfo::approve()]1085        pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {10861087            let sender = ensure_signed(origin)?;10881089            Self::collection_exists(collection_id)?;1090            Self::token_exists(collection_id, item_id, &sender)?;10911092            // Transfer permissions check1093            let target_collection = <Collection<T>>::get(collection_id).unwrap();1094            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1095                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1096                Error::<T>::NoPermission);10971098            if target_collection.access == AccessMode::WhiteList {1099                Self::check_white_list(collection_id, &sender)?;1100                Self::check_white_list(collection_id, &spender)?;1101            }11021103            let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1104            let mut allowance: u128 = amount;1105            if allowance_exists {1106                allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1107            }1108            <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11091110            Ok(())1111        }1112        1113        /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1114        /// 1115        /// # Permissions1116        /// * Collection Owner1117        /// * Collection Admin1118        /// * Current NFT owner1119        /// * Address approved by current NFT owner1120        /// 1121        /// # Arguments1122        /// 1123        /// * from: Address that owns token.1124        /// 1125        /// * recipient: Address of token recipient.1126        /// 1127        /// * collection_id.1128        /// 1129        /// * item_id: ID of the item.1130        /// 1131        /// * value: Amount to transfer.1132        #[weight = <T as Config>::WeightInfo::transfer_from()]1133        pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11341135            let sender = ensure_signed(origin)?;1136            let mut appoved_transfer = false;11371138            // Check approval1139            let mut approval: u128 = 0;1140            if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &sender)) {1141                approval = <Allowances<T>>::get(collection_id, (item_id, &from, &sender));1142                ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1143                appoved_transfer = true;1144            }11451146            let target_collection = <Collection<T>>::get(collection_id).unwrap();11471148            // Limits check1149            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11501151            // Transfer permissions check         1152            ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1153                Error::<T>::NoPermission);11541155            if target_collection.access == AccessMode::WhiteList {1156                Self::check_white_list(collection_id, &sender)?;1157                Self::check_white_list(collection_id, &recipient)?;1158            }11591160            // Reduce approval by transferred amount or remove if remaining approval drops to 01161            if approval.checked_sub(value).unwrap_or(0) > 0 {1162                <Allowances<T>>::insert(collection_id, (item_id, &from, &sender), approval - value);1163            }1164            else {1165                <Allowances<T>>::remove(collection_id, (item_id, &from, &sender));1166            }11671168            match target_collection.mode1169            {1170                CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1171                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1172                CollectionMode::ReFungible  => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1173                _ => ()1174            };11751176            Ok(())1177        }11781179        // #[weight = 0]1180        // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11811182        //     // let no_perm_mes = "You do not have permissions to modify this collection";1183        //     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1184        //     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1185        //     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11861187        //     // // on_nft_received  call11881189        //     // Self::transfer(origin, collection_id, item_id, new_owner)?;11901191        //     Ok(())1192        // }11931194        /// Set off-chain data schema.1195        /// 1196        /// # Permissions1197        /// 1198        /// * Collection Owner1199        /// * Collection Admin1200        /// 1201        /// # Arguments1202        /// 1203        /// * collection_id.1204        /// 1205        /// * schema: String representing the offchain data schema.1206        #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1207        pub fn set_variable_meta_data (1208            origin,1209            collection_id: CollectionId,1210            item_id: TokenId,1211            data: Vec<u8>1212        ) -> DispatchResult {1213            let sender = ensure_signed(origin)?;1214            1215            Self::collection_exists(collection_id)?;1216            Self::token_exists(collection_id, item_id, &sender)?;12171218            ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12191220            // Modify permissions check1221            let target_collection = <Collection<T>>::get(collection_id).unwrap();1222            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1223                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1224                Error::<T>::NoPermission);12251226            match target_collection.mode1227            {1228                CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1229                CollectionMode::ReFungible  => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1230                CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1231                _ => fail!(Error::<T>::UnexpectedCollectionType)1232            };12331234            Ok(())1235        }1236 1237        /// Set schema standard1238        /// ImageURL1239        /// Unique1240        /// 1241        /// # Permissions1242        /// 1243        /// * Collection Owner1244        /// * Collection Admin1245        /// 1246        /// # Arguments1247        /// 1248        /// * collection_id.1249        /// 1250        /// * schema: SchemaVersion: enum1251        #[weight = <T as Config>::WeightInfo::set_schema_version()]1252        pub fn set_schema_version(1253            origin,1254            collection_id: CollectionId,1255            version: SchemaVersion1256        ) -> DispatchResult {1257            let sender = ensure_signed(origin)?;1258            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1259            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();1260            target_collection.schema_version = version;1261            <Collection<T>>::insert(collection_id, target_collection);12621263            Ok(())1264        }12651266        /// Set off-chain data schema.1267        /// 1268        /// # Permissions1269        /// 1270        /// * Collection Owner1271        /// * Collection Admin1272        /// 1273        /// # Arguments1274        /// 1275        /// * collection_id.1276        /// 1277        /// * schema: String representing the offchain data schema.1278        #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1279        pub fn set_offchain_schema(1280            origin,1281            collection_id: CollectionId,1282            schema: Vec<u8>1283        ) -> DispatchResult {1284            let sender = ensure_signed(origin)?;1285            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12861287            // check schema limit1288            ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");12891290            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();1291            target_collection.offchain_schema = schema;1292            <Collection<T>>::insert(collection_id, target_collection);12931294            Ok(())1295        }12961297        /// Set const on-chain data schema.1298        /// 1299        /// # Permissions1300        /// 1301        /// * Collection Owner1302        /// * Collection Admin1303        /// 1304        /// # Arguments1305        /// 1306        /// * collection_id.1307        /// 1308        /// * schema: String representing the const on-chain data schema.1309        #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1310        pub fn set_const_on_chain_schema (1311            origin,1312            collection_id: CollectionId,1313            schema: Vec<u8>1314        ) -> DispatchResult {1315            let sender = ensure_signed(origin)?;1316            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13171318            // check schema limit1319            ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");13201321            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();1322            target_collection.const_on_chain_schema = schema;1323            <Collection<T>>::insert(collection_id, target_collection);13241325            Ok(())1326        }13271328        /// Set variable on-chain data schema.1329        /// 1330        /// # Permissions1331        /// 1332        /// * Collection Owner1333        /// * Collection Admin1334        /// 1335        /// # Arguments1336        /// 1337        /// * collection_id.1338        /// 1339        /// * schema: String representing the variable on-chain data schema.1340        #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1341        pub fn set_variable_on_chain_schema (1342            origin,1343            collection_id: CollectionId,1344            schema: Vec<u8>1345        ) -> DispatchResult {1346            let sender = ensure_signed(origin)?;1347            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13481349            // check schema limit1350            ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");13511352            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();1353            target_collection.variable_on_chain_schema = schema;1354            <Collection<T>>::insert(collection_id, target_collection);13551356            Ok(())1357        }13581359        // Sudo permissions function1360        #[weight = <T as Config>::WeightInfo::set_chain_limits()]1361        pub fn set_chain_limits(1362            origin,1363            limits: ChainLimits1364        ) -> DispatchResult {13651366            #[cfg(not(feature = "runtime-benchmarks"))]1367            ensure_root(origin)?;13681369            <ChainLimit>::put(limits);1370            Ok(())1371        }13721373        /// Enable smart contract self-sponsoring.1374        /// 1375        /// # Permissions1376        /// 1377        /// * Contract Owner1378        /// 1379        /// # Arguments1380        /// 1381        /// * contract address1382        /// * enable flag1383        /// 1384        #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1385        pub fn enable_contract_sponsoring(1386            origin,1387            contract_address: T::AccountId,1388            enable: bool1389        ) -> DispatchResult {13901391            let sender = ensure_signed(origin)?;13921393            #[cfg(feature = "runtime-benchmarks")]1394            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13951396            Self::ensure_contract_owned(sender, &contract_address)?;13971398            <ContractSelfSponsoring<T>>::insert(contract_address, enable);1399            Ok(())1400        }14011402        /// Set the rate limit for contract sponsoring to specified number of blocks.1403        /// 1404        /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1405        /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1406        /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1407        /// from contract endowment if there are at least B blocks between such transactions. 1408        /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1409        /// 1410        /// # Permissions1411        /// 1412        /// * Contract Owner1413        /// 1414        /// # Arguments1415        /// 1416        /// -`contract_address`: Address of the contract to sponsor1417        /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1418        /// 1419        #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1420        pub fn set_contract_sponsoring_rate_limit(1421            origin,1422            contract_address: T::AccountId,1423            rate_limit: T::BlockNumber1424        ) -> DispatchResult {1425            let sender = ensure_signed(origin)?;14261427            #[cfg(feature = "runtime-benchmarks")]1428            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14291430            Self::ensure_contract_owned(sender, &contract_address)?;1431            <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1432            Ok(())1433        }14341435        /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1436        /// 1437        /// # Permissions1438        /// 1439        /// * Address that deployed smart contract.1440        /// 1441        /// # Arguments1442        /// 1443        /// -`contract_address`: Address of the contract.1444        /// 1445        /// - `enable`: .  1446        #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1447        pub fn toggle_contract_white_list(1448            origin,1449            contract_address: T::AccountId,1450            enable: bool1451        ) -> DispatchResult {1452            let sender = ensure_signed(origin)?;14531454            #[cfg(feature = "runtime-benchmarks")]1455            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14561457            Self::ensure_contract_owned(sender, &contract_address)?;1458            <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1459            Ok(())1460        }1461        1462        /// Add an address to smart contract white list.1463        /// 1464        /// # Permissions1465        /// 1466        /// * Address that deployed smart contract.1467        /// 1468        /// # Arguments1469        /// 1470        /// -`contract_address`: Address of the contract.1471        ///1472        /// -`account_address`: Address to add.1473        #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1474        pub fn add_to_contract_white_list(1475            origin,1476            contract_address: T::AccountId,1477            account_address: T::AccountId1478        ) -> DispatchResult {1479            let sender = ensure_signed(origin)?;14801481            #[cfg(feature = "runtime-benchmarks")]1482            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1483            1484            Self::ensure_contract_owned(sender, &contract_address)?;      1485            <ContractWhiteList<T>>::insert(contract_address, account_address, true);1486            Ok(())1487        }14881489        /// Remove an address from smart contract white list.1490        /// 1491        /// # Permissions1492        /// 1493        /// * Address that deployed smart contract.1494        /// 1495        /// # Arguments1496        /// 1497        /// -`contract_address`: Address of the contract.1498        ///1499        /// -`account_address`: Address to remove.1500        #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1501        pub fn remove_from_contract_white_list(1502            origin,1503            contract_address: T::AccountId,1504            account_address: T::AccountId1505        ) -> DispatchResult {1506            let sender = ensure_signed(origin)?;15071508            #[cfg(feature = "runtime-benchmarks")]1509            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15101511            Self::ensure_contract_owned(sender, &contract_address)?;1512            <ContractWhiteList<T>>::remove(contract_address, account_address);1513            Ok(())1514        }15151516        #[weight = <T as Config>::WeightInfo::set_collection_limits()]1517        pub fn set_collection_limits(1518            origin,1519            collection_id: u32,1520            limits: CollectionLimits<T::BlockNumber>,1521        ) -> DispatchResult {1522            let sender = ensure_signed(origin)?;1523            Self::check_owner_permissions(collection_id, sender.clone())?;1524            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();1525            let chain_limits = ChainLimit::get();1526            let climits = target_collection.limits;15271528            // collection bounds1529            ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1530                limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP,  1531                Error::<T>::CollectionLimitBoundsExceeded);15321533            // token_limit   check  prev1534            ensure!(climits.token_limit > limits.token_limit && 1535                limits.token_limit <= chain_limits.account_token_ownership_limit, 1536                Error::<T>::AccountTokenLimitExceeded);15371538            target_collection.limits = limits;1539            <Collection<T>>::insert(collection_id, target_collection);15401541            Ok(())1542        } 1543    }1544}15451546impl<T: Config> Module<T> {15471548    pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {15491550        let target_collection = <Collection<T>>::get(collection_id).unwrap();15511552        // Limits check1553        Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;15541555        // Transfer permissions check1556        ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1557            Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1558            Error::<T>::NoPermission);15591560        if target_collection.access == AccessMode::WhiteList {1561            Self::check_white_list(collection_id, &sender)?;1562            Self::check_white_list(collection_id, &recipient)?;1563        }15641565        match target_collection.mode1566        {1567            CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1568            CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1569            CollectionMode::ReFungible  => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1570            _ => ()1571        };15721573        Ok(())1574    }157515761577    fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T>, recipient: &T::AccountId) -> DispatchResult {15781579        // check token limit and account token limit1580        let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1581        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);1582        1583        Ok(())1584    }15851586    fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15871588        // check token limit and account token limit1589        let total_items: u32 = ItemListIndex::get(collection_id);1590        let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1591        ensure!(collection.limits.token_limit > total_items,  Error::<T>::CollectionTokenLimitExceeded);1592        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);15931594        if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1595            ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1596            Self::check_white_list(collection_id, owner)?;1597            Self::check_white_list(collection_id, sender)?;1598        }15991600        Ok(())1601    }16021603    fn validate_create_item_args(target_collection: &CollectionType<T>, data: &CreateItemData) -> DispatchResult {1604        match target_collection.mode1605        {1606            CollectionMode::NFT => {1607                if let CreateItemData::NFT(data) = data {1608                    // check sizes1609                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1610                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1611                } else {1612                    fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1613                }1614            },1615            CollectionMode::Fungible(_) => {1616                if let CreateItemData::Fungible(_) = data {1617                } else {1618                    fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1619                }1620            },1621            CollectionMode::ReFungible => {1622                if let CreateItemData::ReFungible(data) = data {16231624                    // check sizes1625                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1626                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);16271628                    // Check refungibility limits1629                    ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1630                    ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1631                } else {1632                    fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1633                }1634            },1635            _ => { fail!(Error::<T>::UnexpectedCollectionType); }1636        };16371638        Ok(())1639    }16401641    fn create_item_no_validation(collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1642        match data1643        {1644            CreateItemData::NFT(data) => {1645                let item = NftItemType {1646                    owner,1647                    const_data: data.const_data,1648                    variable_data: data.variable_data1649                };16501651                Self::add_nft_item(collection_id, item)?;1652            },1653            CreateItemData::Fungible(data) => {1654                Self::add_fungible_item(collection_id, &owner, data.value)?;1655            },1656            CreateItemData::ReFungible(data) => {1657                let mut owner_list = Vec::new();1658                owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});16591660                let item = ReFungibleItemType {1661                    owner: owner_list,1662                    const_data: data.const_data,1663                    variable_data: data.variable_data1664                };16651666                Self::add_refungible_item(collection_id, item)?;1667            }1668        };16691670        // call event1671        Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16721673        Ok(())1674    }16751676    fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {16771678        // Does new owner already have an account?1679        let mut balance: u128 = 0;1680        if <FungibleItemList<T>>::contains_key(collection_id, owner) {1681            balance = <FungibleItemList<T>>::get(collection_id, owner).value;1682        } 16831684        // Mint 1685        let item = FungibleItemType {1686            value: balance + value1687        };1688        <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16891690        // Update balance1691        let new_balance = <Balance<T>>::get(collection_id, owner)1692            .checked_add(value)1693            .ok_or(Error::<T>::NumOverflow)?;1694        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16951696        Ok(())1697    }16981699    fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1700        let current_index = <ItemListIndex>::get(collection_id)1701            .checked_add(1)1702            .ok_or(Error::<T>::NumOverflow)?;1703        let itemcopy = item.clone();17041705        let value = item.owner.first().unwrap().fraction;1706        let owner = item.owner.first().unwrap().owner.clone();17071708        Self::add_token_index(collection_id, current_index, &owner)?;17091710        <ItemListIndex>::insert(collection_id, current_index);1711        <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17121713        // Update balance1714        let new_balance = <Balance<T>>::get(collection_id, &owner)1715            .checked_add(value)1716            .ok_or(Error::<T>::NumOverflow)?;1717        <Balance<T>>::insert(collection_id, owner.clone(), new_balance);17181719        Ok(())1720    }17211722    fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1723        let current_index = <ItemListIndex>::get(collection_id)1724            .checked_add(1)1725            .ok_or(Error::<T>::NumOverflow)?;17261727        let item_owner = item.owner.clone();1728        Self::add_token_index(collection_id, current_index, &item.owner)?;17291730        <ItemListIndex>::insert(collection_id, current_index);1731        <NftItemList<T>>::insert(collection_id, current_index, item);17321733        // Update balance1734        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1735            .checked_add(1)1736            .ok_or(Error::<T>::NumOverflow)?;1737        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);17381739        Ok(())1740    }17411742    fn burn_refungible_item(1743        collection_id: CollectionId,1744        item_id: TokenId,1745        owner: &T::AccountId,1746    ) -> DispatchResult {1747        ensure!(1748            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1749            Error::<T>::TokenNotFound1750        );1751        let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id);1752        let rft_balance = token1753            .owner1754            .iter()1755            .filter(|&i| i.owner == *owner)1756            .next()1757            .unwrap();1758        Self::remove_token_index(collection_id, item_id, owner)?;17591760        // update balance1761        let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.clone())1762            .checked_sub(rft_balance.fraction)1763            .ok_or(Error::<T>::NumOverflow)?;1764        <Balance<T>>::insert(collection_id, rft_balance.owner.clone(), new_balance);17651766        // Re-create owners list with sender removed1767        let index = token1768            .owner1769            .iter()1770            .position(|i| i.owner == *owner)1771            .unwrap();1772        token.owner.remove(index);1773        let owner_count = token.owner.len();17741775        // Burn the token completely if this was the last (only) owner1776        if owner_count == 0 {1777            <ReFungibleItemList<T>>::remove(collection_id, item_id);1778            <VariableMetaDataBasket<T>>::remove(collection_id, item_id);1779        }1780        else {1781            <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1782        }17831784        Ok(())1785    }17861787    fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1788        ensure!(1789            <NftItemList<T>>::contains_key(collection_id, item_id),1790            Error::<T>::TokenNotFound1791        );1792        let item = <NftItemList<T>>::get(collection_id, item_id);1793        Self::remove_token_index(collection_id, item_id, &item.owner)?;17941795        // update balance1796        let new_balance = <Balance<T>>::get(collection_id, &item.owner)1797            .checked_sub(1)1798            .ok_or(Error::<T>::NumOverflow)?;1799        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1800        <NftItemList<T>>::remove(collection_id, item_id);1801        <VariableMetaDataBasket<T>>::remove(collection_id, item_id);18021803        Ok(())1804    }18051806    fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1807        ensure!(1808            <FungibleItemList<T>>::contains_key(collection_id, owner),1809            Error::<T>::TokenNotFound1810        );1811        let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1812        ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18131814        // update balance1815        let new_balance = <Balance<T>>::get(collection_id, owner)1816            .checked_sub(value)1817            .ok_or(Error::<T>::NumOverflow)?;1818        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);18191820        if balance.value - value > 0 {1821            balance.value -= value;1822            <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1823        }1824        else {1825            <FungibleItemList<T>>::remove(collection_id, owner);1826        }18271828        Ok(())1829    }18301831    fn collection_exists(collection_id: CollectionId) -> DispatchResult {1832        ensure!(1833            <Collection<T>>::contains_key(collection_id),1834            Error::<T>::CollectionNotFound1835        );1836        Ok(())1837    }18381839    fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1840        Self::collection_exists(collection_id)?;18411842        let target_collection = <Collection<T>>::get(collection_id).unwrap();1843        ensure!(1844            subject == target_collection.owner,1845            Error::<T>::NoPermission1846        );18471848        Ok(())1849    }18501851    fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1852        let target_collection = <Collection<T>>::get(collection_id).unwrap();1853        let mut result: bool = subject == target_collection.owner;1854        let exists = <AdminList<T>>::contains_key(collection_id);18551856        if !result & exists {1857            if <AdminList<T>>::get(collection_id).contains(&subject) {1858                result = true1859            }1860        }18611862        result1863    }18641865    fn check_owner_or_admin_permissions(1866        collection_id: CollectionId,1867        subject: T::AccountId,1868    ) -> DispatchResult {1869        Self::collection_exists(collection_id)?;1870        let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18711872        ensure!(1873            result,1874            Error::<T>::NoPermission1875        );1876        Ok(())1877    }18781879    fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1880        let target_collection = <Collection<T>>::get(collection_id).unwrap();18811882        match target_collection.mode {1883            CollectionMode::NFT => {1884                <NftItemList<T>>::get(collection_id, item_id).owner == subject1885            }1886            CollectionMode::Fungible(_) => {1887                <FungibleItemList<T>>::contains_key(collection_id, &subject)1888            }1889            CollectionMode::ReFungible => {1890                <ReFungibleItemList<T>>::get(collection_id, item_id)1891                    .owner1892                    .iter()1893                    .any(|i| i.owner == subject)1894            }1895            CollectionMode::Invalid => false,1896        }1897    }18981899    fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1900        let mes = Error::<T>::AddresNotInWhiteList;1901        ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);19021903        Ok(())1904    }19051906    /// Check if token exists. In case of Fungible, check if there is an entry for 1907    /// the owner in fungible balances double map1908    fn token_exists(1909        collection_id: CollectionId,1910        item_id: TokenId,1911        owner: &T::AccountId1912    ) -> DispatchResult {1913        let target_collection = <Collection<T>>::get(collection_id).unwrap();1914        let exists = match target_collection.mode1915        {1916            CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1917            CollectionMode::Fungible(_)  => <FungibleItemList<T>>::contains_key(collection_id, owner),1918            CollectionMode::ReFungible  => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1919            _ => false1920        };19211922        ensure!(exists == true, Error::<T>::TokenNotFound);1923        Ok(())1924    }19251926    fn transfer_fungible(1927        collection_id: CollectionId,1928        value: u128,1929        owner: &T::AccountId,1930        recipient: &T::AccountId,1931    ) -> DispatchResult {1932        Self::token_exists(collection_id, 0, owner)?;19331934        let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1935        ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);19361937        // Send balance to recipient (updates balanceOf of recipient)1938        Self::add_fungible_item(collection_id, recipient, value)?;19391940        // update balanceOf of sender1941        <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);19421943        // Reduce or remove sender1944        if balance.value == value {1945            <FungibleItemList<T>>::remove(collection_id, owner);1946        }1947        else {1948            balance.value -= value;1949            <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1950        }19511952        Ok(())1953    }19541955    fn transfer_refungible(1956        collection_id: CollectionId,1957        item_id: TokenId,1958        value: u128,1959        owner: T::AccountId,1960        new_owner: T::AccountId,1961    ) -> DispatchResult {1962        Self::token_exists(collection_id, item_id, &owner)?;19631964        let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1965        let item = full_item1966            .owner1967            .iter()1968            .filter(|i| i.owner == owner)1969            .next()1970            .ok_or(Error::<T>::NumOverflow)?;1971        let amount = item.fraction;19721973        ensure!(amount >= value, Error::<T>::TokenValueTooLow);19741975        // update balance1976        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1977            .checked_sub(value)1978            .ok_or(Error::<T>::NumOverflow)?;1979        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19801981        let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1982            .checked_add(value)1983            .ok_or(Error::<T>::NumOverflow)?;1984        <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19851986        let old_owner = item.owner.clone();1987        let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19881989        // transfer1990        if amount == value && !new_owner_has_account {1991            // change owner1992            // new owner do not have account1993            let mut new_full_item = full_item.clone();1994            new_full_item1995                .owner1996                .iter_mut()1997                .find(|i| i.owner == owner)1998                .unwrap()1999                .owner = new_owner.clone();2000            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);20012002            // update index collection2003            Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2004        } else {2005            let mut new_full_item = full_item.clone();2006            new_full_item2007                .owner2008                .iter_mut()2009                .find(|i| i.owner == owner)2010                .unwrap()2011                .fraction -= value;20122013            // separate amount2014            if new_owner_has_account {2015                // new owner has account2016                new_full_item2017                    .owner2018                    .iter_mut()2019                    .find(|i| i.owner == new_owner)2020                    .unwrap()2021                    .fraction += value;2022            } else {2023                // new owner do not have account2024                new_full_item.owner.push(Ownership {2025                    owner: new_owner.clone(),2026                    fraction: value,2027                });2028                Self::add_token_index(collection_id, item_id, &new_owner)?;2029            }20302031            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2032        }20332034        Ok(())2035    }20362037    fn transfer_nft(2038        collection_id: CollectionId,2039        item_id: TokenId,2040        sender: T::AccountId,2041        new_owner: T::AccountId,2042    ) -> DispatchResult {2043        Self::token_exists(collection_id, item_id, &sender)?;20442045        let mut item = <NftItemList<T>>::get(collection_id, item_id);20462047        ensure!(2048            sender == item.owner,2049            Error::<T>::MustBeTokenOwner2050        );20512052        // update balance2053        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2054            .checked_sub(1)2055            .ok_or(Error::<T>::NumOverflow)?;2056        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20572058        let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2059            .checked_add(1)2060            .ok_or(Error::<T>::NumOverflow)?;2061        <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);20622063        // change owner2064        let old_owner = item.owner.clone();2065        item.owner = new_owner.clone();2066        <NftItemList<T>>::insert(collection_id, item_id, item);20672068        // update index collection2069        Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;20702071        Ok(())2072    }2073    2074    fn set_re_fungible_variable_data(2075        collection_id: CollectionId,2076        item_id: TokenId,2077        data: Vec<u8>2078    ) -> DispatchResult {2079        let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20802081        item.variable_data = data;20822083        <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20842085        Ok(())2086    }20872088    fn set_nft_variable_data(2089        collection_id: CollectionId,2090        item_id: TokenId,2091        data: Vec<u8>2092    ) -> DispatchResult {2093        let mut item = <NftItemList<T>>::get(collection_id, item_id);2094        2095        item.variable_data = data;20962097        <NftItemList<T>>::insert(collection_id, item_id, item);2098        2099        Ok(())2100    }21012102    fn init_collection(item: &CollectionType<T>) {2103        // check params2104        assert!(2105            item.decimal_points <= MAX_DECIMAL_POINTS,2106            "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2107        );2108        assert!(2109            item.name.len() <= 64,2110            "Collection name can not be longer than 63 char"2111        );2112        assert!(2113            item.name.len() <= 256,2114            "Collection description can not be longer than 255 char"2115        );2116        assert!(2117            item.token_prefix.len() <= 16,2118            "Token prefix can not be longer than 15 char"2119        );21202121        // Generate next collection ID2122        let next_id = CreatedCollectionCount::get()2123            .checked_add(1)2124            .unwrap();21252126        CreatedCollectionCount::put(next_id);2127    }21282129    fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2130        let current_index = <ItemListIndex>::get(collection_id)2131            .checked_add(1)2132            .unwrap();21332134        let item_owner = item.owner.clone();2135        Self::add_token_index(collection_id, current_index, &item.owner).unwrap();21362137        <ItemListIndex>::insert(collection_id, current_index);21382139        // Update balance2140        let new_balance = <Balance<T>>::get(collection_id, &item_owner)2141            .checked_add(1)2142            .unwrap();2143        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2144    }21452146    fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2147        let current_index = <ItemListIndex>::get(collection_id)2148            .checked_add(1)2149            .unwrap();21502151        Self::add_token_index(collection_id, current_index, owner).unwrap();21522153        <ItemListIndex>::insert(collection_id, current_index);21542155        // Update balance2156        let new_balance = <Balance<T>>::get(collection_id, owner)2157            .checked_add(item.value)2158            .unwrap();2159        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2160    }21612162    fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2163        let current_index = <ItemListIndex>::get(collection_id)2164            .checked_add(1)2165            .unwrap();21662167        let value = item.owner.first().unwrap().fraction;2168        let owner = item.owner.first().unwrap().owner.clone();21692170        Self::add_token_index(collection_id, current_index, &owner).unwrap();21712172        <ItemListIndex>::insert(collection_id, current_index);21732174        // Update balance2175        let new_balance = <Balance<T>>::get(collection_id, &owner)2176            .checked_add(value)2177            .unwrap();2178        <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2179    }21802181    fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult {21822183        // add to account limit2184        if <AccountItemCount<T>>::contains_key(owner) {21852186            // bound Owned tokens by a single address2187            let count = <AccountItemCount<T>>::get(owner);2188            ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21892190            <AccountItemCount<T>>::insert(owner.clone(), count2191                .checked_add(1)2192                .ok_or(Error::<T>::NumOverflow)?);2193        }2194        else {2195            <AccountItemCount<T>>::insert(owner.clone(), 1);2196        }21972198        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2199        if list_exists {2200            let mut list = <AddressTokens<T>>::get(collection_id, owner);2201            let item_contains = list.contains(&item_index.clone());22022203            if !item_contains {2204                list.push(item_index.clone());2205            }22062207            <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2208        } else {2209            let mut itm = Vec::new();2210            itm.push(item_index.clone());2211            <AddressTokens<T>>::insert(collection_id, owner.clone(), itm);2212        }22132214        Ok(())2215    }22162217    fn remove_token_index(2218        collection_id: CollectionId,2219        item_index: TokenId,2220        owner: &T::AccountId,2221    ) -> DispatchResult {22222223        // update counter2224        <AccountItemCount<T>>::insert(owner.clone(), 2225            <AccountItemCount<T>>::get(owner)2226            .checked_sub(1)2227            .ok_or(Error::<T>::NumOverflow)?);222822292230        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2231        if list_exists {2232            let mut list = <AddressTokens<T>>::get(collection_id, owner);2233            let item_contains = list.contains(&item_index.clone());22342235            if item_contains {2236                list.retain(|&item| item != item_index);2237                <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2238            }2239        }22402241        Ok(())2242    }22432244    fn move_token_index(2245        collection_id: CollectionId,2246        item_index: TokenId,2247        old_owner: &T::AccountId,2248        new_owner: &T::AccountId,2249    ) -> DispatchResult {2250        Self::remove_token_index(collection_id, item_index, old_owner)?;2251        Self::add_token_index(collection_id, item_index, new_owner)?;22522253        Ok(())2254    }2255    2256    fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2257        if <ContractOwner<T>>::contains_key(contract.clone()) {2258            let owner = <ContractOwner<T>>::get(contract);2259            ensure!(account == owner, Error::<T>::NoPermission);2260        } else {2261            fail!(Error::<T>::NoPermission);2262        }22632264        Ok(())2265    }2266}22672268////////////////////////////////////////////////////////////////////////////////////////////////////2269// Economic models2270// #region22712272/// Fee multiplier.2273pub type Multiplier = FixedU128;22742275type BalanceOf<T> = <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::Balance;22762277/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2278/// in the queue.2279#[derive(Encode, Decode, Clone, Eq, PartialEq)]2280pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);22812282impl<T: Config + Send + Sync> sp_std::fmt::Debug 2283    for ChargeTransactionPayment<T>2284{2285	#[cfg(feature = "std")]2286	fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2287		write!(f, "ChargeTransactionPayment<{:?}>", self.0)2288	}2289	#[cfg(not(feature = "std"))]2290	fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2291		Ok(())2292	}2293}22942295impl<T: Config> ChargeTransactionPayment<T>2296where2297    T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2298    BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2299    T::AccountId: AsRef<[u8]>,2300    T::AccountId: UncheckedFrom<T::Hash>,2301{2302    fn traditional_fee(2303        len: usize,2304        info: &DispatchInfoOf<T::Call>,2305        tip: BalanceOf<T>,2306    ) -> BalanceOf<T>2307    where2308        T::Call: Dispatchable<Info = DispatchInfo>,2309    {2310        <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2311    }23122313	fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2314        let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2315        let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2316        let len_saturation = max_block_length as u64 / (len as u64).max(1);2317        let coefficient: BalanceOf<T> = weight_saturation2318            .min(len_saturation)2319            .saturated_into::<BalanceOf<T>>();2320        final_fee2321            .saturating_mul(coefficient)2322            .saturated_into::<TransactionPriority>()2323    }23242325    fn withdraw_fee(2326        &self,2327        who: &T::AccountId,2328        call: &T::Call,2329        info: &DispatchInfoOf<T::Call>,2330        len: usize,2331	) -> Result<2332		(2333			BalanceOf<T>,2334			<<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2335		),2336		TransactionValidityError,2337	> {2338        let tip = self.0;23392340        // Set fee based on call type. Creating collection costs 1 Unique.2341        // All other transactions have traditional fees so far2342        // let fee = match call.is_sub_type() {2343        //     Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2344        //     _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2345        //                                                 // _ => <BalanceOf<T>>::from(100)2346        // };2347        let fee = Self::traditional_fee(len, info, tip);23482349        // Only mess with balances if fee is not zero.2350        if fee.is_zero() {2351            return <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2352			.map(|i| (fee, i));2353        }23542355        // Determine who is paying transaction fee based on ecnomic model2356        // Parse call to extract collection ID and access collection sponsor2357        let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2358            Some(Call::create_item(collection_id, _owner, _properties)) => {2359                let collection = <Collection<T>>::get(collection_id).unwrap();23602361                // sponsor timeout2362                let block_number = <system::Module<T>>::block_number() as T::BlockNumber;23632364                let limit = collection.limits.sponsor_transfer_timeout;2365                let mut sponsored = true;2366                if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2367                    let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2368                    let limit_time = last_tx_block + limit.into();2369                    if block_number <= limit_time {2370                        sponsored = false;2371                    }2372                }2373                if sponsored {2374                    <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);2375                }23762377                // check free create limit2378                if (collection.limits.sponsored_data_size >= (_properties.len() as u32)) &&2379                   (collection.sponsor_confirmed) &&2380                   (sponsored)2381                {2382                    collection.sponsor2383                } else {2384                    T::AccountId::default()2385                }2386            }2387            Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2388                let collection = <Collection<T>>::get(collection_id).unwrap();2389                2390                let mut sponsor_transfer = false;2391                if collection.sponsor_confirmed {23922393                    let collection_limits = collection.limits;2394                    let collection_mode = collection.mode;2395    2396                    // sponsor timeout2397                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2398                    sponsor_transfer = match collection_mode {2399                        CollectionMode::NFT => {2400    2401                            // get correct limit2402                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2403                                collection_limits.sponsor_transfer_timeout2404                            } else {2405                                ChainLimit::get().nft_sponsor_transfer_timeout2406                            };2407    2408                            let mut sponsored = true;2409                            if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2410                                let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2411                                let limit_time = last_tx_block + limit.into();2412                                if block_number <= limit_time {2413                                    sponsored = false;2414                                }2415                            }2416                            if sponsored {2417                                <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2418                            }24192420                            sponsored2421                        }2422                        CollectionMode::Fungible(_) => {2423    2424                            // get correct limit2425                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2426                                collection_limits.sponsor_transfer_timeout2427                            } else {2428                                ChainLimit::get().fungible_sponsor_transfer_timeout2429                            };2430    2431                            let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2432                            let mut sponsored = true;2433                            if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2434                                let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2435                                let limit_time = last_tx_block + limit.into();2436                                if block_number <= limit_time {2437                                    sponsored = false;2438                                }2439                            }2440                            if sponsored {2441                                <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2442                            }24432444                            sponsored2445                        }2446                        CollectionMode::ReFungible => {2447    2448                            // get correct limit2449                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2450                                collection_limits.sponsor_transfer_timeout2451                            } else {2452                                ChainLimit::get().refungible_sponsor_transfer_timeout2453                            };2454    2455                            let mut sponsored = true;2456                            if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2457                                let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2458                                let limit_time = last_tx_block + limit.into();2459                                if block_number <= limit_time {2460                                    sponsored = false;2461                                }2462                            }2463                            if sponsored {2464                                <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2465                            }24662467                            sponsored2468                        }2469                        _ => {2470                            false2471                        },2472                    };2473                }24742475                if !sponsor_transfer {2476                    T::AccountId::default()2477                } else {2478                    collection.sponsor2479                }2480            }24812482            Some(Call::set_variable_meta_data(collection_id, item_id, data)) => {2483                let mut sponsor_metadata_changes = false;24842485                let collection = <Collection<T>>::get(collection_id).unwrap();24862487                if2488                    collection.sponsor_confirmed &&2489                    // Can't sponsor fungible collection, this tx will be rejected2490                    // as invalid2491                    !matches!(collection.mode, CollectionMode::Fungible(_)) &&2492                    data.len() <= collection.limits.sponsored_data_size as usize2493                {2494                    if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {2495                        let block_number = <system::Module<T>>::block_number() as T::BlockNumber;24962497                        if <VariableMetaDataBasket<T>>::get(collection_id, item_id)2498                            .map(|last_block| block_number - last_block > rate_limit)2499                            .unwrap_or(true) 2500                        {2501                            sponsor_metadata_changes = true;2502                            <VariableMetaDataBasket<T>>::insert(collection_id, item_id, block_number);2503                        }2504                    }2505                }25062507                if !sponsor_metadata_changes {2508                    T::AccountId::default()2509                } else {2510                    collection.sponsor2511                }2512            }25132514            _ => T::AccountId::default(),2515        };25162517        // Sponsor smart contracts2518        sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {25192520            // On instantiation: set the contract owner2521            Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {25222523                let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2524                    &who,2525                    code_hash,2526                    salt,2527                );2528                <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());25292530                T::AccountId::default()2531            },25322533            // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2534            Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {25352536                let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());25372538                let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2539                  && <ContractOwner<T>>::get(called_contract.clone()) == *who;2540                let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2541                  2542                if !owned_contract && white_list_enabled {2543                    if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2544                        return Err(InvalidTransaction::Call.into());2545                    }2546                }25472548                let mut sponsor_transfer = false;2549                if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2550                    let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2551                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2552                    let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2553                    let limit_time = last_tx_block + rate_limit;25542555                    if block_number >= limit_time {2556                        <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2557                        sponsor_transfer = true;2558                    }2559                } else {2560                    sponsor_transfer = false;2561                }2562               2563                2564                let mut sp = T::AccountId::default();2565                if sponsor_transfer {2566                    if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2567                        if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2568                            sp = called_contract;2569                        }2570                    }2571                }25722573                sp2574            },25752576            _ => sponsor,2577        };25782579        let mut who_pays_fee: T::AccountId = sponsor.clone();2580        if sponsor == T::AccountId::default() {2581            who_pays_fee = who.clone();2582        }25832584		<<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)2585			.map(|i| (fee, i))2586    }2587}258825892590impl<T: Config + Send + Sync> SignedExtension2591    for ChargeTransactionPayment<T>2592where2593    BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2594    T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2595    T::AccountId: AsRef<[u8]>,2596    T::AccountId: UncheckedFrom<T::Hash>,2597{2598    const IDENTIFIER: &'static str = "ChargeTransactionPayment";2599    type AccountId = T::AccountId;2600    type Call = T::Call;2601    type AdditionalSigned = ();2602    type Pre = (2603        // tip2604        BalanceOf<T>,2605        // who pays fee2606        Self::AccountId,2607		// imbalance resulting from withdrawing the fee2608		<<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2609    );2610    fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2611        Ok(())2612    }26132614    fn validate(2615        &self,2616        who: &Self::AccountId,2617        call: &Self::Call,2618        info: &DispatchInfoOf<Self::Call>,2619        len: usize,2620    ) -> TransactionValidity {2621		let (fee, _) = self.withdraw_fee(who, call, info, len)?;2622		Ok(ValidTransaction {2623			priority: Self::get_priority(len, info, fee),2624			..Default::default()2625		})2626    }26272628    fn pre_dispatch(2629        self,2630        who: &Self::AccountId,2631        call: &Self::Call,2632        info: &DispatchInfoOf<Self::Call>,2633        len: usize,2634    ) -> Result<Self::Pre, TransactionValidityError> {2635        let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2636        Ok((self.0, who.clone(), imbalance))2637    }26382639    fn post_dispatch(2640        pre: Self::Pre,2641        info: &DispatchInfoOf<Self::Call>,2642        post_info: &PostDispatchInfoOf<Self::Call>,2643        len: usize,2644        _result: &DispatchResult,2645    ) -> Result<(), TransactionValidityError> {2646		let (tip, who, imbalance) = pre;2647		let actual_fee = transaction_payment::Module::<T>::compute_actual_fee(2648			len as u32,2649			info,2650			post_info,2651			tip,2652		);2653		<T as transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;2654		Ok(())2655    }2656}26572658// #endregion
after · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18    construct_runtime, decl_event, decl_module, decl_storage, decl_error,19    dispatch::DispatchResult,20    ensure, fail, parameter_types,21    traits::{22        Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23        Randomness, IsSubType,24    },25    weights::{26        constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27        DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28        WeightToFeePolynomial, DispatchClass,29    },30    StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36    traits::{37        DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38    },39    transaction_validity::{40        TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41    },42    FixedPointOperand, FixedU128,43};44use sp_runtime::traits::StaticLookup;45use pallet_contracts::chain_extension::UncheckedFrom;46use transaction_payment::OnChargeTransaction;4748#[cfg(test)]49mod mock;5051#[cfg(test)]52mod tests;5354mod default_weights;5556pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;57pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;58pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;59pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;6061// Structs62// #region6364pub type CollectionId = u32;65pub type TokenId = u32;66pub type DecimalPoints = u8;6768#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]69#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]70pub enum CollectionMode {71    Invalid,72    NFT,73    // decimal points74    Fungible(DecimalPoints),75    ReFungible,76}7778impl Default for CollectionMode {79    fn default() -> Self {80        Self::Invalid81    }82}8384impl Into<u8> for CollectionMode {85    fn into(self) -> u8 {86        match self {87            CollectionMode::Invalid => 0,88            CollectionMode::NFT => 1,89            CollectionMode::Fungible(_) => 2,90            CollectionMode::ReFungible => 3,91        }92    }93}9495#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]96#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]97pub enum AccessMode {98    Normal,99    WhiteList,100}101impl Default for AccessMode {102    fn default() -> Self {103        Self::Normal104    }105}106107#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]108#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]109pub enum SchemaVersion {110    ImageURL,111    Unique,112}113impl Default for SchemaVersion {114    fn default() -> Self {115        Self::ImageURL116    }117}118119#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]120#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]121pub struct Ownership<AccountId> {122    pub owner: AccountId,123    pub fraction: u128,124}125126#[derive(Encode, Decode, Clone, PartialEq)]127#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]128pub struct CollectionType<T: Config> {129    pub owner: T::AccountId,130    pub mode: CollectionMode,131    pub access: AccessMode,132    pub decimal_points: DecimalPoints,133    pub name: Vec<u16>,        // 64 include null escape char134    pub description: Vec<u16>, // 256 include null escape char135    pub token_prefix: Vec<u8>, // 16 include null escape char136    pub mint_mode: bool,137    pub offchain_schema: Vec<u8>,138    pub schema_version: SchemaVersion,139    pub sponsor: T::AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender140    pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.141    pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions 142    pub variable_on_chain_schema: Vec<u8>, //143    pub const_on_chain_schema: Vec<u8>, //144}145146#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]147#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]148pub struct NftItemType<AccountId> {149    pub owner: AccountId,150    pub const_data: Vec<u8>,151    pub variable_data: Vec<u8>,152}153154#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]155#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]156pub struct FungibleItemType {157    pub value: u128,158}159160#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]161#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]162pub struct ReFungibleItemType<AccountId> {163    pub owner: Vec<Ownership<AccountId>>,164    pub const_data: Vec<u8>,165    pub variable_data: Vec<u8>,166}167168// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]169// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]170// pub struct VestingItem<AccountId, Moment> {171//     pub sender: AccountId,172//     pub recipient: AccountId,173//     pub collection_id: CollectionId,174//     pub item_id: TokenId,175//     pub amount: u64,176//     pub vesting_date: Moment,177// }178179#[derive(Encode, Decode, Debug, Clone, PartialEq)]180#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]181pub struct CollectionLimits<BlockNumber: Encode + Decode> {182    pub account_token_ownership_limit: u32,183    pub sponsored_data_size: u32,184    /// None - setVariableMetadata is not sponsored185    /// Some(v) - setVariableMetadata is sponsored 186    ///           if there is v block between txs187    pub sponsored_data_rate_limit: Option<BlockNumber>,188    pub token_limit: u32,189190    // Timeouts for item types in passed blocks191    pub sponsor_transfer_timeout: u32,192    pub owner_can_transfer: bool,193    pub owner_can_destroy: bool,194}195196impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {197    fn default() -> Self {198        Self { 199            account_token_ownership_limit: 10_000_000, 200            token_limit: u32::max_value(),201            sponsored_data_size: u32::MAX, 202            sponsored_data_rate_limit: None,203            sponsor_transfer_timeout: 14400,204            owner_can_transfer: true,205            owner_can_destroy: true206        }207    }208}209210#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]211#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]212pub struct ChainLimits {213    pub collection_numbers_limit: u32,214    pub account_token_ownership_limit: u32,215    pub collections_admins_limit: u64,216    pub custom_data_limit: u32,217218    // Timeouts for item types in passed blocks219    pub nft_sponsor_transfer_timeout: u32,220    pub fungible_sponsor_transfer_timeout: u32,221    pub refungible_sponsor_transfer_timeout: u32,222223    // Schema limits224    pub offchain_schema_limit: u32,225    pub variable_on_chain_schema_limit: u32,226    pub const_on_chain_schema_limit: u32,227}228229pub trait WeightInfo {230	fn create_collection() -> Weight;231	fn destroy_collection() -> Weight;232	fn add_to_white_list() -> Weight;233	fn remove_from_white_list() -> Weight;234    fn set_public_access_mode() -> Weight;235    fn set_mint_permission() -> Weight;236    fn change_collection_owner() -> Weight;237    fn add_collection_admin() -> Weight;238    fn remove_collection_admin() -> Weight;239    fn set_collection_sponsor() -> Weight;240    fn confirm_sponsorship() -> Weight;241    fn remove_collection_sponsor() -> Weight;242    fn create_item(s: usize) -> Weight;243    fn burn_item() -> Weight;244    fn transfer() -> Weight;245    fn approve() -> Weight;246    fn transfer_from() -> Weight;247    fn set_offchain_schema() -> Weight;248    fn set_const_on_chain_schema() -> Weight;249    fn set_variable_on_chain_schema() -> Weight;250    fn set_variable_meta_data() -> Weight;251    fn enable_contract_sponsoring() -> Weight;252    fn set_schema_version() -> Weight;253    fn set_chain_limits() -> Weight;254    fn set_contract_sponsoring_rate_limit() -> Weight;255    fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;256    fn toggle_contract_white_list() -> Weight;257    fn add_to_contract_white_list() -> Weight;258    fn remove_from_contract_white_list() -> Weight;259    fn set_collection_limits() -> Weight;260}261262#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]263#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]264pub struct CreateNftData {265    pub const_data: Vec<u8>,266    pub variable_data: Vec<u8>,267}268269#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]270#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]271pub struct CreateFungibleData {272    pub value: u128,273}274275#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]276#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]277pub struct CreateReFungibleData {278    pub const_data: Vec<u8>,279    pub variable_data: Vec<u8>,280    pub pieces: u128,281}282283#[derive(Encode, Decode, Debug, Clone, PartialEq)]284#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]285pub enum CreateItemData {286    NFT(CreateNftData),287    Fungible(CreateFungibleData),288    ReFungible(CreateReFungibleData),289}290291impl CreateItemData {292    pub fn len(&self) -> usize {293        let len = match self {294            CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),295            CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),296            _ => 0297        };298        299        return len;300    }301}302303impl From<CreateNftData> for CreateItemData {304    fn from(item: CreateNftData) -> Self {305        CreateItemData::NFT(item)306    }307}308309impl From<CreateReFungibleData> for CreateItemData {310    fn from(item: CreateReFungibleData) -> Self {311        CreateItemData::ReFungible(item)312    }313}314315impl From<CreateFungibleData> for CreateItemData {316    fn from(item: CreateFungibleData) -> Self {317        CreateItemData::Fungible(item)318    }319}320321322decl_error! {323	/// Error for non-fungible-token module.324	pub enum Error for Module<T: Config> {325        /// Total collections bound exceeded.326        TotalCollectionsLimitExceeded,327		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.328        CollectionDecimalPointLimitExceeded, 329        /// Collection name can not be longer than 63 char.330        CollectionNameLimitExceeded, 331        /// Collection description can not be longer than 255 char.332        CollectionDescriptionLimitExceeded, 333        /// Token prefix can not be longer than 15 char.334        CollectionTokenPrefixLimitExceeded,335        /// This collection does not exist.336        CollectionNotFound,337        /// Item not exists.338        TokenNotFound,339        /// Admin not found340        AdminNotFound,341        /// Arithmetic calculation overflow.342        NumOverflow,       343        /// Account already has admin role.344        AlreadyAdmin,  345        /// You do not own this collection.346        NoPermission,347        /// This address is not set as sponsor, use setCollectionSponsor first.348        ConfirmUnsetSponsorFail,349        /// Collection is not in mint mode.350        PublicMintingNotAllowed,351        /// Sender parameter and item owner must be equal.352        MustBeTokenOwner,353        /// Item balance not enough.354        TokenValueTooLow,355        /// Size of item is too large.356        NftSizeLimitExceeded,357        /// No approve found358        ApproveNotFound,359        /// Requested value more than approved.360        TokenValueNotEnough,361        /// Only approved addresses can call this method.362        ApproveRequired,363        /// Address is not in white list.364        AddresNotInWhiteList,365        /// Number of collection admins bound exceeded.366        CollectionAdminsLimitExceeded,367        /// Owned tokens by a single address bound exceeded.368        AddressOwnershipLimitExceeded,369        /// Length of items properties must be greater than 0.370        EmptyArgument,371        /// const_data exceeded data limit.372        TokenConstDataLimitExceeded,373        /// variable_data exceeded data limit.374        TokenVariableDataLimitExceeded,375        /// Not NFT item data used to mint in NFT collection.376        NotNftDataUsedToMintNftCollectionToken,377        /// Not Fungible item data used to mint in Fungible collection.378        NotFungibleDataUsedToMintFungibleCollectionToken,379        /// Not Re Fungible item data used to mint in Re Fungible collection.380        NotReFungibleDataUsedToMintReFungibleCollectionToken,381        /// Unexpected collection type.382        UnexpectedCollectionType,383        /// Can't store metadata in fungible tokens.384        CantStoreMetadataInFungibleTokens,385        /// Collection token limit exceeded386        CollectionTokenLimitExceeded,387        /// Account token limit exceeded per collection388        AccountTokenLimitExceeded,389        /// Collection limit bounds per collection exceeded390        CollectionLimitBoundsExceeded,391        /// Tried to enable permissions which are only permitted to be disabled392        OwnerPermissionsCantBeReverted,393        /// Schema data size limit bound exceeded394        SchemaDataLimitExceeded,395        /// Maximum refungibility exceeded396        WrongRefungiblePieces397	}398}399400pub trait Config: system::Config + Sized + transaction_payment::Config + pallet_contracts::Config {401    type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;402403    /// Weight information for extrinsics in this pallet.404	type WeightInfo: WeightInfo;405}406407#[cfg(feature = "runtime-benchmarks")]408mod benchmarking;409410// #endregion411412decl_storage! {413    trait Store for Module<T: Config> as Nft {414415        // Private members416        NextCollectionID: CollectionId;417        CreatedCollectionCount: u32;418        ChainVersion: u64;419        ItemListIndex: map hasher(identity) CollectionId => TokenId;420421        // Chain limits struct422        pub ChainLimit get(fn chain_limit) config(): ChainLimits;423424        // Bound counters425        CollectionCount: u32;426        pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;427428        // Basic collections429        pub Collection get(fn collection) config(): map hasher(identity) CollectionId => Option<CollectionType<T>> = None;430        pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;431        pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;432433        /// Balance owner per collection map434        pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;435436        /// second parameter: item id + owner account id + spender account id437        pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;438439        /// Item collections440        pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;441        pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;442        pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;443444        /// Index list445        pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;446447        /// Tokens transfer baskets448        pub CreateItemBasket get(fn create_item_basket): map hasher(twox_64_concat) (CollectionId, T::AccountId) => T::BlockNumber;449        pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;450        pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;451        pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;452453        /// Variable metadata sponsoring454        pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Option<T::BlockNumber> = None;455456        // Contract Sponsorship and Ownership457        pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;458        pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;459        pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;460        pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;461        pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 462        pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 463    }464    add_extra_genesis {465        build(|config: &GenesisConfig<T>| {466            // Modification of storage467            for (_num, _c) in &config.collection {468                <Module<T>>::init_collection(_c);469            }470471            for (_num, _c, _i) in &config.nft_item_id {472                <Module<T>>::init_nft_token(*_c, _i);473            }474475            for (collection_id, account_id, fungible_item) in &config.fungible_item_id {476                <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);477            }478479            for (_num, _c, _i) in &config.refungible_item_id {480                <Module<T>>::init_refungible_token(*_c, _i);481            }482        })483    }484}485486decl_event!(487    pub enum Event<T>488    where489        AccountId = <T as system::Config>::AccountId,490    {491        /// New collection was created492        /// 493        /// # Arguments494        /// 495        /// * collection_id: Globally unique identifier of newly created collection.496        /// 497        /// * mode: [CollectionMode] converted into u8.498        /// 499        /// * account_id: Collection owner.500        Created(CollectionId, u8, AccountId),501502        /// New item was created.503        /// 504        /// # Arguments505        /// 506        /// * collection_id: Id of the collection where item was created.507        /// 508        /// * item_id: Id of an item. Unique within the collection.509        ///510        /// * recipient: Owner of newly created item 511        ItemCreated(CollectionId, TokenId, AccountId),512513        /// Collection item was burned.514        /// 515        /// # Arguments516        /// 517        /// collection_id.518        /// 519        /// item_id: Identifier of burned NFT.520        ItemDestroyed(CollectionId, TokenId),521522        /// Item was transferred523        ///524        /// * collection_id: Id of collection to which item is belong525        ///526        /// * item_id: Id of an item527        ///528        /// * sender: Original owner of item529        ///530        /// * recipient: New owner of item531        ///532        /// * amount: Always 1 for NFT533        Transfer(CollectionId, TokenId, AccountId, AccountId, u128),534    }535);536537decl_module! {538    pub struct Module<T: Config> for enum Call 539    where 540        origin: T::Origin541    {542        fn deposit_event() = default;543        type Error = Error<T>;544545        fn on_initialize(now: T::BlockNumber) -> Weight {546547            if ChainVersion::get() < 2548            {549                let value = NextCollectionID::get();550                CreatedCollectionCount::put(value);551                ChainVersion::put(2);552            }553554            0555        }556557        /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.558        /// 559        /// # Permissions560        /// 561        /// * Anyone.562        /// 563        /// # Arguments564        /// 565        /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.566        /// 567        /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.568        /// 569        /// * token_prefix: UTF-8 string with token prefix.570        /// 571        /// * mode: [CollectionMode] collection type and type dependent data.572        // returns collection ID573        #[weight = <T as Config>::WeightInfo::create_collection()]574        pub fn create_collection(origin,575                                 collection_name: Vec<u16>,576                                 collection_description: Vec<u16>,577                                 token_prefix: Vec<u8>,578                                 mode: CollectionMode) -> DispatchResult {579580            // Anyone can create a collection581            let who = ensure_signed(origin)?;582583            let decimal_points = match mode {584                CollectionMode::Fungible(points) => points,585                _ => 0586            };587588            let chain_limit = ChainLimit::get();589590            // bound Total number of collections591            ensure!(CollectionCount::get() < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);592593            // check params594            ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);595            ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);596            ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);597            ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);598599            // Generate next collection ID600            let next_id = CreatedCollectionCount::get()601                .checked_add(1)602                .ok_or(Error::<T>::NumOverflow)?;603604            // bound counter605            let total = CollectionCount::get()606                .checked_add(1)607                .ok_or(Error::<T>::NumOverflow)?;608609            CreatedCollectionCount::put(next_id);610            CollectionCount::put(total);611612            let limits = CollectionLimits {613                sponsored_data_size: chain_limit.custom_data_limit,614                ..Default::default()615            };616617            // Create new collection618            let new_collection = CollectionType {619                owner: who.clone(),620                name: collection_name,621                mode: mode.clone(),622                mint_mode: false,623                access: AccessMode::Normal,624                description: collection_description,625                decimal_points: decimal_points,626                token_prefix: token_prefix,627                offchain_schema: Vec::new(),628                schema_version: SchemaVersion::ImageURL,629                sponsor: T::AccountId::default(),630                sponsor_confirmed: false,631                variable_on_chain_schema: Vec::new(),632                const_on_chain_schema: Vec::new(),633                limits,634            };635636            // Add new collection to map637            <Collection<T>>::insert(next_id, new_collection);638639            // call event640            Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));641642            Ok(())643        }644645        /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.646        /// 647        /// # Permissions648        /// 649        /// * Collection Owner.650        /// 651        /// # Arguments652        /// 653        /// * collection_id: collection to destroy.654        #[weight = <T as Config>::WeightInfo::destroy_collection()]655        pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {656657            let sender = ensure_signed(origin)?;658            Self::check_owner_permissions(collection_id, sender)?;659660            let target_collection = <Collection<T>>::get(collection_id);661            if !target_collection.limits.owner_can_destroy {662                fail!(Error::<T>::NoPermission);663            }664665            <AddressTokens<T>>::remove_prefix(collection_id);666            <Allowances<T>>::remove_prefix(collection_id);667            <Balance<T>>::remove_prefix(collection_id);668            <ItemListIndex>::remove(collection_id);669            <AdminList<T>>::remove(collection_id);670            <Collection<T>>::remove(collection_id);671            <WhiteList<T>>::remove_prefix(collection_id);672673            <NftItemList<T>>::remove_prefix(collection_id);674            <FungibleItemList<T>>::remove_prefix(collection_id);675            <ReFungibleItemList<T>>::remove_prefix(collection_id);676677            <NftTransferBasket<T>>::remove_prefix(collection_id);678            <FungibleTransferBasket<T>>::remove_prefix(collection_id);679            <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);680681            <VariableMetaDataBasket<T>>::remove_prefix(collection_id);682683            if CollectionCount::get() > 0684            {685                // bound couter686                let total = CollectionCount::get()687                    .checked_sub(1)688                    .ok_or(Error::<T>::NumOverflow)?;689690                CollectionCount::put(total);691            }692693            Ok(())694        }695696        /// Add an address to white list.697        /// 698        /// # Permissions699        /// 700        /// * Collection Owner701        /// * Collection Admin702        /// 703        /// # Arguments704        /// 705        /// * collection_id.706        /// 707        /// * address.708        #[weight = <T as Config>::WeightInfo::add_to_white_list()]709        pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{710711            let sender = ensure_signed(origin)?;712            Self::check_owner_or_admin_permissions(collection_id, sender)?;713714            <WhiteList<T>>::insert(collection_id, address, true);715            716            Ok(())717        }718719        /// Remove an address from white list.720        /// 721        /// # Permissions722        /// 723        /// * Collection Owner724        /// * Collection Admin725        /// 726        /// # Arguments727        /// 728        /// * collection_id.729        /// 730        /// * address.731        #[weight = <T as Config>::WeightInfo::remove_from_white_list()]732        pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{733734            let sender = ensure_signed(origin)?;735            Self::check_owner_or_admin_permissions(collection_id, sender)?;736737            <WhiteList<T>>::remove(collection_id, address);738739            Ok(())740        }741742        /// Toggle between normal and white list access for the methods with access for `Anyone`.743        /// 744        /// # Permissions745        /// 746        /// * Collection Owner.747        /// 748        /// # Arguments749        /// 750        /// * collection_id.751        /// 752        /// * mode: [AccessMode]753        #[weight = <T as Config>::WeightInfo::set_public_access_mode()]754        pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult755        {756            let sender = ensure_signed(origin)?;757758            Self::check_owner_permissions(collection_id, sender)?;759            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();760            target_collection.access = mode;761            <Collection<T>>::insert(collection_id, target_collection);762763            Ok(())764        }765766        /// Allows Anyone to create tokens if:767        /// * White List is enabled, and768        /// * Address is added to white list, and769        /// * This method was called with True parameter770        /// 771        /// # Permissions772        /// * Collection Owner773        ///774        /// # Arguments775        /// 776        /// * collection_id.777        /// 778        /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.779        #[weight = <T as Config>::WeightInfo::set_mint_permission()]780        pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult781        {782            let sender = ensure_signed(origin)?;783784            Self::check_owner_permissions(collection_id, sender)?;785            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();786            target_collection.mint_mode = mint_permission;787            <Collection<T>>::insert(collection_id, target_collection);788789            Ok(())790        }791792        /// Change the owner of the collection.793        /// 794        /// # Permissions795        /// 796        /// * Collection Owner.797        /// 798        /// # Arguments799        /// 800        /// * collection_id.801        /// 802        /// * new_owner.803        #[weight = <T as Config>::WeightInfo::change_collection_owner()]804        pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {805806            let sender = ensure_signed(origin)?;807            Self::check_owner_permissions(collection_id, sender)?;808            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();809            target_collection.owner = new_owner;810            <Collection<T>>::insert(collection_id, target_collection);811812            Ok(())813        }814815        /// Adds an admin of the Collection.816        /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership. 817        /// 818        /// # Permissions819        /// 820        /// * Collection Owner.821        /// * Collection Admin.822        /// 823        /// # Arguments824        /// 825        /// * collection_id: ID of the Collection to add admin for.826        /// 827        /// * new_admin_id: Address of new admin to add.828        #[weight = <T as Config>::WeightInfo::add_collection_admin()]829        pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {830831            let sender = ensure_signed(origin)?;832            Self::check_owner_or_admin_permissions(collection_id, sender)?;833            let mut admin_arr: Vec<T::AccountId> = Vec::new();834835            if <AdminList<T>>::contains_key(collection_id)836            {837                admin_arr = <AdminList<T>>::get(collection_id);838                ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);839            }840841            // Number of collection admins842            ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);843844            admin_arr.push(new_admin_id);845            <AdminList<T>>::insert(collection_id, admin_arr);846847            Ok(())848        }849850        /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.851        ///852        /// # Permissions853        /// 854        /// * Collection Owner.855        /// * Collection Admin.856        /// 857        /// # Arguments858        /// 859        /// * collection_id: ID of the Collection to remove admin for.860        /// 861        /// * account_id: Address of admin to remove.862        #[weight = <T as Config>::WeightInfo::remove_collection_admin()]863        pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {864865            let sender = ensure_signed(origin)?;866            Self::check_owner_or_admin_permissions(collection_id, sender)?;867            ensure!(<AdminList<T>>::contains_key(collection_id), Error::<T>::AdminNotFound);868869            let mut admin_arr = <AdminList<T>>::get(collection_id);870            admin_arr.retain(|i| *i != account_id);871            <AdminList<T>>::insert(collection_id, admin_arr);872873            Ok(())874        }875876        /// # Permissions877        /// 878        /// * Collection Owner879        /// 880        /// # Arguments881        /// 882        /// * collection_id.883        /// 884        /// * new_sponsor.885        #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]886        pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {887888            let sender = ensure_signed(origin)?;889890            let mut target_collection = <Collection<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?;891            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);892893            target_collection.sponsor = new_sponsor;894            target_collection.sponsor_confirmed = false;895            <Collection<T>>::insert(collection_id, target_collection);896897            Ok(())898        }899900        /// # Permissions901        /// 902        /// * Sponsor.903        /// 904        /// # Arguments905        /// 906        /// * collection_id.907        #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]908        pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {909910            let sender = ensure_signed(origin)?;911912            let mut target_collection = <Collection<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?;913            ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);914915            target_collection.sponsor_confirmed = true;916            <Collection<T>>::insert(collection_id, target_collection);917918            Ok(())919        }920921        /// Switch back to pay-per-own-transaction model.922        ///923        /// # Permissions924        ///925        /// * Collection owner.926        /// 927        /// # Arguments928        /// 929        /// * collection_id.930        #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]931        pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {932933            let sender = ensure_signed(origin)?;934935            let mut target_collection = <Collection<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?;936            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);937938            target_collection.sponsor = T::AccountId::default();939            target_collection.sponsor_confirmed = false;940            <Collection<T>>::insert(collection_id, target_collection);941942            Ok(())943        }944945        /// This method creates a concrete instance of NFT Collection created with CreateCollection method.946        /// 947        /// # Permissions948        /// 949        /// * Collection Owner.950        /// * Collection Admin.951        /// * Anyone if952        ///     * White List is enabled, and953        ///     * Address is added to white list, and954        ///     * MintPermission is enabled (see SetMintPermission method)955        /// 956        /// # Arguments957        /// 958        /// * collection_id: ID of the collection.959        /// 960        /// * owner: Address, initial owner of the NFT.961        ///962        /// * data: Token data to store on chain.963        // #[weight =964        // (130_000_000 as Weight)965        // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))966        // .saturating_add(RocksDbWeight::get().reads(10 as Weight))967        // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]968969        #[weight = <T as Config>::WeightInfo::create_item(data.len())]970        pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {971972            let sender = ensure_signed(origin)?;973974            Self::collection_exists(collection_id)?;975976            let target_collection = <Collection<T>>::get(collection_id).unwrap();977978            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;979            Self::validate_create_item_args(&target_collection, &data)?;980            Self::create_item_no_validation(collection_id, owner, data)?;981982            Ok(())983        }984985        /// This method creates multiple instances of NFT Collection created with CreateCollection method.986        /// 987        /// # Permissions988        /// 989        /// * Collection Owner.990        /// * Collection Admin.991        /// * Anyone if992        ///     * White List is enabled, and993        ///     * Address is added to white list, and994        ///     * MintPermission is enabled (see SetMintPermission method)995        /// 996        /// # Arguments997        /// 998        /// * collection_id: ID of the collection.999        /// 1000        /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].1001        /// 1002        /// * owner: Address, initial owner of the NFT.1003        #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()1004                               .map(|data| { data.len() })1005                               .sum())]1006        pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {10071008            ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);1009            let sender = ensure_signed(origin)?;10101011            Self::collection_exists(collection_id)?;1012            let target_collection = <Collection<T>>::get(collection_id).unwrap();10131014            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;10151016            for data in &items_data {1017                Self::validate_create_item_args(&target_collection, data)?;1018            }1019            for data in &items_data {1020                Self::create_item_no_validation(collection_id, owner.clone(), data.clone())?;1021            }10221023            Ok(())1024        }10251026        /// Destroys a concrete instance of NFT.1027        /// 1028        /// # Permissions1029        /// 1030        /// * Collection Owner.1031        /// * Collection Admin.1032        /// * Current NFT Owner.1033        /// 1034        /// # Arguments1035        /// 1036        /// * collection_id: ID of the collection.1037        /// 1038        /// * item_id: ID of NFT to burn.1039        #[weight = <T as Config>::WeightInfo::burn_item()]1040        pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10411042            let sender = ensure_signed(origin)?;1043            Self::collection_exists(collection_id)?;10441045            // Transfer permissions check1046            let target_collection = <Collection<T>>::get(collection_id).unwrap();1047            ensure!(1048                Self::is_item_owner(sender.clone(), collection_id, item_id) ||1049                (1050                    target_collection.limits.owner_can_transfer &&1051                    Self::is_owner_or_admin_permissions(collection_id, sender.clone())1052                ),1053                Error::<T>::NoPermission1054            );10551056            if target_collection.access == AccessMode::WhiteList {1057                Self::check_white_list(collection_id, &sender)?;1058            }10591060            match target_collection.mode1061            {1062                CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1063                CollectionMode::Fungible(_)  => Self::burn_fungible_item(&sender, collection_id, value)?,1064                CollectionMode::ReFungible  => Self::burn_refungible_item(collection_id, item_id, &sender)?,1065                _ => ()1066            };10671068            // call event1069            Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10701071            Ok(())1072        }10731074        /// Change ownership of the token.1075        /// 1076        /// # Permissions1077        /// 1078        /// * Collection Owner1079        /// * Collection Admin1080        /// * Current NFT owner1081        ///1082        /// # Arguments1083        /// 1084        /// * recipient: Address of token recipient.1085        /// 1086        /// * collection_id.1087        /// 1088        /// * item_id: ID of the item1089        ///     * Non-Fungible Mode: Required.1090        ///     * Fungible Mode: Ignored.1091        ///     * Re-Fungible Mode: Required.1092        /// 1093        /// * value: Amount to transfer.1094        ///     * Non-Fungible Mode: Ignored1095        ///     * Fungible Mode: Must specify transferred amount1096        ///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1097        #[weight = <T as Config>::WeightInfo::transfer()]1098        pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1099            let sender = ensure_signed(origin)?;1100            Self::transfer_internal(sender, recipient, collection_id, item_id, value)1101        }11021103        /// Set, change, or remove approved address to transfer the ownership of the NFT.1104        /// 1105        /// # Permissions1106        /// 1107        /// * Collection Owner1108        /// * Collection Admin1109        /// * Current NFT owner1110        /// 1111        /// # Arguments1112        /// 1113        /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1114        /// 1115        /// * collection_id.1116        /// 1117        /// * item_id: ID of the item.1118        #[weight = <T as Config>::WeightInfo::approve()]1119        pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {11201121            let sender = ensure_signed(origin)?;11221123            Self::collection_exists(collection_id)?;1124            Self::token_exists(collection_id, item_id, &sender)?;11251126            // Transfer permissions check1127            let target_collection = <Collection<T>>::get(collection_id).unwrap();1128            let allowance_limit = if (1129                target_collection.limits.owner_can_transfer &&1130                Self::is_owner_or_admin_permissions(1131                    collection_id,1132                    sender.clone(),1133                )1134            ) {1135                None1136            } else if let Some(amount) = Self::owned_amount(1137                sender.clone(),1138                collection_id,1139                item_id,1140            ) {1141                Some(amount)1142            } else {1143                fail!(Error::<T>::NoPermission);1144            };11451146            if target_collection.access == AccessMode::WhiteList {1147                Self::check_white_list(collection_id, &sender)?;1148                Self::check_white_list(collection_id, &spender)?;1149            }11501151            let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1152            let mut allowance: u128 = amount;1153            if allowance_exists {1154                allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1155            }1156            if let Some(limit) = allowance_limit {1157                ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1158            }1159            <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11601161            Ok(())1162        }1163        1164        /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1165        /// 1166        /// # Permissions1167        /// * Collection Owner1168        /// * Collection Admin1169        /// * Current NFT owner1170        /// * Address approved by current NFT owner1171        /// 1172        /// # Arguments1173        /// 1174        /// * from: Address that owns token.1175        /// 1176        /// * recipient: Address of token recipient.1177        /// 1178        /// * collection_id.1179        /// 1180        /// * item_id: ID of the item.1181        /// 1182        /// * value: Amount to transfer.1183        #[weight = <T as Config>::WeightInfo::transfer_from()]1184        pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11851186            let sender = ensure_signed(origin)?;1187            let mut appoved_transfer = false;11881189            // Check approval1190            let mut approval: u128 = 0;1191            if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &sender)) {1192                approval = <Allowances<T>>::get(collection_id, (item_id, &from, &sender));1193                ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1194                appoved_transfer = true;1195            }11961197            let target_collection = <Collection<T>>::get(collection_id).unwrap();11981199            // Limits check1200            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;12011202            // Transfer permissions check         1203            ensure!(1204                appoved_transfer || 1205                (1206                    target_collection.limits.owner_can_transfer &&1207                    Self::is_owner_or_admin_permissions(collection_id, sender.clone())1208                ),1209                Error::<T>::NoPermission1210            );12111212            if target_collection.access == AccessMode::WhiteList {1213                Self::check_white_list(collection_id, &sender)?;1214                Self::check_white_list(collection_id, &recipient)?;1215            }12161217            // Reduce approval by transferred amount or remove if remaining approval drops to 01218            if approval.checked_sub(value).unwrap_or(0) > 0 {1219                <Allowances<T>>::insert(collection_id, (item_id, &from, &sender), approval - value);1220            }1221            else {1222                <Allowances<T>>::remove(collection_id, (item_id, &from, &sender));1223            }12241225            match target_collection.mode1226            {1227                CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1228                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1229                CollectionMode::ReFungible  => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1230                _ => ()1231            };12321233            Ok(())1234        }12351236        // #[weight = 0]1237        // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {12381239        //     // let no_perm_mes = "You do not have permissions to modify this collection";1240        //     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1241        //     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1242        //     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12431244        //     // // on_nft_received  call12451246        //     // Self::transfer(origin, collection_id, item_id, new_owner)?;12471248        //     Ok(())1249        // }12501251        /// Set off-chain data schema.1252        /// 1253        /// # Permissions1254        /// 1255        /// * Collection Owner1256        /// * Collection Admin1257        /// 1258        /// # Arguments1259        /// 1260        /// * collection_id.1261        /// 1262        /// * schema: String representing the offchain data schema.1263        #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1264        pub fn set_variable_meta_data (1265            origin,1266            collection_id: CollectionId,1267            item_id: TokenId,1268            data: Vec<u8>1269        ) -> DispatchResult {1270            let sender = ensure_signed(origin)?;1271            1272            Self::collection_exists(collection_id)?;1273            Self::token_exists(collection_id, item_id, &sender)?;12741275            ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12761277            // Modify permissions check1278            let target_collection = <Collection<T>>::get(collection_id).unwrap();1279            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1280                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1281                Error::<T>::NoPermission);12821283            match target_collection.mode1284            {1285                CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1286                CollectionMode::ReFungible  => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1287                CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1288                _ => fail!(Error::<T>::UnexpectedCollectionType)1289            };12901291            Ok(())1292        }1293 1294        /// Set schema standard1295        /// ImageURL1296        /// Unique1297        /// 1298        /// # Permissions1299        /// 1300        /// * Collection Owner1301        /// * Collection Admin1302        /// 1303        /// # Arguments1304        /// 1305        /// * collection_id.1306        /// 1307        /// * schema: SchemaVersion: enum1308        #[weight = <T as Config>::WeightInfo::set_schema_version()]1309        pub fn set_schema_version(1310            origin,1311            collection_id: CollectionId,1312            version: SchemaVersion1313        ) -> DispatchResult {1314            let sender = ensure_signed(origin)?;1315            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1316            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();1317            target_collection.schema_version = version;1318            <Collection<T>>::insert(collection_id, target_collection);13191320            Ok(())1321        }13221323        /// Set off-chain data schema.1324        /// 1325        /// # Permissions1326        /// 1327        /// * Collection Owner1328        /// * Collection Admin1329        /// 1330        /// # Arguments1331        /// 1332        /// * collection_id.1333        /// 1334        /// * schema: String representing the offchain data schema.1335        #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1336        pub fn set_offchain_schema(1337            origin,1338            collection_id: CollectionId,1339            schema: Vec<u8>1340        ) -> DispatchResult {1341            let sender = ensure_signed(origin)?;1342            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13431344            // check schema limit1345            ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");13461347            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();1348            target_collection.offchain_schema = schema;1349            <Collection<T>>::insert(collection_id, target_collection);13501351            Ok(())1352        }13531354        /// Set const on-chain data schema.1355        /// 1356        /// # Permissions1357        /// 1358        /// * Collection Owner1359        /// * Collection Admin1360        /// 1361        /// # Arguments1362        /// 1363        /// * collection_id.1364        /// 1365        /// * schema: String representing the const on-chain data schema.1366        #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1367        pub fn set_const_on_chain_schema (1368            origin,1369            collection_id: CollectionId,1370            schema: Vec<u8>1371        ) -> DispatchResult {1372            let sender = ensure_signed(origin)?;1373            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13741375            // check schema limit1376            ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");13771378            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();1379            target_collection.const_on_chain_schema = schema;1380            <Collection<T>>::insert(collection_id, target_collection);13811382            Ok(())1383        }13841385        /// Set variable on-chain data schema.1386        /// 1387        /// # Permissions1388        /// 1389        /// * Collection Owner1390        /// * Collection Admin1391        /// 1392        /// # Arguments1393        /// 1394        /// * collection_id.1395        /// 1396        /// * schema: String representing the variable on-chain data schema.1397        #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1398        pub fn set_variable_on_chain_schema (1399            origin,1400            collection_id: CollectionId,1401            schema: Vec<u8>1402        ) -> DispatchResult {1403            let sender = ensure_signed(origin)?;1404            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;14051406            // check schema limit1407            ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");14081409            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();1410            target_collection.variable_on_chain_schema = schema;1411            <Collection<T>>::insert(collection_id, target_collection);14121413            Ok(())1414        }14151416        // Sudo permissions function1417        #[weight = <T as Config>::WeightInfo::set_chain_limits()]1418        pub fn set_chain_limits(1419            origin,1420            limits: ChainLimits1421        ) -> DispatchResult {14221423            #[cfg(not(feature = "runtime-benchmarks"))]1424            ensure_root(origin)?;14251426            <ChainLimit>::put(limits);1427            Ok(())1428        }14291430        /// Enable smart contract self-sponsoring.1431        /// 1432        /// # Permissions1433        /// 1434        /// * Contract Owner1435        /// 1436        /// # Arguments1437        /// 1438        /// * contract address1439        /// * enable flag1440        /// 1441        #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1442        pub fn enable_contract_sponsoring(1443            origin,1444            contract_address: T::AccountId,1445            enable: bool1446        ) -> DispatchResult {14471448            let sender = ensure_signed(origin)?;14491450            #[cfg(feature = "runtime-benchmarks")]1451            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14521453            Self::ensure_contract_owned(sender, &contract_address)?;14541455            <ContractSelfSponsoring<T>>::insert(contract_address, enable);1456            Ok(())1457        }14581459        /// Set the rate limit for contract sponsoring to specified number of blocks.1460        /// 1461        /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1462        /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1463        /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1464        /// from contract endowment if there are at least B blocks between such transactions. 1465        /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1466        /// 1467        /// # Permissions1468        /// 1469        /// * Contract Owner1470        /// 1471        /// # Arguments1472        /// 1473        /// -`contract_address`: Address of the contract to sponsor1474        /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1475        /// 1476        #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1477        pub fn set_contract_sponsoring_rate_limit(1478            origin,1479            contract_address: T::AccountId,1480            rate_limit: T::BlockNumber1481        ) -> DispatchResult {1482            let sender = ensure_signed(origin)?;14831484            #[cfg(feature = "runtime-benchmarks")]1485            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14861487            Self::ensure_contract_owned(sender, &contract_address)?;1488            <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1489            Ok(())1490        }14911492        /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1493        /// 1494        /// # Permissions1495        /// 1496        /// * Address that deployed smart contract.1497        /// 1498        /// # Arguments1499        /// 1500        /// -`contract_address`: Address of the contract.1501        /// 1502        /// - `enable`: .  1503        #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1504        pub fn toggle_contract_white_list(1505            origin,1506            contract_address: T::AccountId,1507            enable: bool1508        ) -> DispatchResult {1509            let sender = ensure_signed(origin)?;15101511            #[cfg(feature = "runtime-benchmarks")]1512            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15131514            Self::ensure_contract_owned(sender, &contract_address)?;1515            <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1516            Ok(())1517        }1518        1519        /// Add an address to smart contract white list.1520        /// 1521        /// # Permissions1522        /// 1523        /// * Address that deployed smart contract.1524        /// 1525        /// # Arguments1526        /// 1527        /// -`contract_address`: Address of the contract.1528        ///1529        /// -`account_address`: Address to add.1530        #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1531        pub fn add_to_contract_white_list(1532            origin,1533            contract_address: T::AccountId,1534            account_address: T::AccountId1535        ) -> DispatchResult {1536            let sender = ensure_signed(origin)?;15371538            #[cfg(feature = "runtime-benchmarks")]1539            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1540            1541            Self::ensure_contract_owned(sender, &contract_address)?;      1542            <ContractWhiteList<T>>::insert(contract_address, account_address, true);1543            Ok(())1544        }15451546        /// Remove an address from smart contract white list.1547        /// 1548        /// # Permissions1549        /// 1550        /// * Address that deployed smart contract.1551        /// 1552        /// # Arguments1553        /// 1554        /// -`contract_address`: Address of the contract.1555        ///1556        /// -`account_address`: Address to remove.1557        #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1558        pub fn remove_from_contract_white_list(1559            origin,1560            contract_address: T::AccountId,1561            account_address: T::AccountId1562        ) -> DispatchResult {1563            let sender = ensure_signed(origin)?;15641565            #[cfg(feature = "runtime-benchmarks")]1566            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15671568            Self::ensure_contract_owned(sender, &contract_address)?;1569            <ContractWhiteList<T>>::remove(contract_address, account_address);1570            Ok(())1571        }15721573        #[weight = <T as Config>::WeightInfo::set_collection_limits()]1574        pub fn set_collection_limits(1575            origin,1576            collection_id: u32,1577            new_limits: CollectionLimits<T::BlockNumber>,1578        ) -> DispatchResult {1579            let sender = ensure_signed(origin)?;1580            Self::check_owner_permissions(collection_id, sender.clone())?;1581            let mut target_collection = <Collection<T>>::get(collection_id).unwrap();1582            let old_limits = target_collection.limits;1583            let chain_limits = ChainLimit::get();15841585            // collection bounds1586            ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1587                new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1588                new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1589                Error::<T>::CollectionLimitBoundsExceeded);15901591            // token_limit   check  prev1592            ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1593            ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);15941595            ensure!(1596                (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1597                (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1598                Error::<T>::OwnerPermissionsCantBeReverted,1599            );16001601            target_collection.limits = new_limits;1602            <Collection<T>>::insert(collection_id, target_collection);16031604            Ok(())1605        } 1606    }1607}16081609impl<T: Config> Module<T> {16101611    pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {16121613        let target_collection = <Collection<T>>::get(collection_id).unwrap();16141615        // Limits check1616        Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;16171618        // Transfer permissions check1619        ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1620            Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1621            Error::<T>::NoPermission);16221623        if target_collection.access == AccessMode::WhiteList {1624            Self::check_white_list(collection_id, &sender)?;1625            Self::check_white_list(collection_id, &recipient)?;1626        }16271628        match target_collection.mode1629        {1630            CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient.clone())?,1631            CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1632            CollectionMode::ReFungible  => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient.clone())?,1633            _ => ()1634        };16351636        Self::deposit_event(RawEvent::Transfer(collection_id, item_id, sender, recipient, value));16371638        Ok(())1639    }164016411642    fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T>, recipient: &T::AccountId) -> DispatchResult {16431644        // check token limit and account token limit1645        let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1646        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);1647        1648        Ok(())1649    }16501651    fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {16521653        // check token limit and account token limit1654        let total_items: u32 = ItemListIndex::get(collection_id);1655        let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1656        ensure!(collection.limits.token_limit > total_items,  Error::<T>::CollectionTokenLimitExceeded);1657        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);16581659        if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1660            ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1661            Self::check_white_list(collection_id, owner)?;1662            Self::check_white_list(collection_id, sender)?;1663        }16641665        Ok(())1666    }16671668    fn validate_create_item_args(target_collection: &CollectionType<T>, data: &CreateItemData) -> DispatchResult {1669        match target_collection.mode1670        {1671            CollectionMode::NFT => {1672                if let CreateItemData::NFT(data) = data {1673                    // check sizes1674                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1675                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1676                } else {1677                    fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1678                }1679            },1680            CollectionMode::Fungible(_) => {1681                if let CreateItemData::Fungible(_) = data {1682                } else {1683                    fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1684                }1685            },1686            CollectionMode::ReFungible => {1687                if let CreateItemData::ReFungible(data) = data {16881689                    // check sizes1690                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1691                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);16921693                    // Check refungibility limits1694                    ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1695                    ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1696                } else {1697                    fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1698                }1699            },1700            _ => { fail!(Error::<T>::UnexpectedCollectionType); }1701        };17021703        Ok(())1704    }17051706    fn create_item_no_validation(collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1707        match data1708        {1709            CreateItemData::NFT(data) => {1710                let item = NftItemType {1711                    owner: owner.clone(),1712                    const_data: data.const_data,1713                    variable_data: data.variable_data1714                };17151716                Self::add_nft_item(collection_id, item)?;1717            },1718            CreateItemData::Fungible(data) => {1719                Self::add_fungible_item(collection_id, &owner, data.value)?;1720            },1721            CreateItemData::ReFungible(data) => {1722                let mut owner_list = Vec::new();1723                owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});17241725                let item = ReFungibleItemType {1726                    owner: owner_list,1727                    const_data: data.const_data,1728                    variable_data: data.variable_data1729                };17301731                Self::add_refungible_item(collection_id, item)?;1732            }1733        };17341735        // call event1736        Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id), owner));17371738        Ok(())1739    }17401741    fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {17421743        // Does new owner already have an account?1744        let mut balance: u128 = 0;1745        if <FungibleItemList<T>>::contains_key(collection_id, owner) {1746            balance = <FungibleItemList<T>>::get(collection_id, owner).value;1747        } 17481749        // Mint 1750        let item = FungibleItemType {1751            value: balance + value1752        };1753        <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);17541755        // Update balance1756        let new_balance = <Balance<T>>::get(collection_id, owner)1757            .checked_add(value)1758            .ok_or(Error::<T>::NumOverflow)?;1759        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17601761        Ok(())1762    }17631764    fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1765        let current_index = <ItemListIndex>::get(collection_id)1766            .checked_add(1)1767            .ok_or(Error::<T>::NumOverflow)?;1768        let itemcopy = item.clone();17691770        let value = item.owner.first().unwrap().fraction;1771        let owner = item.owner.first().unwrap().owner.clone();17721773        Self::add_token_index(collection_id, current_index, &owner)?;17741775        <ItemListIndex>::insert(collection_id, current_index);1776        <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17771778        // Update balance1779        let new_balance = <Balance<T>>::get(collection_id, &owner)1780            .checked_add(value)1781            .ok_or(Error::<T>::NumOverflow)?;1782        <Balance<T>>::insert(collection_id, owner.clone(), new_balance);17831784        Ok(())1785    }17861787    fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1788        let current_index = <ItemListIndex>::get(collection_id)1789            .checked_add(1)1790            .ok_or(Error::<T>::NumOverflow)?;17911792        let item_owner = item.owner.clone();1793        Self::add_token_index(collection_id, current_index, &item.owner)?;17941795        <ItemListIndex>::insert(collection_id, current_index);1796        <NftItemList<T>>::insert(collection_id, current_index, item);17971798        // Update balance1799        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1800            .checked_add(1)1801            .ok_or(Error::<T>::NumOverflow)?;1802        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);18031804        Ok(())1805    }18061807    fn burn_refungible_item(1808        collection_id: CollectionId,1809        item_id: TokenId,1810        owner: &T::AccountId,1811    ) -> DispatchResult {1812        ensure!(1813            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1814            Error::<T>::TokenNotFound1815        );1816        let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id);1817        let rft_balance = token1818            .owner1819            .iter()1820            .filter(|&i| i.owner == *owner)1821            .next()1822            .unwrap();1823        Self::remove_token_index(collection_id, item_id, owner)?;18241825        // update balance1826        let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.clone())1827            .checked_sub(rft_balance.fraction)1828            .ok_or(Error::<T>::NumOverflow)?;1829        <Balance<T>>::insert(collection_id, rft_balance.owner.clone(), new_balance);18301831        // Re-create owners list with sender removed1832        let index = token1833            .owner1834            .iter()1835            .position(|i| i.owner == *owner)1836            .unwrap();1837        token.owner.remove(index);1838        let owner_count = token.owner.len();18391840        // Burn the token completely if this was the last (only) owner1841        if owner_count == 0 {1842            <ReFungibleItemList<T>>::remove(collection_id, item_id);1843            <VariableMetaDataBasket<T>>::remove(collection_id, item_id);1844        }1845        else {1846            <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1847        }18481849        Ok(())1850    }18511852    fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1853        ensure!(1854            <NftItemList<T>>::contains_key(collection_id, item_id),1855            Error::<T>::TokenNotFound1856        );1857        let item = <NftItemList<T>>::get(collection_id, item_id);1858        Self::remove_token_index(collection_id, item_id, &item.owner)?;18591860        // update balance1861        let new_balance = <Balance<T>>::get(collection_id, &item.owner)1862            .checked_sub(1)1863            .ok_or(Error::<T>::NumOverflow)?;1864        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1865        <NftItemList<T>>::remove(collection_id, item_id);1866        <VariableMetaDataBasket<T>>::remove(collection_id, item_id);18671868        Ok(())1869    }18701871    fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1872        ensure!(1873            <FungibleItemList<T>>::contains_key(collection_id, owner),1874            Error::<T>::TokenNotFound1875        );1876        let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1877        ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18781879        // update balance1880        let new_balance = <Balance<T>>::get(collection_id, owner)1881            .checked_sub(value)1882            .ok_or(Error::<T>::NumOverflow)?;1883        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);18841885        if balance.value - value > 0 {1886            balance.value -= value;1887            <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1888        }1889        else {1890            <FungibleItemList<T>>::remove(collection_id, owner);1891        }18921893        Ok(())1894    }18951896    fn collection_exists(collection_id: CollectionId) -> DispatchResult {1897        ensure!(1898            <Collection<T>>::contains_key(collection_id),1899            Error::<T>::CollectionNotFound1900        );1901        Ok(())1902    }19031904    fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1905        Self::collection_exists(collection_id)?;19061907        let target_collection = <Collection<T>>::get(collection_id).unwrap();1908        ensure!(1909            subject == target_collection.owner,1910            Error::<T>::NoPermission1911        );19121913        Ok(())1914    }19151916    fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1917        let target_collection = <Collection<T>>::get(collection_id).unwrap();1918        let mut result: bool = subject == target_collection.owner;1919        let exists = <AdminList<T>>::contains_key(collection_id);19201921        if !result & exists {1922            if <AdminList<T>>::get(collection_id).contains(&subject) {1923                result = true1924            }1925        }19261927        result1928    }19291930    fn check_owner_or_admin_permissions(1931        collection_id: CollectionId,1932        subject: T::AccountId,1933    ) -> DispatchResult {1934        Self::collection_exists(collection_id)?;1935        let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());19361937        ensure!(1938            result,1939            Error::<T>::NoPermission1940        );1941        Ok(())1942    }19431944    fn owned_amount(1945        subject: T::AccountId,1946        collection_id: CollectionId,1947        item_id: TokenId,1948    ) -> Option<u128> {1949        let target_collection = <Collection<T>>::get(collection_id);19501951        match target_collection.mode {1952            CollectionMode::NFT => {1953                if <NftItemList<T>>::get(collection_id, item_id).owner == subject {1954                    return Some(1)1955                }1956                None1957            },1958            CollectionMode::Fungible(_) => {1959                if <FungibleItemList<T>>::contains_key(collection_id, &subject) {1960                    return Some(<FungibleItemList<T>>::get(collection_id, &subject)1961                        .value);1962                }1963                None1964            },1965            CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)1966                .owner1967                .iter()1968                .find(|i| i.owner == subject)1969                .map(|i| i.fraction),1970            CollectionMode::Invalid => None,1971        }1972    }19731974    fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1975        let target_collection = <Collection<T>>::get(collection_id).unwrap();19761977        match target_collection.mode {1978            CollectionMode::NFT => {1979                <NftItemList<T>>::get(collection_id, item_id).owner == subject1980            }1981            CollectionMode::Fungible(_) => {1982                <FungibleItemList<T>>::contains_key(collection_id, &subject)1983            }1984            CollectionMode::ReFungible => {1985                <ReFungibleItemList<T>>::get(collection_id, item_id)1986                    .owner1987                    .iter()1988                    .any(|i| i.owner == subject)1989            }1990            CollectionMode::Invalid => false,1991        }1992    }19931994    fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1995        let mes = Error::<T>::AddresNotInWhiteList;1996        ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);19971998        Ok(())1999    }20002001    /// Check if token exists. In case of Fungible, check if there is an entry for 2002    /// the owner in fungible balances double map2003    fn token_exists(2004        collection_id: CollectionId,2005        item_id: TokenId,2006        owner: &T::AccountId2007    ) -> DispatchResult {2008        let target_collection = <Collection<T>>::get(collection_id).unwrap();2009        let exists = match target_collection.mode2010        {2011            CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2012            CollectionMode::Fungible(_)  => <FungibleItemList<T>>::contains_key(collection_id, owner),2013            CollectionMode::ReFungible  => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),2014            _ => false2015        };20162017        ensure!(exists == true, Error::<T>::TokenNotFound);2018        Ok(())2019    }20202021    fn transfer_fungible(2022        collection_id: CollectionId,2023        value: u128,2024        owner: &T::AccountId,2025        recipient: &T::AccountId,2026    ) -> DispatchResult {2027        Self::token_exists(collection_id, 0, owner)?;20282029        let mut balance = <FungibleItemList<T>>::get(collection_id, owner);2030        ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);20312032        // Send balance to recipient (updates balanceOf of recipient)2033        Self::add_fungible_item(collection_id, recipient, value)?;20342035        // update balanceOf of sender2036        <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);20372038        // Reduce or remove sender2039        if balance.value == value {2040            <FungibleItemList<T>>::remove(collection_id, owner);2041        }2042        else {2043            balance.value -= value;2044            <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);2045        }20462047        Ok(())2048    }20492050    fn transfer_refungible(2051        collection_id: CollectionId,2052        item_id: TokenId,2053        value: u128,2054        owner: T::AccountId,2055        new_owner: T::AccountId,2056    ) -> DispatchResult {2057        Self::token_exists(collection_id, item_id, &owner)?;20582059        let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);2060        let item = full_item2061            .owner2062            .iter()2063            .filter(|i| i.owner == owner)2064            .next()2065            .ok_or(Error::<T>::NumOverflow)?;2066        let amount = item.fraction;20672068        ensure!(amount >= value, Error::<T>::TokenValueTooLow);20692070        // update balance2071        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2072            .checked_sub(value)2073            .ok_or(Error::<T>::NumOverflow)?;2074        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20752076        let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2077            .checked_add(value)2078            .ok_or(Error::<T>::NumOverflow)?;2079        <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);20802081        let old_owner = item.owner.clone();2082        let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20832084        // transfer2085        if amount == value && !new_owner_has_account {2086            // change owner2087            // new owner do not have account2088            let mut new_full_item = full_item.clone();2089            new_full_item2090                .owner2091                .iter_mut()2092                .find(|i| i.owner == owner)2093                .unwrap()2094                .owner = new_owner.clone();2095            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);20962097            // update index collection2098            Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2099        } else {2100            let mut new_full_item = full_item.clone();2101            new_full_item2102                .owner2103                .iter_mut()2104                .find(|i| i.owner == owner)2105                .unwrap()2106                .fraction -= value;21072108            // separate amount2109            if new_owner_has_account {2110                // new owner has account2111                new_full_item2112                    .owner2113                    .iter_mut()2114                    .find(|i| i.owner == new_owner)2115                    .unwrap()2116                    .fraction += value;2117            } else {2118                // new owner do not have account2119                new_full_item.owner.push(Ownership {2120                    owner: new_owner.clone(),2121                    fraction: value,2122                });2123                Self::add_token_index(collection_id, item_id, &new_owner)?;2124            }21252126            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2127        }21282129        Ok(())2130    }21312132    fn transfer_nft(2133        collection_id: CollectionId,2134        item_id: TokenId,2135        sender: T::AccountId,2136        new_owner: T::AccountId,2137    ) -> DispatchResult {2138        Self::token_exists(collection_id, item_id, &sender)?;21392140        let mut item = <NftItemList<T>>::get(collection_id, item_id);21412142        ensure!(2143            sender == item.owner,2144            Error::<T>::MustBeTokenOwner2145        );21462147        // update balance2148        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2149            .checked_sub(1)2150            .ok_or(Error::<T>::NumOverflow)?;2151        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);21522153        let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2154            .checked_add(1)2155            .ok_or(Error::<T>::NumOverflow)?;2156        <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);21572158        // change owner2159        let old_owner = item.owner.clone();2160        item.owner = new_owner.clone();2161        <NftItemList<T>>::insert(collection_id, item_id, item);21622163        // update index collection2164        Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;21652166        Ok(())2167    }2168    2169    fn set_re_fungible_variable_data(2170        collection_id: CollectionId,2171        item_id: TokenId,2172        data: Vec<u8>2173    ) -> DispatchResult {2174        let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);21752176        item.variable_data = data;21772178        <ReFungibleItemList<T>>::insert(collection_id, item_id, item);21792180        Ok(())2181    }21822183    fn set_nft_variable_data(2184        collection_id: CollectionId,2185        item_id: TokenId,2186        data: Vec<u8>2187    ) -> DispatchResult {2188        let mut item = <NftItemList<T>>::get(collection_id, item_id);2189        2190        item.variable_data = data;21912192        <NftItemList<T>>::insert(collection_id, item_id, item);2193        2194        Ok(())2195    }21962197    fn init_collection(item: &CollectionType<T>) {2198        // check params2199        assert!(2200            item.decimal_points <= MAX_DECIMAL_POINTS,2201            "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2202        );2203        assert!(2204            item.name.len() <= 64,2205            "Collection name can not be longer than 63 char"2206        );2207        assert!(2208            item.name.len() <= 256,2209            "Collection description can not be longer than 255 char"2210        );2211        assert!(2212            item.token_prefix.len() <= 16,2213            "Token prefix can not be longer than 15 char"2214        );22152216        // Generate next collection ID2217        let next_id = CreatedCollectionCount::get()2218            .checked_add(1)2219            .unwrap();22202221        CreatedCollectionCount::put(next_id);2222    }22232224    fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2225        let current_index = <ItemListIndex>::get(collection_id)2226            .checked_add(1)2227            .unwrap();22282229        let item_owner = item.owner.clone();2230        Self::add_token_index(collection_id, current_index, &item.owner).unwrap();22312232        <ItemListIndex>::insert(collection_id, current_index);22332234        // Update balance2235        let new_balance = <Balance<T>>::get(collection_id, &item_owner)2236            .checked_add(1)2237            .unwrap();2238        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2239    }22402241    fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2242        let current_index = <ItemListIndex>::get(collection_id)2243            .checked_add(1)2244            .unwrap();22452246        Self::add_token_index(collection_id, current_index, owner).unwrap();22472248        <ItemListIndex>::insert(collection_id, current_index);22492250        // Update balance2251        let new_balance = <Balance<T>>::get(collection_id, owner)2252            .checked_add(item.value)2253            .unwrap();2254        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2255    }22562257    fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2258        let current_index = <ItemListIndex>::get(collection_id)2259            .checked_add(1)2260            .unwrap();22612262        let value = item.owner.first().unwrap().fraction;2263        let owner = item.owner.first().unwrap().owner.clone();22642265        Self::add_token_index(collection_id, current_index, &owner).unwrap();22662267        <ItemListIndex>::insert(collection_id, current_index);22682269        // Update balance2270        let new_balance = <Balance<T>>::get(collection_id, &owner)2271            .checked_add(value)2272            .unwrap();2273        <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2274    }22752276    fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult {22772278        // add to account limit2279        if <AccountItemCount<T>>::contains_key(owner) {22802281            // bound Owned tokens by a single address2282            let count = <AccountItemCount<T>>::get(owner);2283            ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);22842285            <AccountItemCount<T>>::insert(owner.clone(), count2286                .checked_add(1)2287                .ok_or(Error::<T>::NumOverflow)?);2288        }2289        else {2290            <AccountItemCount<T>>::insert(owner.clone(), 1);2291        }22922293        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2294        if list_exists {2295            let mut list = <AddressTokens<T>>::get(collection_id, owner);2296            let item_contains = list.contains(&item_index.clone());22972298            if !item_contains {2299                list.push(item_index.clone());2300            }23012302            <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2303        } else {2304            let mut itm = Vec::new();2305            itm.push(item_index.clone());2306            <AddressTokens<T>>::insert(collection_id, owner.clone(), itm);2307        }23082309        Ok(())2310    }23112312    fn remove_token_index(2313        collection_id: CollectionId,2314        item_index: TokenId,2315        owner: &T::AccountId,2316    ) -> DispatchResult {23172318        // update counter2319        <AccountItemCount<T>>::insert(owner.clone(), 2320            <AccountItemCount<T>>::get(owner)2321            .checked_sub(1)2322            .ok_or(Error::<T>::NumOverflow)?);232323242325        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2326        if list_exists {2327            let mut list = <AddressTokens<T>>::get(collection_id, owner);2328            let item_contains = list.contains(&item_index.clone());23292330            if item_contains {2331                list.retain(|&item| item != item_index);2332                <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2333            }2334        }23352336        Ok(())2337    }23382339    fn move_token_index(2340        collection_id: CollectionId,2341        item_index: TokenId,2342        old_owner: &T::AccountId,2343        new_owner: &T::AccountId,2344    ) -> DispatchResult {2345        Self::remove_token_index(collection_id, item_index, old_owner)?;2346        Self::add_token_index(collection_id, item_index, new_owner)?;23472348        Ok(())2349    }2350    2351    fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2352        if <ContractOwner<T>>::contains_key(contract.clone()) {2353            let owner = <ContractOwner<T>>::get(contract);2354            ensure!(account == owner, Error::<T>::NoPermission);2355        } else {2356            fail!(Error::<T>::NoPermission);2357        }23582359        Ok(())2360    }2361}23622363////////////////////////////////////////////////////////////////////////////////////////////////////2364// Economic models2365// #region23662367/// Fee multiplier.2368pub type Multiplier = FixedU128;23692370type BalanceOf<T> = <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::Balance;23712372/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2373/// in the queue.2374#[derive(Encode, Decode, Clone, Eq, PartialEq)]2375pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);23762377impl<T: Config + Send + Sync> sp_std::fmt::Debug 2378    for ChargeTransactionPayment<T>2379{2380	#[cfg(feature = "std")]2381	fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2382		write!(f, "ChargeTransactionPayment<{:?}>", self.0)2383	}2384	#[cfg(not(feature = "std"))]2385	fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2386		Ok(())2387	}2388}23892390impl<T: Config> ChargeTransactionPayment<T>2391where2392    T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2393    BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2394    T::AccountId: AsRef<[u8]>,2395    T::AccountId: UncheckedFrom<T::Hash>,2396{2397    fn traditional_fee(2398        len: usize,2399        info: &DispatchInfoOf<T::Call>,2400        tip: BalanceOf<T>,2401    ) -> BalanceOf<T>2402    where2403        T::Call: Dispatchable<Info = DispatchInfo>,2404    {2405        <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2406    }24072408	fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2409        let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2410        let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2411        let len_saturation = max_block_length as u64 / (len as u64).max(1);2412        let coefficient: BalanceOf<T> = weight_saturation2413            .min(len_saturation)2414            .saturated_into::<BalanceOf<T>>();2415        final_fee2416            .saturating_mul(coefficient)2417            .saturated_into::<TransactionPriority>()2418    }24192420    fn withdraw_fee(2421        &self,2422        who: &T::AccountId,2423        call: &T::Call,2424        info: &DispatchInfoOf<T::Call>,2425        len: usize,2426	) -> Result<2427		(2428			BalanceOf<T>,2429			<<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2430		),2431		TransactionValidityError,2432	> {2433        let tip = self.0;24342435        // Set fee based on call type. Creating collection costs 1 Unique.2436        // All other transactions have traditional fees so far2437        // let fee = match call.is_sub_type() {2438        //     Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2439        //     _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2440        //                                                 // _ => <BalanceOf<T>>::from(100)2441        // };2442        let fee = Self::traditional_fee(len, info, tip);24432444        // Only mess with balances if fee is not zero.2445        if fee.is_zero() {2446            return <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2447			.map(|i| (fee, i));2448        }24492450        // Determine who is paying transaction fee based on ecnomic model2451        // Parse call to extract collection ID and access collection sponsor2452        let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2453            Some(Call::create_item(collection_id, _owner, _properties)) => {2454                let collection = <Collection<T>>::get(collection_id).unwrap();24552456                // sponsor timeout2457                let block_number = <system::Module<T>>::block_number() as T::BlockNumber;24582459                let limit = collection.limits.sponsor_transfer_timeout;2460                let mut sponsored = true;2461                if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2462                    let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2463                    let limit_time = last_tx_block + limit.into();2464                    if block_number <= limit_time {2465                        sponsored = false;2466                    }2467                }2468                if sponsored {2469                    <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);2470                }24712472                // check free create limit2473                if (collection.limits.sponsored_data_size >= (_properties.len() as u32)) &&2474                   (collection.sponsor_confirmed) &&2475                   (sponsored)2476                {2477                    collection.sponsor2478                } else {2479                    T::AccountId::default()2480                }2481            }2482            Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2483                let collection = <Collection<T>>::get(collection_id).unwrap();2484                2485                let mut sponsor_transfer = false;2486                if collection.sponsor_confirmed {24872488                    let collection_limits = collection.limits;2489                    let collection_mode = collection.mode;2490    2491                    // sponsor timeout2492                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2493                    sponsor_transfer = match collection_mode {2494                        CollectionMode::NFT => {2495    2496                            // get correct limit2497                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2498                                collection_limits.sponsor_transfer_timeout2499                            } else {2500                                ChainLimit::get().nft_sponsor_transfer_timeout2501                            };2502    2503                            let mut sponsored = true;2504                            if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2505                                let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2506                                let limit_time = last_tx_block + limit.into();2507                                if block_number <= limit_time {2508                                    sponsored = false;2509                                }2510                            }2511                            if sponsored {2512                                <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2513                            }25142515                            sponsored2516                        }2517                        CollectionMode::Fungible(_) => {2518    2519                            // get correct limit2520                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2521                                collection_limits.sponsor_transfer_timeout2522                            } else {2523                                ChainLimit::get().fungible_sponsor_transfer_timeout2524                            };2525    2526                            let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2527                            let mut sponsored = true;2528                            if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2529                                let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2530                                let limit_time = last_tx_block + limit.into();2531                                if block_number <= limit_time {2532                                    sponsored = false;2533                                }2534                            }2535                            if sponsored {2536                                <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2537                            }25382539                            sponsored2540                        }2541                        CollectionMode::ReFungible => {2542    2543                            // get correct limit2544                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2545                                collection_limits.sponsor_transfer_timeout2546                            } else {2547                                ChainLimit::get().refungible_sponsor_transfer_timeout2548                            };2549    2550                            let mut sponsored = true;2551                            if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2552                                let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2553                                let limit_time = last_tx_block + limit.into();2554                                if block_number <= limit_time {2555                                    sponsored = false;2556                                }2557                            }2558                            if sponsored {2559                                <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2560                            }25612562                            sponsored2563                        }2564                        _ => {2565                            false2566                        },2567                    };2568                }25692570                if !sponsor_transfer {2571                    T::AccountId::default()2572                } else {2573                    collection.sponsor2574                }2575            }25762577            Some(Call::set_variable_meta_data(collection_id, item_id, data)) => {2578                let mut sponsor_metadata_changes = false;25792580                let collection = <Collection<T>>::get(collection_id).unwrap();25812582                if2583                    collection.sponsor_confirmed &&2584                    // Can't sponsor fungible collection, this tx will be rejected2585                    // as invalid2586                    !matches!(collection.mode, CollectionMode::Fungible(_)) &&2587                    data.len() <= collection.limits.sponsored_data_size as usize2588                {2589                    if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {2590                        let block_number = <system::Module<T>>::block_number() as T::BlockNumber;25912592                        if <VariableMetaDataBasket<T>>::get(collection_id, item_id)2593                            .map(|last_block| block_number - last_block > rate_limit)2594                            .unwrap_or(true) 2595                        {2596                            sponsor_metadata_changes = true;2597                            <VariableMetaDataBasket<T>>::insert(collection_id, item_id, block_number);2598                        }2599                    }2600                }26012602                if !sponsor_metadata_changes {2603                    T::AccountId::default()2604                } else {2605                    collection.sponsor2606                }2607            }26082609            _ => T::AccountId::default(),2610        };26112612        // Sponsor smart contracts2613        sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {26142615            // On instantiation: set the contract owner2616            Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {26172618                let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2619                    &who,2620                    code_hash,2621                    salt,2622                );2623                <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());26242625                T::AccountId::default()2626            },26272628            // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2629            Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {26302631                let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());26322633                let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2634                  && <ContractOwner<T>>::get(called_contract.clone()) == *who;2635                let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2636                  2637                if !owned_contract && white_list_enabled {2638                    if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2639                        return Err(InvalidTransaction::Call.into());2640                    }2641                }26422643                let mut sponsor_transfer = false;2644                if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2645                    let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2646                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2647                    let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2648                    let limit_time = last_tx_block + rate_limit;26492650                    if block_number >= limit_time {2651                        <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2652                        sponsor_transfer = true;2653                    }2654                } else {2655                    sponsor_transfer = false;2656                }2657               2658                2659                let mut sp = T::AccountId::default();2660                if sponsor_transfer {2661                    if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2662                        if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2663                            sp = called_contract;2664                        }2665                    }2666                }26672668                sp2669            },26702671            _ => sponsor,2672        };26732674        let mut who_pays_fee: T::AccountId = sponsor.clone();2675        if sponsor == T::AccountId::default() {2676            who_pays_fee = who.clone();2677        }26782679		<<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)2680			.map(|i| (fee, i))2681    }2682}268326842685impl<T: Config + Send + Sync> SignedExtension2686    for ChargeTransactionPayment<T>2687where2688    BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2689    T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2690    T::AccountId: AsRef<[u8]>,2691    T::AccountId: UncheckedFrom<T::Hash>,2692{2693    const IDENTIFIER: &'static str = "ChargeTransactionPayment";2694    type AccountId = T::AccountId;2695    type Call = T::Call;2696    type AdditionalSigned = ();2697    type Pre = (2698        // tip2699        BalanceOf<T>,2700        // who pays fee2701        Self::AccountId,2702		// imbalance resulting from withdrawing the fee2703		<<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2704    );2705    fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2706        Ok(())2707    }27082709    fn validate(2710        &self,2711        who: &Self::AccountId,2712        call: &Self::Call,2713        info: &DispatchInfoOf<Self::Call>,2714        len: usize,2715    ) -> TransactionValidity {2716		let (fee, _) = self.withdraw_fee(who, call, info, len)?;2717		Ok(ValidTransaction {2718			priority: Self::get_priority(len, info, fee),2719			..Default::default()2720		})2721    }27222723    fn pre_dispatch(2724        self,2725        who: &Self::AccountId,2726        call: &Self::Call,2727        info: &DispatchInfoOf<Self::Call>,2728        len: usize,2729    ) -> Result<Self::Pre, TransactionValidityError> {2730        let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2731        Ok((self.0, who.clone(), imbalance))2732    }27332734    fn post_dispatch(2735        pre: Self::Pre,2736        info: &DispatchInfoOf<Self::Call>,2737        post_info: &PostDispatchInfoOf<Self::Call>,2738        len: usize,2739        _result: &DispatchResult,2740    ) -> Result<(), TransactionValidityError> {2741		let (tip, who, imbalance) = pre;2742		let actual_fee = transaction_payment::Module::<T>::compute_actual_fee(2743			len as u32,2744			info,2745			post_info,2746			tip,2747		);2748		<T as transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;2749		Ok(())2750    }2751}27522753// #endregion
modifiedruntime/Cargo.tomldiffbeforeafterboth
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
-authors = ['Substrate DevHub <https://github.com/substrate-developer-hub>']
+authors = ['UseTech Professional <https://usetech.com/blockchain>']
 edition = '2018'
 homepage = 'https://substrate.io'
-license = 'Unlicense'
+license = 'All Rights Reserved'
 name = 'nft-runtime'
 repository = 'https://github.com/usetech-llc/nft_parachain/'
-version = '3.0.0'
+version = '2.1.0'
 
 [package.metadata.docs.rs]
 targets = ['x86_64-unknown-linux-gnu']
@@ -25,7 +25,7 @@
 serde = { features = ['derive'], optional = true, version = '1.0.101' }
 
 # local dependencies
-pallet-nft = { path = '../pallets/nft', default-features = false, version = '3.0.0' }
+pallet-nft = { path = '../pallets/nft', default-features = false, version = '2.1.0' }
 
 # Substrate dependencies
 frame-benchmarking = { default-features = false, optional = true, version = '2.0.1' , git = 'https://github.com/usetech-llc/substrate.git', branch = 'unique' }
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -29,6 +29,7 @@
     "testRemoveFromWhiteList": "mocha --timeout 9999999 -r ts-node/register ./**/removeFromWhiteList.test.ts",
     "testConnection": "mocha --timeout 9999999 -r ts-node/register ./**/connection.test.ts",
     "testCollection": "mocha --timeout 9999999 -r ts-node/register ./**/createCollection.test.ts",
+    "testCreateItem": "mocha --timeout 9999999 -r ts-node/register ./**/createItem.test.ts",
     "testCreateMultipleItems": "mocha --timeout 9999999 -r ts-node/register ./**/createMultipleItems.test.ts",
     "testApprove": "mocha --timeout 9999999 -r ts-node/register ./**/approve.test.ts",
     "testTransferFrom": "mocha --timeout 9999999 -r ts-node/register ./**/transferFrom.test.ts",
modifiedtests/src/approve.test.tsdiffbeforeafterboth
--- a/tests/src/approve.test.ts
+++ b/tests/src/approve.test.ts
@@ -2,8 +2,8 @@
 // This file is subject to the terms and conditions defined in
 // file 'LICENSE', which is part of this source code package.
 //
+import { IKeyringPair } from '@polkadot/types/types';
 import { ApiPromise } from '@polkadot/api';
-import BN from 'bn.js';
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
 import privateKey from './substrate/privateKey';
@@ -12,66 +12,85 @@
   approveExpectFail,
   approveExpectSuccess,
   createCollectionExpectSuccess,
-  createFungibleItemExpectSuccess,
   createItemExpectSuccess,
   destroyCollectionExpectSuccess,
-  transferFromExpectSuccess,
-  U128_MAX,
+  setCollectionLimitsExpectSuccess,
+  transferExpectSuccess,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
-const expect = chai.expect;
 
 describe('Integration Test approve(spender, collection_id, item_id, amount):', () => {
+  let Alice: IKeyringPair;
+  let Bob: IKeyringPair;
+  let Charlie: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async () => {
+      Alice = privateKey('//Alice');
+      Bob = privateKey('//Bob');
+      Charlie = privateKey('//Charlie');
+    });
+  });
+
   it('Execute the extrinsic and check approvedList', async () => {
-    await usingApi(async (api: ApiPromise) => {
-      const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      const nftCollectionId = await createCollectionExpectSuccess();
-      // nft
-      const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
-      await approveExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob);
-      // fungible
-      const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
-      const newFungibleTokenId = await createItemExpectSuccess(Alice, fungibleCollectionId, 'Fungible');
-      await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob);
-      // reFungible
-      const reFungibleCollectionId =
-        await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
-      const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
-      await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob);
-    });
+    const nftCollectionId = await createCollectionExpectSuccess();
+    // nft
+    const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+    await approveExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob);
+    // fungible
+    const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+    const newFungibleTokenId = await createItemExpectSuccess(Alice, fungibleCollectionId, 'Fungible');
+    await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob);
+    // reFungible
+    const reFungibleCollectionId =
+      await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+    const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
+    await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob);
   });
 
   it('Remove approval by using 0 amount', async () => {
-    await usingApi(async (api: ApiPromise) => {
-      const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      const nftCollectionId = await createCollectionExpectSuccess();
-      // nft
-      const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
-      await approveExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 1);
-      await approveExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 0);
-      // fungible
-      const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
-      const newFungibleTokenId = await createItemExpectSuccess(Alice, fungibleCollectionId, 'Fungible');
-      await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob, 1);
-      await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob, 0);
-      // reFungible
-      const reFungibleCollectionId =
-        await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
-      const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
-      await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob, 1);
-      await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob, 0);
-    });
+    const nftCollectionId = await createCollectionExpectSuccess();
+    // nft
+    const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+    await approveExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 1);
+    await approveExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 0);
+    // fungible
+    const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+    const newFungibleTokenId = await createItemExpectSuccess(Alice, fungibleCollectionId, 'Fungible');
+    await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob, 1);
+    await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob, 0);
+    // reFungible
+    const reFungibleCollectionId =
+      await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+    const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
+    await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob, 1);
+    await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob, 0);
+  });
+
+  it('can be called by collection owner on non-owned item when OwnerCanTransfer == true', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    const itemId = await createItemExpectSuccess(Alice, collectionId, 'NFT', Bob.address);
+
+    await approveExpectSuccess(collectionId, itemId, Alice, Charlie);
   });
 });
 
 describe('Negative Integration Test approve(spender, collection_id, item_id, amount):', () => {
+  let Alice: IKeyringPair;
+  let Bob: IKeyringPair;
+  let Charlie: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async (api) => {
+      Alice = privateKey('//Alice');
+      Bob = privateKey('//Bob');
+      Charlie = privateKey('//Charlie');
+    });
+  });
+
   it('Approve for a collection that does not exist', async () => {
     await usingApi(async (api: ApiPromise) => {
-      const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
       // nft
       const nftCollectionCount = await api.query.nft.createdCollectionCount() as unknown as number;
       await approveExpectFail(nftCollectionCount + 1, 1, Alice, Bob);
@@ -85,59 +104,79 @@
   });
 
   it('Approve for a collection that was destroyed', async () => {
-    await usingApi(async (api: ApiPromise) => {
-      const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      // nft
-      const nftCollectionId = await createCollectionExpectSuccess();
-      await destroyCollectionExpectSuccess(nftCollectionId);
-      await approveExpectFail(nftCollectionId, 1, Alice, Bob);
-      // fungible
-      const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
-      await destroyCollectionExpectSuccess(fungibleCollectionId);
-      await approveExpectFail(fungibleCollectionId, 1, Alice, Bob);
-      // reFungible
-      const reFungibleCollectionId =
-        await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
-      await destroyCollectionExpectSuccess(reFungibleCollectionId);
-      await approveExpectFail(reFungibleCollectionId, 1, Alice, Bob);
-    });
+    // nft
+    const nftCollectionId = await createCollectionExpectSuccess();
+    await destroyCollectionExpectSuccess(nftCollectionId);
+    await approveExpectFail(nftCollectionId, 1, Alice, Bob);
+    // fungible
+    const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+    await destroyCollectionExpectSuccess(fungibleCollectionId);
+    await approveExpectFail(fungibleCollectionId, 1, Alice, Bob);
+    // reFungible
+    const reFungibleCollectionId =
+      await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+    await destroyCollectionExpectSuccess(reFungibleCollectionId);
+    await approveExpectFail(reFungibleCollectionId, 1, Alice, Bob);
   });
 
   it('Approve transfer of a token that does not exist', async () => {
-    await usingApi(async (api: ApiPromise) => {
-      const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      // nft
-      const nftCollectionId = await createCollectionExpectSuccess();
-      await approveExpectFail(nftCollectionId, 2, Alice, Bob);
-      // fungible
-      const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
-      await approveExpectFail(fungibleCollectionId, 2, Alice, Bob);
-      // reFungible
-      const reFungibleCollectionId =
-        await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
-      await approveExpectFail(reFungibleCollectionId, 2, Alice, Bob);
-    });
+    // nft
+    const nftCollectionId = await createCollectionExpectSuccess();
+    await approveExpectFail(nftCollectionId, 2, Alice, Bob);
+    // fungible
+    const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+    await approveExpectFail(fungibleCollectionId, 2, Alice, Bob);
+    // reFungible
+    const reFungibleCollectionId =
+      await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+    await approveExpectFail(reFungibleCollectionId, 2, Alice, Bob);
   });
 
   it('Approve using the address that does not own the approved token', async () => {
-    await usingApi(async (api: ApiPromise) => {
-      const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      const nftCollectionId = await createCollectionExpectSuccess();
-      // nft
-      const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
-      await approveExpectFail(nftCollectionId, newNftTokenId, Bob, Alice);
-      // fungible
-      const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
-      const newFungibleTokenId = await createItemExpectSuccess(Alice, fungibleCollectionId, 'Fungible');
-      await approveExpectFail(fungibleCollectionId, newFungibleTokenId, Bob, Alice);
-      // reFungible
-      const reFungibleCollectionId =
-        await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
-      const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
-      await approveExpectFail(reFungibleCollectionId, newReFungibleTokenId, Bob, Alice);
-    });
+    const nftCollectionId = await createCollectionExpectSuccess();
+    // nft
+    const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+    await approveExpectFail(nftCollectionId, newNftTokenId, Bob, Alice);
+    // fungible
+    const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+    const newFungibleTokenId = await createItemExpectSuccess(Alice, fungibleCollectionId, 'Fungible');
+    await approveExpectFail(fungibleCollectionId, newFungibleTokenId, Bob, Alice);
+    // reFungible
+    const reFungibleCollectionId =
+      await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+    const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
+    await approveExpectFail(reFungibleCollectionId, newReFungibleTokenId, Bob, Alice);
+  });
+
+  it('should fail if approved more NFTs than owned', async () => {
+    const nftCollectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+    const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+    await transferExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 1, 'NFT');
+    await approveExpectSuccess(nftCollectionId, newNftTokenId, Bob, Alice);
+    await approveExpectFail(nftCollectionId, newNftTokenId, Bob, Alice);
+  });
+
+  it('should fail if approved more ReFungibles than owned', async () => {
+    const nftCollectionId = await createCollectionExpectSuccess({ mode: { type: 'ReFungible' } });
+    const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'ReFungible');
+    await transferExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 100, 'ReFungible');
+    await approveExpectSuccess(nftCollectionId, newNftTokenId, Bob, Alice, 100);
+    await approveExpectFail(nftCollectionId, newNftTokenId, Bob, Alice, 1);
+  });
+
+  it('should fail if approved more Fungibles than owned', async () => {
+    const nftCollectionId = await createCollectionExpectSuccess({ mode: { type: 'Fungible', decimalPoints: 0 } });
+    const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'Fungible');
+    await transferExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 10, 'Fungible');
+    await approveExpectSuccess(nftCollectionId, newNftTokenId, Bob, Alice, 10);
+    await approveExpectFail(nftCollectionId, newNftTokenId, Bob, Alice, 1);
+  });
+
+  it('fails when called by collection owner on non-owned item when OwnerCanTransfer == false', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    const itemId = await createItemExpectSuccess(Alice, collectionId, 'NFT', Bob.address);
+    await setCollectionLimitsExpectSuccess(Alice, collectionId, { OwnerCanTransfer: false });
+
+    await approveExpectFail(collectionId, itemId, Alice, Charlie);
   });
 });
modifiedtests/src/destroyCollection.test.tsdiffbeforeafterboth
--- a/tests/src/destroyCollection.test.ts
+++ b/tests/src/destroyCollection.test.ts
@@ -3,10 +3,12 @@
 // file 'LICENSE', which is part of this source code package.
 //
 
+import { IKeyringPair } from '@polkadot/types/types';
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
+import privateKey from './substrate/privateKey';
 import { default as usingApi } from "./substrate/substrate-api";
-import { createCollectionExpectSuccess, destroyCollectionExpectSuccess, destroyCollectionExpectFailure } from "./util/helpers";
+import { createCollectionExpectSuccess, destroyCollectionExpectSuccess, destroyCollectionExpectFailure, setCollectionLimitsExpectSuccess } from "./util/helpers";
 
 chai.use(chaiAsPromised);
 
@@ -26,6 +28,14 @@
 });
 
 describe('(!negative test!) integration test: ext. destroyCollection():', () => {
+  let alice: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async (api) => {
+      alice = privateKey('//Alice');
+    });
+  });
+
   it('(!negative test!) Destroy a collection that never existed', async () => {
     await usingApi(async (api) => {
       // Find the collection that never existed
@@ -43,4 +53,10 @@
     await destroyCollectionExpectFailure(collectionId, '//Bob');
     await destroyCollectionExpectSuccess(collectionId, '//Alice');
   });
+  it('fails when OwnerCanDestroy == false', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    await setCollectionLimitsExpectSuccess(alice, collectionId, { OwnerCanDestroy: false });
+
+    await destroyCollectionExpectFailure(collectionId, '//Alice');
+  });
 });
modifiedtests/src/setCollectionLimits.test.tsdiffbeforeafterboth
--- a/tests/src/setCollectionLimits.test.ts
+++ b/tests/src/setCollectionLimits.test.ts
@@ -14,6 +14,8 @@
   createCollectionExpectSuccess, getCreatedCollectionCount,
   getCreateItemResult,
   getDetailedCollectionInfo,
+  setCollectionLimitsExpectFailure,
+  setCollectionLimitsExpectSuccess,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
@@ -26,21 +28,8 @@
 const accountTokenOwnershipLimit = 0;
 const sponsoredDataSize = 0;
 const sponsoredMintSize = 0;
-const tokenLimit = 0;
-
-describe('hooks', () => {
-  before(async () => {
-    await usingApi(async () => {
-      const keyring = new Keyring({ type: 'sr25519' });
-      alice = keyring.addFromUri('//Alice');
-    });
-  });
-  it('choose or create collection for testing', async () => {
-    await usingApi(async () => {
-      collectionIdForTesting = await createCollectionExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}});
-    });
-  });
-});
+const sponsorTimeout = 1;
+const tokenLimit = 1;
 
 describe('setCollectionLimits positive', () => {
   let tx;
@@ -48,6 +37,7 @@
     await usingApi(async () => {
       const keyring = new Keyring({ type: 'sr25519' });
       alice = keyring.addFromUri('//Alice');
+      collectionIdForTesting = await createCollectionExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}});
     });
   });
   it('execute setCollectionLimits with predefined params ', async () => {
@@ -55,25 +45,28 @@
       tx = api.tx.nft.setCollectionLimits(
         collectionIdForTesting,
         {
-          accountTokenOwnershipLimit,
-          sponsoredDataSize,
-          sponsoredMintSize,
-          tokenLimit,
+          AccountTokenOwnershipLimit: accountTokenOwnershipLimit,
+          SponsoredMintSize: sponsoredDataSize,
+          TokenLimit: tokenLimit,
+          SponsorTimeout: sponsorTimeout,
+          OwnerCanTransfer: true,
+          OwnerCanDestroy: true
         },
       );
       const events = await submitTransactionAsync(alice, tx);
       const result = getCreateItemResult(events);
+
+      // get collection limits defined previously
+      const collectionInfo = await getDetailedCollectionInfo(api, collectionIdForTesting) as ICollectionInterface;
+
       // tslint:disable-next-line:no-unused-expression
       expect(result.success).to.be.true;
-    });
-  });
-  it('get collection limits defined in previous test', async () => {
-    await usingApi(async (api: ApiPromise) => {
-      const collectionInfo = await getDetailedCollectionInfo(api, collectionIdForTesting) as ICollectionInterface;
       expect(collectionInfo.Limits.AccountTokenOwnershipLimit.toNumber()).to.be.equal(accountTokenOwnershipLimit);
-      expect(collectionInfo.Limits.SponsoredMintSize.toNumber()).to.be.equal(sponsoredMintSize);
+      expect(collectionInfo.Limits.SponsoredMintSize.toNumber()).to.be.equal(sponsoredDataSize);
       expect(collectionInfo.Limits.TokenLimit.toNumber()).to.be.equal(tokenLimit);
-      expect(collectionInfo.Limits.SponsorTimeout.toNumber()).to.be.equal(sponsoredDataSize);
+      expect(collectionInfo.Limits.SponsorTimeout.toNumber()).to.be.equal(sponsorTimeout);
+      expect(collectionInfo.Limits.OwnerCanTransfer.valueOf()).to.be.true;
+      expect(collectionInfo.Limits.OwnerCanDestroy.valueOf()).to.be.true;
     });
   });
 });
@@ -85,6 +78,7 @@
       const keyring = new Keyring({ type: 'sr25519' });
       alice = keyring.addFromUri('//Alice');
       bob = keyring.addFromUri('//Bob');
+      collectionIdForTesting = await createCollectionExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}});
     });
   });
   it('execute setCollectionLimits for not exists collection', async () => {
@@ -131,4 +125,44 @@
       await expect(submitTransactionExpectFailAsync(alice, tx)).to.be.rejected;
     });
   });
+
+  it('fails when trying to enable OwnerCanTransfer after it was disabled', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    await setCollectionLimitsExpectSuccess(alice, collectionId, { 
+      AccountTokenOwnershipLimit: accountTokenOwnershipLimit,
+      SponsoredMintSize: sponsoredDataSize,
+      TokenLimit: tokenLimit,
+      SponsorTimeout: sponsorTimeout,
+      OwnerCanTransfer: false,
+      OwnerCanDestroy: true
+    });
+    await setCollectionLimitsExpectFailure(alice, collectionId, { 
+      AccountTokenOwnershipLimit: accountTokenOwnershipLimit,
+      SponsoredMintSize: sponsoredDataSize,
+      TokenLimit: tokenLimit,
+      SponsorTimeout: sponsorTimeout,
+      OwnerCanTransfer: true,
+      OwnerCanDestroy: true
+    });
+  });
+
+  it('fails when trying to enable OwnerCanDestroy after it was disabled', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    await setCollectionLimitsExpectSuccess(alice, collectionId, {
+      AccountTokenOwnershipLimit: accountTokenOwnershipLimit,
+      SponsoredMintSize: sponsoredDataSize,
+      TokenLimit: tokenLimit,
+      SponsorTimeout: sponsorTimeout,
+      OwnerCanTransfer: true,
+      OwnerCanDestroy: false
+    });
+    await setCollectionLimitsExpectFailure(alice, collectionId, { 
+      AccountTokenOwnershipLimit: accountTokenOwnershipLimit,
+      SponsoredMintSize: sponsoredDataSize,
+      TokenLimit: tokenLimit,
+      SponsorTimeout: sponsorTimeout,
+      OwnerCanTransfer: true,
+      OwnerCanDestroy: true
+    });
+  });
 });
modifiedtests/src/transferFrom.test.tsdiffbeforeafterboth
--- a/tests/src/transferFrom.test.ts
+++ b/tests/src/transferFrom.test.ts
@@ -3,6 +3,7 @@
 // file 'LICENSE', which is part of this source code package.
 //
 import { ApiPromise } from '@polkadot/api';
+import { IKeyringPair } from '@polkadot/types/types';
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
 import privateKey from './substrate/privateKey';
@@ -18,17 +19,27 @@
   transferFromExpectFail,
   transferFromExpectSuccess,
   burnItemExpectSuccess,
+  setCollectionLimitsExpectSuccess,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
 const expect = chai.expect;
 
 describe('Integration Test transferFrom(from, recipient, collection_id, item_id, value):', () => {
+  let Alice: IKeyringPair;
+  let Bob: IKeyringPair;
+  let Charlie: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async (api) => {
+      Alice = privateKey('//Alice');
+      Bob = privateKey('//Bob');
+      Charlie = privateKey('//Charlie');
+    });
+  });
+
   it('Execute the extrinsic and check nftItemList - owner of token', async () => {
     await usingApi(async (api: ApiPromise) => {
-      const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      const Charlie = privateKey('//Charlie');
       // nft
       const nftCollectionId = await createCollectionExpectSuccess();
       const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
@@ -66,14 +77,30 @@
       expect((await getAllowance(fungibleCollectionId, newFungibleTokenId, alice.address, bob.address)).toString()).to.equal('0');
     });
   });
+
+  it('can be called by collection owner on non-owned item when OwnerCanTransfer == true', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    const itemId = await createItemExpectSuccess(Alice, collectionId, 'NFT', Bob.address);
+
+    await transferFromExpectSuccess(collectionId, itemId, Alice, Bob, Charlie);
+  });
 });
 
 describe('Negative Integration Test transferFrom(from, recipient, collection_id, item_id, value):', () => {
+  let Alice: IKeyringPair;
+  let Bob: IKeyringPair;
+  let Charlie: IKeyringPair;
+
+  before(async () => {
+    await usingApi(async (api) => {
+      Alice = privateKey('//Alice');
+      Bob = privateKey('//Bob');
+      Charlie = privateKey('//Charlie');
+    });
+  });
+
   it('transferFrom for a collection that does not exist', async () => {
     await usingApi(async (api: ApiPromise) => {
-      const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      const Charlie = privateKey('//Charlie');
       // nft
       const nftCollectionCount = await api.query.nft.createdCollectionCount() as unknown as number;
       await approveExpectFail(nftCollectionCount + 1, 1, Alice, Bob);
@@ -113,9 +140,6 @@
 
   it('transferFrom for not approved address', async () => {
     await usingApi(async (api: ApiPromise) => {
-      const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      const Charlie = privateKey('//Charlie');
       // nft
       const nftCollectionId = await createCollectionExpectSuccess();
       const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
@@ -137,9 +161,6 @@
 
   it('transferFrom incorrect token count', async () => {
     await usingApi(async (api: ApiPromise) => {
-      const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      const Charlie = privateKey('//Charlie');
       // nft
       const nftCollectionId = await createCollectionExpectSuccess();
       const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
@@ -164,9 +185,6 @@
 
   it('execute transferFrom from account that is not owner of collection', async () => {
     await usingApi(async (api: ApiPromise) => {
-      const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      const Charlie = privateKey('//Charlie');
       const Dave = privateKey('//Dave');
       // nft
       const nftCollectionId = await createCollectionExpectSuccess();
@@ -206,9 +224,6 @@
   });
   it( 'transferFrom burnt token before approve NFT', async () => {
     await usingApi(async (api: ApiPromise) => {
-      const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      const Charlie = privateKey('//Charlie');
       // nft
       const nftCollectionId = await createCollectionExpectSuccess();
       const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
@@ -219,9 +234,6 @@
   });
   it( 'transferFrom burnt token before approve Fungible', async () => {
     await usingApi(async (api: ApiPromise) => {
-    const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      const Charlie = privateKey('//Charlie');
       const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
       const newFungibleTokenId = await createItemExpectSuccess(Alice, fungibleCollectionId, 'Fungible');
       await burnItemExpectSuccess(Alice, fungibleCollectionId, 1, 10);
@@ -232,9 +244,6 @@
   }); 
   it( 'transferFrom burnt token before approve ReFungible', async () => {
     await usingApi(async (api: ApiPromise) => {
-    const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      const Charlie = privateKey('//Charlie');
       const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
       const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
       await burnItemExpectSuccess(Alice, reFungibleCollectionId, newReFungibleTokenId, 1);
@@ -246,9 +255,6 @@
   
   it( 'transferFrom burnt token after approve NFT', async () => {
     await usingApi(async (api: ApiPromise) => {
-      const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      const Charlie = privateKey('//Charlie');
       // nft
       const nftCollectionId = await createCollectionExpectSuccess();
       const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
@@ -259,9 +265,6 @@
   });
   it( 'transferFrom burnt token after approve Fungible', async () => {
     await usingApi(async (api: ApiPromise) => {
-    const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      const Charlie = privateKey('//Charlie');
       const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
       const newFungibleTokenId = await createItemExpectSuccess(Alice, fungibleCollectionId, 'Fungible');
       await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob);
@@ -272,9 +275,6 @@
   }); 
   it( 'transferFrom burnt token after approve ReFungible', async () => {
     await usingApi(async (api: ApiPromise) => {
-    const Alice = privateKey('//Alice');
-      const Bob = privateKey('//Bob');
-      const Charlie = privateKey('//Charlie');
       const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
       const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
       await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob);
@@ -282,5 +282,13 @@
       await transferFromExpectFail(reFungibleCollectionId, newReFungibleTokenId, Bob, Alice, Charlie, 1);
           
     });
-  }); 
+  });
+
+  it('fails when called by collection owner on non-owned item when OwnerCanTransfer == false', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    const itemId = await createItemExpectSuccess(Alice, collectionId, 'NFT', Bob.address);
+    await setCollectionLimitsExpectSuccess(Alice, collectionId, { OwnerCanTransfer: false });
+
+    await transferFromExpectFail(collectionId, itemId, Alice, Bob, Charlie);
+  });
 });
modifiedtests/src/types.tsdiffbeforeafterboth
--- a/tests/src/types.ts
+++ b/tests/src/types.ts
@@ -17,6 +17,8 @@
     SponsoredMintSize: BN;
     TokenLimit: BN;
     SponsorTimeout: BN;
+    OwnerCanTransfer: boolean;
+    OwnerCanDestroy: boolean;
   };
   MintMode: boolean;
   Mode: {
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -36,6 +36,16 @@
   success: boolean;
   collectionId: number;
   itemId: number;
+  recipient: string;
+}
+
+interface TransferResult {
+  success: boolean;
+  collectionId: number;
+  itemId: number;
+  sender: string;
+  recipient: string;
+  value: bigint;
 }
 
 interface IReFungibleOwner {
@@ -94,6 +104,7 @@
   let success = false;
   let collectionId: number = 0;
   let itemId: number = 0;
+  let recipient: string = '';
   events.forEach(({ phase, event: { data, method, section } }) => {
     // console.log(`    ${phase}: ${section}.${method}:: ${data}`);
     if (method == 'ExtrinsicSuccess') {
@@ -101,13 +112,40 @@
     } else if ((section == 'nft') && (method == 'ItemCreated')) {
       collectionId = parseInt(data[0].toString());
       itemId = parseInt(data[1].toString());
+      recipient = data[2].toString();
     }
   });
   const result: CreateItemResult = {
     success,
     collectionId,
     itemId,
+    recipient,
+  };
+  return result;
+}
+
+export function getTransferResult(events: EventRecord[]): TransferResult {
+  const result: TransferResult = {
+    success: false,
+    collectionId: 0,
+    itemId: 0,
+    sender: '',
+    recipient: '',
+    value: 0n,
   };
+
+  events.forEach(({event: {data, method, section}}) => {
+    if (method === 'ExtrinsicSuccess') {
+      result.success = true;
+    } else if (section === 'nft' && method === 'Transfer') {
+      result.collectionId = +data[0].toString();
+      result.itemId = +data[1].toString();
+      result.sender = data[2].toString();
+      result.recipient = data[3].toString();
+      result.value = BigInt(data[4].toString());
+    }
+  });
+
   return result;
 }
 
@@ -670,9 +708,14 @@
     }
     const transferTx = await api.tx.nft.transfer(recipient.address, collectionId, tokenId, value);
     const events = await submitTransactionAsync(sender, transferTx);
-    const result = getCreateItemResult(events);
+    const result = getTransferResult(events);
     // tslint:disable-next-line:no-unused-expression
     expect(result.success).to.be.true;
+    expect(result.collectionId).to.be.equal(collectionId);
+    expect(result.itemId).to.be.equal(tokenId);
+    expect(result.sender).to.be.equal(sender.address);
+    expect(result.recipient).to.be.equal(recipient.address);
+    expect(result.value.toString()).to.be.equal(value.toString());
     if (type === 'NFT') {
       const nftItemData = await api.query.nft.nftItemList(collectionId, tokenId) as unknown as ITokenDataType;
       expect(nftItemData.Owner.toString()).to.be.equal(recipient.address);
@@ -786,6 +829,7 @@
     }
     expect(collectionId).to.be.equal(result.collectionId);
     expect(BItemCount).to.be.equal(result.itemId);
+    expect(owner).to.be.equal(result.recipient);
     newItemId = result.itemId;
   });
   return newItemId;