title: Near Protocol NFT add string values mapped to a TokenId. i.e. tokenizing an ipfs hash
body: <p>I am trying to learn how to use the Near protocol and Rust by recreating my NFT Dapp which mapped string values (ipfs hash's to be exact) which I call a &quot;sketch&quot;.
  I have already done this successfully using Solidity on Ethereum. example shown at bottom of this page.</p>
  <p><strong>I am simply trying to map string values to a tokenId and subsequently an OwnerId. I have used the boilerplate NFT lib.rs found on the <a href="https://examples.near.org/NFT" rel="nofollow noreferrer">Near dev NFT website</a> and have added my own string mapping for collections of &quot;sketch&quot; which will just contain an ipfs hash value to be used later on the front-end as an image source. What I have for lib.rs is below.</strong></p>
  <pre><code>    #![deny(warnings)]
  
      use borsh::{BorshDeserialize, BorshSerialize};
      use near_sdk::collections::{ TreeMap, UnorderedMap, UnorderedSet };
      use near_sdk::{env, near_bindgen, AccountId};
  
      #[global_allocator]
      static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
  
      pub trait NEP4 {
          // Grant the access to the given `accountId` for the given `tokenId`.
          // Requirements:
          // * The caller of the function (`predecessor_id`) should have access to the token.
          fn grant_access(&amp;mut self, escrow_account_id: AccountId);
  
          // Revoke the access to the given `accountId` for the given `tokenId`.
          // Requirements:
          // * The caller of the function (`predecessor_id`) should have access to the token.
          fn revoke_access(&amp;mut self, escrow_account_id: AccountId);
  
          // Transfer the given `tokenId` to the given `accountId`. Account `accountId` becomes the new owner.
          // Requirements:
          // * The caller of the function (`predecessor_id`) should have access to the token.
          fn transfer_from(&amp;mut self, owner_id: AccountId, new_owner_id: AccountId, token_id: TokenId); 
  
          // Transfer the given `tokenId` to the given `accountId`. Account `accountId` becomes the new owner.
          // Requirements:
          // * The caller of the function (`predecessor_id`) should be the owner of the token. Callers who have
          // escrow access should use transfer_from.
          fn transfer(&amp;mut self, new_owner_id: AccountId, token_id: TokenId); 
  
          // Returns `true` or `false` based on caller of the function (`predecessor_id) having access to the token
          fn check_access(&amp;self, account_id: AccountId) -&gt; bool;
  
          // Get an individual owner by given `tokenId`.
          fn get_token_owner(&amp;self, token_id: TokenId) -&gt; String;
      }
      /// The token ID type is also defined in the NEP
      pub type TokenId = u64;
      pub type AccountIdHash = Vec&lt;u8&gt;;
  
      // Begin implementation
      #[near_bindgen]
      #[derive(BorshDeserialize, BorshSerialize)]
      pub struct NonFungibleTokenBasic {
          pub token_to_account: UnorderedMap&lt;TokenId, AccountId&gt;,
          pub account_gives_access: UnorderedMap&lt;AccountIdHash, UnorderedSet&lt;AccountIdHash&gt;&gt;, // Vec&lt;u8&gt; is sha256 of account, makes it safer and is how fungible token also works
          pub owner_id: AccountId,
          pub sketch: TreeMap&lt;TokenId, String&gt; 
      }
  
      impl Default for NonFungibleTokenBasic {
          fn default() -&gt; Self {
              panic!(&quot;NFT should be initialized before usage&quot;)
          }
      }
  
      #[near_bindgen]
      impl NonFungibleTokenBasic {
          #[init]
          pub fn new(owner_id: AccountId) -&gt; Self {
              assert!(env::is_valid_account_id(owner_id.as_bytes()), &quot;Owner's account ID is invalid.&quot;);
              assert!(!env::state_exists(), &quot;Already initialized&quot;);
              Self {
                  token_to_account: UnorderedMap::new(b&quot;token-belongs-to&quot;.to_vec()),
                  account_gives_access: UnorderedMap::new(b&quot;gives-access&quot;.to_vec()),
                  owner_id,
                  sketch: TreeMap::new(b&quot;sketch&quot;.to_vec()),
              }
          }
      }
  
      #[near_bindgen]
      impl NonFungibleTokenBasic {
          /// Creates a token for owner_id, doesn't use autoincrement, fails if id is taken
          /// example input for sketch is an ipfs hash (&quot;QmdMuGrMCfvgwg7F6WM8CHev9PJxQ2n3f49ttSGuYfK4Qp&quot;)
          pub fn mint_token(&amp;mut self, owner_id: String, token_id: TokenId, sketch: String) {
              // make sure that only the owner can call this funtion
              self.only_owner();
              // Since Map doesn't have `contains` we use match
              let token_check = self.token_to_account.get(&amp;token_id);
              if token_check.is_some() {
                  env::panic(b&quot;Token ID already exists.&quot;)
              }
              // No token with that ID exists, mint and add token to data structures
              self.token_to_account.insert(&amp;token_id, &amp;owner_id, &amp;sketch);
          }
  
          /// helper function determining contract ownership
          fn only_owner(&amp;mut self) {
              assert_eq!(env::predecessor_account_id(), self.owner_id, &quot;Only contract owner can call this method.&quot;);
          }
      }
  
  
          fn transfer(&amp;mut self, new_owner_id: AccountId, token_id: TokenId, sketch: String) {
              let token_owner_account_id = self.get_token_owner(token_id);
              let predecessor = env::predecessor_account_id();
              if predecessor != token_owner_account_id {
                  env::panic(b&quot;Attempt to call transfer on tokens belonging to another account.&quot;)
              }
              self.token_to_account.insert(&amp;token_id, &amp;new_owner_id, &amp;sketch);
          }
  
  </code></pre>
  <p>If it is any consolation, what I did in Solidity was map &quot;sketch&quot; as strings and push their value while adding the token Id based on the sketch array's length through to the ERC721.sol contract as shown below:</p>
  <pre><code>
  pragma solidity ^0.6.0;
  
  import &quot;@openzeppelin/contracts/token/ERC721/ERC721.sol&quot;;
  import &quot;@openzeppelin/contracts/access/Ownable.sol&quot;;
  
  contract Sketch is ERC721, Ownable{
    address payable private admin;
    string[] public sketchs;
    mapping(string =&gt; bool) _sketchExists;
  
    constructor() ERC721(&quot;Sketch&quot;, &quot;SKETCH&quot;) public {
      admin = msg.sender;
    }
  
    /// @notice mints the ipfs hash passed from the console
    /// @dev example input for _sketch is an ipfs hash (&quot;QmdMuGrMCfvgwg7F6WM8CHev9PJxQ2n3f49ttSGuYfK4Qp&quot;)
    function mint(string memory _sketch) public {
      require(!_sketchExists[_sketch]);
      sketchs.push(_sketch);
      uint _id = sketchs.length - 1;
      _safeMint(msg.sender, _id,'');
      _sketchExists[_sketch] = true;
    }
  
    function transfersketch(address _to, uint _id) public {
        safeTransferFrom(msg.sender, _to, _id);
    }
  }
  </code></pre>
  <p>Am I in the right direction? Any help would be really appreciated. Thanks in advance!</p>