ZTP 721

Introduction

What is the Token Standard

A non-fungible token (NFT) is a unique cryptographic token that represents ownership of specific digital or physical assets on a blockchain. Unlike cryptocurrencies such as Bitcoin, NFTs are indivisible and cannot be exchanged on a one-to-one basis due to their distinctive characteristics. Each NFT is one-of-a-kind and includes specific metadata that sets it apart from other tokens. NFTs are utilized across various industries, enabling creators to tokenize and monetize their work, including art, music, videos, virtual real estate, collectibles, and more. Ownership and transaction records of NFTs are securely stored on the blockchain using smart contracts, ensuring transparency and authenticity. NFT marketplaces facilitate the buying, selling, and trading of these tokens, allowing collectors and investors to participate in the growing digital asset ecosystem.

What is ZTP-721

ZTP-721 is the standard interface for Non-Fungible Tokens (NFTs). This interface enables users to perform actions such as minting, transferring, and burning tokens. By adopting this default standard, contracts utilizing ZTP-721 can seamlessly integrate with various services across the network.

Methods

The list below outlines the required methods to be implemented in the contract:

Token definition:

Notes: Token definitions are defined during the init procedure.

init

Example:

function init() {
  let paramObj;
  paramObj.name = "Global NFT";
  paramObj.symbol = "GCN";
  paramObj.describe = "Global coin token issued by XYZ";
  paramObj.version = "1";
  paramObj.protocol = "ztp721"; // To define that this contract is ZTP-721 standards


  Chain.store("contract_info", JSON.stringify(paramObj));
}

transferFrom

Example:

function transferFrom(paramObj) {

  _transFrom(paramObj.id, paramObj.from, paramObj.to);
  return;
}

function _transFrom(id, from, to) {

  let owner = getAssetOwner(id);
  saveAssetUserCount(from, Utils.int64Sub(getAssetUserCount(from), '1'));
  saveAssetUserCount(to, Utils.int64Add(getAssetUserCount(to), '1'));

  saveAssetOwner(id, to);

  _approve(owner, id, '');

  Chain.tlog('Transfer', owner, to, id);

  return;
}

safeTranferForm

Example:

function safeTransferFrom(paramObj) {

  _transFrom(paramObj.id, paramObj.from, paramObj.to);
  return;
}

approve

Example:

function approve(paramObj) {

  _approve(owner, paramObj.id, paramObj.approved);
  return;
}

function _approve(owner, id, approved) {
  if (approved !== '') {
    saveApproveSingle(id, approved);
    Chain.tlog('Approval', owner, approved, id);
    return;
  }

  if (delApproveSingle(id)) {
    Chain.tlog('Approval', owner, '0x', id);
    return;
  }
}

setApprovalForAll

Example:

function setApprovalForAll(paramObj) {

  saveApproveAll(Chain.msg.sender, paramObj.operator, paramObj.approved);
  Chain.tlog('ApprovalForAll', Chain.msg.sender, paramObj.operator, paramObj.approved);
  return;
}

mint

Example:

function mint(paramObj) {

  let newId = Utils.int64Add(getAssetSupply(), '1');
  let newUserCount = Utils.int64Add(getAssetUserCount(paramObj.to), '1');
  saveAsset(newId, Chain.msg.sender, paramObj.uri);
  saveAssetOwner(newId, paramObj.to);
  saveAssetUserCount(paramObj.to, newUserCount);
  saveAssetSupply(newId);

  Chain.tlog('Transfer', '0x', paramObj.to, newId);
  return;
}

burn

Example:

function burn(paramObj) {
  
  saveAssetUserCount(owner, Utils.int64Sub(getAssetUserCount(owner), '1'));
  saveAssetOwner(paramObj.id, '');
  _approve(owner, paramObj.id, '');

  Chain.tlog('Transfer', owner, '0x', paramObj.id);
  return;
}

balanceOf

Example:

function balanceOf(paramObj) {

  let result = {};
  result.count = getAssetUserCount(paramObj.owner);
  return result;
}

ownerOf

Example:

function ownerOf(paramObj) {

  let result = {};
  result.address = getAssetOwner(paramObj.id);
  return result;
}

getApproved

Example:

