diff --git a/Makefile b/Makefile
index b9cc1c76..add9dc98 100644
--- a/Makefile
+++ b/Makefile
@@ -7,12 +7,16 @@ build-contract:
 	cd client/balance_of_session && cargo build --release --target wasm32-unknown-unknown
 	cd client/owner_of_session && cargo build --release --target wasm32-unknown-unknown
 	cd client/get_approved_session && cargo build --release --target wasm32-unknown-unknown
+	cd client/minting_contract && cargo build --release --target wasm32-unknown-unknown
+	cd client/transfer_session && cargo build --release --target wasm32-unknown-unknown
 	wasm-strip contract/target/wasm32-unknown-unknown/release/contract.wasm 2>/dev/null | true
 	wasm-strip entrypoint_session/target/wasm32-unknown-unknown/release/entrypoint_call.wasm 2>/dev/null | true
 	wasm-strip client/mint_session/target/wasm32-unknown-unknown/release/mint_call.wasm 2>/dev/null | true
 	wasm-strip client/balance_of_session/target/wasm32-unknown-unknown/release/balance_of_call.wasm 2>/dev/null | true
 	wasm-strip client/owner_of_session/target/wasm32-unknown-unknown/release/owner_of_call.wasm 2>/dev/null | true
 	wasm-strip client/get_approved_session/target/wasm32-unknown-unknown/release/get_approved_call.wasm 2>/dev/null | true
+	wasm-strip client/minting_contract/target/wasm32-unknown-unknown/release/minting_contract.wasm 2>/dev/null | true
+	wasm-strip client/transfer_session/target/wasm32-unknown-unknown/release/transfer_call.wasm 2>/dev/null | true
 
 test: build-contract
 	mkdir -p tests/wasm
@@ -21,6 +25,8 @@ test: build-contract
 	cp client/balance_of_session/target/wasm32-unknown-unknown/release/balance_of_call.wasm tests/wasm
 	cp client/owner_of_session/target/wasm32-unknown-unknown/release/owner_of_call.wasm tests/wasm
 	cp client/get_approved_session/target/wasm32-unknown-unknown/release/get_approved_call.wasm tests/wasm
+	cp client/minting_contract/target/wasm32-unknown-unknown/release/minting_contract.wasm tests/wasm
+	cp client/transfer_session/target/wasm32-unknown-unknown/release/transfer_call.wasm tests/wasm
 	cd tests && cargo test
 
 clippy:
diff --git a/README.md b/README.md
index 465a47db..b0b55848 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,17 @@
 # Enhanced NFT standard
 
 ## Design goals
-- User attempting to use an NFT contract should be able to install the contract
-  with any differentiating arguments easily. Must work out of the box.
-- Reference implementation must be straightforward/clear and obvious.
-- Externally observable association between `Accounts` and `NFT`s they "own".
-- Should be well documented with sufficient/exhaustive tests.
-- Must be entirely self-contained within a singular repo, this includes the contract, tests 
-  and any utilities such as clients and/or links to documentation. 
-- Must support mainstream expectations about common NFT, additional features beyond the norms 
-  as long as they don't interfere with the core functionality.
-- Metadata and Payload should be conformant with the community standards and need not be
-  constrained to CLType.
+- DApp developer attempting to create an NFT contract should be able to install the contract as is,
+  configured for the specific builtin behavior they want their NFT contract instance to have. Must work out of the box.
+- Reference implementation must be straightforward, clear, and obvious.
+- Externally observable association between `Accounts` and/or `Contracts` and `NFT`s they "own".
+- Should be well documented with exhaustive tests that prove all possible combinations of defined behavior work as intended.
+- Must be entirely self-contained within a singular repo, this includes the all code, all tests 
+  all relevant Casperlabs provided SDKs, and all relevant documentation. 
+- Must support mainstream expectations about common NFT conventions.
+- A given NFT contract instance must be able to choose when created if it is using a Metadata schema conformant with existing community standards or a specific custom schema which they provide.
+- A NFT contract instance must validate provided metadata against the specified metadata schema for that contract.
+- Standardized session code to interact with an NFT contract instance must be usable as is, so that a given DApp developer doesn't have to write any Wasm producing logic for normal usage of NFT contract instances produced by this contract.
 
 ## Features and usage
 
