From bc0e08e5d28547f8abe4e9116942c5e432c63b19 Mon Sep 17 00:00:00 2001 From: marco Date: Tue, 18 Jul 2023 11:36:40 +0800 Subject: [PATCH 1/7] merge extensions to md file --- neps/nep-0245.md | 373 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 371 insertions(+), 2 deletions(-) diff --git a/neps/nep-0245.md b/neps/nep-0245.md index 82c11c5ae..e2fc5ce80 100644 --- a/neps/nep-0245.md +++ b/neps/nep-0245.md @@ -1,7 +1,7 @@ --- NEP: 245 Title: Multi Token Standard -Author: Zane Starr , @riqi, @jriemann, @marcos.sun +Author: Zane Starr , @riqi, @jriemann, @marco DiscussionsTo: https://github.com/near/NEPs/discussions/246 Status: Review Type: Standards Track @@ -64,7 +64,8 @@ To recap, we choose to create this standard, to improve interoperability, develo ```ts // The base structure that will be returned for a token. If contract is using // extensions such as Approval Management, Enumeration, Metadata, or other -// attributes may be included in this structure. +// attributes may be included in this structure but must be marked with null +// to indicate themselves as optional. type Token = { token_id: string, owner_id: string | null @@ -393,6 +394,373 @@ function mt_on_transfer( ): Promise; ``` +## Metadata + +The goal is to keep the metadata future-proof as well as lightweight. This will be important to dApps needing additional information about multi token properties, and broadly compatible with other token standards such that the [NEAR Rainbow Bridge](https://near.org/blog/eth-near-rainbow-bridge/) can move tokens between chains. + +The primary value of tokens comes from their metadata. While the [core standard](Core.md) provides the minimum interface that can be considered a multi token, most artists, developers, and dApps will want to associate more data with each token, and will want a predictable way to interact with any MT's metadata. + +NEAR's unique [storage staking](https://docs.near.org/concepts/storage/storage-staking) approach makes it feasible to store more data on-chain than other blockchains. This standard leverages this strength for common metadata attributes, and provides a standard way to link to additional offchain data to support rapid community experimentation. + +This standard also provides a `spec` version. This makes it easy for consumers of Multi Tokens, such as marketplaces, to know if they support all the features of a given token. + +Prior art: + +- NEAR's [Fungible Token Metadata Standard](../FungibleToken/Metadata.md) +- NEAR's [Non-Fungible Token Metadata Standard](../NonFungibleToken/Metadata.md) +- Discussion about NEAR's complete NFT standard: #171 +- Discussion about NEAR's complete Multi Token standard: #245 + +### Metadata Interface + +Metadata applies at both the class level (`MTBaseTokenMetadata`) and the specific instance level (`MTTokenMetadata`). The relevant metadata for each: + +```ts + +type MTContractMetadata = { + spec: string, // required, essentially a version like "mt-1.0.0" + name: string, // required Zoink's Digitial Sword Collection +} + +type MTBaseTokenMetadata = { + name: string, // required, ex. "Silver Swords" or "Metaverse 3" + id: string, // required a unique identifier for the metadata + symbol: string|null, // required, ex. "MOCHI" + icon: string|null, // Data URL + decimals: string|null // number of decimals for the token useful for FT related tokens + 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 + copies: number|null, // number of copies of this set of metadata in existence when token was minted. + reference_hash: string|null, // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. +} + +type MTTokenMetadata = { + title: string|null, // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055" + description: string|null, // free-form description + media: string|null, // URL to associated media, preferably to decentralized, content-addressed storage + media_hash: string|null, // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included. + issued_at: string|null, // When token was issued or minted, Unix epoch in milliseconds + expires_at: string|null, // When token expires, Unix epoch in milliseconds + starts_at: string|null, // When token starts being valid, Unix epoch in milliseconds + updated_at: string|null, // When token was last updated, Unix epoch in milliseconds + extra: string|null, // Anything extra the MT wants to store on-chain. Can be stringified JSON. + reference: string|null, // URL to an off-chain JSON file with more info. + reference_hash: string|null // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. +} + +type MTTokenMetadataAll = { + base: MTBaseTokenMetadata + token: MTTokenMetadata +} +``` + +A new set of functions MUST be supported on the MT contract: + +```ts +// Returns the top-level contract level metadtata +function mt_metadata_contract(): MTContractMetadata {} +function mt_metadata_token_all(token_ids: string[]): MTTokenMetadataAll[] +function mt_metadata_token_by_token_id(token_ids: string[]): MTTokenMetadata[] +function mt_metadata_base_by_token_id(token_ids: string[]): MTBaseTokenMetadata[] +function mt_metadata_base_by_metadata_id(base_metadata_ids: string[]): MTBaseTokenMetadata[] + +``` + +A new attribute MUST be added to each `Token` struct: + +```diff + type Token = { + token_id: string, + owner_id: string | null, ++ token_metadata: MTTokenMetadata | null, ++ base_metadata_id: string | null + } +``` +### An implementing contract MUST include the following fields on-chain +For `MTContractMetadata`: +- `spec`: a string that MUST be formatted `mt-1.0.0` to indicate that a Multi Token contract adheres to the current versions of this Metadata spec. This will allow consumers of the Multi Token to know if they support the features of a given contract. +- `name`: the human-readable name of the contract. + +### An implementing contract must include the following fields on-chain +For `MTBaseTokenMetadata`: +- `name`: the human-readable name of the Token. +- `base_uri`: Centralized gateway known to have reliable access to decentralized storage assets referenced by `reference` or `media` URLs. Can be used by other frontends for initial retrieval of assets, even if these frontends then replicate the data to their own decentralized nodes, which they are encouraged to do. + +### An implementing contract MAY include the following fields on-chain +For `MTBaseTokenMetadata`: +- `symbol`: the abbreviated symbol of the contract, like MOCHI or MV3 +- `icon`: a small image associated with this contract. Encouraged to be a [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs), to help consumers display it quickly while protecting user data. Recommendation: use [optimized SVG](https://codepen.io/tigt/post/optimizing-svgs-in-data-uris), which can result in high-resolution images with only 100s of bytes of [storage cost](https://docs.near.org/concepts/storage/storage-staking). (Note that these storage costs are incurred to the contract deployer, but that querying these icons is a very cheap & cacheable read operation for all consumers of the contract and the RPC nodes that serve the data.) Recommendation: create icons that will work well with both light-mode and dark-mode websites by either using middle-tone color schemes, or by [embedding `media` queries in the SVG](https://timkadlec.com/2013/04/media-queries-within-svg/). +- `reference`: a link to a valid JSON file containing various keys offering supplementary details on the token. Example: "/ipfs/QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm", etc. If the information given in this document conflicts with the on-chain attributes, the values in `reference` shall be considered the source of truth. +- `reference_hash`: the base64-encoded sha256 hash of the JSON file contained in the `reference` field. This is to guard against off-chain tampering. +- `copies`: The number of tokens with this set of metadata or `media` known to exist at time of minting. Supply is a more accurate current reflection. + +For `MTTokenMetadata`: + +- `title`: The title of this specific token. +- `description`: A longer description of the token. +- `media`: URL to associated media. Preferably to decentralized, content-addressed storage. +- `media_hash`: the base64-encoded sha256 hash of content referenced by the `media` field. This is to guard against off-chain tampering. +- `copies`: The number of tokens with this set of metadata or `media` known to exist at time of minting. +- `issued_at`: Unix epoch in milliseconds when token was issued or minted (an unsigned 32-bit integer would suffice until the year 2106) +- `expires_at`: Unix epoch in milliseconds when token expires +- `starts_at`: Unix epoch in milliseconds when token starts being valid +- `updated_at`: Unix epoch in milliseconds when token was last updated +- `extra`: anything extra the MT wants to store on-chain. Can be stringified JSON. +- `reference`: URL to an off-chain JSON file with more info. +- `reference_hash`: Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. + +For `MTTokenMetadataAll `: + +- `base`: The base metadata that corresponds to `MTBaseTokenMetadata` for the token. +- `token`: The token specific metadata that corresponds to `MTTokenMetadata`. + +## Enumeration + +Standard interfaces for counting & fetching tokens, for an entire Multi Token contract or for a given owner. + +Apps such as marketplaces and wallets need a way to show all tokens owned by a given account and to show statistics about all tokens for a given contract. This extension provides a standard way to do so. + +While some Multi Token contracts may forego this extension to save [storage] costs, this requires apps to have custom off-chain indexing layers. This makes it harder for apps to integrate with such Multi Token contracts. Apps which integrate only with Multi Token Standards that use the Enumeration extension do not even need a server-side component at all, since they can retrieve all information they need directly from the blockchain. + +Prior art: + +- [ERC-721]'s enumeration extension +- [Non Fungible Token Standard's](../NonFungibleToken/Enumeration.md) enumeration extension + +### Enumeration Interface + +Metadata field is optional if metadata extension is implemented. Includes the base token metadata id and the token_metadata object, that represents the token specific metadata. + +```ts +// Get a list of all tokens +// +// Arguments: +// * `from_index`: a string representing an unsigned 128-bit integer, +// representing the starting index of tokens to return +// * `limit`: the maximum number of tokens to return +// +// Returns an array of `Token` objects, as described in the Core standard, +// and an empty array if there are no tokens +function mt_tokens( + from_index: string|null, // default: "0" + limit: number|null, // default: unlimited (could fail due to gas limit) +): Token[] {} + +// Get list of all tokens owned by a given account +// +// Arguments: +// * `account_id`: a valid NEAR account +// * `from_index`: a string representing an unsigned 128-bit integer, +// representing the starting index of tokens to return +// * `limit`: the maximum number of tokens to return +// +// Returns a paginated list of all tokens owned by this account, and an empty array if there are no tokens +function mt_tokens_for_owner( + account_id: string, + from_index: string|null, // default: 0 + limit: number|null, // default: unlimited (could fail due to gas limit) +): Token[] {} +``` + +The contract must implement the following view methods if using metadata extension: + +```ts +// Get list of all base metadata for the contract +// +// Arguments: +// * `from_index`: a string representing an unsigned 128-bit integer, +// representing the starting index of tokens to return +// * `limit`: the maximum number of tokens to return +// +// Returns an array of `MTBaseTokenMetadata` objects, as described in the Metadata standard, and an empty array if there are no tokens +function mt_tokens_base_metadata_all( + from_index: string | null, + limit: number | null + ): MTBaseTokenMetadata[] +``` + +## Approval Management + +People familiar with [ERC-721] may expect to need an approval management system for basic transfers, where a simple transfer from Alice to Bob requires that Alice first _approve_ Bob to spend one of her tokens, after which Bob can call `transfer_from` to actually transfer the token to himself. + +The previous MT interface includes good support for safe atomic transfers without such complexity. It even provides "transfer and call" functionality (`mt_transfer_call`) which allows specific tokens to be "attached" to a call to a separate contract. For many token workflows, these options may circumvent the need for a full-blown Approval Management system. + +However, some Multi Token developers, marketplaces, dApps, or artists may require greater control. This standard provides a uniform interface allowing token owners to approve other NEAR accounts, whether individuals or contracts, to transfer specific tokens on the owner's behalf. + +Prior art: + +- Ethereum's [ERC-721] +- Ethereum's [ERC-1155] + +### Approval Interface + +The MT contract must implement the following methods: + +```ts +/******************/ +/* CHANGE METHODS */ +/******************/ + +// Add an approved account for a specific set of tokens. +// +// Requirements +// * Caller of the method must attach a deposit of at least 1 yoctoⓃ for +// security purposes +// * Contract MAY require caller to attach larger deposit, to cover cost of +// storing approver data +// * Contract MUST panic if called by someone other than token owner +// * Contract MUST panic if addition would cause `mt_revoke_all` to exceed +// single-block gas limit. See below for more info. +// * Contract MUST increment approval ID even if re-approving an account +// * If successfully approved or if had already been approved, and if `msg` is +// present, contract MUST call `mt_on_approve` on `account_id`. See +// `mt_on_approve` description below for details. +// +// Arguments: +// * `token_ids`: the token ids for which to add an approval +// * `account_id`: the account to add to `approved_account_ids` +// * `amounts`: the number of tokens to approve for transfer, wrapped in quotes and treated +// like an array of string, although the numbers will be stored as an array of +// unsigned integer with 128 bits. + +// * `msg`: optional string to be passed to `mt_on_approve` +// +// Returns void, if no `msg` given. Otherwise, returns promise call to +// `mt_on_approve`, which can resolve with whatever it wants. +function mt_approve( + token_ids: [string], + amounts: [string], + account_id: string, + msg: string|null, +): void|Promise {} + +// Revoke an approved account for a specific token. +// +// Requirements +// * Caller of the method must attach a deposit of 1 yoctoⓃ for security +// purposes +// * If contract requires >1yN deposit on `mt_approve`, contract +// MUST refund associated storage deposit when owner revokes approval +// * Contract MUST panic if called by someone other than token owner +// +// Arguments: +// * `token_ids`: the token for which to revoke approved_account_ids +// * `account_id`: the account to remove from `approvals` +function mt_revoke( + token_ids: [string], + account_id: string +) {} + +// Revoke all approved accounts for a specific token. +// +// Requirements +// * Caller of the method must attach a deposit of 1 yoctoⓃ for security +// purposes +// * If contract requires >1yN deposit on `mt_approve`, contract +// MUST refund all associated storage deposit when owner revokes approved_account_ids +// * Contract MUST panic if called by someone other than token owner +// +// Arguments: +// * `token_ids`: the token ids with approved_account_ids to revoke +function mt_revoke_all(token_ids: [string]) {} + +/****************/ +/* VIEW METHODS */ +/****************/ + +// Check if tokens are approved for transfer by a given account, optionally +// checking an approval_id +// +// Requirements: +// * Contract MUST panic if `approval_ids` is not null and the length of +// `approval_ids` is not equal to `token_ids` +// +// Arguments: +// * `token_ids`: the tokens for which to check an approval +// * `approved_account_id`: the account to check the existence of in `approved_account_ids` +// * `amounts`: specify the positionally corresponding amount for the `token_id` +// that at least must be approved. The number of tokens to approve for transfer, +// wrapped in quotes and treated like an array of string, although the numbers will be +// stored as an array of unsigned integer with 128 bits. +// * `approval_ids`: an optional array of approval IDs to check against +// current approval IDs for given account and `token_ids`. +// +// Returns: +// if `approval_ids` is given, `true` if `approved_account_id` is approved with given `approval_id` +// and has at least the amount specified approved otherwise, `true` if `approved_account_id` +// is in list of approved accounts and has at least the amount specified approved +// finally it returns false for all other states +function mt_is_approved( + token_ids: [string], + approved_account_id: string, + amounts: [string], + approval_ids: number[]|null +): boolean {} + +// Get a the list of approvals for a given token_id and account_id +// +// Arguments: +// * `token_id`: the token for which to check an approval +// * `account_id`: the account to retrieve approvals for +// +// Returns a TokenApproval object, as described in Approval Management standard +function mt_token_approval( + token_id: string, + account_id: string, +): TokenApproval {} + + +// Get a list of all approvals for a given token_id +// +// Arguments: +// * `from_index`: a string representing an unsigned 128-bit integer, +// representing the starting index of tokens to return +// * `limit`: the maximum number of tokens to return +// +// Returns an array of TokenApproval objects, as described in Approval Management standard, and an empty array if there are no approvals +function mt_token_approvals( + token_id: string, + from_index: string|null, // default: "0" + limit: number|null, +): TokenApproval[] {} +``` + +### Approved Account Contract Interface + +If a contract that gets approved to transfer MTs wants to, it can implement `mt_on_approve` to update its own state when granted approval for a token: + +```ts +// Respond to notification that contract has been granted approval for a token. +// +// Notes +// * Contract knows the token contract ID from `predecessor_account_id` +// +// Arguments: +// * `token_ids`: the token_ids to which this contract has been granted approval +// * `amounts`: the ositionally corresponding amount for the token_id +// that at must be approved. The number of tokens to approve for transfer, +// wrapped in quotes and treated like an array of string, although the numbers will be +// stored as an array of unsigned integer with 128 bits. +// * `owner_id`: the owner of the token +// * `approval_ids`: the approval ID stored by NFT contract for this approval. +// Expected to be a number within the 2^53 limit representable by JSON. +// * `msg`: specifies information needed by the approved contract in order to +// handle the approval. Can indicate both a function to call and the +// parameters to pass to that function. +function mt_on_approve( + token_ids: [TokenId], + amounts: [string], + owner_id: string, + approval_ids: [number], + msg: string, +) {} +``` + +Note that the MT contract will fire-and-forget this call, ignoring any return values or errors generated. This means that even if the approved account does not have a contract or does not implement `mt_on_approve`, the approval will still work correctly from the point of view of the MT contract. + +Further note that there is no parallel `mt_on_revoke` when revoking either a single approval or when revoking all. This is partially because scheduling many `mt_on_revoke` calls when revoking all approvals could incur prohibitive [gas fees](https://docs.near.org/docs/concepts/gas). Apps and contracts which cache MT approvals can therefore not rely on having up-to-date information, and should periodically refresh their caches. Since this will be the necessary reality for dealing with `mt_revoke_all`, there is no reason to complicate `mt_revoke` with an `mt_on_revoke` call. + + ## Events NEAR and third-party applications need to track @@ -400,6 +768,7 @@ NEAR and third-party applications need to track Note that applications, including NEAR Wallet, could require implementing additional methods to display tokens correctly such as [`mt_metadata`][MT Metadata] and [`mt_tokens_for_owner`][MT Enumeration]. + ### Events Interface Multi Token Events MUST have `standard` set to `"nep245"`, standard version set to `"1.0.0"`, `event` value is one of `mt_mint`, `mt_burn`, `mt_transfer`, and `data` must be of one of the following relevant types: `MtMintLog[] | MtBurnLog[] | MtTransferLog[]`: From f7bddee5b3a9833bdf3ad2904317d71e9a91857c Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 18 Jul 2023 18:49:41 +0800 Subject: [PATCH 2/7] update metadata part --- neps/nep-0245.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/neps/nep-0245.md b/neps/nep-0245.md index e2fc5ce80..3fbd8491d 100644 --- a/neps/nep-0245.md +++ b/neps/nep-0245.md @@ -422,23 +422,26 @@ type MTContractMetadata = { name: string, // required Zoink's Digitial Sword Collection } +// Identify different FT or NFT category, +// A combination of FT metadata and NFT ContractMetadata type MTBaseTokenMetadata = { + id: string, // required a contract-wise unique identifier for the metadata name: string, // required, ex. "Silver Swords" or "Metaverse 3" - id: string, // required a unique identifier for the metadata symbol: string|null, // required, ex. "MOCHI" icon: string|null, // Data URL - decimals: string|null // number of decimals for the token useful for FT related tokens + decimals: string|null // should be not-null for FT and null for NFT, 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 - copies: number|null, // number of copies of this set of metadata in existence when token was minted. reference_hash: string|null, // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. } +// Similar to TokenMetadata in NFT type MTTokenMetadata = { title: string|null, // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055" description: string|null, // free-form description media: string|null, // URL to associated media, preferably to decentralized, content-addressed storage media_hash: string|null, // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included. + copies: number|null, // number of copies of this set of metadata in existence when token was minted. issued_at: string|null, // When token was issued or minted, Unix epoch in milliseconds expires_at: string|null, // When token expires, Unix epoch in milliseconds starts_at: string|null, // When token starts being valid, Unix epoch in milliseconds @@ -449,8 +452,8 @@ type MTTokenMetadata = { } type MTTokenMetadataAll = { - base: MTBaseTokenMetadata - token: MTTokenMetadata + base: MTBaseTokenMetadata, + token: MTTokenMetadata | null } ``` @@ -459,10 +462,8 @@ A new set of functions MUST be supported on the MT contract: ```ts // Returns the top-level contract level metadtata function mt_metadata_contract(): MTContractMetadata {} -function mt_metadata_token_all(token_ids: string[]): MTTokenMetadataAll[] -function mt_metadata_token_by_token_id(token_ids: string[]): MTTokenMetadata[] -function mt_metadata_base_by_token_id(token_ids: string[]): MTBaseTokenMetadata[] -function mt_metadata_base_by_metadata_id(base_metadata_ids: string[]): MTBaseTokenMetadata[] +function mt_metadata_token(token_ids: string[]): MTTokenMetadataAll[] +function mt_metadata_base(base_metadata_ids: string[]): MTBaseTokenMetadata[] ``` @@ -472,8 +473,8 @@ A new attribute MUST be added to each `Token` struct: type Token = { token_id: string, owner_id: string | null, -+ token_metadata: MTTokenMetadata | null, -+ base_metadata_id: string | null ++ base_metadata_id: string | null, ++ token_metadata: MTTokenMetadata | null } ``` ### An implementing contract MUST include the following fields on-chain @@ -492,7 +493,6 @@ For `MTBaseTokenMetadata`: - `icon`: a small image associated with this contract. Encouraged to be a [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs), to help consumers display it quickly while protecting user data. Recommendation: use [optimized SVG](https://codepen.io/tigt/post/optimizing-svgs-in-data-uris), which can result in high-resolution images with only 100s of bytes of [storage cost](https://docs.near.org/concepts/storage/storage-staking). (Note that these storage costs are incurred to the contract deployer, but that querying these icons is a very cheap & cacheable read operation for all consumers of the contract and the RPC nodes that serve the data.) Recommendation: create icons that will work well with both light-mode and dark-mode websites by either using middle-tone color schemes, or by [embedding `media` queries in the SVG](https://timkadlec.com/2013/04/media-queries-within-svg/). - `reference`: a link to a valid JSON file containing various keys offering supplementary details on the token. Example: "/ipfs/QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm", etc. If the information given in this document conflicts with the on-chain attributes, the values in `reference` shall be considered the source of truth. - `reference_hash`: the base64-encoded sha256 hash of the JSON file contained in the `reference` field. This is to guard against off-chain tampering. -- `copies`: The number of tokens with this set of metadata or `media` known to exist at time of minting. Supply is a more accurate current reflection. For `MTTokenMetadata`: @@ -573,7 +573,7 @@ The contract must implement the following view methods if using metadata extensi // * `limit`: the maximum number of tokens to return // // Returns an array of `MTBaseTokenMetadata` objects, as described in the Metadata standard, and an empty array if there are no tokens -function mt_tokens_base_metadata_all( +function mt_basetoken_metadata_all( from_index: string | null, limit: number | null ): MTBaseTokenMetadata[] From f2fda2ea3000ce0ebf44fb8957ffb36d5ea2c9dd Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 10 Oct 2023 17:48:03 +0800 Subject: [PATCH 3/7] refactor metadata --- neps/nep-0245.md | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/neps/nep-0245.md b/neps/nep-0245.md index 3fbd8491d..461e08ad4 100644 --- a/neps/nep-0245.md +++ b/neps/nep-0245.md @@ -68,7 +68,8 @@ To recap, we choose to create this standard, to improve interoperability, develo // to indicate themselves as optional. type Token = { token_id: string, - owner_id: string | null + owner_id: string | null, // not null if the token is a NFT + metadata: MTTokenMetadataAll | null // not null if Metadata feature is enabled } /******************/ @@ -106,7 +107,7 @@ type Token = { function mt_transfer( receiver_id: string, token_id: string, - amount: string, + amount: string, // this value is ignored if the token is an NFT. approval: [owner_id: string, approval_id: number]|null, memo: string|null, ) {} @@ -147,7 +148,7 @@ function mt_transfer( function mt_batch_transfer( receiver_id: string, token_ids: string[], - amounts: string[], + amounts: string[], // an item in the list would be ignored if the corresponding token is an NFT. approvals: ([owner_id: string, approval_id: number]| null)[]| null, memo: string|null, ) {} @@ -197,7 +198,7 @@ function mt_batch_transfer( function mt_transfer_call( receiver_id: string, token_id: string, - amount: string, + amount: string, // this value is ignored if the token is an NFT. approval: [owner_id: string, approval_id: number]|null, memo: string|null, msg: string, @@ -254,7 +255,7 @@ function mt_transfer_call( function mt_batch_transfer_call( receiver_id: string, token_ids: string[], - amounts: string[], + amounts: string[], // an item in the list would be ignored if the corresponding token is an NFT. approvals: ([owner_id: string, approval_id: number]|null)[] | null, memo: string|null, msg: string, @@ -350,7 +351,8 @@ function mt_resolve_transfer( sender_id: string, receiver_id: string, token_ids: string[], - approvals: (null | [owner_id: string, approval_id: number, amount: string][]) []| null + amounts: string[], + approvals: (null | [owner_id: string, approval_id: number][]) []| null ):string[] {} ``` @@ -387,7 +389,7 @@ Contracts which want to make use of `mt_transfer_call` and `mt_batch_transfer_ca function mt_on_transfer( sender_id: string, - previous_owner_ids: string[], + previous_owner_ids: string[] | null, token_ids: string[], amounts: string[], msg: string, @@ -451,8 +453,11 @@ type MTTokenMetadata = { reference_hash: string|null // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. } +// For base info, either base_id or base would be included, depends on implementation. +// Suggest to return base to save client from extral query on base info. type MTTokenMetadataAll = { - base: MTBaseTokenMetadata, + base_id: string | null, // id in type MTBaseTokenMetadata + base: MTBaseTokenMetadata | null, token: MTTokenMetadata | null } ``` @@ -467,16 +472,7 @@ function mt_metadata_base(base_metadata_ids: string[]): MTBaseTokenMetadata[] ``` -A new attribute MUST be added to each `Token` struct: -```diff - type Token = { - token_id: string, - owner_id: string | null, -+ base_metadata_id: string | null, -+ token_metadata: MTTokenMetadata | null - } -``` ### An implementing contract MUST include the following fields on-chain For `MTContractMetadata`: - `spec`: a string that MUST be formatted `mt-1.0.0` to indicate that a Multi Token contract adheres to the current versions of this Metadata spec. This will allow consumers of the Multi Token to know if they support the features of a given contract. From 342061cf279e4d2bbd1a9dd91d81b110cc0e7368 Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 10 Oct 2023 19:49:42 +0800 Subject: [PATCH 4/7] fix lint issue --- neps/nep-0245.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/neps/nep-0245.md b/neps/nep-0245.md index 461e08ad4..68898b433 100644 --- a/neps/nep-0245.md +++ b/neps/nep-0245.md @@ -474,17 +474,23 @@ function mt_metadata_base(base_metadata_ids: string[]): MTBaseTokenMetadata[] ### An implementing contract MUST include the following fields on-chain + For `MTContractMetadata`: + - `spec`: a string that MUST be formatted `mt-1.0.0` to indicate that a Multi Token contract adheres to the current versions of this Metadata spec. This will allow consumers of the Multi Token to know if they support the features of a given contract. - `name`: the human-readable name of the contract. ### An implementing contract must include the following fields on-chain + For `MTBaseTokenMetadata`: + - `name`: the human-readable name of the Token. - `base_uri`: Centralized gateway known to have reliable access to decentralized storage assets referenced by `reference` or `media` URLs. Can be used by other frontends for initial retrieval of assets, even if these frontends then replicate the data to their own decentralized nodes, which they are encouraged to do. ### An implementing contract MAY include the following fields on-chain + For `MTBaseTokenMetadata`: + - `symbol`: the abbreviated symbol of the contract, like MOCHI or MV3 - `icon`: a small image associated with this contract. Encouraged to be a [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs), to help consumers display it quickly while protecting user data. Recommendation: use [optimized SVG](https://codepen.io/tigt/post/optimizing-svgs-in-data-uris), which can result in high-resolution images with only 100s of bytes of [storage cost](https://docs.near.org/concepts/storage/storage-staking). (Note that these storage costs are incurred to the contract deployer, but that querying these icons is a very cheap & cacheable read operation for all consumers of the contract and the RPC nodes that serve the data.) Recommendation: create icons that will work well with both light-mode and dark-mode websites by either using middle-tone color schemes, or by [embedding `media` queries in the SVG](https://timkadlec.com/2013/04/media-queries-within-svg/). - `reference`: a link to a valid JSON file containing various keys offering supplementary details on the token. Example: "/ipfs/QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm", etc. If the information given in this document conflicts with the on-chain attributes, the values in `reference` shall be considered the source of truth. From 6be0d35ab3ce598a3ef5215ae78bfa739d95baa1 Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 10 Oct 2023 21:33:55 +0800 Subject: [PATCH 5/7] peer reviewed --- neps/nep-0245.md | 104 +++++++++++++++-------------------------------- 1 file changed, 32 insertions(+), 72 deletions(-) diff --git a/neps/nep-0245.md b/neps/nep-0245.md index 68898b433..7fd8e34e5 100644 --- a/neps/nep-0245.md +++ b/neps/nep-0245.md @@ -62,10 +62,7 @@ To recap, we choose to create this standard, to improve interoperability, develo ### MT Interface ```ts -// The base structure that will be returned for a token. If contract is using -// extensions such as Approval Management, Enumeration, Metadata, or other -// attributes may be included in this structure but must be marked with null -// to indicate themselves as optional. +// The base structure that will be returned for a token. type Token = { token_id: string, owner_id: string | null, // not null if the token is a NFT @@ -94,21 +91,17 @@ type Token = { // * `token_id`: the token to transfer // * `amount`: the number of tokens to transfer, wrapped in quotes and treated // like a string, although the number will be stored as an unsigned integer -// with 128 bits. -// * `approval` (optional): is a tuple of [`owner_id`,`approval_id`]. -// `owner_id` is the valid Near account that owns the tokens. -// `approval_id` is the expected approval ID. A number smaller than +// with 128 bits. This value is ignored if the token is an NFT. +// * `approval_id` (optional): the expected approval ID. A number smaller than // 2^53, and therefore representable as JSON. See Approval Management // standard for full explanation. // * `memo` (optional): for use cases that may benefit from indexing or // providing information for a transfer - - function mt_transfer( receiver_id: string, token_id: string, - amount: string, // this value is ignored if the token is an NFT. - approval: [owner_id: string, approval_id: number]|null, + amount: string, + approval_id: number|null, memo: string|null, ) {} @@ -133,23 +126,16 @@ function mt_transfer( // * `amounts`: the number of tokens to transfer, wrapped in quotes and treated // like an array of strings, although the numbers will be stored as an array of unsigned integer // with 128 bits. -// * `approvals` (optional): is an array of expected `approval` per `token_ids`. +// * `approval_ids` (optional): is an array of expected `approval_id` per `token_ids`. // If a `token_id` does not have a corresponding `approval` then the entry in the array // must be marked null. -// `approval` is a tuple of [`owner_id`,`approval_id`]. -// `owner_id` is the valid Near account that owns the tokens. -// `approval_id` is the expected approval ID. A number smaller than -// 2^53, and therefore representable as JSON. See Approval Management -// standard for full explanation. // * `memo` (optional): for use cases that may benefit from indexing or // providing information for a transfer - - function mt_batch_transfer( receiver_id: string, token_ids: string[], amounts: string[], // an item in the list would be ignored if the corresponding token is an NFT. - approvals: ([owner_id: string, approval_id: number]| null)[]| null, + approval_ids: (number|null)[] | null, memo: string|null, ) {} @@ -182,24 +168,19 @@ function mt_batch_transfer( // * `token_id`: the token to send. // * `amount`: the number of tokens to transfer, wrapped in quotes and treated // like a string, although the number will be stored as an unsigned integer -// with 128 bits. -// * `owner_id`: the valid NEAR account that owns the token -// * `approval` (optional): is a tuple of [`owner_id`,`approval_id`]. -// `owner_id` is the valid Near account that owns the tokens. -// `approval_id` is the expected approval ID. A number smaller than -// 2^53, and therefore representable as JSON. See Approval Management +// with 128 bits. This value is ignored if the token is an NFT. +// * `approval_id` (optional): the expected approval ID. A number smaller than +// 2^53, and therefore representable as JSON. See Approval Management. // * `memo` (optional): for use cases that may benefit from indexing or // providing information for a transfer. // * `msg`: specifies information needed by the receiving contract in // order to properly handle the transfer. Can indicate both a function to // call and the parameters to pass to that function. - - function mt_transfer_call( receiver_id: string, token_id: string, - amount: string, // this value is ignored if the token is an NFT. - approval: [owner_id: string, approval_id: number]|null, + amount: string, + approval_id: number|null, memo: string|null, msg: string, ): Promise {} @@ -236,27 +217,21 @@ function mt_transfer_call( // * `token_ids`: the tokens to transfer // * `amounts`: the number of tokens to transfer, wrapped in quotes and treated // like an array of string, although the numbers will be stored as an array of -// unsigned integer with 128 bits. -// * `approvals` (optional): is an array of expected `approval` per `token_ids`. -// If a `token_id` does not have a corresponding `approval` then the entry in the array -// must be marked null. -// `approval` is a tuple of [`owner_id`,`approval_id`]. -// `owner_id` is the valid Near account that owns the tokens. -// `approval_id` is the expected approval ID. A number smaller than -// 2^53, and therefore representable as JSON. See Approval Management -// standard for full explanation. +// unsigned integer with 128 bits. An item in the list would be ignored if the +// corresponding token is an NFT. +// * `approval_ids` (optional): is an array of expected `approval_id` per `token_ids`. +// If a `token_id` does not have a corresponding `approval` then the entry in the +// array must be marked null. // * `memo` (optional): for use cases that may benefit from indexing or // providing information for a transfer. // * `msg`: specifies information needed by the receiving contract in // order to properly handle the transfer. Can indicate both a function to // call and the parameters to pass to that function. - - function mt_batch_transfer_call( receiver_id: string, token_ids: string[], - amounts: string[], // an item in the list would be ignored if the corresponding token is an NFT. - approvals: ([owner_id: string, approval_id: number]|null)[] | null, + amounts: string[], + approval_ids: (number|null)[] | null, memo: string|null, msg: string, ): Promise {} @@ -288,11 +263,15 @@ function mt_batch_balance_of(account_id: string, token_ids: string[]): string[] // Returns the token supply with the given `token_id` or `null` if no such token exists. // The supply though wrapped in quotes and treated like a string, the number will be stored // as an unsigned integer with 128 bits. +// Arguments: +// * `token_id`: the token to retrieve the balance from function mt_supply(token_id: string): string | null // Returns the token supplies with the given `token_ids`, a string value is returned or `null` // if no such token exists. The supplies though wrapped in quotes and treated like strings, // the numbers will be stored as an unsigned integer with 128 bits. +// Arguments: +// * `token_ids`: the tokens to retrieve the balance from function mt_batch_supply(token_ids: string[]): (string | null)[] ``` @@ -318,7 +297,8 @@ The following behavior is required, but contract authors may name this function // `sender_id` // // Arguments: -// * `sender_id`: the sender of `mt_transfer_call` +// * `sender_id`: the sender of `mt_transfer_call`, pre_owner of all FT. +// * `previous_owner_ids`: the list of original owner of the NFTs, null for FTs. // * `receiver_id`: the `receiver_id` argument given to `mt_transfer_call` // * `token_ids`: the `token_ids` argument given to `mt_transfer_call` // * `amounts`: the `token_ids` argument given to `mt_transfer_call` @@ -328,14 +308,11 @@ The following behavior is required, but contract authors may name this function // `approvals` is an array of expected `approval_list` per `token_ids`. // If a `token_id` does not have a corresponding `approvals_list` then the entry in the // array must be marked null. -// `approvals_list` is an array of triplets of [`owner_id`,`approval_id`,`amount`]. -// `owner_id` is the valid Near account that owns the tokens. +// `approvals_list` is an array of triplets of [`approved_account_id`,`approval_id`]. +// `approved_account_id` is the approved account id, // `approval_id` is the expected approval ID. A number smaller than // 2^53, and therefore representable as JSON. See Approval Management // standard for full explanation. -// `amount`: the number of tokens to transfer, wrapped in quotes and treated -// like a string, although the number will be stored as an unsigned integer -// with 128 bits. // // // @@ -345,14 +322,13 @@ The following behavior is required, but contract authors may name this function // Example: if sender_id calls `mt_transfer_call({ "amounts": ["100"], token_ids: ["55"], receiver_id: "games" })`, // but `receiver_id` only uses 80, `mt_on_transfer` will resolve with `["20"]`, and `mt_resolve_transfer` // will return `["80"]`. - - function mt_resolve_transfer( sender_id: string, + previous_owner_ids: (string|null)[], receiver_id: string, token_ids: string[], amounts: string[], - approvals: (null | [owner_id: string, approval_id: number][]) []| null + approvals: (null | [approved_account_id: string, approval_id: number][]) []| null ):string[] {} ``` @@ -385,11 +361,9 @@ Contracts which want to make use of `mt_transfer_call` and `mt_batch_transfer_ca // is `["10"]` but only 9 are needed, it will return `["1"]`. The amounts returned, // though wrapped in quotes and treated like strings, the numbers will be stored as // an unsigned integer with 128 bits. - - function mt_on_transfer( sender_id: string, - previous_owner_ids: string[] | null, + previous_owner_ids: (string|null)[], token_ids: string[], amounts: string[], msg: string, @@ -511,8 +485,9 @@ For `MTTokenMetadata`: - `reference`: URL to an off-chain JSON file with more info. - `reference_hash`: Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. -For `MTTokenMetadataAll `: +For `MTTokenMetadataAll`: +- `base_id`: The id field in base metadata for the token. - `base`: The base metadata that corresponds to `MTBaseTokenMetadata` for the token. - `token`: The token specific metadata that corresponds to `MTTokenMetadata`. @@ -621,17 +596,12 @@ The MT contract must implement the following methods: // Arguments: // * `token_ids`: the token ids for which to add an approval // * `account_id`: the account to add to `approved_account_ids` -// * `amounts`: the number of tokens to approve for transfer, wrapped in quotes and treated -// like an array of string, although the numbers will be stored as an array of -// unsigned integer with 128 bits. - // * `msg`: optional string to be passed to `mt_on_approve` // // Returns void, if no `msg` given. Otherwise, returns promise call to // `mt_on_approve`, which can resolve with whatever it wants. function mt_approve( token_ids: [string], - amounts: [string], account_id: string, msg: string|null, ): void|Promise {} @@ -680,10 +650,6 @@ function mt_revoke_all(token_ids: [string]) {} // Arguments: // * `token_ids`: the tokens for which to check an approval // * `approved_account_id`: the account to check the existence of in `approved_account_ids` -// * `amounts`: specify the positionally corresponding amount for the `token_id` -// that at least must be approved. The number of tokens to approve for transfer, -// wrapped in quotes and treated like an array of string, although the numbers will be -// stored as an array of unsigned integer with 128 bits. // * `approval_ids`: an optional array of approval IDs to check against // current approval IDs for given account and `token_ids`. // @@ -695,7 +661,6 @@ function mt_revoke_all(token_ids: [string]) {} function mt_is_approved( token_ids: [string], approved_account_id: string, - amounts: [string], approval_ids: number[]|null ): boolean {} @@ -739,10 +704,6 @@ If a contract that gets approved to transfer MTs wants to, it can implement `mt_ // // Arguments: // * `token_ids`: the token_ids to which this contract has been granted approval -// * `amounts`: the ositionally corresponding amount for the token_id -// that at must be approved. The number of tokens to approve for transfer, -// wrapped in quotes and treated like an array of string, although the numbers will be -// stored as an array of unsigned integer with 128 bits. // * `owner_id`: the owner of the token // * `approval_ids`: the approval ID stored by NFT contract for this approval. // Expected to be a number within the 2^53 limit representable by JSON. @@ -751,7 +712,6 @@ If a contract that gets approved to transfer MTs wants to, it can implement `mt_ // parameters to pass to that function. function mt_on_approve( token_ids: [TokenId], - amounts: [string], owner_id: string, approval_ids: [number], msg: string, From ddff09b140311d236a8282ba1c4cf505efd5ffd0 Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 11 Oct 2023 09:39:14 +0800 Subject: [PATCH 6/7] fix references --- neps/nep-0245.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/neps/nep-0245.md b/neps/nep-0245.md index 7fd8e34e5..0f3faa1e9 100644 --- a/neps/nep-0245.md +++ b/neps/nep-0245.md @@ -66,7 +66,8 @@ To recap, we choose to create this standard, to improve interoperability, develo type Token = { token_id: string, owner_id: string | null, // not null if the token is a NFT - metadata: MTTokenMetadataAll | null // not null if Metadata feature is enabled + metadata: MTTokenMetadataAll | null // not null if Metadata extension is enabled + approved_account_ids: Record | null // not null if approval extension is enabled } /******************/ @@ -564,8 +565,10 @@ The previous MT interface includes good support for safe atomic transfers withou However, some Multi Token developers, marketplaces, dApps, or artists may require greater control. This standard provides a uniform interface allowing token owners to approve other NEAR accounts, whether individuals or contracts, to transfer specific tokens on the owner's behalf. -Prior art: +Here, we simply bring approval management concepts form NEP-178 to the MT to serve those NFTs in MT. +Prior art: +- Near's [NEP-178] - Ethereum's [ERC-721] - Ethereum's [ERC-1155] @@ -906,6 +909,7 @@ Please feel free to open pull requests for extending the events standard detaile Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). +[NEP-178]: https://github.com/near/NEPs/blob/master/neps/nep-0178.md [ERC-721]: https://eips.ethereum.org/EIPS/eip-721 [ERC-1155]: https://eips.ethereum.org/EIPS/eip-1155 [storage staking]: https://docs.near.org/concepts/storage/storage-staking From 9af8b0dc94012919bd40593f0c98ba1dd60675a1 Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 11 Oct 2023 09:52:46 +0800 Subject: [PATCH 7/7] fix lint issue --- neps/nep-0245.md | 1 + 1 file changed, 1 insertion(+) diff --git a/neps/nep-0245.md b/neps/nep-0245.md index 0f3faa1e9..df02d793f 100644 --- a/neps/nep-0245.md +++ b/neps/nep-0245.md @@ -568,6 +568,7 @@ However, some Multi Token developers, marketplaces, dApps, or artists may requir Here, we simply bring approval management concepts form NEP-178 to the MT to serve those NFTs in MT. Prior art: + - Near's [NEP-178] - Ethereum's [ERC-721] - Ethereum's [ERC-1155]