function getApproveAll(owner, operator) {
  let data = Chain.load(getKey(APPROVE_ALL_PRE, owner, operator));
  if (data === false) {
    return false;
  }

  return JSON.parse(data).approved;
}

isApprovedForAll

Example:

function isApprovedForAll(paramObj) {

  let result = {};
  result.approved = getApproveAll(paramObj.owner, paramObj.operator);
  return result;
}

contractInfo

Example:

function contractInfo() {
  return loadObj(CONTRACT_PRE);
}

tokenURI

Example:

function tokenURI(paramObj) {

  let result = {};
  result.uri = loadObj(getKey(ASSET_PRE, paramObj.id)).uri;
  return result;
}

totalSupply

Example:

function totalSupply(paramObj) {
  let result = {};
  result.count = getAssetSupply();
  return result;
}

query

function query(input_str) {
  let funcList = {
    'balanceOf': balanceOf,
    'ownerOf': ownerOf,
    'getApproved': getApproved,
    'isApprovedForAll': isApprovedForAll,
    'contractInfo': contractInfo,
    'tokenURI': tokenURI,
    'totalSupply': totalSupply
  };
  let inputObj = JSON.parse(input_str);
  Utils.assert(funcList.hasOwnProperty(inputObj.method) && typeof 

funcList[inputObj.method] === 'function', 'Cannot find func:' + inputObj.method);
  return JSON.stringify(funcList[inputObj.method](inputObj.params));
}

main

Example:

function main(input_str) {
  let funcList = {
    'safeTransferFrom': safeTransferFrom,
    'transferFrom': transferFrom,
    'approve': approve,
    'setApprovalForAll': setApprovalForAll,
    'mint': mint,
    'burn': burn
  };
  let inputObj = JSON.parse(input_str);
  funcList[inputObj.method](inputObj.params);
}

Full ZTP-721 contract example:

'use strict';

const ASSET_PRE = 'asset';
const ASSET_USER_COUNT_PRE = 'asset_user_count';
const ASSET_OWNER_PRE = 'asset_owner';
const APPROVE_SINGLE_PRE = 'approve_single';
const APPROVE_ALL_PRE = 'approve_all';
const CONTRACT_PRE = 'contract_info';
const ASSET_SUPPLY = 'asset_supply';
const ZTP_PROTOCOL = 'ztp721';

function getKey(first, second, third = '') {
  return (third === '') ? (first + '_' + second) : (first + '_' + second + '_' + third);
}

function loadObj(key) {
  let data = Chain.load(key);
  Utils.assert(data !== false, 'Failed to get storage data, key:' + key);
  return JSON.parse(data);
}

function saveObj(key, value) {
  Chain.store(key, JSON.stringify(value));
}

function checkAssetExsit(id) {
  let data = Chain.load(getKey(ASSET_PRE, id));
  if (data === false) {
    return false;
  }

  return true;
}

function saveAsset(id, issuer, uri) {
  let nftObj = {};
  nftObj.id = id;
  nftObj.issuer = issuer;
  nftObj.uri = uri;
  saveObj(getKey(ASSET_PRE, id), nftObj);
}

function getAssetOwner(id) {
  let data = Chain.load(getKey(ASSET_OWNER_PRE, id));
  if (data === false) {
    return '';
  }

  return JSON.parse(data).owner;
}

function saveAssetOwner(id, owner) {
  let obj = {};
  obj.owner = owner;
  saveObj(getKey(ASSET_OWNER_PRE, id), obj);
}

function getAssetUserCount(user) {
  let data = Chain.load(getKey(ASSET_USER_COUNT_PRE, user));
  if (data === false) {
    return '0';
  }

  return JSON.parse(data).count;
}

function saveAssetUserCount(user, count) {
  let key = getKey(ASSET_USER_COUNT_PRE, user);
  if (Utils.int64Compare(count, '0') !== 0) {
    let obj = {};
    obj.count = count;
    saveObj(key, obj);
    return;
  }

  let data = Chain.load(key);
  if (data !== false) {
    Chain.del(key);
  }
}

function getApproveSingle(id) {
  let data = Chain.load(getKey(APPROVE_SINGLE_PRE, id));
  if (data === false) {
    return '';
  }

  return JSON.parse(data).operator;
}

