Get a quick grasp on how to deal with NFTs on NEAR
NFTs are smart contract that live on the blockchain and can serve a wide variety of purposes.
Each NFT has a fixed unique ID, and an owner which can be changed, or better known as 'selling' the NFT.
In order to mint an NFT you have to use a Smart Contract.
The example contracts are built with Rust. If that is new to you, don't worry, you can easily copy the example and mint your NFTs.
Example NFT ProjectExample NFT Contract
use near_contract_standards::non_fungible_token::metadata::{
NFTContractMetadata, NonFungibleTokenMetadataProvider, TokenMetadata, NFT_METADATA_SPEC,
};
use near_contract_standards::non_fungible_token::{Token, TokenId};
use near_contract_standards::non_fungible_token::NonFungibleToken;
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::collections::LazyOption;
use near_sdk::json_types::ValidAccountId;
use near_sdk::{
env, near_bindgen, AccountId, BorshStorageKey, PanicOnDefault, Promise, PromiseOrValue,
};
near_sdk::setup_alloc!();
#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
pub struct Contract {
tokens: NonFungibleToken,
metadata: LazyOption<NFTContractMetadata>,
}
const DATA_IMAGE_SVG_NEAR_ICON: &str = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 288 288'%3E%3Cg id='l' data-name='l'%3E%3Cpath d='M187.58,79.81l-30.1,44.69a3.2,3.2,0,0,0,4.75,4.2L191.86,103a1.2,1.2,0,0,1,2,.91v80.46a1.2,1.2,0,0,1-2.12.77L102.18,77.93A15.35,15.35,0,0,0,90.47,72.5H87.34A15.34,15.34,0,0,0,72,87.84V201.16A15.34,15.34,0,0,0,87.34,216.5h0a15.35,15.35,0,0,0,13.08-7.31l30.1-44.69a3.2,3.2,0,0,0-4.75-4.2L96.14,186a1.2,1.2,0,0,1-2-.91V104.61a1.2,1.2,0,0,1,2.12-.77l89.55,107.23a15.35,15.35,0,0,0,11.71,5.43h3.13A15.34,15.34,0,0,0,216,201.16V87.84A15.34,15.34,0,0,0,200.66,72.5h0A15.35,15.35,0,0,0,187.58,79.81Z'/%3E%3C/g%3E%3C/svg%3E";
#[derive(BorshSerialize, BorshStorageKey)]
enum StorageKey {
NonFungibleToken,
Metadata,
TokenMetadata,
Enumeration,
Approval,
}
#[near_bindgen]
impl Contract {
/// Initializes the contract owned by `owner_id` with
/// default metadata (for example purposes only).
#[init]
pub fn new_default_meta(owner_id: ValidAccountId) -> Self {
Self::new(
owner_id,
NFTContractMetadata {
spec: NFT_METADATA_SPEC.to_string(),
name: "Example NEAR non-fungible token".to_string(),
symbol: "EXAMPLE".to_string(),
icon: Some(DATA_IMAGE_SVG_NEAR_ICON.to_string()),
base_uri: None,
reference: None,
reference_hash: None,
},
)
}
#[init]
pub fn new(owner_id: ValidAccountId, metadata: NFTContractMetadata) -> Self {
assert!(!env::state_exists(), "Already initialized");
metadata.assert_valid();
Self {
tokens: NonFungibleToken::new(
StorageKey::NonFungibleToken,
owner_id,
Some(StorageKey::TokenMetadata),
Some(StorageKey::Enumeration),
Some(StorageKey::Approval),
),
metadata: LazyOption::new(StorageKey::Metadata, Some(&metadata)),
}
}
#[payable]
pub fn nft_mint(
&mut self,
token_id: TokenId,
receiver_id: ValidAccountId,
token_metadata: TokenMetadata,
) -> Token {
self.tokens.mint(token_id, receiver_id, Some(token_metadata))
}
}
near_contract_standards::impl_non_fungible_token_core!(Contract, tokens);
near_contract_standards::impl_non_fungible_token_approval!(Contract, tokens);
near_contract_standards::impl_non_fungible_token_enumeration!(Contract, tokens);
#[near_bindgen]
impl NonFungibleTokenMetadataProvider for Contract {
fn nft_metadata(&self) -> NFTContractMetadata {
self.metadata.get().unwrap()
}
}
After you deploy the contract, you have to initialize it by calling the init
method 'new_default_meta'
You need to provide the owner of the contract in the arguments.
Here is an example of how to call it:
near call nft_dan.testnet new_default_meta --args "{\"owner_id\":\"nft_dan.testnet\"}" --accountId nft_dan.testnet
Now you can mint an NFT by calling the nft_mint method To see full specifications of the NFT standard refer to the NEAR RUST NFTSpecification
Here is a demo near-cli command to mint an NFT:
near call nft_dan.testnet nft_mint --args "{\"token_id\":\"testNFT_dan\",\"receiver_id\":\"nft_dan.testnet\",\"token_metadata\":{\"title\":\"Test 1234\",\"description\":\"Descripti
on 1234\",\"media\":\"https://logowik.com/content/uploads/images/near-protocol-near1824.jpg\"}}" --accountId devtest.testnet --depositYocto 7000000000000000000000
The specification for the token_metadata is as follows:
pub struct TokenMetadata {
pub title: Option<String>, // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055"
pub description: Option<String>, // free-form description
pub media: Option<String>, // URL to associated media, preferably to decentralized, content-addressed storage
pub media_hash: Option<Base64VecU8>, // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included.
pub copies: Option<u64>, // number of copies of this set of metadata in existence when token was minted.
pub issued_at: Option<String>, // ISO 8601 datetime when token was issued or minted
pub expires_at: Option<String>, // ISO 8601 datetime when token expires
pub starts_at: Option<String>, // ISO 8601 datetime when token starts being valid
pub updated_at: Option<String>, // ISO 8601 datetime when token was last updated
pub extra: Option<String>, // anything extra the NFT wants to store on-chain. Can be stringified JSON.
pub reference: Option<String>, // URL to an off-chain JSON file with more info.
pub reference_hash: Option<Base64VecU8>, // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included.
}