diff --git a/client/balance_of_session/README.md b/client/balance_of_session/README.md
new file mode 100644
index 00000000..7283ccb7
--- /dev/null
+++ b/client/balance_of_session/README.md
@@ -0,0 +1,20 @@
+# Session code for the Balance entry point
+
+Utility session code meant for interacting with the `balance_of` entry point on the main enhanced NFT contract.
+The `balance_of` session code calls the relevant entry point and saves the amount of tokens owned by either an `Account`
+or `Contract` and saves the value in the `NamedKeys` of `Account` executing the session code.
+
+
+## Compiling session code
+
+The session code can be compiled to Wasm by running the `make build-contract` command provided in the Makefile at the top level.
+The Wasm will be found in the `client/balance_of_session/target/wasm32-unknown-unknown/release` as `balance_of.wasm`.
+
+## Usage
+
+The `balance_of` session code takes in the following required runtime arguments.
+
+* `nft_contract_hash`: The hash of a given Enhanced NFT contract passed in as a `Key`.
+* `token_owner`: The `Key` of either the `Account` or `Contract` whose balance is being queried.
+* `key_name`: The name for the entry within the `NamedKeys` under which the token amount will be stored, passed in as a `String`.
+
diff --git a/client/balance_of_session/src/main.rs b/client/balance_of_session/src/main.rs
index 509df60c..4370f809 100644
--- a/client/balance_of_session/src/main.rs
+++ b/client/balance_of_session/src/main.rs
@@ -6,8 +6,9 @@ compile_error!("target arch should be wasm32: compile with '--target wasm32-unkn
 
 extern crate alloc;
 use alloc::string::String;
+
 use casper_contract::contract_api::{runtime, storage};
-use casper_types::{runtime_args, ContractHash, Key, RuntimeArgs, U256};
+use casper_types::{runtime_args, ContractHash, Key, RuntimeArgs};
 
 const ENTRY_POINT_BALANCE_OF: &str = "balance_of";
 const ARG_NFT_CONTRACT_HASH: &str = "nft_contract_hash";
@@ -16,11 +17,14 @@ const ARG_KEY_NAME: &str = "key_name";
 
 #[no_mangle]
 pub extern "C" fn call() {
-    let nft_contract_hash: ContractHash = runtime::get_named_arg(ARG_NFT_CONTRACT_HASH);
+    let nft_contract_hash: ContractHash = runtime::get_named_arg::<Key>(ARG_NFT_CONTRACT_HASH)
+        .into_hash()
+        .map(|hash| ContractHash::new(hash))
+        .unwrap();
     let key_name: String = runtime::get_named_arg(ARG_KEY_NAME);
     let token_owner: Key = runtime::get_named_arg(ARG_TOKEN_OWNER);
 
-    let balance = runtime::call_contract::<U256>(
+    let balance = runtime::call_contract::<u64>(
         nft_contract_hash,
         ENTRY_POINT_BALANCE_OF,
         runtime_args! {
diff --git a/client/get_approved_session/README.md b/client/get_approved_session/README.md
new file mode 100644
index 00000000..e20f538f
--- /dev/null
+++ b/client/get_approved_session/README.md
@@ -0,0 +1,18 @@
+# Session code for get_approved
+
+Utility session code for interacting with the `get_approved` entry point present on the enhanced NFT contract. It returns
+a `Key` if a given NFT is approved to be spent by another `Account` or `Contract` apart from the owner of the 
+NFT itself. It returns `Some(Key)` if there is an approved spender, `None` if there is no spender.
+
+## Compiling session code
+
+The session code can be compiled to Wasm by running the `make build-contract` command provided in the Makefile at the top level.
+The Wasm will be found in the `client/get_approved_session/target/wasm32-unknown-unknown/release` as `get_approved.wasm`.
+
+## Usage
+
+The `get_approved` session code takes in the following required runtime arguments.
+
+* `nft_contract_hash`: The hash of a given Enhanced NFT contract passed in as a `Key`.
+* `token_id`: The `id` of the NFT, passed in as a `u64`.
+* `key_name`: The name for the entry within the `NamedKeys` under which `Option<Key>` value is stored, passed in as a `String`.
diff --git a/client/get_approved_session/src/main.rs b/client/get_approved_session/src/main.rs
index fe8496ed..4617f943 100644
--- a/client/get_approved_session/src/main.rs
+++ b/client/get_approved_session/src/main.rs
@@ -6,26 +6,44 @@ compile_error!("target arch should be wasm32: compile with '--target wasm32-unkn
 
 extern crate alloc;
 use alloc::string::String;
+
 use casper_contract::contract_api::{runtime, storage};
-use casper_types::{runtime_args, ContractHash, Key, RuntimeArgs, U256};
+use casper_types::{runtime_args, ContractHash, Key, RuntimeArgs};
 
 const ENTRY_POINT_GET_APPROVED: &str = "get_approved";
 const ARG_NFT_CONTRACT_HASH: &str = "nft_contract_hash";
 const ARG_KEY_NAME: &str = "key_name";
 const ARG_TOKEN_ID: &str = "token_id";
+const ARG_TOKEN_HASH: &str = "token_hash";
+const ARG_IS_HASH_IDENTIFIER_MODE: &str = "is_hash_identifier_mode";
 
 #[no_mangle]
 pub extern "C" fn call() {
-    let nft_contract_hash: ContractHash = runtime::get_named_arg(ARG_NFT_CONTRACT_HASH);
+    let nft_contract_hash: ContractHash = runtime::get_named_arg::<Key>(ARG_NFT_CONTRACT_HASH)
+        .into_hash()
+        .map(|hash| ContractHash::new(hash))
+        .unwrap();
     let key_name: String = runtime::get_named_arg(ARG_KEY_NAME);
-    let token_id = runtime::get_named_arg::<U256>(ARG_TOKEN_ID);
 
-    let maybe_operator = runtime::call_contract::<Option<Key>>(
-        nft_contract_hash,
-        ENTRY_POINT_GET_APPROVED,
-        runtime_args! {
+
+    let maybe_operator = if runtime::get_named_arg::<bool>(ARG_IS_HASH_IDENTIFIER_MODE) {
+        let token_hash = runtime::get_named_arg::<String>(ARG_TOKEN_HASH);
+        runtime::call_contract::<Option<Key>>(
+            nft_contract_hash,
+            ENTRY_POINT_GET_APPROVED,
+            runtime_args! {
+            ARG_TOKEN_HASH => token_hash,
+        },
+        )
+    } else {
+        let token_id = runtime::get_named_arg::<u64>(ARG_TOKEN_ID);
+        runtime::call_contract::<Option<Key>>(
+            nft_contract_hash,
+            ENTRY_POINT_GET_APPROVED,
+            runtime_args! {
             ARG_TOKEN_ID => token_id,
         },
-    );
+        )
+    };
     runtime::put_key(&key_name, storage::new_uref(maybe_operator).into());
 }
diff --git a/client/mint_session/README.md b/client/mint_session/README.md
new file mode 100644
index 00000000..18eb59b2
--- /dev/null
+++ b/client/mint_session/README.md
@@ -0,0 +1,18 @@
+# Session code for minting
+
+Utility session code for interacting with the `mint` entry point present on the enhanced NFT contract. The session code retrieves
+the read only reference and inserts the reference under the executing `Account`s `NamedKeys`.
+
+## Compiling session code
+
+The session code can be compiled to Wasm by running the `make build-contract` command provided in the Makefile at the top level.
+The Wasm will be found in the `client/mint_session/target/wasm32-unknown-unknown/release` as `mint_call.wasm`.
+
+## Usage
+
+The `mint_call` session code takes in the following required runtime arguments.
+
+* `nft_contract_hash`: The hash of a given Enhanced NFT contract passed in as a `Key`.
+* `token_owner`: The `Key` of the owner for the NFT to be minted. Note, this argument is ignored in the `Ownership::Minter` mode.
+* `token_metadata`: The metadata describing the NFT to be minted, passed in as a `String`.
+* `token_uri`: The URI for the off-chain resource represented by the NFT to be minted, passed in as a `String`
\ No newline at end of file
diff --git a/client/mint_session/src/main.rs b/client/mint_session/src/main.rs
index 685a535e..b7948665 100644
--- a/client/mint_session/src/main.rs
+++ b/client/mint_session/src/main.rs
@@ -6,7 +6,7 @@ compile_error!("target arch should be wasm32: compile with '--target wasm32-unkn
 
 extern crate alloc;
 
-use alloc::format;
+
 use alloc::string::String;
 use casper_contract::contract_api::{runtime};
 use casper_types::{runtime_args, ContractHash, Key, RuntimeArgs};
@@ -16,7 +16,7 @@ const ENTRY_POINT_MINT: &str = "mint";
 const ARG_NFT_CONTRACT_HASH: &str = "nft_contract_hash";
 const ARG_TOKEN_OWNER: &str = "token_owner";
 const ARG_TOKEN_META_DATA: &str = "token_meta_data";
-const ARG_TOKEN_URI: &str = "token_uri";
+
 
 #[no_mangle]
 pub extern "C" fn call() {
@@ -27,18 +27,15 @@ pub extern "C" fn call() {
 
     let token_owner = runtime::get_named_arg::<Key>(ARG_TOKEN_OWNER);
     let token_metadata: String = runtime::get_named_arg(ARG_TOKEN_META_DATA);
-    let token_uri: String = runtime::get_named_arg(ARG_TOKEN_URI);
 
-    let (owned_tokens_dictionary_key, collection_name) = runtime::call_contract::<(Key, String)>(
+    let (receipt_name, owned_tokens_dictionary_key, ) = runtime::call_contract::<(String, Key)>(
         nft_contract_hash,
         ENTRY_POINT_MINT,
         runtime_args! {
             ARG_TOKEN_OWNER => token_owner,
             ARG_TOKEN_META_DATA => token_metadata,
-            ARG_TOKEN_URI =>token_uri,
         },
     );
 
-    let nft_contract_named_key = format!("{}_{}", nft_contract_hash.to_formatted_string(), collection_name);
-    runtime::put_key(&nft_contract_named_key, owned_tokens_dictionary_key)
+    runtime::put_key(&receipt_name, owned_tokens_dictionary_key)
 }
diff --git a/client/minting_contract/Cargo.toml b/client/minting_contract/Cargo.toml
new file mode 100644
index 00000000..87927977
--- /dev/null
+++ b/client/minting_contract/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "minting_contract"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+casper-contract = "1.4.3"
+casper-types = "1.4.5"
+
+[[bin]]
+name = "minting_contract"
+path = "src/main.rs"
+bench = false
+doctest = false
+test = false
+
+[profile.release]
+codegen-units = 1
+lto = true
diff --git a/client/minting_contract/src/main.rs b/client/minting_contract/src/main.rs
new file mode 100644
index 00000000..8fcf1616
--- /dev/null
+++ b/client/minting_contract/src/main.rs
@@ -0,0 +1,163 @@
+#![no_std]
+#![no_main]
+
+#[cfg(not(target_arch = "wasm32"))]
+compile_error!("target arch should be wasm32: compile with '--target wasm32-unknown-unknown'");
+
+extern crate alloc;
+
+use alloc::{vec,string::{String, ToString}};
+
+use casper_contract::contract_api::{runtime, storage};
+use casper_types::{CLType, ContractHash, ContractVersion, EntryPoint, EntryPointAccess, EntryPoints, EntryPointType, Key, Parameter, runtime_args, RuntimeArgs};
+use casper_types::contracts::NamedKeys;
+
+const CONTRACT_NAME: &str = "minting_contract_hash";
+const CONTRACT_VERSION: &str = "minting_contract_version";
+const INSTALLER: &str = "installer";
+const HASH_KEY_NAME: &str = "minting_contract_package_hash";
+const ACCESS_KEY_NAME: &str = "minting_contract_access_uref";
+
+const ENTRY_POINT_MINT: &str = "mint";
+const ENTRY_POINT_TRANSFER: &str = "transfer";
+const ENTRY_POINT_BURN: &str = "burn";
+
+const ARG_NFT_CONTRACT_HASH: &str = "nft_contract_hash";
+const ARG_TOKEN_OWNER: &str = "token_owner";
+const ARG_TOKEN_META_DATA: &str = "token_meta_data";
+const ARG_TOKEN_URI: &str = "token_uri";
+const ARG_TARGET_KEY: &str = "target_key";
+const ARG_SOURCE_KEY: &str = "source_key";
+const ARG_TOKEN_ID: &str = "token_id";
+
+
+#[no_mangle]
+pub extern "C" fn mint() {
+    let nft_contract_hash: ContractHash = runtime::get_named_arg::<Key>(ARG_NFT_CONTRACT_HASH)
+        .into_hash()
+        .map(|hash| ContractHash::new(hash))
+        .unwrap();
+
+    let token_owner = runtime::get_named_arg::<Key>(ARG_TOKEN_OWNER);
+    let token_metadata: String = runtime::get_named_arg(ARG_TOKEN_META_DATA);
+    let token_uri: String = runtime::get_named_arg(ARG_TOKEN_URI);
+
+    let (collection_name, owned_tokens_dictionary_key, ) = runtime::call_contract::<(String, Key)>(
+        nft_contract_hash,
+        ENTRY_POINT_MINT,
+        runtime_args! {
+            ARG_TOKEN_OWNER => token_owner,
+            ARG_TOKEN_META_DATA => token_metadata,
+            ARG_TOKEN_URI =>token_uri,
+        },
+    );
+
+    runtime::put_key(&collection_name, owned_tokens_dictionary_key)
+}
+
+#[no_mangle]
+pub extern "C" fn transfer() {
+    let nft_contract_hash: ContractHash = runtime::get_named_arg::<Key>(ARG_NFT_CONTRACT_HASH)
+        .into_hash()
+        .map(|hash| ContractHash::new(hash))
+        .unwrap();
+
+    let token_id = runtime::get_named_arg::<u64>(ARG_TOKEN_ID);
+    let from_token_owner = runtime::get_named_arg::<Key>(ARG_SOURCE_KEY);
+    let target_token_owner = runtime::get_named_arg::<Key>(ARG_TARGET_KEY);
+
+    let (collection_name, owned_tokens_dictionary_key) = runtime::call_contract::<(String, Key)>(
+        nft_contract_hash,
+        ENTRY_POINT_TRANSFER,
+        runtime_args! {
+            ARG_TOKEN_ID => token_id,
+            ARG_SOURCE_KEY => from_token_owner,
+            ARG_TARGET_KEY => target_token_owner
+        }
+    );
+
+    runtime::put_key(&collection_name, owned_tokens_dictionary_key)
+}
+
+#[no_mangle]
+pub extern "C" fn burn() {
+    let nft_contract_hash: ContractHash = runtime::get_named_arg::<Key>(ARG_NFT_CONTRACT_HASH)
+        .into_hash()
+        .map(|hash| ContractHash::new(hash))
+        .unwrap();
+
+    let token_id = runtime::get_named_arg::<u64>(ARG_TOKEN_ID);
+
+    runtime::call_contract::<()>(
+        nft_contract_hash,
+        ENTRY_POINT_BURN,
+        runtime_args! {
+            ARG_TOKEN_ID => token_id
+        }
+    )
+}
+
+
+fn install_minting_contract() -> (ContractHash, ContractVersion) {
+    let mint_entry_point = EntryPoint::new(
+        ENTRY_POINT_MINT,
+        vec![
+            Parameter::new(ARG_TOKEN_META_DATA, CLType::Key),
+            Parameter::new(ARG_TOKEN_OWNER, CLType::Key),
+            Parameter::new(ARG_TOKEN_META_DATA, CLType::String),
+            Parameter::new(ARG_TOKEN_URI, CLType::String)
+        ],
+        CLType::Unit,
+            EntryPointAccess::Public,
+        EntryPointType::Session,
+    );
+
+    let transfer_entry_point = EntryPoint::new(
+        ENTRY_POINT_TRANSFER,
+        vec![
+            Parameter::new(ARG_TOKEN_ID, CLType::U64),
+            Parameter::new(ARG_SOURCE_KEY, CLType::Key),
+            Parameter::new(ARG_TARGET_KEY, CLType::Key),
+        ],
+        CLType::Unit,
+        EntryPointAccess::Public,
+        EntryPointType::Contract,
+    );
+
+    let burn_entry_point = EntryPoint::new(
+        ENTRY_POINT_BURN,
+        vec![Parameter::new(ARG_TOKEN_ID, CLType::U64)],
+        CLType::Unit,
+        EntryPointAccess::Public,
+        EntryPointType::Contract,
+    );
+
+
+    let mut entry_points = EntryPoints::new();
+    entry_points.add_entry_point(mint_entry_point);
+    entry_points.add_entry_point(transfer_entry_point);
+    entry_points.add_entry_point(burn_entry_point);
+
+    let named_keys = {
+        let mut named_keys = NamedKeys::new();
+        named_keys.insert(INSTALLER.to_string(), runtime::get_caller().into());
+
+        named_keys
+    };
+
+    storage::new_contract(
+        entry_points,
+        Some(named_keys),
+        Some(HASH_KEY_NAME.to_string()),
+        Some(ACCESS_KEY_NAME.to_string()),
+    )
+}
+
+#[no_mangle]
+pub extern "C" fn call() {
+    let (contract_hash, contract_version) = install_minting_contract();
+
+    runtime::put_key(CONTRACT_NAME, contract_hash.into());
+    runtime::put_key(CONTRACT_VERSION, storage::new_uref(contract_version).into());
+}
+
diff --git a/client/owner_of_session/README.md b/client/owner_of_session/README.md
new file mode 100644
index 00000000..c6ec4b81
--- /dev/null
+++ b/client/owner_of_session/README.md
@@ -0,0 +1,14 @@
+# Session code for `owner_of`
+
+Utility session code for calling the `owner_of` entrypoint on the enhanced NFT contract. It returns the `Key` of the owner 
+for a given NFT.
+
+## Compiling session code
+
+The session code can be compiled to Wasm by running the `make build-contract` command provided in the Makefile at the top level.
+The Wasm will be found in the `client/owner_of_session/target/wasm32-unknown-unknown/release` as `owner_of_call.wasm`.
+
+## Usage
+
+
+
diff --git a/client/owner_of_session/src/main.rs b/client/owner_of_session/src/main.rs
index 95ec9cf3..66730cde 100644
--- a/client/owner_of_session/src/main.rs
+++ b/client/owner_of_session/src/main.rs
@@ -6,26 +6,41 @@ compile_error!("target arch should be wasm32: compile with '--target wasm32-unkn
 
 extern crate alloc;
 use alloc::string::String;
+
 use casper_contract::contract_api::{runtime, storage};
-use casper_types::{runtime_args, ContractHash, Key, RuntimeArgs, U256};
+use casper_types::{runtime_args, ContractHash, Key, RuntimeArgs};
 
 const ENTRY_POINT_OWNER_OF: &str = "owner_of";
 const ARG_NFT_CONTRACT_HASH: &str = "nft_contract_hash";
 const ARG_KEY_NAME: &str = "key_name";
 const ARG_TOKEN_ID: &str = "token_id";
+const ARG_TOKEN_HASH: &str = "token_hash";
+const ARG_IS_HASH_IDENTIFIER_MODE: &str = "is_hash_identifier_mode";
 
 #[no_mangle]
 pub extern "C" fn call() {
-    let nft_contract_hash: ContractHash = runtime::get_named_arg(ARG_NFT_CONTRACT_HASH);
+    let nft_contract_hash: ContractHash = runtime::get_named_arg::<Key>(ARG_NFT_CONTRACT_HASH)
+        .into_hash()
+        .map(|hash| ContractHash::new(hash))
+        .unwrap();
     let key_name: String = runtime::get_named_arg(ARG_KEY_NAME);
 
-    let token_id: U256 = runtime::get_named_arg(ARG_TOKEN_ID);
-    let owner = runtime::call_contract::<Key>(
-        nft_contract_hash,
-        ENTRY_POINT_OWNER_OF,
-        runtime_args! {
+    let owner = if runtime::get_named_arg(ARG_IS_HASH_IDENTIFIER_MODE) {
+        let token_hash = runtime::get_named_arg::<String>(ARG_TOKEN_HASH);
+        runtime::call_contract::<Key>(
+            nft_contract_hash,
+            ENTRY_POINT_OWNER_OF,
+            runtime_args! {
+            ARG_TOKEN_HASH => token_hash,
+        },)
+    } else {
+        let token_id = runtime::get_named_arg::<u64>(ARG_TOKEN_ID);
+        runtime::call_contract::<Key>(
+            nft_contract_hash,
+            ENTRY_POINT_OWNER_OF,
+            runtime_args! {
             ARG_TOKEN_ID => token_id,
-        },
-    );
+        },)
+    };
     runtime::put_key(&key_name, storage::new_uref(owner).into());
 }
diff --git a/client/transfer_session/Cargo.toml b/client/transfer_session/Cargo.toml
new file mode 100644
index 00000000..0933f36c
--- /dev/null
+++ b/client/transfer_session/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "transfer_session"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+casper-contract = "1.4.3"
+casper-types = "1.4.5"
+
+[[bin]]
+name = "transfer_call"
+path = "src/main.rs"
+bench = false
+doctest = false
+test = false
+
+[profile.release]
+codegen-units = 1
+lto = true
diff --git a/client/transfer_session/src/main.rs b/client/transfer_session/src/main.rs
new file mode 100644
index 00000000..f56a8e46
--- /dev/null
+++ b/client/transfer_session/src/main.rs
@@ -0,0 +1,54 @@
+#![no_std]
+#![no_main]
+
+extern crate alloc;
+
+use alloc::string::String;
+
+use casper_contract::contract_api::runtime;
+use casper_types::{ContractHash, Key, runtime_args, RuntimeArgs};
+
+const ENTRY_POINT_TRANSFER: &str = "transfer";
+
+const ARG_NFT_CONTRACT_HASH: &str = "nft_contract_hash";
+const ARG_IS_HASH_IDENTIFIER_MODE: &str = "is_hash_identifier_mode";
+const ARG_TOKEN_ID: &str = "token_id";
+const ARG_TOKEN_HASH: &str = "token_hash";
+const ARG_TARGET_KEY: &str = "target_key";
+const ARG_SOURCE_KEY: &str = "source_key";
+
+#[no_mangle]
+pub extern "C" fn call() {
+    let nft_contract_hash: ContractHash = runtime::get_named_arg::<Key>(ARG_NFT_CONTRACT_HASH)
+        .into_hash()
+        .map(|hash| ContractHash::new(hash))
+        .unwrap();
+
+    let source_key: Key = runtime::get_named_arg(ARG_SOURCE_KEY);
+    let target_key: Key = runtime::get_named_arg(ARG_TARGET_KEY);
+
+
+    let (receipt_name, owned_tokens_dictionary_key, ) = if !runtime::get_named_arg::<bool>(ARG_IS_HASH_IDENTIFIER_MODE) {
+        let token_id: u64 = runtime::get_named_arg(ARG_TOKEN_ID);
+        runtime::call_contract::<(String, Key)>(
+            nft_contract_hash,
+            ENTRY_POINT_TRANSFER,
+            runtime_args! {
+            ARG_TOKEN_ID => token_id,
+            ARG_TARGET_KEY => target_key,
+            ARG_SOURCE_KEY => source_key
+        })
+    } else {
+        let token_hash: String = runtime::get_named_arg(ARG_TOKEN_HASH);
+        runtime::call_contract::<(String, Key)>(
+            nft_contract_hash,
+            ENTRY_POINT_TRANSFER,
+            runtime_args! {
+            ARG_TOKEN_HASH => token_hash,
+            ARG_TARGET_KEY => target_key,
+            ARG_SOURCE_KEY => source_key
+        })
+    };
+
+    runtime::put_key(&receipt_name, owned_tokens_dictionary_key)
+}
\ No newline at end of file
diff --git a/contract/Cargo.toml b/contract/Cargo.toml
index 58877f1e..d4ce6b54 100644
--- a/contract/Cargo.toml
+++ b/contract/Cargo.toml
@@ -4,8 +4,11 @@ version = "0.1.0"
 edition = "2018"
 
 [dependencies]
-casper-contract = "1.4.3"
+casper-contract = {version = "1.4.3", features = ["test-support"]}
 casper-types = "1.4.5"
+serde = { version = "1", features = ["derive", "alloc"], default-features = false }
+base16 = { version = "0.2", default-features = false, features = ["alloc"] }
+casper-serde-json-wasm = { git = "https://github.com/darthsiroftardis/casper-serde-json-wasm", branch = "casper-no-std"}
 
 [[bin]]
 name = "contract"
diff --git a/contract/src/constants.rs b/contract/src/constants.rs
index d3a06495..12747414 100644
--- a/contract/src/constants.rs
+++ b/contract/src/constants.rs
@@ -2,25 +2,31 @@ pub const ARG_COLLECTION_NAME: &str = "collection_name";
 pub const ARG_COLLECTION_SYMBOL: &str = "collection_symbol";
 pub const ARG_TOTAL_TOKEN_SUPPLY: &str = "total_token_supply";
 pub const ARG_TOKEN_ID: &str = "token_id";
+pub const ARG_TOKEN_HASH: &str = "token_hash";
 pub const ARG_TOKEN_OWNER: &str = "token_owner";
-pub const ARG_TO_ACCOUNT_HASH: &str = "to_account_hash";
-pub const ARG_FROM_ACCOUNT_HASH: &str = "from_account_hash";
+pub const ARG_TARGET_KEY: &str = "target_key";
+pub const ARG_SOURCE_KEY: &str = "source_key";
 pub const ARG_ALLOW_MINTING: &str = "allow_minting";
 pub const ARG_MINTING_MODE: &str = "minting_mode";
 pub const ARG_TOKEN_META_DATA: &str = "token_meta_data";
 pub const ARG_APPROVE_ALL: &str = "approve_all";
 pub const ARG_OPERATOR: &str = "operator";
 pub const ARG_OWNERSHIP_MODE: &str = "ownership_mode";
-pub const _ARG_HOLDER_MODE: &str = "holder_mode";
-pub const _ARG_WHITELIST_MODE: &str = "whitelist_mode";
+pub const ARG_HOLDER_MODE: &str = "holder_mode";
+pub const ARG_WHITELIST_MODE: &str = "whitelist_mode";
 pub const ARG_NFT_KIND: &str = "nft_kind";
 pub const ARG_JSON_SCHEMA: &str = "json_schema";
-pub const ARG_TOKEN_URI: &str = "token_uri";
-pub const _ARG_CONTRACT_WHITELIST: &str = "contract_whitelist";
+
+pub const ARG_RECEIPT_NAME: &str = "receipt_name";
+pub const ARG_CONTRACT_WHITELIST: &str = "contract_whitelist";
+pub const ARG_NFT_METADATA_KIND: &str = "nft_metadata_kind";
+pub const ARG_IDENTIFIER_MODE: &str = "identifier_mode";
+
 pub const OPERATOR: &str = "operator";
 pub const NUMBER_OF_MINTED_TOKENS: &str = "number_of_minted_tokens";
 pub const INSTALLER: &str = "installer";
 pub const JSON_SCHEMA: &str = "json_schema";
+pub const METADATA_SCHEMA: &str = "metadata_schema";
 pub const CONTRACT_NAME: &str = "nft_contract";
 pub const HASH_KEY_NAME: &str = "nft_contract_package";
 pub const ACCESS_KEY_NAME: &str = "nft_contract_package_access";
@@ -29,18 +35,24 @@ pub const COLLECTION_NAME: &str = "collection_name";
 pub const COLLECTION_SYMBOL: &str = "collection_symbol";
 pub const TOTAL_TOKEN_SUPPLY: &str = "total_token_supply";
 pub const OWNERSHIP_MODE: &str = "ownership_mode";
-pub const NFT_KIND: &str = "nft_asset_type";
+pub const NFT_KIND: &str = "nft_kind";
 pub const ALLOW_MINTING: &str = "allow_minting";
 pub const MINTING_MODE: &str = "minting_mode";
-pub const _HOLDER_MODE: &str = "holder_mode";
+pub const HOLDER_MODE: &str = "holder_mode";
+pub const WHITELIST_MODE: &str = "whitelist_mode";
 pub const TOKEN_OWNERS: &str = "token_owners";
 pub const TOKEN_ISSUERS: &str = "token_issuers";
-pub const TOKEN_META_DATA: &str = "token_meta_data";
-pub const TOKEN_URI: &str = "token_uri";
 pub const OWNED_TOKENS: &str = "owned_tokens";
 pub const BURNT_TOKENS: &str = "burnt_tokens";
 pub const TOKEN_COUNTS: &str = "balances";
-pub const _CONTRACT_WHITELIST: &str = "contract_whitelist";
+pub const CONTRACT_WHITELIST: &str = "contract_whitelist";
+pub const RECEIPT_NAME: &str = "receipt_name";
+pub const NFT_METADATA_KIND: &str = "nft_metadata_kind";
+pub const IDENTIFIER_MODE: &str = "identifier_mode";
+pub const METADATA_CUSTOM_VALIDATED: &str = "metadata_custom_validated";
+pub const METADATA_CEP78: &str = "metadata_cep78";
+pub const METADATA_NFT721: &str = "metadata_nft721";
+pub const METADATA_RAW: &str = "metadata_raw";
 pub const ENTRY_POINT_INIT: &str = "init";
 pub const ENTRY_POINT_SET_VARIABLES: &str = "set_variables";
 pub const ENTRY_POINT_MINT: &str = "mint";
diff --git a/contract/src/error.rs b/contract/src/error.rs
index ea417472..f48310f7 100644
--- a/contract/src/error.rs
+++ b/contract/src/error.rs
@@ -85,6 +85,27 @@ pub enum NFTCoreError {
     InvalidContractWhitelist = 80,
     UnlistedContractHash = 81,
     InvalidContract = 82,
+    EmptyContractWhitelist = 83,
+    MissingReceiptName = 84,
+    InvalidReceiptName = 85,
+    InvalidJsonMetadata = 86,
+    InvalidJsonFormat = 87,
+    FailedToParseCep99Metadata = 88,
+    FailedToParse721Metadata = 89,
+    FailedToParseCustomMetadata = 90,
+    InvalidCEP99Metadata = 91,
+    FailedToJsonifyCEP99Metadata = 92,
+    InvalidNFT721Metadata = 93,
+    FailedToJsonifyNFT721Metadata = 94,
+    InvalidCustomMetadata = 95,
+    MissingNFTMetadataKind = 96,
+    InvalidNFTMetadataKind = 97,
+    MissingIdentifierMode = 98,
+    InvalidIdentifierMode = 99,
+    FailedToParseTokenId = 100,
+    MissingMetadataMutability = 101,
+    InvalidMetadataMutability = 102,
+    FailedToJsonifyCustomMetadata = 103,
 }
 
 impl From<NFTCoreError> for ApiError {
diff --git a/contract/src/main.rs b/contract/src/main.rs
index 96837211..1e5249b7 100644
--- a/contract/src/main.rs
+++ b/contract/src/main.rs
@@ -9,19 +9,26 @@ mod error;
 mod utils;
 
 extern crate alloc;
-use core::convert::TryInto;
 
-use alloc::{boxed::Box, string::String, string::ToString, vec, vec::Vec};
+use alloc::{
+    boxed::Box,
+    format,
+    string::{String, ToString},
+    vec,
+    vec::Vec,
+};
+use core::convert::TryInto;
 
 use casper_types::{
-    account::AccountHash, contracts::NamedKeys, runtime_args, CLType, CLValue, ContractHash,
-    ContractVersion, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, Key, Parameter,
-    RuntimeArgs, U256,
+    contracts::NamedKeys, runtime_args, CLType, CLValue, ContractHash, ContractPackageHash,
+    ContractVersion, EntryPoint, EntryPointAccess, EntryPointType, EntryPoints, Key, KeyTag,
+    Parameter, RuntimeArgs, Tagged,
 };
 
 use casper_contract::{
     contract_api::{
         runtime,
+        runtime::revert,
         storage::{self},
     },
     unwrap_or_revert::UnwrapOrRevert,
@@ -67,7 +74,7 @@ pub extern "C" fn init() {
     )
     .unwrap_or_revert();
 
-    let total_token_supply: U256 = get_named_arg_with_user_errors(
+    let total_token_supply: u64 = get_named_arg_with_user_errors(
         ARG_TOTAL_TOKEN_SUPPLY,
         NFTCoreError::MissingTotalTokenSupply,
         NFTCoreError::InvalidTotalTokenSupply,
@@ -108,6 +115,38 @@ pub extern "C" fn init() {
     .try_into()
     .unwrap_or_revert();
 
+    let holder_mode: NFTHolderMode = get_named_arg_with_user_errors::<u8>(
+        ARG_HOLDER_MODE,
+        NFTCoreError::MissingHolderMode,
+        NFTCoreError::InvalidHolderMode,
+    )
+    .unwrap_or_revert()
+    .try_into()
+    .unwrap_or_revert();
+
+    let whitelist_mode: WhitelistMode = get_named_arg_with_user_errors::<u8>(
+        ARG_WHITELIST_MODE,
+        NFTCoreError::MissingWhitelistMode,
+        NFTCoreError::InvalidWhitelistMode,
+    )
+    .unwrap_or_revert()
+    .try_into()
+    .unwrap_or_revert();
+
+    let contract_whitelist = get_named_arg_with_user_errors::<Vec<ContractHash>>(
+        ARG_CONTRACT_WHITELIST,
+        NFTCoreError::MissingContractWhiteList,
+        NFTCoreError::InvalidContractWhitelist,
+    )
+    .unwrap_or_revert();
+
+    if WhitelistMode::Locked == whitelist_mode
+        && NFTHolderMode::Contracts == holder_mode
+        && contract_whitelist.is_empty()
+    {
+        runtime::revert(NFTCoreError::EmptyContractWhitelist)
+    }
+
     let json_schema: String = get_named_arg_with_user_errors(
         ARG_JSON_SCHEMA,
         NFTCoreError::MissingJsonSchema,
@@ -115,6 +154,31 @@ pub extern "C" fn init() {
     )
     .unwrap_or_revert();
 
+    let receipt_name: String = get_named_arg_with_user_errors(
+        ARG_RECEIPT_NAME,
+        NFTCoreError::MissingReceiptName,
+        NFTCoreError::InvalidReceiptName,
+    )
+    .unwrap_or_revert();
+
+    let nft_metadata_kind: NFTMetadataKind = get_named_arg_with_user_errors::<u8>(
+        ARG_NFT_METADATA_KIND,
+        NFTCoreError::MissingNFTMetadataKind,
+        NFTCoreError::InvalidNFTMetadataKind,
+    )
+    .unwrap_or_revert()
+    .try_into()
+    .unwrap_or_revert();
+
+    let identifier_mode: NFTIdentifierMode = get_named_arg_with_user_errors::<u8>(
+        ARG_IDENTIFIER_MODE,
+        NFTCoreError::MissingIdentifierMode,
+        NFTCoreError::InvalidIdentifierMode,
+    )
+    .unwrap_or_revert()
+    .try_into()
+    .unwrap_or_revert();
+
     // Put all created URefs into the contract's context (necessary to retain access rights,
     // for future use).
     //
@@ -132,29 +196,40 @@ pub extern "C" fn init() {
         OWNERSHIP_MODE,
         storage::new_uref(ownership_mode as u8).into(),
     );
-
     runtime::put_key(NFT_KIND, storage::new_uref(nft_kind as u8).into());
-
     runtime::put_key(JSON_SCHEMA, storage::new_uref(json_schema).into());
+    runtime::put_key(MINTING_MODE, storage::new_uref(minting_mode as u8).into());
+    runtime::put_key(HOLDER_MODE, storage::new_uref(holder_mode as u8).into());
+    runtime::put_key(
+        WHITELIST_MODE,
+        storage::new_uref(whitelist_mode as u8).into(),
+    );
+    runtime::put_key(
+        CONTRACT_WHITELIST,
+        storage::new_uref(contract_whitelist).into(),
+    );
+    runtime::put_key(RECEIPT_NAME, storage::new_uref(receipt_name).into());
+    runtime::put_key(
+        NFT_METADATA_KIND,
+        storage::new_uref(nft_metadata_kind as u8).into(),
+    );
+    runtime::put_key(
+        IDENTIFIER_MODE,
+        storage::new_uref(identifier_mode as u8).into(),
+    );
 
     // Initialize contract with variables which must be present but maybe set to
     // different values after initialization.
     runtime::put_key(ALLOW_MINTING, storage::new_uref(allow_minting).into());
-    runtime::put_key(MINTING_MODE, storage::new_uref(minting_mode as u8).into());
     // This is an internal variable that the installing account cannot change
     // but is incremented by the contract itself.
-    runtime::put_key(
-        NUMBER_OF_MINTED_TOKENS,
-        storage::new_uref(U256::zero()).into(),
-    );
+    runtime::put_key(NUMBER_OF_MINTED_TOKENS, storage::new_uref(0u64).into());
 
     // Create the data dictionaries to store essential values, topically.
     storage::new_dictionary(TOKEN_OWNERS)
         .unwrap_or_revert_with(NFTCoreError::FailedToCreateDictionary);
     storage::new_dictionary(TOKEN_ISSUERS)
         .unwrap_or_revert_with(NFTCoreError::FailedToCreateDictionary);
-    storage::new_dictionary(TOKEN_META_DATA)
-        .unwrap_or_revert_with(NFTCoreError::FailedToCreateDictionary);
     storage::new_dictionary(OWNED_TOKENS)
         .unwrap_or_revert_with(NFTCoreError::FailedToCreateDictionary);
     storage::new_dictionary(OPERATOR).unwrap_or_revert_with(NFTCoreError::FailedToCreateDictionary);
@@ -162,7 +237,13 @@ pub extern "C" fn init() {
         .unwrap_or_revert_with(NFTCoreError::FailedToCreateDictionary);
     storage::new_dictionary(TOKEN_COUNTS)
         .unwrap_or_revert_with(NFTCoreError::FailedToCreateDictionary);
-    storage::new_dictionary(TOKEN_URI)
+    storage::new_dictionary(METADATA_CUSTOM_VALIDATED)
+        .unwrap_or_revert_with(NFTCoreError::FailedToCreateDictionary);
+    storage::new_dictionary(METADATA_CEP78)
+        .unwrap_or_revert_with(NFTCoreError::FailedToCreateDictionary);
+    storage::new_dictionary(METADATA_NFT721)
+        .unwrap_or_revert_with(NFTCoreError::FailedToCreateDictionary);
+    storage::new_dictionary(METADATA_RAW)
         .unwrap_or_revert_with(NFTCoreError::FailedToCreateDictionary);
 }
 
@@ -192,6 +273,30 @@ pub extern "C" fn set_variables() {
         );
         storage::write(allow_minting_uref, allow_minting);
     }
+
+    if let Some(new_contract_whitelist) = get_optional_named_arg_with_user_errors::<Vec<ContractHash>>(
+        ARG_CONTRACT_WHITELIST,
+        NFTCoreError::MissingContractWhiteList,
+    ) {
+        let whitelist_mode: WhitelistMode = get_stored_value_with_user_errors::<u8>(
+            WHITELIST_MODE,
+            NFTCoreError::MissingWhitelistMode,
+            NFTCoreError::InvalidWhitelistMode,
+        )
+        .try_into()
+        .unwrap_or_revert();
+        match whitelist_mode {
+            WhitelistMode::Unlocked => {
+                let whitelist_uref = get_uref(
+                    CONTRACT_WHITELIST,
+                    NFTCoreError::MissingContractWhiteList,
+                    NFTCoreError::InvalidWhitelistMode,
+                );
+                storage::write(whitelist_uref, new_contract_whitelist)
+            }
+            WhitelistMode::Locked => runtime::revert(NFTCoreError::InvalidWhitelistMode),
+        }
+    }
 }
 
 // Mints a new token. Minting will fail if allow_minting is set to false.
@@ -207,17 +312,17 @@ pub extern "C" fn mint() {
 
     // If contract minting behavior is currently toggled off we revert.
     if !minting_status {
-        runtime::revert(NFTCoreError::MintingIsPaused);
+        revert(NFTCoreError::MintingIsPaused);
     }
 
-    let total_token_supply = get_stored_value_with_user_errors::<U256>(
+    let total_token_supply = get_stored_value_with_user_errors::<u64>(
         TOTAL_TOKEN_SUPPLY,
         NFTCoreError::MissingTotalTokenSupply,
         NFTCoreError::InvalidTotalTokenSupply,
     );
 
     // The next_index is the number of minted tokens so far.
-    let mut next_index = get_stored_value_with_user_errors::<U256>(
+    let mut next_index = get_stored_value_with_user_errors::<u64>(
         NUMBER_OF_MINTED_TOKENS,
         NFTCoreError::MissingNumberOfMintedTokens,
         NFTCoreError::InvalidNumberOfMintedTokens,
@@ -225,10 +330,9 @@ pub extern "C" fn mint() {
 
     // Revert if the token supply has been exhausted.
     if next_index >= total_token_supply {
-        runtime::revert(NFTCoreError::TokenSupplyDepleted);
+        revert(NFTCoreError::TokenSupplyDepleted);
     }
 
-    let caller = runtime::get_caller();
     let minting_mode: MintingMode = get_stored_value_with_user_errors::<u8>(
         MINTING_MODE,
         NFTCoreError::MissingMintingMode,
@@ -239,58 +343,99 @@ pub extern "C" fn mint() {
 
     // Revert if minting is private and caller is not installer.
     if let MintingMode::Installer = minting_mode {
-        let installer_account = runtime::get_key(INSTALLER)
-            .unwrap_or_revert_with(NFTCoreError::MissingInstallerKey)
-            .into_account()
-            .unwrap_or_revert_with(NFTCoreError::FailedToConvertToAccountHash);
-
-        // Revert if private minting is required and caller is not installer.
-        if caller != installer_account {
-            runtime::revert(NFTCoreError::InvalidMinter)
+        let caller = get_verified_caller().unwrap_or_revert();
+        match caller.tag() {
+            KeyTag::Hash => {
+                let calling_contract = caller
+                    .into_hash()
+                    .map(ContractHash::new)
+                    .unwrap_or_revert_with(NFTCoreError::InvalidKey);
+                let contract_whitelist = get_stored_value_with_user_errors::<Vec<ContractHash>>(
+                    CONTRACT_WHITELIST,
+                    NFTCoreError::MissingWhitelistMode,
+                    NFTCoreError::InvalidWhitelistMode,
+                );
+                // Revert if the calling contract is not in the whitelist.
+                if !contract_whitelist.contains(&calling_contract) {
+                    revert(NFTCoreError::UnlistedContractHash)
+                }
+            }
+            KeyTag::Account => {
+                let installer_account = runtime::get_key(INSTALLER)
+                    .unwrap_or_revert_with(NFTCoreError::MissingInstallerKey)
+                    .into_account()
+                    .unwrap_or_revert_with(NFTCoreError::FailedToConvertToAccountHash);
+
+                // Revert if private minting is required and caller is not installer.
+                if runtime::get_caller() != installer_account {
+                    runtime::revert(NFTCoreError::InvalidMinter)
+                }
+            }
+            _ => revert(NFTCoreError::InvalidKey),
         }
     }
 
     // The contract's ownership behavior (determined at installation) determines,
     // who owns the NFT we are about to mint.()
-    let ownership_mode = utils::get_ownership_mode().unwrap_or_revert();
-    let token_owner_key = {
-        match ownership_mode {
-            OwnershipMode::Minter => Key::Account(caller),
-            OwnershipMode::Assigned | OwnershipMode::Transferable => {
-                runtime::get_named_arg::<Key>(ARG_TOKEN_OWNER)
-            }
-        }
+    let ownership_mode = get_ownership_mode().unwrap_or_revert();
+    let caller = get_verified_caller().unwrap_or_revert();
+    let token_owner_key: Key = if let OwnershipMode::Assigned = ownership_mode {
+        runtime::get_named_arg(ARG_TOKEN_OWNER)
+    } else {
+        caller
     };
 
-    let token_uri: String = get_named_arg_with_user_errors(
-        ARG_TOKEN_URI,
-        NFTCoreError::MissingTokenURI,
-        NFTCoreError::InvalidTokenURI,
+    let metadata_kind: NFTMetadataKind = get_stored_value_with_user_errors::<u8>(
+        NFT_METADATA_KIND,
+        NFTCoreError::MissingNFTMetadataKind,
+        NFTCoreError::InvalidNFTMetadataKind,
     )
+    .try_into()
     .unwrap_or_revert();
 
-    // Get token metadata
-    let token_meta_data: String = get_named_arg_with_user_errors(
+    let token_metadata = get_named_arg_with_user_errors::<String>(
         ARG_TOKEN_META_DATA,
         NFTCoreError::MissingTokenMetaData,
         NFTCoreError::InvalidTokenMetaData,
     )
     .unwrap_or_revert();
 
+    // Get token metadata if valid.
+    let metadata = validate_metadata(&metadata_kind, token_metadata).unwrap_or_revert();
+
+    let identifier_mode: NFTIdentifierMode = get_stored_value_with_user_errors::<u8>(
+        IDENTIFIER_MODE,
+        NFTCoreError::MissingIdentifierMode,
+        NFTCoreError::InvalidIdentifierMode,
+    )
+    .try_into()
+    .unwrap_or_revert();
+
     // This is the token ID.
-    let dictionary_item_key = &next_index.to_string();
+    let token_id: TokenIdentifier = match identifier_mode {
+        NFTIdentifierMode::Ordinal => TokenIdentifier::Index(next_index),
+        NFTIdentifierMode::Hash => {
+            TokenIdentifier::Hash(base16::encode_lower(&runtime::blake2b(&metadata)))
+        }
+    };
 
-    upsert_dictionary_value_from_key(TOKEN_OWNERS, dictionary_item_key, token_owner_key);
-    upsert_dictionary_value_from_key(TOKEN_META_DATA, dictionary_item_key, token_meta_data);
-    upsert_dictionary_value_from_key(TOKEN_URI, dictionary_item_key, token_uri);
-    upsert_dictionary_value_from_key(TOKEN_ISSUERS, dictionary_item_key, Key::Account(caller));
+    upsert_dictionary_value_from_key(
+        TOKEN_OWNERS,
+        &token_id.get_dictionary_item_key(),
+        token_owner_key,
+    );
+    upsert_dictionary_value_from_key(
+        TOKEN_ISSUERS,
+        &token_id.get_dictionary_item_key(),
+        token_owner_key,
+    );
+    upsert_dictionary_value_from_key(
+        &get_metadata_dictionary_name(&metadata_kind),
+        &token_id.get_dictionary_item_key(),
+        metadata,
+    );
 
-    // We use the string representation of the account_hash as to not exceed the dictionary_item_key length limit,
-    // which is currently set to 64. (Using Key::Account().to_string() exceeds the limit of 64.)
-    let owned_tokens_item_key = token_owner_key
-        .into_account()
-        .unwrap_or_revert_with(NFTCoreError::InvalidKey)
-        .to_string();
+    let owned_tokens_item_key = get_owned_tokens_dictionary_item_key(token_owner_key);
 
     let owned_tokens_actual_key = Key::dictionary(
         get_uref(
@@ -302,39 +447,90 @@ pub extern "C" fn mint() {
     );
 
     // Update owned tokens dictionary
-    let maybe_owned_tokens =
-        get_dictionary_value_from_key::<Vec<U256>>(OWNED_TOKENS, &owned_tokens_item_key);
+    let maybe_owned_tokens: Option<Vec<TokenIdentifier>> = {
+        match identifier_mode {
+            NFTIdentifierMode::Ordinal => {
+                get_dictionary_value_from_key::<Vec<u64>>(OWNED_TOKENS, &owned_tokens_item_key).map(
+                    |token_indices| {
+                        token_indices
+                            .into_iter()
+                            .map(TokenIdentifier::new_index)
+                            .collect()
+                    },
+                )
+            }
+            NFTIdentifierMode::Hash => {
+                get_dictionary_value_from_key::<Vec<String>>(OWNED_TOKENS, &owned_tokens_item_key)
+                    .map(|token_hashes| {
+                        token_hashes
+                            .into_iter()
+                            .map(TokenIdentifier::new_hash)
+                            .collect()
+                    })
+            }
+        }
+    };
 
     // Update the value in the owned_tokens dictionary.
     match maybe_owned_tokens {
         Some(mut owned_tokens) => {
             // Check that we are not minting a duplicate token.
-            if owned_tokens.contains(&next_index) {
+            if owned_tokens.contains(&token_id) {
                 runtime::revert(NFTCoreError::FatalTokenIdDuplication);
             }
 
-            owned_tokens.push(next_index);
-            upsert_dictionary_value_from_key(OWNED_TOKENS, &owned_tokens_item_key, owned_tokens);
+            owned_tokens.push(token_id);
+            match identifier_mode {
+                NFTIdentifierMode::Ordinal => {
+                    let token_indices: Vec<u64> = owned_tokens
+                        .into_iter()
+                        .map(|identifier| identifier.get_index().unwrap_or_revert())
+                        .collect();
+                    upsert_dictionary_value_from_key(
+                        OWNED_TOKENS,
+                        &owned_tokens_item_key,
+                        token_indices,
+                    )
+                }
+                NFTIdentifierMode::Hash => {
+                    let token_hashes: Vec<String> = owned_tokens
+                        .into_iter()
+                        .map(|identifier| identifier.get_hash().unwrap_or_revert())
+                        .collect();
+                    upsert_dictionary_value_from_key(
+                        OWNED_TOKENS,
+                        &owned_tokens_item_key,
+                        token_hashes,
+                    )
+                }
+            }
         }
         None => {
-            upsert_dictionary_value_from_key(
-                OWNED_TOKENS,
-                &owned_tokens_item_key,
-                vec![next_index],
-            );
+            match identifier_mode {
+                NFTIdentifierMode::Ordinal => upsert_dictionary_value_from_key(
+                    OWNED_TOKENS,
+                    &owned_tokens_item_key,
+                    vec![token_id.get_index().unwrap_or_revert()],
+                ),
+                NFTIdentifierMode::Hash => upsert_dictionary_value_from_key(
+                    OWNED_TOKENS,
+                    &owned_tokens_item_key,
+                    vec![token_id.get_hash().unwrap_or_revert()],
+                ),
+            };
         }
     };
 
     //Increment the count of owned tokens.
     let updated_token_count =
-        match get_dictionary_value_from_key::<U256>(TOKEN_COUNTS, &owned_tokens_item_key) {
-            Some(balance) => balance + U256::one(),
-            None => U256::one(),
+        match get_dictionary_value_from_key::<u64>(TOKEN_COUNTS, &owned_tokens_item_key) {
+            Some(balance) => balance + 1u64,
+            None => 1u64,
         };
     upsert_dictionary_value_from_key(TOKEN_COUNTS, &owned_tokens_item_key, updated_token_count);
 
     // Increment number_of_minted_tokens by one
-    next_index += U256::one();
+    next_index += 1u64;
     let number_of_minted_tokens_uref = get_uref(
         NUMBER_OF_MINTED_TOKENS,
         NFTCoreError::MissingTotalTokenSupply,
@@ -342,13 +538,13 @@ pub extern "C" fn mint() {
     );
     storage::write(number_of_minted_tokens_uref, next_index);
 
-    let collection_name: String = get_stored_value_with_user_errors(
-        COLLECTION_NAME,
-        NFTCoreError::MissingCollectionName,
-        NFTCoreError::InvalidCollectionName,
+    let receipt_name = get_stored_value_with_user_errors::<String>(
+        RECEIPT_NAME,
+        NFTCoreError::MissingReceiptName,
+        NFTCoreError::InvalidReceiptName,
     );
 
-    let receipt = CLValue::from_t((owned_tokens_actual_key, collection_name))
+    let receipt = CLValue::from_t((receipt_name, owned_tokens_actual_key))
         .unwrap_or_revert_with(NFTCoreError::FailedToConvertToCLValue);
     runtime::ret(receipt)
 }
@@ -356,63 +552,92 @@ pub extern "C" fn mint() {
 // Marks token as burnt. This blocks and future call to transfer token.
 #[no_mangle]
 pub extern "C" fn burn() {
-    let token_id: U256 = get_named_arg_with_user_errors(
-        ARG_TOKEN_ID,
-        NFTCoreError::MissingTokenID,
-        NFTCoreError::InvalidTokenID,
+    let identifier_mode: NFTIdentifierMode = get_stored_value_with_user_errors::<u8>(
+        IDENTIFIER_MODE,
+        NFTCoreError::MissingIdentifierMode,
+        NFTCoreError::InvalidIdentifierMode,
     )
+    .try_into()
     .unwrap_or_revert();
 
-    let caller: AccountHash = runtime::get_caller();
+    let token_identifier = match identifier_mode {
+        NFTIdentifierMode::Ordinal => get_named_arg_with_user_errors::<u64>(
+            ARG_TOKEN_ID,
+            NFTCoreError::MissingTokenID,
+            NFTCoreError::InvalidTokenID,
+        )
+        .map(TokenIdentifier::new_index)
+        .unwrap_or_revert(),
+        NFTIdentifierMode::Hash => get_named_arg_with_user_errors::<String>(
+            ARG_TOKEN_HASH,
+            NFTCoreError::MissingTokenID,
+            NFTCoreError::InvalidTokenID,
+        )
+        .map(TokenIdentifier::new_hash)
+        .unwrap_or_revert(),
+    };
+
+    let expected_token_owner: Key = get_verified_caller().unwrap_or_revert();
 
     // Revert if caller is not token_owner. This seems to be the only check we need to do.
-    let token_owner =
-        match get_dictionary_value_from_key::<Key>(TOKEN_OWNERS, &token_id.to_string()) {
-            Some(token_owner_key) => {
-                let token_owner_account_hash = token_owner_key
-                    .into_account()
-                    .unwrap_or_revert_with(NFTCoreError::InvalidKey);
-                if token_owner_account_hash != caller {
-                    runtime::revert(NFTCoreError::InvalidTokenOwner)
-                }
-                token_owner_account_hash
+    let token_owner = match get_dictionary_value_from_key::<Key>(
+        TOKEN_OWNERS,
+        &token_identifier.get_dictionary_item_key(),
+    ) {
+        Some(token_owner_key) => {
+            if token_owner_key != expected_token_owner {
+                runtime::revert(NFTCoreError::InvalidTokenOwner)
             }
-            None => runtime::revert(NFTCoreError::InvalidTokenID),
-        };
+            token_owner_key
+        }
+        None => runtime::revert(NFTCoreError::InvalidTokenID),
+    };
 
     // It makes sense to keep this token as owned by the caller. It just happens that the caller
-    // owns a burnt token. That's all. Similarly, we should probably also not change the owned_tokens
-    // dictionary.
-    if get_dictionary_value_from_key::<()>(BURNT_TOKENS, &token_id.to_string()).is_some() {
+    // owns a burnt token. That's all. Similarly, we should probably also not change the
+    // owned_tokens dictionary.
+    if get_dictionary_value_from_key::<()>(
+        BURNT_TOKENS,
+        &token_identifier.get_dictionary_item_key(),
+    )
+    .is_some()
+    {
         runtime::revert(NFTCoreError::PreviouslyBurntToken);
     }
 
     // Mark the token as burnt by adding the token_id to the burnt tokens dictionary.
-    upsert_dictionary_value_from_key::<()>(BURNT_TOKENS, &token_id.to_string(), ());
+    upsert_dictionary_value_from_key::<()>(
+        BURNT_TOKENS,
+        &token_identifier.get_dictionary_item_key(),
+        (),
+    );
+
+    let owned_tokens_item_key = get_owned_tokens_dictionary_item_key(token_owner);
 
     let updated_balance =
-        match get_dictionary_value_from_key::<U256>(TOKEN_COUNTS, &token_owner.to_string()) {
+        match get_dictionary_value_from_key::<u64>(TOKEN_COUNTS, &owned_tokens_item_key) {
             Some(balance) => {
-                if balance > U256::zero() {
-                    balance - U256::one()
+                if balance > 0u64 {
+                    balance - 1u64
                 } else {
                     // This should never happen if contract is implemented correctly.
-                    runtime::revert(NFTCoreError::FatalTokenIdDuplication);
+                    revert(NFTCoreError::FatalTokenIdDuplication);
                 }
             }
             None => {
                 // This should never happen if contract is implemented correctly.
-                runtime::revert(NFTCoreError::FatalTokenIdDuplication);
+                revert(NFTCoreError::FatalTokenIdDuplication);
             }
         };
 
-    upsert_dictionary_value_from_key(TOKEN_COUNTS, &caller.to_string(), updated_balance);
+    upsert_dictionary_value_from_key(TOKEN_COUNTS, &owned_tokens_item_key, updated_balance);
 }
 
 // approve marks a token as approved for transfer by an account
 #[no_mangle]
 pub extern "C" fn approve() {
-    // If we are in minter or assigned mode it makes no sense to approve an operator. Hence we revert.
+    // If we are in minter or assigned mode it makes no sense to approve an operator. Hence we
+    // revert.
     let _ownership_mode = match get_ownership_mode().unwrap_or_revert() {
         OwnershipMode::Minter | OwnershipMode::Assigned => {
             runtime::revert(NFTCoreError::InvalidOwnershipMode)
@@ -420,40 +645,50 @@ pub extern "C" fn approve() {
         OwnershipMode::Transferable => OwnershipMode::Transferable,
     };
 
-    let caller = runtime::get_caller();
-    let token_id = get_named_arg_with_user_errors::<U256>(
-        ARG_TOKEN_ID,
-        NFTCoreError::MissingTokenID,
-        NFTCoreError::InvalidTokenID,
+    let caller: Key = get_verified_caller().unwrap_or_revert();
+
+    let identifier_mode: NFTIdentifierMode = get_stored_value_with_user_errors::<u8>(
+        IDENTIFIER_MODE,
+        NFTCoreError::MissingIdentifierMode,
+        NFTCoreError::InvalidIdentifierMode,
     )
+    .try_into()
     .unwrap_or_revert();
 
-    let number_of_minted_tokens = get_stored_value_with_user_errors::<U256>(
+    let token_identifier = get_token_identifier_from_runtime_args(&identifier_mode);
+    let token_identifier_dictionary_key = token_identifier.get_dictionary_item_key();
+
+    let number_of_minted_tokens = get_stored_value_with_user_errors::<u64>(
         NUMBER_OF_MINTED_TOKENS,
         NFTCoreError::MissingNumberOfMintedTokens,
         NFTCoreError::InvalidNumberOfMintedTokens,
     );
 
-    // Revert if token_id is out of bounds
-    if token_id >= number_of_minted_tokens {
-        runtime::revert(NFTCoreError::InvalidTokenID);
+    if let NFTIdentifierMode::Ordinal = identifier_mode {
+        // Revert if token_id is out of bounds
+        if let TokenIdentifier::Index(index) = &token_identifier {
+            if *index >= number_of_minted_tokens {
+                revert(NFTCoreError::InvalidTokenID);
+            }
+        }
     }
 
-    let token_owner_account_hash =
-        match get_dictionary_value_from_key::<Key>(TOKEN_OWNERS, &token_id.to_string()) {
-            Some(token_owner) => token_owner,
-            None => runtime::revert(NFTCoreError::InvalidAccountHash),
-        }
-        .into_account()
-        .unwrap_or_revert_with(NFTCoreError::InvalidKey);
+    let token_owner_key = match get_dictionary_value_from_key::<Key>(
+        TOKEN_OWNERS,
+        &token_identifier_dictionary_key,
+    ) {
+        Some(token_owner) => token_owner,
+        None => runtime::revert(NFTCoreError::InvalidAccountHash),
+    };
 
     // Revert if caller is not the token_owner. Only the token owner can approve an operator
-    if token_owner_account_hash != caller {
+    if token_owner_key != caller {
         runtime::revert(NFTCoreError::InvalidAccountHash);
     }
 
     // We assume a burnt token cannot be approved
-    if get_dictionary_value_from_key::<()>(BURNT_TOKENS, &token_id.to_string()).is_some() {
+    if get_dictionary_value_from_key::<()>(BURNT_TOKENS, &token_identifier_dictionary_key).is_some()
+    {
         runtime::revert(NFTCoreError::PreviouslyBurntToken);
     }
 
@@ -462,12 +697,10 @@ pub extern "C" fn approve() {
         NFTCoreError::MissingApprovedAccountHash,
         NFTCoreError::InvalidApprovedAccountHash,
     )
-    .unwrap_or_revert()
-    .into_account()
-    .unwrap_or_revert_with(NFTCoreError::InvalidKey);
+    .unwrap_or_revert();
 
     // If token_owner tries to approve themselves that's probably a mistake and we revert.
-    if token_owner_account_hash == operator {
+    if token_owner_key == operator {
         runtime::revert(NFTCoreError::InvalidAccount);
     }
 
@@ -479,15 +712,16 @@ pub extern "C" fn approve() {
 
     storage::dictionary_put(
         approved_uref,
-        &token_id.to_string(),
-        Some(Key::Account(operator)),
+        &token_identifier_dictionary_key,
+        Some(operator),
     );
 }
 
 // Approves the specified operator for transfer token_owner's tokens.
 #[no_mangle]
 pub extern "C" fn set_approval_for_all() {
-    // If we are in minter or assigned mode it makes no sense to approve an operator. Hence we revert.
+    // If we are in minter or assigned mode it makes no sense to approve an operator. Hence we
+    // revert.
     let _ownership_mode = match utils::get_ownership_mode().unwrap_or_revert() {
         OwnershipMode::Minter | OwnershipMode::Assigned => {
             runtime::revert(NFTCoreError::InvalidOwnershipMode)
@@ -510,30 +744,71 @@ pub extern "C" fn set_approval_for_all() {
     )
     .unwrap_or_revert();
 
-    let caller = runtime::get_caller().to_string();
+    let caller: Key = get_verified_caller().unwrap_or_revert();
+
+    let caller_dictionary_item_key = get_owned_tokens_dictionary_item_key(caller);
+
     let approved_uref = get_uref(
         OPERATOR,
         NFTCoreError::MissingStorageUref,
         NFTCoreError::InvalidStorageUref,
     );
 
-    if let Some(owned_tokens) = get_dictionary_value_from_key::<Vec<U256>>(OWNED_TOKENS, &caller) {
+    let identifier_mode: NFTIdentifierMode = get_stored_value_with_user_errors::<u8>(
+        IDENTIFIER_MODE,
+        NFTCoreError::MissingIdentifierMode,
+        NFTCoreError::InvalidIdentifierMode,
+    )
+    .try_into()
+    .unwrap_or_revert();
+
+    let maybe_owned_tokens: Option<Vec<TokenIdentifier>> = match identifier_mode {
+        NFTIdentifierMode::Ordinal => {
+            get_dictionary_value_from_key::<Vec<u64>>(OWNED_TOKENS, &caller_dictionary_item_key)
+                .map(|token_hashes| {
+                    token_hashes
+                        .into_iter()
+                        .map(TokenIdentifier::new_index)
+                        .collect()
+                })
+        }
+        NFTIdentifierMode::Hash => {
+            get_dictionary_value_from_key::<Vec<String>>(OWNED_TOKENS, &caller_dictionary_item_key)
+                .map(|token_hashes| {
+                    token_hashes
+                        .into_iter()
+                        .map(TokenIdentifier::new_hash)
+                        .collect()
+                })
+        }
+    };
+
+    if let Some(owned_tokens) = maybe_owned_tokens {
         // Depending on approve_all we either approve all or disapprove all.
-        for t in owned_tokens {
+        for token_id in owned_tokens {
             if approve_all {
-                storage::dictionary_put(approved_uref, &t.to_string(), Some(operator));
+                storage::dictionary_put(
+                    approved_uref,
+                    &token_id.get_dictionary_item_key(),
+                    Some(operator),
+                );
             } else {
-                storage::dictionary_put(approved_uref, &t.to_string(), Option::<Key>::None);
+                storage::dictionary_put(
+                    approved_uref,
+                    &token_id.get_dictionary_item_key(),
+                    Option::<Key>::None,
+                );
             }
         }
     };
 }
 
-// Transfers token from token_owner to specified account. Transfer will go through if caller is owner or an approved operator.
-// Transfer will fail if OwnershipMode is Minter or Assigned.
+// Transfers token from token_owner to specified account. Transfer will go through if caller is
+// owner or an approved operator. Transfer will fail if OwnershipMode is Minter or Assigned.
 #[no_mangle]
 pub extern "C" fn transfer() {
-    // If we are in minter or assigned mode we are not allowed to transfer ownership of token, hence we revert.
+    // If we are in minter or assigned mode we are not allowed to transfer ownership of token, hence
+    // we revert.
     let _ownership_mode = match utils::get_ownership_mode().unwrap_or_revert() {
         OwnershipMode::Minter | OwnershipMode::Assigned => {
             runtime::revert(NFTCoreError::InvalidOwnershipMode)
@@ -541,151 +816,152 @@ pub extern "C" fn transfer() {
         OwnershipMode::Transferable => OwnershipMode::Transferable,
     };
 
-    // Get token_id argument
-    let token_id: U256 = get_named_arg_with_user_errors(
-        ARG_TOKEN_ID,
-        NFTCoreError::MissingTokenID,
-        NFTCoreError::InvalidTokenID,
+    let holder_mode = get_holder_mode().unwrap_or_revert();
+
+    let identifier_mode: NFTIdentifierMode = get_stored_value_with_user_errors::<u8>(
+        IDENTIFIER_MODE,
+        NFTCoreError::MissingIdentifierMode,
+        NFTCoreError::InvalidIdentifierMode,
     )
+    .try_into()
     .unwrap_or_revert();
 
+    let token_identifier = get_token_identifier_from_runtime_args(&identifier_mode);
+
     // We assume we cannot transfer burnt tokens
-    if get_dictionary_value_from_key::<()>(BURNT_TOKENS, &token_id.to_string()).is_some() {
+    if get_dictionary_value_from_key::<()>(
+        BURNT_TOKENS,
+        &token_identifier.get_dictionary_item_key(),
+    )
+    .is_some()
+    {
         runtime::revert(NFTCoreError::PreviouslyBurntToken);
     }
 
-    let token_owner_account_hash =
-        match get_dictionary_value_from_key::<Key>(TOKEN_OWNERS, &token_id.to_string()) {
-            Some(token_owner) => token_owner,
-            None => runtime::revert(NFTCoreError::InvalidTokenID),
-        }
-        .into_account()
-        .unwrap_or_revert_with(NFTCoreError::InvalidKey);
+    let token_owner_key = match get_dictionary_value_from_key::<Key>(
+        TOKEN_OWNERS,
+        &token_identifier.get_dictionary_item_key(),
+    ) {
+        Some(token_owner) => token_owner,
+        None => runtime::revert(NFTCoreError::InvalidTokenID),
+    };
 
-    let from_token_owner_account_hash = get_named_arg_with_user_errors::<Key>(
-        ARG_FROM_ACCOUNT_HASH,
+    let from_token_owner_key = get_named_arg_with_user_errors::<Key>(
+        ARG_SOURCE_KEY,
         NFTCoreError::MissingAccountHash,
         NFTCoreError::InvalidAccountHash,
     )
-    .unwrap_or_revert()
-    .into_account()
-    .unwrap_or_revert_with(NFTCoreError::InvalidKey);
+    .unwrap_or_revert();
 
-    // Revert if from account is not the token_owner
-    if from_token_owner_account_hash != token_owner_account_hash {
+    if from_token_owner_key != token_owner_key {
         runtime::revert(NFTCoreError::InvalidAccount);
     }
 
-    let caller = runtime::get_caller();
+    let caller = get_verified_caller().unwrap_or_revert();
 
     // Check if caller is approved to execute transfer
-    let is_approved =
-        match get_dictionary_value_from_key::<Option<Key>>(OPERATOR, &token_id.to_string()) {
-            Some(Some(approved_public_key)) => {
-                approved_public_key.into_account().unwrap_or_revert() == caller
-            }
-            Some(None) | None => false,
-        };
+    let is_approved = match get_dictionary_value_from_key::<Option<Key>>(
+        OPERATOR,
+        &token_identifier.get_dictionary_item_key(),
+    ) {
+        Some(Some(approved_key)) => approved_key == caller,
+        Some(None) | None => false,
+    };
 
     // Revert if caller is not owner and not approved.
-    if caller != token_owner_account_hash && !is_approved {
+    if caller != token_owner_key && !is_approved && NFTHolderMode::Accounts == holder_mode {
         runtime::revert(NFTCoreError::InvalidAccount);
     }
 
     let target_owner_key: Key = get_named_arg_with_user_errors(
-        ARG_TO_ACCOUNT_HASH,
+        ARG_TARGET_KEY,
         NFTCoreError::MissingAccountHash,
         NFTCoreError::InvalidAccountHash,
     )
     .unwrap_or_revert();
 
-    let target_owner_item_key = target_owner_key
-        .into_account()
-        .unwrap_or_revert_with(NFTCoreError::InvalidKey)
-        .to_string();
+    let target_owner_item_key = get_owned_tokens_dictionary_item_key(target_owner_key);
+    let from_owner_item_key = get_owned_tokens_dictionary_item_key(from_token_owner_key);
 
     // Updated token_owners dictionary. Revert if token_owner not found.
-    match get_dictionary_value_from_key::<Key>(TOKEN_OWNERS, &token_id.to_string()) {
+    match get_dictionary_value_from_key::<Key>(
+        TOKEN_OWNERS,
+        &token_identifier.get_dictionary_item_key(),
+    ) {
         Some(token_actual_owner) => {
-            let token_actual_owner_account_hash = token_actual_owner
-                .into_account()
-                .unwrap_or_revert_with(NFTCoreError::InvalidKey);
-            if token_actual_owner_account_hash != from_token_owner_account_hash {
+            if token_actual_owner != from_token_owner_key {
                 runtime::revert(NFTCoreError::InvalidTokenOwner)
             }
-
-            upsert_dictionary_value_from_key(TOKEN_OWNERS, &token_id.to_string(), target_owner_key);
+            upsert_dictionary_value_from_key(
+                TOKEN_OWNERS,
+                &token_identifier.get_dictionary_item_key(),
+                target_owner_key,
+            );
         }
         None => runtime::revert(NFTCoreError::InvalidTokenID),
     }
 
     // Update to_account owned_tokens. Revert if owned_tokens list is not found
-    match get_dictionary_value_from_key::<Vec<U256>>(
-        OWNED_TOKENS,
-        &from_token_owner_account_hash.to_string(),
-    ) {
+    match get_token_identifiers_from_dictionary(&identifier_mode, &from_owner_item_key) {
         Some(mut owned_tokens) => {
             // Check that token_id is in owned tokens list. If so remove token_id from list
             // If not revert.
-            if let Some(id) = owned_tokens.iter().position(|id| *id == token_id) {
+            if let Some(id) = owned_tokens.iter().position(|id| *id == token_identifier) {
                 owned_tokens.remove(id);
             } else {
                 runtime::revert(NFTCoreError::InvalidTokenOwner)
             }
-            upsert_dictionary_value_from_key(
-                OWNED_TOKENS,
-                &from_token_owner_account_hash.to_string(),
-                owned_tokens,
-            );
+            upsert_token_identifiers(&identifier_mode, &from_owner_item_key, owned_tokens)
+                .unwrap_or_revert();
         }
         None => runtime::revert(NFTCoreError::InvalidTokenID),
     }
 
     // Update the from_account balance
-    let updated_from_account_balance = match get_dictionary_value_from_key::<U256>(
-        TOKEN_COUNTS,
-        &from_token_owner_account_hash.to_string(),
-    ) {
-        Some(balance) => {
-            if balance > U256::zero() {
-                balance - U256::one()
-            } else {
+    let updated_from_account_balance =
+        match get_dictionary_value_from_key::<u64>(TOKEN_COUNTS, &from_owner_item_key) {
+            Some(balance) => {
+                if balance > 0u64 {
+                    balance - 1u64
+                } else {
+                    // This should never happen...
+                    runtime::revert(NFTCoreError::FatalTokenIdDuplication);
+                }
+            }
+            None => {
                 // This should never happen...
                 runtime::revert(NFTCoreError::FatalTokenIdDuplication);
             }
-        }
-        None => {
-            // This should never happen...
-            runtime::revert(NFTCoreError::FatalTokenIdDuplication);
-        }
-    };
+        };
     upsert_dictionary_value_from_key(
         TOKEN_COUNTS,
-        &from_token_owner_account_hash.to_string(),
+        &from_owner_item_key,
         updated_from_account_balance,
     );
 
     // Update to_account owned_tokens
-    match get_dictionary_value_from_key::<Vec<U256>>(OWNED_TOKENS, &target_owner_item_key) {
+    match get_token_identifiers_from_dictionary(&identifier_mode, &target_owner_item_key) {
         Some(mut owned_tokens) => {
-            if owned_tokens.iter().any(|id| *id == token_id) {
+            if owned_tokens.iter().any(|id| *id == token_identifier) {
                 runtime::revert(NFTCoreError::FatalTokenIdDuplication)
             } else {
-                owned_tokens.push(token_id);
+                owned_tokens.push(token_identifier.clone());
             }
-            upsert_dictionary_value_from_key(OWNED_TOKENS, &target_owner_item_key, owned_tokens);
+            upsert_token_identifiers(&identifier_mode, &target_owner_item_key, owned_tokens)
+                .unwrap_or_revert();
         }
         None => {
-            let owned_tokens = vec![token_id];
-            upsert_dictionary_value_from_key(OWNED_TOKENS, &target_owner_item_key, owned_tokens);
+            let owned_tokens = vec![token_identifier.clone()];
+            upsert_token_identifiers(&identifier_mode, &target_owner_item_key, owned_tokens)
+                .unwrap_or_revert();
         }
     }
 
     // Update the to_account balance
     let updated_to_account_balance =
-        match get_dictionary_value_from_key::<U256>(TOKEN_COUNTS, &target_owner_item_key) {
-            Some(balance) => balance + U256::one(),
-            None => U256::one(),
+        match get_dictionary_value_from_key::<u64>(TOKEN_COUNTS, &target_owner_item_key) {
+            Some(balance) => balance + 1u64,
+            None => 1u64,
         };
     upsert_dictionary_value_from_key(
         TOKEN_COUNTS,
@@ -699,27 +975,47 @@ pub extern "C" fn transfer() {
         NFTCoreError::InvalidStorageUref,
     );
 
-    storage::dictionary_put(approved_uref, &token_id.to_string(), Option::<Key>::None);
+    storage::dictionary_put(
+        approved_uref,
+        &token_identifier.get_dictionary_item_key(),
+        Option::<Key>::None,
+    );
+
+    let owned_tokens_actual_key = Key::dictionary(
+        get_uref(
+            OWNED_TOKENS,
+            NFTCoreError::MissingOwnedTokens,
+            NFTCoreError::InvalidOwnedTokens,
+        ),
+        target_owner_item_key.as_bytes(),
+    );
+
+    let receipt_name = get_stored_value_with_user_errors::<String>(
+        RECEIPT_NAME,
+        NFTCoreError::MissingReceiptName,
+        NFTCoreError::InvalidReceiptName,
+    );
+
+    let receipt = CLValue::from_t((receipt_name, owned_tokens_actual_key))
+        .unwrap_or_revert_with(NFTCoreError::FailedToConvertToCLValue);
+    runtime::ret(receipt)
 }
 
-// Returns the length of the Vec<U256> in OWNED_TOKENS dictionary. If key is not found
+// Returns the length of the Vec<String> in OWNED_TOKENS dictionary. If key is not found
 // it returns 0.
 #[no_mangle]
 pub extern "C" fn balance_of() {
-    let account_key = get_named_arg_with_user_errors::<Key>(
+    let owner_key = get_named_arg_with_user_errors::<Key>(
         ARG_TOKEN_OWNER,
         NFTCoreError::MissingAccountHash,
         NFTCoreError::InvalidAccountHash,
     )
-    .unwrap_or_revert()
-    .into_account()
-    .unwrap_or_revert_with(NFTCoreError::InvalidKey)
-    .to_string();
+    .unwrap_or_revert();
 
-    let balance = match get_dictionary_value_from_key(TOKEN_COUNTS, &account_key) {
-        Some(balance) => balance,
-        None => U256::zero(),
-    };
+    let owner_key_item_string = get_owned_tokens_dictionary_item_key(owner_key);
+
+    let balance =
+        get_dictionary_value_from_key::<u64>(TOKEN_COUNTS, &owner_key_item_string).unwrap_or(0u64);
 
     let balance_cl_value =
         CLValue::from_t(balance).unwrap_or_revert_with(NFTCoreError::FailedToConvertToCLValue);
@@ -728,30 +1024,39 @@ pub extern "C" fn balance_of() {
 
 #[no_mangle]
 pub extern "C" fn owner_of() {
-    let token_id = get_named_arg_with_user_errors::<U256>(
-        ARG_TOKEN_ID,
-        NFTCoreError::MissingTokenID,
-        NFTCoreError::InvalidTokenID,
+    let identifier_mode: NFTIdentifierMode = get_stored_value_with_user_errors::<u8>(
+        IDENTIFIER_MODE,
+        NFTCoreError::MissingIdentifierMode,
+        NFTCoreError::InvalidIdentifierMode,
     )
+    .try_into()
     .unwrap_or_revert();
 
-    let number_of_minted_tokens = get_stored_value_with_user_errors::<U256>(
+    let token_identifier = get_token_identifier_from_runtime_args(&identifier_mode);
+
+    let number_of_minted_tokens = get_stored_value_with_user_errors::<u64>(
         NUMBER_OF_MINTED_TOKENS,
         NFTCoreError::MissingNumberOfMintedTokens,
         NFTCoreError::InvalidNumberOfMintedTokens,
     );
 
-    // Check if token_id is out of bounds.
-    if token_id >= number_of_minted_tokens {
-        runtime::revert(NFTCoreError::InvalidTokenID);
+    if let NFTIdentifierMode::Ordinal = identifier_mode {
+        // Revert if token_id is out of bounds
+        if token_identifier.get_index().unwrap_or_revert() >= number_of_minted_tokens {
+            runtime::revert(NFTCoreError::InvalidTokenID);
+        }
     }
 
-    let maybe_token_owner =
-        get_dictionary_value_from_key::<Key>(TOKEN_OWNERS, &token_id.to_string());
+    let maybe_token_owner = get_dictionary_value_from_key::<Key>(
+        TOKEN_OWNERS,
+        &token_identifier.get_dictionary_item_key(),
+    );
 
     let token_owner = match maybe_token_owner {
         Some(token_owner) => token_owner,
-        None => runtime::revert(NFTCoreError::InvalidTokenID), // If a token does not have an owner it could not have been minted.
+        None => runtime::revert(NFTCoreError::InvalidTokenID), /* If a token does not have an
+                                                                * owner it could not have been
+                                                                * minted. */
     };
 
     let token_owner_cl_value =
@@ -762,31 +1067,45 @@ pub extern "C" fn owner_of() {
 
 #[no_mangle]
 pub extern "C" fn metadata() {
-    let token_id = get_named_arg_with_user_errors::<U256>(
-        ARG_TOKEN_ID,
-        NFTCoreError::MissingTokenID,
-        NFTCoreError::InvalidTokenID,
-    )
-    .unwrap_or_revert();
-
-    let number_of_minted_tokens = get_stored_value_with_user_errors::<U256>(
+    let number_of_minted_tokens = get_stored_value_with_user_errors::<u64>(
         NUMBER_OF_MINTED_TOKENS,
         NFTCoreError::MissingNumberOfMintedTokens,
         NFTCoreError::InvalidNumberOfMintedTokens,
     );
 
-    // Check if token_id is out of bounds.
-    if token_id >= number_of_minted_tokens {
-        runtime::revert(NFTCoreError::InvalidTokenID);
+    let identifier_mode: NFTIdentifierMode = get_stored_value_with_user_errors::<u8>(
+        IDENTIFIER_MODE,
+        NFTCoreError::MissingIdentifierMode,
+        NFTCoreError::InvalidIdentifierMode,
+    )
+    .try_into()
+    .unwrap_or_revert();
+
+    let token_identifier = get_token_identifier_from_runtime_args(&identifier_mode);
+
+    if let NFTIdentifierMode::Ordinal = identifier_mode {
+        // Revert if token_id is out of bounds
+        if token_identifier.get_index().unwrap_or_revert() >= number_of_minted_tokens {
+            runtime::revert(NFTCoreError::InvalidTokenID);
+        }
     }
 
-    let maybe_token_metadata =
-        get_dictionary_value_from_key::<String>(TOKEN_META_DATA, &token_id.to_string());
+    let metadata_kind: NFTMetadataKind = get_stored_value_with_user_errors::<u8>(
+        METADATA_SCHEMA,
+        NFTCoreError::MissingNFTMetadataKind,
+        NFTCoreError::InvalidNFTMetadataKind,
+    )
+    .try_into()
+    .unwrap_or_revert();
+
+    let maybe_token_metadata = get_dictionary_value_from_key::<String>(
+        &get_metadata_dictionary_name(&metadata_kind),
+        &token_identifier.get_dictionary_item_key(),
+    );
 
     if let Some(metadata) = maybe_token_metadata {
         let metadata_cl_value =
             CLValue::from_t(metadata).unwrap_or_revert_with(NFTCoreError::FailedToConvertToCLValue);
-
         runtime::ret(metadata_cl_value);
     } else {
         runtime::revert(NFTCoreError::InvalidTokenID)
@@ -796,35 +1115,47 @@ pub extern "C" fn metadata() {
 // Returns approved account_hash from token_id, throws error if token id is not valid
 #[no_mangle]
 pub extern "C" fn get_approved() {
-    let token_id = get_named_arg_with_user_errors::<U256>(
-        ARG_TOKEN_ID,
-        NFTCoreError::MissingTokenID,
-        NFTCoreError::InvalidTokenID,
+    let identifier_mode: NFTIdentifierMode = get_stored_value_with_user_errors::<u8>(
+        IDENTIFIER_MODE,
+        NFTCoreError::MissingIdentifierMode,
+        NFTCoreError::InvalidIdentifierMode,
     )
+    .try_into()
     .unwrap_or_revert();
 
-    // Revert if already burnt
-    if get_dictionary_value_from_key::<()>(BURNT_TOKENS, &token_id.to_string()).is_some() {
-        runtime::revert(NFTCoreError::PreviouslyBurntToken);
-    }
+    let token_identifier = get_token_identifier_from_runtime_args(&identifier_mode);
 
     // Revert if token_id is out of bounds.
-    let number_of_minted_tokens = get_stored_value_with_user_errors::<U256>(
+    let number_of_minted_tokens = get_stored_value_with_user_errors::<u64>(
         NUMBER_OF_MINTED_TOKENS,
         NFTCoreError::MissingNumberOfMintedTokens,
         NFTCoreError::InvalidNumberOfMintedTokens,
     );
 
-    // Check if token_id is out of bounds.
-    if token_id >= number_of_minted_tokens {
-        runtime::revert(NFTCoreError::InvalidTokenID);
+    if let NFTIdentifierMode::Ordinal = identifier_mode {
+        // Revert if token_id is out of bounds
+        if token_identifier.get_index().unwrap_or_revert() >= number_of_minted_tokens {
+            runtime::revert(NFTCoreError::InvalidTokenID);
+        }
     }
 
-    let maybe_approved =
-        match get_dictionary_value_from_key::<Option<Key>>(OPERATOR, &token_id.to_string()) {
-            Some(maybe_approved) => maybe_approved,
-            None => None,
-        };
+    // Revert if already burnt
+    if get_dictionary_value_from_key::<()>(
+        BURNT_TOKENS,
+        &token_identifier.get_dictionary_item_key(),
+    )
+    .is_some()
+    {
+        runtime::revert(NFTCoreError::PreviouslyBurntToken);
+    }
+
+    let maybe_approved = match get_dictionary_value_from_key::<Option<Key>>(
+        OPERATOR,
+        &token_identifier.get_dictionary_item_key(),
+    ) {
+        Some(maybe_approved) => maybe_approved,
+        None => None,
+    };
 
     let approved_cl_value = CLValue::from_t(maybe_approved)
         .unwrap_or_revert_with(NFTCoreError::FailedToConvertToCLValue);
@@ -837,9 +1168,10 @@ fn install_nft_contract() -> (ContractHash, ContractVersion) {
         let mut entry_points = EntryPoints::new();
 
         // This entrypoint initializes the contract and is required to be called during the session
-        // where the contract is installed; immediately after the contract has been installed but before
-        // exiting session. All parameters are required.
-        // This entrypoint is intended to be called exactly once and will error if called more than once.
+        // where the contract is installed; immediately after the contract has been installed but
+        // before exiting session. All parameters are required.
+        // This entrypoint is intended to be called exactly once and will error if called more than
+        // once.
         let init_contract = EntryPoint::new(
             ENTRY_POINT_INIT,
             vec![
@@ -850,16 +1182,24 @@ fn install_nft_contract() -> (ContractHash, ContractVersion) {
                 Parameter::new(ARG_MINTING_MODE, CLType::U8),
                 Parameter::new(ARG_OWNERSHIP_MODE, CLType::U8),
                 Parameter::new(ARG_NFT_KIND, CLType::U8),
+                Parameter::new(ARG_HOLDER_MODE, CLType::U8),
+                Parameter::new(ARG_WHITELIST_MODE, CLType::U8),
+                Parameter::new(
+                    ARG_CONTRACT_WHITELIST,
+                    CLType::List(Box::new(CLType::ByteArray(32u32))),
+                ),
                 Parameter::new(ARG_JSON_SCHEMA, CLType::String),
+                Parameter::new(ARG_RECEIPT_NAME, CLType::String),
+                Parameter::new(ARG_IDENTIFIER_MODE, CLType::U8),
             ],
             CLType::Unit,
             EntryPointAccess::Public,
             EntryPointType::Contract,
         );
 
-        // This entrypoint exposes all variables that can be changed by managing account post installation.
-        // Meant to be called by the managing account (INSTALLER) post installation
-        // if a variable needs to be changed. Each parameter of the entrypoint
+        // This entrypoint exposes all variables that can be changed by managing account post
+        // installation. Meant to be called by the managing account (INSTALLER) post
+        // installation if a variable needs to be changed. Each parameter of the entrypoint
         // should only be passed if that variable is changed.
         // For instance if the allow_minting variable is being changed and nothing else
         // the managing account would send the new allow_minting value as the only argument.
@@ -868,7 +1208,13 @@ fn install_nft_contract() -> (ContractHash, ContractVersion) {
         // By switching allow_minting to false we pause minting.
         let set_variables = EntryPoint::new(
             ENTRY_POINT_SET_VARIABLES,
-            vec![Parameter::new(ARG_ALLOW_MINTING, CLType::Bool)],
+            vec![
+                Parameter::new(ARG_ALLOW_MINTING, CLType::Bool),
+                Parameter::new(
+                    ARG_CONTRACT_WHITELIST,
+                    CLType::List(Box::new(CLType::ByteArray(32u32))),
+                ),
+            ],
             CLType::Unit,
             EntryPointAccess::Public,
             EntryPointType::Contract,
@@ -877,51 +1223,56 @@ fn install_nft_contract() -> (ContractHash, ContractVersion) {
         // This entrypoint mints a new token with provided metadata.
         // Meant to be called post installation.
         // Reverts with MintingIsPaused error if allow_minting is false.
-        // When a token is minted the calling account is listed as its owner and the token is automatically
-        // assigned an U256 ID equal to the current number_of_minted_tokens.
+        // When a token is minted the calling account is listed as its owner and the token is
+        // automatically assigned an U64 ID equal to the current number_of_minted_tokens.
         // Before minting the token the entrypoint checks if number_of_minted_tokens
-        // exceed the total_token_supply. If so, it reverts the minting with an error TokenSupplyDepleted.
-        // The mint entrypoint also checks whether the calling account is the managing account (the installer)
-        // If not, and if public_minting is set to false, it reverts with the error InvalidAccount.
-        // The newly minted token is automatically assigned a U256 ID equal to the current number_of_minted_tokens.
-        // The account is listed as the token owner, as well as added to the accounts list of owned tokens.
-        // After minting is successful the number_of_minted_tokens is incremented by one.
+        // exceed the total_token_supply. If so, it reverts the minting with an error
+        // TokenSupplyDepleted. The mint entrypoint also checks whether the calling account
+        // is the managing account (the installer) If not, and if public_minting is set to
+        // false, it reverts with the error InvalidAccount. The newly minted token is
+        // automatically assigned a U64 ID equal to the current number_of_minted_tokens. The
+        // account is listed as the token owner, as well as added to the accounts list of owned
+        // tokens. After minting is successful the number_of_minted_tokens is incremented by
+        // one.
         let mint = EntryPoint::new(
             ENTRY_POINT_MINT,
             vec![
                 Parameter::new(ARG_TOKEN_OWNER, CLType::Key),
                 Parameter::new(ARG_TOKEN_META_DATA, CLType::String),
-                Parameter::new(ARG_TOKEN_URI, CLType::String),
             ],
             CLType::Unit,
             EntryPointAccess::Public,
             EntryPointType::Contract,
         );
 
-        // This entrypoint burns the token with provided token_id argument, after which it is no longer
-        // possible to transfer it.
-        // Looks up the owner of the supplied token_id arg. If caller is not owner we revert with error
-        // InvalidTokenOwner. If token id is invalid (e.g. out of bounds) it reverts with error  InvalidTokenID.
-        // If token is listed as already burnt we revert with error PreviouslyBurntTOken. If not the token is then
-        // registered as burnt.
+        // This entrypoint burns the token with provided token_id argument, after which it is no
+        // longer possible to transfer it.
+        // Looks up the owner of the supplied token_id arg. If caller is not owner we revert with
+        // error InvalidTokenOwner. If token id is invalid (e.g. out of bounds) it reverts
+        // with error  InvalidTokenID. If token is listed as already burnt we revert with
+        // error PreviouslyBurntTOken. If not the token is then registered as burnt.
         let burn = EntryPoint::new(
             ENTRY_POINT_BURN,
-            vec![Parameter::new(ARG_TOKEN_ID, CLType::U256)],
+            vec![
+                Parameter::new(ARG_TOKEN_ID, CLType::U64),
+                Parameter::new(ARG_TOKEN_HASH, CLType::String),
+            ],
             CLType::Unit,
             EntryPointAccess::Public,
             EntryPointType::Contract,
         );
 
         // This entrypoint transfers ownership of token from one account to another.
-        // It looks up the owner of the supplied token_id arg. Revert if token is already burnt, token_id
-        // is unvalid, or if caller is not owner and not approved operator.
+        // It looks up the owner of the supplied token_id arg. Revert if token is already burnt,
+        // token_id is unvalid, or if caller is not owner and not approved operator.
         // If token id is invalid it reverts with error InvalidTokenID.
         let transfer = EntryPoint::new(
             ENTRY_POINT_TRANSFER,
             vec![
-                Parameter::new(ARG_TOKEN_ID, CLType::U256),
-                Parameter::new(ARG_FROM_ACCOUNT_HASH, CLType::Key),
-                Parameter::new(ARG_TO_ACCOUNT_HASH, CLType::Key),
+                Parameter::new(ARG_TOKEN_ID, CLType::U64),
+                Parameter::new(ARG_TOKEN_HASH, CLType::String),
+                Parameter::new(ARG_SOURCE_KEY, CLType::Key),
+                Parameter::new(ARG_TARGET_KEY, CLType::Key),
             ],
             CLType::Unit,
             EntryPointAccess::Public,
@@ -934,7 +1285,8 @@ fn install_nft_contract() -> (ContractHash, ContractVersion) {
         let approve = EntryPoint::new(
             ENTRY_POINT_APPROVE,
             vec![
-                Parameter::new(ARG_TOKEN_ID, CLType::U256),
+                Parameter::new(ARG_TOKEN_ID, CLType::U64),
+                Parameter::new(ARG_TOKEN_HASH, CLType::String),
                 Parameter::new(ARG_OPERATOR, CLType::Key),
             ],
             CLType::Unit,
@@ -954,7 +1306,10 @@ fn install_nft_contract() -> (ContractHash, ContractVersion) {
         // is invalid. A burnt token still has an associated owner.
         let owner_of = EntryPoint::new(
             ENTRY_POINT_OWNER_OF,
-            vec![Parameter::new(ARG_TOKEN_ID, CLType::U256)],
+            vec![
+                Parameter::new(ARG_TOKEN_ID, CLType::U64),
+                Parameter::new(ARG_TOKEN_HASH, CLType::String),
+            ],
             CLType::Key,
             EntryPointAccess::Public,
             EntryPointType::Contract,
@@ -964,7 +1319,10 @@ fn install_nft_contract() -> (ContractHash, ContractVersion) {
         // Reverts if token has been burnt.
         let get_approved = EntryPoint::new(
             ENTRY_POINT_GET_APPROVED,
-            vec![Parameter::new(ARG_TOKEN_ID, CLType::U256)],
+            vec![
+                Parameter::new(ARG_TOKEN_ID, CLType::U64),
+                Parameter::new(ARG_TOKEN_HASH, CLType::String),
+            ],
             CLType::Option(Box::new(CLType::Key)),
             EntryPointAccess::Public,
             EntryPointType::Contract,
@@ -974,7 +1332,7 @@ fn install_nft_contract() -> (ContractHash, ContractVersion) {
         let balance_of = EntryPoint::new(
             ENTRY_POINT_BALANCE_OF,
             vec![Parameter::new(ARG_TOKEN_OWNER, CLType::Key)],
-            CLType::U256,
+            CLType::U64,
             EntryPointAccess::Public,
             EntryPointType::Contract,
         );
@@ -982,7 +1340,10 @@ fn install_nft_contract() -> (ContractHash, ContractVersion) {
         // This entrypoint returns the metadata associated with the provided token_id
         let metadata = EntryPoint::new(
             ENTRY_POINT_METADATA,
-            vec![Parameter::new(ARG_TOKEN_ID, CLType::U256)],
+            vec![
+                Parameter::new(ARG_TOKEN_ID, CLType::U64),
+                Parameter::new(ARG_TOKEN_HASH, CLType::String),
+            ],
             CLType::String,
             EntryPointAccess::Public,
             EntryPointType::Contract,
@@ -1042,7 +1403,7 @@ pub extern "C" fn call() {
     // This represents the total number of NFTs that will
     // be minted by a specific instance of a contract.
     // This value cannot be changed after installation.
-    let total_token_supply: U256 = get_named_arg_with_user_errors(
+    let total_token_supply: u64 = get_named_arg_with_user_errors(
         ARG_TOTAL_TOKEN_SUPPLY,
         NFTCoreError::MissingTotalTokenSupply,
         NFTCoreError::InvalidTotalTokenSupply,
@@ -1086,6 +1447,44 @@ pub extern "C" fn call() {
     )
     .unwrap_or_revert();
 
+    // Represents whether Accounts or Contracts, or both can hold NFTs for
+    // a given contract instance. Refer to the enum `NFTHolderMode`
+    // in the `src/utils.rs` file for details.
+    // This value cannot be changed after installation
+    let holder_mode: u8 =
+        get_optional_named_arg_with_user_errors(ARG_HOLDER_MODE, NFTCoreError::InvalidHolderMode)
+            .unwrap_or_revert_with(NFTCoreError::InvalidHolderMode);
+
+    // Represents whether a given contract whitelist can be modified
+    // for a given NFT contract instance. If not provided as an argument
+    // it will default to unlocked.
+    // This value cannot be changed after installation
+    let whitelist_lock: u8 = get_optional_named_arg_with_user_errors(
+        ARG_WHITELIST_MODE,
+        NFTCoreError::InvalidWhitelistMode,
+    )
+    .unwrap_or(0u8);
+
+    // A whitelist of contract hashes specifying which contracts can mint
+    // NFTs in the contract holder mode with restricted minting.
+    // This value can only be modified if the whitelist lock is
+    // set to be unlocked.
+    let contract_white_list: Vec<ContractHash> = get_optional_named_arg_with_user_errors(
+        ARG_CONTRACT_WHITELIST,
+        NFTCoreError::InvalidContractWhitelist,
+    )
+    .unwrap_or_default();
+
+    // Represents the schema for the metadata for a given NFT contract instance.
+    // Refer to the `NFTMetadataKind` enum in src/utils for details.
+    // This value cannot be changed after installation.
+    let nft_metadata_kind: u8 = get_named_arg_with_user_errors(
+        ARG_NFT_METADATA_KIND,
+        NFTCoreError::MissingNFTMetadataKind,
+        NFTCoreError::InvalidNFTMetadataKind,
+    )
+    .unwrap_or_revert();
+
     // The JSON schema representation of the NFT which will be minted.
     // This value cannot be changed after installation.
     let json_schema: String = get_named_arg_with_user_errors(
@@ -1095,12 +1494,38 @@ pub extern "C" fn call() {
     )
     .unwrap_or_revert();
 
+    // Represents whether NFTs minted by a given contract will be identified
+    // by an ordinal u64 index or a base16 encoded SHA256 hash of an NFTs metadata.
+    // This value cannot be changed after installation.
+    let identifier_mode: u8 = get_named_arg_with_user_errors(
+        ARG_IDENTIFIER_MODE,
+        NFTCoreError::MissingIdentifierMode,
+        NFTCoreError::InvalidIdentifierMode,
+    )
+    .unwrap_or_revert();
+
     let (contract_hash, contract_version) = install_nft_contract();
 
     // Store contract_hash and contract_version under the keys CONTRACT_NAME and CONTRACT_VERSION
     runtime::put_key(CONTRACT_NAME, contract_hash.into());
     runtime::put_key(CONTRACT_VERSION, storage::new_uref(contract_version).into());
 
+    let package_hash: ContractPackageHash = runtime::get_key(HASH_KEY_NAME)
+        .unwrap_or_revert()
+        .into_hash()
+        .map(ContractPackageHash::new)
+        .unwrap();
+
+    // A sentinel string value which represents the entry for the addition
+    // of a read only reference to the NFTs owned by the calling `Account` or `Contract`
+    // This allows for users to look up a set of named keys and correctly identify
+    // the contract package from which the NFTs were obtained.
+    let receipt_name = format!(
+        "nft-{}-{}",
+        collection_name,
+        package_hash.to_formatted_string()
+    );
+
     // Call contract to initialize it
     runtime::call_contract::<()>(
         contract_hash,
@@ -1113,7 +1538,13 @@ pub extern "C" fn call() {
              ARG_OWNERSHIP_MODE => ownership_mode,
              ARG_NFT_KIND => nft_kind,
              ARG_MINTING_MODE => minting_mode,
+             ARG_HOLDER_MODE => holder_mode,
+             ARG_WHITELIST_MODE => whitelist_lock,
+             ARG_CONTRACT_WHITELIST => contract_white_list,
              ARG_JSON_SCHEMA => json_schema,
+             ARG_RECEIPT_NAME => receipt_name,
+             ARG_NFT_METADATA_KIND => nft_metadata_kind,
+             ARG_IDENTIFIER_MODE => identifier_mode
         },
     );
 }
diff --git a/contract/src/utils.rs b/contract/src/utils.rs
index f4f7b82a..c9619a83 100644
--- a/contract/src/utils.rs
+++ b/contract/src/utils.rs
@@ -1,23 +1,34 @@
-use core::{convert::TryInto, mem::MaybeUninit};
+use alloc::{
+    borrow::ToOwned,
+    collections::BTreeMap,
+    string::{String, ToString},
+    vec,
+    vec::Vec,
+};
 
-use alloc::{vec, vec::Vec};
 use casper_contract::{
-    contract_api::{self, runtime, storage},
+    contract_api::{self, runtime, runtime::revert, storage},
     ext_ffi,
     unwrap_or_revert::UnwrapOrRevert,
 };
 use casper_types::{
     account::AccountHash,
     api_error,
-    bytesrepr::{self, FromBytes, ToBytes},
-    ApiError, CLTyped, ContractHash, Key, URef,
+    bytesrepr::{self, Error, FromBytes, ToBytes},
+    ApiError, CLType, CLTyped, ContractHash, Key, URef,
 };
-
 use core::convert::TryFrom;
 
-use crate::{constants::OWNERSHIP_MODE, error::NFTCoreError};
+use casper_types::system::CallStackElement;
+use core::{convert::TryInto, mem::MaybeUninit};
+
+use serde::{Deserialize, Serialize};
 
-const _CONTRACT_WHITELIST: &str = "contract_whitelist";
+use crate::{
+    constants::OWNERSHIP_MODE, error::NFTCoreError, ARG_JSON_SCHEMA, ARG_TOKEN_HASH, ARG_TOKEN_ID,
+    HOLDER_MODE, METADATA_CEP78, METADATA_CUSTOM_VALIDATED, METADATA_NFT721, METADATA_RAW,
+    OWNED_TOKENS,
+};
 
 pub(crate) fn upsert_dictionary_value_from_key<T: CLTyped + FromBytes + ToBytes>(
     dictionary_name: &str,
@@ -30,7 +41,6 @@ pub(crate) fn upsert_dictionary_value_from_key<T: CLTyped + FromBytes + ToBytes>
         NFTCoreError::InvalidStorageUref,
     );
 
-    // TODO: Write a test for this upsert method.
     match storage::dictionary_get::<T>(seed_uref, key) {
         Ok(None | Some(_)) => storage::dictionary_put(seed_uref, key, value),
         Err(error) => runtime::revert(error),
@@ -38,6 +48,7 @@ pub(crate) fn upsert_dictionary_value_from_key<T: CLTyped + FromBytes + ToBytes>
 }
 
 #[repr(u8)]
+#[derive(PartialEq)]
 pub enum WhitelistMode {
     Unlocked = 0,
     Locked = 1,
@@ -56,9 +67,11 @@ impl TryFrom<u8> for WhitelistMode {
 }
 
 #[repr(u8)]
+#[derive(PartialEq, Clone, Copy)]
 pub enum NFTHolderMode {
     Accounts = 0,
     Contracts = 1,
+    Mixed = 2,
 }
 
 impl TryFrom<u8> for NFTHolderMode {
@@ -68,6 +81,7 @@ impl TryFrom<u8> for NFTHolderMode {
         match value {
             0 => Ok(NFTHolderMode::Accounts),
             1 => Ok(NFTHolderMode::Contracts),
+            2 => Ok(NFTHolderMode::Mixed),
             _ => Err(NFTCoreError::InvalidHolderMode),
         }
     }
@@ -97,14 +111,14 @@ impl TryFrom<u8> for MintingMode {
 pub enum NFTKind {
     /// The NFT represents a real-world physical
     /// like a house.
-    Physical,
+    Physical = 0,
     /// The NFT represents a digital asset like a unique
     /// JPEG or digital art.
-    Digital,
+    Digital = 1,
     /// The NFT is the virtual representation
     /// of a physical notion, e.g a patent
     /// or copyright.
-    Virtual,
+    Virtual = 2,
 }
 
 impl TryFrom<u8> for NFTKind {
@@ -120,6 +134,28 @@ impl TryFrom<u8> for NFTKind {
     }
 }
 
+#[repr(u8)]
+pub enum NFTMetadataKind {
+    CEP78 = 0,
+    NFT721 = 1,
+    Raw = 2,
+    CustomValidated = 3,
+}
+
+impl TryFrom<u8> for NFTMetadataKind {
+    type Error = NFTCoreError;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            0 => Ok(NFTMetadataKind::CEP78),
+            1 => Ok(NFTMetadataKind::NFT721),
+            2 => Ok(NFTMetadataKind::Raw),
+            3 => Ok(NFTMetadataKind::CustomValidated),
+            _ => Err(NFTCoreError::InvalidNFTMetadataKind),
+        }
+    }
+}
+
 #[repr(u8)]
 pub enum OwnershipMode {
     /// The minter owns it and can never transfer it.
@@ -128,8 +164,6 @@ pub enum OwnershipMode {
     Assigned = 1,
     /// The NFT can be transferred even to an recipient that does not exist.
     Transferable = 2,
-    // TODO
-    // Platform = 3,
 }
 
 impl TryFrom<u8> for OwnershipMode {
@@ -145,6 +179,24 @@ impl TryFrom<u8> for OwnershipMode {
     }
 }
 
+#[repr(u8)]
+pub enum NFTIdentifierMode {
+    Ordinal = 0,
+    Hash = 1,
+}
+
+impl TryFrom<u8> for NFTIdentifierMode {
+    type Error = NFTCoreError;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            0 => Ok(NFTIdentifierMode::Ordinal),
+            1 => Ok(NFTIdentifierMode::Hash),
+            _ => Err(NFTCoreError::InvalidIdentifierMode),
+        }
+    }
+}
+
 pub(crate) fn get_ownership_mode() -> Result<OwnershipMode, NFTCoreError> {
     get_stored_value_with_user_errors::<u8>(
         OWNERSHIP_MODE,
@@ -154,6 +206,23 @@ pub(crate) fn get_ownership_mode() -> Result<OwnershipMode, NFTCoreError> {
     .try_into()
 }
 
+pub(crate) fn get_holder_mode() -> Result<NFTHolderMode, NFTCoreError> {
+    get_stored_value_with_user_errors::<u8>(
+        HOLDER_MODE,
+        NFTCoreError::MissingHolderMode,
+        NFTCoreError::InvalidHolderMode,
+    )
+    .try_into()
+}
+
+pub(crate) fn get_owned_tokens_dictionary_item_key(token_owner_key: Key) -> String {
+    match token_owner_key {
+        Key::Account(token_owner_account_hash) => token_owner_account_hash.to_string(),
+        Key::Hash(token_owner_hash_addr) => ContractHash::new(token_owner_hash_addr).to_string(),
+        _ => revert(NFTCoreError::InvalidKey),
+    }
+}
+
 pub(crate) fn get_dictionary_value_from_key<T: CLTyped + FromBytes>(
     dictionary_name: &str,
     key: &str,
@@ -349,19 +418,398 @@ pub(crate) fn to_ptr<T: ToBytes>(t: T) -> (*const u8, usize, Vec<u8>) {
     (ptr, size, bytes)
 }
 
-pub(crate) fn _get_calling_contract_hash() -> ContractHash {
-    let contract_hash = *runtime::get_call_stack()
-        .pop()
+pub(crate) fn get_verified_caller() -> Result<Key, NFTCoreError> {
+    let holder_mode = get_holder_mode()?;
+    match *runtime::get_call_stack()
+        .iter()
+        .nth_back(1)
+        .to_owned()
         .unwrap_or_revert()
-        .contract_hash()
-        .unwrap_or_revert();
-    let whitelist = get_stored_value_with_user_errors::<Vec<ContractHash>>(
-        _CONTRACT_WHITELIST,
-        NFTCoreError::MissingContractWhiteList,
-        NFTCoreError::InvalidContractWhitelist,
-    );
-    if !whitelist.contains(&contract_hash) {
-        runtime::revert(NFTCoreError::UnlistedContractHash)
+    {
+        CallStackElement::Session {
+            account_hash: calling_account_hash,
+        } => {
+            if let NFTHolderMode::Contracts = holder_mode {
+                return Err(NFTCoreError::InvalidHolderMode);
+            }
+            Ok(Key::Account(calling_account_hash))
+        }
+        CallStackElement::StoredSession { contract_hash, .. }
+        | CallStackElement::StoredContract { contract_hash, .. } => {
+            if let NFTHolderMode::Accounts = holder_mode {
+                return Err(NFTCoreError::InvalidHolderMode);
+            }
+            Ok(contract_hash.into())
+        }
+    }
+}
+
+#[derive(PartialEq, Clone)]
+pub(crate) enum TokenIdentifier {
+    Index(u64),
+    Hash(String),
+}
+
+impl TokenIdentifier {
+    pub(crate) fn new_index(index: u64) -> Self {
+        TokenIdentifier::Index(index)
+    }
+
+    pub(crate) fn new_hash(hash: String) -> Self {
+        TokenIdentifier::Hash(hash)
+    }
+
+    pub(crate) fn get_index(&self) -> Option<u64> {
+        if let Self::Index(index) = self {
+            return Some(*index);
+        }
+        None
+    }
+
+    pub(crate) fn get_hash(self) -> Option<String> {
+        if let Self::Hash(hash) = self {
+            return Some(hash);
+        }
+        None
+    }
+
+    pub(crate) fn get_dictionary_item_key(&self) -> String {
+        match self {
+            TokenIdentifier::Index(token_index) => token_index.to_string(),
+            TokenIdentifier::Hash(hash) => hash.clone(),
+        }
+    }
+}
+
+pub(crate) fn get_token_identifier_from_runtime_args(
+    identifier_mode: &NFTIdentifierMode,
+) -> TokenIdentifier {
+    match identifier_mode {
+        NFTIdentifierMode::Ordinal => get_named_arg_with_user_errors::<u64>(
+            ARG_TOKEN_ID,
+            NFTCoreError::MissingTokenID,
+            NFTCoreError::InvalidTokenID,
+        )
+        .map(TokenIdentifier::new_index)
+        .unwrap_or_revert(),
+        NFTIdentifierMode::Hash => get_named_arg_with_user_errors::<String>(
+            ARG_TOKEN_HASH,
+            NFTCoreError::MissingTokenID,
+            NFTCoreError::InvalidTokenID,
+        )
+        .map(TokenIdentifier::new_hash)
+        .unwrap_or_revert(),
+    }
+}
+
+pub(crate) fn get_token_identifiers_from_dictionary(
+    identifier_mode: &NFTIdentifierMode,
+    owners_item_key: &str,
+) -> Option<Vec<TokenIdentifier>> {
+    match identifier_mode {
+        NFTIdentifierMode::Ordinal => {
+            get_dictionary_value_from_key::<Vec<u64>>(OWNED_TOKENS, owners_item_key).map(
+                |token_indices| {
+                    token_indices
+                        .into_iter()
+                        .map(TokenIdentifier::new_index)
+                        .collect()
+                },
+            )
+        }
+        NFTIdentifierMode::Hash => {
+            get_dictionary_value_from_key::<Vec<String>>(OWNED_TOKENS, owners_item_key).map(
+                |token_hashes| {
+                    token_hashes
+                        .into_iter()
+                        .map(TokenIdentifier::new_hash)
+                        .collect()
+                },
+            )
+        }
+    }
+}
+
+pub(crate) fn upsert_token_identifiers(
+    identifier_mode: &NFTIdentifierMode,
+    owners_item_key: &str,
+    token_identifiers: Vec<TokenIdentifier>,
+) -> Result<(), NFTCoreError> {
+    match identifier_mode {
+        NFTIdentifierMode::Ordinal => {
+            let token_indices: Vec<u64> = token_identifiers
+                .into_iter()
+                .map(|token_identifier| {
+                    token_identifier
+                        .get_index()
+                        .unwrap_or_revert_with(NFTCoreError::InvalidIdentifierMode)
+                })
+                .collect();
+            upsert_dictionary_value_from_key(OWNED_TOKENS, owners_item_key, token_indices);
+            Ok(())
+        }
+        NFTIdentifierMode::Hash => {
+            let token_hashes: Vec<String> = token_identifiers
+                .into_iter()
+                .map(|token_identifier| {
+                    token_identifier
+                        .get_hash()
+                        .unwrap_or_revert_with(NFTCoreError::InvalidIdentifierMode)
+                })
+                .collect();
+            upsert_dictionary_value_from_key(OWNED_TOKENS, owners_item_key, token_hashes);
+            Ok(())
+        }
+    }
+}
+
+// Metadata mutability is different from schema mutability.
+#[derive(Serialize, Deserialize, Clone)]
+pub(crate) struct MetadataSchemaProperty {
+    name: String,
+    description: String,
+    required: bool,
+}
+
+impl ToBytes for MetadataSchemaProperty {
+    fn to_bytes(&self) -> Result<Vec<u8>, Error> {
+        let mut result = bytesrepr::allocate_buffer(self)?;
+        result.extend(self.name.to_bytes()?);
+        result.extend(self.description.to_bytes()?);
+        result.extend(self.required.to_bytes()?);
+        Ok(result)
+    }
+
+    fn serialized_length(&self) -> usize {
+        self.name.serialized_length()
+            + self.description.serialized_length()
+            + self.required.serialized_length()
+    }
+}
+
+impl FromBytes for MetadataSchemaProperty {
+    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
+        let (name, remainder) = String::from_bytes(bytes)?;
+        let (description, remainder) = String::from_bytes(remainder)?;
+        let (required, remainder) = bool::from_bytes(remainder)?;
+        let metadata_schema_property = MetadataSchemaProperty {
+            name,
+            description,
+            required,
+        };
+        Ok((metadata_schema_property, remainder))
     }
-    contract_hash
+}
+
+impl CLTyped for MetadataSchemaProperty {
+    fn cl_type() -> CLType {
+        CLType::Any
+    }
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub(crate) struct CustomMetadataSchema {
+    properties: BTreeMap<String, MetadataSchemaProperty>,
+}
+
+pub(crate) fn get_metadata_schema(kind: &NFTMetadataKind) -> CustomMetadataSchema {
+    match kind {
+        NFTMetadataKind::Raw => CustomMetadataSchema {
+            properties: BTreeMap::new(),
+        },
+        NFTMetadataKind::NFT721 => {
+            let mut properties = BTreeMap::new();
+            properties.insert(
+                "name".to_string(),
+                MetadataSchemaProperty {
+                    name: "name".to_string(),
+                    description: "The name of the NFT".to_string(),
+                    required: true,
+                },
+            );
+            properties.insert(
+                "symbol".to_string(),
+                MetadataSchemaProperty {
+                    name: "symbol".to_string(),
+                    description: "The symbol of the NFT collection".to_string(),
+                    required: true,
+                },
+            );
+            properties.insert(
+                "token_uri".to_string(),
+                MetadataSchemaProperty {
+                    name: "token_uri".to_string(),
+                    description: "The URI pointing to an off chain resource".to_string(),
+                    required: true,
+                },
+            );
+            CustomMetadataSchema { properties }
+        }
+        NFTMetadataKind::CEP78 => {
+            let mut properties = BTreeMap::new();
+            properties.insert(
+                "name".to_string(),
+                MetadataSchemaProperty {
+                    name: "name".to_string(),
+                    description: "The name of the NFT".to_string(),
+                    required: true,
+                },
+            );
+            properties.insert(
+                "token_uri".to_string(),
+                MetadataSchemaProperty {
+                    name: "token_uri".to_string(),
+                    description: "The URI pointing to an off chain resource".to_string(),
+                    required: true,
+                },
+            );
+            properties.insert(
+                "checksum".to_string(),
+                MetadataSchemaProperty {
+                    name: "checksum".to_string(),
+                    description: "A SHA256 hash of the content at the token_uri".to_string(),
+                    required: true,
+                },
+            );
+            CustomMetadataSchema { properties }
+        }
+        NFTMetadataKind::CustomValidated => {
+            let custom_schema_json = get_stored_value_with_user_errors::<String>(
+                ARG_JSON_SCHEMA,
+                NFTCoreError::MissingJsonSchema,
+                NFTCoreError::InvalidJsonSchema,
+            );
+
+            casper_serde_json_wasm::from_str::<CustomMetadataSchema>(&custom_schema_json)
+                .map_err(|_| NFTCoreError::InvalidJsonSchema)
+                .unwrap_or_revert()
+        }
+    }
+}
+
+impl ToBytes for CustomMetadataSchema {
+    fn to_bytes(&self) -> Result<Vec<u8>, Error> {
+        let mut result = bytesrepr::allocate_buffer(self)?;
+        result.extend(self.properties.to_bytes()?);
+        Ok(result)
+    }
+
+    fn serialized_length(&self) -> usize {
+        self.properties.serialized_length()
+    }
+}
+
+impl FromBytes for CustomMetadataSchema {
+    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
+        let (properties, remainder) =
+            BTreeMap::<String, MetadataSchemaProperty>::from_bytes(bytes)?;
+        let metadata_schema = CustomMetadataSchema { properties };
+        Ok((metadata_schema, remainder))
+    }
+}
+
+impl CLTyped for CustomMetadataSchema {
+    fn cl_type() -> CLType {
+        CLType::Any
+    }
+}
+
+// Using a structure for the purposes of serialization formatting.
+#[derive(Serialize, Deserialize)]
+pub(crate) struct MetadataNFT721 {
+    name: String,
+    symbol: String,
+    token_uri: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub(crate) struct MetadataCEP78 {
+    name: String,
+    token_uri: String,
+    checksum: String,
+}
+
+// Using a structure for the purposes of serialization formatting.
+#[derive(Serialize, Deserialize)]
+pub(crate) struct CustomMetadata {
+    attributes: BTreeMap<String, String>,
+}
+
+pub(crate) fn validate_metadata(
+    metadata_kind: &NFTMetadataKind,
+    token_metadata: String,
+) -> Result<String, NFTCoreError> {
+    let token_schema = get_metadata_schema(metadata_kind);
+    match metadata_kind {
+        NFTMetadataKind::CEP78 => {
+            let metadata = casper_serde_json_wasm::from_str::<MetadataCEP78>(&token_metadata)
+                .map_err(|_| NFTCoreError::FailedToParseCep99Metadata)?;
+
+            if let Some(name_property) = token_schema.properties.get("name") {
+                if name_property.required && metadata.name.is_empty() {
+                    revert(NFTCoreError::InvalidCEP99Metadata)
+                }
+            }
+            if let Some(token_uri_property) = token_schema.properties.get("token_uri") {
+                if token_uri_property.required && metadata.token_uri.is_empty() {
+                    revert(NFTCoreError::InvalidCEP99Metadata)
+                }
+            }
+            if let Some(checksum_property) = token_schema.properties.get("checksum") {
+                if checksum_property.required && metadata.checksum.is_empty() {
+                    revert(NFTCoreError::InvalidCEP99Metadata)
+                }
+            }
+            casper_serde_json_wasm::to_string_pretty(&metadata)
+                .map_err(|_| NFTCoreError::FailedToJsonifyCEP99Metadata)
+        }
+        NFTMetadataKind::NFT721 => {
+            let metadata = casper_serde_json_wasm::from_str::<MetadataNFT721>(&token_metadata)
+                .map_err(|_| NFTCoreError::FailedToParse721Metadata)?;
+
+            if let Some(name_property) = token_schema.properties.get("name") {
+                if name_property.required && metadata.name.is_empty() {
+                    revert(NFTCoreError::InvalidNFT721Metadata)
+                }
+            }
+            if let Some(token_uri_property) = token_schema.properties.get("token_uri") {
+                if token_uri_property.required && metadata.token_uri.is_empty() {
+                    revert(NFTCoreError::InvalidNFT721Metadata)
+                }
+            }
+            if let Some(symbol_property) = token_schema.properties.get("symbol") {
+                if symbol_property.required && metadata.symbol.is_empty() {
+                    revert(NFTCoreError::InvalidNFT721Metadata)
+                }
+            }
+            casper_serde_json_wasm::to_string_pretty(&metadata)
+                .map_err(|_| NFTCoreError::FailedToJsonifyNFT721Metadata)
+        }
+        NFTMetadataKind::Raw => Ok(token_metadata),
+        NFTMetadataKind::CustomValidated => {
+            let custom_metadata =
+                casper_serde_json_wasm::from_str::<BTreeMap<String, String>>(&token_metadata)
+                    .map(|attributes| CustomMetadata { attributes })
+                    .map_err(|_| NFTCoreError::FailedToParseCustomMetadata)?;
+
+            for (property_name, property_type) in token_schema.properties.iter() {
+                if property_type.required && custom_metadata.attributes.get(property_name).is_none()
+                {
+                    revert(NFTCoreError::InvalidCustomMetadata)
+                }
+            }
+            casper_serde_json_wasm::to_string_pretty(&custom_metadata.attributes)
+                .map_err(|_| NFTCoreError::FailedToJsonifyCustomMetadata)
+        }
+    }
+}
+
+pub(crate) fn get_metadata_dictionary_name(metadata_kind: &NFTMetadataKind) -> String {
+    let name = match metadata_kind {
+        NFTMetadataKind::CEP78 => METADATA_CEP78,
+        NFTMetadataKind::NFT721 => METADATA_NFT721,
+        NFTMetadataKind::Raw => METADATA_RAW,
+        NFTMetadataKind::CustomValidated => METADATA_CUSTOM_VALIDATED,
+    };
+    name.to_string()
 }
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 00000000..3d2e76e9
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,4 @@
+wrap_comments = true
+comment_width = 100
+imports_granularity = "Crate"
+edition = "2018"
diff --git a/tests/Cargo.toml b/tests/Cargo.toml
index d9206e70..490483d8 100644
--- a/tests/Cargo.toml
+++ b/tests/Cargo.toml
@@ -7,3 +7,8 @@ edition = "2018"
 casper-engine-test-support = { version = "2.2.0", features = ["test-support"] }
 casper-execution-engine = "2.0.0"
 casper-types = "1.4.5"
+serde = { version = "1.0", default-features = false }
+serde_json = "1.0.81"
+once_cell = "1"
+blake2 = { version = "0.9.0", default-features = false }
+base16 = { version = "0.2", default-features = false }
\ No newline at end of file
diff --git a/tests/src/burn.rs b/tests/src/burn.rs
index 549423ca..c58a8e95 100644
--- a/tests/src/burn.rs
+++ b/tests/src/burn.rs
@@ -2,28 +2,33 @@ use casper_engine_test_support::{
     ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_ACCOUNT_ADDR,
     DEFAULT_RUN_GENESIS_REQUEST,
 };
-use casper_types::{runtime_args, system::mint, ContractHash, Key, RuntimeArgs, U256};
+use casper_types::{runtime_args, system::mint, ContractHash, Key, RuntimeArgs};
 
 use crate::utility::{
     constants::{
         ACCOUNT_USER_1, ARG_KEY_NAME, ARG_NFT_CONTRACT_HASH, ARG_TOKEN_ID, ARG_TOKEN_META_DATA,
         ARG_TOKEN_OWNER, ARG_TOKEN_URI, BALANCES, BURNT_TOKENS, CONTRACT_NAME, ENTRY_POINT_BURN,
-        MINT_SESSION_WASM, NFT_CONTRACT_WASM, OWNED_TOKENS, OWNED_TOKENS_DICTIONARY_KEY,
-        TEST_META_DATA, TEST_URI,
+        ENTRY_POINT_MINT, MINTING_CONTRACT_WASM, MINT_SESSION_WASM, NFT_CONTRACT_WASM,
+        OWNED_TOKENS, OWNED_TOKENS_DICTIONARY_KEY, TEST_PRETTY_721_META_DATA, TEST_URI,
+        TOKEN_COUNTS,
+    },
+    installer_request_builder::{
+        InstallerRequestBuilder, MintingMode, NFTHolderMode, OwnershipMode, WhitelistMode,
+    },
+    support::{
+        self, get_dictionary_value_from_key, get_minting_contract_hash, get_nft_contract_hash,
     },
-    installer_request_builder::{InstallerRequestBuilder, OwnershipMode},
-    support::{self, get_nft_contract_hash},
 };
 
 #[test]
 fn should_burn_minted_token() {
-    const TOKEN_ID: U256 = U256::zero();
+    let token_id = 0u64;
     let mut builder = InMemoryWasmTestBuilder::default();
     builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
 
     let install_request_builder =
         InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-            .with_total_token_supply(U256::from(100u64))
+            .with_total_token_supply(100u64)
             .with_ownership_mode(OwnershipMode::Transferable)
             .build();
 
@@ -47,7 +52,7 @@ fn should_burn_minted_token() {
             ARG_NFT_CONTRACT_HASH => Key::Hash(nft_contract_hash.value()),
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string(),
         },
     )
@@ -55,23 +60,23 @@ fn should_burn_minted_token() {
 
     builder.exec(mint_session_call).expect_success().commit();
 
-    let actual_owned_tokens = support::get_dictionary_value_from_key::<Vec<U256>>(
+    let actual_owned_tokens = support::get_dictionary_value_from_key::<Vec<u64>>(
         &builder,
         nft_contract_key,
         OWNED_TOKENS,
         &DEFAULT_ACCOUNT_ADDR.clone().to_string(),
     );
 
-    let expected_owned_tokens = vec![U256::zero()];
+    let expected_owned_tokens = vec![token_id];
     assert_eq!(expected_owned_tokens, actual_owned_tokens);
-    let actual_balance_before_burn = support::get_dictionary_value_from_key::<U256>(
+    let actual_balance_before_burn = support::get_dictionary_value_from_key::<u64>(
         &builder,
         nft_contract_key,
         BALANCES,
         &DEFAULT_ACCOUNT_ADDR.clone().to_string(),
     );
 
-    let expected_balance_before_burn = U256::one();
+    let expected_balance_before_burn = 1u64;
     assert_eq!(actual_balance_before_burn, expected_balance_before_burn);
 
     let burn_request = ExecuteRequestBuilder::contract_call_by_name(
@@ -79,7 +84,7 @@ fn should_burn_minted_token() {
         CONTRACT_NAME,
         ENTRY_POINT_BURN,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
+            ARG_TOKEN_ID => token_id,
         },
     )
     .build();
@@ -90,18 +95,18 @@ fn should_burn_minted_token() {
         &builder,
         nft_contract_key,
         BURNT_TOKENS,
-        &TOKEN_ID.to_string(),
+        &token_id.to_string(),
     );
 
     // This will error of token is not registered as
-    let actual_balance = support::get_dictionary_value_from_key::<U256>(
+    let actual_balance = support::get_dictionary_value_from_key::<u64>(
         &builder,
         nft_contract_key,
         BALANCES,
         &DEFAULT_ACCOUNT_ADDR.clone().to_string(),
     );
 
-    let expected_balance = U256::zero();
+    let expected_balance = 0u64;
     assert_eq!(actual_balance, expected_balance);
 }
 
@@ -112,7 +117,7 @@ fn should_not_burn_previously_burnt_token() {
 
     let install_request_builder =
         InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-            .with_total_token_supply(U256::from(100u64))
+            .with_total_token_supply(100u64)
             .with_ownership_mode(OwnershipMode::Transferable)
             .build();
 
@@ -135,7 +140,7 @@ fn should_not_burn_previously_burnt_token() {
             ARG_NFT_CONTRACT_HASH => Key::Hash(nft_contract_hash.value()),
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string(),
         },
     )
@@ -143,14 +148,14 @@ fn should_not_burn_previously_burnt_token() {
 
     builder.exec(mint_session_call).expect_success().commit();
 
-    let actual_owned_tokens = support::get_dictionary_value_from_key::<Vec<U256>>(
+    let actual_owned_tokens = support::get_dictionary_value_from_key::<Vec<u64>>(
         &builder,
         nft_contract_key,
         OWNED_TOKENS,
         &DEFAULT_ACCOUNT_ADDR.clone().to_string(),
     );
 
-    let expected_owned_tokens = vec![U256::zero()];
+    let expected_owned_tokens = vec![0u64];
     assert_eq!(expected_owned_tokens, actual_owned_tokens);
 
     let burn_request = ExecuteRequestBuilder::contract_call_by_name(
@@ -158,7 +163,7 @@ fn should_not_burn_previously_burnt_token() {
         CONTRACT_NAME,
         ENTRY_POINT_BURN,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
+            ARG_TOKEN_ID => 0u64,
         },
     )
     .build();
@@ -170,7 +175,7 @@ fn should_not_burn_previously_burnt_token() {
         CONTRACT_NAME,
         ENTRY_POINT_BURN,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
+            ARG_TOKEN_ID => 0u64,
         },
     )
     .build();
@@ -192,7 +197,7 @@ fn should_return_expected_error_when_burning_non_existing_token() {
 
     let install_request_builder =
         InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-            .with_total_token_supply(U256::from(100u64))
+            .with_total_token_supply(100u64)
             .with_ownership_mode(OwnershipMode::Transferable)
             .build();
 
@@ -206,7 +211,7 @@ fn should_return_expected_error_when_burning_non_existing_token() {
         CONTRACT_NAME,
         ENTRY_POINT_BURN,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
+            ARG_TOKEN_ID => 0u64,
         },
     )
     .build();
@@ -228,7 +233,7 @@ fn should_return_expected_error_burning_of_others_users_token() {
 
     let install_request_builder =
         InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-            .with_total_token_supply(U256::from(100u64))
+            .with_total_token_supply(100u64)
             .with_ownership_mode(OwnershipMode::Transferable)
             .build();
 
@@ -270,7 +275,7 @@ fn should_return_expected_error_burning_of_others_users_token() {
             ARG_NFT_CONTRACT_HASH => Key::Hash(nft_contract_hash),
             ARG_KEY_NAME => Option::<String>::None,
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string(),
         },
     )
@@ -278,22 +283,21 @@ fn should_return_expected_error_burning_of_others_users_token() {
 
     builder.exec(mint_session_call).expect_success().commit();
 
-    let actual_owned_tokens = support::get_dictionary_value_from_key::<Vec<U256>>(
+    let actual_owned_tokens = support::get_dictionary_value_from_key::<Vec<u64>>(
         &builder,
         nft_contract_key,
         OWNED_TOKENS,
         &DEFAULT_ACCOUNT_ADDR.clone().to_string(),
     );
 
-    let expected_owned_tokens = vec![U256::zero()];
-    assert_eq!(expected_owned_tokens, actual_owned_tokens);
+    assert_eq!(vec![0u64], actual_owned_tokens);
 
     let incorrect_burn_request = ExecuteRequestBuilder::contract_call_by_hash(
         account_user_1.to_account_hash(),
         ContractHash::new(nft_contract_hash),
         ENTRY_POINT_BURN,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
+            ARG_TOKEN_ID => 0u64,
         },
     )
     .build();
@@ -303,8 +307,6 @@ fn should_return_expected_error_burning_of_others_users_token() {
     let error = builder.get_error().expect("must have error");
 
     support::assert_expected_error(error, 6u16, "should disallow burning of other users' token");
-
-    // TODO is this really diffferent than should_return_expected_error_when_burning_not_owned_token() ???
 }
 
 #[test]
@@ -314,7 +316,7 @@ fn should_return_expected_error_when_burning_not_owned_token() {
 
     let install_request_builder =
         InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-            .with_total_token_supply(U256::from(100u64))
+            .with_total_token_supply(100u64)
             .with_ownership_mode(OwnershipMode::Transferable)
             .build();
 
@@ -357,7 +359,7 @@ fn should_return_expected_error_when_burning_not_owned_token() {
             ARG_NFT_CONTRACT_HASH => Key::Hash(nft_contract_hash),
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string(),
         },
     )
@@ -365,22 +367,21 @@ fn should_return_expected_error_when_burning_not_owned_token() {
 
     builder.exec(mint_session_call).expect_success().commit();
 
-    let actual_owned_tokens = support::get_dictionary_value_from_key::<Vec<U256>>(
+    let actual_owned_tokens = support::get_dictionary_value_from_key::<Vec<u64>>(
         &builder,
         nft_contract_key,
         OWNED_TOKENS,
         &DEFAULT_ACCOUNT_ADDR.clone().to_string(),
     );
 
-    let expected_owned_tokens = vec![U256::zero()];
-    assert_eq!(expected_owned_tokens, actual_owned_tokens);
+    assert_eq!(vec![0u64], actual_owned_tokens);
 
     let incorrect_burn_request = ExecuteRequestBuilder::contract_call_by_hash(
         account_user_1.to_account_hash(),
         ContractHash::new(nft_contract_hash),
         ENTRY_POINT_BURN,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero()
+            ARG_TOKEN_ID => 0u64
         },
     )
     .build();
@@ -394,3 +395,92 @@ fn should_return_expected_error_when_burning_not_owned_token() {
         "should disallow burning on mismatch of owner key",
     );
 }
+
+#[test]
+fn should_allow_contract_to_burn_token() {
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    let minting_contract_install_request = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        MINTING_CONTRACT_WASM,
+        runtime_args! {},
+    )
+    .build();
+
+    builder
+        .exec(minting_contract_install_request)
+        .expect_success()
+        .commit();
+
+    let minting_contract_hash = get_minting_contract_hash(&builder);
+
+    let contract_whitelist = vec![minting_contract_hash];
+
+    let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+        .with_total_token_supply(100u64)
+        .with_holder_mode(NFTHolderMode::Contracts)
+        .with_whitelist_mode(WhitelistMode::Locked)
+        .with_ownership_mode(OwnershipMode::Minter)
+        .with_minting_mode(Some(MintingMode::Installer as u8))
+        .with_contract_whitelist(contract_whitelist)
+        .build();
+
+    builder.exec(install_request).expect_success().commit();
+
+    let nft_contract_key: Key = get_nft_contract_hash(&builder).into();
+
+    let mint_runtime_args = runtime_args! {
+        ARG_NFT_CONTRACT_HASH => nft_contract_key,
+        ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+        ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
+        ARG_TOKEN_URI => TEST_URI.to_string()
+    };
+
+    let mint_via_contract_call = ExecuteRequestBuilder::contract_call_by_hash(
+        *DEFAULT_ACCOUNT_ADDR,
+        minting_contract_hash,
+        ENTRY_POINT_MINT,
+        mint_runtime_args,
+    )
+    .build();
+
+    builder
+        .exec(mint_via_contract_call)
+        .expect_success()
+        .commit();
+
+    let current_token_balance = get_dictionary_value_from_key::<u64>(
+        &builder,
+        &nft_contract_key,
+        TOKEN_COUNTS,
+        &minting_contract_hash.to_string(),
+    );
+
+    assert_eq!(1u64, current_token_balance);
+
+    let burn_via_contract_call = ExecuteRequestBuilder::contract_call_by_hash(
+        *DEFAULT_ACCOUNT_ADDR,
+        minting_contract_hash,
+        ENTRY_POINT_BURN,
+        runtime_args! {
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_TOKEN_ID => 0u64
+        },
+    )
+    .build();
+
+    builder
+        .exec(burn_via_contract_call)
+        .expect_success()
+        .commit();
+
+    let updated_token_balance = get_dictionary_value_from_key::<u64>(
+        &builder,
+        &nft_contract_key,
+        TOKEN_COUNTS,
+        &minting_contract_hash.to_string(),
+    );
+
+    assert_eq!(updated_token_balance, 0u64)
+}
diff --git a/tests/src/installer.rs b/tests/src/installer.rs
index 192b080e..90dd2de0 100644
--- a/tests/src/installer.rs
+++ b/tests/src/installer.rs
@@ -2,15 +2,16 @@ use casper_engine_test_support::{
     ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_ACCOUNT_ADDR,
     DEFAULT_RUN_GENESIS_REQUEST,
 };
-use casper_types::{runtime_args, CLValue, RuntimeArgs, U256};
+use casper_types::{runtime_args, CLValue, ContractHash, RuntimeArgs};
 
 use crate::utility::{
     constants::{
-        ARG_ALLOW_MINTING, ARG_COLLECTION_NAME, ARG_COLLECTION_SYMBOL, ARG_MINTING_MODE,
-        ARG_TOTAL_TOKEN_SUPPLY, CONTRACT_NAME, ENTRY_POINT_INIT, NFT_CONTRACT_WASM,
-        NFT_TEST_COLLECTION, NFT_TEST_SYMBOL, NUMBER_OF_MINTED_TOKENS,
+        ARG_ALLOW_MINTING, ARG_COLLECTION_NAME, ARG_COLLECTION_SYMBOL, ARG_CONTRACT_WHITELIST,
+        ARG_HOLDER_MODE, ARG_MINTING_MODE, ARG_TOTAL_TOKEN_SUPPLY, ARG_WHITELIST_MODE,
+        CONTRACT_NAME, ENTRY_POINT_INIT, NFT_CONTRACT_WASM, NFT_TEST_COLLECTION, NFT_TEST_SYMBOL,
+        NUMBER_OF_MINTED_TOKENS,
     },
-    installer_request_builder::InstallerRequestBuilder,
+    installer_request_builder::{InstallerRequestBuilder, NFTHolderMode, WhitelistMode},
     support,
 };
 
@@ -22,7 +23,7 @@ fn should_install_contract() {
     let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
         .with_collection_name(NFT_TEST_COLLECTION.to_string())
         .with_collection_symbol(NFT_TEST_SYMBOL.to_string())
-        .with_total_token_supply(U256::from(1u64))
+        .with_total_token_supply(1u64)
         .build();
 
     builder.exec(install_request).expect_success().commit();
@@ -57,15 +58,14 @@ fn should_install_contract() {
         "collection_symbol initialized at installation should exist"
     );
 
-    let query_result: U256 = support::query_stored_value(
+    let query_result: u64 = support::query_stored_value(
         &mut builder,
         *nft_contract_key,
         vec![ARG_TOTAL_TOKEN_SUPPLY.to_string()],
     );
 
     assert_eq!(
-        query_result,
-        U256::from(1u64),
+        query_result, 1u64,
         "total_token_supply initialized at installation should exist"
     );
 
@@ -88,15 +88,14 @@ fn should_install_contract() {
         "minting mode should default to installer"
     );
 
-    let query_result: U256 = support::query_stored_value(
+    let query_result: u64 = support::query_stored_value(
         &mut builder,
         *nft_contract_key,
         vec![NUMBER_OF_MINTED_TOKENS.to_string()],
     );
 
     assert_eq!(
-        query_result,
-        U256::zero(),
+        query_result, 0u64,
         "number_of_minted_tokens initialized at installation should exist"
     );
 }
@@ -108,7 +107,7 @@ fn should_only_allow_init_during_installation_session() {
 
     let install_request_builder =
         InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-            .with_total_token_supply(U256::from(2u64));
+            .with_total_token_supply(2u64);
     builder
         .exec(install_request_builder.build())
         .expect_success()
@@ -146,7 +145,7 @@ fn should_install_with_allow_minting_set_to_false() {
     let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
         .with_collection_name(NFT_TEST_COLLECTION.to_string())
         .with_collection_symbol(NFT_TEST_SYMBOL.to_string())
-        .with_total_token_supply(U256::from(1u64))
+        .with_total_token_supply(1u64)
         .build();
 
     builder.exec(install_request).expect_success().commit();
@@ -156,9 +155,7 @@ fn should_install_with_allow_minting_set_to_false() {
 fn should_reject_invalid_collection_name() {
     let install_request_builder =
         InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-            .with_invalid_collection_name(
-                CLValue::from_t::<U256>(U256::zero()).expect("expected CLValue"),
-            );
+            .with_invalid_collection_name(CLValue::from_t::<u64>(0u64).expect("expected CLValue"));
 
     support::assert_expected_invalid_installer_request(
         install_request_builder,
@@ -172,7 +169,7 @@ fn should_reject_invalid_collection_symbol() {
     let install_request_builder =
         InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
             .with_invalid_collection_symbol(
-                CLValue::from_t::<U256>(U256::zero()).expect("expected CLValue"),
+                CLValue::from_t::<u64>(0u64).expect("expected CLValue"),
             );
 
     support::assert_expected_invalid_installer_request(
@@ -195,3 +192,78 @@ fn should_reject_non_numerical_total_token_supply_value() {
         "should reject installation when given an invalid total supply value",
     );
 }
+
+#[test]
+fn should_install_with_contract_holder_mode() {
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+        .with_holder_mode(NFTHolderMode::Contracts)
+        .with_whitelist_mode(WhitelistMode::Unlocked)
+        .with_contract_whitelist(vec![ContractHash::default()]);
+
+    builder
+        .exec(install_request.build())
+        .expect_success()
+        .commit();
+
+    let account = builder.get_expected_account(*DEFAULT_ACCOUNT_ADDR);
+    let nft_contract_key = account
+        .named_keys()
+        .get(CONTRACT_NAME)
+        .expect("must have key in named keys");
+
+    let actual_holder_mode: u8 = support::query_stored_value(
+        &mut builder,
+        *nft_contract_key,
+        vec![ARG_HOLDER_MODE.to_string()],
+    );
+
+    assert_eq!(
+        actual_holder_mode,
+        NFTHolderMode::Contracts as u8,
+        "holder mode is not set to contracts"
+    );
+
+    let actual_whitelist_mode: u8 = support::query_stored_value(
+        &mut builder,
+        *nft_contract_key,
+        vec![ARG_WHITELIST_MODE.to_string()],
+    );
+
+    assert_eq!(
+        actual_whitelist_mode,
+        WhitelistMode::Unlocked as u8,
+        "whitelist mode is not set to unlocked"
+    );
+
+    let actual_contract_whitelist: Vec<ContractHash> = support::query_stored_value(
+        &mut builder,
+        *nft_contract_key,
+        vec![ARG_CONTRACT_WHITELIST.to_string()],
+    );
+
+    assert_eq!(
+        actual_contract_whitelist,
+        vec![ContractHash::default()],
+        "contract whitelist is incorrectly set"
+    );
+}
+
+#[test]
+fn should_disallow_installation_of_contract_with_empty_locked_whitelist() {
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    let install_request_builder =
+        InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+            .with_holder_mode(NFTHolderMode::Contracts)
+            .with_whitelist_mode(WhitelistMode::Locked);
+
+    support::assert_expected_invalid_installer_request(
+        install_request_builder,
+        83,
+        "should fail execution since whitelist mode is locked and the provided whitelist is empty",
+    );
+}
diff --git a/tests/src/mint.rs b/tests/src/mint.rs
index a5020b5a..33507aed 100644
--- a/tests/src/mint.rs
+++ b/tests/src/mint.rs
@@ -1,33 +1,45 @@
+use serde::{Deserialize, Serialize};
+
 use casper_engine_test_support::{
     ExecuteRequestBuilder, InMemoryWasmTestBuilder, WasmTestBuilder, DEFAULT_ACCOUNT_ADDR,
     DEFAULT_RUN_GENESIS_REQUEST,
 };
-
 use casper_execution_engine::storage::global_state::in_memory::InMemoryGlobalState;
-use casper_types::{runtime_args, system::mint, Key, RuntimeArgs, U256};
+use casper_types::{runtime_args, system::mint, ContractHash, Key, RuntimeArgs};
 
-use crate::utility::constants::{
-    ARG_APPROVE_ALL, ARG_KEY_NAME, ARG_OPERATOR, ARG_TOKEN_ID, ARG_TOKEN_URI,
-    BALANCE_OF_SESSION_WASM, ENTRY_POINT_APPROVE, ENTRY_POINT_SET_APPROVE_FOR_ALL,
-    MINT_SESSION_WASM, OWNED_TOKENS_DICTIONARY_KEY, TEST_URI,
-};
-use crate::utility::installer_request_builder::{MintingMode, OwnershipMode};
-use crate::utility::support::{
-    call_entry_point_with_ret, create_dummy_key_pair, get_nft_contract_hash,
-};
 use crate::utility::{
     constants::{
-        ACCOUNT_USER_1, ACCOUNT_USER_2, ARG_MINTING_MODE, ARG_NFT_CONTRACT_HASH,
-        ARG_TOKEN_META_DATA, ARG_TOKEN_OWNER, BALANCES, CONTRACT_NAME, ENTRY_POINT_MINT,
-        NFT_CONTRACT_WASM, NUMBER_OF_MINTED_TOKENS, OWNED_TOKENS, TEST_META_DATA, TOKEN_ISSUERS,
-        TOKEN_META_DATA, TOKEN_OWNERS,
+        ACCOUNT_USER_1, ACCOUNT_USER_2, ARG_APPROVE_ALL, ARG_CONTRACT_WHITELIST,
+        ARG_IS_HASH_IDENTIFIER_MODE, ARG_KEY_NAME, ARG_MINTING_MODE, ARG_NFT_CONTRACT_HASH,
+        ARG_OPERATOR, ARG_TOKEN_ID, ARG_TOKEN_META_DATA, ARG_TOKEN_OWNER, ARG_TOKEN_URI, BALANCES,
+        BALANCE_OF_SESSION_WASM, CONTRACT_NAME, ENTRY_POINT_APPROVE, ENTRY_POINT_MINT,
+        ENTRY_POINT_SET_APPROVE_FOR_ALL, ENTRY_POINT_SET_VARIABLES, MALFORMED_META_DATA,
+        METADATA_CEP78, METADATA_CUSTOM_VALIDATED, METADATA_NFT721, METADATA_RAW,
+        MINTING_CONTRACT_WASM, MINT_SESSION_WASM, NFT_CONTRACT_WASM, NUMBER_OF_MINTED_TOKENS,
+        OWNED_TOKENS, OWNED_TOKENS_DICTIONARY_KEY, RECEIPT_NAME, TEST_COMPACT_META_DATA,
+        TEST_PRETTY_721_META_DATA, TEST_PRETTY_CEP78_METADATA, TEST_URI, TOKEN_ISSUERS,
+        TOKEN_OWNERS,
+    },
+    installer_request_builder::{
+        InstallerRequestBuilder, MintingMode, NFTHolderMode, NFTIdentifierMode, NFTMetadataKind,
+        OwnershipMode, WhitelistMode, TEST_CUSTOM_METADATA, TEST_CUSTOM_METADATA_SCHEMA,
+    },
+    support::{
+        self, assert_expected_error, call_entry_point_with_ret, create_dummy_key_pair,
+        get_dictionary_value_from_key, get_minting_contract_hash, get_nft_contract_hash,
+        query_stored_value,
     },
-    installer_request_builder::InstallerRequestBuilder,
-    support,
 };
 
+#[derive(Serialize, Deserialize, Debug)]
+struct Metadata {
+    name: String,
+    symbol: String,
+    token_uri: String,
+}
+
 fn setup_nft_contract(
-    total_token_supply: Option<U256>,
+    total_token_supply: Option<u64>,
     allowing_minting: Option<bool>,
 ) -> WasmTestBuilder<InMemoryGlobalState> {
     let mut builder = InMemoryWasmTestBuilder::default();
@@ -52,14 +64,14 @@ fn setup_nft_contract(
 
 #[test]
 fn should_disallow_minting_when_allow_minting_is_set_to_false() {
-    let mut builder = setup_nft_contract(Some(U256::from(2u64)), Some(false));
+    let mut builder = setup_nft_contract(Some(2u64), Some(false));
 
     let mint_request = ExecuteRequestBuilder::contract_call_by_name(
         *DEFAULT_ACCOUNT_ADDR,
         CONTRACT_NAME,
         ENTRY_POINT_MINT,
         runtime_args! {
-            ARG_TOKEN_META_DATA=>TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA=>TEST_PRETTY_721_META_DATA.to_string(),
         },
     )
     .build();
@@ -67,7 +79,7 @@ fn should_disallow_minting_when_allow_minting_is_set_to_false() {
 
     // Error should be MintingIsPaused=59
     let actual_error = builder.get_error().expect("must have error");
-    support::assert_expected_error(
+    assert_expected_error(
         actual_error,
         59u16,
         "should now allow minting when minting is disabled",
@@ -79,14 +91,12 @@ fn entry_points_with_ret_should_return_correct_value() {
     let mut builder = InMemoryWasmTestBuilder::default();
     builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
 
-    let install_request_builder =
-        InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-            .with_total_token_supply(U256::from(2u64))
-            .with_ownership_mode(OwnershipMode::Transferable);
-    builder
-        .exec(install_request_builder.build())
-        .expect_success()
-        .commit();
+    let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+        .with_total_token_supply(2u64)
+        .with_ownership_mode(OwnershipMode::Transferable)
+        .build();
+
+    builder.exec(install_request).expect_success().commit();
 
     let nft_contract_key: Key = get_nft_contract_hash(&builder).into();
 
@@ -97,7 +107,7 @@ fn entry_points_with_ret_should_return_correct_value() {
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string(),
         },
     )
@@ -108,10 +118,10 @@ fn entry_points_with_ret_should_return_correct_value() {
     let nft_contract_hash = get_nft_contract_hash(&builder);
     let account_hash = *DEFAULT_ACCOUNT_ADDR;
 
-    let actual_balance: U256 = call_entry_point_with_ret(
+    let actual_balance: u64 = call_entry_point_with_ret(
         &mut builder,
         account_hash,
-        nft_contract_hash,
+        nft_contract_key,
         runtime_args! {
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
         },
@@ -119,7 +129,7 @@ fn entry_points_with_ret_should_return_correct_value() {
         "balance_of",
     );
 
-    let expected_balance = U256::one();
+    let expected_balance = 1u64;
     assert_eq!(
         actual_balance, expected_balance,
         "actual and expected balances should be equal"
@@ -128,9 +138,10 @@ fn entry_points_with_ret_should_return_correct_value() {
     let actual_owner: Key = call_entry_point_with_ret(
         &mut builder,
         account_hash,
-        nft_contract_hash,
+        nft_contract_key,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
+            ARG_IS_HASH_IDENTIFIER_MODE => false,
+            ARG_TOKEN_ID => 0u64,
         },
         "owner_of_call.wasm",
         "owner_of",
@@ -148,7 +159,7 @@ fn entry_points_with_ret_should_return_correct_value() {
         nft_contract_hash,
         ENTRY_POINT_APPROVE,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
+            ARG_TOKEN_ID => 0u64,
             ARG_OPERATOR => Key::Account(operator_public_key.to_account_hash())
         },
     )
@@ -158,9 +169,10 @@ fn entry_points_with_ret_should_return_correct_value() {
     let actual_operator: Option<Key> = call_entry_point_with_ret(
         &mut builder,
         account_hash,
-        nft_contract_hash,
+        nft_contract_key,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
+            ARG_IS_HASH_IDENTIFIER_MODE => false,
+            ARG_TOKEN_ID => 0u64,
         },
         "get_approved_call.wasm",
         "get_approved",
@@ -175,13 +187,21 @@ fn entry_points_with_ret_should_return_correct_value() {
 }
 
 #[test]
-fn should_call_mint_via_session_code() {
+fn should_mint() {
     let mut builder = InMemoryWasmTestBuilder::default();
     builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
 
+    let metadata = Metadata {
+        name: "Ed".to_string(),
+        symbol: "avc".to_string(),
+        token_uri: "http://www.google.com".to_string(),
+    };
+
+    let json_metadata = serde_json::to_string(&metadata).expect("must convert to JSON string");
+
     let install_request_builder =
         InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-            .with_total_token_supply(U256::from(2u64));
+            .with_total_token_supply(2u64);
     builder
         .exec(install_request_builder.build())
         .expect_success()
@@ -196,7 +216,7 @@ fn should_call_mint_via_session_code() {
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => json_metadata,
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -213,7 +233,7 @@ fn mint_should_return_dictionary_key_to_callers_owned_tokens() {
 
     let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
         .with_collection_name(NFT_COLLECTION_NAME.to_string())
-        .with_total_token_supply(U256::from(100u8))
+        .with_total_token_supply(100u64)
         .with_allowing_minting(Some(true))
         .build();
 
@@ -226,7 +246,7 @@ fn mint_should_return_dictionary_key_to_callers_owned_tokens() {
         runtime_args! {
             ARG_NFT_CONTRACT_HASH => nft_contract_hash,
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -236,21 +256,23 @@ fn mint_should_return_dictionary_key_to_callers_owned_tokens() {
 
     let account = builder.get_expected_account(*DEFAULT_ACCOUNT_ADDR);
 
-    let owned_key_name = format!(
-        "{}_{}",
-        get_nft_contract_hash(&builder).to_formatted_string(),
-        NFT_COLLECTION_NAME
+    let receipt: String = query_stored_value(
+        &mut builder,
+        nft_contract_hash,
+        vec![RECEIPT_NAME.to_string()],
     );
 
     let (_, owned_tokens_key) = account
         .named_keys()
-        .get_key_value(&owned_key_name)
+        .get_key_value(&receipt)
         .expect("should have owned_tokens_key");
 
     match builder.query(None, *owned_tokens_key, &[]).unwrap() {
         casper_types::StoredValue::CLValue(val) => {
-            let expected = val.into_t::<Vec<U256>>().expect("should be Vec<U256>");
-            println!("{:?}", expected);
+            let expected = val
+                .into_t::<Vec<u64>>()
+                .expect("should be Vec<u64> as Identifier defaults to indices");
+            assert_eq!(vec![0u64], expected);
         }
         _ => panic!("wrong stored value type"),
     }
@@ -261,7 +283,7 @@ fn mint_should_return_dictionary_key_to_callers_owned_tokens() {
         runtime_args! {
             ARG_NFT_CONTRACT_HASH => nft_contract_hash,
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -271,10 +293,8 @@ fn mint_should_return_dictionary_key_to_callers_owned_tokens() {
 
     match builder.query(None, *owned_tokens_key, &[]).unwrap() {
         casper_types::StoredValue::CLValue(val) => {
-            let expected = val
-                .into_t::<Vec<U256>>()
-                .expect("should still be Vec<U256>");
-            println!("{:?}", expected);
+            let expected = val.into_t::<Vec<u64>>().expect("should still be Vec<U256>");
+            assert_eq!(vec![0u64, 1u64], expected);
         }
         _ => panic!("also the wrong stored value type"),
     }
@@ -287,7 +307,7 @@ fn mint_should_increment_number_of_minted_tokens_by_one_and_add_public_key_to_to
 
     let install_request_builder =
         InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-            .with_total_token_supply(U256::from(2u64));
+            .with_total_token_supply(2u64);
     builder
         .exec(install_request_builder.build())
         .expect_success()
@@ -302,7 +322,7 @@ fn mint_should_increment_number_of_minted_tokens_by_one_and_add_public_key_to_to
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -318,47 +338,45 @@ fn mint_should_increment_number_of_minted_tokens_by_one_and_add_public_key_to_to
         .expect("must have key in named keys");
 
     //mint should have incremented number_of_minted_tokens by one
-    let query_result: U256 = support::query_stored_value(
+    let query_result: u64 = support::query_stored_value(
         &mut builder,
         *nft_contract_key,
         vec![NUMBER_OF_MINTED_TOKENS.to_string()],
     );
 
     assert_eq!(
-        query_result,
-        U256::one(),
+        query_result, 1u64,
         "number_of_minted_tokens initialized at installation should have incremented by one"
     );
 
     let actual_token_meta_data = support::get_dictionary_value_from_key::<String>(
         &builder,
         nft_contract_key,
-        TOKEN_META_DATA,
-        &U256::zero().to_string(),
+        METADATA_NFT721,
+        &0u64.to_string(),
     );
 
-    assert_eq!(actual_token_meta_data, TEST_META_DATA);
+    assert_eq!(actual_token_meta_data, TEST_PRETTY_721_META_DATA);
 
     let minter_account_hash = support::get_dictionary_value_from_key::<Key>(
         &builder,
         nft_contract_key,
         TOKEN_OWNERS,
-        &U256::zero().to_string(),
+        &0u64.to_string(),
     )
     .into_account()
     .unwrap();
 
     assert_eq!(DEFAULT_ACCOUNT_ADDR.clone(), minter_account_hash);
 
-    let actual_token_ids = support::get_dictionary_value_from_key::<Vec<U256>>(
+    let actual_token_ids = support::get_dictionary_value_from_key::<Vec<u64>>(
         &builder,
         nft_contract_key,
         OWNED_TOKENS,
         &DEFAULT_ACCOUNT_ADDR.clone().to_string(),
     );
 
-    let expected_token_ids = vec![U256::zero()];
-    assert_eq!(expected_token_ids, actual_token_ids);
+    assert_eq!(vec![0u64], actual_token_ids);
 
     // If total_token_supply is initialized to 1 the following test should fail.
     // If we set total_token_supply > 1 it should pass
@@ -370,8 +388,8 @@ fn mint_should_increment_number_of_minted_tokens_by_one_and_add_public_key_to_to
             ARG_NFT_CONTRACT_HASH => *nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
-             ARG_TOKEN_URI => TEST_URI.to_string()
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
+            ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
     .build();
@@ -385,7 +403,7 @@ fn should_set_meta_data() {
 
     let install_request_builder =
         InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-            .with_total_token_supply(U256::from(2u32));
+            .with_total_token_supply(2u64);
     builder
         .exec(install_request_builder.build())
         .expect_success()
@@ -400,7 +418,7 @@ fn should_set_meta_data() {
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -417,11 +435,11 @@ fn should_set_meta_data() {
     let actual_token_meta_data = support::get_dictionary_value_from_key::<String>(
         &builder,
         nft_contract_key,
-        TOKEN_META_DATA,
-        &U256::zero().to_string(),
+        METADATA_NFT721,
+        &0u64.to_string(),
     );
 
-    assert_eq!(actual_token_meta_data, TEST_META_DATA);
+    assert_eq!(actual_token_meta_data, TEST_PRETTY_721_META_DATA);
 }
 
 #[test]
@@ -431,7 +449,7 @@ fn should_set_issuer() {
 
     let install_request_builder =
         InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-            .with_total_token_supply(U256::from(2u32));
+            .with_total_token_supply(2u64);
     builder
         .exec(install_request_builder.build())
         .expect_success()
@@ -446,7 +464,7 @@ fn should_set_issuer() {
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -464,7 +482,7 @@ fn should_set_issuer() {
         &builder,
         nft_contract_key,
         TOKEN_ISSUERS,
-        &U256::zero().to_string(),
+        &0u64.to_string(),
     )
     .into_account()
     .unwrap();
@@ -479,7 +497,7 @@ fn should_track_token_balance_by_owner() {
 
     let install_request_builder =
         InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-            .with_total_token_supply(U256::from(2u32));
+            .with_total_token_supply(2u64);
     builder
         .exec(install_request_builder.build())
         .expect_success()
@@ -494,7 +512,7 @@ fn should_track_token_balance_by_owner() {
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -510,16 +528,14 @@ fn should_track_token_balance_by_owner() {
 
     let token_owner = DEFAULT_ACCOUNT_ADDR.clone().to_string();
 
-    let actual_minter_balance = support::get_dictionary_value_from_key::<U256>(
+    let actual_minter_balance = support::get_dictionary_value_from_key::<u64>(
         &builder,
         nft_contract_key,
         BALANCES,
         &token_owner,
     );
-    let expected_minter_balance = U256::one();
+    let expected_minter_balance = 1u64;
     assert_eq!(actual_minter_balance, expected_minter_balance);
-
-    // TODO: come up with better name than balance. Or not...
 }
 
 #[test]
@@ -528,7 +544,7 @@ fn should_allow_public_minting_with_flag_set_to_true() {
     builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
 
     let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-        .with_total_token_supply(U256::from(100u64))
+        .with_total_token_supply(100u64)
         .with_minting_mode(Some(MintingMode::Public as u8))
         .build();
     builder.exec(install_request).expect_success().commit();
@@ -578,7 +594,7 @@ fn should_allow_public_minting_with_flag_set_to_true() {
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(account_1_public_key.to_account_hash()),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -590,7 +606,7 @@ fn should_allow_public_minting_with_flag_set_to_true() {
         &builder,
         &nft_contract_key,
         TOKEN_OWNERS,
-        &U256::zero().to_string(),
+        &0u64.to_string(),
     )
     .into_account()
     .unwrap();
@@ -604,7 +620,7 @@ fn should_disallow_public_minting_with_flag_set_to_false() {
     builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
 
     let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-        .with_total_token_supply(U256::from(100u64))
+        .with_total_token_supply(100u64)
         .with_minting_mode(Some(MintingMode::Installer as u8))
         .build();
     builder.exec(install_request).expect_success().commit();
@@ -651,7 +667,7 @@ fn should_disallow_public_minting_with_flag_set_to_false() {
         runtime_args! {
             ARG_NFT_CONTRACT_HASH => *nft_contract_key,
             ARG_TOKEN_OWNER => Key::Account(account_1_public_key.to_account_hash()),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -666,7 +682,7 @@ fn should_allow_minting_for_different_public_key_with_minting_mode_set_to_public
     builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
 
     let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-        .with_total_token_supply(U256::from(100u64))
+        .with_total_token_supply(100u64)
         .with_minting_mode(Some(MintingMode::Public as u8))
         .build();
     builder.exec(install_request).expect_success().commit();
@@ -727,7 +743,7 @@ fn should_allow_minting_for_different_public_key_with_minting_mode_set_to_public
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(account_1_public_key.to_account_hash()),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -742,7 +758,7 @@ fn should_set_approval_for_all() {
     builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
 
     let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
-        .with_total_token_supply(U256::from(100u64))
+        .with_total_token_supply(100u64)
         .with_ownership_mode(OwnershipMode::Transferable)
         .build();
     builder.exec(install_request).expect_success().commit();
@@ -756,7 +772,7 @@ fn should_set_approval_for_all() {
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -770,7 +786,7 @@ fn should_set_approval_for_all() {
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -797,9 +813,10 @@ fn should_set_approval_for_all() {
     let actual_operator: Option<Key> = call_entry_point_with_ret(
         &mut builder,
         *DEFAULT_ACCOUNT_ADDR,
-        nft_contract_hash,
+        nft_contract_key,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
+            ARG_IS_HASH_IDENTIFIER_MODE => false,
+            ARG_TOKEN_ID => 0u64,
         },
         "get_approved_call.wasm",
         "get_approved",
@@ -815,9 +832,10 @@ fn should_set_approval_for_all() {
     let actual_operator: Option<Key> = call_entry_point_with_ret(
         &mut builder,
         *DEFAULT_ACCOUNT_ADDR,
-        nft_contract_hash,
+        nft_contract_key,
         runtime_args! {
-            ARG_TOKEN_ID => U256::one(),
+            ARG_IS_HASH_IDENTIFIER_MODE => false,
+            ARG_TOKEN_ID => 0u64,
         },
         "get_approved_call.wasm",
         "get_approved",
@@ -830,3 +848,518 @@ fn should_set_approval_for_all() {
         "actual and expected operator should be equal"
     );
 }
+
+#[test]
+fn should_allow_whitelisted_contract_to_mint() {
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    let minting_contract_install_request = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        MINTING_CONTRACT_WASM,
+        runtime_args! {},
+    )
+    .build();
+
+    builder
+        .exec(minting_contract_install_request)
+        .expect_success()
+        .commit();
+
+    let minting_contract_hash = get_minting_contract_hash(&builder);
+
+    let contract_whitelist = vec![minting_contract_hash];
+
+    let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+        .with_total_token_supply(100u64)
+        .with_holder_mode(NFTHolderMode::Contracts)
+        .with_whitelist_mode(WhitelistMode::Locked)
+        .with_ownership_mode(OwnershipMode::Minter)
+        .with_minting_mode(Some(MintingMode::Installer as u8))
+        .with_contract_whitelist(contract_whitelist.clone())
+        .build();
+
+    builder.exec(install_request).expect_success().commit();
+
+    let nft_contract_key: Key = get_nft_contract_hash(&builder).into();
+
+    let actual_contract_whitelist: Vec<ContractHash> = query_stored_value(
+        &mut builder,
+        nft_contract_key,
+        vec![ARG_CONTRACT_WHITELIST.to_string()],
+    );
+
+    assert_eq!(actual_contract_whitelist, contract_whitelist);
+
+    let mint_runtime_args = runtime_args! {
+        ARG_NFT_CONTRACT_HASH => nft_contract_key,
+        ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+        ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
+        ARG_TOKEN_URI => TEST_URI.to_string()
+    };
+
+    let mint_via_contract_call = ExecuteRequestBuilder::contract_call_by_hash(
+        *DEFAULT_ACCOUNT_ADDR,
+        minting_contract_hash,
+        ENTRY_POINT_MINT,
+        mint_runtime_args,
+    )
+    .build();
+
+    builder
+        .exec(mint_via_contract_call)
+        .expect_success()
+        .commit();
+
+    let token_id = 0u64.to_string();
+
+    let actual_token_owner: Key =
+        get_dictionary_value_from_key(&builder, &nft_contract_key, TOKEN_OWNERS, &token_id);
+
+    let minting_contract_key: Key = minting_contract_hash.into();
+
+    assert_eq!(actual_token_owner, minting_contract_key)
+}
+
+#[test]
+fn should_disallow_unlisted_contract_from_minting() {
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    let minting_contract_install_request = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        MINTING_CONTRACT_WASM,
+        runtime_args! {},
+    )
+    .build();
+
+    builder
+        .exec(minting_contract_install_request)
+        .expect_success()
+        .commit();
+
+    let minting_contract_hash = get_minting_contract_hash(&builder);
+    let contract_whitelist = vec![
+        ContractHash::from([1u8; 32]),
+        ContractHash::from([2u8; 32]),
+        ContractHash::from([3u8; 32]),
+    ];
+
+    let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+        .with_total_token_supply(100u64)
+        .with_holder_mode(NFTHolderMode::Contracts)
+        .with_whitelist_mode(WhitelistMode::Locked)
+        .with_ownership_mode(OwnershipMode::Minter)
+        .with_minting_mode(Some(MintingMode::Installer as u8))
+        .with_contract_whitelist(contract_whitelist)
+        .build();
+
+    builder.exec(install_request).expect_success().commit();
+
+    let nft_contract_key: Key = get_nft_contract_hash(&builder).into();
+
+    let mint_runtime_args = runtime_args! {
+        ARG_NFT_CONTRACT_HASH => nft_contract_key,
+        ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+        ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
+        ARG_TOKEN_URI => TEST_URI.to_string()
+    };
+
+    let mint_via_contract_call = ExecuteRequestBuilder::contract_call_by_hash(
+        *DEFAULT_ACCOUNT_ADDR,
+        minting_contract_hash,
+        ENTRY_POINT_MINT,
+        mint_runtime_args,
+    )
+    .build();
+
+    builder.exec(mint_via_contract_call).expect_failure();
+
+    let error = builder.get_error().expect("should have an error");
+    assert_expected_error(
+        error,
+        81,
+        "Unlisted contract hash should not be permitted to mint",
+    );
+}
+
+#[test]
+fn should_be_able_to_update_whitelist_for_minting() {
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    let minting_contract_install_request = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        MINTING_CONTRACT_WASM,
+        runtime_args! {},
+    )
+    .build();
+
+    builder
+        .exec(minting_contract_install_request)
+        .expect_success()
+        .commit();
+
+    let minting_contract_hash = get_minting_contract_hash(&builder);
+
+    let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+        .with_total_token_supply(100u64)
+        .with_holder_mode(NFTHolderMode::Contracts)
+        .with_whitelist_mode(WhitelistMode::Unlocked)
+        .with_ownership_mode(OwnershipMode::Minter)
+        .with_minting_mode(Some(MintingMode::Installer as u8))
+        .with_contract_whitelist(vec![])
+        .build();
+
+    builder.exec(install_request).expect_success().commit();
+
+    let nft_contract_hash = get_nft_contract_hash(&builder);
+    let nft_contract_key = nft_contract_hash.into();
+
+    let current_contract_whitelist: Vec<ContractHash> = query_stored_value(
+        &mut builder,
+        nft_contract_key,
+        vec![ARG_CONTRACT_WHITELIST.to_string()],
+    );
+
+    assert!(current_contract_whitelist.is_empty());
+    let mint_runtime_args = runtime_args! {
+        ARG_NFT_CONTRACT_HASH => nft_contract_key,
+        ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+        ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
+        ARG_TOKEN_URI => TEST_URI.to_string()
+    };
+
+    let mint_via_contract_call = ExecuteRequestBuilder::contract_call_by_hash(
+        *DEFAULT_ACCOUNT_ADDR,
+        minting_contract_hash,
+        ENTRY_POINT_MINT,
+        mint_runtime_args.clone(),
+    )
+    .build();
+
+    builder.exec(mint_via_contract_call).expect_failure();
+
+    let error = builder.get_error().expect("should have an error");
+    assert_expected_error(
+        error,
+        81,
+        "Unlisted contract hash should not be permitted to mint",
+    );
+
+    let update_whitelist_request = ExecuteRequestBuilder::contract_call_by_hash(
+        *DEFAULT_ACCOUNT_ADDR,
+        nft_contract_hash,
+        ENTRY_POINT_SET_VARIABLES,
+        runtime_args! {
+            ARG_CONTRACT_WHITELIST => Some(vec![minting_contract_hash])
+        },
+    )
+    .build();
+
+    builder
+        .exec(update_whitelist_request)
+        .expect_success()
+        .commit();
+
+    let updated_contract_whitelist: Vec<ContractHash> = query_stored_value(
+        &mut builder,
+        nft_contract_key,
+        vec![ARG_CONTRACT_WHITELIST.to_string()],
+    );
+
+    assert_eq!(vec![minting_contract_hash], updated_contract_whitelist);
+
+    let mint_via_contract_call = ExecuteRequestBuilder::contract_call_by_hash(
+        *DEFAULT_ACCOUNT_ADDR,
+        minting_contract_hash,
+        ENTRY_POINT_MINT,
+        mint_runtime_args,
+    )
+    .build();
+
+    builder
+        .exec(mint_via_contract_call)
+        .expect_success()
+        .commit();
+}
+
+#[test]
+fn should_not_mint_with_invalid_nft721_metadata() {
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    let install_request_builder =
+        InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+            .with_total_token_supply(2u64);
+    builder
+        .exec(install_request_builder.build())
+        .expect_success()
+        .commit();
+
+    let nft_contract_key: Key = get_nft_contract_hash(&builder).into();
+
+    let mint_session_call = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        MINT_SESSION_WASM,
+        runtime_args! {
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
+            ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+            ARG_TOKEN_META_DATA => MALFORMED_META_DATA,
+            ARG_TOKEN_URI => TEST_URI.to_string()
+        },
+    )
+    .build();
+
+    builder.exec(mint_session_call).expect_failure();
+
+    let error = builder.get_error().expect("mint request must have failed");
+    assert_expected_error(
+        error,
+        89,
+        "FailedToParse721Metadata error (89) must have been raised due to mangled metadata",
+    )
+}
+
+#[test]
+fn should_mint_with_compactified_metadata() {
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    let install_request_builder =
+        InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+            .with_total_token_supply(2u64)
+            .build();
+
+    builder
+        .exec(install_request_builder)
+        .expect_success()
+        .commit();
+
+    let nft_contract_key: Key = get_nft_contract_hash(&builder).into();
+
+    let mint_session_call = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        MINT_SESSION_WASM,
+        runtime_args! {
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
+            ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+            ARG_TOKEN_META_DATA => TEST_COMPACT_META_DATA,
+            ARG_TOKEN_URI => TEST_URI.to_string()
+        },
+    )
+    .build();
+
+    builder.exec(mint_session_call).expect_success().commit();
+
+    let actual_metadata = get_dictionary_value_from_key::<String>(
+        &builder,
+        &nft_contract_key,
+        METADATA_NFT721,
+        &0u64.to_string(),
+    );
+
+    assert_eq!(TEST_PRETTY_721_META_DATA, actual_metadata)
+}
+
+#[test]
+fn should_mint_with_valid_cep99_metadata() {
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+        .with_total_token_supply(2u64)
+        .with_nft_metadata_kind(NFTMetadataKind::CEP99)
+        .build();
+
+    builder.exec(install_request).expect_success().commit();
+
+    let nft_contract_key: Key = get_nft_contract_hash(&builder).into();
+
+    let mint_session_call = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        MINT_SESSION_WASM,
+        runtime_args! {
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
+            ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_CEP78_METADATA,
+            ARG_TOKEN_URI => TEST_URI.to_string()
+        },
+    )
+    .build();
+
+    builder.exec(mint_session_call).expect_success().commit();
+
+    let actual_metadata = get_dictionary_value_from_key::<String>(
+        &builder,
+        &nft_contract_key,
+        METADATA_CEP78,
+        &0u64.to_string(),
+    );
+
+    assert_eq!(TEST_PRETTY_CEP78_METADATA, actual_metadata)
+}
+
+#[test]
+fn should_mint_with_custom_metadata_validation() {
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    let custom_json_schema =
+        serde_json::to_string(&*TEST_CUSTOM_METADATA_SCHEMA).expect("must convert to json schema");
+
+    let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+        .with_total_token_supply(2u64)
+        .with_nft_metadata_kind(NFTMetadataKind::CustomValidated)
+        .with_json_schema(custom_json_schema)
+        .build();
+
+    builder.exec(install_request).expect_success().commit();
+
+    let nft_contract_key: Key = get_nft_contract_hash(&builder).into();
+
+    let custom_metadata =
+        serde_json::to_string(&*TEST_CUSTOM_METADATA).expect("must convert to json metadata");
+
+    let mint_session_call = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        MINT_SESSION_WASM,
+        runtime_args! {
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
+            ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+            ARG_TOKEN_META_DATA => custom_metadata ,
+            ARG_TOKEN_URI => TEST_URI.to_string()
+        },
+    )
+    .build();
+
+    builder.exec(mint_session_call).expect_success().commit();
+
+    let actual_metadata = get_dictionary_value_from_key::<String>(
+        &builder,
+        &nft_contract_key,
+        METADATA_CUSTOM_VALIDATED,
+        &0u64.to_string(),
+    );
+
+    let pretty_custom_metadata = serde_json::to_string_pretty(&*TEST_CUSTOM_METADATA)
+        .expect("must convert to json metadata");
+
+    assert_eq!(pretty_custom_metadata, actual_metadata)
+}
+
+#[test]
+fn should_mint_with_raw_metadata() {
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+        .with_total_token_supply(2u64)
+        .with_nft_metadata_kind(NFTMetadataKind::Raw)
+        .build();
+
+    builder.exec(install_request).expect_success().commit();
+
+    let nft_contract_key: Key = get_nft_contract_hash(&builder).into();
+
+    let mint_session_call = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        MINT_SESSION_WASM,
+        runtime_args! {
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
+            ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+            ARG_TOKEN_META_DATA => "raw_string".to_string() ,
+        },
+    )
+    .build();
+
+    builder.exec(mint_session_call).expect_success().commit();
+
+    let actual_metadata = get_dictionary_value_from_key::<String>(
+        &builder,
+        &nft_contract_key,
+        METADATA_RAW,
+        &0u64.to_string(),
+    );
+
+    assert_eq!("raw_string".to_string(), actual_metadata)
+}
+
+#[test]
+fn should_mint_with_hash_identifier_mode() {
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+        .with_identifier_mode(NFTIdentifierMode::Hash)
+        .with_total_token_supply(10u64)
+        .build();
+
+    builder.exec(install_request).expect_success().commit();
+
+    let nft_contract_key: Key = get_nft_contract_hash(&builder).into();
+
+    let mint_session_call = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        MINT_SESSION_WASM,
+        runtime_args! {
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA ,
+            ARG_TOKEN_URI => TEST_URI.to_string()
+        },
+    )
+    .build();
+
+    builder.exec(mint_session_call).expect_success().commit();
+
+    let token_id_hash: String =
+        base16::encode_lower(&support::create_blake2b_hash(&TEST_PRETTY_721_META_DATA));
+
+    let actual_token_ids = get_dictionary_value_from_key::<Vec<String>>(
+        &builder,
+        &nft_contract_key,
+        OWNED_TOKENS,
+        &DEFAULT_ACCOUNT_ADDR.clone().to_string(),
+    );
+
+    assert_eq!(vec![token_id_hash], actual_token_ids);
+}
+
+#[test]
+fn should_fail_to_mint_when_immediate_caller_is_account_in_contract_mode() {
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+        .with_total_token_supply(2u64)
+        .with_holder_mode(NFTHolderMode::Contracts)
+        .with_whitelist_mode(WhitelistMode::Unlocked)
+        .build();
+
+    builder.exec(install_request).expect_success().commit();
+
+    let nft_contract_key: Key = get_nft_contract_hash(&builder).into();
+
+    let mint_session_call = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        MINT_SESSION_WASM,
+        runtime_args! {
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+            ARG_TOKEN_META_DATA => TEST_COMPACT_META_DATA,
+        },
+    )
+    .build();
+
+    builder.exec(mint_session_call).expect_failure();
+
+    let error = builder.get_error().expect("must have error");
+
+    assert_expected_error(error, 76, "InvalidHolderMode(76) must have been raised");
+}
diff --git a/tests/src/set_variables.rs b/tests/src/set_variables.rs
index 2d85459f..d5dfcb9a 100644
--- a/tests/src/set_variables.rs
+++ b/tests/src/set_variables.rs
@@ -2,7 +2,7 @@ use casper_engine_test_support::{
     ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_ACCOUNT_ADDR,
     DEFAULT_RUN_GENESIS_REQUEST,
 };
-use casper_types::{runtime_args, ContractHash, Key, RuntimeArgs, U256};
+use casper_types::{runtime_args, ContractHash, Key, RuntimeArgs};
 
 use crate::utility::{
     constants::{
@@ -23,7 +23,7 @@ fn only_installer_should_be_able_to_toggle_allow_minting() {
     let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
         .with_collection_name(NFT_TEST_COLLECTION.to_string())
         .with_collection_symbol(NFT_TEST_SYMBOL.to_string())
-        .with_total_token_supply(U256::one())
+        .with_total_token_supply(1u64)
         .with_allowing_minting(Some(false))
         .build();
 
diff --git a/tests/src/transfer.rs b/tests/src/transfer.rs
index e5d8f1ab..59908bb3 100644
--- a/tests/src/transfer.rs
+++ b/tests/src/transfer.rs
@@ -1,20 +1,31 @@
 use casper_engine_test_support::{
     ExecuteRequestBuilder, InMemoryWasmTestBuilder, DEFAULT_ACCOUNT_ADDR,
-    DEFAULT_ACCOUNT_PUBLIC_KEY, DEFAULT_RUN_GENESIS_REQUEST,
+    DEFAULT_ACCOUNT_PUBLIC_KEY, DEFAULT_RUN_GENESIS_REQUEST, MINIMUM_ACCOUNT_CREATION_BALANCE,
+};
+use casper_types::{
+    account::AccountHash, runtime_args, system::mint, ContractHash, Key, PublicKey, RuntimeArgs,
+    SecretKey, U512,
 };
-use casper_types::{runtime_args, system::mint, ContractHash, Key, RuntimeArgs, U256};
 
 use crate::utility::{
     constants::{
-        ACCOUNT_USER_1, ACCOUNT_USER_2, ACCOUNT_USER_3, ARG_FROM_ACCOUNT_HASH, ARG_KEY_NAME,
-        ARG_NFT_CONTRACT_HASH, ARG_OPERATOR, ARG_TOKEN_ID, ARG_TOKEN_META_DATA, ARG_TOKEN_OWNER,
-        ARG_TOKEN_URI, ARG_TO_ACCOUNT_HASH, BALANCES, CONTRACT_NAME, ENTRY_POINT_APPROVE,
-        ENTRY_POINT_TRANSFER, MINT_SESSION_WASM, NFT_CONTRACT_WASM, NFT_TEST_COLLECTION,
-        NFT_TEST_SYMBOL, OPERATOR, OWNED_TOKENS, OWNED_TOKENS_DICTIONARY_KEY, TEST_META_DATA,
-        TEST_URI, TOKEN_OWNERS,
+        ACCOUNT_USER_1, ACCOUNT_USER_2, ACCOUNT_USER_3, ARG_CONTRACT_WHITELIST,
+        ARG_IS_HASH_IDENTIFIER_MODE, ARG_KEY_NAME, ARG_NFT_CONTRACT_HASH, ARG_OPERATOR,
+        ARG_SOURCE_KEY, ARG_TARGET_KEY, ARG_TOKEN_HASH, ARG_TOKEN_ID, ARG_TOKEN_META_DATA,
+        ARG_TOKEN_OWNER, ARG_TOKEN_URI, BALANCES, CONTRACT_NAME, ENTRY_POINT_APPROVE,
+        ENTRY_POINT_MINT, ENTRY_POINT_TRANSFER, MINTING_CONTRACT_WASM, MINT_SESSION_WASM,
+        NFT_CONTRACT_WASM, NFT_TEST_COLLECTION, NFT_TEST_SYMBOL, OPERATOR, OWNED_TOKENS,
+        OWNED_TOKENS_DICTIONARY_KEY, TEST_PRETTY_721_META_DATA, TEST_URI, TOKEN_OWNERS,
+        TRANSFER_SESSION_WASM,
+    },
+    installer_request_builder::{
+        InstallerRequestBuilder, MintingMode, NFTHolderMode, NFTIdentifierMode, OwnershipMode,
+        WhitelistMode,
+    },
+    support::{
+        self, assert_expected_error, get_dictionary_value_from_key, get_minting_contract_hash,
+        get_nft_contract_hash, query_stored_value,
     },
-    installer_request_builder::{InstallerRequestBuilder, OwnershipMode},
-    support::{self, get_dictionary_value_from_key, get_nft_contract_hash},
 };
 
 #[test]
@@ -25,7 +36,7 @@ fn should_dissallow_transfer_with_minter_or_assigned_ownership_mode() {
     let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
         .with_collection_name(NFT_TEST_COLLECTION.to_string())
         .with_collection_symbol(NFT_TEST_SYMBOL.to_string())
-        .with_total_token_supply(U256::from(1u64))
+        .with_total_token_supply(1u64)
         .with_ownership_mode(OwnershipMode::Minter)
         .build();
 
@@ -51,7 +62,7 @@ fn should_dissallow_transfer_with_minter_or_assigned_ownership_mode() {
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -65,13 +76,13 @@ fn should_dissallow_transfer_with_minter_or_assigned_ownership_mode() {
         .get(CONTRACT_NAME)
         .expect("must have key in named keys");
 
-    let actual_owner_balance: U256 = support::get_dictionary_value_from_key(
+    let actual_owner_balance: u64 = support::get_dictionary_value_from_key(
         &builder,
         nft_contract_key,
         BALANCES,
         &token_owner.to_string(),
     );
-    let expected_owner_balance = U256::one();
+    let expected_owner_balance = 1u64;
     assert_eq!(actual_owner_balance, expected_owner_balance);
 
     let (_, token_receiver) = support::create_dummy_key_pair(ACCOUNT_USER_1);
@@ -80,9 +91,10 @@ fn should_dissallow_transfer_with_minter_or_assigned_ownership_mode() {
         nft_contract_hash,
         ENTRY_POINT_TRANSFER,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),// We need mint to return the token_id!!
-            ARG_FROM_ACCOUNT_HASH => Key::Account(token_owner),
-            ARG_TO_ACCOUNT_HASH =>  Key::Account( token_receiver.to_account_hash()),
+            ARG_SOURCE_KEY => Key::Account(token_owner),
+            ARG_TARGET_KEY =>  Key::Account( token_receiver.to_account_hash()),
+            ARG_IS_HASH_IDENTIFIER_MODE => false,
+            ARG_TOKEN_ID => 0u64,
         },
     )
     .build();
@@ -104,21 +116,12 @@ fn should_transfer_token_from_sender_to_receiver() {
     let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
         .with_collection_name(NFT_TEST_COLLECTION.to_string())
         .with_collection_symbol(NFT_TEST_SYMBOL.to_string())
-        .with_total_token_supply(U256::from(1u64))
+        .with_total_token_supply(1u64)
         .with_ownership_mode(OwnershipMode::Transferable)
         .build();
 
     builder.exec(install_request).expect_success().commit();
 
-    let account = builder.get_expected_account(*DEFAULT_ACCOUNT_ADDR);
-    let nft_contract_hash = account
-        .named_keys()
-        .get(CONTRACT_NAME)
-        .cloned()
-        .and_then(Key::into_hash)
-        .map(ContractHash::new)
-        .expect("failed to find nft contract");
-
     let token_owner = *DEFAULT_ACCOUNT_ADDR;
 
     let nft_contract_key: Key = get_nft_contract_hash(&builder).into();
@@ -130,7 +133,7 @@ fn should_transfer_token_from_sender_to_receiver() {
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -139,29 +142,30 @@ fn should_transfer_token_from_sender_to_receiver() {
     builder.exec(mint_session_call).expect_success().commit();
 
     let installing_account = builder.get_expected_account(*DEFAULT_ACCOUNT_ADDR);
-    let nft_contract_key = installing_account
+    let nft_contract_key = *installing_account
         .named_keys()
         .get(CONTRACT_NAME)
         .expect("must have key in named keys");
 
-    let actual_owner_balance: U256 = support::get_dictionary_value_from_key(
+    let actual_owner_balance: u64 = support::get_dictionary_value_from_key(
         &builder,
-        nft_contract_key,
+        &nft_contract_key,
         BALANCES,
         &token_owner.to_string(),
     );
-    let expected_owner_balance = U256::one();
+    let expected_owner_balance = 1u64;
     assert_eq!(actual_owner_balance, expected_owner_balance);
 
     let (_, token_receiver) = support::create_dummy_key_pair(ACCOUNT_USER_1);
-    let transfer_request = ExecuteRequestBuilder::contract_call_by_hash(
+    let transfer_request = ExecuteRequestBuilder::standard(
         *DEFAULT_ACCOUNT_ADDR,
-        nft_contract_hash,
-        ENTRY_POINT_TRANSFER,
+        TRANSFER_SESSION_WASM,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),// We need mint to return the token_id!!
-            ARG_FROM_ACCOUNT_HASH => Key::Account(token_owner),
-            ARG_TO_ACCOUNT_HASH =>  Key::Account( token_receiver.to_account_hash()),
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_TOKEN_ID => 0u64,
+            ARG_IS_HASH_IDENTIFIER_MODE => false,
+            ARG_SOURCE_KEY => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+            ARG_TARGET_KEY =>  Key::Account(token_receiver.to_account_hash()),
         },
     )
     .build();
@@ -169,41 +173,40 @@ fn should_transfer_token_from_sender_to_receiver() {
 
     let actual_token_owner = support::get_dictionary_value_from_key::<Key>(
         &builder,
-        nft_contract_key,
+        &nft_contract_key,
         TOKEN_OWNERS,
-        &U256::zero().to_string(),
+        &0u64.to_string(),
     )
     .into_account()
     .unwrap();
 
     assert_eq!(actual_token_owner, token_receiver.to_account_hash()); // Change  token_receiver to token_owner for red test
 
-    let actual_owned_tokens: Vec<U256> = support::get_dictionary_value_from_key(
+    let actual_owned_tokens: Vec<u64> = support::get_dictionary_value_from_key(
         &builder,
-        nft_contract_key,
+        &nft_contract_key,
         OWNED_TOKENS,
         &token_receiver.to_account_hash().to_string(),
     );
 
-    let expected_owned_tokens = vec![U256::zero()]; //Change zero() to one() for red test
-    assert_eq!(actual_owned_tokens, expected_owned_tokens);
+    assert_eq!(actual_owned_tokens, vec![0u64]);
 
-    let actual_sender_balance: U256 = support::get_dictionary_value_from_key(
+    let actual_sender_balance: u64 = support::get_dictionary_value_from_key(
         &builder,
-        nft_contract_key,
+        &nft_contract_key,
         BALANCES,
         &token_owner.to_string(),
     );
-    let expected_sender_balance = U256::zero();
+    let expected_sender_balance = 0u64;
     assert_eq!(actual_sender_balance, expected_sender_balance);
 
-    let actual_receiver_balance: U256 = support::get_dictionary_value_from_key(
+    let actual_receiver_balance: u64 = support::get_dictionary_value_from_key(
         &builder,
-        nft_contract_key,
+        &nft_contract_key,
         BALANCES,
         &token_receiver.to_account_hash().to_string(),
     );
-    let expected_receiver_balance = U256::one();
+    let expected_receiver_balance = 1u64;
     assert_eq!(actual_receiver_balance, expected_receiver_balance);
 }
 
@@ -215,7 +218,7 @@ fn approve_token_for_transfer_should_add_entry_to_approved_dictionary() {
     let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
         .with_collection_name(NFT_TEST_COLLECTION.to_string())
         .with_collection_symbol(NFT_TEST_SYMBOL.to_string())
-        .with_total_token_supply(U256::one())
+        .with_total_token_supply(1u64)
         .with_ownership_mode(OwnershipMode::Transferable)
         .build();
 
@@ -239,7 +242,7 @@ fn approve_token_for_transfer_should_add_entry_to_approved_dictionary() {
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -253,7 +256,7 @@ fn approve_token_for_transfer_should_add_entry_to_approved_dictionary() {
         nft_contract_hash,
         ENTRY_POINT_APPROVE,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
+            ARG_TOKEN_ID => 0u64,
             ARG_OPERATOR => Key::Account(approve_public_key.to_account_hash())
         },
     )
@@ -270,7 +273,7 @@ fn approve_token_for_transfer_should_add_entry_to_approved_dictionary() {
         &builder,
         nft_contract_key,
         OPERATOR,
-        &U256::zero().to_string(),
+        &0u64.to_string(),
     );
 
     assert_eq!(
@@ -287,7 +290,7 @@ fn should_dissallow_approving_when_ownership_mode_is_minter_or_assigned() {
     let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
         .with_collection_name(NFT_TEST_COLLECTION.to_string())
         .with_collection_symbol(NFT_TEST_SYMBOL.to_string())
-        .with_total_token_supply(U256::one())
+        .with_total_token_supply(1u64)
         .with_ownership_mode(OwnershipMode::Assigned)
         .build();
 
@@ -311,7 +314,7 @@ fn should_dissallow_approving_when_ownership_mode_is_minter_or_assigned() {
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
 
         },
@@ -326,7 +329,7 @@ fn should_dissallow_approving_when_ownership_mode_is_minter_or_assigned() {
         nft_contract_hash,
         ENTRY_POINT_APPROVE,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
+            ARG_TOKEN_ID => 0u64,
             ARG_OPERATOR => Key::Account(approve_public_key.to_account_hash())
         },
     )
@@ -349,7 +352,7 @@ fn should_be_able_to_transfer_token_using_approved_operator() {
     let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
         .with_collection_name(NFT_TEST_COLLECTION.to_string())
         .with_collection_symbol(NFT_TEST_SYMBOL.to_string())
-        .with_total_token_supply(U256::one())
+        .with_total_token_supply(1u64)
         .with_ownership_mode(OwnershipMode::Transferable)
         .build();
 
@@ -374,7 +377,7 @@ fn should_be_able_to_transfer_token_using_approved_operator() {
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -401,7 +404,7 @@ fn should_be_able_to_transfer_token_using_approved_operator() {
         nft_contract_hash,
         ENTRY_POINT_APPROVE,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
+            ARG_TOKEN_ID => 0u64,
             ARG_OPERATOR => Key::Account (operator.to_account_hash())
         },
     )
@@ -409,16 +412,12 @@ fn should_be_able_to_transfer_token_using_approved_operator() {
     builder.exec(approve_request).expect_success().commit();
 
     let installing_account = builder.get_expected_account(*DEFAULT_ACCOUNT_ADDR);
-    let nft_contract_key = installing_account
+    let nft_contract_key = *installing_account
         .named_keys()
         .get(CONTRACT_NAME)
         .expect("must have key in named keys");
-    let actual_operator: Option<Key> = get_dictionary_value_from_key(
-        &builder,
-        nft_contract_key,
-        OPERATOR,
-        &U256::zero().to_string(),
-    );
+    let actual_operator: Option<Key> =
+        get_dictionary_value_from_key(&builder, &nft_contract_key, OPERATOR, &0u64.to_string());
 
     let expected_operator = Some(Key::Account(operator.to_account_hash()));
     assert_eq!(
@@ -441,26 +440,23 @@ fn should_be_able_to_transfer_token_using_approved_operator() {
         .exec(transfer_to_to_account)
         .expect_success()
         .commit();
-
-    let transfer_request = ExecuteRequestBuilder::contract_call_by_hash(
-        operator.to_account_hash(),
-        nft_contract_hash,
-        ENTRY_POINT_TRANSFER,
+    //
+    let transfer_request = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        TRANSFER_SESSION_WASM,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
-            ARG_FROM_ACCOUNT_HASH =>  Key::Account(token_owner),
-            ARG_TO_ACCOUNT_HASH => Key::Account( to_account_public_key.to_account_hash()),
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_SOURCE_KEY =>  Key::Account(token_owner),
+            ARG_TARGET_KEY => Key::Account(to_account_public_key.to_account_hash()),
+            ARG_IS_HASH_IDENTIFIER_MODE => false,
+            ARG_TOKEN_ID => 0u64,
         },
     )
     .build();
     builder.exec(transfer_request).expect_success().commit();
 
-    let actual_approved_account_hash: Option<Key> = get_dictionary_value_from_key(
-        &builder,
-        nft_contract_key,
-        OPERATOR,
-        &U256::zero().to_string(),
-    );
+    let actual_approved_account_hash: Option<Key> =
+        get_dictionary_value_from_key(&builder, &nft_contract_key, OPERATOR, &0u64.to_string());
 
     assert_eq!(
         actual_approved_account_hash, None,
@@ -476,7 +472,7 @@ fn should_dissallow_same_operator_to_tranfer_token_twice() {
     let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
         .with_collection_name(NFT_TEST_COLLECTION.to_string())
         .with_collection_symbol(NFT_TEST_SYMBOL.to_string())
-        .with_total_token_supply(U256::one())
+        .with_total_token_supply(1u64)
         .with_ownership_mode(OwnershipMode::Transferable)
         .build();
 
@@ -501,7 +497,7 @@ fn should_dissallow_same_operator_to_tranfer_token_twice() {
             ARG_NFT_CONTRACT_HASH => nft_contract_key,
             ARG_KEY_NAME => Some(OWNED_TOKENS_DICTIONARY_KEY.to_string()),
             ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
-            ARG_TOKEN_META_DATA => TEST_META_DATA.to_string(),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
             ARG_TOKEN_URI => TEST_URI.to_string()
         },
     )
@@ -528,7 +524,7 @@ fn should_dissallow_same_operator_to_tranfer_token_twice() {
         nft_contract_hash,
         ENTRY_POINT_APPROVE,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
+            ARG_TOKEN_ID => 0u64,
             ARG_OPERATOR => Key::Account (operator.to_account_hash())
         },
     )
@@ -536,16 +532,12 @@ fn should_dissallow_same_operator_to_tranfer_token_twice() {
     builder.exec(approve_request).expect_success().commit();
 
     let installing_account = builder.get_expected_account(*DEFAULT_ACCOUNT_ADDR);
-    let nft_contract_key = installing_account
+    let nft_contract_key = *installing_account
         .named_keys()
         .get(CONTRACT_NAME)
         .expect("must have key in named keys");
-    let actual_operator: Option<Key> = get_dictionary_value_from_key(
-        &builder,
-        nft_contract_key,
-        OPERATOR,
-        &U256::zero().to_string(),
-    );
+    let actual_operator: Option<Key> =
+        get_dictionary_value_from_key(&builder, &nft_contract_key, OPERATOR, &0u64.to_string());
 
     let expected_operator = Some(Key::Account(operator.to_account_hash()));
 
@@ -570,30 +562,259 @@ fn should_dissallow_same_operator_to_tranfer_token_twice() {
         .expect_success()
         .commit();
 
-    let transfer_request = ExecuteRequestBuilder::contract_call_by_hash(
-        operator.to_account_hash(),
-        nft_contract_hash,
-        ENTRY_POINT_TRANSFER,
+    let transfer_request = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        TRANSFER_SESSION_WASM,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
-            ARG_FROM_ACCOUNT_HASH =>  Key::Account(token_owner),
-            ARG_TO_ACCOUNT_HASH => Key::Account( to_account_public_key.to_account_hash()),
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_TOKEN_ID => 0u64,
+            ARG_IS_HASH_IDENTIFIER_MODE => false,
+            ARG_SOURCE_KEY =>  Key::Account(token_owner),
+            ARG_TARGET_KEY => Key::Account(to_account_public_key.to_account_hash()),
         },
     )
     .build();
     builder.exec(transfer_request).expect_success().commit();
 
     let (_, to_other_account_public_key) = support::create_dummy_key_pair(ACCOUNT_USER_3);
+    let transfer_request = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        TRANSFER_SESSION_WASM,
+        runtime_args! {
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_TOKEN_ID => 0u64,
+            ARG_IS_HASH_IDENTIFIER_MODE => false,
+            ARG_SOURCE_KEY =>  Key::Account(token_owner),
+            ARG_TARGET_KEY => Key::Account(to_other_account_public_key.to_account_hash()),
+        },
+    )
+    .build();
+    builder.exec(transfer_request).expect_failure();
+}
+
+#[test]
+fn should_transfer_between_contract_to_account() {
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    let minting_contract_install_request = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        MINTING_CONTRACT_WASM,
+        runtime_args! {},
+    )
+    .build();
+
+    builder
+        .exec(minting_contract_install_request)
+        .expect_success()
+        .commit();
+
+    let minting_contract_hash = get_minting_contract_hash(&builder);
+    let minting_contract_key: Key = minting_contract_hash.into();
+
+    let contract_whitelist = vec![minting_contract_hash];
+
+    let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+        .with_total_token_supply(100u64)
+        .with_holder_mode(NFTHolderMode::Contracts)
+        .with_whitelist_mode(WhitelistMode::Locked)
+        .with_ownership_mode(OwnershipMode::Transferable)
+        .with_minting_mode(Some(MintingMode::Installer as u8))
+        .with_contract_whitelist(contract_whitelist.clone())
+        .build();
+
+    builder.exec(install_request).expect_success().commit();
+
+    let nft_contract_key: Key = get_nft_contract_hash(&builder).into();
+
+    let actual_contract_whitelist: Vec<ContractHash> = query_stored_value(
+        &mut builder,
+        nft_contract_key,
+        vec![ARG_CONTRACT_WHITELIST.to_string()],
+    );
+
+    assert_eq!(actual_contract_whitelist, contract_whitelist);
+
+    let mint_runtime_args = runtime_args! {
+        ARG_NFT_CONTRACT_HASH => nft_contract_key,
+        ARG_TOKEN_OWNER => minting_contract_key,
+        ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
+        ARG_TOKEN_URI => TEST_URI.to_string()
+    };
+
+    let minting_request = ExecuteRequestBuilder::contract_call_by_hash(
+        *DEFAULT_ACCOUNT_ADDR,
+        minting_contract_hash,
+        ENTRY_POINT_MINT,
+        mint_runtime_args,
+    )
+    .build();
+
+    builder.exec(minting_request).expect_success().commit();
+
+    let token_id = 0u64.to_string();
+
+    let actual_token_owner: Key =
+        get_dictionary_value_from_key(&builder, &nft_contract_key, TOKEN_OWNERS, &token_id);
+
+    assert_eq!(minting_contract_key, actual_token_owner);
+
+    let transfer_runtime_arguments = runtime_args! {
+        ARG_NFT_CONTRACT_HASH => nft_contract_key,
+        ARG_TOKEN_ID => 0u64,
+        ARG_TARGET_KEY => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+        ARG_SOURCE_KEY => minting_contract_key
+    };
+
     let transfer_request = ExecuteRequestBuilder::contract_call_by_hash(
-        operator.to_account_hash(),
-        nft_contract_hash,
+        *DEFAULT_ACCOUNT_ADDR,
+        minting_contract_hash,
         ENTRY_POINT_TRANSFER,
+        transfer_runtime_arguments,
+    )
+    .build();
+
+    builder.exec(transfer_request).expect_success().commit();
+
+    let updated_token_owner: Key =
+        get_dictionary_value_from_key(&builder, &nft_contract_key, TOKEN_OWNERS, &token_id);
+
+    assert_eq!(Key::Account(*DEFAULT_ACCOUNT_ADDR), updated_token_owner);
+}
+
+#[test]
+fn should_prevent_transfer_when_caller_is_not_owner() {
+    const ARG_AMOUNT: &str = "amount";
+    const ARG_TARGET: &str = "target";
+    const ARG_ID: &str = "id";
+    const ID_NONE: Option<u64> = None;
+
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    // Create an account that is not the owner of the NFT to transfer the token itself.
+    let other_account_secret_key = SecretKey::ed25519_from_bytes([9u8; 32]).unwrap();
+    let other_account_public_key = PublicKey::from(&other_account_secret_key);
+
+    let other_account_fund_request = ExecuteRequestBuilder::transfer(
+        *DEFAULT_ACCOUNT_ADDR,
         runtime_args! {
-            ARG_TOKEN_ID => U256::zero(),
-            ARG_FROM_ACCOUNT_HASH =>  Key::Account(token_owner),
-            ARG_TO_ACCOUNT_HASH => Key::Account( to_other_account_public_key.to_account_hash()),
+            ARG_TARGET => other_account_public_key.to_account_hash(),
+            ARG_AMOUNT => U512::from(MINIMUM_ACCOUNT_CREATION_BALANCE),
+            ARG_ID => ID_NONE
         },
     )
     .build();
-    builder.exec(transfer_request).expect_failure();
+
+    builder
+        .exec(other_account_fund_request)
+        .expect_success()
+        .commit();
+
+    let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+        .with_collection_name(NFT_TEST_COLLECTION.to_string())
+        .with_collection_symbol(NFT_TEST_SYMBOL.to_string())
+        .with_total_token_supply(100u64)
+        .with_ownership_mode(OwnershipMode::Transferable)
+        .with_holder_mode(NFTHolderMode::Accounts)
+        .build();
+
+    builder.exec(install_request).expect_success().commit();
+
+    let nft_contract_hash = get_nft_contract_hash(&builder);
+
+    let nft_contract_key: Key = nft_contract_hash.into();
+
+    let mint_session_call = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        MINT_SESSION_WASM,
+        runtime_args! {
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA.to_string(),
+            ARG_TOKEN_URI => TEST_URI.to_string()
+        },
+    )
+    .build();
+
+    builder.exec(mint_session_call).expect_success().commit();
+
+    let actual_token_owner: Key =
+        get_dictionary_value_from_key(&builder, &nft_contract_key, TOKEN_OWNERS, &0u64.to_string());
+
+    assert_eq!(Key::Account(*DEFAULT_ACCOUNT_ADDR), actual_token_owner);
+
+    let unauthorized_transfer = ExecuteRequestBuilder::standard(
+        other_account_public_key.to_account_hash(),
+        TRANSFER_SESSION_WASM,
+        runtime_args! {
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_TOKEN_ID => 0u64,
+            ARG_IS_HASH_IDENTIFIER_MODE => false,
+            ARG_SOURCE_KEY => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+            ARG_TARGET_KEY => Key::Account(other_account_public_key.to_account_hash())
+        },
+    )
+    .build();
+
+    builder.exec(unauthorized_transfer).expect_failure();
+
+    let error = builder
+        .get_error()
+        .expect("previous execution must have failed");
+
+    assert_expected_error(
+        error,
+        1u16,
+        "transfer from another account must raise InvalidAccount",
+    );
+}
+
+#[test]
+fn should_transfer_token_in_hash_identifier_mode() {
+    let mut builder = InMemoryWasmTestBuilder::default();
+    builder.run_genesis(&DEFAULT_RUN_GENESIS_REQUEST).commit();
+
+    let install_request = InstallerRequestBuilder::new(*DEFAULT_ACCOUNT_ADDR, NFT_CONTRACT_WASM)
+        .with_identifier_mode(NFTIdentifierMode::Hash)
+        .with_ownership_mode(OwnershipMode::Transferable)
+        .with_total_token_supply(10u64)
+        .build();
+
+    builder.exec(install_request).expect_success().commit();
+
+    let nft_contract_hash = get_nft_contract_hash(&builder);
+    let nft_contract_key: Key = nft_contract_hash.into();
+
+    let mint_session_call = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        MINT_SESSION_WASM,
+        runtime_args! {
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_TOKEN_OWNER => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+            ARG_TOKEN_META_DATA => TEST_PRETTY_721_META_DATA ,
+            ARG_TOKEN_URI => TEST_URI.to_string()
+        },
+    )
+    .build();
+
+    builder.exec(mint_session_call).expect_success().commit();
+
+    let token_hash: String =
+        base16::encode_lower(&support::create_blake2b_hash(&TEST_PRETTY_721_META_DATA));
+
+    let transfer_request = ExecuteRequestBuilder::standard(
+        *DEFAULT_ACCOUNT_ADDR,
+        TRANSFER_SESSION_WASM,
+        runtime_args! {
+            ARG_NFT_CONTRACT_HASH => nft_contract_key,
+            ARG_IS_HASH_IDENTIFIER_MODE => true,
+            ARG_TOKEN_HASH => token_hash,
+            ARG_SOURCE_KEY => Key::Account(*DEFAULT_ACCOUNT_ADDR),
+            ARG_TARGET_KEY =>  Key::Account(AccountHash::new([3u8;32])),
+        },
+    )
+    .build();
+
+    builder.exec(transfer_request).expect_success().commit();
 }
diff --git a/tests/src/utility/constants.rs b/tests/src/utility/constants.rs
index 8f1bc4ba..8dad8091 100644
--- a/tests/src/utility/constants.rs
+++ b/tests/src/utility/constants.rs
@@ -1,7 +1,10 @@
 pub(crate) const NFT_CONTRACT_WASM: &str = "contract.wasm";
 pub(crate) const MINT_SESSION_WASM: &str = "mint_call.wasm";
 pub(crate) const BALANCE_OF_SESSION_WASM: &str = "balance_of_call.wasm";
+pub(crate) const MINTING_CONTRACT_WASM: &str = "minting_contract.wasm";
+pub(crate) const TRANSFER_SESSION_WASM: &str = "transfer_call.wasm";
 pub(crate) const CONTRACT_NAME: &str = "nft_contract";
+pub(crate) const MINTING_CONTRACT_NAME: &str = "minting_contract_hash";
 pub(crate) const NFT_TEST_COLLECTION: &str = "nft_test";
 pub(crate) const NFT_TEST_SYMBOL: &str = "TEST";
 pub(crate) const ENTRY_POINT_INIT: &str = "init";
@@ -16,30 +19,58 @@ pub(crate) const ARG_COLLECTION_SYMBOL: &str = "collection_symbol";
 pub(crate) const ARG_TOTAL_TOKEN_SUPPLY: &str = "total_token_supply";
 pub(crate) const ARG_ALLOW_MINTING: &str = "allow_minting";
 pub(crate) const ARG_MINTING_MODE: &str = "minting_mode";
+pub(crate) const ARG_HOLDER_MODE: &str = "holder_mode";
+pub(crate) const ARG_WHITELIST_MODE: &str = "whitelist_mode";
+pub(crate) const ARG_CONTRACT_WHITELIST: &str = "contract_whitelist";
 pub(crate) const NUMBER_OF_MINTED_TOKENS: &str = "number_of_minted_tokens";
 pub(crate) const ARG_TOKEN_META_DATA: &str = "token_meta_data";
-pub(crate) const TOKEN_META_DATA: &str = "token_meta_data";
+pub(crate) const METADATA_CUSTOM_VALIDATED: &str = "metadata_custom_validated";
+pub(crate) const METADATA_CEP78: &str = "metadata_cep78";
+pub(crate) const METADATA_NFT721: &str = "metadata_nft721";
+pub(crate) const METADATA_RAW: &str = "metadata_raw";
 pub(crate) const ARG_TOKEN_OWNER: &str = "token_owner";
 pub(crate) const ARG_NFT_CONTRACT_HASH: &str = "nft_contract_hash";
 pub(crate) const ARG_JSON_SCHEMA: &str = "json_schema";
 pub(crate) const ARG_TOKEN_URI: &str = "token_uri";
 pub(crate) const ARG_APPROVE_ALL: &str = "approve_all";
+pub(crate) const ARG_NFT_METADATA_KIND: &str = "nft_metadata_kind";
+pub(crate) const ARG_IDENTIFIER_MODE: &str = "identifier_mode";
 pub(crate) const TOKEN_ISSUERS: &str = "token_issuers";
 pub(crate) const ARG_OWNERSHIP_MODE: &str = "ownership_mode";
 pub(crate) const ARG_NFT_KIND: &str = "nft_kind";
 pub(crate) const TOKEN_OWNERS: &str = "token_owners";
 pub(crate) const OWNED_TOKENS: &str = "owned_tokens";
 pub(crate) const BURNT_TOKENS: &str = "burnt_tokens";
+pub(crate) const TOKEN_COUNTS: &str = "balances";
 pub(crate) const OPERATOR: &str = "operator";
 pub(crate) const BALANCES: &str = "balances";
+pub(crate) const RECEIPT_NAME: &str = "receipt_name";
 pub(crate) const ARG_OPERATOR: &str = "operator";
 pub(crate) const OWNED_TOKENS_DICTIONARY_KEY: &str = "owned_tokens_dictionary_key";
 pub(crate) const ARG_KEY_NAME: &str = "key_name";
-pub(crate) const ARG_TO_ACCOUNT_HASH: &str = "to_account_hash";
-pub(crate) const ARG_FROM_ACCOUNT_HASH: &str = "from_account_hash";
+pub(crate) const ARG_TARGET_KEY: &str = "target_key";
+pub(crate) const ARG_SOURCE_KEY: &str = "source_key";
 pub(crate) const ARG_TOKEN_ID: &str = "token_id";
+pub(crate) const ARG_TOKEN_HASH: &str = "token_hash";
+pub(crate) const ARG_IS_HASH_IDENTIFIER_MODE: &str = "is_hash_identifier_mode";
 pub(crate) const ACCOUNT_USER_1: [u8; 32] = [1u8; 32];
 pub(crate) const ACCOUNT_USER_2: [u8; 32] = [2u8; 32];
 pub(crate) const ACCOUNT_USER_3: [u8; 32] = [2u8; 32];
-pub(crate) const TEST_META_DATA: &str = "test meta";
+pub(crate) const TEST_PRETTY_721_META_DATA: &str = r#"{
+  "name": "John Doe",
+  "symbol": "abc",
+  "token_uri": "https://www.google.com"
+}"#;
+pub(crate) const TEST_PRETTY_CEP78_METADATA: &str = r#"{
+  "name": "John Doe",
+  "token_uri": "https://www.google.com",
+  "checksum": "940bffb3f2bba35f84313aa26da09ece3ad47045c6a1292c2bbd2df4ab1a55fb"
+}"#;
+pub(crate) const TEST_COMPACT_META_DATA: &str =
+    r#"{"name": "John Doe","symbol": "abc","token_uri": "https://www.google.com"}"#;
 pub(crate) const TEST_URI: &str = "www.google.com";
+pub(crate) const MALFORMED_META_DATA: &str = r#"{
+  "name": "John Doe",
+  "symbol": abc,
+  "token_uri": "https://www.google.com"
+}"#;
diff --git a/tests/src/utility/installer_request_builder.rs b/tests/src/utility/installer_request_builder.rs
index c4fc248e..858ee3f0 100644
--- a/tests/src/utility/installer_request_builder.rs
+++ b/tests/src/utility/installer_request_builder.rs
@@ -1,12 +1,63 @@
+use std::collections::BTreeMap;
+
+use once_cell::sync::Lazy;
+use serde::{Deserialize, Serialize};
+
 use casper_engine_test_support::ExecuteRequestBuilder;
 use casper_execution_engine::core::engine_state::ExecuteRequest;
-use casper_types::{account::AccountHash, CLValue, RuntimeArgs, U256};
+use casper_types::{account::AccountHash, CLValue, ContractHash, RuntimeArgs};
+
+use crate::utility::constants::{
+    ARG_CONTRACT_WHITELIST, ARG_HOLDER_MODE, ARG_IDENTIFIER_MODE, ARG_WHITELIST_MODE,
+};
 
 use super::constants::{
     ARG_ALLOW_MINTING, ARG_COLLECTION_NAME, ARG_COLLECTION_SYMBOL, ARG_JSON_SCHEMA,
-    ARG_MINTING_MODE, ARG_NFT_KIND, ARG_OWNERSHIP_MODE, ARG_TOTAL_TOKEN_SUPPLY,
+    ARG_MINTING_MODE, ARG_NFT_KIND, ARG_NFT_METADATA_KIND, ARG_OWNERSHIP_MODE,
+    ARG_TOTAL_TOKEN_SUPPLY,
 };
 
+pub(crate) static TEST_CUSTOM_METADATA_SCHEMA: Lazy<CustomMetadataSchema> = Lazy::new(|| {
+    let mut properties = BTreeMap::new();
+    properties.insert(
+        "deity_name".to_string(),
+        MetadataSchemaProperty {
+            name: "deity_name".to_string(),
+            description: "The name of deity from a particular pantheon.".to_string(),
+            required: true,
+        },
+    );
+    properties.insert(
+        "mythology".to_string(),
+        MetadataSchemaProperty {
+            name: "mythology".to_string(),
+            description: "The mythology the deity belongs to.".to_string(),
+            required: true,
+        },
+    );
+    CustomMetadataSchema { properties }
+});
+
+pub(crate) static TEST_CUSTOM_METADATA: Lazy<BTreeMap<String, String>> = Lazy::new(|| {
+    let mut attributes = BTreeMap::new();
+    attributes.insert("deity_name".to_string(), "Baldur".to_string());
+    attributes.insert("mythology".to_string(), "Nordic".to_string());
+    attributes
+});
+
+#[repr(u8)]
+pub enum WhitelistMode {
+    Unlocked = 0,
+    Locked = 1,
+}
+
+#[repr(u8)]
+pub enum NFTHolderMode {
+    Accounts = 0,
+    Contracts = 1,
+    Mixed = 2,
+}
+
 #[repr(u8)]
 pub enum MintingMode {
     /// The ability to mint NFTs is restricted to the installing account only.
@@ -32,6 +83,39 @@ pub enum NFTKind {
     Virtual = 2, // The NFT can be transferred even to an recipient that does not exist
 }
 
+#[derive(Serialize, Deserialize, Clone)]
+pub(crate) struct MetadataSchemaProperty {
+    name: String,
+    description: String,
+    required: bool,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub(crate) struct CustomMetadataSchema {
+    properties: BTreeMap<String, MetadataSchemaProperty>,
+}
+
+#[derive(Serialize, Deserialize)]
+struct Metadata {
+    name: String,
+    symbol: String,
+    token_uri: String,
+}
+
+#[repr(u8)]
+pub enum NFTMetadataKind {
+    CEP99 = 0,
+    NFT721 = 1,
+    Raw = 2,
+    CustomValidated = 3,
+}
+
+#[repr(u8)]
+pub enum NFTIdentifierMode {
+    Ordinal = 0,
+    Hash = 1,
+}
+
 #[derive(Debug)]
 pub(crate) struct InstallerRequestBuilder {
     account_hash: AccountHash,
@@ -43,7 +127,12 @@ pub(crate) struct InstallerRequestBuilder {
     minting_mode: CLValue,
     ownership_mode: CLValue,
     nft_kind: CLValue,
+    holder_mode: CLValue,
+    whitelist_mode: CLValue,
+    contract_whitelist: CLValue,
     json_schema: CLValue,
+    nft_metadata_kind: CLValue,
+    identifier_mode: CLValue,
 }
 
 impl InstallerRequestBuilder {
@@ -59,14 +148,18 @@ impl InstallerRequestBuilder {
             session_file: String::default(),
             collection_name: CLValue::from_t("name".to_string()).expect("name is legit CLValue"),
             collection_symbol: CLValue::from_t("SYM").expect("collection_symbol is legit CLValue"),
-            total_token_supply: CLValue::from_t(U256::one())
-                .expect("total_token_supply is legit CLValue"),
+            total_token_supply: CLValue::from_t(1u64).expect("total_token_supply is legit CLValue"),
             allow_minting: CLValue::from_t(Some(true)).unwrap(),
             minting_mode: CLValue::from_t(Some(MintingMode::Installer as u8)).unwrap(),
             ownership_mode: CLValue::from_t(OwnershipMode::Minter as u8).unwrap(),
             nft_kind: CLValue::from_t(NFTKind::Physical as u8).unwrap(),
-            json_schema: CLValue::from_t("my_json_schema".to_string())
-                .expect("my_json_schema is legit CLValue"),
+            holder_mode: CLValue::from_t(Some(NFTHolderMode::Mixed as u8)).unwrap(),
+            whitelist_mode: CLValue::from_t(Some(WhitelistMode::Locked as u8)).unwrap(),
+            contract_whitelist: CLValue::from_t(Some(Vec::<ContractHash>::new())).unwrap(),
+            json_schema: CLValue::from_t("test".to_string())
+                .expect("test_metadata was created from a concrete value"),
+            nft_metadata_kind: CLValue::from_t(NFTMetadataKind::NFT721 as u8).unwrap(),
+            identifier_mode: CLValue::from_t(NFTIdentifierMode::Ordinal as u8).unwrap(),
         }
     }
 
@@ -102,7 +195,7 @@ impl InstallerRequestBuilder {
         self
     }
 
-    pub(crate) fn with_total_token_supply(mut self, total_token_supply: U256) -> Self {
+    pub(crate) fn with_total_token_supply(mut self, total_token_supply: u64) -> Self {
         self.total_token_supply =
             CLValue::from_t(total_token_supply).expect("total_token_supply is legit CLValue");
         self
@@ -130,11 +223,36 @@ impl InstallerRequestBuilder {
         self
     }
 
-    pub(crate) fn _with_json_schema(mut self, json_schema: &str) -> Self {
+    pub(crate) fn with_holder_mode(mut self, holder_mode: NFTHolderMode) -> Self {
+        self.holder_mode = CLValue::from_t(Some(holder_mode as u8)).unwrap();
+        self
+    }
+
+    pub(crate) fn with_whitelist_mode(mut self, whitelist_mode: WhitelistMode) -> Self {
+        self.whitelist_mode = CLValue::from_t(Some(whitelist_mode as u8)).unwrap();
+        self
+    }
+
+    pub(crate) fn with_contract_whitelist(mut self, contract_whitelist: Vec<ContractHash>) -> Self {
+        self.contract_whitelist = CLValue::from_t(Some(contract_whitelist)).unwrap();
+        self
+    }
+
+    pub(crate) fn with_nft_metadata_kind(mut self, nft_metadata_kind: NFTMetadataKind) -> Self {
+        self.nft_metadata_kind = CLValue::from_t(nft_metadata_kind as u8).unwrap();
+        self
+    }
+
+    pub(crate) fn with_json_schema(mut self, json_schema: String) -> Self {
         self.json_schema = CLValue::from_t(json_schema).expect("json_schema is legit CLValue");
         self
     }
 
+    pub(crate) fn with_identifier_mode(mut self, identifier_mode: NFTIdentifierMode) -> Self {
+        self.identifier_mode = CLValue::from_t(identifier_mode as u8).unwrap();
+        self
+    }
+
     pub(crate) fn build(self) -> ExecuteRequest {
         let mut runtime_args = RuntimeArgs::new();
         runtime_args.insert_cl_value(ARG_COLLECTION_NAME, self.collection_name);
@@ -144,7 +262,12 @@ impl InstallerRequestBuilder {
         runtime_args.insert_cl_value(ARG_MINTING_MODE, self.minting_mode.clone());
         runtime_args.insert_cl_value(ARG_OWNERSHIP_MODE, self.ownership_mode);
         runtime_args.insert_cl_value(ARG_NFT_KIND, self.nft_kind);
+        runtime_args.insert_cl_value(ARG_HOLDER_MODE, self.holder_mode);
+        runtime_args.insert_cl_value(ARG_WHITELIST_MODE, self.whitelist_mode);
+        runtime_args.insert_cl_value(ARG_CONTRACT_WHITELIST, self.contract_whitelist);
         runtime_args.insert_cl_value(ARG_JSON_SCHEMA, self.json_schema);
+        runtime_args.insert_cl_value(ARG_NFT_METADATA_KIND, self.nft_metadata_kind);
+        runtime_args.insert_cl_value(ARG_IDENTIFIER_MODE, self.identifier_mode);
         ExecuteRequestBuilder::standard(self.account_hash, &self.session_file, runtime_args).build()
     }
 }
diff --git a/tests/src/utility/support.rs b/tests/src/utility/support.rs
index 7da47180..3c00b822 100644
--- a/tests/src/utility/support.rs
+++ b/tests/src/utility/support.rs
@@ -1,4 +1,8 @@
-use crate::utility::constants::{ARG_KEY_NAME, ARG_NFT_CONTRACT_HASH};
+use crate::utility::constants::{ARG_KEY_NAME, ARG_NFT_CONTRACT_HASH, MINTING_CONTRACT_NAME};
+use blake2::{
+    digest::{Update, VariableOutput},
+    VarBlake2b,
+};
 
 use super::{constants::CONTRACT_NAME, installer_request_builder::InstallerRequestBuilder};
 use casper_engine_test_support::{
@@ -11,7 +15,7 @@ use casper_execution_engine::{
 };
 use casper_types::{
     account::AccountHash, bytesrepr::FromBytes, ApiError, CLTyped, ContractHash, Key, PublicKey,
-    RuntimeArgs, SecretKey, URef,
+    RuntimeArgs, SecretKey, URef, BLAKE2B_DIGEST_LENGTH,
 };
 
 pub(crate) fn get_nft_contract_hash(
@@ -28,6 +32,20 @@ pub(crate) fn get_nft_contract_hash(
     ContractHash::new(nft_hash_addr)
 }
 
+pub(crate) fn get_minting_contract_hash(
+    builder: &WasmTestBuilder<InMemoryGlobalState>,
+) -> ContractHash {
+    let minting_contract_hash = builder
+        .get_expected_account(*DEFAULT_ACCOUNT_ADDR)
+        .named_keys()
+        .get(MINTING_CONTRACT_NAME)
+        .expect("must have minting contract hash entry in named keys")
+        .into_hash()
+        .expect("must get hash_addr");
+
+    ContractHash::new(minting_contract_hash)
+}
+
 pub(crate) fn get_dictionary_value_from_key<T: CLTyped + FromBytes>(
     builder: &WasmTestBuilder<InMemoryGlobalState>,
     nft_contract_key: &Key,
@@ -120,13 +138,13 @@ pub(crate) fn query_stored_value<T: CLTyped + FromBytes>(
 pub(crate) fn call_entry_point_with_ret<T: CLTyped + FromBytes>(
     builder: &mut InMemoryWasmTestBuilder,
     account_hash: AccountHash,
-    nft_contract_hash: ContractHash,
+    nft_contract_key: Key,
     mut runtime_args: RuntimeArgs,
     wasm_file_name: &str,
     key_name: &str,
 ) -> T {
     runtime_args
-        .insert(ARG_NFT_CONTRACT_HASH, nft_contract_hash)
+        .insert(ARG_NFT_CONTRACT_HASH, nft_contract_key)
         .unwrap();
 
     runtime_args
@@ -141,3 +159,15 @@ pub(crate) fn call_entry_point_with_ret<T: CLTyped + FromBytes>(
     println!("Querying: {}", key_name);
     query_stored_value::<T>(builder, account_hash.into(), [key_name.to_string()].into())
 }
+
+pub(crate) fn create_blake2b_hash<T: AsRef<[u8]>>(data: T) -> [u8; BLAKE2B_DIGEST_LENGTH] {
+    let mut result = [0; BLAKE2B_DIGEST_LENGTH];
+    // NOTE: Assumed safe as `BLAKE2B_DIGEST_LENGTH` is a valid value for a hasher
+    let mut hasher = VarBlake2b::new(BLAKE2B_DIGEST_LENGTH).expect("should create hasher");
+
+    hasher.update(data);
+    hasher.finalize_variable(|slice| {
+        result.copy_from_slice(slice);
+    });
+    result
+}