function saveApproveSingle(id, operator) {
  let obj = {};
  obj.operator = operator;
  saveObj(getKey(APPROVE_SINGLE_PRE, id), obj);
}

function delApproveSingle(id) {
  let key = getKey(APPROVE_SINGLE_PRE, id);
  let data = Chain.load(key);
  if (data === false) {
    return false;
  }
  Chain.del(key);
  return true;
}

function getApproveAll(owner, operator) {
  let data = Chain.load(getKey(APPROVE_ALL_PRE, owner, operator));
  if (data === false) {
    return false;
  }

  return JSON.parse(data).approved;
}

function saveApproveAll(owner, operator, approved) {
  let key = getKey(APPROVE_ALL_PRE, owner, operator);
  if (approved) {
    let approvedObj = {};
    approvedObj.approved = approved;
    saveObj(key, approvedObj);
    return;
  }

  let data = Chain.load(key);
  if (data !== false) {
    Chain.del(key);
  }
}

function getAssetSupply() {
  let data = Chain.load(ASSET_SUPPLY);
  if (data === false) {
    return '0';
  }

  return JSON.parse(data).count;
}

function saveAssetSupply(count) {
  let supplyObj = {};
  supplyObj.count = count;
  saveObj(ASSET_SUPPLY, supplyObj);
}

function _approve(owner, id, approved) {
  if (approved !== '') {
    saveApproveSingle(id, approved);
    Chain.tlog('Approval', owner, approved, id);
    return;
  }

  if (delApproveSingle(id)) {
    Chain.tlog('Approval', owner, '0x', id);
    return;
  }
}

function _transFrom(id, from, to) {
  Utils.assert(checkAssetExsit(id), 'Check nft not exist.');

  let owner = getAssetOwner(id);
  Utils.assert(owner === from, 'Nft owner not equal from.');
  Utils.assert(owner === Chain.msg.sender || getApproveSingle(id) === Chain.msg.sender || getApproveAll(owner, Chain.msg.sender), 'No privilege to trans.');

  saveAssetUserCount(from, Utils.int64Sub(getAssetUserCount(from), '1'));
  saveAssetUserCount(to, Utils.int64Add(getAssetUserCount(to), '1'));

  saveAssetOwner(id, to);

  _approve(owner, id, '');

  Chain.tlog('Transfer', owner, to, id);

  return;
}

function safeTransferFrom(paramObj) {

  Utils.assert(paramObj.from !== undefined && paramObj.from.length > 0, 'Param obj has no from.');
  Utils.assert(paramObj.to !== undefined && paramObj.to.length > 0, 'Param obj has no to.');
  Utils.assert(paramObj.id !== undefined && paramObj.id.length > 0, 'Param obj has no id.');
  Utils.assert(Utils.addressCheck(paramObj.from), 'From address is invalid.');
  Utils.assert(Utils.addressCheck(paramObj.to), 'To address is invalid.');

  _transFrom(paramObj.id, paramObj.from, paramObj.to);
  return;
}

function transferFrom(paramObj) {

  Utils.assert(paramObj.from !== undefined && paramObj.from.length > 0, 'Param obj has no from.');
  Utils.assert(paramObj.to !== undefined && paramObj.to.length > 0, 'Param obj has no to.');
  Utils.assert(paramObj.id !== undefined && paramObj.id.length > 0, 'Param obj has no id.');
  Utils.assert(Utils.addressCheck(paramObj.from), 'From address is invalid.');
  Utils.assert(Utils.addressCheck(paramObj.to), 'To address is invalid.');

  _transFrom(paramObj.id, paramObj.from, paramObj.to);
  return;
}

function approve(paramObj) {

  Utils.assert(paramObj.approved !== undefined && paramObj.approved.length >= 0, 'Param obj has no approved.');
  Utils.assert(paramObj.id !== undefined && paramObj.id.length > 0, 'Param obj has no id.');
  Utils.assert(Utils.addressCheck(paramObj.approved) || paramObj.approved === '', 'Approved address is invalid.');
  Utils.assert(Chain.msg.sender !== paramObj.approved, 'Approved cannot equal msg sender.');
  Utils.assert(checkAssetExsit(paramObj.id), 'Check nft not exist.');
  let owner = getAssetOwner(paramObj.id);
  Utils.assert(owner === Chain.msg.sender, 'No privilege to trans.');

  _approve(owner, paramObj.id, paramObj.approved);
  return;
}

