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);
}
}
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