A class of unique tokens. NRC721 is a free, open standard that describes how to build unique tokens on the Nebulas blockchain. While all tokens are fungible (every token is the same as every other token) in NRC20, NRC721 tokens are all unique.
NRC721 defines a minimum interface a smart contract must implement to allow unique tokens to be managed, owned, and traded. It does not mandate a standard for token metadata or restrict adding supplemental functions.
Returns the name of the token - e.g. "MyToken"
.
// returns string, the name of the token.
function name()
Returns the number of tokens owned by _owner
.
// returns The number of NFTs owned by `_owner`, possibly zero
function balanceOf(_owner)
Returns the address of the owner of the tokens.
// returns the address of the owner of the tokens
function ownerOf(_tokenId)
Transfers the ownership of an token from one address to another address. The caller is responsible to confirm that _to
is capable of receiving token or else they may be permanently lost.
Transfers _tokenId
tokenId from address _from
to address _to
, and MUST fire the Transfer
event.
The function SHOULD throws
unless the transaction from is the current owner, an authorized operator, or the approved address for this token. throws
if _from
is not the current owner. throws
if _to
is the contract address. throws
if _tokenId
is not a valid token.
// if transfer fail, throw error
function transferFrom(_from, _to, _tokenId)
Set or reaffirm the approved address for an token.
The function SHOULD throws
unless transcation from is the current token owner, or an authorized operator of the current owner.
function approve(_to, _tokenId)
Enable or disable approval for a third party (operator
) to manage all of transaction from's assets.
_operator
Address to add to the set of authorized operators.
_approved
True if the operators is approved, false to revoke approval
function setApprovalForAll(_operator, _approved)
Get the approved address for a single token.
// return the approved address for this token, or "" if there is none
function getApproved(_tokenId)
Query if an address is an authorized operator for another address.
// return true if `_operator` is an approved operator for `_owner`, false otherwise
function isApprovedForAll(_owner, _operator)
This emits when ownership of any token changes by any mechanism.
function transferEvent: function(status, from, to, value)
This emits when the approved address for an token is changed or reaffirmed.
When a Transfer event emits, this also indicates that the approved address for that token (if any) is reset to none
function approveEvent: function(status, from, spender, value)
'use strict';
var Operator = function (obj) {
this.operator = {};
this.parse(obj);
};
Operator.prototype = {
toString: function () {
return JSON.stringify(this.operator);
},
parse: function (obj) {
if (typeof obj != "undefined") {
var data = JSON.parse(obj);
for (var key in data) {
this.operator[key] = data[key];
}
}
},
get: function (key) {
return this.operator[key];
},
set: function (key, value) {
this.operator[key] = value;
}
};
var StandardToken = function () {
LocalContractStorage.defineProperties(this, {
_name: null,
});
LocalContractStorage.defineMapProperties(this, {
"tokenOwner": null,
"ownedTokensCount": {
parse: function (value) {
return new BigNumber(value);
},
stringify: function (o) {
return o.toString(10);
}
},
"tokenApprovals": null,
"operatorApprovals": {
parse: function (value) {
return new Operator(value);
},
stringify: function (o) {
return o.toString();
}
},
});
};
StandardToken.prototype = {
init: function (name) {
this._name = name;
},
name: function () {
return this._name;
},
balanceOf: function (_owner) {
var balance = this.ownedTokensCount.get(_owner);
if (balance instanceof BigNumber) {
return balance.toString(10);
} else {
return "0";
}
},
ownerOf: function (_tokenId) {
return this.tokenOwner.get(_tokenId);
},
approve: function (_to, _tokenId) {
var from = Blockchain.transaction.from;
var owner = this.ownerOf(_tokenId);
if (_to == owner) {
throw new Error("invalid address in approve.");
}
if (owner == from || this.isApprovedForAll(owner, from)) {
this.tokenApprovals.set(_tokenId, _to);
this.approveEvent(true, owner, _to, _tokenId);
} else {
throw new Error("permission denied in approve.");
}
},
getApproved: function (_tokenId) {
return this.tokenApprovals.get(_tokenId);
},
setApprovalForAll: function(_to, _approved) {
var from = Blockchain.transaction.from;
if (from == _to) {
throw new Error("invalid address in setApprovalForAll.");
}
var operator = this.operatorApprovals.get(from) || new Operator();
operator.set(_to, _approved);
this.operatorApprovals.set(from, operator);
},
isApprovedForAll: function(_owner, _operator) {
var operator = this.operatorApprovals.get(_owner);
if (operator != null) {
if (operator.get(_operator) === "true") {
return true;
} else {
return false;
}
}
},
isApprovedOrOwner: function(_spender, _tokenId) {
var owner = this.ownerOf(_tokenId);
return _spender == owner || this.getApproved(_tokenId) == _spender || this.isApprovedForAll(owner, _spender);
},
transferFrom: function (_from, _to, _tokenId) {
var from = Blockchain.transaction.from;
var contractAddress = Blockchain.transaction.to;
if (contractAddress == _to) {
throw new Error("Forbidden to transfer money to a smart contract address");
}
if (this.isApprovedOrOwner(from, _tokenId)) {
this.clearApproval(_from, _tokenId);
this.removeTokenFrom(_from, _tokenId);
this.addTokenTo(_to, _tokenId);
this.transferEvent(true, _from, _to, _tokenId);
} else {
throw new Error("permission denied in transferFrom.");
}
},
clearApproval: function (_owner, _tokenId) {
var owner = this.ownerOf(_tokenId);
if (_owner != owner) {
throw new Error("permission denied in clearApproval.");
}
this.tokenApprovals.del(_tokenId);
},
removeTokenFrom: function(_from, _tokenId) {
if (_from != this.ownerOf(_tokenId)) {
throw new Error("permission denied in removeTokenFrom.");
}
var tokenCount = this.ownedTokensCount.get(_from);
if (tokenCount.lt(1)) {
throw new Error("Insufficient account balance in removeTokenFrom.");
}
this.ownedTokensCount.set(_from, tokenCount-1);
},
addTokenTo: function(_to, _tokenId) {
this.tokenOwner.set(_tokenId, _to);
var tokenCount = this.ownedTokensCount.get(_to) || new BigNumber(0);
this.ownedTokensCount.set(_to, tokenCount+1);
},
mint: function(_to, _tokenId) {
this.addTokenTo(_to, _tokenId);
this.transferEvent(true, "", _to, _tokenId);
},
burn: function(_owner, _tokenId) {
this.clearApproval(_owner, _tokenId);
this.removeTokenFrom(_owner, _tokenId);
this.transferEvent(true, _owner, "", _tokenId);
},
transferEvent: function (status, _from, _to, _tokenId) {
Event.Trigger(this.name(), {
Status: status,
Transfer: {
from: _from,
to: _to,
tokenId: _tokenId
}
});
},
approveEvent: function (status, _owner, _spender, _tokenId) {
Event.Trigger(this.name(), {
Status: status,
Approve: {
owner: _owner,
spender: _spender,
tokenId: _tokenId
}
});
}
};
module.exports = StandardToken;