function setApprovalForAll(paramObj) {

  Utils.assert(paramObj.operator !== undefined && paramObj.operator.length > 0, 'Param obj has no operator.');
  Utils.assert(paramObj.approved !== undefined, 'Param obj has no approved.');
  Utils.assert(paramObj.approved === true || paramObj.approved === false, 'Approved must be true or false.');
  Utils.assert(Utils.addressCheck(paramObj.operator), 'Operator address is invalid.');
  Utils.assert(Chain.msg.sender !== paramObj.operator, 'Operator cannot equal msg sender.');

  saveApproveAll(Chain.msg.sender, paramObj.operator, paramObj.approved);

  Chain.tlog('ApprovalForAll', Chain.msg.sender, paramObj.operator, paramObj.approved);
  return;
}

function mint(paramObj) {

  Utils.assert(paramObj.to !== undefined && paramObj.to.length > 0, 'Param obj has no to.');
  Utils.assert(paramObj.uri !== undefined && paramObj.uri.length > 0, 'Param obj has no uri.');
  Utils.assert(Utils.addressCheck(paramObj.to), 'To address is invalid.');

  let newId = Utils.int64Add(getAssetSupply(), '1');
  let newUserCount = Utils.int64Add(getAssetUserCount(paramObj.to), '1');
  saveAsset(newId, Chain.msg.sender, paramObj.uri);
  saveAssetOwner(newId, paramObj.to);
  saveAssetUserCount(paramObj.to, newUserCount);
  saveAssetSupply(newId);

  Chain.tlog('Transfer', '0x', paramObj.to, newId);
  return;
}

function burn(paramObj) {
  Utils.assert(paramObj.id !== undefined && paramObj.id.length > 0, 'Param obj has no id.');
  Utils.assert(checkAssetExsit(paramObj.id), 'Check nft not exist.');

  let owner = getAssetOwner(paramObj.id);
  Utils.assert(owner === Chain.msg.sender || getApproveSingle(paramObj.id) === Chain.msg.sender || getApproveAll(owner, Chain.msg.sender), 'No privilege to burn.');

  saveAssetUserCount(owner, Utils.int64Sub(getAssetUserCount(owner), '1'));

  saveAssetOwner(paramObj.id, '');

  _approve(owner, paramObj.id, '');

  Chain.tlog('Transfer', owner, '0x', paramObj.id);
  return;
}

function balanceOf(paramObj) {

  Utils.assert(paramObj.owner !== undefined && paramObj.owner.length > 0, 'Param obj has no owner');

  let result = {};
  result.count = getAssetUserCount(paramObj.owner);
  return result;
}

function ownerOf(paramObj) {

  Utils.assert(paramObj.id !== undefined && paramObj.id.length > 0, 'Param obj has no id.');

  let result = {};
  result.address = getAssetOwner(paramObj.id);
  return result;
}

function getApproved(paramObj) {

  Utils.assert(paramObj.id !== undefined && paramObj.id.length > 0, 'Param obj has no id.');

  let result = {};
  result.address = getApproveSingle(paramObj.id);
  return result;
}

function isApprovedForAll(paramObj) {

  Utils.assert(paramObj.owner !== undefined && paramObj.owner.length > 0, 'Param obj has no owner.');
  Utils.assert(paramObj.operator !== undefined && paramObj.operator.length > 0, 'Param obj has no operator.');

  let result = {};
  result.approved = getApproveAll(paramObj.owner, paramObj.operator);
  return result;
}

function contractInfo() {
  return loadObj(CONTRACT_PRE);
}

function tokenURI(paramObj) {
  Utils.assert(paramObj.id !== undefined && paramObj.id.length > 0, 'Param obj has no id.');
  let result = {};
  result.uri = loadObj(getKey(ASSET_PRE, paramObj.id)).uri;
  return result;
}

function totalSupply(paramObj) {
  let result = {};
  result.count = getAssetSupply();
  return result;
}

