Search code examples
nearprotocol

How can I make my NFT visible in the NEAR wallet (AssemblyScript)?


I'm trying to implement my own NFT-contract, following this tutorial on NEAR, and the Non-Fungible Token (NEP-171) specifications. The tutorial is in Rust, but I'm trying to do something similar with AssemblyScript. I thought if I implemented the methods with the correct names and signature, it would be possible for the NEAR wallet to call the respective methods (e.g. nft_tokens_for_owner) on my NFT-contract. I'm just wondering if I'm missing something, or if I have the wrong understanding on how it's suppose to work.

I have minted one NFT-token, using the nft_mint method in my contract, using my own testnet account. The transaction can be found here. However, the NFT is not displayed in the "Collectibles" tab in my testnet wallet.

My contract (index.ts) looks like this:

import { PersistentMap } from 'near-sdk-as';
import { Token, TokenMetadata } from './reit-token';

@nearBindgen
class NFTContractMetadata {
  spec: string; // required, essentially a version like "nft-1.0.0"
  name: string; // required, ex. "Mochi Rising — Digital Edition" or "Metaverse 3"
  symbol: string; // required, ex. "MOCHI"
  icon: string | null; // Data URL
  base_uri: string | null; // Centralized gateway known to have reliable access to decentralized storage assets referenced by `reference` or `media` URLs
  reference: string | null; // URL to a JSON file with more info
  reference_hash: string | null; // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included.
}

@nearBindgen
export class Contract {
  //keeps track of the metadata for the contract
  metadata: NFTContractMetadata = {
    spec: 'reit-token-0.0.0',
    name: 'Reit Token',
    symbol: 'REIT',
    icon: null,
    base_uri: null,
    reference: null,
    reference_hash: null,
  };

  //contract owner
  owner_id: string; // Not sure if this is relevant for the contract itself

  //keeps track of all the token IDs for a given account
  tokens_per_owner: PersistentMap<string, Array<string>> = new PersistentMap<
    string,
    Array<string>
  >('tokens_pr_owner');

  //keeps track of the token struct for a given token ID
  tokens_by_id: PersistentMap<string, Token> = new PersistentMap<string, Token>(
    'tokens_by_id'
  );

  //keeps track of the token metadata for a given token ID
  token_metadata_by_id: PersistentMap<string, TokenMetadata> =
    new PersistentMap<string, TokenMetadata>('token_metadata_by_id');

  nft_tokens_for_owner(account_id: string): Array<Token> {
    const tokenIds: string[] = this.tokens_per_owner.getSome(account_id);

    const tokens: Array<Token> = new Array<Token>();
    for (let i = 0; i < tokenIds.length; ++i) {
      const token: Token = this.tokens_by_id.getSome(tokenIds[i]);
      tokens.push(token);
    }
    return tokens;
  }

  nft_mint(
    token_id: string,
    metadata: TokenMetadata,
    receiver_id: string
  ): void {
    // assert(
    //   this.tokens_by_id.contains(token_id),
    //   'ID is already taken, create new ID'
    // );
    const token = new Token(token_id, metadata, receiver_id);
    const tokens: Array<string> = new Array<string>();
    tokens.push(token_id);
    this.tokens_per_owner.set(receiver_id, tokens);
    this.tokens_by_id.set(token_id, token);
    this.token_metadata_by_id.set(token_id, token.metadata);
  }

Inside reit-token.ts

import { ContractPromise } from 'near-sdk-as';
// implementation based on NEP-171
// https://nomicon.io/Standards/NonFungibleToken/Core.html

@nearBindgen
export class TokenMetadata {
  title: string; // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055"
  description: string; // free-form description
  media: string; // URL to associated media, preferably to decentralized, content-addressed storage
  media_hash: string; // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included.
  copies: number; // number of copies of this set of metadata in existence when token was minted.
  issued_at: number; // When token was issued or minted, Unix epoch in milliseconds
  expires_at: number; // When token expires, Unix epoch in milliseconds
  starts_at: number; // When token starts being valid, Unix epoch in milliseconds
  updated_at: number; // When token was last updated, Unix epoch in milliseconds
  extra: string; // anything extra the NFT wants to store on-chain. Can be stringified JSON.
  reference: string; // URL to an off-chain JSON file with more info.
  reference_hash: string; // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included.
}

@nearBindgen
export class Token {
  id: string;
  owner_id: string;
  metadata: TokenMetadata;
  constructor(token_id: string, metadata: TokenMetadata, receiver_id: string) {
    this.id = token_id;
    this.metadata = metadata;
    this.owner_id = receiver_id;
  }
}

@nearBindgen
export class ReitToken {
  token: Token;

  constructor(token_id: string, metadata: TokenMetadata, receiver_id: string) {
    this.token = { id: token_id, metadata: metadata, owner_id: receiver_id };
  }
  // --- view methods --- //

  nft_token(): Token {
    return this.token;
  }

  // --- change methods --- //

  nft_transfer(
    receiver_id: string,
    token_id: string,
    approval_id: number,
    memo: string
  ): void {
    // assert(false, 'nft_transfer not implemented');
  }

  nft_transfer_call(
    receiver_id: string,
    token_id: string,
    approval_id: number,
    memo: string,
    msg: string
  ): ContractPromise {
    // assert(false, 'nft_transfer_call not implemented');
    return ContractPromise.create('', '', {}, 1);
  }
}

Solution

  • After asking around, I was pointed to two different GitHub repositories that implemented an NFT-token smart contract in AssemblyScript.

    After going through those repositories, I noticed that my contract wasn't that far off to be able to display my NFT-tokens in the wallet. I had to implement one more function, nft_metadata().

    I did have metadata in my contract, but as a variable, metadata. Returning the same metadata in nft_metadata() seemed to do the trick

    @nearBindgen
    class NFTContractMetadata {
      constructor(
        public spec: string = 'reit-token-0.0.0', // required, essentially a version like "nft-1.0.0"
        public name: string = 'Reit Token', // required, ex. "Mochi Rising — Digital Edition" or "Metaverse 3"
        public symbol: string = 'REIT', // required, ex. "MOCHI"
        public icon: string = '', // Data URL
        public base_uri: string = '', // Centralized gateway known to have reliable access to decentralized storage assets referenced by `reference` or `media` URLs
        public reference: string = '', // URL to a JSON file with more info
        public reference_hash: string = '' // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included.
      ) {}
    }
    
    @nearBindgen
    export class Contract {
    // ...
    
      nft_metadata(): NFTContractMetadata {
        return new NFTContractMetadata();
      }
    
    // ...
    }
    

    I did override my existing NFT with a new one (transaction found here) while testing, and here it is, visible in the NEAR testnet wallet

    enter image description here