DIP721 is an ERC-721 style non-fungible token standard built mirroring its Ethereum counterpart and adapting it to the Internet Computer, maintaining the same interface.
This standard aims to adopt the EIP-721 to the Internet Computer; providing a simple, non-ambiguous, extendable API for the transfer and tracking ownership of NFTs and expanding/building upon the EXT standard with partial compatibility.
- Candid and interface.
Important: This is an an in-development standard, consider it a work in progress as we finalize details in its design and gather feedback from the community.
We'd like to collaborate with the community to provide better token standard implementation for the developers on the IC, if you have some ideas you'd like to discuss, submit an issue, if you want to improve the code or you made a different implementation, make a pull request!
DIP-721 tries to improve on existing Internet Computer standards in the following ways:
- Most NFT projects don't require a multi-token standard, and a simple NFT standard like DIP-721 would suffice. Users of NFTs based on multi-token standards (such as EXT) will be required to pay extra cycle cost compared to DIP-721.
- Most NFT projects don't require the generalization of IC Principals into Ledger Accounts, and avoiding that direction can help reduce the complexity of the API.
- Most current NFT standards on the IC don't yet have proper metadata support for NFTs.
- The ability to track the history of NFT transfers is an important requirement of almost every NFT projects, and it should be a core part of the standard.
- Most NFT projects don't require arbitrarily large token balances, and that can lead to more cycle inefficient implementations.
- DIP-721 closely follows the original EIP-721, and that will make porting existing Ethereum contracts onto the IC more straightforward.
- NFTs projects that choose to implement DIP-721, will be able to implement other NFT token standards without worrying about interface function name collision. This is achieved by DIP-721 postfixing all its interface methods with DIP721.
Every DIP-721 compatible smart contract must implement this interface. All other interfaces are optional. For all interface methods trapping (instead of returning an error) is allowed, but not encouraged.
Count of all NFTs assigned to user
.
balanceOfDip721: (user: principal) -> (nat64) query;
Returns the owner of the NFT associated with token_id
. Returns ApiError.InvalidTokenId, if the token id is invalid.
ownerOfDip721: (token_id: nat64) -> (OwnerResult) query;
Safely transfers token_id token from user from
to user to
.
If to
is zero, then ApiError.ZeroAddress
should be returned. If the caller is neither
the owner, nor an approved operator, nor someone approved with the approveDip721
function, then ApiError.Unauthorized
should be returned. If token_id
is not valid, then ApiError.InvalidTokenId
is returned.
safeTransferFromDip721: (from: principal, to: principal, token_id: nat64) -> (TxReceipt);
Identical to safeTransferFromDip721
except that this function doesn't check whether the to
is a zero address or not.
transferFromDip721: (from: principal, to: principal, token_id: nat64) -> (TxReceipt);
Returns the interfaces supported by this smart contract.
supportedInterfacesDip721: () -> (vec InterfaceId) query;
Returns the logo of the NFT contract.
logoDip721: () -> (LogoResult) query;
Returns the name of the NFT contract.
nameDip721: () -> (text) query;
Returns the symbol of the NFT contract.
symbolDip721: () -> (text) query;
Returns the total current supply of NFT tokens. NFTs that are minted and later burned explictely or sent to the zero address should also count towards totalSupply.
totalSupplyDip721: () -> (nat64) query;
Returns the metadata for token_id
. Returns ApiError.InvalidTokenId
, if the token_id
is invalid.
getMetadataDip721: (token_id: nat64) -> MetadataResult query;
Returns all the metadata for the coins user
owns.
getMetadataForUserDip721: (user: principal) -> (vec ExtendedMetadataResult);
This interface add notification feature for NFT transfers. Implementing this interface might open up other smart contracts to re-entrancy attacks.
Same as safeTransferFromDip721
, but to
is treated as a smart contract that implements
the Notification
interface. Upon successful transfer onDIP721Received
is called with data
.
safeTransferFromNotifyDip721: (from: principal, to: principal, token_id: nat64, data: vec nat8) -> (TxReceipt);
Same as transferFromDip721
, but to
is treated as a smart contract that implements
the Notification
interface. Upon successful transfer onDIP721Received
is called with data
.
transferFromNotifyDip721: (from: principal, to: principal, token_id: nat64, data: vec nat8) -> (TxReceipt);
This interface adds approve functionality to DIP-721 tokens.
Change or reaffirm the approved address for an NFT. The zero address indicates
there is no approved address. Only one user can be approved at a time to manage token_id.
Approvals given by the approveDip721
function are independent from approvals given by the setApprovalForAllDip721
.
Returns ApiError.InvalidTokenId
, if the token_id
is not valid.
Returns ApiError.Unauthorized
in case the caller neither owns
token_id
nor he is an operator approved by a call to
the setApprovalForAll
function.
approveDip721: (user: principal, nat64: token_id) -> (TxReceipt) query;
Enable or disable an operator
to manage all of the tokens for the caller of
this function. Multiple operators can be given permission at the same time.
Approvals granted by the approveDip721
function are independent from the approvals granted
by setApprovalForAll
function. The zero address indicates
there are no approved operators.
setApprovalForAllDip721: (operator: principal, isApproved: bool) -> (TxReceipt);
Returns the approved user for token_id
. Returns ApiError.InvalidTokenId
if the token_id
is invalid.
getApprovedDip721: (token_id: nat64) -> (TxReceipt) query;
Returns true
if the given operator
is an approved operator for all the tokens owned by the caller, returns false
otherwise.
isApprovedForAllDip721: (operator: principal) -> (bool) query;
This interface adds mint functionality to DIP-721 tokens.
Mint an NFT for principal to
. The parameter blobContent
is non zero, if the NFT
contract embeds the NFTs in the smart contract. Implementations are encouraged
to only allow minting by the owner of the smart contract. Returns ApiError.Unauthorized
,
if the caller doesn't have the permission to mint the NFT.
mintDip721: (to: principal, metadata: Metadata, blobContent: blob) -> (MintReceipt);
This interface adds burn functionality to DIP-721 tokens.
Burn an NFT identified by token_id
. Implementations are encouraged to only allow
burning by the owner of the token_id
. Returns ApiError.Unauthorized
,
if the caller doesn't have the permission to burn the NFT. Returns ApiError.InvalidTokenId
,
if the provided token_id doesn't exist.
burnDip721: (token_id: nat64) -> (TxReceipt);
transferFromNotifyDip721
and safeTransferFromNotifyDip721
functions can - upon successfull NFT transfer - notify other smart contracts
that adhere to the following interface.
caller
is the entity that called the transferFromNotifyDip721
or safeTransferFromNotifyDip721
function,
and from
is the previous owner of the NFT.
onDIP721Received: (address caller, address from, uint256 token_id, bytes data) -> ();
type ApiError =
variant {
Unauthorized;
InvalidTokenId;
ZeroAddress;
Other;
};
type OwnerResult =
variant {
Err: ApiError;
Ok: Principal;
};
type TxReceipt =
variant {
Err: ApiError;
Ok: nat;
};
type InterfaceId =
variant {
Approval;
TransactionHistory;
Mint;
Burn;
TransferNotification;
};
type LogoResult =
record {
logo_type: text // MIME type of the logo
data: text // Base64 encoded logo
};
type ExtendedMetadataResult =
record {
metadata_desc: MetadataDesc;
token_id: nat64;
};
type MetadataResult =
variant {
Err: ApiError;
Ok: MetadataDesc;
};
type MetadataDesc = vec MetadataPart;
type MetadataPart =
record {
purpose: MetadataPurpose;
key_val_data: vec MetadataKeyVal;
data: blob;
};
type MetadataPurpose =
variant {
Preview; // used as a preview, can be used as preivew in a wallet
Rendered; // used as a detailed version of the NFT
};
type MetadataKeyVal =
record {
text;
MetadataVal;
};
type MetadataVal =
variant {
TextContent : Text;
BlobContent : blob;
NatContent : Nat;
Nat8Content: Nat8;
Nat16Content: Nat16;
Nat32Content: Nat32;
Nat64Content: Nat64;
};
Uniquely identifies the content of the NFT by its hash fingerprint. This field might be missing unless the NFT is stored on the Web, in which case the content hash is mandatory.
{"contentHash", BlobContent(<hash of the content>)}
{"contentType", TextContent(<MIME type of the NFT>)}
{"locationType", Nat8Content(<type of the location>)}
1 - IPFS storage
2 - Asset canister storage
3 - URI(Web) storage
4 - Embedded in the token contract
{"location", any(<location>)}
// where any(<location>) is one of the followings based on the "locationType"
BlobContent(<IPFS location hash>) - IPFS
TextContent(<PrincipalId of the asset canister>) - Asset canister
TextContent(<URI of the NFT location on the Web>) - URI
location field is missing - Embedded in the token contract
type TxResult =
record {
fee: Nat;
transaction_type: TransactionType;
};
type TransactionType =
variant {
Transfer:
record {
token_id: nat64;
from: principal;
to: principal;
};
TransferFrom:
record {
token_id: nat64;
from: principal;
to: principal;
};
Approve:
record {
token_id: nat64;
from: principal;
to: principal;
};
SetApprovalForAll:
record {
from: principal;
to: principal;
};
Mint:
record {
token_id: nat64;
};
Burn:
record {
token_id: nat64;
};
};
type MintReceipt =
variant {
Err: variant {
Unauthorized;
};
Ok: record {
token_id: nat64; // minted token id
id: nat // transaction id
};
};
type BurnRequest =
record {
token_id: nat64;
}
Implementations are encouraged not to charge any fees when an approved entity
transfers NFTs on the user's behalf, as that entity might have no means for payment.
If any fees needs to be taken for such a transferFromDip721
, safeTransferFromDip721
,
transferFromNotifyDip721
, safeTransferFromNotifyDip721
call,
then it is encouraged to be taken during the call to approveDip721
, setApprovalForAllDip721
from the caller's balance.