function init(input_str) {
  let paramObj = JSON.parse(input_str).params;
  Utils.assert(paramObj.name !== undefined && paramObj.name.length > 0, 'Param obj has no name.');
  Utils.assert(paramObj.symbol !== undefined && paramObj.symbol.length > 0, 'Param obj has no symbol.');
  Utils.assert(paramObj.describe !== undefined && paramObj.describe.length > 0, 'Param obj has no describe.');
  Utils.assert(paramObj.protocol !== undefined && paramObj.protocol.length > 0 && paramObj.protocol.toLowerCase() === ZTP_PROTOCOL, 'Param obj protocol must be ZTP721.');
  Utils.assert(paramObj.version !== undefined && paramObj.version.length > 0, 'Param obj has no version.');
  Utils.assert(paramObj.url !== undefined && paramObj.url.length > 0, 'Param obj has no url.');

  saveObj(CONTRACT_PRE, paramObj);
  return;
}

function main(input_str) {
  let funcList = {
    'safeTransferFrom': safeTransferFrom,
    'transferFrom': transferFrom,
    'approve': approve,
    'setApprovalForAll': setApprovalForAll,
    'mint': mint,
    'burn': burn
  };
  let inputObj = JSON.parse(input_str);
  Utils.assert(funcList.hasOwnProperty(inputObj.method) && typeof funcList[inputObj.method] === 'function', 'Cannot find func:' + inputObj.method);
  funcList[inputObj.method](inputObj.params);
}

function query(input_str) {
  let funcList = {
    'balanceOf': balanceOf,
    'ownerOf': ownerOf,
    'getApproved': getApproved,
    'isApprovedForAll': isApprovedForAll,
    'contractInfo': contractInfo,
    'tokenURI': tokenURI,
    'totalSupply': totalSupply
  };
  let inputObj = JSON.parse(input_str);
  Utils.assert(funcList.hasOwnProperty(inputObj.method) && typeof funcList[inputObj.method] === 'function', 'Cannot find func:' + inputObj.method);
  return JSON.stringify(funcList[inputObj.method](inputObj.params));
}

Use Case

Digital Art and Collectibles

Artists and creators could tokenize their artwork and collectibles as ZTP721 tokens, allowing them to prove ownership, authenticity, and scarcity. Platforms on the ZETRIX blockchain could facilitate the buying, selling, and trading of digital art and collectibles as ZTP721 tokens.

Gaming and Virtual Real Estate

ZTP721 tokens could be used in the gaming industry to represent in-game assets, characters, and virtual real estate. Games built on the ZETRIX blockchain could leverage ZTP721 tokens to create unique, tradable assets within their virtual worlds, allowing players to buy, sell, and trade in-game items and properties.

Tokenized Real-World Assets

The ZTP721 standard could be used to tokenize real-world assets such as real estate, luxury goods, and collectible items on the ZETRIX blockchain. By representing these assets as NFTs (ZTP721 tokens), ownership could be easily transferred, fractional ownership could be established, and the provenance of the asset could be verified, leading to increased liquidity and accessibility to traditional asset classes.

Lending and Borrowing Platforms

TP721 tokens could be used to create digital event tickets and access passes on the ZETRIX blockchain, providing a secure and transparent way to manage ticket sales, prevent counterfeiting, and verify attendance. This could streamline the ticketing process, reduce fraud, and enhance the overall event experience for organizers and attendees.

Digital Identity and Credentials

The ZTP721 standard could be used to create digital identities and credentials, such as academic diplomas, professional certifications, and personal identification documents on the ZETRIX blockchain. By storing these credentials as NFTs (ZTP721 tokens), individuals could have greater control over their personal data, share verifiable proofs of their achievements and qualifications, and easily transfer ownership of their credentials when necessary.

Supply Chain and Provenance Tracking

ZTP721 tokens could be used to track the provenance and authenticity of goods throughout the supply chain on the ZETRIX blockchain. By creating NFTs for each product or batch of products, companies could monitor the movement and ownership of their goods, verify the authenticity of products, and combat counterfeit products effectively.

Music and Intellectual Property Rights

Musicians, artists, and content creators could tokenize their music, artworks, and intellectual property rights as ZTP721 tokens on the ZETRIX blockchain. This would enable them to monetize their creations through the sale of NFTs, establish ownership and copyright protections, and receive royalties automatically whenever their works are resold or used by others.

Last updated